diff --git a/.github/workflows/sdk.yaml b/.github/workflows/sdk.yaml index d129d196b..53613534e 100644 --- a/.github/workflows/sdk.yaml +++ b/.github/workflows/sdk.yaml @@ -34,11 +34,14 @@ jobs: - name: Install SDK dependencies run: | rustup target add x86_64-unknown-linux-musl + rustup component add rust-src --toolchain stable-x86_64-unknown-linux-gnu rustup target add aarch64-unknown-linux-musl + rustup component add rust-src --toolchain stable-aarch64-unknown-linux-musl sudo apt update sudo apt install software-properties-common sudo add-apt-repository ppa:deadsnakes/ppa sudo apt install \ + gcc-x86-64-linux-gnu \ gcc-riscv64-unknown-elf \ cmake pandoc device-tree-compiler ninja-build \ texlive-latex-base texlive-latex-recommended \ @@ -90,14 +93,14 @@ jobs: - name: Set version run: echo "SDK_VERSION=$(./ci/dev_version.sh)" >> $GITHUB_ENV - name: Build SDK (x86-64) - run: nix develop --ignore-environment -c bash -c "python3 build_sdk.py --sel4=seL4 --version ${{ env.SDK_VERSION }}-macos-x86-64 --gcc-toolchain-prefix-riscv64 riscv64-none-elf --tool-target-triple=x86_64-apple-darwin" + run: nix develop --ignore-environment -c bash -c "python3 build_sdk.py --sel4=seL4 --version ${{ env.SDK_VERSION }}-macos-x86-64 --gcc-toolchain-prefix-x86_64 x86_64-elf --gcc-toolchain-prefix-riscv64 riscv64-none-elf --tool-target-triple=x86_64-apple-darwin" - name: Upload SDK (x86-64) uses: actions/upload-artifact@v4 with: name: microkit-sdk-${{ env.SDK_VERSION }}-macos-x86-64 path: release/microkit-sdk-${{ env.SDK_VERSION }}-macos-x86-64.tar.gz - name: Build SDK (ARM64) - run: nix develop --ignore-environment -c bash -c "python3 build_sdk.py --sel4=seL4 --version ${{ env.SDK_VERSION }}-macos-aarch64 --gcc-toolchain-prefix-riscv64 riscv64-none-elf --tool-target-triple=aarch64-apple-darwin" + run: nix develop --ignore-environment -c bash -c "python3 build_sdk.py --sel4=seL4 --version ${{ env.SDK_VERSION }}-macos-aarch64 --gcc-toolchain-prefix-x86_64 x86_64-elf --gcc-toolchain-prefix-riscv64 riscv64-none-elf --tool-target-triple=aarch64-apple-darwin" - name: Upload SDK (ARM64) uses: actions/upload-artifact@v4 with: diff --git a/.reuse/dep5 b/.reuse/dep5 index 9c4368638..7351be4a1 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -7,6 +7,7 @@ Files: tool/microkit/Cargo.lock example/rust/Cargo.lock example/rust/support/*.json + initialiser/support/*.json flake.lock CHANGES.md VERSION diff --git a/DEVELOPER.md b/DEVELOPER.md index b49927527..8db59c122 100644 --- a/DEVELOPER.md +++ b/DEVELOPER.md @@ -22,6 +22,7 @@ This section attempts to list the packages or external development tools which a * xmllint * qemu-system-aarch64 * qemu-system-riscv64 +* qemu-system-x86_64 To build the documentation you also need * pandoc @@ -37,13 +38,15 @@ On a Debian-like system you can do: $ curl https://sh.rustup.rs -sSf | sh $ rustup target add x86_64-unknown-linux-musl + $ rustup component add rust-src --toolchain stable-x86_64-unknown-linux-gnu $ sudo apt install build-essential git cmake ninja-build \ device-tree-compiler libxml2-utils \ pandoc texlive-latex-base texlive-latex-recommended \ texlive-fonts-recommended texlive-fonts-extra \ python3.12 python3.12-venv \ - qemu-system-arm qemu-system-misc \ - gcc-riscv64-unknown-elf + qemu-system-arm qemu-system-misc qemu-system-x86 \ + gcc-riscv64-unknown-elf \ + gcc-x86-64-linux-gnu $ python3.12 -m venv pyenv $ ./pyenv/bin/pip install --upgrade pip setuptools wheel $ ./pyenv/bin/pip install -r requirements.txt @@ -72,7 +75,7 @@ On macOS, with the [Homebrew](https://brew.sh) package manager you can do: $ curl https://sh.rustup.rs -sSf | sh $ brew tap riscv-software-src/riscv $ brew install riscv-tools - $ brew install pandoc cmake dtc ninja libxml2 python@3.12 coreutils texlive qemu + $ brew install x86_64-elf-gcc pandoc cmake dtc ninja libxml2 python@3.12 coreutils texlive qemu $ python3.12 -m venv pyenv $ ./pyenv/bin/pip install --upgrade pip setuptools wheel $ ./pyenv/bin/pip install -r requirements.txt @@ -100,15 +103,14 @@ Will give a shell with all the required dependencies to build the SDK. An important note is that Nix's RISC-V cross-compiler will have a different prefix to the default one the SDK build script expects. -When you build the SDK, provide an extra argument `--toolchain-prefix-riscv64 riscv64-none-elf`. +When you build the SDK, provide two extra arguments: +`--gcc-toolchain-prefix-x86_64 x86_64-elf --gcc-toolchain-prefix-riscv64 riscv64-none-elf`. ## seL4 Version The SDK includes a binary of the seL4 kernel. During the SDK build process the kernel is build from source. -At this point in time there are some minor changes to the seL4 kernel required for Microkit. This is temporary, more details can be found [here](https://github.com/seL4/microkit/issues/52). - Please clone seL4 from: https://github.com/seL4/seL4.git diff --git a/README.md b/README.md index b573bc484..5f659b670 100644 --- a/README.md +++ b/README.md @@ -7,11 +7,12 @@ The purpose of the seL4 Microkit is to enable system designers to create static software systems based on the seL4 microkernel. -The seL4 Microkit consists of four parts: +The seL4 Microkit consists of five components: * Microkit bootloader + * CapDL initialiser * Microkit library - * Microkit initial task + * Microkit monitor * Microkit tool The Microkit is distributed as a software development kit (SDK). diff --git a/build_sdk.py b/build_sdk.py index c2ee0b9be..4c08a1b52 100644 --- a/build_sdk.py +++ b/build_sdk.py @@ -33,12 +33,15 @@ TRIPLE_AARCH64 = "aarch64-none-elf" TRIPLE_RISCV = "riscv64-unknown-elf" +# TODO: this won't work for LLVM, to fix later +TRIPLE_X86_64 = "x86_64-linux-gnu" KERNEL_CONFIG_TYPE = Union[bool, str] KERNEL_OPTIONS = Dict[str, Union[bool, str]] DEFAULT_KERNEL_OPTIONS = { "KernelIsMCS": True, + "KernelRootCNodeSizeBits": "17", } DEFAULT_KERNEL_OPTIONS_AARCH64 = { @@ -50,16 +53,34 @@ DEFAULT_KERNEL_OPTIONS_RISCV64 = DEFAULT_KERNEL_OPTIONS +DEFAULT_KERNEL_OPTIONS_X86_64 = { + "KernelPlatform": "pc99", + "KernelX86MicroArch": "generic", +} | DEFAULT_KERNEL_OPTIONS + class KernelArch(IntEnum): AARCH64 = 1 RISCV64 = 2 + X86_64 = 3 def target_triple(self) -> str: if self == KernelArch.AARCH64: return TRIPLE_AARCH64 elif self == KernelArch.RISCV64: return TRIPLE_RISCV + elif self == KernelArch.X86_64: + return TRIPLE_X86_64 + else: + raise Exception(f"Unsupported toolchain architecture '{self}'") + + def rust_toolchain(self) -> str: + if self == KernelArch.AARCH64: + return f"{self.to_str()}-sel4-minimal" + elif self == KernelArch.RISCV64: + return f"{self.to_str()}imac-sel4-minimal" + elif self == KernelArch.X86_64: + return f"{self.to_str()}-sel4-minimal" else: raise Exception(f"Unsupported toolchain target triple '{self}'") @@ -69,11 +90,16 @@ def is_riscv(self) -> bool: def is_arm(self) -> bool: return self == KernelArch.AARCH64 + def is_x86(self) -> bool: + return self == KernelArch.X86_64 + def to_str(self) -> str: if self == KernelArch.AARCH64: return "aarch64" elif self == KernelArch.RISCV64: return "riscv64" + elif self == KernelArch.X86_64: + return "x86_64" else: raise Exception(f"Unsupported arch {self}") @@ -89,7 +115,7 @@ class BoardInfo: name: str arch: KernelArch gcc_cpu: Optional[str] - loader_link_address: int + loader_link_address: int | None kernel_options: KERNEL_OPTIONS @@ -314,6 +340,60 @@ class ConfigInfo: "KernelRiscvExtF": True, } | DEFAULT_KERNEL_OPTIONS_RISCV64, ), + BoardInfo( + name="x86_64_generic", + arch=KernelArch.X86_64, + gcc_cpu="generic", + loader_link_address=None, + kernel_options={ + # @billn revisit + "KernelSupportPCID": False, + "KernelVTX": False, + } | DEFAULT_KERNEL_OPTIONS_X86_64, + ), + # This particular configuration requires support for Intel VT-x + # (plus nested virtualisation on your host if targeting QEMU). + # AMD SVM is currently unsupported by seL4. + BoardInfo( + name="x86_64_generic_vtx", + arch=KernelArch.X86_64, + gcc_cpu="generic", + loader_link_address=None, + kernel_options={ + # @billn revisit + "KernelSupportPCID": False, + "KernelVTX": True, + "KernelX86_64VTX64BitGuests": True, + } | DEFAULT_KERNEL_OPTIONS_X86_64, + ), + # BoardInfo( + # name="x86_64_generic_no_pcid", + # arch=KernelArch.X86_64, + # gcc_cpu="generic", + # loader_link_address=None, + # kernel_options={ + # "KernelVTX": False, + # # QEMU TCG and some CPUs doesn't support PCID, so we have a + # # special configuration here for convenience. + # # For the generic configs, we want that on, as it improve context switching + # # performance by allowing seL4 to skip flushing the entire TLB when + # # switching page tables. + # "KernelSupportPCID": False, + # } | DEFAULT_KERNEL_OPTIONS_X86_64, + # ), + # BoardInfo( + # name="x86_64_generic_no_skim", + # arch=KernelArch.X86_64, + # gcc_cpu="generic", + # loader_link_address=None, + # kernel_options={ + # "KernelVTX": False, + # # No mitigation against Meltdown attack for non-vulnerable processors to + # # prevent needless performance degredation + # "KernelSkimWindow": False, + # } | DEFAULT_KERNEL_OPTIONS_X86_64, + # ), + # # @billn Do we need a x86_64_generic_no_pcid_no_skim ?? ) SUPPORTED_CONFIGS = ( @@ -339,12 +419,17 @@ class ConfigInfo: kernel_options={ "KernelDebugBuild": False, "KernelVerificationBuild": False, - "KernelBenchmarks": "track_utilisation" + "KernelBenchmarks": "track_utilisation", + "KernelSignalFastpath": True, }, kernel_options_arch={ KernelArch.AARCH64: { "KernelArmExportPMUUser": True, }, + KernelArch.X86_64: { + "KernelExportPMCUser": True, + "KernelX86DangerousMSR": True, + } }, ), ) @@ -520,11 +605,13 @@ def build_sel4( copy(p, dest) dest.chmod(0o744) - platform_gen = sel4_build_dir / "gen_headers" / "plat" / "machine" / "platform_gen.json" - dest = root_dir / "board" / board.name / config.name / "platform_gen.json" - dest.unlink(missing_ok=True) - copy(platform_gen, dest) - dest.chmod(0o744) + if not board.arch.is_x86(): + # only non-x86 platforms have this file to describe memory regions + platform_gen = sel4_build_dir / "gen_headers" / "plat" / "machine" / "platform_gen.json" + dest = root_dir / "board" / board.name / config.name / "platform_gen.json" + dest.unlink(missing_ok=True) + copy(platform_gen, dest) + dest.chmod(0o744) gen_config_path = sel4_install_dir / "libsel4/include/kernel/gen_config.json" with open(gen_config_path, "r") as f: @@ -637,9 +724,66 @@ def build_lib_component( dest.chmod(0o744) +def build_initialiser( + component_name: str, + custom_rust_sel4_dir: Path, + root_dir: Path, + build_dir: Path, + board: BoardInfo, + config: ConfigInfo, +) -> None: + sel4_src_dir = build_dir / board.name / config.name / "sel4" / "install" + + cargo_cross_options = "-Z build-std=core,alloc,compiler_builtins -Z build-std-features=compiler-builtins-mem" + cargo_target = board.arch.rust_toolchain() + rust_target_path = Path("initialiser/support/targets").absolute() + + dest = ( + root_dir / "board" / board.name / config.name / "elf" / f"{component_name}.elf" + ) + + # To save on build times, we share a single 'build target' dir for the component, + # this means many initialiser dependencies do not have to be rebuilt unless something + # with the seL4 headers changes or the target architecture. + rust_target_dir = build_dir / component_name + component_build_dir = build_dir / board.name / config.name / component_name + component_build_dir.mkdir(exist_ok=True, parents=True) + + if custom_rust_sel4_dir is None: + capdl_init_elf = component_build_dir / "bin" / "sel4-capdl-initializer.elf" + cmd = f""" + RUSTC_BOOTSTRAP=1 \ + RUST_TARGET_PATH={rust_target_path} SEL4_PREFIX={sel4_src_dir.absolute()} \ + cargo install {cargo_cross_options} \ + --target {cargo_target} \ + --git https://github.com/au-ts/rust-seL4 --branch capdl_dev sel4-capdl-initializer \ + --target-dir {rust_target_dir} \ + --root {component_build_dir} + """ + else: + capdl_init_elf = custom_rust_sel4_dir / "target" / cargo_target / "release" / "sel4-capdl-initializer.elf" + cmd = f""" + cd {custom_rust_sel4_dir} && SEL4_PREFIX={sel4_src_dir.absolute()} {cargo_env} \ + cargo build {cargo_cross_options} --target {cargo_target} \ + --release -p sel4-capdl-initializer + """ + + r = system(cmd) + if r != 0: + raise Exception( + f"Error building: {component_name} for board: {board.name} config: {config.name}" + ) + + dest.unlink(missing_ok=True) + copy(capdl_init_elf, dest) + # Make output read-only + dest.chmod(0o744) + + def main() -> None: parser = ArgumentParser() parser.add_argument("--sel4", type=Path, required=True) + parser.add_argument("--rust-sel4", type=Path, required=False, default=None, help="Compile capDL initialiser from local repository") parser.add_argument("--tool-target-triple", default=get_tool_target_triple(), help="Compile the Microkit tool for this target triple") parser.add_argument("--llvm", action="store_true", help="Cross-compile seL4 and Microkit's run-time targets with LLVM") parser.add_argument("--boards", metavar="BOARDS", help="Comma-separated list of boards to support. When absent, all boards are supported.") @@ -661,8 +805,10 @@ def main() -> None: global TRIPLE_AARCH64 global TRIPLE_RISCV + global TRIPLE_X86_64 TRIPLE_AARCH64 = args.gcc_toolchain_prefix_aarch64 TRIPLE_RISCV = args.gcc_toolchain_prefix_riscv64 + TRIPLE_X86_64 = args.gcc_toolchain_prefix_x86_64 version = args.version @@ -745,7 +891,6 @@ def main() -> None: sel4_gen_config = build_sel4(sel4_dir, root_dir, build_dir, board, config, args.llvm) loader_printing = 1 if config.name == "debug" else 0 loader_defines = [ - ("LINK_ADDRESS", hex(board.loader_link_address)), ("PRINTING", loader_printing) ] # There are some architecture dependent configuration options that the loader @@ -761,9 +906,12 @@ def main() -> None: raise Exception("Unexpected ARM physical address bits defines") loader_defines.append(("PHYSICAL_ADDRESS_BITS", arm_pa_size_bits)) - build_elf_component("loader", root_dir, build_dir, board, config, args.llvm, loader_defines) + if not board.arch.is_x86(): + loader_defines.append(("LINK_ADDRESS", hex(board.loader_link_address))) + build_elf_component("loader", root_dir, build_dir, board, config, args.llvm, loader_defines) build_elf_component("monitor", root_dir, build_dir, board, config, args.llvm, []) build_lib_component("libmicrokit", root_dir, build_dir, board, config, args.llvm) + build_initialiser("initialiser", args.rust_sel4, root_dir, build_dir, board, config) # Setup the examples for example, example_path in EXAMPLES.items(): @@ -780,6 +928,7 @@ def main() -> None: dest.chmod(0o744) if not args.skip_tar: + print(f"Generating {tar_file}") # At this point we create a tar.gz file with tar_open(tar_file, "w:gz") as tar: tar.add(root_dir, arcname=root_dir.name, filter=tar_filter) diff --git a/docs/manual.md b/docs/manual.md index a271224fb..b35b99cd8 100644 --- a/docs/manual.md +++ b/docs/manual.md @@ -58,10 +58,12 @@ To build a Microkit system you will write some programs that use `libmicrokit`. Microkit programs are a little different to a typical process on a Linux-like operating system. Rather than a single `main` entry point, a program has four distinct entry points: `init`, `notified` and, potentially, `protected`, `fault`. -The individual programs are combined to produce a single bootable *system image*. +On ARM and RISC-V, the individual programs are combined to produce a single bootable *system image*. The format of the image is suitable for loading by the target board's bootloader. The Microkit tool, which is provided as part of the SDK, is used to generate the system image. +On x86-64, the individual programs are combined to produce a capDL initialiser ELF image that is loaded by Multiboot as a boot module to seL4. More details in later sections. + The Microkit tool takes a *system description* as input. The system description is an XML file that specifies all the objects that make up the system. @@ -104,6 +106,7 @@ This document attempts to clearly describe all of these terms, however as the co * [notification](#notification) * [interrupt](#irq) * [fault](#fault) +* [ioport](#ioport) ## System {#system} @@ -163,7 +166,7 @@ The overall computational model for a Microkit system is a set of isolated compo The PD has a number of scheduling attributes that are configured in the system description: -* priority (0 -- 254) +* priority (0 -- 253) * period (microseconds) * budget (microseconds) * passive (boolean) @@ -216,7 +219,7 @@ The size of a memory region must be a multiple of a supported page size. The supported page sizes are architecture dependent. For example, on AArch64 architectures, Microkit support 4KiB and 2MiB pages. The page size for a memory region may be specified explicitly in the system description. -If page size is not specified, the smallest supported page size is used. +If page size is not specified, the largest supported page size is used. *Note:* The page size also restricts the alignment of the memory region's physical address. A fixed physical address must be a multiple of the specified page size. @@ -328,6 +331,10 @@ The default system fault handler (aka the monitor) has the highest priority and execute and handle faults immediately after they occur. For child PDs that have their faults delivered to another PD, the fault being handled depends on when the parent PD is scheduled. +## I/O Ports {#ioport} + +I/O ports are x86 mechanisms to access certain physical devices (e.g. PC serial ports or PCI) using the `in` and `out` CPU instructions. The system description specifies if a protection domain have access to certain port address ranges. These accesses will be executed by seL4 and the result returned to protection domains. + # SDK {#sdk} Microkit is distributed as a software development kit (SDK). @@ -343,11 +350,13 @@ The SDK contains: Additionally, for each supported board configuration the following are provided: -* `libmicrokit` -* `loader.elf` -* `kernel.elf` +* `libmicrokit.a` +* `initialiser.elf` +* `sel4.elf` * `monitor.elf` +On ARM and RISC-V, a `loader.elf` is provided, which acts as the system's bootloader. + There are also examples provided in the `example` directory. The Microkit SDK does **not** provide, nor require, any specific build system. @@ -355,7 +364,7 @@ The user is free to build their system using whatever build system is deemed mos The Microkit tool should be invoked by the system build process to transform a system description (and any referenced program images) into an image file which can be loaded by the target board's bootloader. -The ELF files provided as program images should be standard ELF files and have been linked against the provided libmicrokit. +The ELF files provided as program images should be standard ELF files and have been linked against the provided `libmicrokit.a`. ## Configurations {#config} @@ -365,7 +374,7 @@ The *debug* configuration includes a debug build of the seL4 kernel to allow con ## Release -The *release* configuration is a release build of the seL4 kernel and is intended for production builds. The loader, monitor, and +The *release* configuration is a release build of the seL4 kernel and is intended for production builds. The loader, monitor, capDL initialiser and kernel do *not* perform any serial output. ## Benchmark @@ -406,7 +415,8 @@ In the case of success, a loadable image file and a report shall be produced. The output paths for these can be specified by `-o` and `-r` respectively. The default output paths are `loader.img` and `report.txt`. -The loadable image will be a binary that can be loaded by the board's bootloader. +On ARM and RISC-V, the loadable image will be a binary that can be loaded by the board's bootloader. +On x86, the image will be an ELF containing the capDL initialiser to be loaded as a Multiboot boot module. The report is a plain text file describing important information about the system. The report can be useful when debugging potential system problems. @@ -466,6 +476,12 @@ If the protection domain has children it must also implement: seL4_Word microkit_vcpu_arm_read_reg(microkit_child vcpu, seL4_Word reg); void microkit_vcpu_arm_write_reg(microkit_child vcpu, seL4_Word reg, seL4_Word value); void microkit_arm_smc_call(seL4_ARM_SMCContext *args, seL4_ARM_SMCContext *response); + void microkit_x86_ioport_write_8(microkit_ioport ioport_id, seL4_Word port_addr, seL4_Word data); + void microkit_x86_ioport_write_16(microkit_ioport ioport_id, seL4_Word port_addr, seL4_Word data); + void microkit_x86_ioport_write_32(microkit_ioport ioport_id, seL4_Word port_addr, seL4_Word data); + seL4_Uint8 microkit_x86_ioport_read_8(microkit_ioport ioport_id, seL4_Word port_addr); + seL4_Uint16 microkit_x86_ioport_read_16(microkit_ioport ioport_id, seL4_Word port_addr); + seL4_Uint32 microkit_x86_ioport_read_32(microkit_ioport ioport_id, seL4_Word port_addr); ## `void init(void)` @@ -644,6 +660,14 @@ Note that this API is only available when the PD making the call has been config have SMC enabled in the SDF. Note that when the kernel makes the actual SMC, it cannot pre-empt the Secure Monitor and therefore any kernel WCET properties are no longer guaranteed. +## `void microkit_x86_ioport_write_(8|16|32)(microkit_ioport ioport_id, seL4_Word port_addr, seL4_Word data)` + +Write an 8, 16, or 32 bits value at port address `port_addr` to I/O Port with ID `ioport_id`. + +## `seL4_Uint(8|16|32) microkit_x86_ioport_read_(8|16|32)(microkit_ioport ioport_id, seL4_Word port_addr)` + +Read an 8, 16, or 32 bits value at port address `port_addr` from I/O Port with ID `ioport_id`. + # System Description File {#sysdesc} This section describes the format of the System Description File (SDF). @@ -694,16 +718,40 @@ The `map` element has the following attributes: * `setvar_vaddr`: (optional) Specifies a symbol in the program image. This symbol will be rewritten with the virtual address of the memory region. * `setvar_size`: (optional) Specifies a symbol in the program image. This symbol will be rewritten with the size of the memory region. -The `irq` element has the following attributes: +The `irq` element has the following attributes on ARM and RISC-V: * `irq`: The hardware interrupt number. * `id`: The channel identifier. Must be at least 0 and less than 63. * `trigger`: (optional) Whether the IRQ is edge triggered ("edge") or level triggered ("level"). Defaults to "level". +The `irq` element has the following attributes when registering X86_64 IOAPIC interrupts: + +* `id`: The channel identifier. Must be at least 0 and less than 63. +* `pin`: IOAPIC pin that generates the interrupt. +* `vector`: CPU vector to deliver the interrupt to. +* `ioapic`: (optional) Zero based index of the IOAPIC to get the interrupt from. Defaults to 0. +* `level`: (optional) Whether the IRQ is level triggered (1) or edge triggered (0). Defaults to level (1). +* `polarity`: (optional) Whether the line polarity is high (1) or low (0). Defaults to high (1). + +The `irq` element has the following attributes when registering X86_64 MSI interrupts: + +* `id`: The channel identifier. Must be at least 0 and less than 63. +* `pcidev`: The PCI device address of the device that will generate the interrupt, in BUS:DEV:FUNC notation (e.g. 01:1f:2). +* `handle`: Value of the handle programmed into the data portion of the MSI. +* `vector`: CPU vector to deliver the interrupt to. + +The `ioport` element has the following attributes: + +* `id`: The I/O port identifier. Must be at least 0 and less than 63. +* `addr`: The base address of the I/O port. +* `size`: The size in bytes of the I/O port region. + The `setvar` element has the following attributes: * `symbol`: Name of a symbol in the ELF file. * `region_paddr`: Name of an MR. The symbol's value shall be updated to this MR's physical address. + Note that on x86-64 platforms this MR must have a specified physical address. + This restriction is due to x86-64 physical memory layout not being known at build-time. The `protection_domain` element has the same attributes as any other protection domain as well as: @@ -752,6 +800,11 @@ Below are the available page sizes for each architecture that Microkit supports. * 0x1000 (4KiB) * 0x200000 (2MiB) +#### x86-64 + +* 0x1000 (4KiB) +* 0x200000 (2MiB) + ## `channel` The `channel` element has exactly two `end` children elements for specifying the two PDs associated with the channel. @@ -792,6 +845,8 @@ The currently supported platforms are: * star64 * tqma8xqp1gb * ultra96v2 +* x86_64_generic +* x86_64_generic_vtx * zcu102 ## Ariane (CVA6) {#ariane} @@ -1105,6 +1160,34 @@ ZynqMP> tftpboot 0x40000000 loader.img ZynqMP> go 0x40000000 ``` +## x86-64 Generic {#x86_64_generic} + +Support is available for x86-64 with generic micro-architecture and no virtualisation (VTX). + +On x86, Microkit only produces an initial task ELF image. + +For simulating an x86_64 machine using QEMU, use the following command: +``` + $ qemu-system-x86_64 \ + -cpu qemu64,+fsgsbase,+pdpe1gb,+xsaveopt,+xsave \ + -m "1G" \ + -display none \ + -serial mon:stdio \ + -kernel $(COPIED_KERNEL) \ + -initrd $(INITIAL_TASK) +``` + +If you see the following message on boot: +``` +PCIDs not supported by the processor +``` + +@billn continue + +@billn directing the user to write grub stanzas and stuff is a bit complicated, how about we build a bootable ISO from the tool? + +@billn GRUB is also not Mac friendly, maybe recommend users to use something like Limine instead? + ## ZCU102 {#zcu102} The ZCU102 can run on a physical board or on an appropriate QEMU based emulator. @@ -1137,7 +1220,7 @@ To avoid this behaviour, the call to `armv8_switch_to_el1` should be replaced wi ## Adding Platform Support -The following section is a guide for adding support for a new platform to Microkit. +The following section is a guide for adding support for a new ARM or RISC-V platform to Microkit. ### Prerequisites @@ -1209,9 +1292,6 @@ For the kinds of systems targeted by the Microkit, this reduction of the usable The limitation on the size of by-value arguments is forced by the (architecture-dependent) limits on the payload size of the underlying seL4 operations, as well as by efficiency considerations. The protected procedure payload should be considered as analogous to function arguments in the C language; similar limitations exist in the C ABIs (Application Binary Interfaces) of various platforms. -\ -\ - ## Limits The limitation on the number of protection domains in the system is relatively arbitrary. @@ -1238,32 +1318,30 @@ correspond to PD program images to the Microkit tool which is responsible to for packaging everything together into a single bootable image for the target platform. -This final image contains a couple different things: +On ARM and RISC-V, this final image contains a couple different things: * the Microkit loader * seL4 -* the Monitor (and associated invocation data) -* the images for all the user's PDs +* the capDL initialiser (and associated capDL specification) When booting the image, the Microkit loader starts, jumps to the kernel, which -starts the monitor, which then sets up the entire system and starts all the PDs. +starts the capDL initialiser, which then sets up the entire system and starts all the PDs, including the Monitor. + +On x86, the tool produces a boot module that only contains the capDL initialiser. +The seL4 kernel image is distributed as part of the SDK. You must bring your own +Multiboot 2 compliant bootloader which then loads the kernel and boot module. Now, we will go into a bit more detail about each of these stages of the booting process as well as what exactly the Microkit tool is doing. -## Loader +## Loader (ARM and RISC-V) The loader starts first, it has two main jobs: -1. Unpack all the parts of the system (kernel, monitor, PD images, etc) into +1. Unpack all the parts of the system (kernel and capDL initialiser) into their expected locations within main memory. 2. Finish initialising the hardware such that the rest of the system can start. -Unpacking the system image is fairly straight-forward, as all the information -about what parts of the system image need to go where is figured out by the -tool and embedded into the loader at build-time so when it starts it just goe -through an array and copies data into the right locations. - Before the Microkit loader starts, there would most likely have been some other bootloader such as U-Boot or firmware on the target that did its own hardware initialisation before starting Microkit. @@ -1278,6 +1356,7 @@ that will not be done by a previous booting stage, such as: Once this is all completed, the loader jumps to seL4 which starts executing. The loader will never be executed again. +@bill continue fixing below ## Monitor Once the kernel has done its own initialisation, it will begin the diff --git a/example/arm_smc/Makefile b/example/arm_smc/Makefile new file mode 100644 index 000000000..0a535a469 --- /dev/null +++ b/example/arm_smc/Makefile @@ -0,0 +1,58 @@ +# +# Copyright 2025, UNSW. +# +# SPDX-License-Identifier: BSD-2-Clause +# +ifeq ($(strip $(BUILD_DIR)),) +$(error BUILD_DIR must be specified) +endif + +ifeq ($(strip $(MICROKIT_SDK)),) +$(error MICROKIT_SDK must be specified) +endif + +ifeq ($(strip $(MICROKIT_BOARD)),) +$(error MICROKIT_BOARD must be specified) +endif + +ifeq ($(strip $(MICROKIT_CONFIG)),) +$(error MICROKIT_CONFIG must be specified) +endif + +BOARD_DIR := $(MICROKIT_SDK)/board/$(MICROKIT_BOARD)/$(MICROKIT_CONFIG) + +ARCH := ${shell grep 'CONFIG_SEL4_ARCH ' $(BOARD_DIR)/include/kernel/gen_config.h | cut -d' ' -f4} + +ifeq ($(ARCH),aarch64) + TOOLCHAIN := aarch64-none-elf + # No specific AArch64 flags + CFLAGS_ARCH := +else +$(error Unsupported ARCH) +endif + +CC := $(TOOLCHAIN)-gcc +LD := $(TOOLCHAIN)-ld +AS := $(TOOLCHAIN)-as +MICROKIT_TOOL ?= $(MICROKIT_SDK)/bin/microkit + +ARM_SMC_OBJS := arm_smc.o + +IMAGES := arm_smc.elf +CFLAGS := -mstrict-align -nostdlib -ffreestanding -g -O3 -Wall -Wno-unused-function -Werror -I$(BOARD_DIR)/include $(CFLAGS_ARCH) +LDFLAGS := -L$(BOARD_DIR)/lib +LIBS := -lmicrokit -Tmicrokit.ld + +IMAGE_FILE = $(BUILD_DIR)/loader.img +REPORT_FILE = $(BUILD_DIR)/report.txt + +all: $(IMAGE_FILE) + +$(BUILD_DIR)/%.o: %.c Makefile + $(CC) -c $(CFLAGS) $< -o $@ + +$(BUILD_DIR)/arm_smc.elf: $(addprefix $(BUILD_DIR)/, $(ARM_SMC_OBJS)) + $(LD) $(LDFLAGS) $^ $(LIBS) -o $@ + +$(IMAGE_FILE) $(REPORT_FILE): $(addprefix $(BUILD_DIR)/, $(IMAGES)) arm_smc.system + $(MICROKIT_TOOL) arm_smc.system --search-path $(BUILD_DIR) --board $(MICROKIT_BOARD) --config $(MICROKIT_CONFIG) -o $(IMAGE_FILE) -r $(REPORT_FILE) diff --git a/example/arm_smc/README.md b/example/arm_smc/README.md new file mode 100644 index 000000000..76869c338 --- /dev/null +++ b/example/arm_smc/README.md @@ -0,0 +1,20 @@ + +# Example - ARM SMC + +This is a basic example that performs a Secure Monitor Call. + +All supported ARM platforms are supported in this example. + +## Building + +```sh +mkdir build +make BUILD_DIR=build MICROKIT_BOARD= MICROKIT_CONFIG= MICROKIT_SDK=/path/to/sdk +``` + +## Running + +See instructions for your board in the manual. diff --git a/example/arm_smc/arm_smc.c b/example/arm_smc/arm_smc.c new file mode 100644 index 000000000..ae319e921 --- /dev/null +++ b/example/arm_smc/arm_smc.c @@ -0,0 +1,30 @@ +/* + * Copyright 2025, UNSW. + * + * SPDX-License-Identifier: BSD-2-Clause + */ +#include +#include + +#define PSCI_VERSION_FID 0x84000000 + +void init(void) +{ + microkit_dbg_puts("Getting SMC version via microkit_arm_smc_call()\n"); + + seL4_ARM_SMCContext args = {0}; + seL4_ARM_SMCContext resp = {0}; + + args.x0 = PSCI_VERSION_FID; + microkit_arm_smc_call(&args, &resp); + + microkit_dbg_puts("PSCI version: "); + microkit_dbg_put32(((uint32_t) resp.x0 >> 16) & 0xFFFF); + microkit_dbg_puts("."); + microkit_dbg_put32((uint32_t) resp.x0 & 0xFFFF); + microkit_dbg_puts("\n"); +} + +void notified(microkit_channel ch) +{ +} diff --git a/example/arm_smc/arm_smc.system b/example/arm_smc/arm_smc.system new file mode 100644 index 000000000..b448d5043 --- /dev/null +++ b/example/arm_smc/arm_smc.system @@ -0,0 +1,11 @@ + + + + + + + \ No newline at end of file diff --git a/example/hello/hello.system b/example/hello/hello.system index 146da4bab..2db81de2d 100644 --- a/example/hello/hello.system +++ b/example/hello/hello.system @@ -5,7 +5,7 @@ SPDX-License-Identifier: BSD-2-Clause --> - + \ No newline at end of file diff --git a/example/x86_64_ioport/Makefile b/example/x86_64_ioport/Makefile new file mode 100644 index 000000000..253d3d29b --- /dev/null +++ b/example/x86_64_ioport/Makefile @@ -0,0 +1,72 @@ +# +# Copyright 2025, UNSW +# +# SPDX-License-Identifier: BSD-2-Clause +# +ifeq ($(strip $(BUILD_DIR)),) +$(error BUILD_DIR must be specified) +endif + +ifeq ($(strip $(MICROKIT_SDK)),) +$(error MICROKIT_SDK must be specified) +endif + +ifeq ($(strip $(MICROKIT_BOARD)),) +$(error MICROKIT_BOARD must be specified) +endif + +ifeq ($(strip $(MICROKIT_CONFIG)),) +$(error MICROKIT_CONFIG must be specified) +endif + +BOARD_DIR := $(MICROKIT_SDK)/board/$(MICROKIT_BOARD)/$(MICROKIT_CONFIG) + +ARCH := ${shell grep 'CONFIG_SEL4_ARCH ' $(BOARD_DIR)/include/kernel/gen_config.h | cut -d' ' -f4} + +ifeq ($(ARCH),x86_64) + TOOLCHAIN := x86_64-linux-gnu + CFLAGS_ARCH := -march=x86-64 -mtune=generic +else +$(error Unsupported ARCH) +endif + +CC := $(TOOLCHAIN)-gcc +LD := $(TOOLCHAIN)-ld +AS := $(TOOLCHAIN)-as +OBJCOPY := $(TOOLCHAIN)-objcopy +MICROKIT_TOOL ?= $(MICROKIT_SDK)/bin/microkit + +IMAGES := x86_64_ioport.elf +CFLAGS := -nostdlib -ffreestanding -g3 -O3 -I$(BOARD_DIR)/include $(CFLAGS_ARCH) +LDFLAGS := -L$(BOARD_DIR)/lib -z noexecstack +LIBS := -lmicrokit -Tmicrokit.ld + +INITIAL_TASK = $(BUILD_DIR)/capdl_initialiser_with_spec.elf +KERNEL = $(MICROKIT_SDK)/board/$(MICROKIT_BOARD)/$(MICROKIT_CONFIG)/elf/sel4.elf +COPIED_KERNEL = $(BUILD_DIR)/kernel.elf +REPORT = $(BUILD_DIR)/report.txt +SPEC = $(BUILD_DIR)/capdl_spec.json + +all qemu: $(COPIED_KERNEL) $(INITIAL_TASK) + +$(BUILD_DIR)/x86_64_ioport.o: x86_64_ioport.c Makefile + $(CC) -c $(CFLAGS) -Wall -Wno-unused-function -Werror $< -o $@ + +$(BUILD_DIR)/x86_64_ioport.elf: $(BUILD_DIR)/x86_64_ioport.o + $(LD) $(LDFLAGS) $^ $(LIBS) -o $@ + +$(INITIAL_TASK): $(addprefix $(BUILD_DIR)/, $(IMAGES)) x86_64_ioport.system + $(MICROKIT_TOOL) x86_64_ioport.system --search-path $(BUILD_DIR) --board $(MICROKIT_BOARD) --config $(MICROKIT_CONFIG) -o $(INITIAL_TASK) -r $(REPORT) --capdl-spec $(SPEC) + +# This step is required for 'qemu-system-x86_64' as the '-kernel' flag does not accept a 64-bit kernel. +$(COPIED_KERNEL): $(KERNEL) + $(OBJCOPY) -O elf32-i386 $(KERNEL) $(COPIED_KERNEL) + +qemu: + qemu-system-x86_64 \ + -cpu qemu64,+fsgsbase,+pdpe1gb,+xsaveopt,+xsave \ + -m "1G" \ + -display none \ + -serial mon:stdio \ + -kernel $(COPIED_KERNEL) \ + -initrd $(INITIAL_TASK) diff --git a/example/x86_64_ioport/README.md b/example/x86_64_ioport/README.md new file mode 100644 index 000000000..09be40b17 --- /dev/null +++ b/example/x86_64_ioport/README.md @@ -0,0 +1,21 @@ + +# Example - Hello World + +This is a basic hello world example that has a single protection domain +that simply prints "hello!" via the first serial I/O port (0x3F8) upon initialisation. + +Only x86_64 platforms are supported. + +## Building + +```sh +mkdir build +make BUILD_DIR=build MICROKIT_BOARD= MICROKIT_CONFIG= MICROKIT_SDK=/path/to/sdk +``` + +## Running + +See instructions for your board in the manual. diff --git a/example/x86_64_ioport/x86_64_ioport.c b/example/x86_64_ioport/x86_64_ioport.c new file mode 100644 index 000000000..83a853a6e --- /dev/null +++ b/example/x86_64_ioport/x86_64_ioport.c @@ -0,0 +1,40 @@ +/* + * Copyright 2025, UNSW + * + * SPDX-License-Identifier: BSD-2-Clause + */ +#include +#include + +#define SERIAL_IOPORT_ID 0 +#define SERIAL_IOPORT_ADDRESS 0x3f8 + +static inline void serial_putc(char ch) +{ + // Danger: may overflow hardware FIFO, but we are only writing a small message. + microkit_x86_ioport_write_8(SERIAL_IOPORT_ID, SERIAL_IOPORT_ADDRESS, ch); +} + +static inline void serial_puts(const char *s) +{ + while (*s) { + if (*s == '\n') { + serial_putc('\r'); + } + serial_putc(*s++); + } +} + +void init(void) +{ + microkit_dbg_puts("hello, world. my name is "); + microkit_dbg_puts(microkit_name); + microkit_dbg_puts("\n"); + + microkit_dbg_puts("Now writing to serial I/O port: "); + serial_puts("hello!\n"); +} + +void notified(microkit_channel ch) +{ +} diff --git a/example/x86_64_ioport/x86_64_ioport.system b/example/x86_64_ioport/x86_64_ioport.system new file mode 100644 index 000000000..02fb63e38 --- /dev/null +++ b/example/x86_64_ioport/x86_64_ioport.system @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/flake.lock b/flake.lock index b813ced2b..bc19ab9f2 100644 --- a/flake.lock +++ b/flake.lock @@ -18,11 +18,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1728538411, - "narHash": "sha256-f0SBJz1eZ2yOuKUr5CA9BHULGXVSn6miBuUWdTyhUhU=", + "lastModified": 1744536153, + "narHash": "sha256-awS2zRgF4uTwrOKwwiJcByDzDOdo3Q1rPZbiHQg/N38=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "b69de56fac8c2b6f8fd27f2eca01dcda8e0a4221", + "rev": "18dd725c29603f582cf1900e0d25f9f1063dbf11", "type": "github" }, "original": { @@ -45,11 +45,11 @@ "nixpkgs": "nixpkgs_2" }, "locked": { - "lastModified": 1729736953, - "narHash": "sha256-Rb6JUop7NRklg0uzcre+A+Ebrn/ZiQPkm4QdKg6/3pw=", + "lastModified": 1753671061, + "narHash": "sha256-IU4eBWfe9h2QejJYST+EAlhg8a1H6mh9gbcmWgZ2/mQ=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "29b1275740d9283467b8117499ec8cbb35250584", + "rev": "40065d17ee4dbec3ded8ca61236132aede843fab", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 1e92e45bb..5a3f9268d 100644 --- a/flake.nix +++ b/flake.nix @@ -49,8 +49,8 @@ ps.setuptools ]); - microkiToolToml = nixpkgs.lib.trivial.importTOML ./tool/microkit/Cargo.toml; - microkitToolVersion = microkiToolToml.package.rust-version; + microkitToolToml = nixpkgs.lib.trivial.importTOML ./tool/microkit/Cargo.toml; + microkitToolVersion = microkitToolToml.package.rust-version; # Unfortunately Cargo does not support all targets by default so for cross-compiling # we must explicitly add certain targets. @@ -62,9 +62,9 @@ }.${system} or (throw "Unsupported system: ${system}"); rustTool = pkgs.rust-bin.stable.${microkitToolVersion}.default.override { + extensions = [ "rust-src" ]; targets = [ pkgs.pkgsStatic.hostPlatform.rust.rustcTarget ] ++ rustAdditionalTargets; }; - in { # for `nix fmt` @@ -76,6 +76,8 @@ name = "microkit-shell"; nativeBuildInputs = with pkgs; [ + pkgsCross.x86_64-embedded.stdenv.cc.bintools.bintools + pkgsCross.x86_64-embedded.stdenv.cc.cc pkgsCross.aarch64-embedded.stdenv.cc.bintools.bintools pkgsCross.aarch64-embedded.stdenv.cc.cc pkgsCross.riscv64-embedded.stdenv.cc.bintools.bintools @@ -95,6 +97,9 @@ libxml2 qemu ]; + + # Necessary for Rust bindgen + LIBCLANG_PATH = "${pkgs.llvmPackages_18.libclang.lib}/lib"; }; }); } diff --git a/initialiser/support/targets/aarch64-sel4-minimal.json b/initialiser/support/targets/aarch64-sel4-minimal.json new file mode 100644 index 000000000..46fc21aa8 --- /dev/null +++ b/initialiser/support/targets/aarch64-sel4-minimal.json @@ -0,0 +1,34 @@ +{ + "arch": "aarch64", + "crt-objects-fallback": "false", + "data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128-Fn32", + "disable-redzone": true, + "exe-suffix": ".elf", + "features": "+v8a,+strict-align,+neon,+fp-armv8", + "linker": "rust-lld", + "linker-flavor": "gnu-lld", + "llvm-target": "aarch64-unknown-none", + "max-atomic-width": 128, + "metadata": { + "description": null, + "host_tools": null, + "std": null, + "tier": null + }, + "panic-strategy": "abort", + "pre-link-args": { + "gnu-lld": [ + "-z", + "max-page-size=4096" + ] + }, + "relocation-model": "static", + "stack-probes": { + "kind": "inline" + }, + "supported-sanitizers": [ + "kcfi", + "kernel-address" + ], + "target-pointer-width": "64" +} diff --git a/initialiser/support/targets/riscv64imac-sel4-minimal.json b/initialiser/support/targets/riscv64imac-sel4-minimal.json new file mode 100644 index 000000000..8697c8c59 --- /dev/null +++ b/initialiser/support/targets/riscv64imac-sel4-minimal.json @@ -0,0 +1,28 @@ +{ + "arch": "riscv64", + "code-model": "medium", + "cpu": "generic-rv64", + "crt-objects-fallback": "false", + "data-layout": "e-m:e-p:64:64-i64:64-i128:128-n32:64-S128", + "emit-debug-gdb-scripts": false, + "exe-suffix": ".elf", + "features": "+m,+a,+c", + "linker": "rust-lld", + "linker-flavor": "gnu-lld", + "llvm-abiname": "lp64", + "llvm-target": "riscv64", + "max-atomic-width": 64, + "metadata": { + "description": null, + "host_tools": null, + "std": null, + "tier": null + }, + "panic-strategy": "abort", + "relocation-model": "static", + "supported-sanitizers": [ + "shadow-call-stack", + "kernel-address" + ], + "target-pointer-width": "64" +} diff --git a/initialiser/support/targets/x86_64-sel4-minimal.json b/initialiser/support/targets/x86_64-sel4-minimal.json new file mode 100644 index 000000000..c09af2c79 --- /dev/null +++ b/initialiser/support/targets/x86_64-sel4-minimal.json @@ -0,0 +1,32 @@ +{ + "arch": "x86_64", + "code-model": "small", + "cpu": "x86-64", + "crt-objects-fallback": "false", + "data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128", + "disable-redzone": true, + "exe-suffix": ".elf", + "features": "-mmx,-sse,-sse2,-sse3,-ssse3,-sse4.1,-sse4.2,-avx,-avx2,+soft-float", + "linker": "rust-lld", + "linker-flavor": "gnu-lld", + "llvm-target": "x86_64-unknown-none-elf", + "max-atomic-width": 64, + "metadata": { + "description": null, + "host_tools": null, + "std": null, + "tier": null + }, + "panic-strategy": "abort", + "plt-by-default": false, + "relro-level": "full", + "rustc-abi": "x86-softfloat", + "stack-probes": { + "kind": "inline" + }, + "supported-sanitizers": [ + "kcfi", + "kernel-address" + ], + "target-pointer-width": "64" +} diff --git a/libmicrokit/Makefile b/libmicrokit/Makefile index d0738d706..2916a9869 100644 --- a/libmicrokit/Makefile +++ b/libmicrokit/Makefile @@ -41,6 +41,10 @@ else ifeq ($(ARCH),riscv64) CFLAGS_RISCV64 := -mcmodel=medany -march=rv64imafdc_zicsr_zifencei -mabi=lp64d CFLAGS_ARCH := $(CFLAGS_RISCV64) ARCH_DIR := riscv +else ifeq ($(ARCH),x86_64) + CFLAGS_ARCH := -march=x86-64 -mtune=$(GCC_CPU) + ASM_FLAGS := -march=generic64 + ARCH_DIR := x86_64 endif CFLAGS := -std=gnu11 \ diff --git a/libmicrokit/include/microkit.h b/libmicrokit/include/microkit.h index 3eeb5ef1a..9905747f4 100644 --- a/libmicrokit/include/microkit.h +++ b/libmicrokit/include/microkit.h @@ -1,5 +1,6 @@ /* * Copyright 2021, Breakaway Consulting Pty. Ltd. + * Copyright 2025, UNSW * * SPDX-License-Identifier: BSD-2-Clause */ @@ -13,6 +14,7 @@ typedef unsigned int microkit_channel; typedef unsigned int microkit_child; +typedef unsigned int microkit_ioport; typedef seL4_MessageInfo_t microkit_msginfo; #define MONITOR_EP 5 @@ -26,9 +28,11 @@ typedef seL4_MessageInfo_t microkit_msginfo; #define BASE_TCB_CAP 202 #define BASE_VM_TCB_CAP 266 #define BASE_VCPU_CAP 330 +#define BASE_IOPORT_CAP 394 #define MICROKIT_MAX_CHANNELS 62 #define MICROKIT_MAX_CHANNEL_ID (MICROKIT_MAX_CHANNELS - 1) +#define MICROKIT_MAX_IOPORT_ID MICROKIT_MAX_CHANNELS #define MICROKIT_PD_NAME_LENGTH 64 /* User provided functions */ @@ -48,6 +52,7 @@ extern seL4_MessageInfo_t microkit_signal_msg; extern seL4_Word microkit_irqs; extern seL4_Word microkit_notifications; extern seL4_Word microkit_pps; +extern seL4_Word microkit_ioports; /* * Output a single character on the debug console. @@ -110,7 +115,13 @@ static inline void microkit_pd_restart(microkit_child pd, seL4_Word entry_point) { seL4_Error err; seL4_UserContext ctxt = {0}; +#if defined(CONFIG_ARCH_X86_64) + ctxt.rip = entry_point; +#elif defined(CONFIG_ARCH_AARCH64) || defined(CONFIG_ARCH_RISCV) ctxt.pc = entry_point; +#else +#error "Unsupported architecture for 'microkit_pd_restart'" +#endif err = seL4_TCB_WriteRegisters( BASE_TCB_CAP + pd, seL4_True, @@ -173,12 +184,18 @@ static inline seL4_Word microkit_mr_get(seL4_Uint8 mr) } /* The following APIs are only available where the kernel is built as a hypervisor. */ -#if defined(CONFIG_ARM_HYPERVISOR_SUPPORT) +#if defined(CONFIG_ARM_HYPERVISOR_SUPPORT) || defined(CONFIG_VTX) static inline void microkit_vcpu_restart(microkit_child vcpu, seL4_Word entry_point) { seL4_Error err; seL4_UserContext ctxt = {0}; +#if defined(CONFIG_ARCH_AARCH64) ctxt.pc = entry_point; +#elif defined(CONFIG_ARCH_X86_64) + ctxt.rip = entry_point; +#else +#error "unknown architecture for 'microkit_vcpu_restart'" +#endif err = seL4_TCB_WriteRegisters( BASE_VM_TCB_CAP + vcpu, seL4_True, @@ -202,7 +219,9 @@ static inline void microkit_vcpu_stop(microkit_child vcpu) microkit_internal_crash(err); } } +#endif +#if defined(CONFIG_ARM_HYPERVISOR_SUPPORT) static inline void microkit_vcpu_arm_inject_irq(microkit_child vcpu, seL4_Uint16 irq, seL4_Uint8 priority, seL4_Uint8 group, seL4_Uint8 index) { @@ -259,6 +278,122 @@ static inline void microkit_arm_smc_call(seL4_ARM_SMCContext *args, seL4_ARM_SMC } #endif +#if defined(CONFIG_ARCH_X86_64) +static inline void microkit_x86_ioport_write_8(microkit_ioport ioport_id, seL4_Word port_addr, seL4_Word data) +{ + if (ioport_id > MICROKIT_MAX_IOPORT_ID || (microkit_ioports & (1ULL << ioport_id)) == 0) { + microkit_dbg_puts(microkit_name); + microkit_dbg_puts(" microkit_x86_ioport_write_8: invalid I/O Port ID given '"); + microkit_dbg_put32(ioport_id); + microkit_dbg_puts("'\n"); + return; + } + + seL4_Error err; + err = seL4_X86_IOPort_Out8(BASE_IOPORT_CAP + ioport_id, port_addr, data); + if (err != seL4_NoError) { + microkit_dbg_puts("microkit_x86_ioport_write_8: error writing data\n"); + microkit_internal_crash(err); + } +} + +static inline void microkit_x86_ioport_write_16(microkit_ioport ioport_id, seL4_Word port_addr, seL4_Word data) +{ + if (ioport_id > MICROKIT_MAX_IOPORT_ID || (microkit_ioports & (1ULL << ioport_id)) == 0) { + microkit_dbg_puts(microkit_name); + microkit_dbg_puts(" microkit_x86_ioport_write_16: invalid I/O Port ID given '"); + microkit_dbg_put32(ioport_id); + microkit_dbg_puts("'\n"); + return; + } + + seL4_Error err; + err = seL4_X86_IOPort_Out16(BASE_IOPORT_CAP + ioport_id, port_addr, data); + if (err != seL4_NoError) { + microkit_dbg_puts("microkit_x86_ioport_write_16: error writing data\n"); + microkit_internal_crash(err); + } +} + +static inline void microkit_x86_ioport_write_32(microkit_ioport ioport_id, seL4_Word port_addr, seL4_Word data) +{ + if (ioport_id > MICROKIT_MAX_IOPORT_ID || (microkit_ioports & (1ULL << ioport_id)) == 0) { + microkit_dbg_puts(microkit_name); + microkit_dbg_puts(" microkit_x86_ioport_write_32: invalid I/O Port ID given '"); + microkit_dbg_put32(ioport_id); + microkit_dbg_puts("'\n"); + return; + } + + seL4_Error err; + err = seL4_X86_IOPort_Out32(BASE_IOPORT_CAP + ioport_id, port_addr, data); + if (err != seL4_NoError) { + microkit_dbg_puts("microkit_x86_ioport_write_32: error writing data\n"); + microkit_internal_crash(err); + } +} + +static inline seL4_Uint8 microkit_x86_ioport_read_8(microkit_ioport ioport_id, seL4_Word port_addr) +{ + if (ioport_id > MICROKIT_MAX_IOPORT_ID || (microkit_ioports & (1ULL << ioport_id)) == 0) { + microkit_dbg_puts(microkit_name); + microkit_dbg_puts(" microkit_x86_ioport_read_8: invalid I/O Port ID given '"); + microkit_dbg_put32(ioport_id); + microkit_dbg_puts("'\n"); + return 0; + } + + seL4_X86_IOPort_In8_t ret; + ret = seL4_X86_IOPort_In8(BASE_IOPORT_CAP + ioport_id, port_addr); + if (ret.error != seL4_NoError) { + microkit_dbg_puts("microkit_x86_ioport_read_8: error reading data\n"); + microkit_internal_crash(ret.error); + } + + return ret.result; +} + +static inline seL4_Uint16 microkit_x86_ioport_read_16(microkit_ioport ioport_id, seL4_Word port_addr) +{ + if (ioport_id > MICROKIT_MAX_IOPORT_ID || (microkit_ioports & (1ULL << ioport_id)) == 0) { + microkit_dbg_puts(microkit_name); + microkit_dbg_puts(" microkit_x86_ioport_read_16: invalid I/O Port ID given '"); + microkit_dbg_put32(ioport_id); + microkit_dbg_puts("'\n"); + return 0; + } + + seL4_X86_IOPort_In16_t ret; + ret = seL4_X86_IOPort_In16(BASE_IOPORT_CAP + ioport_id, port_addr); + if (ret.error != seL4_NoError) { + microkit_dbg_puts("microkit_x86_ioport_read_16: error reading data\n"); + microkit_internal_crash(ret.error); + } + + return ret.result; +} + +static inline seL4_Uint32 microkit_x86_ioport_read_32(microkit_ioport ioport_id, seL4_Word port_addr) +{ + if (ioport_id > MICROKIT_MAX_IOPORT_ID || (microkit_ioports & (1ULL << ioport_id)) == 0) { + microkit_dbg_puts(microkit_name); + microkit_dbg_puts(" microkit_x86_ioport_read_32: invalid I/O Port ID given '"); + microkit_dbg_put32(ioport_id); + microkit_dbg_puts("'\n"); + return 0; + } + + seL4_X86_IOPort_In32_t ret; + ret = seL4_X86_IOPort_In32(BASE_IOPORT_CAP + ioport_id, port_addr); + if (ret.error != seL4_NoError) { + microkit_dbg_puts("microkit_x86_ioport_read_32: error reading data\n"); + microkit_internal_crash(ret.error); + } + + return ret.result; +} +#endif + static inline void microkit_deferred_notify(microkit_channel ch) { if (ch > MICROKIT_MAX_CHANNEL_ID || (microkit_notifications & (1ULL << ch)) == 0) { diff --git a/libmicrokit/microkit.ld b/libmicrokit/microkit.ld index 350789a7b..8604370fe 100644 --- a/libmicrokit/microkit.ld +++ b/libmicrokit/microkit.ld @@ -36,6 +36,15 @@ SECTIONS PROVIDE(__init_array_end = .); } :data + .data.rel.ro : + { + . = ALIGN(8); + __data_rel_ro_start = .; + *(.data.rel.ro.local*) *(.data.rel.ro*) *(.data.rel*) + __data_rel_ro_end = .; + . = ALIGN(8); + } :data + .data : { _data = .; diff --git a/libmicrokit/src/main.c b/libmicrokit/src/main.c index 54b5aa71e..9ebad4712 100644 --- a/libmicrokit/src/main.c +++ b/libmicrokit/src/main.c @@ -31,6 +31,7 @@ seL4_MessageInfo_t microkit_signal_msg; seL4_Word microkit_irqs; seL4_Word microkit_notifications; seL4_Word microkit_pps; +seL4_Word microkit_ioports; extern seL4_IPCBuffer __sel4_ipc_buffer_obj; diff --git a/libmicrokit/src/x86_64/crt0.s b/libmicrokit/src/x86_64/crt0.s new file mode 100644 index 000000000..d5cd7ab19 --- /dev/null +++ b/libmicrokit/src/x86_64/crt0.s @@ -0,0 +1,10 @@ +/* + * Copyright 2025, UNSW + * + * SPDX-License-Identifier: BSD-2-Clause + */ + + .section .text.start + .globl _start +_start: + call main diff --git a/loader/src/loader.c b/loader/src/loader.c index 9d404c22b..08b27f4b4 100644 --- a/loader/src/loader.c +++ b/loader/src/loader.c @@ -64,8 +64,6 @@ struct loader_data { uintptr_t ui_p_reg_end; uintptr_t pv_offset; uintptr_t v_entry; - uintptr_t extra_device_addr_p; - uintptr_t extra_device_size; uintptr_t num_regions; struct region regions[]; @@ -652,8 +650,8 @@ static void start_kernel(void) loader_data->v_entry, 0, 0, - loader_data->extra_device_addr_p, - loader_data->extra_device_size + 0, + 0 ); } diff --git a/monitor/Makefile b/monitor/Makefile index 640569800..1223795a1 100644 --- a/monitor/Makefile +++ b/monitor/Makefile @@ -40,9 +40,15 @@ ifeq ($(ARCH),aarch64) else ifeq ($(ARCH),riscv64) ASM_CPP_FLAGS := -x assembler-with-cpp -c -g -march=rv64imac_zicsr_zifencei -mabi=lp64 ASM_FLAGS := -march=rv64imac_zicsr_zifencei -mabi=lp64 - CFLAGS_ARCH := -mcmodel=medany -march=rv64imac_zicsr_zifencei -mabi=lp64 -DARCH_riscv64 + CFLAGS_ARCH := -mcmodel=medany -march=rv64imac_zicsr_zifencei -mabi=lp64 ARCH_DIR := riscv +else ifeq ($(ARCH),x86_64) + CFLAGS_ARCH := -march=x86-64 -mtune=$(GCC_CPU) + ASM_CPP_FLAGS := -x assembler-with-cpp -c -g + ASM_FLAGS := -g -march=generic64 + + ARCH_DIR := x86_64 else $(error ARCH is unsupported) endif @@ -50,8 +56,8 @@ endif CFLAGS := -std=gnu11 -g -O3 -nostdlib -ffreestanding -Wall $(CFLAGS_TOOLCHAIN) -Werror -I$(SEL4_SDK)/include $(CFLAGS_ARCH) -DARCH_$(ARCH) PROGS := monitor.elf -OBJECTS := main.o crt0.o debug.o util.o -LINKSCRIPT := monitor.ld +OBJECTS := main.o crt0.o util.o +LINKSCRIPT := ../libmicrokit/microkit.ld $(BUILD_DIR)/%.o : src/$(ARCH_DIR)/%.S $(CC) $(ASM_CPP_FLAGS) $< -o $@ @@ -67,4 +73,4 @@ OBJPROG = $(addprefix $(BUILD_DIR)/, $(PROGS)) all: $(OBJPROG) $(OBJPROG): $(addprefix $(BUILD_DIR)/, $(OBJECTS)) $(LINKSCRIPT) - $(LD) -T$(LINKSCRIPT) $(addprefix $(BUILD_DIR)/, $(OBJECTS)) -o $@ + $(LD) -z noexecstack -T$(LINKSCRIPT) $(addprefix $(BUILD_DIR)/, $(OBJECTS)) -o $@ diff --git a/monitor/monitor.ld b/monitor/monitor.ld deleted file mode 100644 index 706f4c8b3..000000000 --- a/monitor/monitor.ld +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2021, Breakaway Consulting Pty. Ltd. - * - * SPDX-License-Identifier: BSD-2-Clause - */ -PHDRS -{ - all PT_LOAD AT (0x08a000000); -} - -SECTIONS -{ - . = 0x08a000000; - - .text : - { - _text = .; - *(.text.start) - *(.text*) - *(.rodata) - _text_end = .; - } :all - - .data : - { - _data = .; - *(.data) - __global_pointer$ = . + 0x800; - *(.srodata) - *(.sdata) - _data_end = .; - } :all - - .bss : - { - _bss = .; - *(.sbss) - *(.bss) - *(COMMON) - . = ALIGN(4); - _bss_end = .; - } :all -} diff --git a/monitor/src/aarch64/crt0.s b/monitor/src/aarch64/crt0.s index 0d767e543..4d7d2b0e2 100644 --- a/monitor/src/aarch64/crt0.s +++ b/monitor/src/aarch64/crt0.s @@ -10,6 +10,4 @@ .global _start; .type _start, %function; _start: - ldr x1, =_stack + 0xff0 - mov sp, x1 b main diff --git a/monitor/src/debug.c b/monitor/src/debug.c deleted file mode 100644 index 1a4eaed4e..000000000 --- a/monitor/src/debug.c +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright 2021, Breakaway Consulting Pty. Ltd. - * - * SPDX-License-Identifier: BSD-2-Clause - */ -#include - -#define __thread -#include - -#include "util.h" -#include "debug.h" - -void dump_bootinfo(seL4_BootInfo *bi) -{ - unsigned i; - - puts("Bootinfo: "); - puthex64((uintptr_t)bi); - puts("\n"); - - puts("extraLen = "); - puthex64(bi->extraLen); - puts("\n"); - - puts("nodeID = "); - puthex64(bi->nodeID); - puts("\n"); - - puts("numNodes = "); - puthex64(bi->numNodes); - puts("\n"); - - puts("numIOPTLevels = "); - puthex64(bi->numIOPTLevels); - puts("\n"); - - puts("ipcBuffer* = "); - puthex64((uintptr_t)bi->ipcBuffer); - puts("\n"); - - puts("initThreadCNodeSizeBits = "); - puthex64(bi->initThreadCNodeSizeBits); - puts("\n"); - - puts("initThreadDomain = "); - puthex64(bi->initThreadDomain); - puts("\n"); - - puts("userImagePaging = "); - puthex64(bi->userImagePaging.start); - puts(".."); - puthex64(bi->userImagePaging.end - 1); - puts("\n"); - - puts("schedcontrol = "); - puthex64(bi->schedcontrol.start); - puts(".."); - puthex64(bi->schedcontrol.end - 1); - puts("\n"); - - puts("userImageFrames = "); - puthex64(bi->userImageFrames.start); - puts(".."); - puthex64(bi->userImageFrames.end - 1); - puts("\n"); - - puts("untyped = "); - puthex64(bi->untyped.start); - puts(".."); - puthex64(bi->untyped.end - 1); - puts("\n"); - - puts("empty = "); - puthex64(bi->empty.start); - puts(".."); - puthex64(bi->empty.end - 1); - puts("\n"); - - puts("sharedFrames = "); - puthex64(bi->sharedFrames.start); - puts(".."); - puthex64(bi->sharedFrames.end - 1); - puts("\n"); - - puts("ioSpaceCaps = "); - puthex64(bi->ioSpaceCaps.start); - puts(".."); - puthex64(bi->ioSpaceCaps.end - 1); - puts("\n"); - - puts("extraBIPages = "); - puthex64(bi->extraBIPages.start); - puts(".."); - puthex64(bi->extraBIPages.end - 1); - puts("\n"); - -#if 1 - for (i = 0; i < bi->untyped.end - bi->untyped.start; i++) { - puts("untypedList["); - puthex32(i); - puts("] = slot: "); - puthex32(bi->untyped.start + i); - puts(", paddr: "); - puthex64(bi->untypedList[i].paddr); - puts(" - "); - puthex64(bi->untypedList[i].paddr + (1UL << bi->untypedList[i].sizeBits)); - puts(" ("); - puts(bi->untypedList[i].isDevice ? "device" : "normal"); - puts(") bits: "); - puthex32(bi->untypedList[i].sizeBits); - puts("\n"); - } -#endif - /* The extended printing over the individual untypes is good if you care - about the individual objects, but annoying if you want to focus on memory - regions. This coalesces thing before printing to summarize the regions. - This works best when the input is sorted! In practise untyped are sorted - by device/normal and then address, so coalescing works well, but not perfectly. - Good enough for debug. - - Note: the 'gaps' we see are where the kernel is using the memory. For device - memory, this is the memory regions of the GIC. For regular memory that is - memory used for kernel and rootserver. - */ -#if 1 - puts("\nBoot Info Untyped Memory Ranges\n"); - seL4_Word start = bi->untypedList[0].paddr; - seL4_Word end = start + (1ULL << bi->untypedList[0].sizeBits); - seL4_Word is_device = bi->untypedList[0].isDevice; - for (i = 1; i < bi->untyped.end - bi->untyped.start; i++) { - if (bi->untypedList[i].paddr != end || bi->untypedList[i].isDevice != is_device) { - puts(" paddr: "); - puthex64(start); - puts(" - "); - puthex64(end); - puts(" ("); - puts(is_device ? "device" : "normal"); - puts(")\n"); - start = bi->untypedList[i].paddr; - end = start + (1ULL << bi->untypedList[i].sizeBits); - is_device = bi->untypedList[i].isDevice; - } else { - end += (1ULL << bi->untypedList[i].sizeBits); - } - } - puts(" paddr: "); - puthex64(start); - puts(" - "); - puthex64(end); - puts(" ("); - puts(is_device ? "device" : "normal"); - puts(")\n"); -#endif -} diff --git a/monitor/src/debug.h b/monitor/src/debug.h deleted file mode 100644 index 999a1a79f..000000000 --- a/monitor/src/debug.h +++ /dev/null @@ -1,8 +0,0 @@ -/* - * Copyright 2021, Breakaway Consulting Pty. Ltd. - * - * SPDX-License-Identifier: BSD-2-Clause - */ -#pragma once - -void dump_bootinfo(seL4_BootInfo *bi); diff --git a/monitor/src/main.c b/monitor/src/main.c index 0a1f97a43..82609e786 100644 --- a/monitor/src/main.c +++ b/monitor/src/main.c @@ -6,35 +6,10 @@ /* * The Microkit Monitor. * - * The monitor is the initial task in a Microkit system. - * - * The monitor fulfills two purposes: - * - * 1. creating the initial state of the system. - * 2. acting as the fault handler for for protection domains. - * - * Initialisation is performed by executing a number of kernel - * invocations to create and configure kernel objects. - * - * The specific invocations to make are configured by the build - * tool; the monitor simply reads a data structure to execute - * each invocation one at a time. - * - * The process occurs in a two step manner. The first bootstrap - * step execute the `bootstrap_invocations` only. The purpose - * of this bootstrap is to get the system to the point for the - * `system_invocations` is mapped into the monitors address space. - * Once this occurs it is possible for the monitor to switch to - * executing invocation from this second data structure. - * - * The motivation for this design is to keep both the initial - * task image and the initial CNode as small, fixed size entities. - * - * Fixed size allows both kernel and monitor to avoid unnecessary - * recompilation for different system configurations. Keeping things - * small optimizes overall memory usage. - * + * The monitor is the highest priority Protection Domain + * exclusively in a Microkit system. It fulfills one purpose: * + * Acting as the fault handler for protection domains. */ /* @@ -63,99 +38,42 @@ #include #include "util.h" -#include "debug.h" #define MAX_VMS 64 #define MAX_PDS 64 #define MAX_NAME_LEN 64 -#define MAX_UNTYPED_REGIONS 256 +#define FAULT_EP_CAP 1 +#define REPLY_CAP 2 +#define BASE_PD_TCB_CAP 10 +#define BASE_VM_TCB_CAP 74 +#define BASE_SCHED_CONTEXT_CAP 138 +#define BASE_NOTIFICATION_CAP 202 -/* Max words available for bootstrap invocations. - * - * Only a small number of syscalls is required to - * get to the point where the main syscalls data - * is mapped in, so we keep this small. - * - * FIXME: This can be smaller once compression is enabled. - */ -#define BOOTSTRAP_INVOCATION_DATA_SIZE 150 - -seL4_IPCBuffer *__sel4_ipc_buffer; - -char _stack[4096]; +extern seL4_IPCBuffer __sel4_ipc_buffer_obj; +seL4_IPCBuffer *__sel4_ipc_buffer = &__sel4_ipc_buffer_obj; char pd_names[MAX_PDS][MAX_NAME_LEN]; seL4_Word pd_names_len; char vm_names[MAX_VMS][MAX_NAME_LEN] __attribute__((unused)); seL4_Word vm_names_len; -seL4_Word fault_ep; -seL4_Word reply; -seL4_Word pd_tcbs[MAX_PDS]; -seL4_Word vm_tcbs[MAX_VMS]; -seL4_Word scheduling_contexts[MAX_PDS]; -seL4_Word notification_caps[MAX_PDS]; - /* For reporting potential stack overflows, keep track of the stack regions for each PD. */ -seL4_Word pd_stack_addrs[MAX_PDS]; - -struct region { - uintptr_t paddr; - uintptr_t size_bits; - uintptr_t is_device; /*FIXME: should back size_bits / is_device */ -}; - -struct untyped_info { - seL4_Word cap_start; - seL4_Word cap_end; - struct region regions[MAX_UNTYPED_REGIONS]; -}; - -seL4_Word bootstrap_invocation_count; -seL4_Word bootstrap_invocation_data[BOOTSTRAP_INVOCATION_DATA_SIZE]; +seL4_Word pd_stack_bottom_addrs[MAX_PDS]; -seL4_Word system_invocation_count; -seL4_Word *system_invocation_data = (void *)0x80000000; - -struct untyped_info untyped_info; - -void dump_untyped_info() -{ - puts("\nUntyped Info Expected Memory Ranges\n"); - seL4_Word start = untyped_info.regions[0].paddr; - seL4_Word end = start + (1ULL << untyped_info.regions[0].size_bits); - seL4_Word is_device = untyped_info.regions[0].is_device; - for (int i = 1; i < untyped_info.cap_end - untyped_info.cap_start; i++) { - if (untyped_info.regions[i].paddr != end || untyped_info.regions[i].is_device != is_device) { - puts(" paddr: "); - puthex64(start); - puts(" - "); - puthex64(end); - puts(" ("); - puts(is_device ? "device" : "normal"); - puts(")\n"); - start = untyped_info.regions[i].paddr; - end = start + (1ULL << untyped_info.regions[i].size_bits); - is_device = untyped_info.regions[i].is_device; - } else { - end += (1ULL << untyped_info.regions[i].size_bits); - } - } - puts(" paddr: "); - puthex64(start); - puts(" - "); - puthex64(end); - puts(" ("); - puts(is_device ? "device" : "normal"); - puts(")\n"); -} +/* Sanity check that the architecture specific macro have been set. */ +#if defined(ARCH_aarch64) +#elif defined(ARCH_x86_64) +#elif defined(ARCH_riscv64) +#else +#error "No architecture flag was defined, double check your CC flags" +#endif +#ifdef ARCH_riscv64 /* * Convert the fault status register given by the kernel into a string describing * what fault happened. The FSR is the 'scause' register. */ -#ifdef ARCH_riscv64 static char *riscv_fsr_to_string(seL4_Word fsr) { switch (fsr) { @@ -329,6 +247,29 @@ static char *data_abort_dfsc_to_string(uintptr_t dfsc) } #endif +#ifdef ARCH_x86_64 +static char *page_fault_to_string(seL4_Word fsr) +{ + // https://wiki.osdev.org/Exceptions#Page_Fault + switch (fsr) { + case 0 | 4: + return "read to a non-present page at ring 3"; + case 1 | 4: + return "page-protection violation from read at ring 3"; + case 2 | 4: + return "write to a non-present page at ring 3"; + case 3 | 4: + return "page-protection violation from write at ring 3"; + case 16: + // Note that seL4 currently does not implement the NX/XD bit + // to mark a page as non-executable so we will never see the below message. + return "instruction fetch from non-executable page"; + default: + return "invalid FSR or unimplemented decoding"; + } +} +#endif + /* UBSAN decoding related functionality */ #define UBSAN_ARM64_BRK_IMM 0x5500 #define UBSAN_ARM64_BRK_MASK 0x00ff @@ -427,197 +368,6 @@ static char *usban_code_to_string(seL4_Word code) } #endif -static bool check_untypeds_match(seL4_BootInfo *bi) -{ - /* Check that untypeds list generate from build matches the kernel */ - if (untyped_info.cap_start != bi->untyped.start) { - puts("MON|ERROR: cap start mismatch. Expected cap start: "); - puthex32(untyped_info.cap_start); - puts(" boot info cap start: "); - puthex32(bi->untyped.start); - puts("\n"); - puts("cap start mismatch"); - return false; - } - - if (untyped_info.cap_end != bi->untyped.end) { - puts("MON|ERROR: cap end mismatch. Expected cap end: "); - puthex32(untyped_info.cap_end); - puts(" boot info cap end: "); - puthex32(bi->untyped.end); - puts("\n"); - puts("cap end mismatch"); - return false; - } - - for (unsigned i = 0; i < untyped_info.cap_end - untyped_info.cap_start; i++) { - if (untyped_info.regions[i].paddr != bi->untypedList[i].paddr) { - puts("MON|ERROR: paddr mismatch for untyped region: "); - puthex32(i); - puts(" expected paddr: "); - puthex64(untyped_info.regions[i].paddr); - puts(" boot info paddr: "); - puthex64(bi->untypedList[i].paddr); - puts("\n"); - puts("paddr mismatch"); - return false; - } - if (untyped_info.regions[i].size_bits != bi->untypedList[i].sizeBits) { - puts("MON|ERROR: size_bits mismatch for untyped region: "); - puthex32(i); - puts(" expected size_bits: "); - puthex32(untyped_info.regions[i].size_bits); - puts(" boot info size_bits: "); - puthex32(bi->untypedList[i].sizeBits); - puts("\n"); - puts("size_bits mismatch"); - return false; - } - if (untyped_info.regions[i].is_device != bi->untypedList[i].isDevice) { - puts("MON|ERROR: is_device mismatch for untyped region: "); - puthex32(i); - puts(" expected is_device: "); - puthex32(untyped_info.regions[i].is_device); - puts(" boot info is_device: "); - puthex32(bi->untypedList[i].isDevice); - puts("\n"); - puts("is_device mismatch"); - return false; - } - } - - puts("MON|INFO: bootinfo untyped list matches expected list\n"); - - return true; -} - -static unsigned perform_invocation(seL4_Word *invocation_data, unsigned offset, unsigned idx) -{ - seL4_MessageInfo_t tag, out_tag; - seL4_Error result; - seL4_Word mr0; - seL4_Word mr1; - seL4_Word mr2; - seL4_Word mr3; - seL4_Word service; - seL4_Word service_incr; - seL4_Word cmd = invocation_data[offset]; - seL4_Word iterations = (cmd >> 32) + 1; - seL4_Word tag0 = cmd & 0xffffffffULL; - unsigned int cap_offset, cap_incr_offset, cap_count; - unsigned int mr_offset, mr_incr_offset, mr_count; - unsigned int next_offset; - - tag.words[0] = tag0; - service = invocation_data[offset + 1]; - cap_count = seL4_MessageInfo_get_extraCaps(tag); - mr_count = seL4_MessageInfo_get_length(tag); - -#if 0 - puts("Doing invocation: "); - puthex32(idx); - puts(" cap count: "); - puthex32(cap_count); - puts(" MR count: "); - puthex32(mr_count); - puts("\n"); -#endif - - cap_offset = offset + 2; - mr_offset = cap_offset + cap_count; - if (iterations > 1) { - service_incr = invocation_data[mr_offset + mr_count]; - cap_incr_offset = mr_offset + mr_count + 1; - mr_incr_offset = cap_incr_offset + cap_count; - next_offset = mr_incr_offset + mr_count; - } else { - next_offset = mr_offset + mr_count; - } - - if (seL4_MessageInfo_get_capsUnwrapped(tag) != 0) { - fail("kernel invocation should never have unwrapped caps"); - } - - for (unsigned i = 0; i < iterations; i++) { -#if 0 - puts("Preparing invocation:\n"); -#endif - /* Set all the caps */ - seL4_Word call_service = service; - if (i > 0) { - call_service += service_incr * i; - } - for (unsigned j = 0; j < cap_count; j++) { - seL4_Word cap = invocation_data[cap_offset + j]; - if (i > 0) { - cap += invocation_data[cap_incr_offset + j] * i; - } -#if 0 - puts(" SetCap: "); - puthex32(j); - puts(" "); - puthex64(cap); - puts("\n"); -#endif - seL4_SetCap(j, cap); - } - - for (unsigned j = 0; j < mr_count; j++) { - seL4_Word mr = invocation_data[mr_offset + j]; - if (i > 0) { - mr += invocation_data[mr_incr_offset + j] * i; - } -#if 0 - puts(" SetMR: "); - puthex32(j); - puts(" "); - puthex64(mr); - puts("\n"); -#endif - switch (j) { - case 0: - mr0 = mr; - break; - case 1: - mr1 = mr; - break; - case 2: - mr2 = mr; - break; - case 3: - mr3 = mr; - break; - default: - seL4_SetMR(j, mr); - break; - } - } - - out_tag = seL4_CallWithMRs(call_service, tag, &mr0, &mr1, &mr2, &mr3); - result = (seL4_Error) seL4_MessageInfo_get_label(out_tag); - if (result != seL4_NoError) { - puts("ERROR: "); - puthex64(result); - puts(" "); - puts(sel4_strerror(result)); - puts(" invocation idx: "); - puthex32(idx); - puts("."); - puthex32(i); - puts("\n"); - fail("invocation error"); - } -#if 0 - puts("Done invocation: "); - puthex32(idx); - puts("."); - puthex32(i); - puts("\n"); -#endif - } - return next_offset; -} - static void print_tcb_registers(seL4_UserContext *regs) { #if defined(ARCH_riscv64) @@ -819,6 +569,68 @@ static void print_tcb_registers(seL4_UserContext *regs) puts("tpidrro_el0 : "); puthex64(regs->tpidrro_el0); puts("\n"); +#elif ARCH_x86_64 + puts("Registers: \n"); + puts("rip : "); + puthex64(regs->rip); + puts("\n"); + puts("rsp: "); + puthex64(regs->rsp); + puts("\n"); + puts("rflags : "); + puthex64(regs->rflags); + puts("\n"); + puts("rax : "); + puthex64(regs->rax); + puts("\n"); + puts("rbx : "); + puthex64(regs->rbx); + puts("\n"); + puts("rcx : "); + puthex64(regs->rcx); + puts("\n"); + puts("rdx : "); + puthex64(regs->rdx); + puts("\n"); + puts("rsi : "); + puthex64(regs->rsi); + puts("\n"); + puts("rdi : "); + puthex64(regs->rdi); + puts("\n"); + puts("rbp : "); + puthex64(regs->rbp); + puts("\n"); + puts("r8 : "); + puthex64(regs->r8); + puts("\n"); + puts("r9 : "); + puthex64(regs->r9); + puts("\n"); + puts("r10 : "); + puthex64(regs->r10); + puts("\n"); + puts("r11 : "); + puthex64(regs->r11); + puts("\n"); + puts("r12 : "); + puthex64(regs->r12); + puts("\n"); + puts("r13 : "); + puthex64(regs->r13); + puts("\n"); + puts("r14 : "); + puthex64(regs->r14); + puts("\n"); + puts("r15 : "); + puthex64(regs->r15); + puts("\n"); + puts("fs_base : "); + puthex64(regs->fs_base); + puts("\n"); + puts("gs_base : "); + puthex64(regs->gs_base); + puts("\n"); #endif } @@ -844,6 +656,29 @@ static void riscv_print_vm_fault() } #endif +#if ARCH_x86_64 +static void x86_64_print_vm_fault() +{ + seL4_Word ip = seL4_GetMR(seL4_VMFault_IP); + seL4_Word fault_addr = seL4_GetMR(seL4_VMFault_Addr); + seL4_Word is_instruction = seL4_GetMR(seL4_VMFault_PrefetchFault); + seL4_Word fsr = seL4_GetMR(seL4_VMFault_FSR); + puts("MON|ERROR: VMFault: ip="); + puthex64(ip); + puts(" fault_addr="); + puthex64(fault_addr); + puts(" fsr="); + puthex64(fsr); + puts(" "); + puts(is_instruction ? "(instruction fault)" : "(data fault)"); + puts("\n"); + + puts("MON|ERROR: description of fault: "); + puts(page_fault_to_string(fsr)); + puts("\n"); +} +#endif + #ifdef ARCH_aarch64 static void aarch64_print_vm_fault() { @@ -911,20 +746,21 @@ static void monitor(void) seL4_MessageInfo_t tag; seL4_Error err; - tag = seL4_Recv(fault_ep, &badge, reply); + tag = seL4_Recv(FAULT_EP_CAP, &badge, REPLY_CAP); label = seL4_MessageInfo_get_label(tag); - seL4_Word tcb_cap = pd_tcbs[badge]; + seL4_Word pd_id = badge - 1; + seL4_Word tcb_cap = BASE_PD_TCB_CAP + pd_id; - if (label == seL4_Fault_NullFault && badge < MAX_PDS) { + if (label == seL4_Fault_NullFault && pd_id < MAX_PDS) { /* This is a request from our PD to become passive */ - err = seL4_SchedContext_UnbindObject(scheduling_contexts[badge], tcb_cap); - err = seL4_SchedContext_Bind(scheduling_contexts[badge], notification_caps[badge]); + err = seL4_SchedContext_UnbindObject(BASE_SCHED_CONTEXT_CAP + pd_id, tcb_cap); + err = seL4_SchedContext_Bind(BASE_SCHED_CONTEXT_CAP + pd_id, BASE_NOTIFICATION_CAP + pd_id); if (err != seL4_NoError) { puts("MON|ERROR: could not bind scheduling context to notification object"); } else { puts("MON|INFO: PD '"); - puts(pd_names[badge]); + puts(pd_names[pd_id]); puts("' is now passive!\n"); } continue; @@ -938,9 +774,9 @@ static void monitor(void) puthex64(tcb_cap); puts("\n"); - if (badge < MAX_PDS && pd_names[badge][0] != 0) { + if (pd_id < MAX_PDS && pd_names[pd_id][0] != 0) { puts("MON|ERROR: faulting PD: "); - puts(pd_names[badge]); + puts(pd_names[pd_id]); puts("\n"); } else { fail("MON|ERROR: unknown/invalid badge\n"); @@ -1023,12 +859,14 @@ static void monitor(void) aarch64_print_vm_fault(); #elif defined(ARCH_riscv64) riscv_print_vm_fault(); +#elif defined(ARCH_x86_64) + x86_64_print_vm_fault(); #else #error "Unknown architecture to print a VM fault for" #endif seL4_Word fault_addr = seL4_GetMR(seL4_VMFault_Addr); - seL4_Word stack_addr = pd_stack_addrs[badge]; + seL4_Word stack_addr = pd_stack_bottom_addrs[pd_id]; if (fault_addr < stack_addr && fault_addr >= stack_addr - 0x1000) { puts("MON|ERROR: potential stack overflow, fault address within one page outside of stack region\n"); } @@ -1065,58 +903,22 @@ static void monitor(void) } } -void main(seL4_BootInfo *bi) +void main(void) { - __sel4_ipc_buffer = bi->ipcBuffer; - puts("MON|INFO: Microkit Bootstrap\n"); - - if (!check_untypeds_match(bi)) { - /* This can be useful to enable during new platform bring up - * if there are problems - */ - dump_bootinfo(bi); - dump_untyped_info(); - fail("MON|ERROR: found mismatch between boot info and untyped info"); - } - - puts("MON|INFO: Number of bootstrap invocations: "); - puthex32(bootstrap_invocation_count); - puts("\n"); - - puts("MON|INFO: Number of system invocations: "); - puthex32(system_invocation_count); - puts("\n"); - - unsigned offset = 0; - for (unsigned idx = 0; idx < bootstrap_invocation_count; idx++) { - offset = perform_invocation(bootstrap_invocation_data, offset, idx); - } - puts("MON|INFO: completed bootstrap invocations\n"); - - offset = 0; - for (unsigned idx = 0; idx < system_invocation_count; idx++) { - offset = perform_invocation(system_invocation_data, offset, idx); - } - #if CONFIG_DEBUG_BUILD /* * Assign PD/VM names to each TCB with seL4, this helps debugging when an error * message is printed by seL4 or if we dump the scheduler state. - * This is done specifically in the monitor rather than being prepared as an - * invocation like everything else because it is technically a separate system - * call and not an invocation. - * If we end up doing various different kinds of system calls we should add - * support in the tooling and make the monitor generic. */ - for (unsigned idx = 1; idx < pd_names_len + 1; idx++) { - seL4_DebugNameThread(pd_tcbs[idx], pd_names[idx]); + for (unsigned idx = 0; idx < pd_names_len; idx++) { + seL4_DebugNameThread(BASE_PD_TCB_CAP + idx, pd_names[idx]); } - for (unsigned idx = 1; idx < vm_names_len + 1; idx++) { - seL4_DebugNameThread(vm_tcbs[idx], vm_names[idx]); + for (unsigned idx = 0; idx < vm_names_len; idx++) { + seL4_DebugNameThread(BASE_VM_TCB_CAP + idx, vm_names[idx]); } #endif - puts("MON|INFO: completed system invocations\n"); + puts("MON|INFO: Microkit Monitor started!\n"); monitor(); } diff --git a/monitor/src/riscv/crt0.s b/monitor/src/riscv/crt0.s index 0cb0b4fea..28d38a98c 100644 --- a/monitor/src/riscv/crt0.s +++ b/monitor/src/riscv/crt0.s @@ -17,6 +17,4 @@ _start: 1: auipc gp, %pcrel_hi(__global_pointer$) addi gp, gp, %pcrel_lo(1b) .option pop - la s1, (_stack + 0xff0) - mv sp, s1 j main diff --git a/monitor/src/x86_64/crt0.s b/monitor/src/x86_64/crt0.s new file mode 100644 index 000000000..d5cd7ab19 --- /dev/null +++ b/monitor/src/x86_64/crt0.s @@ -0,0 +1,10 @@ +/* + * Copyright 2025, UNSW + * + * SPDX-License-Identifier: BSD-2-Clause + */ + + .section .text.start + .globl _start +_start: + call main diff --git a/tool/microkit/Cargo.lock b/tool/microkit/Cargo.lock index 9073ccf28..d996f67d7 100644 --- a/tool/microkit/Cargo.lock +++ b/tool/microkit/Cargo.lock @@ -1,6 +1,39 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cobs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" +dependencies = [ + "thiserror", +] + +[[package]] +name = "embedded-io" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" [[package]] name = "itoa" @@ -8,29 +41,58 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + [[package]] name = "microkit-tool" version = "2.0.1-dev" dependencies = [ + "postcard", "roxmltree", + "sel4-capdl-initializer-types", "serde", "serde_json", ] +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", +] + +[[package]] +name = "postcard" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" +dependencies = [ + "cobs", + "embedded-io 0.4.0", + "embedded-io 0.6.1", + "serde", +] + [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.36" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" dependencies = [ "proc-macro2", ] @@ -47,20 +109,52 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +[[package]] +name = "sel4-capdl-initializer-types" +version = "0.1.0" +source = "git+https://github.com/au-ts/rust-seL4?branch=capdl_dev#118c9cd67d3a7c95431a0aeb08d8ce8692ab9d80" +dependencies = [ + "cfg-if", + "miniz_oxide", + "sel4-capdl-initializer-types-derive", + "serde", + "serde_json", +] + +[[package]] +name = "sel4-capdl-initializer-types-derive" +version = "0.1.0" +source = "git+https://github.com/au-ts/rust-seL4?branch=capdl_dev#118c9cd67d3a7c95431a0aeb08d8ce8692ab9d80" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "serde" -version = "1.0.203" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.203" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -69,26 +163,48 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.117" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" dependencies = [ "itoa", + "memchr", "ryu", "serde", + "serde_core", ] [[package]] name = "syn" -version = "2.0.68" +version = "2.0.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "901fa70d88b9d6c98022e23b4136f9f3e54e4662c3bc1bd1d84a42a9a0f0c1e9" +checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "unicode-ident" version = "1.0.12" diff --git a/tool/microkit/Cargo.toml b/tool/microkit/Cargo.toml index f58496557..ea6b0ee34 100644 --- a/tool/microkit/Cargo.toml +++ b/tool/microkit/Cargo.toml @@ -8,7 +8,7 @@ name = "microkit-tool" version = "2.0.1-dev" edition = "2021" -rust-version = "1.73.0" +rust-version = "1.88.0" [[bin]] name = "microkit" @@ -16,8 +16,10 @@ path = "src/main.rs" [dependencies] roxmltree = "0.19.0" -serde = { version = "1.0.203", features = ["derive"] } +serde = { version = "1.0.228", features = ["derive"] } serde_json = "1.0.117" +postcard = { version = "1.0.2", default-features = false, features = ["alloc"] } +sel4-capdl-initializer-types = { git = "https://github.com/au-ts/rust-seL4", branch = "capdl_dev", features = ["alloc", "serde", "deflate", "std"] } [profile.release] strip = true diff --git a/tool/microkit/src/capdl/allocation.rs b/tool/microkit/src/capdl/allocation.rs new file mode 100644 index 000000000..69bf35562 --- /dev/null +++ b/tool/microkit/src/capdl/allocation.rs @@ -0,0 +1,216 @@ +// +// Copyright 2025, UNSW +// +// SPDX-License-Identifier: BSD-2-Clause +// + +use std::ops::Range; + +use crate::{ + capdl::{spec::ExpectedAllocation, CapDLSpec}, + sel4::{BootInfo, Config}, + util::human_size_strict, + UntypedObject, +}; + +/// For the given spec and list of untypeds, simulate the CapDL initialiser's +/// object allocation algorithm. Record each object's paddr and UT's index in +/// its `expected_alloc` struct field. Assumes that the spec objects are sorted +/// by paddr, then by size +/// +/// Returns `true` if all objects can be allocated, `false` otherwise. +pub fn simulate_capdl_object_alloc_algorithm( + spec: &mut CapDLSpec, + kernel_boot_info: &BootInfo, + kernel_config: &Config, +) -> bool { + // Step 1: sort untypeds by paddr. + // We don't want to mess with the original order in `kernel_boot_info` as we will patch + // them out to the initialiser later. + let mut untypeds_by_paddr: Vec<(usize, &UntypedObject)> = kernel_boot_info + .untyped_objects + .iter() + .enumerate() + .collect(); + untypeds_by_paddr.sort_by_key(|(_, ut)| ut.base()); + + // Step 2: create object "windows" for objects that doesn't specify paddr, + // where each window contains all objects of the array index size bits. + let mut object_windows_by_size: Vec>> = + vec![None; kernel_config.word_size as usize]; + let first_obj_id_without_paddr = spec + .objects + .partition_point(|named_obj| named_obj.object.paddr().is_some()); + for (id, named_object) in spec.objects[first_obj_id_without_paddr..] + .iter() + .enumerate() + { + let phys_size_bit = named_object.object.physical_size_bits(kernel_config) as usize; + if phys_size_bit > 0 { + let window_maybe = object_windows_by_size.get_mut(phys_size_bit).unwrap(); + match window_maybe { + Some(window) => window.end += 1, + None => { + let _ = window_maybe.insert( + first_obj_id_without_paddr + id..first_obj_id_without_paddr + id + 1, + ); + } + } + } + } + + // Step 3: Sanity check that all objects with a paddr attached can be allocated. + let mut phys_addrs_ok = true; + for obj_with_paddr_id in 0..first_obj_id_without_paddr { + let named_obj = spec.objects.get(obj_with_paddr_id).unwrap(); + let paddr_base = named_obj.object.paddr().unwrap() as u64; + + let obj_size_bytes = 1 << named_obj.object.physical_size_bits(kernel_config); + let paddr_range = paddr_base..paddr_base + obj_size_bytes; + + // Binary search for the UT that is next to the UT that might fit. + // i.e. we are looking for the first UT that is uts[i_ut].paddr() > paddr_range.start + let ut_after_candidate_idx = + untypeds_by_paddr.partition_point(|(_, ut)| ut.base() <= paddr_range.start); + + if ut_after_candidate_idx == 0 { + // Predicate returned false for the first UT, cannot allocate this object as all UTs are + // after the object. + phys_addrs_ok = false; + } else { + let candidate_ut = &untypeds_by_paddr[ut_after_candidate_idx - 1].1; + let candidate_ut_range = + candidate_ut.base()..candidate_ut.base() + (1 << candidate_ut.size_bits()); + if !(candidate_ut_range.start <= paddr_range.start + && candidate_ut_range.end >= paddr_range.end) + { + eprintln!("ERROR: object '{}', with paddr 0x{:0>12x}..0x{:0>12x} is not in any valid memory region.", named_obj.name, paddr_range.start, paddr_range.end); + phys_addrs_ok = false; + } + } + } + + if !phys_addrs_ok { + eprintln!("Below are the valid ranges of memory to be allocated from:"); + eprintln!("Valid ranges outside of main memory:"); + for (_i, ut) in untypeds_by_paddr.iter().filter(|(_i, ut)| ut.is_device) { + eprintln!(" [0x{:0>12x}..0x{:0>12x})", ut.base(), ut.end()); + } + eprintln!("Valid ranges within main memory:"); + for (_i, ut) in untypeds_by_paddr.iter().filter(|(_i, ut)| !ut.is_device) { + eprintln!(" [0x{:0>12x}..0x{:0>12x})", ut.base(), ut.end()); + } + return false; + } + + let num_objs_with_paddr = first_obj_id_without_paddr; + let mut next_obj_id_with_paddr = 0; + for (ut_orig_idx, ut) in untypeds_by_paddr.iter() { + let mut cur_paddr = ut.base(); + + loop { + // If this untyped covers frames that specify a paddr, don't allocate ordinary objects + // past the lowest frame's paddr. + let target = if next_obj_id_with_paddr < num_objs_with_paddr { + ut.end().min( + spec.objects + .get(next_obj_id_with_paddr) + .unwrap() + .object + .paddr() + .unwrap() as u64, + ) + } else { + ut.end() + }; + let target_is_obj_with_paddr = target < ut.end(); + + while cur_paddr < target { + let max_size_bits = usize::try_from(cur_paddr.trailing_zeros()) + .unwrap() + .min((target - cur_paddr).trailing_zeros().try_into().unwrap()); + let mut created = false; + + // If this UT is in main memory, allocate all the objects that does not specify a paddr first. + if !ut.is_device { + // Greedily create a largest possible objects that would fit in this untyped. + // If at the current size we cannot allocate any more object, drop to objects of smaller + // size that still need to be allocated. + for size_bits in (0..=max_size_bits).rev() { + let obj_id_range_maybe = object_windows_by_size.get_mut(size_bits).unwrap(); + if obj_id_range_maybe.is_some() { + // Got objects at this size bits, check if we still have any to allocate + if obj_id_range_maybe.as_ref().unwrap().start + < obj_id_range_maybe.as_ref().unwrap().end + { + let named_obj = spec + .get_root_object_mut(obj_id_range_maybe.as_ref().unwrap().start) + .unwrap(); + + // Should not have touched this object before + assert!(named_obj.expected_alloc.is_none()); + // Book-keep where this object will be allocated so we can write the details out to the report later. + named_obj.expected_alloc = Some(ExpectedAllocation { + ut_idx: *ut_orig_idx, + paddr: cur_paddr, + }); + + cur_paddr += + 1 << named_obj.object.physical_size_bits(kernel_config); + obj_id_range_maybe.as_mut().unwrap().start += 1; + created = true; + break; + } + } + } + } + if !created { + if target_is_obj_with_paddr { + // Manipulate the untyped's watermark to allocate at the correct paddr. + cur_paddr += 1 << max_size_bits; + } else { + cur_paddr = target; + } + } + } + if target_is_obj_with_paddr { + // Watermark now at the correct level, make the actual object + let named_obj = spec.get_root_object_mut(next_obj_id_with_paddr).unwrap(); + + assert_eq!(named_obj.object.paddr().unwrap() as u64, cur_paddr); + // Should not have touched this object before + assert!(named_obj.expected_alloc.is_none()); + // Book-keep where this object will be allocated so we can write the details out to the report later. + named_obj.expected_alloc = Some(ExpectedAllocation { + ut_idx: *ut_orig_idx, + paddr: cur_paddr, + }); + + cur_paddr += 1 << named_obj.object.physical_size_bits(kernel_config); + next_obj_id_with_paddr += 1; + } else { + break; + } + } + } + + // Ensure that we've created every objects + let mut oom = false; + for size_bit in 0..kernel_config.word_size { + let obj_id_range_maybe = object_windows_by_size.get(size_bit as usize).unwrap(); + if obj_id_range_maybe.is_some() { + let obj_id_range = obj_id_range_maybe.as_ref().unwrap(); + if obj_id_range.start != obj_id_range.end { + oom = true; + let shortfall = (obj_id_range.end - obj_id_range.start) as u64; + let individual_sz = (1 << size_bit) as u64; + eprintln!( + "ERROR: ran out of untypeds for allocating objects of size {}, still need to create {} objects which requires {} of additional memory.", + human_size_strict(individual_sz), shortfall, human_size_strict(individual_sz * shortfall) + ); + } + } + } + + !oom +} diff --git a/tool/microkit/src/capdl/builder.rs b/tool/microkit/src/capdl/builder.rs new file mode 100644 index 000000000..67476cc9f --- /dev/null +++ b/tool/microkit/src/capdl/builder.rs @@ -0,0 +1,1097 @@ +// +// Copyright 2025, UNSW +// +// SPDX-License-Identifier: BSD-2-Clause +// +use core::ops::Range; + +use std::{ + cmp::{min, Ordering}, + collections::HashMap, +}; + +use sel4_capdl_initializer_types::{ + AsidSlotEntry, CapTableEntry, IrqEntry, ObjectId, UntypedCover, +}; +use serde::Serialize; + +use crate::{ + capdl::{ + irq::create_irq_handler_cap, + memory::{create_vspace, create_vspace_ept, map_page}, + spec::{ + capdl_object::{self}, + CapDLObject, ElfContent, Fill, FillEntry, FillEntryContent, FrameInit, NamedObject, + }, + util::*, + }, + elf::ElfFile, + sdf::{ + SysMap, SysMapPerms, SystemDescription, BUDGET_DEFAULT, MONITOR_PD_NAME, MONITOR_PRIORITY, + }, + sel4::{Arch, Config, PageSize}, + util::{ranges_overlap, round_down, round_up}, +}; + +// Corresponds to the IPC buffer symbol in libmicrokit and the monitor +const SYMBOL_IPC_BUFFER: &str = "__sel4_ipc_buffer_obj"; + +const FAULT_BADGE: u64 = 1 << 62; +const PPC_BADGE: u64 = 1 << 63; + +// The sel4-capdl-initialiser crate expects caps that you want to bind to a TCB to be at +// certain slots. From dep/rust-sel4/crates/sel4-capdl-initializer/types/src/cap_table.rs +pub enum TcbBoundSlot { + CSpace = 0, + VSpace = 1, + IpcBuffer = 4, + FaultEp = 5, + SchedContext = 6, + BoundNotification = 8, + VCpu = 9, + // Guest VM root page table object on x86 + X86Eptpml4 = 10, +} + +impl From for TcbBoundSlot { + fn from(value: usize) -> Self { + match value { + 0 => Self::CSpace, + 1 => Self::VSpace, + 4 => Self::IpcBuffer, + 5 => Self::FaultEp, + 6 => Self::SchedContext, + 8 => Self::BoundNotification, + 9 => Self::VCpu, + 10 => Self::X86Eptpml4, + _ => unreachable!("internal bug: unknown value for TcbBoundSlot::from"), + } + } +} + +const MON_STACK_SIZE: u64 = 0x1000; + +// Where caps must be in the Monitor's CSpace +const MON_FAULT_EP_CAP_IDX: u64 = 1; +const MON_REPLY_CAP_IDX: u64 = 2; +const MON_BASE_PD_TCB_CAP: u64 = 10; +const MON_BASE_VM_TCB_CAP: u64 = MON_BASE_PD_TCB_CAP + 64; +const MON_BASE_SCHED_CONTEXT_CAP: u64 = MON_BASE_VM_TCB_CAP + 64; +const MON_BASE_NOTIFICATION_CAP: u64 = MON_BASE_SCHED_CONTEXT_CAP + 64; + +// Where caps must be in a PD's CSpace +const PD_INPUT_CAP_IDX: u64 = 1; +const PD_FAULT_EP_CAP_IDX: u64 = 2; +const PD_VSPACE_CAP_IDX: u64 = 3; +const PD_REPLY_CAP_IDX: u64 = 4; +// Valid only if the PD is passive. +const PD_MONITOR_EP_CAP_IDX: u64 = 5; +// Valid only in benchmark configuration. +const PD_TCB_CAP_IDX: u64 = 6; +const PD_ARM_SMC_CAP_IDX: u64 = 7; + +const PD_BASE_OUTPUT_NOTIFICATION_CAP: u64 = 10; +const PD_BASE_OUTPUT_ENDPOINT_CAP: u64 = PD_BASE_OUTPUT_NOTIFICATION_CAP + 64; +const PD_BASE_IRQ_CAP: u64 = PD_BASE_OUTPUT_ENDPOINT_CAP + 64; +const PD_BASE_PD_TCB_CAP: u64 = PD_BASE_IRQ_CAP + 64; +const PD_BASE_VM_TCB_CAP: u64 = PD_BASE_PD_TCB_CAP + 64; +const PD_BASE_VCPU_CAP: u64 = PD_BASE_VM_TCB_CAP + 64; +const PD_BASE_IOPORT_CAP: u64 = PD_BASE_VCPU_CAP + 64; + +pub const PD_CAP_SIZE: u64 = 512; +const PD_CAP_BITS: u64 = PD_CAP_SIZE.ilog2() as u64; +const PD_SCHEDCONTEXT_EXTRA_SIZE: u64 = 256; +const PD_SCHEDCONTEXT_EXTRA_SIZE_BITS: u64 = PD_SCHEDCONTEXT_EXTRA_SIZE.ilog2() as u64; + +pub const SLOT_BITS: u64 = 5; +pub const SLOT_SIZE: u64 = 1 << SLOT_BITS; + +#[derive(Serialize)] +pub struct CapDLSpec { + pub objects: Vec, + pub irqs: Vec, + pub asid_slots: Vec, + pub root_objects: Range, + pub untyped_covers: Vec, +} + +impl Default for CapDLSpec { + fn default() -> Self { + Self::new() + } +} + +impl CapDLSpec { + pub fn new() -> Self { + Self { + objects: Vec::new(), + irqs: Vec::new(), + asid_slots: Vec::new(), + root_objects: Range { start: 0, end: 0 }, + untyped_covers: Vec::new(), + } + } + + pub fn add_root_object(&mut self, obj: NamedObject) -> ObjectId { + self.objects.push(obj); + self.root_objects.end += 1; + assert_eq!(self.objects.len(), self.root_objects.end); + self.root_objects.end - 1 + } + + pub fn get_root_object_mut(&mut self, obj_id: ObjectId) -> Option<&mut NamedObject> { + if obj_id < self.root_objects.end { + Some(&mut self.objects[obj_id]) + } else { + None + } + } + + pub fn get_root_object(&self, obj_id: ObjectId) -> Option<&NamedObject> { + if obj_id < self.root_objects.end { + Some(&self.objects[obj_id]) + } else { + None + } + } + + /// Add the details of the given ELF into the given CapDL spec while inferring as much information + /// as possible. These are the objects that will be created: + /// -> TCB: PC, SP and IPC buffer vaddr set. VSpace and IPC buffer frame caps bound. + /// -> VSpace: all ELF loadable pages and IPC buffer mapped in. + /// Returns the object ID of the TCB + /// NOTE that all ELF frames will just be reference to the original ELF object rather than the actual data. + /// So that symbols can be patched before the frames' data are filled in. + fn add_elf_to_spec( + &mut self, + sel4_config: &Config, + pd_name: &str, + elf_id: usize, + elf: &ElfFile, + ) -> Result { + // We assumes that ELFs and PDs have a one-to-one relationship. So for each ELF we create a VSpace. + let vspace_obj_id = create_vspace(self, sel4_config, pd_name); + let vspace_cap = capdl_util_make_page_table_cap(vspace_obj_id); + + // For each loadable segment in the ELF, map it into the address space of this PD. + let mut frame_sequence = 0; // For object naming purpose only. + for (seg_idx, segment) in elf.loadable_segments().iter().enumerate() { + if segment.data().is_empty() { + continue; + } + + let seg_base_vaddr = segment.virt_addr; + let seg_mem_size: u64 = segment.mem_size(); + + let page_size = PageSize::Small; + let page_size_bytes = page_size as u64; + + // Create and map all frames for this segment. + let mut cur_vaddr = round_down(seg_base_vaddr, page_size_bytes); + while cur_vaddr < seg_base_vaddr + seg_mem_size { + let mut frame_init_maybe: Option = None; + + // Now compute the ELF file offset to fill in this page. + let mut dest_offset = 0; + if cur_vaddr < seg_base_vaddr { + // Take care of case where the ELF segment is not aligned on page boundary: + // | ELF | ELF | ELF | + // | Page | Page | Page | + // <-> + dest_offset = seg_base_vaddr - cur_vaddr; + } + + let target_vaddr_start = cur_vaddr + dest_offset; + let section_offset = target_vaddr_start - seg_base_vaddr; + if section_offset < seg_mem_size { + // We have data to load + let len_to_cpy = + min(page_size_bytes - dest_offset, seg_mem_size - section_offset); + + frame_init_maybe = Some(FrameInit::Fill(Fill { + entries: [FillEntry { + range: Range { + start: dest_offset as usize, + end: (dest_offset + len_to_cpy) as usize, + }, + content: FillEntryContent::Data(ElfContent { + elf_id, + elf_seg_idx: seg_idx, + elf_seg_data_range: (section_offset as usize + ..((section_offset + len_to_cpy) as usize)), + }), + }] + .to_vec(), + })); + } + + let frame_init = match frame_init_maybe { + Some(actual_frame_init) => actual_frame_init, + None => FrameInit::Fill(Fill { + entries: [].to_vec(), + }), + }; + // Create the frame object, cap to the object, add it to the spec and map it in. + let frame_obj_id = capdl_util_make_frame_obj( + self, + frame_init, + &format!("elf_{pd_name}_{frame_sequence:09}"), + None, + PageSize::Small.fixed_size_bits(sel4_config) as usize, + ); + let frame_cap = capdl_util_make_frame_cap( + frame_obj_id, + segment.is_readable(), + segment.is_writable(), + segment.is_executable(), + true, + ); + + match map_page( + self, + sel4_config, + pd_name, + vspace_obj_id, + frame_cap, + page_size_bytes, + cur_vaddr, + ) { + Ok(_) => { + frame_sequence += 1; + cur_vaddr += page_size_bytes; + } + Err(map_err_reason) => { + return Err(format!( + "add_elf_to_spec(): failed to map segment page to ELF because: {map_err_reason}" + )) + } + }; + } + } + + // Create and map the IPC buffer for this ELF + let ipcbuf_frame_obj_id = capdl_util_make_frame_obj( + self, + FrameInit::Fill(Fill { + entries: [].to_vec(), + }), + &format!("ipcbuf_{pd_name}"), + None, + PageSize::Small.fixed_size_bits(sel4_config) as usize, + ); + let ipcbuf_frame_cap = + capdl_util_make_frame_cap(ipcbuf_frame_obj_id, true, true, false, true); + // We need to clone the IPC buf cap because in addition to mapping the frame into the VSpace, we need to bind + // this frame to the TCB as well. + let ipcbuf_frame_cap_for_tcb = ipcbuf_frame_cap.clone(); + let ipcbuf_vaddr = elf + .find_symbol(SYMBOL_IPC_BUFFER) + .unwrap_or_else(|_| panic!("Could not find {SYMBOL_IPC_BUFFER}")) + .0; + match map_page( + self, + sel4_config, + pd_name, + vspace_obj_id, + ipcbuf_frame_cap, + PageSize::Small as u64, + ipcbuf_vaddr, + ) { + Ok(_) => {} + Err(map_err_reason) => { + return Err(format!( + "build_capdl_spec(): failed to map ipc buffer frame to {pd_name} because: {map_err_reason}" + )) + } + }; + + let tcb_name = format!("tcb_{pd_name}"); + let entry_point = elf.entry; + + let tcb_extra_info = capdl_object::TcbExtraInfo { + ipc_buffer_addr: ipcbuf_vaddr, + affinity: 0, + prio: 0, + max_prio: 0, + resume: false, + ip: entry_point, + sp: 0, + gprs: Vec::new(), + master_fault_ep: None, + }; + + let tcb_inner_obj = capdl_object::Tcb { + // Bind the VSpace into the TCB + slots: [ + (TcbBoundSlot::VSpace as usize, vspace_cap), + (TcbBoundSlot::IpcBuffer as usize, ipcbuf_frame_cap_for_tcb), + ] + .to_vec(), + extra: tcb_extra_info, + }; + + let tcb_obj = NamedObject { + name: tcb_name, + object: CapDLObject::Tcb(tcb_inner_obj), + expected_alloc: None, + }; + + Ok(self.add_root_object(tcb_obj)) + } +} + +/// Given a SysMap, page size, VSpace object ID, and a Vec of frame object ids, +/// map all frames into the given VSpace at the requested vaddr. +fn map_memory_region( + spec: &mut CapDLSpec, + sel4_config: &Config, + pd_name: &str, + map: &SysMap, + page_sz: u64, + target_vspace: ObjectId, + frames: &[ObjectId], +) { + let mut cur_vaddr = map.vaddr; + let read = map.perms & SysMapPerms::Read as u8 != 0; + let write = map.perms & SysMapPerms::Write as u8 != 0; + let execute = map.perms & SysMapPerms::Execute as u8 != 0; + let cached = map.cached; + for frame_obj_id in frames.iter() { + // Make a cap for this frame. + let frame_cap = capdl_util_make_frame_cap(*frame_obj_id, read, write, execute, cached); + // Map it into this PD address space. + map_page( + spec, + sel4_config, + pd_name, + target_vspace, + frame_cap, + page_sz, + cur_vaddr, + ) + .unwrap(); + cur_vaddr += page_sz; + } +} + +/// Build a CapDL Spec according to the System Description File. +pub fn build_capdl_spec( + kernel_config: &Config, + elfs: &mut [ElfFile], + system: &SystemDescription, +) -> Result { + let mut spec = CapDLSpec::new(); + + // ********************************* + // Step 1. Create the monitor's spec. + // ********************************* + // Parse ELF, create VSpace, map in all ELF loadable frames and IPC buffer, and create TCB. + // We expect the PD ELFs to be first and the monitor ELF last in the list of ELFs. + let mon_elf_id = elfs.len() - 1; + assert!(elfs.len() == system.protection_domains.len() + 1); + let monitor_tcb_obj_id = { + let monitor_elf = elfs.get(mon_elf_id).unwrap(); + spec.add_elf_to_spec(kernel_config, MONITOR_PD_NAME, mon_elf_id, monitor_elf) + .unwrap() + }; + + // Create monitor fault endpoint object + cap + let mon_fault_ep_obj_id = capdl_util_make_endpoint_obj(&mut spec, MONITOR_PD_NAME, true); + let mon_fault_ep_cap = capdl_util_make_endpoint_cap(mon_fault_ep_obj_id, true, true, true, 0); + + // Create monitor reply object object + cap + let mon_reply_obj_id = capdl_util_make_reply_obj(&mut spec, MONITOR_PD_NAME); + let mon_reply_cap = capdl_util_make_reply_cap(mon_reply_obj_id); + + // Create monitor scheduling context object + cap + let mon_sc_obj_id = capdl_util_make_sc_obj( + &mut spec, + MONITOR_PD_NAME, + PD_SCHEDCONTEXT_EXTRA_SIZE_BITS as usize, + BUDGET_DEFAULT, + BUDGET_DEFAULT, + 0, + ); + let mon_sc_cap = capdl_util_make_sc_cap(mon_sc_obj_id); + + // Create monitor CSpace and pre-insert the fault EP and reply caps into the correct slots in CSpace. + let mon_cnode_obj_id = capdl_util_make_cnode_obj( + &mut spec, + MONITOR_PD_NAME, + PD_CAP_BITS as usize, + [ + (MON_FAULT_EP_CAP_IDX as usize, mon_fault_ep_cap), + (MON_REPLY_CAP_IDX as usize, mon_reply_cap), + ] + .to_vec(), + ); + let mon_guard_size = kernel_config.cap_address_bits - PD_CAP_BITS; + let mon_cnode_cap = capdl_util_make_cnode_cap(mon_cnode_obj_id, 0, mon_guard_size); + + // Create monitor stack frame + let mon_stack_frame_obj_id = capdl_util_make_frame_obj( + &mut spec, + FrameInit::Fill(Fill { + entries: [].to_vec(), + }), + &format!("{MONITOR_PD_NAME}_stack"), + None, + PageSize::Small.fixed_size_bits(kernel_config) as usize, + ); + let mon_stack_frame_cap = + capdl_util_make_frame_cap(mon_stack_frame_obj_id, true, true, false, true); + let mon_vspace_obj_id = capdl_util_get_vspace_id_from_tcb_id(&spec, monitor_tcb_obj_id); + map_page( + &mut spec, + kernel_config, + MONITOR_PD_NAME, + mon_vspace_obj_id, + mon_stack_frame_cap, + PageSize::Small as u64, + kernel_config.pd_stack_bottom(MON_STACK_SIZE), + ) + .unwrap(); + + // At this point, all of the required objects for the monitor have been created and it caps inserted into + // the correct slot in the CSpace. We need to bind those objects into the TCB for the monitor to use them. + // In addition, `add_elf_to_spec()` doesn't fill most the details in the TCB. + // Now fill them in: stack ptr, priority, ipc buf vaddr, etc. + if let CapDLObject::Tcb(monitor_tcb) = + &mut spec.get_root_object_mut(monitor_tcb_obj_id).unwrap().object + { + // Special case, monitor has its stack statically allocated. + monitor_tcb.extra.sp = kernel_config.pd_stack_top(); + // While there is nothing stopping us from running the monitor at the highest priority alongside the + // CapDL initialiser, the debug kernel serial output can get garbled when the monitor TCB is resumed. + monitor_tcb.extra.prio = MONITOR_PRIORITY; + monitor_tcb.extra.max_prio = MONITOR_PRIORITY; + monitor_tcb.extra.resume = true; + + monitor_tcb + .slots + .push((TcbBoundSlot::CSpace as usize, mon_cnode_cap)); + + monitor_tcb + .slots + .push((TcbBoundSlot::SchedContext as usize, mon_sc_cap)); + } else { + unreachable!("internal bug: build_capdl_spec() got a non TCB object ID when trying to set TCB parameters for the monitor."); + } + + // ********************************* + // Step 2. Create the memory regions' spec. Result is a hashmap keyed on MR name, value is (parsed XML obj, Vec of frame object IDs) + // ********************************* + let mut mr_name_to_frames: HashMap<&String, Vec> = HashMap::new(); + for mr in system.memory_regions.iter() { + let mut frame_ids = Vec::new(); + let frame_size_bits = mr.page_size.fixed_size_bits(kernel_config); + + for frame_sequence in 0..mr.page_count { + let paddr = mr + .phys_addr + .map(|base_paddr| (base_paddr + (frame_sequence * mr.page_size_bytes())) as usize); + frame_ids.push(capdl_util_make_frame_obj( + &mut spec, + FrameInit::Fill(Fill { + entries: [].to_vec(), + }), + &format!("mr_{}_{:09}", mr.name, frame_sequence), + paddr, + frame_size_bits as usize, + )); + } + + mr_name_to_frames.insert(&mr.name, frame_ids); + } + + // ********************************* + // Step 3. Create the PDs' spec + // ********************************* + // On ARM, check if we need to create the SMC object + let arm_smc_obj_id = if kernel_config.arch == Arch::Aarch64 + && kernel_config.arm_smc.unwrap_or(false) + && system.protection_domains.iter().any(|pd| pd.smc) + { + Some(spec.add_root_object(NamedObject { + name: "arm_smc".to_owned(), + object: CapDLObject::ArmSmc, + expected_alloc: None, + })) + } else { + None + }; + + // Keep tabs on each PD's CSpace, Notification and Endpoint objects so we can create channels between them at a later step. + let mut pd_id_to_cspace_id: HashMap = HashMap::new(); + let mut pd_id_to_ntfn_id: HashMap = HashMap::new(); + let mut pd_id_to_ep_id: HashMap = HashMap::new(); + + // Keep track of the global count of vCPU objects so we can bind them to the monitor for setting TCB name in debug config. + // Only used on ARM and RISC-V as on x86-64 VMs share the same TCB as PD's which will have their TCB name set separately. + let mut monitor_vcpu_idx = 0; + + // Keep tabs on each PD's stack bottom so we can write it out to the monitor for stack overflow detection. + let mut pd_stack_bottoms: Vec = Vec::new(); + + for (pd_global_idx, pd) in system.protection_domains.iter().enumerate() { + let elf_obj = &elfs[pd_global_idx]; + + let mut caps_to_bind_to_tcb: Vec = Vec::new(); + let mut caps_to_insert_to_pd_cspace: Vec = Vec::new(); + + // Step 3-1: Create TCB and VSpace with all ELF loadable frames mapped in. + let pd_tcb_obj_id = spec + .add_elf_to_spec(kernel_config, &pd.name, pd_global_idx, elf_obj) + .unwrap(); + let pd_vspace_obj_id = capdl_util_get_vspace_id_from_tcb_id(&spec, pd_tcb_obj_id); + + // In the benchmark configuration, we allow PDs to access their own TCB. + // This is necessary for accessing kernel's benchmark API. + if kernel_config.benchmark { + caps_to_insert_to_pd_cspace.push(( + PD_TCB_CAP_IDX as usize, + capdl_util_make_tcb_cap(pd_tcb_obj_id), + )); + } + + // Allow PD to access their own VSpace for ops such as cache cleaning on ARM. + caps_to_insert_to_pd_cspace.push(( + PD_VSPACE_CAP_IDX as usize, + capdl_util_make_page_table_cap(pd_vspace_obj_id), + )); + + // Step 3-2: Map in all Memory Regions, keep tabs on what MR is mapped where so we can setvar later + let mut mr_to_vaddr: HashMap<&String, u64> = HashMap::new(); + for map in pd.maps.iter() { + let frames = mr_name_to_frames.get(&map.mr).unwrap(); + // MRs have frames of equal size so just use the first frame's page size. + let page_size_bytes = + 1 << capdl_util_get_frame_size_bits(&spec, *frames.first().unwrap()); + + // sdf.rs sanity checks that the memory regions doesn't overlap with each others, etc. + // But it doesn't actually check for whether they overlap with a PD's stack or ELF segments. + // We perform this check here, otherwise the tool will panic with quite cryptic page-table related errors. + let mr_vaddr_range = map.vaddr..(map.vaddr + (page_size_bytes * frames.len() as u64)); + + let pd_stack_range = + kernel_config.pd_stack_bottom(pd.stack_size)..kernel_config.pd_stack_top(); + if ranges_overlap(&mr_vaddr_range, &pd_stack_range) { + return Err(format!("ERROR: mapping MR '{}' to PD '{}' with vaddr [0x{:x}..0x{:x}) will overlap with the stack at [0x{:x}..0x{:x})", map.mr, pd.name, mr_vaddr_range.start, mr_vaddr_range.end, pd_stack_range.start, pd_stack_range.end)); + } + + for elf_seg in elf_obj.loadable_segments().iter() { + let elf_seg_vaddr_range = elf_seg.virt_addr + ..elf_seg.virt_addr + round_up(elf_seg.mem_size(), PageSize::Small as u64); + if ranges_overlap(&mr_vaddr_range, &elf_seg_vaddr_range) { + return Err(format!("ERROR: mapping MR '{}' to PD '{}' with vaddr [0x{:x}..0x{:x}) will overlap with an ELF segment at [0x{:x}..0x{:x})", map.mr, pd.name, mr_vaddr_range.start, mr_vaddr_range.end, elf_seg_vaddr_range.start, elf_seg_vaddr_range.end)); + } + } + + map_memory_region( + &mut spec, + kernel_config, + &pd.name, + map, + page_size_bytes, + pd_vspace_obj_id, + frames, + ); + mr_to_vaddr.insert(&map.mr, map.vaddr); + } + + // Step 3-3: Create and map in the stack (bottom up) + let mut cur_stack_vaddr = kernel_config.pd_stack_bottom(pd.stack_size); + pd_stack_bottoms.push(cur_stack_vaddr); + let num_stack_frames = pd.stack_size / PageSize::Small as u64; + for stack_frame_seq in 0..num_stack_frames { + let stack_frame_obj_id = capdl_util_make_frame_obj( + &mut spec, + FrameInit::Fill(Fill { + entries: [].to_vec(), + }), + &format!("{}_stack_{:09}", pd.name, stack_frame_seq), + None, + PageSize::Small.fixed_size_bits(kernel_config) as usize, + ); + let stack_frame_cap = + capdl_util_make_frame_cap(stack_frame_obj_id, true, true, false, true); + map_page( + &mut spec, + kernel_config, + &pd.name, + pd_vspace_obj_id, + stack_frame_cap, + PageSize::Small as u64, + cur_stack_vaddr, + ) + .unwrap(); + cur_stack_vaddr += PageSize::Small as u64; + } + + // Step 3-4 Create Scheduling Context + let pd_sc_obj_id = capdl_util_make_sc_obj( + &mut spec, + &pd.name, + PD_SCHEDCONTEXT_EXTRA_SIZE_BITS as usize, + pd.period, + pd.budget, + 0x100 + pd_global_idx as u64, + ); + let pd_sc_cap = capdl_util_make_sc_cap(pd_sc_obj_id); + caps_to_bind_to_tcb.push((TcbBoundSlot::SchedContext as usize, pd_sc_cap)); + + // Step 3-5 Create fault Endpoint cap to parent/monitor + let pd_fault_ep_cap = if pd.parent.is_none() { + // badge = pd_global_idx + 1 because seL4 considers badge = 0 as no badge. + let badge: u64 = pd_global_idx as u64 + 1; + capdl_util_make_endpoint_cap(mon_fault_ep_obj_id, true, true, true, badge) + } else { + assert!(pd_global_idx > pd.parent.unwrap()); + let badge: u64 = FAULT_BADGE | pd.id.unwrap(); + let parent_ep_obj_id = pd_id_to_ep_id.get(&pd.parent.unwrap()).unwrap(); + let fault_ep_cap = + capdl_util_make_endpoint_cap(*parent_ep_obj_id, true, true, true, badge); + + // Allow the parent PD to access the child's TCB: + let parent_cspace_obj_id = pd_id_to_cspace_id.get(&pd.parent.unwrap()).unwrap(); + capdl_util_insert_cap_into_cspace( + &mut spec, + *parent_cspace_obj_id, + (PD_BASE_PD_TCB_CAP + pd.id.unwrap()) as usize, + capdl_util_make_tcb_cap(pd_tcb_obj_id), + ); + + fault_ep_cap + }; + caps_to_insert_to_pd_cspace.push((PD_FAULT_EP_CAP_IDX as usize, pd_fault_ep_cap.clone())); + caps_to_bind_to_tcb.push((TcbBoundSlot::FaultEp as usize, pd_fault_ep_cap.clone())); + + // Step 3-6 Create cap to Monitor's endpoint for passive PDs. + if pd.passive { + let pd_monitor_ep_cap = capdl_util_make_endpoint_cap( + mon_fault_ep_obj_id, + true, + true, + true, + pd_global_idx as u64 + 1, + ); + caps_to_insert_to_pd_cspace.push((PD_MONITOR_EP_CAP_IDX as usize, pd_monitor_ep_cap)); + } + + // Step 3-7 Create endpoint object for the PD if it has children or can receive PPCs, else it will be a notification + let pd_ntfn_obj_id = capdl_util_make_ntfn_obj(&mut spec, &pd.name); + let pd_ntfn_cap = capdl_util_make_ntfn_cap(pd_ntfn_obj_id, true, true, 0); + let mut pd_ep_obj_id: Option = None; + pd_id_to_ntfn_id.insert(pd_global_idx, pd_ntfn_obj_id); + if pd.needs_ep(pd_global_idx, &system.channels) { + pd_ep_obj_id = Some(capdl_util_make_endpoint_obj(&mut spec, &pd.name, false)); + let pd_ep_cap = + capdl_util_make_endpoint_cap(pd_ep_obj_id.unwrap(), true, true, true, 0); + pd_id_to_ep_id.insert(pd_global_idx, pd_ep_obj_id.unwrap()); + caps_to_insert_to_pd_cspace.push((PD_INPUT_CAP_IDX as usize, pd_ep_cap)); + } else { + let pd_ntfn_cap_clone = pd_ntfn_cap.clone(); + caps_to_insert_to_pd_cspace.push((PD_INPUT_CAP_IDX as usize, pd_ntfn_cap_clone)); + } + caps_to_bind_to_tcb.push((TcbBoundSlot::BoundNotification as usize, pd_ntfn_cap)); + + // Step 3-8 Create Reply obj + cap and insert into CSpace + let pd_reply_obj_id = capdl_util_make_reply_obj(&mut spec, &pd.name); + let pd_reply_cap = capdl_util_make_reply_cap(pd_reply_obj_id); + caps_to_insert_to_pd_cspace.push((PD_REPLY_CAP_IDX as usize, pd_reply_cap)); + + // Step 3-9 Create spec and caps to IRQs + for irq in pd.irqs.iter() { + // Create a IRQ handler cap and insert into the requested CSpace's slot. + let irq_handle_cap = + create_irq_handler_cap(&mut spec, kernel_config, &pd.name, pd_ntfn_obj_id, irq); + let irq_cap_idx = PD_BASE_IRQ_CAP + irq.id; + caps_to_insert_to_pd_cspace.push((irq_cap_idx as usize, irq_handle_cap)); + } + + // Step 3-10 Create I/O port objects on x86 platform. + for ioport in pd.ioports.iter() { + let ioport_obj_id = + capdl_util_make_ioport_obj(&mut spec, &pd.name, ioport.addr, ioport.size); + let ioport_cap = capdl_util_make_ioport_cap(ioport_obj_id); + caps_to_insert_to_pd_cspace + .push(((PD_BASE_IOPORT_CAP + ioport.id) as usize, ioport_cap)); + } + + // Step 3-11 Create VM Spec. + if let Some(virtual_machine) = &pd.virtual_machine { + // A VM really is just a collection of special threads, it has its own TCBs, Scheduling Contexts, etc... + // The difference is that it have a vCPU for each TCB to store the virtual CPUs' states. + + // Create VM's Address Space and map in all memory regions. + // This address space is shared across all vCPUs. The virtual address that we "map" the region is guest-physical. + let vm_vspace_obj_id = match kernel_config.arch { + Arch::X86_64 => create_vspace_ept(&mut spec, kernel_config, &virtual_machine.name), + _ => create_vspace(&mut spec, kernel_config, &virtual_machine.name), + }; + let vm_vspace_cap = capdl_util_make_page_table_cap(vm_vspace_obj_id); + for map in virtual_machine.maps.iter() { + let frames = mr_name_to_frames.get(&map.mr).unwrap(); + let page_size_bytes = + 1 << capdl_util_get_frame_size_bits(&spec, *frames.first().unwrap()); + map_memory_region( + &mut spec, + kernel_config, + &virtual_machine.name, + map, + page_size_bytes, + vm_vspace_obj_id, + frames, + ); + } + + if kernel_config.arch == Arch::X86_64 { + // Only support 1 vcpu on x86 right now. + assert_eq!(virtual_machine.vcpus.len(), 1); + let vcpu = virtual_machine.vcpus.first().unwrap(); + + // Create the vCPU object and bind it to the VMM PD. + let vm_vcpu_obj_id = capdl_util_make_vcpu_obj( + &mut spec, + &format!("{}_{}", virtual_machine.name, vcpu.id), + ); + let vcpu_cap = capdl_util_make_vcpu_cap(vm_vcpu_obj_id); + caps_to_bind_to_tcb.push((TcbBoundSlot::VCpu as usize, vcpu_cap.clone())); + + // Allow the parent PD to access the vCPU object. + caps_to_insert_to_pd_cspace.push(((PD_BASE_VCPU_CAP + vcpu.id) as usize, vcpu_cap)); + + // Bind the guest's root page table to the parent PD. + caps_to_bind_to_tcb.push((TcbBoundSlot::X86Eptpml4 as usize, vm_vspace_cap)); + } else { + for (vcpu_idx, vcpu) in virtual_machine.vcpus.iter().enumerate() { + // All vCPUs get to access the same address space. + let mut caps_to_bind_to_vm_tcbs: Vec = Vec::new(); + caps_to_bind_to_vm_tcbs + .push((TcbBoundSlot::VSpace as usize, vm_vspace_cap.clone())); + + // Create an empty CSpace + let vm_cnode_obj_id = capdl_util_make_cnode_obj( + &mut spec, + &format!("{}_{}", virtual_machine.name, vcpu.id), + PD_CAP_BITS as usize, + [].to_vec(), + ); + let vm_guard_size = kernel_config.cap_address_bits - PD_CAP_BITS; + let vm_cnode_cap = capdl_util_make_cnode_cap(vm_cnode_obj_id, 0, vm_guard_size); + caps_to_bind_to_vm_tcbs.push((TcbBoundSlot::CSpace as usize, vm_cnode_cap)); + + // Create and map the IPC buffer. + let vm_ipcbuf_frame_obj_id = capdl_util_make_frame_obj( + &mut spec, + FrameInit::Fill(Fill { + entries: [].to_vec(), + }), + &format!("ipcbuf_{}_{}", virtual_machine.name, vcpu.id), + None, + // Must be consistent with the granule bits used in spec serialisation + PageSize::Small.fixed_size_bits(kernel_config) as usize, + ); + let vm_ipcbuf_frame_cap = + capdl_util_make_frame_cap(vm_ipcbuf_frame_obj_id, true, true, false, true); + caps_to_bind_to_vm_tcbs + .push((TcbBoundSlot::IpcBuffer as usize, vm_ipcbuf_frame_cap)); + + // Create fault endpoint cap to the parent PD. + let vm_vcpu_fault_ep_cap = capdl_util_make_endpoint_cap( + pd_ep_obj_id.unwrap(), + true, + true, + true, + FAULT_BADGE | vcpu.id, + ); + caps_to_bind_to_vm_tcbs + .push((TcbBoundSlot::FaultEp as usize, vm_vcpu_fault_ep_cap)); + + // Create scheduling context + let vm_vcpu_sc_obj_id = capdl_util_make_sc_obj( + &mut spec, + &format!("{}_{}", virtual_machine.name, vcpu.id), + PD_SCHEDCONTEXT_EXTRA_SIZE_BITS as usize, + virtual_machine.period, + virtual_machine.budget, + 0x100 + vcpu_idx as u64, + ); + caps_to_bind_to_vm_tcbs.push(( + TcbBoundSlot::SchedContext as usize, + capdl_util_make_sc_cap(vm_vcpu_sc_obj_id), + )); + + // Create vCPU object + let vm_vcpu_obj_id = capdl_util_make_vcpu_obj( + &mut spec, + &format!("{}_{}", virtual_machine.name, vcpu.id), + ); + caps_to_bind_to_vm_tcbs.push(( + TcbBoundSlot::VCpu as usize, + capdl_util_make_vcpu_cap(vm_vcpu_obj_id), + )); + + // Finally create TCB, unlike PDs, VMs are suspended by default until resume'd by their parent. + let vm_vcpu_tcb_inner_obj = capdl_object::Tcb { + slots: caps_to_bind_to_vm_tcbs, + extra: capdl_object::TcbExtraInfo { + ipc_buffer_addr: 0, + affinity: 0, // @billn revisit for SMP, need a way to specify node id in the XML + prio: virtual_machine.priority, + max_prio: virtual_machine.priority, + resume: false, + // VMs do not have program images associated with them so these are always zero. + ip: 0, + sp: 0, + gprs: [].to_vec(), + master_fault_ep: None, // Not used on MCS kernel. + }, + }; + let vm_vcpu_tcb_obj_id = spec.add_root_object(NamedObject { + name: format!("tcb_{}_{}", virtual_machine.name, vcpu.id), + object: CapDLObject::Tcb(vm_vcpu_tcb_inner_obj), + expected_alloc: None, + }); + + // Allow parent PD to access this vCPU object and associated TCB + caps_to_insert_to_pd_cspace.push(( + (PD_BASE_VCPU_CAP + vcpu.id) as usize, + capdl_util_make_vcpu_cap(vm_vcpu_obj_id), + )); + caps_to_insert_to_pd_cspace.push(( + (PD_BASE_VM_TCB_CAP + vcpu.id) as usize, + capdl_util_make_tcb_cap(vm_vcpu_tcb_obj_id), + )); + + // Bind vCPU's TCB to the monitor so that the name can be set at start up in debug config + capdl_util_insert_cap_into_cspace( + &mut spec, + mon_cnode_obj_id, + MON_BASE_VM_TCB_CAP as usize + monitor_vcpu_idx, + capdl_util_make_tcb_cap(vm_vcpu_tcb_obj_id), + ); + monitor_vcpu_idx += 1; + } + } + } + + // Step 3-12 Create ARM SMC cap if requested. + if pd.smc { + caps_to_insert_to_pd_cspace.push(( + PD_ARM_SMC_CAP_IDX as usize, + capdl_util_make_arm_smc_cap(arm_smc_obj_id.unwrap()), + )); + } + + // Step 3-13 Create CSpace and add all caps that the PD code and libmicrokit need to access. + let pd_cnode_obj_id = capdl_util_make_cnode_obj( + &mut spec, + &pd.name, + PD_CAP_BITS as usize, + caps_to_insert_to_pd_cspace, + ); + let pd_guard_size = kernel_config.cap_address_bits - PD_CAP_BITS; + let pd_cnode_cap = capdl_util_make_cnode_cap(pd_cnode_obj_id, 0, pd_guard_size); + caps_to_bind_to_tcb.push((TcbBoundSlot::CSpace as usize, pd_cnode_cap)); + pd_id_to_cspace_id.insert(pd_global_idx, pd_cnode_obj_id); + + // Step 3-14 Set the TCB parameters and all the various caps that we need to bind to this TCB. + if let CapDLObject::Tcb(pd_tcb) = + &mut spec.get_root_object_mut(pd_tcb_obj_id).unwrap().object + { + pd_tcb.extra.sp = kernel_config.pd_stack_top(); + pd_tcb.extra.master_fault_ep = None; // Not used on MCS kernel. + pd_tcb.extra.prio = pd.priority; + pd_tcb.extra.max_prio = pd.priority; + pd_tcb.extra.resume = true; + + pd_tcb.slots.extend(caps_to_bind_to_tcb); + // Stylistic purposes only + pd_tcb.slots.sort_by_key(|cte| cte.0); + } else { + unreachable!("internal bug: build_capdl_spec() got a non TCB object ID when trying to set TCB parameters for the monitor."); + } + + // Step 3-15 bind this PD's TCB to the monitor, this accomplish two purposes: + // 1. Allow PDs' TCBs to be named to their proper name in SDF in debug config. + // 2. Allow passive PDs. + capdl_util_insert_cap_into_cspace( + &mut spec, + mon_cnode_obj_id, + MON_BASE_PD_TCB_CAP as usize + pd_global_idx, + capdl_util_make_tcb_cap(pd_tcb_obj_id), + ); + if pd.passive { + // When a PD is passive, it will signal the Monitor once init() returns. The monitor will + // then unbind the PD's TCB from its Scheduling Context and bind it to its Notification. + capdl_util_insert_cap_into_cspace( + &mut spec, + mon_cnode_obj_id, + MON_BASE_SCHED_CONTEXT_CAP as usize + pd_global_idx, + capdl_util_make_sc_cap(pd_sc_obj_id), + ); + capdl_util_insert_cap_into_cspace( + &mut spec, + mon_cnode_obj_id, + MON_BASE_NOTIFICATION_CAP as usize + pd_global_idx, + capdl_util_make_ntfn_cap(pd_ntfn_obj_id, true, true, 0), + ); + } + } + + // ********************************* + // Step 4. Create channels + // ********************************* + for channel in system.channels.iter() { + let pd_a_cspace_id = *pd_id_to_cspace_id.get(&channel.end_a.pd).unwrap(); + let pd_b_cspace_id = *pd_id_to_cspace_id.get(&channel.end_b.pd).unwrap(); + let pd_a_ntfn_id = *pd_id_to_ntfn_id.get(&channel.end_a.pd).unwrap(); + let pd_b_ntfn_id = *pd_id_to_ntfn_id.get(&channel.end_b.pd).unwrap(); + + // We trust that the SDF parsing code have checked for duplicate IDs. + if channel.end_a.notify { + let pd_a_ntfn_cap_idx = PD_BASE_OUTPUT_NOTIFICATION_CAP + channel.end_a.id; + let pd_a_ntfn_badge = 1 << channel.end_b.id; + let pd_a_ntfn_cap = capdl_util_make_ntfn_cap(pd_b_ntfn_id, true, true, pd_a_ntfn_badge); + capdl_util_insert_cap_into_cspace( + &mut spec, + pd_a_cspace_id, + pd_a_ntfn_cap_idx as usize, + pd_a_ntfn_cap, + ); + } + + if channel.end_b.notify { + let pd_b_ntfn_cap_idx = PD_BASE_OUTPUT_NOTIFICATION_CAP + channel.end_b.id; + let pd_b_ntfn_badge = 1 << channel.end_a.id; + let pd_b_ntfn_cap = capdl_util_make_ntfn_cap(pd_a_ntfn_id, true, true, pd_b_ntfn_badge); + capdl_util_insert_cap_into_cspace( + &mut spec, + pd_b_cspace_id, + pd_b_ntfn_cap_idx as usize, + pd_b_ntfn_cap, + ); + } + + if channel.end_a.pp { + let pd_a_ep_cap_idx = PD_BASE_OUTPUT_ENDPOINT_CAP + channel.end_a.id; + let pd_a_ep_badge = PPC_BADGE | channel.end_b.id; + let pd_b_ep_id = *pd_id_to_ep_id.get(&channel.end_b.pd).unwrap(); + let pd_a_ep_cap = + capdl_util_make_endpoint_cap(pd_b_ep_id, true, true, true, pd_a_ep_badge); + capdl_util_insert_cap_into_cspace( + &mut spec, + pd_a_cspace_id, + pd_a_ep_cap_idx as usize, + pd_a_ep_cap, + ); + } + + if channel.end_b.pp { + let pd_b_ep_cap_idx = PD_BASE_OUTPUT_ENDPOINT_CAP + channel.end_b.id; + let pd_b_ep_badge = PPC_BADGE | channel.end_a.id; + let pd_a_ep_id = *pd_id_to_ep_id.get(&channel.end_a.pd).unwrap(); + let pd_b_ep_cap = + capdl_util_make_endpoint_cap(pd_a_ep_id, true, true, true, pd_b_ep_badge); + capdl_util_insert_cap_into_cspace( + &mut spec, + pd_b_cspace_id, + pd_b_ep_cap_idx as usize, + pd_b_ep_cap, + ); + } + } + + // ********************************* + // Step 5. Sort the root objects + // ********************************* + // The CapDL initialiser expects objects with paddr to come first, then sorted by size so that the + // allocation algorithm at run-time can run more efficiently. + // Capabilities to objects in CapDL are referenced by the object's index in the root objects + // vector. Since sorting the objects will shuffle them, we need to: + // 1. Record all root objects name + original index. + // 2. Sort paddr first, size bits descending and break tie alphabetically. + // 3. Record all of the root objects new index. + // 4. Recurse through every cap, for any cap bearing the original object ID, write the new object ID. + + // Step 6-1 + let mut obj_name_to_old_id: HashMap = HashMap::new(); + for (id, obj) in spec.objects.iter().enumerate() { + obj_name_to_old_id.insert(obj.name.clone(), id); + } + + // Step 6-2 + spec.objects.sort_by(|a, b| { + // Objects with paddrs always come first. + if a.object.paddr().is_none() && b.object.paddr().is_some() { + return Ordering::Greater; + } else if a.object.paddr().is_some() && b.object.paddr().is_none() { + return Ordering::Less; + } + + // If both have paddrs, make the lower paddr come first. + if a.object.paddr().is_some() && b.object.paddr().is_some() { + let phys_addr_order = a.object.paddr().unwrap().cmp(&b.object.paddr().unwrap()); + if phys_addr_order != Ordering::Equal { + return phys_addr_order; + } + } + + // Both have no paddr or equal paddr, break tie by object size (descending) and name. + let size_cmp = a + .object + .physical_size_bits(kernel_config) + .cmp(&b.object.physical_size_bits(kernel_config)) + .reverse(); + if size_cmp == Ordering::Equal { + let name_cmp = a.name.cmp(&b.name); + if name_cmp == Ordering::Equal { + // Make sure the sorting function implement a total order to comply with .sort_by()'s doc. + unreachable!("internal bug: object names must be unique! {}", a.name); + } + name_cmp + } else { + size_cmp + } + }); + + // Step 6-3 + let mut obj_old_id_to_new_id: HashMap = HashMap::new(); + for (new_id, obj) in spec.objects.iter().enumerate() { + obj_old_id_to_new_id.insert(*obj_name_to_old_id.get(&obj.name).unwrap(), new_id); + } + + // Step 6-4 + for obj in spec.objects.iter_mut() { + match obj.object.get_cap_entries_mut() { + Some(caps) => { + for cap in caps { + let old_id = cap.1.obj(); + let new_id = obj_old_id_to_new_id.get(&old_id).unwrap(); + cap.1.set_obj(*new_id); + } + } + None => continue, + } + } + for irq in spec.irqs.iter_mut() { + irq.handler = *obj_old_id_to_new_id.get(&irq.handler).unwrap(); + } + + // Only for aesthetic purposes: + // Sort cap entries by their index. + spec.irqs.sort_by_key(|irq_entry| irq_entry.irq); + spec.objects + .iter_mut() + .filter(|named_object| matches!(named_object.object, CapDLObject::CNode(_))) + .for_each(|cnode_named_obj: &mut NamedObject| { + cnode_named_obj + .object + .get_cap_entries_mut() + .unwrap() + .sort_by_key(|(cap_addr, _)| *cap_addr) + }); + + Ok(spec) +} diff --git a/tool/microkit/src/capdl/initialiser.rs b/tool/microkit/src/capdl/initialiser.rs new file mode 100644 index 000000000..6ce7d3682 --- /dev/null +++ b/tool/microkit/src/capdl/initialiser.rs @@ -0,0 +1,137 @@ +// +// Copyright 2025, UNSW +// +// SPDX-License-Identifier: BSD-2-Clause +// + +use std::ops::Range; + +use crate::elf::ElfSegmentData; +use crate::util::round_up; +use crate::{elf::ElfFile, sel4::PageSize}; +use crate::{serialise_ut, UntypedObject}; + +// The capDL initialiser heap size is calculated by: +// (spec size * multiplier) + INITIALISER_HEAP_ADD_ON_CONSTANT +pub const DEFAULT_INITIALISER_HEAP_MULTIPLIER: f64 = 2.0; +const INITIALISER_HEAP_ADD_ON_CONSTANT: u64 = 16 * 4096; +// Page size used for allocating the spec and heap segments. +pub const INITIALISER_GRANULE_SIZE: PageSize = PageSize::Small; + +pub struct CapDLInitialiser { + pub elf: ElfFile, + pub heap_multiplier: f64, + pub spec_size: Option, + pub heap_size: Option, +} + +impl CapDLInitialiser { + pub fn new(elf: ElfFile, heap_multiplier: f64) -> CapDLInitialiser { + CapDLInitialiser { + elf, + heap_multiplier, + spec_size: None, + heap_size: None, + } + } + + pub fn image_bound(&self) -> Range { + self.elf.lowest_vaddr()..round_up(self.elf.highest_vaddr(), INITIALISER_GRANULE_SIZE as u64) + } + + pub fn add_spec(&mut self, payload: Vec) { + if self.spec_size.is_some() || self.heap_size.is_some() { + unreachable!("internal bug: CapDLInitialiser::add_spec() called more than once"); + } + + let spec_vaddr = self.elf.next_vaddr(INITIALISER_GRANULE_SIZE); + let spec_size = payload.len() as u64; + self.elf.add_segment( + true, + false, + false, + spec_vaddr, + ElfSegmentData::RealData(payload), + ); + + // These symbol names must match rust-sel4/crates/sel4-capdl-initializer/src/main.rs + self.elf + .write_symbol( + "sel4_capdl_initializer_serialized_spec_start", + &spec_vaddr.to_le_bytes(), + ) + .unwrap(); + self.elf + .write_symbol( + "sel4_capdl_initializer_serialized_spec_size", + &spec_size.to_le_bytes(), + ) + .unwrap(); + + // Very important to make the heap the last region in memory so we can optimise the bootable image size later. + let heap_vaddr = self.elf.next_vaddr(INITIALISER_GRANULE_SIZE); + let heap_size = round_up( + (spec_size as f64 * self.heap_multiplier) as u64 + INITIALISER_HEAP_ADD_ON_CONSTANT, + INITIALISER_GRANULE_SIZE as u64, + ); + self.elf.add_segment( + true, + true, + false, + heap_vaddr, + ElfSegmentData::UninitialisedData(heap_size), + ); + self.elf + .write_symbol( + "sel4_capdl_initializer_heap_start", + &heap_vaddr.to_le_bytes(), + ) + .unwrap(); + self.elf + .write_symbol("sel4_capdl_initializer_heap_size", &heap_size.to_le_bytes()) + .unwrap(); + + self.elf + .write_symbol( + "sel4_capdl_initializer_image_start", + &self.elf.lowest_vaddr().to_le_bytes(), + ) + .unwrap(); + self.elf + .write_symbol( + "sel4_capdl_initializer_image_end", + &self.elf.highest_vaddr().to_le_bytes(), + ) + .unwrap(); + + self.spec_size = Some(spec_size); + self.heap_size = Some(heap_size); + } + + pub fn replace_spec(&mut self, new_payload: Vec) { + if self.spec_size.is_none() || self.heap_size.is_none() { + unreachable!("internal bug: CapDLInitialiser::replace_spec() called when no spec have been added before"); + } + + self.elf.segments.pop(); + self.elf.segments.pop(); + self.add_spec(new_payload); + } + + pub fn add_expected_untypeds(&mut self, untypeds: &[UntypedObject]) { + let mut uts_desc: Vec = Vec::new(); + for ut in untypeds.iter() { + uts_desc.extend(serialise_ut(ut)); + } + + self.elf + .write_symbol( + "sel4_capdl_initializer_expected_untypeds_list_num_entries", + &(untypeds.len() as u64).to_le_bytes(), + ) + .unwrap(); + self.elf + .write_symbol("sel4_capdl_initializer_expected_untypeds_list", &uts_desc) + .unwrap(); + } +} diff --git a/tool/microkit/src/capdl/irq.rs b/tool/microkit/src/capdl/irq.rs new file mode 100644 index 000000000..a12a3d876 --- /dev/null +++ b/tool/microkit/src/capdl/irq.rs @@ -0,0 +1,146 @@ +// +// Copyright 2025, UNSW +// +// SPDX-License-Identifier: BSD-2-Clause +// + +use sel4_capdl_initializer_types::{ + cap, + object::{ArmIrqExtraInfo, IrqIOApicExtraInfo, IrqMsiExtraInfo, RiscvIrqExtraInfo}, + Cap, IrqEntry, ObjectId, +}; + +use crate::{ + capdl::{spec::*, util::capdl_util_make_ntfn_cap, CapDLSpec}, + sdf::{SysIrq, SysIrqKind}, + sel4::{Arch, Config}, +}; + +/// Create all the objects needed in the spec for the requested IRQ. +/// Returns an IRQ handler Cap for insertion into the PD's CSpace. +pub fn create_irq_handler_cap( + spec: &mut CapDLSpec, + sel4_config: &Config, + pd_name: &str, + pd_ntfn_obj_id: ObjectId, + irq_desc: &SysIrq, +) -> Cap { + // Create the IRQ object and add it to the special `irqs` vec in the spec. + // This is a pseudo object so we can bind a cap to the IRQ + let irq_obj_id = create_irq_obj(spec, sel4_config, pd_name, irq_desc); + + // Create the real IRQ in the separate IRQ vector. + spec.irqs.push(IrqEntry { + irq: irq_desc.irq_num(), + handler: irq_obj_id, + }); + + // Bind IRQ into the PD's notification with the correct badge + let pd_irq_ntfn_cap = capdl_util_make_ntfn_cap(pd_ntfn_obj_id, true, true, 1 << irq_desc.id); + bind_irq_to_ntfn(spec, irq_obj_id, pd_irq_ntfn_cap); + + // Create a IRQ handler cap + make_irq_handler_cap(sel4_config, irq_obj_id, &irq_desc.kind) +} + +fn create_irq_obj( + spec: &mut CapDLSpec, + sel4_config: &Config, + pd_name: &str, + irq_desc: &SysIrq, +) -> ObjectId { + let irq_inner_obj = match irq_desc.kind { + SysIrqKind::Conventional { trigger, .. } => match sel4_config.arch { + Arch::Aarch64 => CapDLObject::ArmIrq(capdl_object::ArmIrq { + slots: [].to_vec(), + extra: ArmIrqExtraInfo { + trigger: trigger as u64, + target: 0, // @billn revisit for SMP + }, + }), + Arch::Riscv64 => CapDLObject::RiscvIrq(capdl_object::RiscvIrq { + slots: [].to_vec(), + extra: RiscvIrqExtraInfo { + trigger: trigger as u64, + }, + }), + Arch::X86_64 => unreachable!( + "create_irq_obj(): internal bug: ARM and RISC-V IRQs not supported on x86." + ), + }, + SysIrqKind::IOAPIC { + ioapic, + pin, + trigger, + polarity, + .. + } => CapDLObject::IrqIOApic(capdl_object::IrqIOApic { + slots: [].to_vec(), + extra: IrqIOApicExtraInfo { + ioapic, + pin, + level: trigger as u64, + polarity: polarity as u64, + }, + }), + SysIrqKind::MSI { + pci_bus, + pci_dev, + pci_func, + handle, + .. + } => CapDLObject::IrqMsi(capdl_object::IrqMsi { + slots: [].to_vec(), + extra: IrqMsiExtraInfo { + handle, + pci_bus, + pci_dev, + pci_func, + }, + }), + }; + let irq_obj = NamedObject { + name: format!("irq_{}_{}", irq_desc.irq_num(), pd_name), + object: irq_inner_obj, + expected_alloc: None, + }; + spec.add_root_object(irq_obj) +} + +fn bind_irq_to_ntfn(spec: &mut CapDLSpec, irq_obj_id: ObjectId, ntfn_cap: Cap) { + match &mut spec.get_root_object_mut(irq_obj_id).unwrap().object { + CapDLObject::ArmIrq(arm_irq) => { + arm_irq.slots.push((0, ntfn_cap)); + } + CapDLObject::IrqMsi(irq_msi) => { + irq_msi.slots.push((0, ntfn_cap)); + } + CapDLObject::IrqIOApic(irq_ioapic) => { + irq_ioapic.slots.push((0, ntfn_cap)); + } + CapDLObject::RiscvIrq(riscv_irq) => { + riscv_irq.slots.push((0, ntfn_cap)); + } + _ => unreachable!( + "bind_irq_to_ntfn(): internal bug: got non irq object id {} with name '{}'", + irq_obj_id, + spec.get_root_object(irq_obj_id).unwrap().name + ), + } +} + +fn make_irq_handler_cap(sel4_config: &Config, irq_obj_id: ObjectId, irq_kind: &SysIrqKind) -> Cap { + match irq_kind { + SysIrqKind::Conventional { .. } => match sel4_config.arch { + Arch::Aarch64 => Cap::ArmIrqHandler(cap::ArmIrqHandler { object: irq_obj_id }), + Arch::Riscv64 => Cap::RiscvIrqHandler(cap::RiscvIrqHandler { object: irq_obj_id }), + Arch::X86_64 => unreachable!( + "make_irq_handler_cap(): internal bug: ARM and RISC-V IRQs not supported on x86." + ), + }, + SysIrqKind::IOAPIC { .. } => { + Cap::IrqIOApicHandler(cap::IrqIOApicHandler { object: irq_obj_id }) + } + SysIrqKind::MSI { .. } => Cap::IrqMsiHandler(cap::IrqMsiHandler { object: irq_obj_id }), + } +} diff --git a/tool/microkit/src/capdl/memory.rs b/tool/microkit/src/capdl/memory.rs new file mode 100644 index 000000000..8c9665fc6 --- /dev/null +++ b/tool/microkit/src/capdl/memory.rs @@ -0,0 +1,323 @@ +// +// Copyright 2025, UNSW +// +// SPDX-License-Identifier: BSD-2-Clause +// +use crate::{ + capdl::{ + spec::{capdl_object::PageTable, CapDLObject, NamedObject}, + CapDLSpec, + }, + sel4::{Arch, Config, PageSize}, +}; +use sel4_capdl_initializer_types::{cap, Cap, ObjectId}; +use std::ops::Range; + +/// For naming and debugging purposes only, no functional purpose. +fn get_pt_level_name(sel4_config: &Config, level: usize) -> &str { + match sel4_config.arch { + crate::sel4::Arch::Aarch64 => match level { + 0 => "pgd", + 1 => "pud", + 2 => "pd", + 3 => "pt", + _ => unreachable!( + "get_pt_level_name(): internal bug: unknown page table level {} for aarch64", + level + ), + }, + crate::sel4::Arch::Riscv64 => match level { + 0 => "pgd", + 1 => "pmd", + 2 => "pte", + _ => unreachable!( + "get_pt_level_name(): internal bug: unknown page table level {} for riscv64", + level + ), + }, + crate::sel4::Arch::X86_64 => match level { + 0 => "pml4", + 1 => "pdpt", + 2 => "pd", + 3 => "pt", + _ => unreachable!( + "get_pt_level_name(): internal bug: unknown page table level {} for x86_64", + level + ), + }, + } +} + +fn get_pt_level_index(sel4_config: &Config, level: usize, vaddr: u64) -> u64 { + let levels = sel4_config.num_page_table_levels(); + + assert!(level < levels); + + let index_bits = |level: usize| -> u64 { + if level == top_pt_level_number(sel4_config) + && sel4_config.arch == Arch::Aarch64 + && sel4_config.aarch64_vspace_s2_start_l1() + { + // Special case for first level on AArch64 platforms with hyp and 40 bits PA. + // It have 10 bits index for VSpace. + // match up with seL4_VSpaceBits in seL4/libsel4/sel4_arch_include/aarch64/sel4/sel4_arch/constants.h + 10 + } else { + 9 + } + }; + + let page_bits = 12; + let bits_from_higher_lvls: u64 = ((level + 1)..levels).map(index_bits).sum(); + let shift = page_bits + bits_from_higher_lvls; + let width = index_bits(level); + let mask = (1u64 << width) - 1; + + (vaddr >> shift) & mask +} + +fn get_pt_level_coverage(sel4_config: &Config, level: usize, vaddr: u64) -> Range { + let levels = sel4_config.num_page_table_levels() as u64; + let page_bits = 12; + let bits_from_higher_lvls: u64 = (levels - (level as u64)) * 9; + + let coverage_bits = page_bits + bits_from_higher_lvls; + + let low = (vaddr >> coverage_bits) << coverage_bits; + let high = vaddr | ((1 << coverage_bits) - 1); + + low..high +} + +fn get_pt_level_to_insert(sel4_config: &Config, page_size_bytes: u64) -> usize { + const SMALL_PAGE_BYTES: u64 = PageSize::Small as u64; + const LARGE_PAGE_BYTES: u64 = PageSize::Large as u64; + match page_size_bytes { + SMALL_PAGE_BYTES => sel4_config.num_page_table_levels() - 1, + LARGE_PAGE_BYTES => sel4_config.num_page_table_levels() - 2, + _ => unreachable!( + "internal bug: get_pt_level_to_insert(): unknown page_size_bytes: {page_size_bytes}" + ), + } +} + +fn top_pt_level_number(sel4_config: &Config) -> usize { + if sel4_config.arch == Arch::Aarch64 && sel4_config.aarch64_vspace_s2_start_l1() { + 1 + } else { + 0 + } +} + +fn insert_cap_into_page_table_level( + spec: &mut CapDLSpec, + cur_level_obj_id: ObjectId, + cur_level: usize, + cur_level_slot: u64, + cap: Cap, +) -> Result<(), String> { + let page_table_level_obj_wrapper = spec.get_root_object_mut(cur_level_obj_id).unwrap(); + if let CapDLObject::PageTable(page_table_object) = &mut page_table_level_obj_wrapper.object { + // Sanity check that this slot is free + match page_table_object + .slots + .iter() + .find(|cte| cte.0 == cur_level_slot as usize) + { + Some(_) => Err(format!( + "insert_cap_into_page_table_level(): internal bug: slot {} at PT level {} with name '{}' already filled", + cur_level_slot, cur_level, spec.get_root_object(cur_level_obj_id).unwrap().name + )), + None => { + page_table_object.slots.push((cur_level_slot as usize, cap)); + Ok(()) + } + } + } else { + Err(format!( + "insert_cap_into_page_table_level(): internal bug: received a non-Page Table object id {} with name '{}'", + cur_level_obj_id, spec.get_root_object(cur_level_obj_id).unwrap().name + )) + } +} + +// Just this one time pinky promise +#[allow(clippy::too_many_arguments)] +fn map_intermediary_level_helper( + spec: &mut CapDLSpec, + sel4_config: &Config, + pd_name: &str, + next_level_name_prefix: &str, + vspace_obj_id: ObjectId, + cur_level_obj_id: ObjectId, + cur_level: usize, + cur_level_slot: u64, + vaddr: u64, +) -> Result { + let page_table_level_obj_wrapper = spec.get_root_object(cur_level_obj_id).unwrap(); + if let CapDLObject::PageTable(page_table_object) = &page_table_level_obj_wrapper.object { + match page_table_object + .slots + .iter() + .find(|cte| cte.0 == cur_level_slot as usize) + { + Some(cte_unwrapped) => { + // Next level object already created, nothing to do here + return Ok(cte_unwrapped.1.obj()); + } + None => { + // We need to create the next level paging structure, get out of this scope for now + // so we don't get a double mutable borrow of spec when we need to insert the next level object + } + } + } else { + return Err(format!("map_intermediary_level_helper(): internal bug: received a non-Page Table object id {} with name '{}', for mapping at level {}, to pd {}.", + cur_level_obj_id, spec.get_root_object(cur_level_obj_id).unwrap().name, cur_level, pd_name)); + } + + // Next level object not already created, create it. + let vspace_obj = match &spec.get_root_object(vspace_obj_id).unwrap().object { + CapDLObject::PageTable(o) => o, + _ => unreachable!( + "map_intermediary_level_helper(): internal bug: received a non VSpace object id {} with name '{}'", + vspace_obj_id, spec.get_root_object(vspace_obj_id).unwrap().name + ), + }; + let next_level_coverage = get_pt_level_coverage(sel4_config, cur_level + 1, vaddr); + let next_level_inner_obj = PageTable { + x86_ept: vspace_obj.x86_ept, + is_root: false, // because the VSpace has already been created separately + level: Some(cur_level as u8 + 1), + slots: [].to_vec(), + }; + // We create name with this PT level coverage so that every object names are unique + let next_level_object = NamedObject { + name: format!( + "{}_{}_vaddr_0x{:x}", + next_level_name_prefix, pd_name, next_level_coverage.start + ), + object: CapDLObject::PageTable(next_level_inner_obj), + expected_alloc: None, + }; + let next_level_obj_id = spec.add_root_object(next_level_object); + let next_level_cap = Cap::PageTable(cap::PageTable { + object: next_level_obj_id, + }); + + // Then insert into the correct slot at the current level, return and continue mapping + match insert_cap_into_page_table_level( + spec, + cur_level_obj_id, + cur_level, + cur_level_slot, + next_level_cap, + ) { + Ok(_) => Ok(next_level_obj_id), + Err(err_reason) => Err(err_reason), + } +} + +pub fn create_vspace(spec: &mut CapDLSpec, sel4_config: &Config, pd_name: &str) -> ObjectId { + spec.add_root_object(NamedObject { + name: format!( + "{}_{}", + get_pt_level_name(sel4_config, top_pt_level_number(sel4_config)), + pd_name + ), + object: CapDLObject::PageTable(PageTable { + x86_ept: false, + is_root: true, + level: Some(top_pt_level_number(sel4_config) as u8), + slots: [].to_vec(), + }), + expected_alloc: None, + }) +} + +pub fn create_vspace_ept(spec: &mut CapDLSpec, sel4_config: &Config, vm_name: &str) -> ObjectId { + assert!(sel4_config.arch == Arch::X86_64); + + spec.add_root_object(NamedObject { + name: format!("{}_{}", get_pt_level_name(sel4_config, 0), vm_name), + object: CapDLObject::PageTable(PageTable { + x86_ept: true, + is_root: true, + level: Some(top_pt_level_number(sel4_config) as u8), + slots: [].to_vec(), + }), + expected_alloc: None, + }) +} + +#[allow(clippy::too_many_arguments)] +fn map_recursive( + spec: &mut CapDLSpec, + sel4_config: &Config, + pd_name: &str, + vspace_obj_id: ObjectId, + pt_obj_id: ObjectId, + cur_level: usize, + frame_cap: Cap, + frame_size_bytes: u64, + vaddr: u64, +) -> Result<(), String> { + if cur_level >= sel4_config.num_page_table_levels() { + unreachable!("internal bug: we should have never recursed further!"); + } + + let this_level_index = get_pt_level_index(sel4_config, cur_level, vaddr); + + if cur_level == get_pt_level_to_insert(sel4_config, frame_size_bytes) { + // Base case: we got to the target level to insert the frame cap. + insert_cap_into_page_table_level(spec, pt_obj_id, cur_level, this_level_index, frame_cap) + } else { + // Recursive case: we have not gotten to the correct level, create the next level and recurse down. + let next_level_name_prefix = get_pt_level_name(sel4_config, cur_level + 1); + match map_intermediary_level_helper( + spec, + sel4_config, + pd_name, + next_level_name_prefix, + vspace_obj_id, + pt_obj_id, + cur_level, + this_level_index, + vaddr, + ) { + Ok(next_level_pt_obj_id) => map_recursive( + spec, + sel4_config, + pd_name, + vspace_obj_id, + next_level_pt_obj_id, + cur_level + 1, + frame_cap, + frame_size_bytes, + vaddr, + ), + Err(err_reason) => Err(err_reason), + } + } +} + +pub fn map_page( + spec: &mut CapDLSpec, + sel4_config: &Config, + pd_name: &str, + vspace_obj_id: ObjectId, + frame_cap: Cap, + frame_size_bytes: u64, + vaddr: u64, +) -> Result<(), String> { + map_recursive( + spec, + sel4_config, + pd_name, + vspace_obj_id, + vspace_obj_id, + top_pt_level_number(sel4_config), + frame_cap, + frame_size_bytes, + vaddr, + ) +} diff --git a/tool/microkit/src/capdl/mod.rs b/tool/microkit/src/capdl/mod.rs new file mode 100644 index 000000000..e6f2d48f4 --- /dev/null +++ b/tool/microkit/src/capdl/mod.rs @@ -0,0 +1,17 @@ +// +// Copyright 2025, UNSW +// +// SPDX-License-Identifier: BSD-2-Clause +// + +pub mod allocation; +pub mod builder; +pub mod initialiser; +mod irq; +mod memory; +pub mod reserialise_spec; +pub mod spec; +mod util; + +pub use self::builder::*; +pub use self::reserialise_spec::*; diff --git a/tool/microkit/src/capdl/reserialise_spec.rs b/tool/microkit/src/capdl/reserialise_spec.rs new file mode 100644 index 000000000..b6d09e82a --- /dev/null +++ b/tool/microkit/src/capdl/reserialise_spec.rs @@ -0,0 +1,83 @@ +// +// Copyright 2023, Colias Group, LLC +// Copyright 2025, UNSW +// +// SPDX-License-Identifier: BSD-2-Clause +// + +// A simple reimplementation of +// https://github.com/seL4/rust-sel4/blob/6f8d1baaad3aaca6f20966a2acb40e4651546519/crates/sel4-capdl-initializer/add-spec/src/reserialize_spec.rs +// We can't reuse the original code because it assumes that we are loading ELF frames from files. +// Which isn't suitable for Microkit as we want to embed the frames' data directly into the spec for +// easily patching ELF symbols. + +use std::ops::Range; + +use sel4_capdl_initializer_types::*; + +use crate::{capdl::spec::ElfContent, elf::ElfFile}; + +// @billn TODO: instead of doing this serialise our type -> deserialise into their type -> serialise business +// we can directly insert IndirectObjectName and IndirectDeflatedBytesContent into our spec type +// and one shot serialise at the cost of more complicated type definitions in spec.rs. +// But this is more of a performance concern rather than a bug. + +// Given a `Spec` data structure from sel4_capdl_initializer_types, "flatten" it into a vector of bytes +// for encapsulating it into the CapDL initialiser ELF. +pub fn reserialise_spec( + elfs: &[ElfFile], + input_spec: &Spec<'static, String, ElfContent, ()>, + object_names_level: &ObjectNamesLevel, +) -> Vec { + // A data structure to manage allocation of buffers in the flattened spec. + let mut sources = SourcesBuilder::new(); + + let final_spec = input_spec + // This first step applies the debugging level from `object_names_level` to all root object + // and copy them into `sources`. + .traverse_names_with_context(|named_obj| { + object_names_level + .apply(named_obj) + .map(|s| IndirectObjectName { + range: sources.append(s.as_bytes()), + }) + }) + // The final step is to take the frame data and compress it using miniz_oxide::deflate::compress_to_vec() + // to save memory then append it to `sources`. + .traverse_data(|data| IndirectDeflatedBytesContent { + deflated_bytes_range: sources.append(&DeflatedBytesContent::pack( + &elfs + .get(data.elf_id) + .unwrap() + .segments + .get(data.elf_seg_idx) + .unwrap() + .data()[data.elf_seg_data_range.clone()], + )), + }); + + let mut blob = postcard::to_allocvec(&final_spec).unwrap(); + blob.extend(sources.build()); + blob +} + +struct SourcesBuilder { + buf: Vec, +} + +impl SourcesBuilder { + fn new() -> Self { + Self { buf: vec![] } + } + + fn build(self) -> Vec { + self.buf + } + + fn append(&mut self, bytes: &[u8]) -> Range { + let start = self.buf.len(); + self.buf.extend(bytes); + let end = self.buf.len(); + start..end + } +} diff --git a/tool/microkit/src/capdl/spec.rs b/tool/microkit/src/capdl/spec.rs new file mode 100644 index 000000000..bb143366e --- /dev/null +++ b/tool/microkit/src/capdl/spec.rs @@ -0,0 +1,247 @@ +// +// Copyright 2025, UNSW +// +// SPDX-License-Identifier: BSD-2-Clause +// +use core::ops::Range; +use sel4_capdl_initializer_types::{ + object::{AsidPool, IOPorts, SchedContext}, + CapTableEntry, Word, +}; +use serde::{Deserialize, Serialize}; + +use crate::{ + capdl::SLOT_BITS, + sel4::{Config, ObjectType, PageSize}, +}; + +#[derive(Clone, Eq, PartialEq)] +pub struct ExpectedAllocation { + pub ut_idx: usize, + pub paddr: u64, +} + +#[derive(Serialize, Clone, Eq, PartialEq)] +pub struct NamedObject { + pub name: String, + pub object: CapDLObject, + + // Internal Microkit tool use only, to keep tabs of + // where objects will be allocated for the report. + #[serde(skip_serializing)] + pub expected_alloc: Option, +} + +#[derive(Serialize, Clone, Eq, PartialEq)] +pub enum FrameInit { + Fill(Fill), +} + +#[derive(Serialize, Clone, Eq, PartialEq)] +pub struct Fill { + pub entries: Vec, +} + +#[derive(Serialize, Clone, Eq, PartialEq)] +pub struct FillEntry { + pub range: Range, + pub content: FillEntryContent, +} + +#[derive(Serialize, Clone, Eq, PartialEq)] +pub enum FillEntryContent { + Data(ElfContent), +} + +#[derive(Serialize, Deserialize, Clone, Eq, PartialEq)] +pub struct ElfContent { + pub elf_id: usize, + pub elf_seg_idx: usize, + pub elf_seg_data_range: Range, +} + +#[derive(Serialize, Clone, Eq, PartialEq)] +pub enum CapDLObject { + Endpoint, + Notification, + CNode(capdl_object::CNode), + Tcb(capdl_object::Tcb), + VCpu, + Frame(capdl_object::Frame), + PageTable(capdl_object::PageTable), + AsidPool(AsidPool), + ArmIrq(capdl_object::ArmIrq), + IrqMsi(capdl_object::IrqMsi), + IrqIOApic(capdl_object::IrqIOApic), + RiscvIrq(capdl_object::RiscvIrq), + IOPorts(IOPorts), + SchedContext(SchedContext), + Reply, + ArmSmc, +} + +impl CapDLObject { + pub fn paddr(&self) -> Option { + match self { + CapDLObject::Frame(obj) => obj.paddr, + _ => None, + } + } + + /// CNode and SchedContext are quirky as they have variable size. + pub fn physical_size_bits(&self, sel4_config: &Config) -> u64 { + match self { + CapDLObject::Endpoint => ObjectType::Endpoint.fixed_size_bits(sel4_config).unwrap(), + CapDLObject::Notification => ObjectType::Notification + .fixed_size_bits(sel4_config) + .unwrap(), + CapDLObject::CNode(cnode) => cnode.size_bits as u64 + SLOT_BITS, + CapDLObject::Tcb(_) => ObjectType::Tcb.fixed_size_bits(sel4_config).unwrap(), + CapDLObject::VCpu => ObjectType::Vcpu.fixed_size_bits(sel4_config).unwrap(), + CapDLObject::Frame(frame) => frame.size_bits as u64, + CapDLObject::PageTable(pt) => { + if pt.is_root { + ObjectType::VSpace.fixed_size_bits(sel4_config).unwrap() + } else { + ObjectType::PageTable.fixed_size_bits(sel4_config).unwrap() + } + } + CapDLObject::AsidPool(_) => ObjectType::AsidPool.fixed_size_bits(sel4_config).unwrap(), + CapDLObject::SchedContext(sched_context) => sched_context.size_bits as u64, + CapDLObject::Reply => ObjectType::Reply.fixed_size_bits(sel4_config).unwrap(), + _ => 0, + } + } + + pub fn get_cap_entries(&self) -> Option<&Vec> { + match self { + CapDLObject::CNode(cnode) => Some(&cnode.slots), + CapDLObject::Tcb(tcb) => Some(&tcb.slots), + CapDLObject::PageTable(page_table) => Some(&page_table.slots), + CapDLObject::ArmIrq(arm_irq) => Some(&arm_irq.slots), + CapDLObject::IrqMsi(irq_msi) => Some(&irq_msi.slots), + CapDLObject::IrqIOApic(irq_ioapic) => Some(&irq_ioapic.slots), + CapDLObject::RiscvIrq(riscv_irq) => Some(&riscv_irq.slots), + _ => None, + } + } + + pub fn get_cap_entries_mut(&mut self) -> Option<&mut Vec> { + match self { + CapDLObject::CNode(cnode) => Some(&mut cnode.slots), + CapDLObject::Tcb(tcb) => Some(&mut tcb.slots), + CapDLObject::PageTable(page_table) => Some(&mut page_table.slots), + CapDLObject::ArmIrq(arm_irq) => Some(&mut arm_irq.slots), + CapDLObject::IrqMsi(irq_msi) => Some(&mut irq_msi.slots), + CapDLObject::IrqIOApic(irq_ioapic) => Some(&mut irq_ioapic.slots), + CapDLObject::RiscvIrq(riscv_irq) => Some(&mut riscv_irq.slots), + _ => None, + } + } + + pub fn human_name(&self, sel4_config: &Config) -> &str { + match self { + CapDLObject::Endpoint => "Endpoint", + CapDLObject::Notification => "Notification", + CapDLObject::CNode(_) => "CNode", + CapDLObject::Tcb(_) => "TCB", + CapDLObject::VCpu => "VCPU", + CapDLObject::Frame(frame) => { + if frame.size_bits == PageSize::Small.fixed_size_bits(sel4_config) as usize { + "Page(4 KiB)" + } else if frame.size_bits == PageSize::Large.fixed_size_bits(sel4_config) as usize { + "Page(2 MiB)" + } else { + unreachable!("unknown frame size bits {}", frame.size_bits); + } + } + CapDLObject::PageTable(_) => "PageTable", + CapDLObject::AsidPool(_) => "AsidPool", + CapDLObject::ArmIrq(_) => "ARM IRQ", + CapDLObject::IrqMsi(_) => "x86 MSI IRQ", + CapDLObject::IrqIOApic(_) => "x86 IOAPIC IRQ", + CapDLObject::RiscvIrq(_) => "RISC-V IRQ", + CapDLObject::IOPorts(_) => "x86 I/O Ports", + CapDLObject::SchedContext(_) => "SchedContext", + CapDLObject::Reply => "Reply", + CapDLObject::ArmSmc => "ARM SMC", + } + } +} + +pub mod capdl_object { + use sel4_capdl_initializer_types::{ + object::{ArmIrqExtraInfo, IrqIOApicExtraInfo, IrqMsiExtraInfo, RiscvIrqExtraInfo}, + CPtr, + }; + + use super::*; + /// Any object that takes a size bits is in addition to the base size + + #[derive(Serialize, Clone, Eq, PartialEq)] + pub struct CNode { + pub size_bits: usize, + pub slots: Vec, + } + + #[derive(Serialize, Clone, Eq, PartialEq)] + pub struct Tcb { + pub slots: Vec, + pub extra: TcbExtraInfo, + } + + #[derive(Serialize, Clone, Eq, PartialEq)] + pub struct TcbExtraInfo { + pub ipc_buffer_addr: Word, + + pub affinity: Word, + pub prio: u8, + pub max_prio: u8, + pub resume: bool, + + pub ip: Word, + pub sp: Word, + pub gprs: Vec, + + pub master_fault_ep: Option, + } + + #[derive(Serialize, Clone, Eq, PartialEq)] + pub struct Frame { + pub size_bits: usize, + pub paddr: Option, + pub init: FrameInit, + } + + #[derive(Serialize, Clone, Eq, PartialEq)] + pub struct PageTable { + pub x86_ept: bool, + pub is_root: bool, + pub level: Option, + pub slots: Vec, + } + + #[derive(Serialize, Clone, Eq, PartialEq)] + pub struct ArmIrq { + pub slots: Vec, + pub extra: ArmIrqExtraInfo, + } + + #[derive(Serialize, Clone, Eq, PartialEq)] + pub struct IrqMsi { + pub slots: Vec, + pub extra: IrqMsiExtraInfo, + } + + #[derive(Serialize, Clone, Eq, PartialEq)] + pub struct IrqIOApic { + pub slots: Vec, + pub extra: IrqIOApicExtraInfo, + } + + #[derive(Serialize, Clone, Eq, PartialEq)] + pub struct RiscvIrq { + pub slots: Vec, + pub extra: RiscvIrqExtraInfo, + } +} diff --git a/tool/microkit/src/capdl/util.rs b/tool/microkit/src/capdl/util.rs new file mode 100644 index 000000000..478bd50e6 --- /dev/null +++ b/tool/microkit/src/capdl/util.rs @@ -0,0 +1,296 @@ +// +// Copyright 2025, UNSW +// +// SPDX-License-Identifier: BSD-2-Clause +// + +use crate::capdl::{ + builder::PD_CAP_SIZE, + spec::{ + capdl_object::{CNode, Frame}, + CapDLObject, FrameInit, NamedObject, + }, + CapDLSpec, +}; +use sel4_capdl_initializer_types::{ + cap, + object::{IOPorts, SchedContext, SchedContextExtraInfo}, + Cap, CapTableEntry, ObjectId, Rights, +}; + +// This module contains utility functions used by higher-level +// CapDL spec generation code. For simplicity, this code will trust +// all arguments given to it as it is only meant to be used internally +// in the CapDL implementation. + +/// Create a frame object and add it to the spec, returns the +/// object number. +pub fn capdl_util_make_frame_obj( + spec: &mut CapDLSpec, + frame_init: FrameInit, + name: &str, + paddr: Option, + size_bits: usize, +) -> ObjectId { + let frame_inner_obj = CapDLObject::Frame(Frame { + size_bits, + paddr, + init: frame_init, + }); + let frame_obj = NamedObject { + name: format!("frame_{name}"), + object: frame_inner_obj, + expected_alloc: None, + }; + spec.add_root_object(frame_obj) +} + +/// Create a frame capability from a frame object for mapping the frame in a VSpace +pub fn capdl_util_make_frame_cap( + frame_obj_id: ObjectId, + read: bool, + write: bool, + executable: bool, + cached: bool, +) -> Cap { + Cap::Frame(cap::Frame { + object: frame_obj_id, + rights: Rights { + read, + write, + grant: false, + grant_reply: false, + }, + // This is not used on RISC-V, PTEs have no cached bit, see seL4_RISCV_VMAttributes. + cached, + // This is ignored on x86 by seL4. As the NX/XD bit that marks page as non-executable + // is unsupported on old hardware. + executable, + }) +} + +pub fn capdl_util_make_tcb_cap(tcb_obj_id: ObjectId) -> Cap { + Cap::Tcb(cap::Tcb { object: tcb_obj_id }) +} + +pub fn capdl_util_make_page_table_cap(pt_obj_id: ObjectId) -> Cap { + Cap::PageTable(cap::PageTable { object: pt_obj_id }) +} + +// Given a TCB object ID, return that TCB's VSpace object ID. +pub fn capdl_util_get_vspace_id_from_tcb_id(spec: &CapDLSpec, tcb_obj_id: ObjectId) -> ObjectId { + let tcb = match spec.get_root_object(tcb_obj_id) { + Some(named_object) => { + if let CapDLObject::Tcb(tcb) = &named_object.object { + Some(tcb) + } else { + unreachable!("get_vspace_id_from_tcb_id(): internal bug: got a non TCB object id {} with name '{}'", tcb_obj_id, named_object.name); + } + } + None => { + unreachable!( + "get_vspace_id_from_tcb_id(): internal bug: couldn't find tcb with given obj id." + ); + } + }; + let vspace_cap = tcb + .unwrap() + .slots + .iter() + .find(|&cte| matches!(&cte.1, Cap::PageTable(_))); + vspace_cap.unwrap().1.obj() +} + +pub fn capdl_util_get_frame_size_bits(spec: &CapDLSpec, frame_obj_id: ObjectId) -> usize { + if let CapDLObject::Frame(frame) = &spec.get_root_object(frame_obj_id).unwrap().object { + frame.size_bits + } else { + unreachable!( + "internal bug: capdl_util_get_frame_size_bits() received a non Frame object ID" + ); + } +} + +pub fn capdl_util_make_endpoint_obj( + spec: &mut CapDLSpec, + pd_name: &str, + is_fault: bool, +) -> ObjectId { + let fault_ep_obj = NamedObject { + name: format!("ep_{}{}", if is_fault { "fault_" } else { "" }, pd_name), + object: CapDLObject::Endpoint, + expected_alloc: None, + }; + spec.add_root_object(fault_ep_obj) +} + +pub fn capdl_util_make_endpoint_cap( + ep_obj_id: ObjectId, + read: bool, + write: bool, + grant: bool, + badge: u64, +) -> Cap { + Cap::Endpoint(cap::Endpoint { + object: ep_obj_id, + badge, + rights: Rights { + read, + write, + grant, + grant_reply: false, + }, + }) +} + +pub fn capdl_util_make_ntfn_obj(spec: &mut CapDLSpec, pd_name: &str) -> ObjectId { + let ntfn_obj = NamedObject { + name: format!("ntfn_{pd_name}"), + object: CapDLObject::Notification, + expected_alloc: None, + }; + spec.add_root_object(ntfn_obj) +} + +pub fn capdl_util_make_ntfn_cap(ntfn_obj_id: ObjectId, read: bool, write: bool, badge: u64) -> Cap { + Cap::Notification(cap::Notification { + object: ntfn_obj_id, + badge, + rights: Rights { + read, + write, + // Irrelevant for notifications, seL4 manual v13.0.0 pg11 + grant: false, + grant_reply: false, + }, + }) +} + +pub fn capdl_util_make_reply_obj(spec: &mut CapDLSpec, pd_name: &str) -> ObjectId { + let reply_obj = NamedObject { + name: format!("reply_{pd_name}"), + object: CapDLObject::Reply, + expected_alloc: None, + }; + spec.add_root_object(reply_obj) +} + +pub fn capdl_util_make_reply_cap(reply_obj_id: ObjectId) -> Cap { + Cap::Reply(cap::Reply { + object: reply_obj_id, + }) +} + +pub fn capdl_util_make_sc_obj( + spec: &mut CapDLSpec, + pd_name: &str, + size_bits: usize, + period: u64, + budget: u64, + badge: u64, +) -> ObjectId { + let sc_inner_obj = CapDLObject::SchedContext(SchedContext { + size_bits, + extra: SchedContextExtraInfo { + period, + budget, + badge, + }, + }); + let sc_obj = NamedObject { + name: format!("sched_context_{pd_name}"), + object: sc_inner_obj, + expected_alloc: None, + }; + spec.add_root_object(sc_obj) +} + +pub fn capdl_util_make_sc_cap(sc_obj_id: ObjectId) -> Cap { + Cap::SchedContext(cap::SchedContext { object: sc_obj_id }) +} + +pub fn capdl_util_make_cnode_obj( + spec: &mut CapDLSpec, + pd_name: &str, + size_bits: usize, + slots: Vec, +) -> ObjectId { + let cnode_inner_obj = CapDLObject::CNode(CNode { size_bits, slots }); + let cnode_obj = NamedObject { + name: format!("cnode_{pd_name}"), + object: cnode_inner_obj, + expected_alloc: None, + }; + // Move monitor CSpace into spec and make a cap for it to insert into TCB later. + spec.add_root_object(cnode_obj) +} + +pub fn capdl_util_make_cnode_cap(cnode_obj_id: ObjectId, guard: u64, guard_size: u64) -> Cap { + Cap::CNode(cap::CNode { + object: cnode_obj_id, + guard, + + guard_size, + }) +} + +pub fn capdl_util_make_ioport_obj( + spec: &mut CapDLSpec, + pd_name: &str, + start_addr: u64, + size: u64, +) -> ObjectId { + let ioport_inner_obj = CapDLObject::IOPorts(IOPorts { + start_port: start_addr, + end_port: start_addr + size - 1, + }); + let ioport_obj = NamedObject { + name: format!("ioports_0x{start_addr:x}_{pd_name}"), + object: ioport_inner_obj, + expected_alloc: None, + }; + spec.add_root_object(ioport_obj) +} + +pub fn capdl_util_make_ioport_cap(ioport_obj_id: ObjectId) -> Cap { + Cap::IOPorts(cap::IOPorts { + object: ioport_obj_id, + }) +} + +pub fn capdl_util_insert_cap_into_cspace( + spec: &mut CapDLSpec, + cspace_obj_id: ObjectId, + idx: usize, + cap: Cap, +) { + assert!(idx < PD_CAP_SIZE as usize); + let cspace_obj = spec.get_root_object_mut(cspace_obj_id).unwrap(); + if let CapDLObject::CNode(cspace_inner_obj) = &mut cspace_obj.object { + cspace_inner_obj.slots.push((idx, cap)); + } else { + unreachable!("capdl_util_insert_cap_into_cspace(): internal bug: got a non CNode object id {} with name '{}'", cspace_obj_id, cspace_obj.name); + } +} + +pub fn capdl_util_make_vcpu_obj(spec: &mut CapDLSpec, name: &String) -> ObjectId { + let vcpu_inner_obj = CapDLObject::VCpu; + let vcpu_obj = NamedObject { + name: format!("vcpu_{name}"), + object: vcpu_inner_obj, + expected_alloc: None, + }; + spec.add_root_object(vcpu_obj) +} + +pub fn capdl_util_make_vcpu_cap(vcpu_obj_id: ObjectId) -> Cap { + Cap::VCpu(cap::VCpu { + object: vcpu_obj_id, + }) +} + +pub fn capdl_util_make_arm_smc_cap(arm_smc_obj_id: ObjectId) -> Cap { + Cap::ArmSmc(cap::ArmSmc { + object: arm_smc_obj_id, + }) +} diff --git a/tool/microkit/src/elf.rs b/tool/microkit/src/elf.rs index 5ed60ddd1..d5b1826af 100644 --- a/tool/microkit/src/elf.rs +++ b/tool/microkit/src/elf.rs @@ -4,10 +4,13 @@ // SPDX-License-Identifier: BSD-2-Clause // -use crate::util::bytes_to_struct; +use crate::sel4::PageSize; +use crate::util::{bytes_to_struct, round_down, struct_to_bytes}; use std::collections::HashMap; -use std::fs; -use std::path::Path; +use std::fs::{self, metadata, File}; +use std::io::Write; +use std::path::{Path, PathBuf}; +use std::slice::from_raw_parts; #[repr(C, packed)] struct ElfHeader32 { @@ -34,7 +37,7 @@ struct ElfHeader32 { } #[repr(C, packed)] -#[derive(Copy, Clone)] +#[derive(Clone, Eq, PartialEq)] struct ElfSymbol64 { name: u32, info: u8, @@ -96,8 +99,22 @@ struct ElfHeader64 { const ELF_MAGIC: &[u8; 4] = b"\x7FELF"; +const PHENT_TYPE_LOADABLE: u32 = 1; + +/// ELF program-header flags (`p_flags`) +const PF_X: u32 = 0x1; +const PF_W: u32 = 0x2; +const PF_R: u32 = 0x4; + +#[derive(Eq, PartialEq, Clone)] +pub enum ElfSegmentData { + RealData(Vec), + UninitialisedData(u64), +} + +#[derive(Eq, PartialEq, Clone)] pub struct ElfSegment { - pub data: Vec, + pub data: ElfSegmentData, pub phys_addr: u64, pub virt_addr: u64, pub loadable: bool, @@ -106,34 +123,63 @@ pub struct ElfSegment { impl ElfSegment { pub fn mem_size(&self) -> u64 { - self.data.len() as u64 + match &self.data { + ElfSegmentData::RealData(bytes) => bytes.len() as u64, + ElfSegmentData::UninitialisedData(size) => *size, + } + } + + pub fn file_size(&self) -> u64 { + match &self.data { + ElfSegmentData::RealData(bytes) => bytes.len() as u64, + ElfSegmentData::UninitialisedData(_) => 0, + } + } + + pub fn data(&self) -> &Vec { + match &self.data { + ElfSegmentData::RealData(bytes) => bytes, + ElfSegmentData::UninitialisedData(_) => { + unreachable!("internal bug: data() called on an uninitialised ELF segment.") + } + } + } + + pub fn data_mut(&mut self) -> &mut Vec { + match &mut self.data { + ElfSegmentData::RealData(bytes) => bytes, + ElfSegmentData::UninitialisedData(_) => { + unreachable!("internal bug: data_mut() called on an uninitialised ELF segment.") + } + } + } + + pub fn is_uninitialised(&self) -> bool { + match &self.data { + ElfSegmentData::RealData(_) => false, + ElfSegmentData::UninitialisedData(_) => true, + } } pub fn is_writable(&self) -> bool { - (self.attrs & ElfSegmentAttributes::Write as u32) != 0 + self.attrs & PF_W == PF_W } pub fn is_readable(&self) -> bool { - (self.attrs & ElfSegmentAttributes::Read as u32) != 0 + self.attrs & PF_R == PF_R } pub fn is_executable(&self) -> bool { - (self.attrs & ElfSegmentAttributes::Execute as u32) != 0 + self.attrs & PF_X == PF_X } } -enum ElfSegmentAttributes { - /// Corresponds to PF_X - Execute = 0x1, - /// Corresponds to PF_W - Write = 0x2, - /// Corresponds to PF_R - Read = 0x4, -} - +#[derive(Eq, PartialEq, Clone)] pub struct ElfFile { + pub path: PathBuf, pub word_size: usize, pub entry: u64, + pub machine: u16, pub segments: Vec, symbols: HashMap, } @@ -191,6 +237,10 @@ impl ElfFile { let entry = hdr.entry; // Read all the segments + if hdr.phnum == 0 { + return Err(format!("ELF '{}': has no program headers", path.display())); + } + let mut segments = Vec::with_capacity(hdr.phnum as usize); for i in 0..hdr.phnum { let phent_start = hdr.phoff + (i * hdr.phentsize) as u64; @@ -202,20 +252,23 @@ impl ElfFile { let segment_start = phent.offset as usize; let segment_end = phent.offset as usize + phent.filesz as usize; - if phent.type_ != 1 { + if phent.type_ != PHENT_TYPE_LOADABLE { continue; } - let mut segment_data = vec![0; phent.memsz as usize]; - segment_data[..phent.filesz as usize] + let mut segment_data_bytes = vec![0; phent.memsz as usize]; + segment_data_bytes[..phent.filesz as usize] .copy_from_slice(&bytes[segment_start..segment_end]); + let segment_data = ElfSegmentData::RealData(segment_data_bytes); + + let flags = phent.flags; let segment = ElfSegment { data: segment_data, phys_addr: phent.paddr, virt_addr: phent.vaddr, - loadable: phent.type_ == 1, - attrs: phent.flags, + loadable: phent.type_ == PHENT_TYPE_LOADABLE, + attrs: flags, }; segments.push(segment) @@ -275,7 +328,7 @@ impl ElfFile { assert!(sym_body.len() == 1); assert!(sym_tail.is_empty()); - let sym = sym_body[0]; + let sym = &sym_body[0]; let name = Self::get_string(symtab_str, sym.name as usize)?; // It is possible for a valid ELF to contain multiple global symbols with the same name. @@ -288,15 +341,17 @@ impl ElfFile { // Here we are doing something that could end up being fairly expensive, we are copying // the string for each symbol name. It should be possible to turn this into a reference // although it might be awkward in order to please the borrow checker. - let insert = symbols.insert(name.to_string(), (sym, false)); + let insert = symbols.insert(name.to_string(), (sym.clone(), false)); assert!(insert.is_none()); } offset += symbol_size; } Ok(ElfFile { + path: path.to_owned(), word_size, entry, + machine: hdr.machine, segments, symbols, }) @@ -319,10 +374,10 @@ impl ElfFile { pub fn write_symbol(&mut self, variable_name: &str, data: &[u8]) -> Result<(), String> { let (vaddr, size) = self.find_symbol(variable_name)?; for seg in &mut self.segments { - if vaddr >= seg.virt_addr && vaddr + size <= seg.virt_addr + seg.data.len() as u64 { + if vaddr >= seg.virt_addr && vaddr + size <= seg.virt_addr + seg.mem_size() { let offset = (vaddr - seg.virt_addr) as usize; assert!(data.len() as u64 <= size); - seg.data[offset..offset + data.len()].copy_from_slice(data); + seg.data_mut()[offset..offset + data.len()].copy_from_slice(data); return Ok(()); } } @@ -332,9 +387,9 @@ impl ElfFile { pub fn get_data(&self, vaddr: u64, size: u64) -> Option<&[u8]> { for seg in &self.segments { - if vaddr >= seg.virt_addr && vaddr + size <= seg.virt_addr + seg.data.len() as u64 { + if vaddr >= seg.virt_addr && vaddr + size <= seg.virt_addr + seg.mem_size() { let offset = (vaddr - seg.virt_addr) as usize; - return Some(&seg.data[offset..offset + size as usize]); + return Some(&seg.data()[offset..offset + size as usize]); } } @@ -358,7 +413,156 @@ impl ElfFile { } } + pub fn lowest_vaddr(&self) -> u64 { + // This unwrap is safe as we have ensured that there will always be at least 1 segment when parsing the ELF. + let existing_vaddrs: Vec = self + .loadable_segments() + .iter() + .map(|segm| segm.virt_addr) + .collect(); + *existing_vaddrs.iter().min().unwrap() + } + + pub fn highest_vaddr(&self) -> u64 { + // This unwrap is safe as we have ensured that there will always be at least 1 segment when parsing the ELF. + let existing_vaddrs: Vec = self + .loadable_segments() + .iter() + .map(|segm| segm.virt_addr + segm.mem_size()) + .collect(); + *existing_vaddrs.iter().max().unwrap() + } + + /// Returns the next available page aligned virtual address for inserting a new segment. + pub fn next_vaddr(&self, page_size: PageSize) -> u64 { + round_down(self.highest_vaddr() + page_size as u64, page_size as u64) + } + + pub fn add_segment( + &mut self, + read: bool, + write: bool, + execute: bool, + vaddr: u64, + data: ElfSegmentData, + ) { + let r = if read { PF_R } else { 0 }; + let w = if write { PF_W } else { 0 }; + let x = if execute { PF_X } else { 0 }; + + let elf_segment = ElfSegment { + data, + phys_addr: vaddr, + virt_addr: vaddr, + loadable: true, + attrs: r | w | x, + }; + self.segments.push(elf_segment); + } + pub fn loadable_segments(&self) -> Vec<&ElfSegment> { self.segments.iter().filter(|s| s.loadable).collect() } + + /// Re-create a minimal ELF file with all the segments such that it can be loaded + /// by seL4 on a x86 platform as a boot module. This is the kernel code that does the + /// loading of what we generate here: seL4/src/arch/x86/64/kernel/elf.c + /// We use this function after patching the spec into the CapDL initialiser. + /// We don't guarantee that this ELF can be loaded by other kinds of ELF loader. + pub fn reserialise(&self, out: &std::path::Path) -> Result { + let phnum = self.loadable_segments().len(); + let phentsize = size_of::(); + let ehsize = size_of::(); + + let mut elf_file = match File::create(out) { + Ok(file) => file, + Err(e) => { + return Err(format!( + "ELF: cannot reserialise '{}' to '{}': {}", + self.path.display(), + out.display(), + e + )) + } + }; + + // ELF header + let header = ElfHeader64 { + ident_magic: u32::from_le_bytes(*ELF_MAGIC), + ident_class: 2, // 64-bits object + ident_data: 1, // little endian + ident_version: 1, + ident_osabi: 0, + ident_abiversion: 0, + _padding: [0; 7], + type_: 2, // executable file + machine: self.machine, + version: 1, + entry: self.entry, + // Program headers starts after main header + phoff: ehsize as u64, + shoff: 0, // no section headers! + flags: 0, + ehsize: ehsize as u16, + phentsize: phentsize as u16, + phnum: phnum as u16, + shentsize: 0, + shnum: 0, + shstrndx: 0, + }; + elf_file + .write_all(unsafe { + from_raw_parts((&header as *const ElfHeader64) as *const u8, ehsize) + }) + .unwrap_or_else(|_| panic!("Failed to write ELF header for '{}'", out.display())); + + // keep a running file offset where segment data will be written + let mut data_off = (ehsize as u64) + (phnum as u64) * (phentsize as u64); + + // first write out the headers table + for (i, seg) in self.loadable_segments().iter().enumerate() { + let seg_serialised = ElfProgramHeader64 { + type_: PHENT_TYPE_LOADABLE, // loadable segment + flags: seg.attrs, + offset: data_off, + vaddr: seg.virt_addr, + paddr: seg.phys_addr, + filesz: seg.file_size(), + memsz: seg.mem_size(), + align: 0, + }; + + elf_file + .write_all(unsafe { struct_to_bytes(&seg_serialised) }) + .unwrap_or_else(|_| { + panic!( + "Failed to write ELF segment header #{} for '{}'", + i, + out.display() + ) + }); + + data_off += seg.file_size(); + } + + // then the data for each segment will follow + for (i, seg) in self + .loadable_segments() + .iter() + .filter(|seg| !seg.is_uninitialised()) + .enumerate() + { + elf_file.write_all(seg.data()).unwrap_or_else(|_| { + panic!( + "Failed to write ELF segment data #{} for '{}'", + i, + out.display() + ) + }); + } + + elf_file.flush().unwrap(); + + Ok(metadata(out).unwrap().len()) + } } diff --git a/tool/microkit/src/lib.rs b/tool/microkit/src/lib.rs index e4195df10..f0a33764a 100644 --- a/tool/microkit/src/lib.rs +++ b/tool/microkit/src/lib.rs @@ -4,16 +4,19 @@ // SPDX-License-Identifier: BSD-2-Clause // +use std::{cmp::min, fmt}; + +use crate::{sel4::Config, util::struct_to_bytes}; + +pub mod capdl; pub mod elf; pub mod loader; +pub mod report; pub mod sdf; pub mod sel4; +pub mod symbols; pub mod util; -use sel4::Config; -use std::cmp::min; -use std::fmt; - // Note that these values are used in the monitor so should also be changed there // if any of these were to change. pub const MAX_PDS: usize = 63; @@ -24,13 +27,39 @@ pub const MAX_VMS: usize = 63; pub const PD_MAX_NAME_LENGTH: usize = 64; pub const VM_MAX_NAME_LENGTH: usize = 64; -#[derive(Debug, Copy, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq)] pub struct UntypedObject { pub cap: u64, pub region: MemoryRegion, pub is_device: bool, } +pub const UNTYPED_DESC_PADDING: usize = size_of::() - (2 * size_of::()); +#[repr(C)] +struct SeL4UntypedDesc { + paddr: u64, + size_bits: u8, + is_device: u8, + padding: [u8; UNTYPED_DESC_PADDING], +} + +impl From<&UntypedObject> for SeL4UntypedDesc { + fn from(value: &UntypedObject) -> Self { + Self { + paddr: value.base(), + size_bits: value.size_bits() as u8, + is_device: if value.is_device { 1 } else { 0 }, + padding: [0u8; UNTYPED_DESC_PADDING], + } + } +} + +/// Getting a `seL4_UntypedDesc` for patching into the initialiser +pub fn serialise_ut(ut: &UntypedObject) -> Vec { + let sel4_untyped_desc: SeL4UntypedDesc = ut.into(); + unsafe { struct_to_bytes(&sel4_untyped_desc).to_vec() } +} + impl UntypedObject { pub fn new(cap: u64, region: MemoryRegion, is_device: bool) -> UntypedObject { UntypedObject { @@ -74,7 +103,7 @@ impl Region { } pub fn data<'a>(&self, elf: &'a elf::ElfFile) -> &'a Vec { - &elf.segments[self.segment_idx].data + elf.segments[self.segment_idx].data() } } @@ -158,7 +187,7 @@ impl MemoryRegion { } } -#[derive(Default)] +#[derive(Default, Debug)] pub struct DisjointMemoryRegion { pub regions: Vec, } @@ -280,230 +309,3 @@ impl DisjointMemoryRegion { } } } - -#[derive(Copy, Clone)] -pub struct KernelAllocation { - pub untyped_cap_address: u64, // FIXME: possibly this is an object, not an int? - pub phys_addr: u64, - pub size: u64, -} - -pub struct UntypedAllocator { - untyped_object: UntypedObject, - allocation_point: u64, - allocations: Vec, -} - -impl UntypedAllocator { - pub fn new( - untyped_object: UntypedObject, - allocation_point: u64, - allocations: Vec, - ) -> UntypedAllocator { - UntypedAllocator { - untyped_object, - allocation_point, - allocations, - } - } - - pub fn base(&self) -> u64 { - self.untyped_object.region.base - } - - pub fn end(&self) -> u64 { - self.untyped_object.region.end - } -} - -/// Allocator for kernel objects. -/// -/// This tracks the space available in a set of untyped objects. -/// On allocation an untyped with sufficient remaining space is -/// returned (while updating the internal tracking). -/// -/// Within an untyped object this mimics the kernel's allocation -/// policy (basically a bump allocator with alignment). -/// -/// The only 'choice' this allocator has is which untyped object -/// to use. The current algorithm is simply first fit: the first -/// untyped that has sufficient space. This is not optimal. -/// -/// Note: The allocator does not generate the Retype invocations; -/// this must be done with more knowledge (specifically the destination -/// cap) which is distinct. -/// -/// It is critical that invocations are generated in the same order -/// as the allocations are made. -pub struct ObjectAllocator { - pub init_capacity: u64, - allocation_idx: u64, - pub untyped: Vec, -} - -/// First entry is potential padding, then the actual allocation is the second -/// entry. -type FixedAllocation = (Option>, KernelAllocation); - -pub enum FindFixedError { - AlreadyAllocated, - TooLarge, -} - -impl ObjectAllocator { - pub fn new(untyped_pool: Vec<&UntypedObject>) -> ObjectAllocator { - let mut untyped: Vec = untyped_pool - .into_iter() - .map(|ut| UntypedAllocator::new(*ut, 0, vec![])) - .collect(); - untyped.sort_by(|a, b| a.untyped_object.base().cmp(&b.untyped_object.base())); - - let mut capacity = 0; - for ut in &untyped { - capacity += ut.end() - (ut.base() + ut.allocation_point); - } - - ObjectAllocator { - init_capacity: capacity, - allocation_idx: 0, - untyped, - } - } - - pub fn capacity(&self) -> u64 { - let mut capacity = 0; - for ut in &self.untyped { - capacity += ut.end() - (ut.base() + ut.allocation_point); - } - - capacity - } - - pub fn max_alloc_size(&self) -> u64 { - let mut largest_capacity = 0; - for ut in &self.untyped { - let ut_capacity = ut.end() - (ut.base() + ut.allocation_point); - if ut_capacity > largest_capacity { - largest_capacity = ut_capacity; - } - } - - largest_capacity - } - - pub fn alloc(&mut self, size: u64) -> Option { - self.alloc_n(size, 1) - } - - pub fn alloc_n(&mut self, size: u64, count: u64) -> Option { - assert!(util::is_power_of_two(size)); - assert!(count > 0); - let mem_size = count * size; - for ut in &mut self.untyped { - // See if this fits - let start = util::round_up(ut.base() + ut.allocation_point, size); - if start + mem_size <= ut.end() { - ut.allocation_point = (start - ut.base()) + mem_size; - self.allocation_idx += 1; - let allocation = KernelAllocation { - untyped_cap_address: ut.untyped_object.cap, - phys_addr: start, - size: mem_size, - }; - ut.allocations.push(allocation); - return Some(allocation); - } - } - - None - } - - pub fn reserve(&mut self, alloc: (&UntypedObject, u64)) { - for ut in &mut self.untyped { - if *alloc.0 == ut.untyped_object { - if ut.base() <= alloc.1 && alloc.1 <= ut.end() { - ut.allocation_point = alloc.1 - ut.base(); - return; - } else { - panic!( - "Allocation {:?} ({:x}) not in untyped region {:?}", - alloc.0, alloc.1, ut.untyped_object - ); - } - } - } - - panic!( - "Allocation {:?} ({:x}) not in any device untyped", - alloc.0, alloc.1 - ); - } - - pub fn find_fixed( - &mut self, - phys_addr: u64, - size: u64, - ) -> Result, FindFixedError> { - for ut in &mut self.untyped { - /* Find the right untyped */ - if phys_addr >= ut.base() && phys_addr < ut.end() { - if phys_addr < ut.base() + ut.allocation_point { - return Err(FindFixedError::AlreadyAllocated); - } - - let space_left = ut.end() - (ut.base() + ut.allocation_point); - if space_left < size { - return Err(FindFixedError::TooLarge); - } - - let mut watermark = ut.base() + ut.allocation_point; - let mut allocations: Option>; - - if phys_addr != watermark { - allocations = Some(Vec::new()); - /* If the watermark isn't at the right place, we need to pad */ - let mut padding_required = phys_addr - watermark; - // We are restricted in how much we can pad: - // 1: Untyped objects must be power-of-two sized. - // 2: Untyped objects must be aligned to their size. - let mut padding_sizes = Vec::new(); - // We have two potential approaches for how we pad. - // 1: Use largest objects possible respecting alignment - // and size restrictions. - // 2: Use a fixed size object multiple times. This will - // create more objects, but as same sized objects can be - // create in a batch, required fewer invocations. - // For now we choose #1 - while padding_required > 0 { - let wm_lsb = util::lsb(watermark); - let sz_msb = util::msb(padding_required); - let pad_object_size = 1 << min(wm_lsb, sz_msb); - padding_sizes.push(pad_object_size); - - allocations.as_mut().unwrap().push(KernelAllocation { - untyped_cap_address: ut.untyped_object.cap, - phys_addr: watermark, - size: pad_object_size, - }); - - watermark += pad_object_size; - padding_required -= pad_object_size; - } - } else { - allocations = None; - } - - let obj = KernelAllocation { - untyped_cap_address: ut.untyped_object.cap, - phys_addr: watermark, - size, - }; - - ut.allocation_point = (watermark + size) - ut.base(); - return Ok(Some((allocations, obj))); - } - } - - Ok(None) - } -} diff --git a/tool/microkit/src/loader.rs b/tool/microkit/src/loader.rs index bd2ebd122..453bc1b64 100644 --- a/tool/microkit/src/loader.rs +++ b/tool/microkit/src/loader.rs @@ -3,13 +3,12 @@ // // SPDX-License-Identifier: BSD-2-Clause // - use crate::elf::ElfFile; use crate::sel4::{Arch, Config}; -use crate::util::{kb, mask, mb, round_up, struct_to_bytes}; -use crate::MemoryRegion; +use crate::util::{mask, mb, round_up, struct_to_bytes}; use std::fs::File; use std::io::{BufWriter, Write}; +use std::ops::Range; use std::path::Path; const PAGE_TABLE_SIZE: usize = 4096; @@ -113,8 +112,6 @@ struct LoaderHeader64 { ui_p_reg_end: u64, pv_offset: u64, v_entry: u64, - extra_device_addr_p: u64, - extra_device_size: u64, num_regions: u64, } @@ -131,15 +128,15 @@ impl<'a> Loader<'a> { loader_elf_path: &Path, kernel_elf: &'a ElfFile, initial_task_elf: &'a ElfFile, - initial_task_phys_base: Option, - reserved_region: MemoryRegion, - system_regions: Vec<(u64, &'a [u8])>, + initial_task_phy_base: u64, + initial_task_vaddr_range: Range, ) -> Loader<'a> { - // Note: If initial_task_phys_base is not None, then it just this address - // as the base physical address of the initial task, rather than the address - // that comes from the initial_task_elf file. - let elf = ElfFile::from_path(loader_elf_path).unwrap(); - let sz = elf.word_size; + if config.arch == Arch::X86_64 { + unreachable!("internal error: x86_64 does not support creating a loader image"); + } + + let loader_elf = ElfFile::from_path(loader_elf_path).unwrap(); + let sz = loader_elf.word_size; let magic = match sz { 32 => 0x5e14dead, 64 => 0x5e14dead14de5ead, @@ -150,7 +147,7 @@ impl<'a> Loader<'a> { ), }; - let mut regions = Vec::new(); + let mut regions: Vec<(u64, &[u8])> = Vec::new(); let mut kernel_first_vaddr = None; let mut kernel_last_vaddr = None; @@ -174,69 +171,71 @@ impl<'a> Loader<'a> { kernel_first_paddr = Some(segment.phys_addr); } - if kernel_p_v_offset.is_none() { + if let Some(p_v_offset) = kernel_p_v_offset { + if p_v_offset != segment.virt_addr - segment.phys_addr { + panic!("Kernel does not have a consistent physical to virtual offset"); + } + } else { kernel_p_v_offset = Some(segment.virt_addr - segment.phys_addr); - } else if kernel_p_v_offset.unwrap() != segment.virt_addr - segment.phys_addr { - panic!("Kernel does not have a consistent physical to virtual offset"); } - regions.push((segment.phys_addr, segment.data.as_slice())); + regions.push((segment.phys_addr, segment.data().as_slice())); } } assert!(kernel_first_paddr.is_some()); - // Note: This could be extended to support multi-segment ELF files - // (and indeed initial did support multi-segment ELF files). However - // it adds significant complexity, and the calling functions enforce - // only single-segment ELF files, so we keep things simple here. - let initial_task_segments: Vec<_> = initial_task_elf - .segments - .iter() - .filter(|s| s.loadable) - .collect(); - assert!(initial_task_segments.len() == 1); - let segment = &initial_task_segments[0]; - assert!(segment.loadable); - - let inittask_first_vaddr = segment.virt_addr; - let inittask_last_vaddr = round_up(segment.virt_addr + segment.mem_size(), kb(4)); - - let inittask_first_paddr = match initial_task_phys_base { - Some(paddr) => paddr, - None => segment.phys_addr, - }; - let inittask_p_v_offset = inittask_first_vaddr - inittask_first_paddr; - - // Note: For now we include any zeroes. We could optimize in the future - regions.push((inittask_first_paddr, &segment.data)); + // We support an initial task ELF with multiple segments. This is implemented by amalgamating all the segments + // into 1 segment, so if your segments are sparse, a lot of memory will be wasted. + let initial_task_segments = initial_task_elf.loadable_segments(); + + // Compute an available physical memory segment large enough to house the initial task (CapDL initialiser with spec) + // that is after the kernel window. + let inittask_p_v_offset = initial_task_vaddr_range.start - initial_task_phy_base; + let inittask_v_entry = initial_task_elf.entry; + + // initialiser.rs will always place the heap as the last region in the initial task's address space. + // So instead of copying a bunch of useless zeroes into the image, we can just leave it uninitialised. + assert!(initial_task_segments.last().unwrap().is_uninitialised()); + // Skip heap segment + for segment in initial_task_segments[..initial_task_segments.len() - 1].iter() { + if segment.mem_size() > 0 { + let segment_paddr = + initial_task_phy_base + (segment.virt_addr - initial_task_vaddr_range.start); + regions.push((segment_paddr, segment.data())); + } + } // Determine the pagetable variables assert!(kernel_first_vaddr.is_some()); assert!(kernel_first_vaddr.is_some()); let pagetable_vars = match config.arch { Arch::Aarch64 => Loader::aarch64_setup_pagetables( - &elf, + &loader_elf, kernel_first_vaddr.unwrap(), kernel_first_paddr.unwrap(), ), Arch::Riscv64 => Loader::riscv64_setup_pagetables( config, - &elf, + &loader_elf, kernel_first_vaddr.unwrap(), kernel_first_paddr.unwrap(), ), + Arch::X86_64 => unreachable!("x86_64 does not support creating a loader image"), }; - let image_segment = elf + let image_segment = loader_elf .segments .into_iter() .find(|segment| segment.loadable) .expect("Did not find loadable segment"); let image_vaddr = image_segment.virt_addr; - let mut image = image_segment.data; + // We have to clone here as the image executable is part of this function return object, + // and the loader ELF is deserialised in this scope, so its lifetime will be shorter than + // the return object. + let mut image = image_segment.data().clone(); - if image_vaddr != elf.entry { + if image_vaddr != loader_elf.entry { panic!("The loader entry point must be the first byte in the image"); } @@ -250,25 +249,14 @@ impl<'a> Loader<'a> { let kernel_entry = kernel_elf.entry; - let pv_offset = inittask_first_paddr.wrapping_sub(inittask_first_vaddr); + let pv_offset = initial_task_phy_base.wrapping_sub(initial_task_vaddr_range.start); - let ui_p_reg_start = inittask_first_paddr; - let ui_p_reg_end = inittask_last_vaddr - inittask_p_v_offset; + let ui_p_reg_start = initial_task_phy_base; + let ui_p_reg_end = initial_task_vaddr_range.end - inittask_p_v_offset; assert!(ui_p_reg_end > ui_p_reg_start); - let v_entry = initial_task_elf.entry; - - let extra_device_addr_p = reserved_region.base; - let extra_device_size = reserved_region.size(); - - let mut all_regions = Vec::with_capacity(regions.len() + system_regions.len()); - for region_set in [regions, system_regions] { - for r in region_set { - all_regions.push(r); - } - } - - let mut all_regions_with_loader = all_regions.clone(); + // This clone isn't too bad as it is just a Vec<(u64, &[u8])> + let mut all_regions_with_loader = regions.clone(); all_regions_with_loader.push((image_vaddr, &image)); check_non_overlapping(&all_regions_with_loader); @@ -279,7 +267,7 @@ impl<'a> Loader<'a> { let mut region_metadata = Vec::new(); let mut offset: u64 = 0; - for (addr, data) in &all_regions { + for (addr, data) in ®ions { region_metadata.push(LoaderRegion64 { load_addr: *addr, size: data.len() as u64, @@ -302,17 +290,15 @@ impl<'a> Loader<'a> { ui_p_reg_start, ui_p_reg_end, pv_offset, - v_entry, - extra_device_addr_p, - extra_device_size, - num_regions: all_regions.len() as u64, + v_entry: inittask_v_entry, + num_regions: regions.len() as u64, }; Loader { image, header, region_metadata, - regions: all_regions, + regions, } } diff --git a/tool/microkit/src/main.rs b/tool/microkit/src/main.rs index 483b41105..e9a5b86af 100644 --- a/tool/microkit/src/main.rs +++ b/tool/microkit/src/main.rs @@ -1,5 +1,5 @@ // -// Copyright 2024, UNSW +// Copyright 2025, UNSW // // SPDX-License-Identifier: BSD-2-Clause // @@ -7,2904 +7,38 @@ // we want our asserts, even if the compiler figures out they hold true already during compile-time #![allow(clippy::assertions_on_constants)] -use elf::ElfFile; -use loader::Loader; -use microkit_tool::{ - elf, loader, sdf, sel4, util, DisjointMemoryRegion, FindFixedError, MemoryRegion, - ObjectAllocator, Region, UntypedObject, MAX_PDS, MAX_VMS, PD_MAX_NAME_LENGTH, - VM_MAX_NAME_LENGTH, +use microkit_tool::capdl::allocation::simulate_capdl_object_alloc_algorithm; +use microkit_tool::capdl::initialiser::{CapDLInitialiser, DEFAULT_INITIALISER_HEAP_MULTIPLIER}; +use microkit_tool::capdl::spec::ElfContent; +use microkit_tool::capdl::{build_capdl_spec, reserialise_spec}; +use microkit_tool::elf::ElfFile; +use microkit_tool::loader::Loader; +use microkit_tool::report::write_report; +use microkit_tool::sdf::parse; +use microkit_tool::sel4::{ + emulate_kernel_boot, emulate_kernel_boot_partial, Arch, Config, PlatformConfig, + RiscvVirtualMemory, }; -use sdf::{ - parse, Channel, ProtectionDomain, SysMap, SysMapPerms, SysMemoryRegion, SysMemoryRegionKind, - SystemDescription, VirtualMachine, -}; -use sel4::{ - default_vm_attr, Aarch64Regs, Arch, ArmVmAttributes, BootInfo, Config, Invocation, - InvocationArgs, Object, ObjectType, PageSize, PlatformConfig, Rights, Riscv64Regs, - RiscvVirtualMemory, RiscvVmAttributes, -}; -use std::cmp::{max, min}; -use std::collections::{HashMap, HashSet}; -use std::fs; -use std::io::{BufWriter, Write}; -use std::iter::zip; -use std::mem::size_of; -use std::path::{Path, PathBuf}; -use util::{ - comma_sep_u64, comma_sep_usize, human_size_strict, json_str, json_str_as_bool, json_str_as_u64, - monitor_serialise_names, monitor_serialise_u64_vec, struct_to_bytes, -}; - -// Corresponds to the IPC buffer symbol in libmicrokit and the monitor -const SYMBOL_IPC_BUFFER: &str = "__sel4_ipc_buffer_obj"; - -const FAULT_BADGE: u64 = 1 << 62; -const PPC_BADGE: u64 = 1 << 63; - -const INPUT_CAP_IDX: u64 = 1; -#[allow(dead_code)] -const FAULT_EP_CAP_IDX: u64 = 2; -const VSPACE_CAP_IDX: u64 = 3; -const REPLY_CAP_IDX: u64 = 4; -const MONITOR_EP_CAP_IDX: u64 = 5; -const TCB_CAP_IDX: u64 = 6; -const SMC_CAP_IDX: u64 = 7; - -const BASE_OUTPUT_NOTIFICATION_CAP: u64 = 10; -const BASE_OUTPUT_ENDPOINT_CAP: u64 = BASE_OUTPUT_NOTIFICATION_CAP + 64; -const BASE_IRQ_CAP: u64 = BASE_OUTPUT_ENDPOINT_CAP + 64; -const BASE_PD_TCB_CAP: u64 = BASE_IRQ_CAP + 64; -const BASE_VM_TCB_CAP: u64 = BASE_PD_TCB_CAP + 64; -const BASE_VCPU_CAP: u64 = BASE_VM_TCB_CAP + 64; - -const MAX_SYSTEM_INVOCATION_SIZE: u64 = util::mb(128); - -const PD_CAP_SIZE: u64 = 512; -const PD_CAP_BITS: u64 = PD_CAP_SIZE.ilog2() as u64; -const PD_SCHEDCONTEXT_SIZE: u64 = 1 << 8; - -const SLOT_BITS: u64 = 5; -const SLOT_SIZE: u64 = 1 << SLOT_BITS; - -const INIT_NULL_CAP_ADDRESS: u64 = 0; -const INIT_TCB_CAP_ADDRESS: u64 = 1; -const INIT_CNODE_CAP_ADDRESS: u64 = 2; -const INIT_VSPACE_CAP_ADDRESS: u64 = 3; -const IRQ_CONTROL_CAP_ADDRESS: u64 = 4; // Singleton -const INIT_ASID_POOL_CAP_ADDRESS: u64 = 6; -const SMC_CAP_ADDRESS: u64 = 15; - -// const ASID_CONTROL_CAP_ADDRESS: u64 = 5; // Singleton -// const IO_PORT_CONTROL_CAP_ADDRESS: u64 = 7; // Null on this platform -// const IO_SPACE_CAP_ADDRESS: u64 = 8; // Null on this platform -// const BOOT_INFO_FRAME_CAP_ADDRESS: u64 = 9; -// const INIT_THREAD_IPC_BUFFER_CAP_ADDRESS: u64 = 10; -// const DOMAIN_CAP_ADDRESS: u64 = 11; -// const SMMU_SID_CONTROL_CAP_ADDRESS: u64 = 12; -// const SMMU_CB_CONTROL_CAP_ADDRESS: u64 = 13; -// const INIT_THREAD_SC_CAP_ADDRESS: u64 = 14; - -/// Corresponds to 'struct untyped_info' in the monitor -/// It should be noted that this is called a 'header' since -/// it omits the 'regions' field. -/// This struct assumes a 64-bit target -#[repr(C)] -struct MonitorUntypedInfoHeader64 { - cap_start: u64, - cap_end: u64, -} - -/// Corresponds to 'struct region' in the monitor -/// This struct assumes a 64-bit target -#[repr(C)] -struct MonitorRegion64 { - paddr: u64, - size_bits: u64, - is_device: u64, -} - -struct MonitorConfig { - untyped_info_symbol_name: &'static str, - bootstrap_invocation_count_symbol_name: &'static str, - bootstrap_invocation_data_symbol_name: &'static str, - system_invocation_count_symbol_name: &'static str, -} - -impl MonitorConfig { - pub fn max_untyped_objects(&self, symbol_size: u64) -> u64 { - (symbol_size - size_of::() as u64) - / size_of::() as u64 - } -} - -struct InitSystem<'a> { - config: &'a Config, - cnode_cap: u64, - cnode_mask: u64, - invocations: &'a mut Vec, - cap_slot: u64, - last_fixed_address: u64, - normal_untyped: &'a mut ObjectAllocator, - device_untyped: &'a mut ObjectAllocator, - cap_address_names: &'a mut HashMap, - objects: Vec, -} - -impl<'a> InitSystem<'a> { - #[allow(clippy::too_many_arguments)] // just this one time, pinky-promise... - pub fn new( - config: &'a Config, - cnode_cap: u64, - cnode_mask: u64, - first_available_cap_slot: u64, - normal_untyped: &'a mut ObjectAllocator, - device_untyped: &'a mut ObjectAllocator, - invocations: &'a mut Vec, - cap_address_names: &'a mut HashMap, - ) -> InitSystem<'a> { - InitSystem { - config, - cnode_cap, - cnode_mask, - invocations, - cap_slot: first_available_cap_slot, - last_fixed_address: 0, - normal_untyped, - device_untyped, - cap_address_names, - objects: Vec::new(), - } - } - - pub fn reserve(&mut self, allocations: Vec<(&UntypedObject, u64)>) { - for alloc in allocations { - self.device_untyped.reserve(alloc); - } - } - - /// Note: Fixed objects must be allocated in order! - pub fn allocate_fixed_object( - &mut self, - phys_address: u64, - object_type: ObjectType, - name: String, - ) -> Object { - assert!(phys_address >= self.last_fixed_address); - assert!(object_type.fixed_size(self.config).is_some()); - - let alloc_size = object_type.fixed_size(self.config).unwrap(); - - // Find an untyped that contains the given address, it could either be - // in device memory or normal memory. - let device_ut = self.device_untyped.find_fixed(phys_address, alloc_size).unwrap_or_else(|err| { - match err { - FindFixedError::AlreadyAllocated => eprintln!("ERROR: attempted to allocate object '{name}' at 0x{phys_address:x} from reserved region, pick another physical address"), - FindFixedError::TooLarge => eprintln!("ERROR: attempted too allocate too large of an object '{name}' for this physical address 0x{phys_address:x}"), - } - std::process::exit(1); - }); - let normal_ut = self.normal_untyped.find_fixed(phys_address, alloc_size).unwrap_or_else(|err| { - match err { - FindFixedError::AlreadyAllocated => eprintln!("ERROR: attempted to allocate object '{name}' at 0x{phys_address:x} from reserved region, pick another physical address"), - FindFixedError::TooLarge => eprintln!("ERROR: attempted too allocate too large of an object '{name}' for this physical address 0x{phys_address:x}"), - } - std::process::exit(1); - }); - - // We should never have found the physical address in both device and normal untyped - assert!(!(device_ut.is_some() && normal_ut.is_some())); - - let (padding, ut) = if let Some(x) = device_ut { - x - } else if let Some(x) = normal_ut { - x - } else { - eprintln!( - "ERROR: physical address 0x{phys_address:x} not in any valid region, below are the valid ranges of memory to be allocated from:" - ); - eprintln!("valid ranges outside of main memory:"); - for ut in &self.device_untyped.untyped { - eprintln!(" [0x{:0>12x}..0x{:0>12x})", ut.base(), ut.end()); - } - eprintln!("valid ranges within main memory:"); - for ut in &self.normal_untyped.untyped { - eprintln!(" [0x{:0>12x}..0x{:0>12x})", ut.base(), ut.end()); - } - std::process::exit(1); - }; - - if let Some(padding_unwrapped) = padding { - for pad_ut in padding_unwrapped { - self.invocations.push(Invocation::new( - self.config, - InvocationArgs::UntypedRetype { - untyped: pad_ut.untyped_cap_address, - object_type: ObjectType::Untyped, - size_bits: pad_ut.size.ilog2() as u64, - root: self.cnode_cap, - node_index: 1, - node_depth: 1, - node_offset: self.cap_slot, - num_objects: 1, - }, - )); - self.cap_slot += 1; - } - } - - let object_cap = self.cap_slot; - self.cap_slot += 1; - self.invocations.push(Invocation::new( - self.config, - InvocationArgs::UntypedRetype { - untyped: ut.untyped_cap_address, - object_type, - size_bits: 0, - root: self.cnode_cap, - node_index: 1, - node_depth: 1, - node_offset: object_cap, - num_objects: 1, - }, - )); - - self.last_fixed_address = phys_address + alloc_size; - let cap_addr = self.cnode_mask | object_cap; - let kernel_object = Object { - object_type, - cap_addr, - phys_addr: phys_address, - }; - self.objects.push(kernel_object); - self.cap_address_names.insert(cap_addr, name); - - kernel_object - } - - pub fn allocate_objects( - &mut self, - object_type: ObjectType, - names: Vec, - size: Option, - ) -> Vec { - // Nothing to do if we get a zero count. - if names.is_empty() { - return Vec::new(); - } - - let count = names.len() as u64; - - let alloc_size; - let api_size: u64; - if let Some(object_size) = object_type.fixed_size(self.config) { - // An object with a fixed size should not be allocated with a given size - assert!(size.is_none()); - alloc_size = object_size; - api_size = 0; - } else if object_type == ObjectType::CNode || object_type == ObjectType::SchedContext { - let sz = size.unwrap(); - assert!(util::is_power_of_two(sz)); - api_size = sz.ilog2() as u64; - if object_type == ObjectType::CNode { - alloc_size = sz * SLOT_SIZE; - } else { - alloc_size = sz; - } - } else { - panic!("Internal error: invalid object type: {object_type:?}"); - } - - let allocation = self.normal_untyped - .alloc_n(alloc_size, count) - .unwrap_or_else(|| { - let (human_size, human_size_label) = human_size_strict(alloc_size * count); - let (human_max_alloc, human_max_alloc_label) = human_size_strict(self.normal_untyped.max_alloc_size()); - eprintln!("ERROR: failed to allocate objects for '{}' of object type '{}'", names[0], object_type.to_str()); - if alloc_size * count > self.normal_untyped.max_alloc_size() { - eprintln!("ERROR: allocation size ({human_size} {human_size_label}) is greater than current maximum size for a single allocation ({human_max_alloc} {human_max_alloc_label})"); - } - std::process::exit(1); - } - ); - let base_cap_slot = self.cap_slot; - self.cap_slot += count; - - let mut to_alloc = count; - let mut alloc_cap_slot = base_cap_slot; - while to_alloc > 0 { - let call_count = min(to_alloc, self.config.fan_out_limit); - self.invocations.push(Invocation::new( - self.config, - InvocationArgs::UntypedRetype { - untyped: allocation.untyped_cap_address, - object_type, - size_bits: api_size, - root: self.cnode_cap, - node_index: 1, - node_depth: 1, - node_offset: alloc_cap_slot, - num_objects: call_count, - }, - )); - to_alloc -= call_count; - alloc_cap_slot += call_count; - } - - let mut kernel_objects = Vec::new(); - let mut phys_addr = allocation.phys_addr; - for (idx, name) in names.into_iter().enumerate() { - let cap_slot = base_cap_slot + idx as u64; - let cap_addr = self.cnode_mask | cap_slot; - let kernel_object = Object { - object_type, - cap_addr, - phys_addr, - }; - kernel_objects.push(kernel_object); - self.cap_address_names.insert(cap_addr, name); - - phys_addr += alloc_size; - - self.objects.push(kernel_object); - } - - kernel_objects - } -} - -struct BuiltSystem { - number_of_system_caps: u64, - invocation_data: Vec, - invocation_data_size: u64, - bootstrap_invocations: Vec, - system_invocations: Vec, - kernel_boot_info: BootInfo, - reserved_region: MemoryRegion, - fault_ep_cap_address: u64, - reply_cap_address: u64, - cap_lookup: HashMap, - pd_tcb_caps: Vec, - vm_tcb_caps: Vec, - sched_caps: Vec, - ntfn_caps: Vec, - pd_elf_regions: Vec>, - pd_setvar_values: Vec>, - pd_stack_addrs: Vec, - kernel_objects: Vec, - initial_task_virt_region: MemoryRegion, - initial_task_phys_region: MemoryRegion, -} - -pub fn pd_write_symbols( - pds: &[ProtectionDomain], - channels: &[Channel], - pd_elf_files: &mut [ElfFile], - pd_setvar_values: &[Vec], -) -> Result<(), String> { - for (i, pd) in pds.iter().enumerate() { - let elf = &mut pd_elf_files[i]; - let name = pd.name.as_bytes(); - let name_length = min(name.len(), PD_MAX_NAME_LENGTH); - elf.write_symbol("microkit_name", &name[..name_length])?; - elf.write_symbol("microkit_passive", &[pd.passive as u8])?; - - let mut notification_bits: u64 = 0; - let mut pp_bits: u64 = 0; - for channel in channels { - if channel.end_a.pd == i { - if channel.end_a.notify { - notification_bits |= 1 << channel.end_a.id; - } - if channel.end_a.pp { - pp_bits |= 1 << channel.end_a.id; - } - } - if channel.end_b.pd == i { - if channel.end_b.notify { - notification_bits |= 1 << channel.end_b.id; - } - if channel.end_b.pp { - pp_bits |= 1 << channel.end_b.id; - } - } - } - - elf.write_symbol("microkit_irqs", &pd.irq_bits().to_le_bytes())?; - elf.write_symbol("microkit_notifications", ¬ification_bits.to_le_bytes())?; - elf.write_symbol("microkit_pps", &pp_bits.to_le_bytes())?; - - for (setvar_idx, setvar) in pd.setvars.iter().enumerate() { - let value = pd_setvar_values[i][setvar_idx]; - let result = elf.write_symbol(&setvar.symbol, &value.to_le_bytes()); - if result.is_err() { - return Err(format!( - "No symbol named '{}' in ELF '{}' for PD '{}'", - setvar.symbol, - pd.program_image.display(), - pd.name - )); - } - } - } - - Ok(()) -} - -/// Determine the physical memory regions for an ELF file with a given -/// alignment. -/// -/// The returned region shall be extended (if necessary) so that the start -/// and end are congruent with the specified alignment (usually a page size). -fn phys_mem_regions_from_elf(elf: &ElfFile, alignment: u64) -> Vec { - assert!(alignment > 0); - - elf.segments - .iter() - .filter(|s| s.loadable) - .map(|s| { - MemoryRegion::new( - util::round_down(s.phys_addr, alignment), - util::round_up(s.phys_addr + s.data.len() as u64, alignment), - ) - }) - .collect() -} - -/// Determine a single physical memory region for an ELF. -/// -/// Works as per phys_mem_regions_from_elf, but checks the ELF has a single -/// segment, and returns the region covering the first segment. -fn phys_mem_region_from_elf(elf: &ElfFile, alignment: u64) -> MemoryRegion { - assert!(alignment > 0); - assert!(elf.segments.iter().filter(|s| s.loadable).count() == 1); - - phys_mem_regions_from_elf(elf, alignment)[0] -} - -/// Determine the virtual memory regions for an ELF file with a given -/// alignment. -/// The returned region shall be extended (if necessary) so that the start -/// and end are congruent with the specified alignment (usually a page size). -fn virt_mem_regions_from_elf(elf: &ElfFile, alignment: u64) -> Vec { - assert!(alignment > 0); - elf.segments - .iter() - .filter(|s| s.loadable) - .map(|s| { - MemoryRegion::new( - util::round_down(s.virt_addr, alignment), - util::round_up(s.virt_addr + s.data.len() as u64, alignment), - ) - }) - .collect() -} - -/// Determine a single virtual memory region for an ELF. -/// -/// Works as per virt_mem_regions_from_elf, but checks the ELF has a single -/// segment, and returns the region covering the first segment. -fn virt_mem_region_from_elf(elf: &ElfFile, alignment: u64) -> MemoryRegion { - assert!(alignment > 0); - assert!(elf.segments.iter().filter(|s| s.loadable).count() == 1); - - virt_mem_regions_from_elf(elf, alignment)[0] -} - -fn get_full_path(path: &Path, search_paths: &Vec) -> Option { - for search_path in search_paths { - let full_path = search_path.join(path); - if full_path.exists() { - return Some(full_path.to_path_buf()); - } - } - - None -} - -struct KernelPartialBootInfo { - device_memory: DisjointMemoryRegion, - normal_memory: DisjointMemoryRegion, - boot_region: MemoryRegion, -} - -fn kernel_self_mem(kernel_elf: &ElfFile) -> MemoryRegion { - let segments = kernel_elf.loadable_segments(); - let base = segments[0].phys_addr; - let (ki_end_v, _) = kernel_elf - .find_symbol("ki_end") - .expect("Could not find 'ki_end' symbol"); - let ki_end_p = ki_end_v - segments[0].virt_addr + base; - - MemoryRegion::new(base, ki_end_p) -} - -fn kernel_boot_mem(kernel_elf: &ElfFile) -> MemoryRegion { - let segments = kernel_elf.loadable_segments(); - let base = segments[0].phys_addr; - let (ki_boot_end_v, _) = kernel_elf - .find_symbol("ki_boot_end") - .expect("Could not find 'ki_boot_end' symbol"); - let ki_boot_end_p = ki_boot_end_v - segments[0].virt_addr + base; - - MemoryRegion::new(base, ki_boot_end_p) -} - -/// -/// Emulate what happens during a kernel boot, up to the point -/// where the reserved region is allocated. -/// -/// This factors the common parts of 'emulate_kernel_boot' and -/// 'emulate_kernel_boot_partial' to avoid code duplication. -/// -fn kernel_partial_boot(kernel_config: &Config, kernel_elf: &ElfFile) -> KernelPartialBootInfo { - // Determine the untyped caps of the system - // This lets allocations happen correctly. - let mut device_memory = DisjointMemoryRegion::default(); - let mut normal_memory = DisjointMemoryRegion::default(); - - for r in &kernel_config.device_regions { - device_memory.insert_region(r.start, r.end); - } - for r in &kernel_config.normal_regions { - normal_memory.insert_region(r.start, r.end); - } - - // Remove the kernel image itself - let self_mem = kernel_self_mem(kernel_elf); - normal_memory.remove_region(self_mem.base, self_mem.end); - - // but get the boot region, we'll add that back later - // FIXME: Why calcaultae it now if we add it back later? - let boot_region = kernel_boot_mem(kernel_elf); - - KernelPartialBootInfo { - device_memory, - normal_memory, - boot_region, - } -} - -fn emulate_kernel_boot_partial( - kernel_config: &Config, - kernel_elf: &ElfFile, -) -> (DisjointMemoryRegion, MemoryRegion) { - let partial_info = kernel_partial_boot(kernel_config, kernel_elf); - (partial_info.normal_memory, partial_info.boot_region) -} - -fn get_n_paging(region: MemoryRegion, bits: u64) -> u64 { - let start = util::round_down(region.base, 1 << bits); - let end = util::round_up(region.end, 1 << bits); - - (end - start) / (1 << bits) -} - -fn get_arch_n_paging(config: &Config, region: MemoryRegion) -> u64 { - match config.arch { - Arch::Aarch64 => { - const PT_INDEX_OFFSET: u64 = 12; - const PD_INDEX_OFFSET: u64 = PT_INDEX_OFFSET + 9; - const PUD_INDEX_OFFSET: u64 = PD_INDEX_OFFSET + 9; - - if config.aarch64_vspace_s2_start_l1() { - get_n_paging(region, PUD_INDEX_OFFSET) + get_n_paging(region, PD_INDEX_OFFSET) - } else { - const PGD_INDEX_OFFSET: u64 = PUD_INDEX_OFFSET + 9; - get_n_paging(region, PGD_INDEX_OFFSET) - + get_n_paging(region, PUD_INDEX_OFFSET) - + get_n_paging(region, PD_INDEX_OFFSET) - } - } - Arch::Riscv64 => match config.riscv_pt_levels.unwrap() { - RiscvVirtualMemory::Sv39 => { - const PT_INDEX_OFFSET: u64 = 12; - const LVL1_INDEX_OFFSET: u64 = PT_INDEX_OFFSET + 9; - const LVL2_INDEX_OFFSET: u64 = LVL1_INDEX_OFFSET + 9; - - get_n_paging(region, LVL2_INDEX_OFFSET) + get_n_paging(region, LVL1_INDEX_OFFSET) - } - }, - } -} - -fn rootserver_max_size_bits(config: &Config) -> u64 { - let slot_bits = 5; // seL4_SlotBits - let root_cnode_bits = config.init_cnode_bits; // CONFIG_ROOT_CNODE_SIZE_BITS - let vspace_bits = ObjectType::VSpace.fixed_size_bits(config).unwrap(); - - let cnode_size_bits = root_cnode_bits + slot_bits; - max(cnode_size_bits, vspace_bits) -} - -fn calculate_rootserver_size(config: &Config, initial_task_region: MemoryRegion) -> u64 { - // FIXME: These constants should ideally come from the config / kernel - // binary not be hard coded here. - // But they are constant so it isn't too bad. - let slot_bits = 5; // seL4_SlotBits - let root_cnode_bits = config.init_cnode_bits; // CONFIG_ROOT_CNODE_SIZE_BITS - let tcb_bits = ObjectType::Tcb.fixed_size_bits(config).unwrap(); // seL4_TCBBits - let page_bits = ObjectType::SmallPage.fixed_size_bits(config).unwrap(); // seL4_PageBits - let asid_pool_bits = 12; // seL4_ASIDPoolBits - let vspace_bits = ObjectType::VSpace.fixed_size_bits(config).unwrap(); // seL4_VSpaceBits - let page_table_bits = ObjectType::PageTable.fixed_size_bits(config).unwrap(); // seL4_PageTableBits - let min_sched_context_bits = 7; // seL4_MinSchedContextBits - - let mut size = 0; - size += 1 << (root_cnode_bits + slot_bits); - size += 1 << (tcb_bits); - size += 2 * (1 << page_bits); - size += 1 << asid_pool_bits; - size += 1 << vspace_bits; - size += get_arch_n_paging(config, initial_task_region) * (1 << page_table_bits); - size += 1 << min_sched_context_bits; - - size -} - -/// Emulate what happens during a kernel boot, generating a -/// representation of the BootInfo struct. -fn emulate_kernel_boot( - config: &Config, - kernel_elf: &ElfFile, - initial_task_phys_region: MemoryRegion, - initial_task_virt_region: MemoryRegion, - reserved_region: MemoryRegion, -) -> BootInfo { - assert!(initial_task_phys_region.size() == initial_task_virt_region.size()); - let partial_info = kernel_partial_boot(config, kernel_elf); - let mut normal_memory = partial_info.normal_memory; - let device_memory = partial_info.device_memory; - let boot_region = partial_info.boot_region; - - normal_memory.remove_region(initial_task_phys_region.base, initial_task_phys_region.end); - normal_memory.remove_region(reserved_region.base, reserved_region.end); - - // Now, the tricky part! determine which memory is used for the initial task objects - let initial_objects_size = calculate_rootserver_size(config, initial_task_virt_region); - let initial_objects_align = rootserver_max_size_bits(config); - - // Find an appropriate region of normal memory to allocate the objects - // from; this follows the same algorithm used within the kernel boot code - // (or at least we hope it does!) - // TODO: this loop could be done better in a functional way? - let mut region_to_remove: Option = None; - for region in normal_memory.regions.iter().rev() { - let start = util::round_down( - region.end - initial_objects_size, - 1 << initial_objects_align, - ); - if start >= region.base { - region_to_remove = Some(start); - break; - } - } - if let Some(start) = region_to_remove { - normal_memory.remove_region(start, start + initial_objects_size); - } else { - panic!("Couldn't find appropriate region for initial task kernel objects"); - } - - let fixed_cap_count = 0x10; - let sched_control_cap_count = 1; - let paging_cap_count = get_arch_n_paging(config, initial_task_virt_region); - let page_cap_count = initial_task_virt_region.size() / config.minimum_page_size; - let first_untyped_cap = - fixed_cap_count + paging_cap_count + sched_control_cap_count + page_cap_count; - let sched_control_cap = fixed_cap_count + paging_cap_count; - - let max_bits = match config.arch { - Arch::Aarch64 => 47, - Arch::Riscv64 => 38, - }; - let device_regions: Vec = [ - reserved_region.aligned_power_of_two_regions(config, max_bits), - device_memory.aligned_power_of_two_regions(config, max_bits), - ] - .concat(); - let normal_regions: Vec = [ - boot_region.aligned_power_of_two_regions(config, max_bits), - normal_memory.aligned_power_of_two_regions(config, max_bits), - ] - .concat(); - let mut untyped_objects = Vec::new(); - for (i, r) in device_regions.iter().enumerate() { - let cap = i as u64 + first_untyped_cap; - untyped_objects.push(UntypedObject::new(cap, *r, true)); - } - let normal_regions_start_cap = first_untyped_cap + device_regions.len() as u64; - for (i, r) in normal_regions.iter().enumerate() { - let cap = i as u64 + normal_regions_start_cap; - untyped_objects.push(UntypedObject::new(cap, *r, false)); - } - - let first_available_cap = - first_untyped_cap + device_regions.len() as u64 + normal_regions.len() as u64; - BootInfo { - fixed_cap_count, - paging_cap_count, - page_cap_count, - sched_control_cap, - first_available_cap, - untyped_objects, - } -} - -fn build_system( - config: &Config, - pd_elf_files: &Vec, - kernel_elf: &ElfFile, - monitor_elf: &ElfFile, - system: &SystemDescription, - invocation_table_size: u64, - system_cnode_size: u64, -) -> Result { - assert!(util::is_power_of_two(system_cnode_size)); - assert!(invocation_table_size % config.minimum_page_size == 0); - assert!(invocation_table_size <= MAX_SYSTEM_INVOCATION_SIZE); - - let mut cap_address_names: HashMap = HashMap::new(); - cap_address_names.insert(INIT_NULL_CAP_ADDRESS, "null".to_string()); - cap_address_names.insert(INIT_TCB_CAP_ADDRESS, "TCB: init".to_string()); - cap_address_names.insert(INIT_CNODE_CAP_ADDRESS, "CNode: init".to_string()); - cap_address_names.insert(INIT_VSPACE_CAP_ADDRESS, "VSpace: init".to_string()); - cap_address_names.insert(INIT_ASID_POOL_CAP_ADDRESS, "ASID Pool: init".to_string()); - cap_address_names.insert(IRQ_CONTROL_CAP_ADDRESS, "IRQ Control".to_string()); - cap_address_names.insert(SMC_CAP_IDX, "SMC".to_string()); - - let system_cnode_bits = system_cnode_size.ilog2() as u64; - - // Emulate kernel boot - - // Determine physical memory region used by the monitor - let initial_task_size = phys_mem_region_from_elf(monitor_elf, config.minimum_page_size).size(); - - // Determine physical memory region for 'reserved' memory. - // - // The 'reserved' memory region will not be touched by seL4 during boot - // and allows the monitor (initial task) to create memory regions - // from this area, which can then be made available to the appropriate - // protection domains - let mut pd_elf_size = 0; - for pd_elf in pd_elf_files { - for r in phys_mem_regions_from_elf(pd_elf, config.minimum_page_size) { - pd_elf_size += r.size(); - } - } - let reserved_size = invocation_table_size + pd_elf_size; - - // Now that the size is determined, find a free region in the physical memory - // space. - let (mut available_memory, kernel_boot_region) = - emulate_kernel_boot_partial(config, kernel_elf); - - // The kernel relies on the reserved region being allocated above the kernel - // boot/ELF region, so we have the end of the kernel boot region as the lower - // bound for allocating the reserved region. - let reserved_base = available_memory.allocate_from(reserved_size, kernel_boot_region.end); - assert!(kernel_boot_region.base < reserved_base); - // The kernel relies on the initial task being allocated above the reserved - // region, so we have the address of the end of the reserved region as the - // lower bound for allocating the initial task. - let initial_task_phys_base = - available_memory.allocate_from(initial_task_size, reserved_base + reserved_size); - assert!(reserved_base < initial_task_phys_base); - - let initial_task_phys_region = MemoryRegion::new( - initial_task_phys_base, - initial_task_phys_base + initial_task_size, - ); - let initial_task_virt_region = virt_mem_region_from_elf(monitor_elf, config.minimum_page_size); - - let reserved_region = MemoryRegion::new(reserved_base, reserved_base + reserved_size); - - // Now that the reserved region has been allocated we can determine the specific - // region of physical memory required for the inovcation table itself, and - // all the ELF segments - let invocation_table_region = - MemoryRegion::new(reserved_base, reserved_base + invocation_table_size); - - // 1.3 With both the initial task region and reserved region determined the kernel - // boot can be emulated. This provides the boot info information which is needed - // for the next steps - let kernel_boot_info = emulate_kernel_boot( - config, - kernel_elf, - initial_task_phys_region, - initial_task_virt_region, - reserved_region, - ); - - for ut in &kernel_boot_info.untyped_objects { - let dev_str = if ut.is_device { " (device)" } else { "" }; - let ut_str = format!( - "Untyped @ 0x{:x}:0x{:x}{}", - ut.region.base, - ut.region.size(), - dev_str - ); - cap_address_names.insert(ut.cap, ut_str); - } - - // The kernel boot info allows us to create an allocator for kernel objects - let mut kao = ObjectAllocator::new( - kernel_boot_info - .untyped_objects - .iter() - .filter(|ut| !ut.is_device) - .collect::>(), - ); - - let mut kad = ObjectAllocator::new( - kernel_boot_info - .untyped_objects - .iter() - .filter(|ut| ut.is_device) - .collect::>(), - ); - - // 2. Now that the available resources are known it is possible to proceed with the - // monitor task boot strap. - // - // The boot strap of the monitor works in two phases: - // - // 1. Setting up the monitor's CSpace - // 2. Making the system invocation table available in the monitor's address - // space. - - // 2.1 The monitor's CSpace consists of two CNodes: a/ the initial task CNode - // which consists of all the fixed initial caps along with caps for the - // object create during kernel bootstrap, and b/ the system CNode, which - // contains caps to all objects that will be created in this process. - // The system CNode is of `system_cnode_size`. (Note: see also description - // on how `system_cnode_size` is iteratively determined). - // - // The system CNode is not available at startup and must be created (by retyping - // memory from an untyped object). Once created the two CNodes must be arranged - // as a tree such that the slots in both CNodes are addressable. - // - // The system CNode shall become the root of the CSpace. The initial CNode shall - // be copied to slot zero of the system CNode. In this manner all caps in the initial - // CNode will keep their original cap addresses. This isn't required but it makes - // allocation, debugging and reasoning about the system more straight forward. - // - // The guard shall be selected so the least significant bits are used. The guard - // for the root shall be: - // - // 64 - system cnode bits - initial cnode bits - // - // The guard for the initial CNode will be zero. - // - // 2.1.1: Allocate the *root* CNode. It is two entries: - // slot 0: the existing init cnode - // slot 1: our main system cnode - let root_cnode_bits = 1; - let root_cnode_allocation = kao - .alloc((1 << root_cnode_bits) * (1 << SLOT_BITS)) - .unwrap_or_else(|| panic!("Internal error: failed to allocate root CNode")); - let root_cnode_cap = kernel_boot_info.first_available_cap; - cap_address_names.insert(root_cnode_cap, "CNode: root".to_string()); - - // 2.1.2: Allocate the *system* CNode. It is the cnodes that - // will have enough slots for all required caps. - let system_cnode_allocation = kao - .alloc(system_cnode_size * (1 << SLOT_BITS)) - .unwrap_or_else(|| panic!("Internal error: failed to allocate system CNode")); - let system_cnode_cap = kernel_boot_info.first_available_cap + 1; - cap_address_names.insert(system_cnode_cap, "CNode: system".to_string()); - - // 2.1.3: Now that we've allocated the space for these we generate - // the actual systems calls. - // - // First up create the root cnode - let mut bootstrap_invocations = Vec::new(); - - bootstrap_invocations.push(Invocation::new( - config, - InvocationArgs::UntypedRetype { - untyped: root_cnode_allocation.untyped_cap_address, - object_type: ObjectType::CNode, - size_bits: root_cnode_bits, - root: INIT_CNODE_CAP_ADDRESS, - node_index: 0, - node_depth: 0, - node_offset: root_cnode_cap, - num_objects: 1, - }, - )); - - // 2.1.4: Now insert a cap to the initial Cnode into slot zero of the newly - // allocated root Cnode. It uses sufficient guard bits to ensure it is - // completed padded to word size - // - // guard size is the lower bit of the guard, upper bits are the guard itself - // which for out purposes is always zero. - let guard = config.cap_address_bits - root_cnode_bits - config.init_cnode_bits; - bootstrap_invocations.push(Invocation::new( - config, - InvocationArgs::CnodeMint { - cnode: root_cnode_cap, - dest_index: 0, - dest_depth: root_cnode_bits, - src_root: INIT_CNODE_CAP_ADDRESS, - src_obj: INIT_CNODE_CAP_ADDRESS, - src_depth: config.cap_address_bits, - rights: Rights::All as u64, - badge: guard, - }, - )); - - // 2.1.5: Now it is possible to switch our root Cnode to the newly create - // root cnode. We have a zero sized guard. This Cnode represents the top - // bit of any cap addresses. - let root_guard = 0; - bootstrap_invocations.push(Invocation::new( - config, - InvocationArgs::TcbSetSpace { - tcb: INIT_TCB_CAP_ADDRESS, - fault_ep: INIT_NULL_CAP_ADDRESS, - cspace_root: root_cnode_cap, - cspace_root_data: root_guard, - vspace_root: INIT_VSPACE_CAP_ADDRESS, - vspace_root_data: 0, - }, - )); - - // 2.1.6: Now we can create our new system Cnode. We will place it into - // a temporary cap slot in the initial CNode to start with. - bootstrap_invocations.push(Invocation::new( - config, - InvocationArgs::UntypedRetype { - untyped: system_cnode_allocation.untyped_cap_address, - object_type: ObjectType::CNode, - size_bits: system_cnode_bits, - root: INIT_CNODE_CAP_ADDRESS, - node_index: 0, - node_depth: 0, - node_offset: system_cnode_cap, - num_objects: 1, - }, - )); - - // 2.1.7: Now that the we have create the object, we can 'mutate' it - // to the correct place: - // Slot #1 of the new root cnode - let system_cap_address_mask = 1 << (config.cap_address_bits - 1); - bootstrap_invocations.push(Invocation::new( - config, - InvocationArgs::CnodeMint { - cnode: root_cnode_cap, - dest_index: 1, - dest_depth: root_cnode_bits, - src_root: INIT_CNODE_CAP_ADDRESS, - src_obj: system_cnode_cap, - src_depth: config.cap_address_bits, - rights: Rights::All as u64, - badge: config.cap_address_bits - root_cnode_bits - system_cnode_bits, - }, - )); - - // 2.2 At this point it is necessary to get the frames containing the - // main system invocations into the virtual address space. (Remember the - // invocations we are writing out here actually _execute_ at run time! - // It is a bit weird that we talk about mapping in the invocation data - // before we have even generated the invocation data!). - // - // This needs a few steps: - // - // 1. Turn untyped into page objects - // 2. Map the page objects into the address space - // - - // 2.2.1: The memory for the system invocation data resides at the start - // of the reserved region. We can retype multiple frames as a time ( - // which reduces the number of invocations we need). However, it is possible - // that the region spans multiple untyped objects. - // At this point in time we assume we will map the area using the minimum - // page size. It would be good in the future to use super pages (when - // it makes sense to - this would reduce memory usage, and the number of - // invocations required to set up the address space - let pages_required = invocation_table_size / config.minimum_page_size; - let base_page_cap = 0; - for pta in base_page_cap..base_page_cap + pages_required { - cap_address_names.insert( - system_cap_address_mask | pta, - "SmallPage: monitor invocation table".to_string(), - ); - } - - let mut remaining_pages = pages_required; - let mut invocation_table_allocations = Vec::new(); - let mut cap_slot = base_page_cap; - let mut phys_addr = invocation_table_region.base; - - let boot_info_device_untypeds: Vec<&UntypedObject> = kernel_boot_info - .untyped_objects - .iter() - .filter(|o| o.is_device) - .collect(); - for ut in boot_info_device_untypeds { - let ut_pages = ut.region.size() / config.minimum_page_size; - let retype_page_count = min(ut_pages, remaining_pages); - - let mut retypes_remaining = retype_page_count; - while retypes_remaining > 0 { - let num_retypes = min(retypes_remaining, config.fan_out_limit); - bootstrap_invocations.push(Invocation::new( - config, - InvocationArgs::UntypedRetype { - untyped: ut.cap, - object_type: ObjectType::SmallPage, - size_bits: 0, - root: root_cnode_cap, - node_index: 1, - node_depth: 1, - node_offset: cap_slot, - num_objects: num_retypes, - }, - )); - - retypes_remaining -= num_retypes; - cap_slot += num_retypes; - } - - remaining_pages -= retype_page_count; - phys_addr += retype_page_count * config.minimum_page_size; - invocation_table_allocations.push((ut, phys_addr)); - if remaining_pages == 0 { - break; - } - } - - // 2.2.1: Now that physical pages have been allocated it is possible to setup - // the virtual memory objects so that the pages can be mapped into virtual memory - // At this point we map into the arbitrary address of 0x0.8000.0000 (i.e.: 2GiB) - // We arbitrary limit the maximum size to be 128MiB. This allows for at least 1 million - // invocations to occur at system startup. This should be enough for any reasonable - // sized system. - // - // Before mapping it is necessary to install page tables that can cover the region. - let large_page_size = ObjectType::LargePage.fixed_size(config).unwrap(); - let page_table_size = ObjectType::PageTable.fixed_size(config).unwrap(); - let page_tables_required = - util::round_up(invocation_table_size, large_page_size) / large_page_size; - let page_table_allocation = kao - .alloc_n(page_table_size, page_tables_required) - .unwrap_or_else(|| panic!("Internal error: failed to allocate page tables")); - let base_page_table_cap = cap_slot; - - for pta in base_page_table_cap..base_page_table_cap + page_tables_required { - cap_address_names.insert( - system_cap_address_mask | pta, - "PageTable: monitor".to_string(), - ); - } - - assert!(page_tables_required <= config.fan_out_limit); - bootstrap_invocations.push(Invocation::new( - config, - InvocationArgs::UntypedRetype { - untyped: page_table_allocation.untyped_cap_address, - object_type: ObjectType::PageTable, - size_bits: 0, - root: root_cnode_cap, - node_index: 1, - node_depth: 1, - node_offset: cap_slot, - num_objects: page_tables_required, - }, - )); - cap_slot += page_tables_required; - - let page_table_vaddr: u64 = 0x8000_0000; - // Now that the page tables are allocated they can be mapped into vspace - let bootstrap_pt_attr = match config.arch { - Arch::Aarch64 => ArmVmAttributes::default(), - Arch::Riscv64 => RiscvVmAttributes::default(), - }; - let mut pt_map_invocation = Invocation::new( - config, - InvocationArgs::PageTableMap { - page_table: system_cap_address_mask | base_page_table_cap, - vspace: INIT_VSPACE_CAP_ADDRESS, - vaddr: page_table_vaddr, - attr: bootstrap_pt_attr, - }, - ); - pt_map_invocation.repeat( - page_tables_required as u32, - InvocationArgs::PageTableMap { - page_table: 1, - vspace: 0, - vaddr: ObjectType::LargePage.fixed_size(config).unwrap(), - attr: 0, - }, - ); - bootstrap_invocations.push(pt_map_invocation); - - // Finally, once the page tables are allocated the pages can be mapped - let page_vaddr: u64 = 0x8000_0000; - let bootstrap_page_attr = match config.arch { - Arch::Aarch64 => ArmVmAttributes::default() | ArmVmAttributes::ExecuteNever as u64, - Arch::Riscv64 => RiscvVmAttributes::default() | RiscvVmAttributes::ExecuteNever as u64, - }; - let mut map_invocation = Invocation::new( - config, - InvocationArgs::PageMap { - page: system_cap_address_mask | base_page_cap, - vspace: INIT_VSPACE_CAP_ADDRESS, - vaddr: page_vaddr, - rights: Rights::Read as u64, - attr: bootstrap_page_attr, - }, - ); - map_invocation.repeat( - pages_required as u32, - InvocationArgs::PageMap { - page: 1, - vspace: 0, - vaddr: config.minimum_page_size, - rights: 0, - attr: 0, - }, - ); - bootstrap_invocations.push(map_invocation); - - // 3. Now we can start setting up the system based on the information - // the user provided in the System Description Format. - // - // Create all the objects: - // - // TCBs: one per PD - // Endpoints: one per PD with a PP + one for the monitor - // Notification: one per PD - // VSpaces: one per PD - // CNodes: one per PD - // Small Pages: - // one per pd for IPC buffer - // as needed by MRs - // Large Pages: - // as needed by MRs - // Page table structs: - // as needed by protection domains based on mappings required - let mut phys_addr_next = reserved_base + invocation_table_size; - // Now we create additional MRs (and mappings) for the ELF files. - let mut pd_elf_regions: Vec> = Vec::with_capacity(system.protection_domains.len()); - let mut extra_mrs = Vec::new(); - let mut pd_extra_maps: HashMap<&ProtectionDomain, Vec> = HashMap::new(); - for (i, pd) in system.protection_domains.iter().enumerate() { - pd_elf_regions.push(Vec::with_capacity(pd_elf_files[i].segments.len())); - for (seg_idx, segment) in pd_elf_files[i].segments.iter().enumerate() { - if !segment.loadable { - continue; - } - - let segment_phys_addr = phys_addr_next + (segment.virt_addr % config.minimum_page_size); - pd_elf_regions[i].push(Region::new( - format!("PD-ELF {}-{}", pd.name, seg_idx), - segment_phys_addr, - segment.data.len() as u64, - seg_idx, - )); - - let mut perms = 0; - if segment.is_readable() { - perms |= SysMapPerms::Read as u8; - } - if segment.is_writable() { - perms |= SysMapPerms::Write as u8; - } - if segment.is_executable() { - perms |= SysMapPerms::Execute as u8; - } - - let base_vaddr = util::round_down(segment.virt_addr, config.minimum_page_size); - let end_vaddr = util::round_up( - segment.virt_addr + segment.mem_size(), - config.minimum_page_size, - ); - let aligned_size = end_vaddr - base_vaddr; - let name = format!("ELF:{}-{}", pd.name, seg_idx); - let mr = SysMemoryRegion { - name, - size: aligned_size, - page_size: PageSize::Small, - page_count: aligned_size / PageSize::Small as u64, - phys_addr: Some(phys_addr_next), - text_pos: None, - kind: SysMemoryRegionKind::Elf, - }; - phys_addr_next += aligned_size; - - let mp = SysMap { - mr: mr.name.clone(), - vaddr: base_vaddr, - perms, - cached: true, - text_pos: None, - }; - if let Some(extra_maps) = pd_extra_maps.get_mut(pd) { - extra_maps.push(mp); - } else { - pd_extra_maps.insert(pd, vec![mp]); - } - - // Add to extra_mrs at the end to avoid movement issues with the MR since it's used in - // constructing the SysMap struct - extra_mrs.push(mr); - } - } - - assert!(phys_addr_next - (reserved_base + invocation_table_size) == pd_elf_size); - - // Here we create a memory region/mapping for the stack for each PD. - // We allocate the stack at the highest possible virtual address that the - // kernel allows us. - let mut pd_stack_addrs = Vec::with_capacity(system.protection_domains.len()); - for pd in &system.protection_domains { - let stack_mr = SysMemoryRegion { - name: format!("STACK:{}", pd.name), - size: pd.stack_size, - page_size: PageSize::Small, - page_count: pd.stack_size / PageSize::Small as u64, - phys_addr: None, - text_pos: None, - kind: SysMemoryRegionKind::Stack, - }; - - let stack_map = SysMap { - mr: stack_mr.name.clone(), - vaddr: config.pd_stack_bottom(pd.stack_size), - perms: SysMapPerms::Read as u8 | SysMapPerms::Write as u8, - cached: true, - text_pos: None, - }; - - pd_stack_addrs.push(stack_map.vaddr); - - extra_mrs.push(stack_mr); - pd_extra_maps.get_mut(pd).unwrap().push(stack_map); - } - - let mut all_mrs: Vec<&SysMemoryRegion> = - Vec::with_capacity(system.memory_regions.len() + extra_mrs.len()); - for mr_set in [&system.memory_regions, &extra_mrs] { - for mr in mr_set { - all_mrs.push(mr); - } - } - let all_mr_by_name: HashMap<&str, &SysMemoryRegion> = - all_mrs.iter().map(|mr| (mr.name.as_str(), *mr)).collect(); - - let mut system_invocations: Vec = Vec::new(); - let mut init_system = InitSystem::new( - config, - root_cnode_cap, - system_cap_address_mask, - cap_slot, - &mut kao, - &mut kad, - &mut system_invocations, - &mut cap_address_names, - ); - - init_system.reserve(invocation_table_allocations); - let mut mr_pages: HashMap<&SysMemoryRegion, Vec> = HashMap::new(); - - // 3.1 Work out how many fixed page objects are required - - // First we need to find all the requested pages and sorted them - let mut fixed_pages = Vec::new(); - for mr in &all_mrs { - if let Some(mut phys_addr) = mr.phys_addr { - mr_pages.insert(mr, vec![]); - for _ in 0..mr.page_count { - fixed_pages.push((phys_addr, mr)); - phys_addr += mr.page_size_bytes(); - } - } - } - - // Sort based on the starting physical address - fixed_pages.sort_by_key(|p| p.0); - - // FIXME: At this point we can recombine them into - // groups to optimize allocation - for (phys_addr, mr) in fixed_pages { - let obj_type = match mr.page_size { - PageSize::Small => ObjectType::SmallPage, - PageSize::Large => ObjectType::LargePage, - }; - - let (page_size_human, page_size_label) = util::human_size_strict(mr.page_size as u64); - let name = format!( - "Page({} {}): MR={} @ {:x}", - page_size_human, page_size_label, mr.name, phys_addr - ); - let page = init_system.allocate_fixed_object(phys_addr, obj_type, name); - mr_pages.get_mut(mr).unwrap().push(page); - } - - // 3.2 Work out how many regular (non-fixed) page objects are required - let mut small_page_names = Vec::new(); - let mut large_page_names = Vec::new(); - - for pd in &system.protection_domains { - let (page_size_human, page_size_label) = util::human_size_strict(PageSize::Small as u64); - let ipc_buffer_str = format!( - "Page({} {}): IPC Buffer PD={}", - page_size_human, page_size_label, pd.name - ); - small_page_names.push(ipc_buffer_str); - } - - for mr in &all_mrs { - if mr.phys_addr.is_some() { - continue; - } - - let (page_size_human, page_size_label) = util::human_size_strict(mr.page_size as u64); - for idx in 0..mr.page_count { - let page_str = format!( - "Page({} {}): MR={} #{}", - page_size_human, page_size_label, mr.name, idx - ); - match mr.page_size as PageSize { - PageSize::Small => small_page_names.push(page_str), - PageSize::Large => large_page_names.push(page_str), - } - } - } - - let large_page_objs = - init_system.allocate_objects(ObjectType::LargePage, large_page_names, None); - let small_page_objs = - init_system.allocate_objects(ObjectType::SmallPage, small_page_names, None); - - // All the IPC buffers are the first to be allocated which is why this works - let ipc_buffer_objs = &small_page_objs[..system.protection_domains.len()]; - - let mut page_small_idx = ipc_buffer_objs.len(); - let mut page_large_idx = 0; - - for mr in &all_mrs { - if mr.phys_addr.is_some() { - continue; - } - - let idx = match mr.page_size { - PageSize::Small => page_small_idx, - PageSize::Large => page_large_idx, - }; - let objs = match mr.page_size { - PageSize::Small => small_page_objs[idx..idx + mr.page_count as usize].to_vec(), - PageSize::Large => large_page_objs[idx..idx + mr.page_count as usize].to_vec(), - }; - mr_pages.insert(mr, objs); - match mr.page_size { - PageSize::Small => page_small_idx += mr.page_count as usize, - PageSize::Large => page_large_idx += mr.page_count as usize, - } - } - - let virtual_machines: Vec<&VirtualMachine> = system - .protection_domains - .iter() - .filter_map(|pd| match &pd.virtual_machine { - Some(vm) => Some(vm), - None => None, - }) - .collect(); - - // TCBs - let mut tcb_names: Vec = system - .protection_domains - .iter() - .map(|pd| format!("TCB: PD={}", pd.name)) - .collect(); - let mut vcpu_tcb_names = vec![]; - for vm in &virtual_machines { - for vcpu in &vm.vcpus { - vcpu_tcb_names.push(format!("TCB: VM(VCPU-{})={}", vcpu.id, vm.name)); - } - } - tcb_names.extend(vcpu_tcb_names); - let tcb_objs = init_system.allocate_objects(ObjectType::Tcb, tcb_names, None); - let tcb_caps: Vec = tcb_objs.iter().map(|tcb| tcb.cap_addr).collect(); - - let pd_tcb_objs = &tcb_objs[..system.protection_domains.len()]; - let vcpu_tcb_objs = &tcb_objs[system.protection_domains.len()..]; - assert!(pd_tcb_objs.len() + vcpu_tcb_objs.len() == tcb_objs.len()); - // VCPUs - let mut vcpu_names = vec![]; - for vm in &virtual_machines { - for vcpu in &vm.vcpus { - vcpu_names.push(format!("VCPU-{}: VM={}", vcpu.id, vm.name)); - } - } - let vcpu_objs = init_system.allocate_objects(ObjectType::Vcpu, vcpu_names, None); - // Scheduling Contexts - let mut sched_context_names: Vec = system - .protection_domains - .iter() - .map(|pd| format!("SchedContext: PD={}", pd.name)) - .collect(); - let mut vm_sched_context_names = vec![]; - for vm in &virtual_machines { - for vcpu in &vm.vcpus { - vm_sched_context_names.push(format!("SchedContext: VM(VCPU-{})={}", vcpu.id, vm.name)); - } - } - sched_context_names.extend(vm_sched_context_names); - let sched_context_objs = init_system.allocate_objects( - ObjectType::SchedContext, - sched_context_names, - Some(PD_SCHEDCONTEXT_SIZE), - ); - let sched_context_caps: Vec = sched_context_objs.iter().map(|sc| sc.cap_addr).collect(); - - let pd_sched_context_objs = &sched_context_objs[..system.protection_domains.len()]; - let vm_sched_context_objs = &sched_context_objs[system.protection_domains.len()..]; - - // Endpoints - let pd_endpoint_names: Vec = system - .protection_domains - .iter() - .enumerate() - .filter(|(idx, pd)| pd.needs_ep(*idx, &system.channels)) - .map(|(_, pd)| format!("EP: PD={}", pd.name)) - .collect(); - let endpoint_names = [vec![format!("EP: Monitor Fault")], pd_endpoint_names].concat(); - // Reply objects - let pd_reply_names: Vec = system - .protection_domains - .iter() - .map(|pd| format!("Reply: PD={}", pd.name)) - .collect(); - let reply_names = [vec![format!("Reply: Monitor")], pd_reply_names].concat(); - let reply_objs = init_system.allocate_objects(ObjectType::Reply, reply_names, None); - let reply_obj = &reply_objs[0]; - // FIXME: Probably only need reply objects for PPs - let pd_reply_objs = &reply_objs[1..]; - let endpoint_objs = init_system.allocate_objects(ObjectType::Endpoint, endpoint_names, None); - let fault_ep_endpoint_object = &endpoint_objs[0]; - - // Because the first reply object is for the monitor, we map from index 1 of endpoint_objs - let pd_endpoint_objs: Vec> = { - let mut i = 0; - system - .protection_domains - .iter() - .enumerate() - .map(|(idx, pd)| { - if pd.needs_ep(idx, &system.channels) { - let obj = &endpoint_objs[1..][i]; - i += 1; - Some(obj) - } else { - None - } - }) - .collect() - }; - - let notification_names = system - .protection_domains - .iter() - .map(|pd| format!("Notification: PD={}", pd.name)) - .collect(); - let notification_objs = - init_system.allocate_objects(ObjectType::Notification, notification_names, None); - let notification_caps = notification_objs.iter().map(|ntfn| ntfn.cap_addr).collect(); - - // Determine number of upper directory / directory / page table objects required - // - // Upper directory (level 3 table) is based on how many 512 GiB parts of the address - // space is covered (normally just 1!). - // - // Page directory (level 2 table) is based on how many 1,024 MiB parts of - // the address space is covered - // - // Page table (level 3 table) is based on how many 2 MiB parts of the - // address space is covered (excluding any 2MiB regions covered by large - // pages). - let mut all_pd_uds: Vec<(usize, u64)> = Vec::new(); - let mut all_pd_ds: Vec<(usize, u64)> = Vec::new(); - let mut all_pd_pts: Vec<(usize, u64)> = Vec::new(); - for (pd_idx, pd) in system.protection_domains.iter().enumerate() { - let (ipc_buffer_vaddr, _) = pd_elf_files[pd_idx] - .find_symbol(SYMBOL_IPC_BUFFER) - .unwrap_or_else(|_| panic!("Could not find {SYMBOL_IPC_BUFFER}")); - let mut upper_directory_vaddrs = HashSet::new(); - let mut directory_vaddrs = HashSet::new(); - let mut page_table_vaddrs = HashSet::new(); - - // For each page, in each map determine we determine - // which upper directory, directory and page table is resides - // in, and then page sure this is set - let mut vaddrs = vec![(ipc_buffer_vaddr, PageSize::Small)]; - for map_set in [&pd.maps, &pd_extra_maps[pd]] { - for map in map_set { - let mr = all_mr_by_name[map.mr.as_str()]; - let mut vaddr = map.vaddr; - for _ in 0..mr.page_count { - vaddrs.push((vaddr, mr.page_size)); - vaddr += mr.page_size_bytes(); - } - } - } - - for (vaddr, page_size) in vaddrs { - match config.arch { - Arch::Aarch64 => { - if !config.aarch64_vspace_s2_start_l1() { - upper_directory_vaddrs.insert(util::mask_bits(vaddr, 12 + 9 + 9 + 9)); - } - } - Arch::Riscv64 => {} - } - - directory_vaddrs.insert(util::mask_bits(vaddr, 12 + 9 + 9)); - if page_size == PageSize::Small { - page_table_vaddrs.insert(util::mask_bits(vaddr, 12 + 9)); - } - } - - let mut pd_uds: Vec<(usize, u64)> = upper_directory_vaddrs - .into_iter() - .map(|vaddr| (pd_idx, vaddr)) - .collect(); - pd_uds.sort_by_key(|ud| ud.1); - all_pd_uds.extend(pd_uds); - - let mut pd_ds: Vec<(usize, u64)> = directory_vaddrs - .into_iter() - .map(|vaddr| (pd_idx, vaddr)) - .collect(); - pd_ds.sort_by_key(|d| d.1); - all_pd_ds.extend(pd_ds); - - let mut pd_pts: Vec<(usize, u64)> = page_table_vaddrs - .into_iter() - .map(|vaddr| (pd_idx, vaddr)) - .collect(); - pd_pts.sort_by_key(|pt| pt.1); - all_pd_pts.extend(pd_pts); - } - all_pd_uds.sort_by_key(|ud| ud.0); - all_pd_ds.sort_by_key(|d| d.0); - all_pd_pts.sort_by_key(|pt| pt.0); - - let mut all_vm_uds: Vec<(usize, u64)> = Vec::new(); - let mut all_vm_ds: Vec<(usize, u64)> = Vec::new(); - let mut all_vm_pts: Vec<(usize, u64)> = Vec::new(); - for (vm_idx, vm) in virtual_machines.iter().enumerate() { - let mut upper_directory_vaddrs = HashSet::new(); - let mut directory_vaddrs = HashSet::new(); - let mut page_table_vaddrs = HashSet::new(); - - let mut vaddrs = vec![]; - for map in &vm.maps { - let mr = all_mr_by_name[map.mr.as_str()]; - let mut vaddr = map.vaddr; - for _ in 0..mr.page_count { - vaddrs.push((vaddr, mr.page_size)); - vaddr += mr.page_size_bytes(); - } - } - - for (vaddr, page_size) in vaddrs { - assert!(config.hypervisor); - if !config.aarch64_vspace_s2_start_l1() { - upper_directory_vaddrs.insert(util::mask_bits(vaddr, 12 + 9 + 9 + 9)); - } - directory_vaddrs.insert(util::mask_bits(vaddr, 12 + 9 + 9)); - if page_size == PageSize::Small { - page_table_vaddrs.insert(util::mask_bits(vaddr, 12 + 9)); - } - } - - let mut vm_uds: Vec<(usize, u64)> = upper_directory_vaddrs - .into_iter() - .map(|vaddr| (vm_idx, vaddr)) - .collect(); - vm_uds.sort_by_key(|ud| ud.1); - all_vm_uds.extend(vm_uds); - - let mut vm_ds: Vec<(usize, u64)> = directory_vaddrs - .into_iter() - .map(|vaddr| (vm_idx, vaddr)) - .collect(); - vm_ds.sort_by_key(|d| d.1); - all_vm_ds.extend(vm_ds); - - let mut vm_pts: Vec<(usize, u64)> = page_table_vaddrs - .into_iter() - .map(|vaddr| (vm_idx, vaddr)) - .collect(); - vm_pts.sort_by_key(|pt| pt.1); - all_vm_pts.extend(vm_pts); - } - all_vm_uds.sort_by_key(|ud| ud.0); - all_vm_ds.sort_by_key(|d| d.0); - all_vm_pts.sort_by_key(|pt| pt.0); - - let pd_names: Vec<&str> = system - .protection_domains - .iter() - .map(|pd| pd.name.as_str()) - .collect(); - let vm_names: Vec<&str> = virtual_machines.iter().map(|vm| vm.name.as_str()).collect(); - - let mut vspace_names: Vec = system - .protection_domains - .iter() - .map(|pd| format!("VSpace: PD={}", pd.name)) - .collect(); - let vm_vspace_names: Vec = virtual_machines - .iter() - .map(|vm| format!("VSpace: VM={}", vm.name)) - .collect(); - vspace_names.extend(vm_vspace_names); - let vspace_objs = init_system.allocate_objects(ObjectType::VSpace, vspace_names, None); - let pd_vspace_objs = &vspace_objs[..system.protection_domains.len()]; - let vm_vspace_objs = &vspace_objs[system.protection_domains.len()..]; - - let pd_ud_names: Vec = all_pd_uds - .iter() - .map(|(pd_idx, vaddr)| format!("PageTable: PD={} VADDR=0x{:x}", pd_names[*pd_idx], vaddr)) - .collect(); - let vm_ud_names: Vec = all_vm_uds - .iter() - .map(|(vm_idx, vaddr)| format!("PageTable: VM={} VADDR=0x{:x}", vm_names[*vm_idx], vaddr)) - .collect(); - - let pd_ud_objs = init_system.allocate_objects(ObjectType::PageTable, pd_ud_names, None); - let vm_ud_objs = init_system.allocate_objects(ObjectType::PageTable, vm_ud_names, None); - - if !config.hypervisor { - assert!(vm_ud_objs.is_empty()); - } - - let pd_d_names: Vec = all_pd_ds - .iter() - .map(|(pd_idx, vaddr)| format!("PageTable: PD={} VADDR=0x{:x}", pd_names[*pd_idx], vaddr)) - .collect(); - let vm_d_names: Vec = all_vm_ds - .iter() - .map(|(vm_idx, vaddr)| format!("PageTable: VM={} VADDR=0x{:x}", vm_names[*vm_idx], vaddr)) - .collect(); - let pd_d_objs = init_system.allocate_objects(ObjectType::PageTable, pd_d_names, None); - let vm_d_objs = init_system.allocate_objects(ObjectType::PageTable, vm_d_names, None); - - let pd_pt_names: Vec = all_pd_pts - .iter() - .map(|(pd_idx, vaddr)| format!("PageTable: PD={} VADDR=0x{:x}", pd_names[*pd_idx], vaddr)) - .collect(); - let vm_pt_names: Vec = all_vm_pts - .iter() - .map(|(vm_idx, vaddr)| format!("PageTable: VM={} VADDR=0x{:x}", vm_names[*vm_idx], vaddr)) - .collect(); - let pd_pt_objs = init_system.allocate_objects(ObjectType::PageTable, pd_pt_names, None); - let vm_pt_objs = init_system.allocate_objects(ObjectType::PageTable, vm_pt_names, None); - - // Create CNodes - all CNode objects are the same size: 128 slots. - let mut cnode_names: Vec = system - .protection_domains - .iter() - .map(|pd| format!("CNode: PD={}", pd.name)) - .collect(); - let vm_cnode_names: Vec = virtual_machines - .iter() - .map(|vm| format!("CNode: VM={}", vm.name)) - .collect(); - cnode_names.extend(vm_cnode_names); - - let cnode_objs = - init_system.allocate_objects(ObjectType::CNode, cnode_names, Some(PD_CAP_SIZE)); - let mut cnode_objs_by_pd: HashMap<&ProtectionDomain, &Object> = - HashMap::with_capacity(system.protection_domains.len()); - for (i, pd) in system.protection_domains.iter().enumerate() { - cnode_objs_by_pd.insert(pd, &cnode_objs[i]); - } - - let vm_cnode_objs = &cnode_objs[system.protection_domains.len()..]; - - let mut cap_slot = init_system.cap_slot; - let kernel_objects = init_system.objects; - - // Create all the necessary interrupt handler objects. These aren't - // created through retype though! - let mut irq_cap_addresses: HashMap<&ProtectionDomain, Vec> = HashMap::new(); - for pd in &system.protection_domains { - irq_cap_addresses.insert(pd, vec![]); - for sysirq in &pd.irqs { - let cap_address = system_cap_address_mask | cap_slot; - system_invocations.push(Invocation::new( - config, - InvocationArgs::IrqControlGetTrigger { - irq_control: IRQ_CONTROL_CAP_ADDRESS, - irq: sysirq.irq, - trigger: sysirq.trigger, - dest_root: root_cnode_cap, - dest_index: cap_address, - dest_depth: config.cap_address_bits, - }, - )); - - cap_slot += 1; - cap_address_names.insert(cap_address, format!("IRQ Handler: irq={}", sysirq.irq)); - irq_cap_addresses.get_mut(pd).unwrap().push(cap_address); - } - } - - // This has to be done prior to minting! - let num_asid_invocations = system.protection_domains.len() + virtual_machines.len(); - let mut asid_invocation = Invocation::new( - config, - InvocationArgs::AsidPoolAssign { - asid_pool: INIT_ASID_POOL_CAP_ADDRESS, - vspace: vspace_objs[0].cap_addr, - }, - ); - asid_invocation.repeat( - num_asid_invocations as u32, - InvocationArgs::AsidPoolAssign { - asid_pool: 0, - vspace: 1, - }, - ); - system_invocations.push(asid_invocation); - - // Check that the user has not created any maps that clash with our extra maps - for pd in &system.protection_domains { - let curr_pd_extra_maps = &pd_extra_maps[pd]; - for pd_map in &pd.maps { - for extra_map in curr_pd_extra_maps { - let mr = all_mr_by_name[pd_map.mr.as_str()]; - let base = pd_map.vaddr; - let end = base + mr.size; - let extra_mr = all_mr_by_name[extra_map.mr.as_str()]; - let extra_map_base = extra_map.vaddr; - let extra_map_end = extra_map_base + extra_mr.size; - if !(base >= extra_map_end || end <= extra_map_base) { - eprintln!("ERROR: PD '{}' contains overlapping map, mapping for '{}' [0x{:x}..0x{:x}) overlaps with mapping for '{}' [0x{:x}..0x{:x})", - pd.name, pd_map.mr, base, end, extra_map.mr, extra_map_base, extra_map_end); - match extra_mr.kind { - SysMemoryRegionKind::Elf => { - eprintln!( - "ERROR: mapping for '{}' would overlap with the ELF for PD '{}'", - pd_map.mr, pd.name - ); - } - SysMemoryRegionKind::Stack => { - eprintln!( - "ERROR: mapping for '{}' would overlap with stack region or PD '{}'", - pd_map.mr, pd.name - ); - } - SysMemoryRegionKind::User => { - // This is not expected because there should not be any 'User' kind of MRs - // in the extra maps list. - panic!("internal error: did not expect to encounter user defined MR in this case"); - } - } - std::process::exit(1); - } - } - } - } - - // Create copies of all caps required via minting. - - // Mint copies of required pages, while also determining what's required - // for later mapping - let mut pd_page_descriptors = Vec::new(); - for (pd_idx, pd) in system.protection_domains.iter().enumerate() { - for map_set in [&pd.maps, &pd_extra_maps[pd]] { - for mp in map_set { - let mr = all_mr_by_name[mp.mr.as_str()]; - let mut rights: u64 = Rights::None as u64; - let mut attrs = match config.arch { - Arch::Aarch64 => ArmVmAttributes::ParityEnabled as u64, - Arch::Riscv64 => 0, - }; - if mp.perms & SysMapPerms::Read as u8 != 0 { - rights |= Rights::Read as u64; - } - if mp.perms & SysMapPerms::Write as u8 != 0 { - rights |= Rights::Write as u64; - } - if mp.perms & SysMapPerms::Execute as u8 == 0 { - match config.arch { - Arch::Aarch64 => attrs |= ArmVmAttributes::ExecuteNever as u64, - Arch::Riscv64 => attrs |= RiscvVmAttributes::ExecuteNever as u64, - } - } - if mp.cached { - match config.arch { - Arch::Aarch64 => attrs |= ArmVmAttributes::Cacheable as u64, - Arch::Riscv64 => {} - } - } - - assert!(!mr_pages[mr].is_empty()); - assert!(util::objects_adjacent(&mr_pages[mr])); - - let mut invocation = Invocation::new( - config, - InvocationArgs::CnodeMint { - cnode: system_cnode_cap, - dest_index: cap_slot, - dest_depth: system_cnode_bits, - src_root: root_cnode_cap, - src_obj: mr_pages[mr][0].cap_addr, - src_depth: config.cap_address_bits, - rights, - badge: 0, - }, - ); - invocation.repeat( - mr_pages[mr].len() as u32, - InvocationArgs::CnodeMint { - cnode: 0, - dest_index: 1, - dest_depth: 0, - src_root: 0, - src_obj: 1, - src_depth: 0, - rights: 0, - badge: 0, - }, - ); - system_invocations.push(invocation); - - pd_page_descriptors.push(( - system_cap_address_mask | cap_slot, - pd_idx, - mp.vaddr, - rights, - attrs, - mr_pages[mr].len() as u64, - mr.page_size_bytes(), - )); - - for idx in 0..mr_pages[mr].len() { - cap_address_names.insert( - system_cap_address_mask | (cap_slot + idx as u64), - format!( - "{} (derived)", - cap_address_names - .get(&(mr_pages[mr][0].cap_addr + idx as u64)) - .unwrap() - ), - ); - } - - cap_slot += mr_pages[mr].len() as u64; - } - } - } - - let mut vm_page_descriptors = Vec::new(); - for (vm_idx, vm) in virtual_machines.iter().enumerate() { - for mp in &vm.maps { - let mr = all_mr_by_name[mp.mr.as_str()]; - let mut rights: u64 = Rights::None as u64; - let mut attrs = match config.arch { - Arch::Aarch64 => ArmVmAttributes::ParityEnabled as u64, - Arch::Riscv64 => 0, - }; - if mp.perms & SysMapPerms::Read as u8 != 0 { - rights |= Rights::Read as u64; - } - if mp.perms & SysMapPerms::Write as u8 != 0 { - rights |= Rights::Write as u64; - } - if mp.perms & SysMapPerms::Execute as u8 == 0 { - match config.arch { - Arch::Aarch64 => attrs |= ArmVmAttributes::ExecuteNever as u64, - Arch::Riscv64 => attrs |= RiscvVmAttributes::ExecuteNever as u64, - } - } - if mp.cached { - match config.arch { - Arch::Aarch64 => attrs |= ArmVmAttributes::Cacheable as u64, - Arch::Riscv64 => {} - } - } - - assert!(!mr_pages[mr].is_empty()); - assert!(util::objects_adjacent(&mr_pages[mr])); - - let mut invocation = Invocation::new( - config, - InvocationArgs::CnodeMint { - cnode: system_cnode_cap, - dest_index: cap_slot, - dest_depth: system_cnode_bits, - src_root: root_cnode_cap, - src_obj: mr_pages[mr][0].cap_addr, - src_depth: config.cap_address_bits, - rights, - badge: 0, - }, - ); - invocation.repeat( - mr_pages[mr].len() as u32, - InvocationArgs::CnodeMint { - cnode: 0, - dest_index: 1, - dest_depth: 0, - src_root: 0, - src_obj: 1, - src_depth: 0, - rights: 0, - badge: 0, - }, - ); - system_invocations.push(invocation); - - vm_page_descriptors.push(( - system_cap_address_mask | cap_slot, - vm_idx, - mp.vaddr, - rights, - attrs, - mr_pages[mr].len() as u64, - mr.page_size_bytes(), - )); - - for idx in 0..mr_pages[mr].len() { - cap_address_names.insert( - system_cap_address_mask | (cap_slot + idx as u64), - format!( - "{} (derived)", - cap_address_names - .get(&(mr_pages[mr][0].cap_addr + idx as u64)) - .unwrap() - ), - ); - } - - cap_slot += mr_pages[mr].len() as u64; - } - } - - let mut badged_irq_caps: HashMap<&ProtectionDomain, Vec> = HashMap::new(); - for (notification_obj, pd) in zip(¬ification_objs, &system.protection_domains) { - badged_irq_caps.insert(pd, vec![]); - for sysirq in &pd.irqs { - let badge = 1 << sysirq.id; - let badged_cap_address = system_cap_address_mask | cap_slot; - system_invocations.push(Invocation::new( - config, - InvocationArgs::CnodeMint { - cnode: system_cnode_cap, - dest_index: cap_slot, - dest_depth: system_cnode_bits, - src_root: root_cnode_cap, - src_obj: notification_obj.cap_addr, - src_depth: config.cap_address_bits, - rights: Rights::All as u64, - badge, - }, - )); - let badged_name = format!( - "{} (badge=0x{:x})", - cap_address_names[¬ification_obj.cap_addr], badge - ); - cap_address_names.insert(badged_cap_address, badged_name); - badged_irq_caps - .get_mut(pd) - .unwrap() - .push(badged_cap_address); - cap_slot += 1; - } - } - - // Create a fault endpoint cap for each protection domain. - // For root PDs, this shall be the system fault EP endpoint object. - // For non-root PDs, this shall be the parent endpoint. - let badged_fault_ep = system_cap_address_mask | cap_slot; - for (i, pd) in system.protection_domains.iter().enumerate() { - let is_root = pd.parent.is_none(); - let fault_ep_cap; - let badge: u64; - if is_root { - fault_ep_cap = fault_ep_endpoint_object.cap_addr; - badge = i as u64 + 1; - } else { - assert!(pd.id.is_some()); - assert!(pd.parent.is_some()); - fault_ep_cap = pd_endpoint_objs[pd.parent.unwrap()].unwrap().cap_addr; - badge = FAULT_BADGE | pd.id.unwrap(); - } - - let invocation = Invocation::new( - config, - InvocationArgs::CnodeMint { - cnode: system_cnode_cap, - dest_index: cap_slot, - dest_depth: system_cnode_bits, - src_root: root_cnode_cap, - src_obj: fault_ep_cap, - src_depth: config.cap_address_bits, - rights: Rights::All as u64, - badge, - }, - ); - system_invocations.push(invocation); - cap_slot += 1; - } - - // Create a fault endpoint cap for each virtual machine. - // This will be the endpoint for the parent protection domain of the virtual machine. - for vm in &virtual_machines { - let mut parent_pd = None; - for (pd_idx, pd) in system.protection_domains.iter().enumerate() { - if let Some(virtual_machine) = &pd.virtual_machine { - if virtual_machine == *vm { - parent_pd = Some(pd_idx); - break; - } - } - } - assert!(parent_pd.is_some()); - - let fault_ep_cap = pd_endpoint_objs[parent_pd.unwrap()].unwrap().cap_addr; - - for vcpu in &vm.vcpus { - let badge = FAULT_BADGE | vcpu.id; - - let invocation = Invocation::new( - config, - InvocationArgs::CnodeMint { - cnode: system_cnode_cap, - dest_index: cap_slot, - dest_depth: system_cnode_bits, - src_root: root_cnode_cap, - src_obj: fault_ep_cap, - src_depth: config.cap_address_bits, - rights: Rights::All as u64, - badge, - }, - ); - system_invocations.push(invocation); - cap_slot += 1; - } - } - - let final_cap_slot = cap_slot; - - // Minting in the address space - for (idx, pd) in system.protection_domains.iter().enumerate() { - let obj = if pd.needs_ep(idx, &system.channels) { - pd_endpoint_objs[idx].unwrap() - } else { - ¬ification_objs[idx] - }; - assert!(INPUT_CAP_IDX < PD_CAP_SIZE); - - system_invocations.push(Invocation::new( - config, - InvocationArgs::CnodeMint { - cnode: cnode_objs[idx].cap_addr, - dest_index: INPUT_CAP_IDX, - dest_depth: PD_CAP_BITS, - src_root: root_cnode_cap, - src_obj: obj.cap_addr, - src_depth: config.cap_address_bits, - rights: Rights::All as u64, - badge: 0, - }, - )); - } - - // Mint access to the reply cap - assert!(REPLY_CAP_IDX < PD_CAP_SIZE); - let mut reply_mint_invocation = Invocation::new( - config, - InvocationArgs::CnodeMint { - cnode: cnode_objs[0].cap_addr, - dest_index: REPLY_CAP_IDX, - dest_depth: PD_CAP_BITS, - src_root: root_cnode_cap, - src_obj: pd_reply_objs[0].cap_addr, - src_depth: config.cap_address_bits, - rights: Rights::All as u64, - badge: 1, - }, - ); - reply_mint_invocation.repeat( - system.protection_domains.len() as u32, - InvocationArgs::CnodeMint { - cnode: 1, - dest_index: 0, - dest_depth: 0, - src_root: 0, - src_obj: 1, - src_depth: 0, - rights: 0, - badge: 0, - }, - ); - system_invocations.push(reply_mint_invocation); - - // Mint access to the VSpace cap - assert!(VSPACE_CAP_IDX < PD_CAP_SIZE); - let num_vspace_mint_invocations = system.protection_domains.len() + virtual_machines.len(); - let mut vspace_mint_invocation = Invocation::new( - config, - InvocationArgs::CnodeMint { - cnode: cnode_objs[0].cap_addr, - dest_index: VSPACE_CAP_IDX, - dest_depth: PD_CAP_BITS, - src_root: root_cnode_cap, - src_obj: vspace_objs[0].cap_addr, - src_depth: config.cap_address_bits, - rights: Rights::All as u64, - badge: 0, - }, - ); - vspace_mint_invocation.repeat( - num_vspace_mint_invocations as u32, - InvocationArgs::CnodeMint { - cnode: 1, - dest_index: 0, - dest_depth: 0, - src_root: 0, - src_obj: 1, - src_depth: 0, - rights: 0, - badge: 0, - }, - ); - system_invocations.push(vspace_mint_invocation); - - // Mint access to interrupt handlers in the PD CSpace - for (pd_idx, pd) in system.protection_domains.iter().enumerate() { - for (sysirq, irq_cap_address) in zip(&pd.irqs, &irq_cap_addresses[pd]) { - let cap_idx = BASE_IRQ_CAP + sysirq.id; - assert!(cap_idx < PD_CAP_SIZE); - system_invocations.push(Invocation::new( - config, - InvocationArgs::CnodeMint { - cnode: cnode_objs[pd_idx].cap_addr, - dest_index: cap_idx, - dest_depth: PD_CAP_BITS, - src_root: root_cnode_cap, - src_obj: *irq_cap_address, - src_depth: config.cap_address_bits, - rights: Rights::All as u64, - badge: 0, - }, - )); - } - } - - // Mint access to the child TCB in the CSpace of root PDs - for (pd_idx, _) in system.protection_domains.iter().enumerate() { - for (maybe_child_idx, maybe_child_pd) in system.protection_domains.iter().enumerate() { - // Before doing anything, check if we are dealing with a child PD - if let Some(parent_idx) = maybe_child_pd.parent { - // We are dealing with a child PD, now check if the index of its parent - // matches this iteration's PD. - if parent_idx == pd_idx { - let cap_idx = BASE_PD_TCB_CAP + maybe_child_pd.id.unwrap(); - assert!(cap_idx < PD_CAP_SIZE); - system_invocations.push(Invocation::new( - config, - InvocationArgs::CnodeMint { - cnode: cnode_objs[pd_idx].cap_addr, - dest_index: cap_idx, - dest_depth: PD_CAP_BITS, - src_root: root_cnode_cap, - src_obj: tcb_objs[maybe_child_idx].cap_addr, - src_depth: config.cap_address_bits, - rights: Rights::All as u64, - badge: 0, - }, - )); - } - } - } - } - - // Mint access to virtual machine TCBs in the CSpace of parent PDs - for (pd_idx, pd) in system.protection_domains.iter().enumerate() { - if let Some(vm) = &pd.virtual_machine { - // This PD that we are dealing with has a virtual machine, now we - // need to find the TCB that corresponds to it. - let vm_idx = virtual_machines.iter().position(|&x| x == vm).unwrap(); - - for (vcpu_idx, vcpu) in vm.vcpus.iter().enumerate() { - let cap_idx = BASE_VM_TCB_CAP + vcpu.id; - assert!(cap_idx < PD_CAP_SIZE); - system_invocations.push(Invocation::new( - config, - InvocationArgs::CnodeMint { - cnode: cnode_objs[pd_idx].cap_addr, - dest_index: cap_idx, - dest_depth: PD_CAP_BITS, - src_root: root_cnode_cap, - src_obj: vcpu_tcb_objs[vm_idx + vcpu_idx].cap_addr, - src_depth: config.cap_address_bits, - rights: Rights::All as u64, - badge: 0, - }, - )); - } - } - } - - // Mint access to virtual machine vCPUs in the CSpace of the parent PDs - for (pd_idx, pd) in system.protection_domains.iter().enumerate() { - if let Some(vm) = &pd.virtual_machine { - // This PD that we are dealing with has a virtual machine, now we - // need to find the vCPU that corresponds to it. - let vm_idx = virtual_machines.iter().position(|&x| x == vm).unwrap(); - - for (vcpu_idx, vcpu) in vm.vcpus.iter().enumerate() { - let cap_idx = BASE_VCPU_CAP + vcpu.id; - assert!(cap_idx < PD_CAP_SIZE); - system_invocations.push(Invocation::new( - config, - InvocationArgs::CnodeMint { - cnode: cnode_objs[pd_idx].cap_addr, - dest_index: cap_idx, - dest_depth: PD_CAP_BITS, - src_root: root_cnode_cap, - src_obj: vcpu_objs[vm_idx + vcpu_idx].cap_addr, - src_depth: config.cap_address_bits, - rights: Rights::All as u64, - badge: 0, - }, - )); - } - } - } - - for cc in &system.channels { - for (send, recv) in [(&cc.end_a, &cc.end_b), (&cc.end_b, &cc.end_a)] { - let send_pd = &system.protection_domains[send.pd]; - let send_cnode_obj = cnode_objs_by_pd[send_pd]; - let recv_notification_obj = ¬ification_objs[recv.pd]; - - if send.notify { - let send_cap_idx = BASE_OUTPUT_NOTIFICATION_CAP + send.id; - assert!(send_cap_idx < PD_CAP_SIZE); - // receiver sees the sender's badge. - let send_badge = 1 << recv.id; - - system_invocations.push(Invocation::new( - config, - InvocationArgs::CnodeMint { - cnode: send_cnode_obj.cap_addr, - dest_index: send_cap_idx, - dest_depth: PD_CAP_BITS, - src_root: root_cnode_cap, - src_obj: recv_notification_obj.cap_addr, - src_depth: config.cap_address_bits, - rights: Rights::All as u64, // FIXME: Check rights - badge: send_badge, - }, - )); - } - - if send.pp { - let send_cap_idx = BASE_OUTPUT_ENDPOINT_CAP + send.id; - assert!(send_cap_idx < PD_CAP_SIZE); - // receiver sees the sender's badge. - let send_badge = PPC_BADGE | recv.id; - - let recv_endpoint_obj = - pd_endpoint_objs[recv.pd].expect("endpoint object to exist"); - - system_invocations.push(Invocation::new( - config, - InvocationArgs::CnodeMint { - cnode: send_cnode_obj.cap_addr, - dest_index: send_cap_idx, - dest_depth: PD_CAP_BITS, - src_root: root_cnode_cap, - src_obj: recv_endpoint_obj.cap_addr, - src_depth: config.cap_address_bits, - rights: Rights::All as u64, // FIXME: Check rights - badge: send_badge, - }, - )); - } - } - } - - // Mint a cap between monitor and passive PDs. - for (pd_idx, pd) in system.protection_domains.iter().enumerate() { - if pd.passive { - let cnode_obj = &cnode_objs[pd_idx]; - system_invocations.push(Invocation::new( - config, - InvocationArgs::CnodeMint { - cnode: cnode_obj.cap_addr, - dest_index: MONITOR_EP_CAP_IDX, - dest_depth: PD_CAP_BITS, - src_root: root_cnode_cap, - src_obj: fault_ep_endpoint_object.cap_addr, - src_depth: config.cap_address_bits, - rights: Rights::All as u64, // FIXME: Check rights - // Badge needs to start at 1 - badge: pd_idx as u64 + 1, - }, - )); - } - } - - for (pd_idx, pd) in system.protection_domains.iter().enumerate() { - if pd.smc { - assert!(config.arm_smc.is_some() && config.arm_smc.unwrap()); - let cnode_obj = &cnode_objs[pd_idx]; - system_invocations.push(Invocation::new( - config, - InvocationArgs::CnodeMint { - cnode: cnode_obj.cap_addr, - dest_index: SMC_CAP_IDX, - dest_depth: PD_CAP_BITS, - src_root: root_cnode_cap, - src_obj: SMC_CAP_ADDRESS, - src_depth: config.cap_address_bits, - rights: Rights::All as u64, // FIXME: Check rights - badge: 0, - }, - )); - } - } - - // All minting is complete at this point - - // Associate badges - // FIXME: This could use repeat - for pd in &system.protection_domains { - for (irq_cap_address, badged_notification_cap_address) in - zip(&irq_cap_addresses[pd], &badged_irq_caps[pd]) - { - system_invocations.push(Invocation::new( - config, - InvocationArgs::IrqHandlerSetNotification { - irq_handler: *irq_cap_address, - notification: *badged_notification_cap_address, - }, - )); - } - } - - // Initialise the VSpaces -- assign them all the the initial asid pool. - let pd_vspace_invocations = [ - (all_pd_uds, pd_ud_objs), - (all_pd_ds, pd_d_objs), - (all_pd_pts, pd_pt_objs), - ]; - for (descriptors, objects) in pd_vspace_invocations { - for ((pd_idx, vaddr), obj) in zip(descriptors, objects) { - system_invocations.push(Invocation::new( - config, - InvocationArgs::PageTableMap { - page_table: obj.cap_addr, - vspace: pd_vspace_objs[pd_idx].cap_addr, - vaddr, - attr: default_vm_attr(config), - }, - )); - } - } - - if !config.hypervisor { - assert!(all_vm_uds.is_empty() && vm_ud_objs.is_empty()); - assert!(all_vm_ds.is_empty() && vm_d_objs.is_empty()); - assert!(all_vm_pts.is_empty() && vm_pt_objs.is_empty()); - } - - let vm_vspace_invocations = [ - (all_vm_uds, vm_ud_objs), - (all_vm_ds, vm_d_objs), - (all_vm_pts, vm_pt_objs), - ]; - for (descriptors, objects) in vm_vspace_invocations { - for ((vm_idx, vaddr), obj) in zip(descriptors, objects) { - system_invocations.push(Invocation::new( - config, - InvocationArgs::PageTableMap { - page_table: obj.cap_addr, - vspace: vm_vspace_objs[vm_idx].cap_addr, - vaddr, - attr: default_vm_attr(config), - }, - )); - } - } - - // Now map all the pages - for (page_cap_address, pd_idx, vaddr, rights, attr, count, vaddr_incr) in pd_page_descriptors { - let mut invocation = Invocation::new( - config, - InvocationArgs::PageMap { - page: page_cap_address, - vspace: pd_vspace_objs[pd_idx].cap_addr, - vaddr, - rights, - attr, - }, - ); - invocation.repeat( - count as u32, - InvocationArgs::PageMap { - page: 1, - vspace: 0, - vaddr: vaddr_incr, - rights: 0, - attr: 0, - }, - ); - system_invocations.push(invocation); - } - for (page_cap_address, vm_idx, vaddr, rights, attr, count, vaddr_incr) in vm_page_descriptors { - let mut invocation = Invocation::new( - config, - InvocationArgs::PageMap { - page: page_cap_address, - vspace: vm_vspace_objs[vm_idx].cap_addr, - vaddr, - rights, - attr, - }, - ); - invocation.repeat( - count as u32, - InvocationArgs::PageMap { - page: 1, - vspace: 0, - vaddr: vaddr_incr, - rights: 0, - attr: 0, - }, - ); - system_invocations.push(invocation); - } - - // And, finally, map all the IPC buffers - let ipc_buffer_attr = match config.arch { - Arch::Aarch64 => ArmVmAttributes::default() | ArmVmAttributes::ExecuteNever as u64, - Arch::Riscv64 => RiscvVmAttributes::default() | RiscvVmAttributes::ExecuteNever as u64, - }; - for pd_idx in 0..system.protection_domains.len() { - let (vaddr, _) = pd_elf_files[pd_idx] - .find_symbol(SYMBOL_IPC_BUFFER) - .unwrap_or_else(|_| panic!("Could not find {SYMBOL_IPC_BUFFER}")); - system_invocations.push(Invocation::new( - config, - InvocationArgs::PageMap { - page: ipc_buffer_objs[pd_idx].cap_addr, - vspace: pd_vspace_objs[pd_idx].cap_addr, - vaddr, - rights: Rights::Read as u64 | Rights::Write as u64, - attr: ipc_buffer_attr, - }, - )); - } - - // Initialise the TCBs - - // Set the scheduling parameters - for (pd_idx, pd) in system.protection_domains.iter().enumerate() { - system_invocations.push(Invocation::new( - config, - InvocationArgs::SchedControlConfigureFlags { - sched_control: kernel_boot_info.sched_control_cap, - sched_context: pd_sched_context_objs[pd_idx].cap_addr, - budget: pd.budget, - period: pd.period, - extra_refills: 0, - badge: 0x100 + pd_idx as u64, - flags: 0, - }, - )); - } - for (vm_idx, vm) in virtual_machines.iter().enumerate() { - for vcpu_idx in 0..vm.vcpus.len() { - let idx = vm_idx + vcpu_idx; - system_invocations.push(Invocation::new( - config, - InvocationArgs::SchedControlConfigureFlags { - sched_control: kernel_boot_info.sched_control_cap, - sched_context: vm_sched_context_objs[idx].cap_addr, - budget: vm.budget, - period: vm.period, - extra_refills: 0, - badge: 0x100 + idx as u64, - flags: 0, - }, - )); - } - } - - for (pd_idx, pd) in system.protection_domains.iter().enumerate() { - system_invocations.push(Invocation::new( - config, - InvocationArgs::TcbSetSchedParams { - tcb: pd_tcb_objs[pd_idx].cap_addr, - authority: INIT_TCB_CAP_ADDRESS, - mcp: pd.priority as u64, - priority: pd.priority as u64, - sched_context: pd_sched_context_objs[pd_idx].cap_addr, - // This gets over-written by the call to TCB_SetSpace - fault_ep: fault_ep_endpoint_object.cap_addr, - }, - )); - } - for (vm_idx, vm) in virtual_machines.iter().enumerate() { - for vcpu_idx in 0..vm.vcpus.len() { - system_invocations.push(Invocation::new( - config, - InvocationArgs::TcbSetSchedParams { - tcb: vcpu_tcb_objs[vm_idx + vcpu_idx].cap_addr, - authority: INIT_TCB_CAP_ADDRESS, - mcp: vm.priority as u64, - priority: vm.priority as u64, - sched_context: vm_sched_context_objs[vm_idx + vcpu_idx].cap_addr, - // This gets over-written by the call to TCB_SetSpace - fault_ep: fault_ep_endpoint_object.cap_addr, - }, - )); - } - } - - // In the benchmark configuration, we allow PDs to access their own TCB. - // This is necessary for accessing kernel's benchmark API. - if config.benchmark { - let mut tcb_cap_copy_invocation = Invocation::new( - config, - InvocationArgs::CnodeCopy { - cnode: cnode_objs[0].cap_addr, - dest_index: TCB_CAP_IDX, - dest_depth: PD_CAP_BITS, - src_root: root_cnode_cap, - src_obj: pd_tcb_objs[0].cap_addr, - src_depth: config.cap_address_bits, - rights: Rights::All as u64, - }, - ); - tcb_cap_copy_invocation.repeat( - system.protection_domains.len() as u32, - InvocationArgs::CnodeCopy { - cnode: 1, - dest_index: 0, - dest_depth: 0, - src_root: 0, - src_obj: 1, - src_depth: 0, - rights: 0, - }, - ); - system_invocations.push(tcb_cap_copy_invocation); - } - - // Set VSpace and CSpace - let mut pd_set_space_invocation = Invocation::new( - config, - InvocationArgs::TcbSetSpace { - tcb: tcb_objs[0].cap_addr, - fault_ep: badged_fault_ep, - cspace_root: cnode_objs[0].cap_addr, - cspace_root_data: config.cap_address_bits - PD_CAP_BITS, - vspace_root: vspace_objs[0].cap_addr, - vspace_root_data: 0, - }, - ); - pd_set_space_invocation.repeat( - system.protection_domains.len() as u32, - InvocationArgs::TcbSetSpace { - tcb: 1, - fault_ep: 1, - cspace_root: 1, - cspace_root_data: 0, - vspace_root: 1, - vspace_root_data: 0, - }, - ); - system_invocations.push(pd_set_space_invocation); - - for (vm_idx, vm) in virtual_machines.iter().enumerate() { - let fault_ep_offset = system.protection_domains.len() + vm_idx; - let mut vcpu_set_space_invocation = Invocation::new( - config, - InvocationArgs::TcbSetSpace { - tcb: vcpu_tcb_objs[vm_idx].cap_addr, - fault_ep: badged_fault_ep + fault_ep_offset as u64, - cspace_root: vm_cnode_objs[vm_idx].cap_addr, - cspace_root_data: config.cap_address_bits - PD_CAP_BITS, - vspace_root: vm_vspace_objs[vm_idx].cap_addr, - vspace_root_data: 0, - }, - ); - vcpu_set_space_invocation.repeat( - vm.vcpus.len() as u32, - InvocationArgs::TcbSetSpace { - tcb: 1, - fault_ep: 1, - cspace_root: 0, - cspace_root_data: 0, - vspace_root: 0, - vspace_root_data: 0, - }, - ); - system_invocations.push(vcpu_set_space_invocation); - } - - // Set IPC buffer - for pd_idx in 0..system.protection_domains.len() { - let (ipc_buffer_vaddr, _) = pd_elf_files[pd_idx] - .find_symbol(SYMBOL_IPC_BUFFER) - .unwrap_or_else(|_| panic!("Could not find {SYMBOL_IPC_BUFFER}")); - system_invocations.push(Invocation::new( - config, - InvocationArgs::TcbSetIpcBuffer { - tcb: tcb_objs[pd_idx].cap_addr, - buffer: ipc_buffer_vaddr, - buffer_frame: ipc_buffer_objs[pd_idx].cap_addr, - }, - )); - } - - // Set TCB registers (we only set the entry point) - for pd_idx in 0..system.protection_domains.len() { - let regs = match config.arch { - Arch::Aarch64 => Aarch64Regs { - pc: pd_elf_files[pd_idx].entry, - sp: config.pd_stack_top(), - ..Default::default() - } - .field_names(), - Arch::Riscv64 => Riscv64Regs { - pc: pd_elf_files[pd_idx].entry, - sp: config.pd_stack_top(), - ..Default::default() - } - .field_names(), - }; - - system_invocations.push(Invocation::new( - config, - InvocationArgs::TcbWriteRegisters { - tcb: tcb_objs[pd_idx].cap_addr, - resume: false, - // There are no arch-dependent flags to set - arch_flags: 0, - // FIXME: we could optimise this since we are only setting the program counter - count: regs.len() as u64, - regs, - }, - )); - } - // AArch64 and RISC-V expect the stack pointer to be 16-byte aligned - assert!(config.pd_stack_top() % 16 == 0); - - // Bind the notification object - let mut bind_ntfn_invocation = Invocation::new( - config, - InvocationArgs::TcbBindNotification { - tcb: tcb_objs[0].cap_addr, - notification: notification_objs[0].cap_addr, - }, - ); - bind_ntfn_invocation.repeat( - system.protection_domains.len() as u32, - InvocationArgs::TcbBindNotification { - tcb: 1, - notification: 1, - }, - ); - system_invocations.push(bind_ntfn_invocation); - - // Bind virtual machine TCBs to vCPUs - if !virtual_machines.is_empty() { - match config.arch { - Arch::Aarch64 => {} - _ => panic!("Support for virtual machines is only for AArch64"), - } - let mut vcpu_bind_invocation = Invocation::new( - config, - InvocationArgs::ArmVcpuSetTcb { - vcpu: vcpu_objs[0].cap_addr, - tcb: vcpu_tcb_objs[0].cap_addr, - }, - ); - let num_vcpus = virtual_machines - .iter() - .fold(0, |acc, vm| acc + vm.vcpus.len()); - vcpu_bind_invocation.repeat( - num_vcpus as u32, - InvocationArgs::ArmVcpuSetTcb { vcpu: 1, tcb: 1 }, - ); - system_invocations.push(vcpu_bind_invocation); - } - - // Resume (start) all the threads that belong to PDs (VMs are not started upon system init) - let mut resume_invocation = Invocation::new( - config, - InvocationArgs::TcbResume { - tcb: tcb_objs[0].cap_addr, - }, - ); - resume_invocation.repeat( - system.protection_domains.len() as u32, - InvocationArgs::TcbResume { tcb: 1 }, - ); - system_invocations.push(resume_invocation); - - // All of the objects are created at this point; we don't need both - // the allocators from here. - - // And now we are finally done. We have all the invocations - - let mut system_invocation_data: Vec = Vec::new(); - for system_invocation in &system_invocations { - system_invocation.add_raw_invocation(config, &mut system_invocation_data); - } - - let pd_setvar_values: Vec> = system - .protection_domains - .iter() - .map(|pd| { - pd.setvars - .iter() - .map(|setvar| match &setvar.kind { - sdf::SysSetVarKind::Size { mr } => { - system - .memory_regions - .iter() - .find(|m| m.name == *mr) - .unwrap() - .size - } - sdf::SysSetVarKind::Vaddr { address } => *address, - sdf::SysSetVarKind::Paddr { region } => { - let mr = system - .memory_regions - .iter() - .find(|mr| mr.name == *region) - .unwrap_or_else(|| panic!("Cannot find region: {region}")); - - mr_pages[mr][0].phys_addr - } - }) - .collect() - }) - .collect(); - - Ok(BuiltSystem { - number_of_system_caps: final_cap_slot, - invocation_data_size: system_invocation_data.len() as u64, - invocation_data: system_invocation_data, - bootstrap_invocations, - system_invocations, - kernel_boot_info, - reserved_region, - fault_ep_cap_address: fault_ep_endpoint_object.cap_addr, - reply_cap_address: reply_obj.cap_addr, - cap_lookup: cap_address_names, - pd_tcb_caps: tcb_caps[..system.protection_domains.len()].to_vec(), - vm_tcb_caps: tcb_caps[system.protection_domains.len()..].to_vec(), - sched_caps: sched_context_caps, - ntfn_caps: notification_caps, - pd_elf_regions, - pd_setvar_values, - pd_stack_addrs, - kernel_objects, - initial_task_phys_region, - initial_task_virt_region, - }) -} +use microkit_tool::symbols::patch_symbols; +use microkit_tool::util::{human_size_strict, json_str, json_str_as_bool, json_str_as_u64}; +use microkit_tool::MemoryRegion; +use sel4_capdl_initializer_types::{ObjectNamesLevel, Spec}; +use std::fs::{self, metadata}; +use std::path::{Path, PathBuf}; -fn write_report( - buf: &mut BufWriter, - config: &Config, - built_system: &BuiltSystem, - bootstrap_invocation_data: &[u8], -) -> std::io::Result<()> { - writeln!(buf, "# Kernel Boot Info\n")?; - - writeln!( - buf, - " # of fixed caps : {:>8}", - comma_sep_u64(built_system.kernel_boot_info.fixed_cap_count) - )?; - writeln!( - buf, - " # of page table caps: {:>8}", - comma_sep_u64(built_system.kernel_boot_info.paging_cap_count) - )?; - writeln!( - buf, - " # of page caps : {:>8}", - comma_sep_u64(built_system.kernel_boot_info.page_cap_count) - )?; - writeln!( - buf, - " # of untyped objects: {:>8}", - comma_sep_usize(built_system.kernel_boot_info.untyped_objects.len()) - )?; - writeln!(buf, "\n# Loader Regions\n")?; - for regions in &built_system.pd_elf_regions { - for region in regions { - writeln!(buf, " {region}")?; +fn get_full_path(path: &Path, search_paths: &Vec) -> Option { + for search_path in search_paths { + let full_path = search_path.join(path); + if full_path.exists() { + return Some(full_path.to_path_buf()); } } - writeln!(buf, "\n# Monitor (Initial Task) Info\n")?; - writeln!( - buf, - " virtual memory : {}", - built_system.initial_task_virt_region - )?; - writeln!( - buf, - " physical memory: {}", - built_system.initial_task_phys_region - )?; - writeln!(buf, "\n# Allocated Kernel Objects Summary\n")?; - writeln!( - buf, - " # of allocated objects: {}", - comma_sep_usize(built_system.kernel_objects.len()) - )?; - writeln!(buf, "\n# Bootstrap Kernel Invocations Summary\n")?; - writeln!( - buf, - " # of invocations : {:>10}", - comma_sep_usize(built_system.bootstrap_invocations.len()) - )?; - writeln!( - buf, - " size of invocations: {:>10}", - comma_sep_usize(bootstrap_invocation_data.len()) - )?; - writeln!(buf, "\n# System Kernel Invocations Summary\n")?; - writeln!( - buf, - " # of invocations : {:>10}", - comma_sep_usize(built_system.system_invocations.len()) - )?; - writeln!( - buf, - " size of invocations: {:>10}", - comma_sep_usize(built_system.invocation_data.len()) - )?; - writeln!(buf, "\n# Allocated Kernel Objects Detail\n")?; - for ko in &built_system.kernel_objects { - // FIXME: would be good to print both the number for the object type and the string - let name = &built_system.cap_lookup[&ko.cap_addr]; - writeln!( - buf, - " {:<50} {} cap_addr={:x} phys_addr={:x}", - name, - ko.object_type.value(config), - ko.cap_addr, - ko.phys_addr - )?; - } - writeln!(buf, "\n# Bootstrap Kernel Invocations Detail\n")?; - for (i, invocation) in built_system.bootstrap_invocations.iter().enumerate() { - write!(buf, " 0x{i:04x} ")?; - invocation.report_fmt(buf, config, &built_system.cap_lookup); - } - writeln!(buf, "\n# System Kernel Invocations Detail\n")?; - for (i, invocation) in built_system.system_invocations.iter().enumerate() { - write!(buf, " 0x{i:04x} ")?; - invocation.report_fmt(buf, config, &built_system.cap_lookup); - } - Ok(()) + None } fn print_usage() { - println!("usage: microkit [-h] [-o OUTPUT] [-r REPORT] --board BOARD --config CONFIG [--search-path [SEARCH_PATH ...]] system") + println!("usage: microkit [-h] [-o OUTPUT] [-r REPORT] --board BOARD --config CONFIG [--capdl-spec CAPDL_SPEC --search-path [SEARCH_PATH ...]] system") } fn print_help(available_boards: &[String]) { @@ -2917,6 +51,7 @@ fn print_help(available_boards: &[String]) { println!(" -r, --report REPORT"); println!(" --board {}", available_boards.join("\n ")); println!(" --config CONFIG"); + println!(" --capdl-spec CAPDL_SPEC (outputs as JSON)"); println!(" --search-path [SEARCH_PATH ...]"); } @@ -2925,8 +60,10 @@ struct Args<'a> { board: &'a str, config: &'a str, report: &'a str, + capdl_spec: Option<&'a str>, output: &'a str, search_paths: Vec<&'a String>, + initialiser_heap_size_multiplier: f64, } impl<'a> Args<'a> { @@ -2934,11 +71,13 @@ impl<'a> Args<'a> { // Default arguments let mut output = "loader.img"; let mut report = "report.txt"; + let mut capdl_spec = None; let mut search_paths = Vec::new(); // Arguments expected to be provided by the user let mut system = None; let mut board = None; let mut config = None; + let mut initialiser_heap_size_multiplier = DEFAULT_INITIALISER_HEAP_MULTIPLIER; if args.len() <= 1 { print_usage(); @@ -2994,6 +133,32 @@ impl<'a> Args<'a> { std::process::exit(1); } } + "--capdl-spec" => { + in_search_path = false; + if i < args.len() - 1 { + capdl_spec = Some(args[i + 1].as_str()); + i += 1; + } else { + eprintln!("microkit: error: argument --capdl-spec: expected one argument"); + std::process::exit(1); + } + } + "--initialiser_heap_size_multiplier" => { + in_search_path = false; + if i < args.len() - 1 { + match args[i + 1].parse::() { + Ok(multiplier) => initialiser_heap_size_multiplier = multiplier, + Err(e) => { + eprintln!("microkit: error: argument --initialiser_heap_size_multiplier: failed to parse as float: {e}"); + std::process::exit(1); + } + } + i += 1; + } else { + eprintln!("microkit: error: argument --initialiser_heap_size_multiplier: expected one argument"); + std::process::exit(1); + } + } "--search-path" => { in_search_path = true; } @@ -3047,8 +212,10 @@ impl<'a> Args<'a> { board: board.unwrap(), config: config.unwrap(), report, + capdl_spec, output, search_paths, + initialiser_heap_size_multiplier, } } } @@ -3138,6 +305,7 @@ fn main() -> Result<(), String> { let loader_elf_path = elf_path.join("loader.elf"); let kernel_elf_path = elf_path.join("sel4.elf"); let monitor_elf_path = elf_path.join("monitor.elf"); + let capdl_init_elf_path = elf_path.join("initialiser.elf"); let kernel_config_path = sdk_dir .join("board") @@ -3145,12 +313,6 @@ fn main() -> Result<(), String> { .join(args.config) .join("include/kernel/gen_config.json"); - let kernel_platform_config_path = sdk_dir - .join("board") - .join(args.board) - .join(args.config) - .join("platform_gen.json"); - let invocations_all_path = sdk_dir .join("board") .join(args.board) @@ -3164,13 +326,6 @@ fn main() -> Result<(), String> { ); std::process::exit(1); } - if !loader_elf_path.exists() { - eprintln!( - "Error: loader ELF '{}' does not exist", - loader_elf_path.display() - ); - std::process::exit(1); - } if !kernel_elf_path.exists() { eprintln!( "Error: kernel ELF '{}' does not exist", @@ -3185,17 +340,17 @@ fn main() -> Result<(), String> { ); std::process::exit(1); } - if !kernel_config_path.exists() { + if !capdl_init_elf_path.exists() { eprintln!( - "Error: kernel configuration file '{}' does not exist", - kernel_config_path.display() + "Error: CapDL initialiser ELF '{}' does not exist", + capdl_init_elf_path.display() ); std::process::exit(1); } - if !kernel_platform_config_path.exists() { + if !kernel_config_path.exists() { eprintln!( - "Error: kernel platform configuration file '{}' does not exist", - kernel_platform_config_path.display() + "Error: kernel configuration file '{}' does not exist", + kernel_config_path.display() ); std::process::exit(1); } @@ -3221,22 +376,49 @@ fn main() -> Result<(), String> { let kernel_config_json: serde_json::Value = serde_json::from_str(&fs::read_to_string(kernel_config_path).unwrap()).unwrap(); - let kernel_platform_config: PlatformConfig = - serde_json::from_str(&fs::read_to_string(kernel_platform_config_path).unwrap()).unwrap(); - let invocations_labels: serde_json::Value = serde_json::from_str(&fs::read_to_string(invocations_all_path).unwrap()).unwrap(); let arch = match json_str(&kernel_config_json, "SEL4_ARCH")? { "aarch64" => Arch::Aarch64, "riscv64" => Arch::Riscv64, + "x86_64" => Arch::X86_64, _ => panic!("Unsupported kernel config architecture"), }; + let (device_regions, normal_regions) = match arch { + Arch::X86_64 => (None, None), + _ => { + let kernel_platform_config_path = sdk_dir + .join("board") + .join(args.board) + .join(args.config) + .join("platform_gen.json"); + + if !kernel_platform_config_path.exists() { + eprintln!( + "Error: kernel platform configuration file '{}' does not exist", + kernel_platform_config_path.display() + ); + std::process::exit(1); + } + + let kernel_platform_config: PlatformConfig = + serde_json::from_str(&fs::read_to_string(kernel_platform_config_path).unwrap()) + .unwrap(); + + ( + Some(kernel_platform_config.devices), + Some(kernel_platform_config.memory), + ) + } + }; + let hypervisor = match arch { Arch::Aarch64 => json_str_as_bool(&kernel_config_json, "ARM_HYPERVISOR_SUPPORT")?, + Arch::X86_64 => json_str_as_bool(&kernel_config_json, "VTX")?, // Hypervisor mode is not available on RISC-V - Arch::Riscv64 => false, + _ => false, }; let arm_pa_size_bits = match arch { @@ -3249,7 +431,7 @@ fn main() -> Result<(), String> { panic!("Expected ARM platform to have 40 or 44 physical address bits") } } - Arch::Riscv64 => None, + Arch::X86_64 | Arch::Riscv64 => None, }; let arm_smc = match arch { @@ -3257,9 +439,15 @@ fn main() -> Result<(), String> { _ => None, }; + let x86_xsave_size = match arch { + Arch::X86_64 => Some(json_str_as_u64(&kernel_config_json, "XSAVE_SIZE")? as usize), + _ => None, + }; + let kernel_frame_size = match arch { Arch::Aarch64 => 1 << 12, Arch::Riscv64 => 1 << 21, + Arch::X86_64 => 1 << 12, }; let kernel_config = Config { @@ -3271,17 +459,30 @@ fn main() -> Result<(), String> { init_cnode_bits: json_str_as_u64(&kernel_config_json, "ROOT_CNODE_SIZE_BITS")?, cap_address_bits: 64, fan_out_limit: json_str_as_u64(&kernel_config_json, "RETYPE_FAN_OUT_LIMIT")?, + max_num_bootinfo_untypeds: json_str_as_u64( + &kernel_config_json, + "MAX_NUM_BOOTINFO_UNTYPED_CAPS", + )?, hypervisor, benchmark: args.config == "benchmark", fpu: json_str_as_bool(&kernel_config_json, "HAVE_FPU")?, arm_pa_size_bits, arm_smc, riscv_pt_levels: Some(RiscvVirtualMemory::Sv39), + x86_xsave_size, invocations_labels, - device_regions: kernel_platform_config.devices, - normal_regions: kernel_platform_config.memory, + device_regions, + normal_regions, }; + if kernel_config.arch != Arch::X86_64 && !loader_elf_path.exists() { + eprintln!( + "Error: loader ELF '{}' does not exist", + loader_elf_path.display() + ); + std::process::exit(1); + } + if let Arch::Aarch64 = kernel_config.arch { assert!( kernel_config.hypervisor, @@ -3302,37 +503,22 @@ fn main() -> Result<(), String> { } }; - let monitor_config = MonitorConfig { - untyped_info_symbol_name: "untyped_info", - bootstrap_invocation_count_symbol_name: "bootstrap_invocation_count", - bootstrap_invocation_data_symbol_name: "bootstrap_invocation_data", - system_invocation_count_symbol_name: "system_invocation_count", - }; - - let kernel_elf = ElfFile::from_path(&kernel_elf_path)?; let mut monitor_elf = ElfFile::from_path(&monitor_elf_path)?; - if monitor_elf.segments.iter().filter(|s| s.loadable).count() > 1 { - eprintln!( - "Monitor ({}) has {} segments, it must only have one", - monitor_elf_path.display(), - monitor_elf.segments.len() - ); - std::process::exit(1); - } - let mut search_paths = vec![std::env::current_dir().unwrap()]; for path in args.search_paths { search_paths.push(PathBuf::from(path)); } + // This list refers to all PD ELFs as well as the Monitor ELF. + // The monitor is very similar to a PD so it is useful to pass around + // a list like this. + let mut system_elfs = Vec::with_capacity(system.protection_domains.len()); // Get the elf files for each pd: - let mut pd_elf_files = Vec::with_capacity(system.protection_domains.len()); for pd in &system.protection_domains { match get_full_path(&pd.program_image, &search_paths) { Some(path) => { - let elf = ElfFile::from_path(&path).unwrap(); - pd_elf_files.push(elf); + system_elfs.push(ElfFile::from_path(&path)?); } None => { return Err(format!( @@ -3342,229 +528,156 @@ fn main() -> Result<(), String> { } } } + // Patch all the required symbols in the Monitor and PDs according to the Microkit's requirements + patch_symbols(&kernel_config, &mut system_elfs, &mut monitor_elf, &system)?; - let mut invocation_table_size = kernel_config.minimum_page_size; - let mut system_cnode_size = 2; + // The monitor is just a special PD + system_elfs.push(monitor_elf); + // We have parsed the XML and all ELF files, create the CapDL spec of the system described in the XML. + let mut spec = build_capdl_spec(&kernel_config, &mut system_elfs, &system)?; - let mut built_system; - loop { - built_system = build_system( - &kernel_config, - &pd_elf_files, - &kernel_elf, - &monitor_elf, - &system, - invocation_table_size, - system_cnode_size, - )?; - println!("BUILT: system_cnode_size={} built_system.number_of_system_caps={} invocation_table_size={} built_system.invocation_data_size={}", - system_cnode_size, built_system.number_of_system_caps, invocation_table_size, built_system.invocation_data_size); - - if built_system.number_of_system_caps <= system_cnode_size - && built_system.invocation_data_size <= invocation_table_size - { - break; + // Reserialise the spec into a type that can be understood by rust-sel4. + let spec_reserialised = { + // Eagerly write out the spec so we can debug in case something crash later. + let spec_as_json = if args.capdl_spec.is_some() { + let serialised = serde_json::to_string_pretty(&spec).unwrap(); + fs::write(args.capdl_spec.unwrap(), &serialised).unwrap(); + serialised + } else { + serde_json::to_string(&spec).unwrap() + }; + + // The full type definition is `Spec<'a, N, D, M>` where: + // N = object name type + // D = frame fill data type + // M = embedded frame data type + // Only N and D is useful for Microkit. + serde_json::from_str::>(&spec_as_json).unwrap() + }; + + // Now embed the built spec into the CapDL initialiser. + let name_level = match args.config { + "debug" => ObjectNamesLevel::All, + // We don't copy over the object names as there is no printing in these configuration to save memory. + "release" | "benchmark" => ObjectNamesLevel::None, + _ => panic!("unknown configuration {}", args.config), + }; + + let num_objects = spec.objects.len(); + let capdl_spec_as_binary = + reserialise_spec::reserialise_spec(&system_elfs, &spec_reserialised, &name_level); + + // Patch the spec and heap into the ELF image. + let mut capdl_initialiser = CapDLInitialiser::new( + ElfFile::from_path(&capdl_init_elf_path)?, + args.initialiser_heap_size_multiplier, + ); + capdl_initialiser.add_spec(capdl_spec_as_binary); + + println!( + "MICROKIT|CAPDL SPEC: number of root objects = {}, spec footprint = {}, initialiser heap size = {}", + num_objects, + human_size_strict(capdl_initialiser.spec_size.unwrap()), + human_size_strict(capdl_initialiser.heap_size.unwrap()) + ); + let initialiser_vaddr_range = capdl_initialiser.image_bound(); + println!( + "MICROKIT|INITIAL TASK: memory size = {}", + human_size_strict(initialiser_vaddr_range.end - initialiser_vaddr_range.start), + ); + + // For x86 we write out the initialiser ELF as is, but on ARM and RISC-V we build the bootloader image. + if kernel_config.arch == Arch::X86_64 { + match capdl_initialiser.elf.reserialise(Path::new(args.output)) { + Ok(size) => { + println!( + "MICROKIT|BOOT MODULE: image file size = {}", + human_size_strict(size) + ); + } + Err(err) => { + eprintln!("Error: couldn't write the boot module to filesystem: {err}"); + } } + } else { + // Now that we have the entire spec and CapDL initialiser ELF with embedded spec, + // we can determine exactly how much memory will be available statically when the kernel + // drops to userspace on ARM and RISC-V. This allow us to sanity check that: + // 1. There are enough memory to allocate all the objects required in the spec. + // 2. All frames with a physical attached reside in legal memory (device or normal). + // 3. Objects can be allocated from the free untyped list. For example, we detect + // situations where you might have a few frames with size bit 12 to allocate but + // only have untyped with size bit <12 remaining. - // Recalculate the sizes for the next iteration - let new_invocation_table_size = util::round_up( - built_system.invocation_data_size, - kernel_config.minimum_page_size, - ); - let new_system_cnode_size = 2_u64.pow( - built_system - .number_of_system_caps - .next_power_of_two() - .ilog2(), - ); + // We achieve this by emulating the kernel's boot process in the tool: - invocation_table_size = max(invocation_table_size, new_invocation_table_size); - system_cnode_size = max(system_cnode_size, new_system_cnode_size); - } + // Determine how much memory the CapDL initialiser needs. + let initial_task_size = initialiser_vaddr_range.end - initialiser_vaddr_range.start; - // At this point we just need to patch the files (in memory) and write out the final image. - - // A: The monitor - - // A.1: As part of emulated boot we determined exactly how the kernel would - // create untyped objects. Through testing we know that this matches, but - // we could have a bug, or the kernel could change. It that happens we are - // in a bad spot! Things will break. So we write out this information so that - // the monitor can double check this at run time. - let (_, untyped_info_size) = monitor_elf - .find_symbol(monitor_config.untyped_info_symbol_name) - .unwrap_or_else(|_| { - panic!( - "Could not find '{}' symbol", - monitor_config.untyped_info_symbol_name - ) - }); - let max_untyped_objects = monitor_config.max_untyped_objects(untyped_info_size); - if built_system.kernel_boot_info.untyped_objects.len() as u64 > max_untyped_objects { - eprintln!( - "Too many untyped objects: monitor ({}) supports {} regions. System has {} objects.", - monitor_elf_path.display(), - max_untyped_objects, - built_system.kernel_boot_info.untyped_objects.len() - ); - std::process::exit(1); - } + // Parse the kernel's ELF to determine the kernel's window. + let kernel_elf = ElfFile::from_path(&kernel_elf_path).unwrap(); - let untyped_info_header = MonitorUntypedInfoHeader64 { - cap_start: built_system.kernel_boot_info.untyped_objects[0].cap, - cap_end: built_system - .kernel_boot_info - .untyped_objects - .last() - .unwrap() - .cap - + 1, - }; - let untyped_info_object_data: Vec = built_system - .kernel_boot_info - .untyped_objects - .iter() - .map(|ut| MonitorRegion64 { - paddr: ut.base(), - size_bits: ut.size_bits(), - is_device: ut.is_device as u64, - }) - .collect(); - let mut untyped_info_data: Vec = - Vec::from(unsafe { struct_to_bytes(&untyped_info_header) }); - for o in &untyped_info_object_data { - untyped_info_data.extend(unsafe { struct_to_bytes(o) }); - } - monitor_elf.write_symbol(monitor_config.untyped_info_symbol_name, &untyped_info_data)?; + // Now determine how much memory we have after the kernel boots. + let (mut available_memory, kernel_boot_region) = + emulate_kernel_boot_partial(&kernel_config, &kernel_elf); - let mut bootstrap_invocation_data: Vec = Vec::new(); - for invocation in &built_system.bootstrap_invocations { - invocation.add_raw_invocation(&kernel_config, &mut bootstrap_invocation_data); - } + // The kernel relies on the initial task region being allocated above the kernel + // boot/ELF region, so we have the end of the kernel boot region as the lower + // bound for allocating the reserved region. + let initial_task_phys_base = + available_memory.allocate_from(initial_task_size, kernel_boot_region.end); - let (_, bootstrap_invocation_data_size) = - monitor_elf.find_symbol(monitor_config.bootstrap_invocation_data_symbol_name)?; - if bootstrap_invocation_data.len() as u64 > bootstrap_invocation_data_size { - eprintln!("bootstrap invocation array size : {bootstrap_invocation_data_size}"); - eprintln!( - "bootstrap invocation required size: {}", - bootstrap_invocation_data.len() + let initial_task_phys_region = MemoryRegion::new( + initial_task_phys_base, + initial_task_phys_base + initial_task_size, + ); + let initial_task_virt_region = MemoryRegion::new( + capdl_initialiser.elf.lowest_vaddr(), + initialiser_vaddr_range.end, ); - let mut stderr = BufWriter::new(std::io::stderr()); - for bootstrap_invocation in &built_system.bootstrap_invocations { - bootstrap_invocation.report_fmt(&mut stderr, &kernel_config, &built_system.cap_lookup); - } - stderr.flush().unwrap(); - eprintln!("Internal error: bootstrap invocations too large"); - } + // With the initial task region determined the kernel boot can be emulated. This provides + // the boot info information which is needed for the next steps + let kernel_boot_info = emulate_kernel_boot( + &kernel_config, + &kernel_elf, + initial_task_phys_region, + initial_task_virt_region, + ); - monitor_elf.write_symbol( - monitor_config.bootstrap_invocation_count_symbol_name, - &built_system.bootstrap_invocations.len().to_le_bytes(), - )?; - monitor_elf.write_symbol( - monitor_config.system_invocation_count_symbol_name, - &built_system.system_invocations.len().to_le_bytes(), - )?; - monitor_elf.write_symbol( - monitor_config.bootstrap_invocation_data_symbol_name, - &bootstrap_invocation_data, - )?; - - let pd_tcb_cap_bytes = monitor_serialise_u64_vec(&built_system.pd_tcb_caps); - let vm_tcb_cap_bytes = monitor_serialise_u64_vec(&built_system.vm_tcb_caps); - let sched_cap_bytes = monitor_serialise_u64_vec(&built_system.sched_caps); - let ntfn_cap_bytes = monitor_serialise_u64_vec(&built_system.ntfn_caps); - let pd_stack_addrs_bytes = monitor_serialise_u64_vec(&built_system.pd_stack_addrs); - - monitor_elf.write_symbol("fault_ep", &built_system.fault_ep_cap_address.to_le_bytes())?; - monitor_elf.write_symbol("reply", &built_system.reply_cap_address.to_le_bytes())?; - monitor_elf.write_symbol("pd_tcbs", &pd_tcb_cap_bytes)?; - monitor_elf.write_symbol("vm_tcbs", &vm_tcb_cap_bytes)?; - monitor_elf.write_symbol("scheduling_contexts", &sched_cap_bytes)?; - monitor_elf.write_symbol("notification_caps", &ntfn_cap_bytes)?; - monitor_elf.write_symbol("pd_stack_addrs", &pd_stack_addrs_bytes)?; - let pd_names = system - .protection_domains - .iter() - .map(|pd| &pd.name) - .collect(); - monitor_elf.write_symbol( - "pd_names", - &monitor_serialise_names(pd_names, MAX_PDS, PD_MAX_NAME_LENGTH), - )?; - monitor_elf.write_symbol( - "pd_names_len", - &system.protection_domains.len().to_le_bytes(), - )?; - let vm_names: Vec<&String> = system - .protection_domains - .iter() - .filter_map(|pd| pd.virtual_machine.as_ref().map(|vm| &vm.name)) - .collect(); - monitor_elf.write_symbol("vm_names_len", &vm_names.len().to_le_bytes())?; - monitor_elf.write_symbol( - "vm_names", - &monitor_serialise_names(vm_names, MAX_VMS, VM_MAX_NAME_LENGTH), - )?; - - // Write out all the symbols for each PD - pd_write_symbols( - &system.protection_domains, - &system.channels, - &mut pd_elf_files, - &built_system.pd_setvar_values, - )?; - - // Generate the report - let report = match std::fs::File::create(args.report) { - Ok(file) => file, - Err(e) => { - return Err(format!( - "Could not create report file '{}': {}", - args.report, e - )) + let alloc_ok = + simulate_capdl_object_alloc_algorithm(&mut spec, &kernel_boot_info, &kernel_config); + write_report(&spec, &kernel_config, args.report); + if !alloc_ok { + eprintln!("ERROR: could not allocate all required kernel objects. Please see report for more details."); + std::process::exit(1); } - }; - let mut report_buf = BufWriter::new(report); - match write_report( - &mut report_buf, - &kernel_config, - &built_system, - &bootstrap_invocation_data, - ) { - Ok(()) => report_buf.flush().unwrap(), - Err(err) => { - return Err(format!( - "Could not write out report file '{}': {}", - args.report, err - )) - } - } - report_buf.flush().unwrap(); - - let mut loader_regions: Vec<(u64, &[u8])> = vec![( - built_system.reserved_region.base, - &built_system.invocation_data, - )]; - for (i, regions) in built_system.pd_elf_regions.iter().enumerate() { - for r in regions { - loader_regions.push((r.addr, r.data(&pd_elf_files[i]))); - } + // Everything checks out, patch the list of untypeds we used to simulate object allocation into the initialiser. + // At runtime the intialiser will validate what we simulated against what the kernel gives it. If they deviate + // we will have problems! For example, if we simulated with more memory than what's actually available, the initialiser + // can crash. + capdl_initialiser.add_expected_untypeds(&kernel_boot_info.untyped_objects); + + // Everything checks out, now build the bootloader! + let loader = Loader::new( + &kernel_config, + Path::new(&loader_elf_path), + &kernel_elf, + &capdl_initialiser.elf, + initial_task_phys_base, + initialiser_vaddr_range, + ); + + loader.write_image(Path::new(args.output)); + + println!( + "MICROKIT|LOADER: image file size = {}", + human_size_strict(metadata(args.output).unwrap().len()) + ); } - let loader = Loader::new( - &kernel_config, - Path::new(&loader_elf_path), - &kernel_elf, - &monitor_elf, - Some(built_system.initial_task_phys_region.base), - built_system.reserved_region, - loader_regions, - ); - loader.write_image(Path::new(args.output)); + write_report(&spec, &kernel_config, args.report); Ok(()) } diff --git a/tool/microkit/src/report.rs b/tool/microkit/src/report.rs new file mode 100644 index 000000000..b319cc0b8 --- /dev/null +++ b/tool/microkit/src/report.rs @@ -0,0 +1,313 @@ +// +// Copyright 2025, UNSW +// +// SPDX-License-Identifier: BSD-2-Clause +// + +use std::{fs::File, io::Write}; + +use crate::{ + capdl::{spec::CapDLObject, CapDLSpec, TcbBoundSlot}, + sel4::{Arch, ArmRiscvIrqTrigger, Config, X86IoapicIrqPolarity, X86IoapicIrqTrigger}, +}; + +pub fn write_report(spec: &CapDLSpec, kernel_config: &Config, output_path: &str) { + let mut report_file = File::create(output_path).expect("Cannot create report file"); + + report_file + .write_all(b"# Initial Task (CapDL Initialiser) Details\n") + .unwrap(); + + report_file.write_all(b"\n# IRQ Details\n").unwrap(); + for irq in spec.irqs.iter() { + let irq_num = irq.irq; + let handler = spec.get_root_object(irq.handler).unwrap(); + + report_file + .write_all(format!("\t- IRQ: '{}'\n", handler.name).as_bytes()) + .unwrap(); + + match &handler.object { + CapDLObject::ArmIrq(arm_irq) => { + report_file + .write_all(format!("\t\t* Number: {irq_num}\n").as_bytes()) + .unwrap(); + report_file + .write_all( + format!( + "\t\t* Trigger: {}\n", + ArmRiscvIrqTrigger::from(arm_irq.extra.trigger).human_name() + ) + .as_bytes(), + ) + .unwrap(); + report_file + .write_all(format!("\t\t* CPU: {}\n", arm_irq.extra.target).as_bytes()) + .unwrap(); + } + CapDLObject::RiscvIrq(riscv_irq) => { + report_file + .write_all(format!("\t\t* Number: {irq_num}\n").as_bytes()) + .unwrap(); + report_file + .write_all( + format!( + "\t\t* Trigger: {}\n", + ArmRiscvIrqTrigger::from(riscv_irq.extra.trigger).human_name() + ) + .as_bytes(), + ) + .unwrap(); + } + CapDLObject::IrqMsi(irq_msi) => { + report_file + .write_all(format!("\t\t* Vector: {irq_num}\n").as_bytes()) + .unwrap(); + report_file + .write_all(format!("\t\t* PCI Bus: {}\n", irq_msi.extra.pci_bus).as_bytes()) + .unwrap(); + report_file + .write_all(format!("\t\t* PCI Device: {}\n", irq_msi.extra.pci_dev).as_bytes()) + .unwrap(); + report_file + .write_all( + format!("\t\t* PCI Function: {}\n", irq_msi.extra.pci_func).as_bytes(), + ) + .unwrap(); + report_file + .write_all(format!("\t\t* Handle: {}\n", irq_msi.extra.handle).as_bytes()) + .unwrap(); + } + CapDLObject::IrqIOApic(irq_ioapic) => { + report_file + .write_all(format!("\t\t* Vector: {irq_num}\n").as_bytes()) + .unwrap(); + report_file + .write_all(format!("\t\t* IOAPIC: {}\n", irq_ioapic.extra.ioapic).as_bytes()) + .unwrap(); + report_file + .write_all(format!("\t\t* Pin: {}\n", irq_ioapic.extra.pin).as_bytes()) + .unwrap(); + report_file + .write_all( + format!( + "\t\t* Trigger: {}\n", + X86IoapicIrqTrigger::from(irq_ioapic.extra.level).human_name() + ) + .as_bytes(), + ) + .unwrap(); + report_file + .write_all( + format!( + "\t\t* Polarity: {}\n", + X86IoapicIrqPolarity::from(irq_ioapic.extra.polarity).human_name() + ) + .as_bytes(), + ) + .unwrap(); + } + _ => unreachable!("internal bug: object is not IRQ!"), + }; + report_file.write_all(b"\n").unwrap(); + } + + report_file.write_all(b"\n# TCB Details\n").unwrap(); + let tcb_objects = spec + .objects + .iter() + .filter(|named_object| matches!(named_object.object, CapDLObject::Tcb(_))); + for named_tcb_objects in tcb_objects { + report_file + .write_all(format!("\t- TCB: '{}'\n", named_tcb_objects.name).as_bytes()) + .unwrap(); + match &named_tcb_objects.object { + CapDLObject::Tcb(tcb) => { + report_file + .write_all(format!("\t\t* IP: 0x{:x}\n", tcb.extra.ip).as_bytes()) + .unwrap(); + report_file + .write_all(format!("\t\t* SP: 0x{:x}\n", tcb.extra.sp).as_bytes()) + .unwrap(); + report_file + .write_all( + format!("\t\t* IPC Buffer: 0x{:x}\n", tcb.extra.ipc_buffer_addr).as_bytes(), + ) + .unwrap(); + report_file + .write_all(format!("\t\t* Priority: {}\n", tcb.extra.prio).as_bytes()) + .unwrap(); + report_file + .write_all(format!("\t\t* CPU Affinity: {}\n", tcb.extra.affinity).as_bytes()) + .unwrap(); + + report_file.write_all(b"\t\t* Bound Objects:\n").unwrap(); + for (bound_slot, cap) in tcb.slots.iter() { + let slot_enum = TcbBoundSlot::from(*bound_slot); + let object_name = &spec.get_root_object(cap.obj()).unwrap().name; + let prefix = match slot_enum { + TcbBoundSlot::CSpace => "CSpace", + TcbBoundSlot::VSpace => "VSpace", + TcbBoundSlot::IpcBuffer => "IPC Buffer", + TcbBoundSlot::FaultEp => "Fault Endpoint", + TcbBoundSlot::SchedContext => "Scheduling Context", + TcbBoundSlot::BoundNotification => "Notification", + TcbBoundSlot::VCpu => "VCpu", + TcbBoundSlot::X86Eptpml4 => "x86 EPT PML4", + }; + + report_file + .write_all(format!("\t\t\t-> {prefix}: '{object_name}'\n").as_bytes()) + .unwrap(); + } + } + _ => unreachable!("internal bug: object is not TCB!"), + } + report_file.write_all(b"\n").unwrap(); + } + + report_file.write_all(b"\n# CNode Details\n").unwrap(); + let cnode_objects = spec + .objects + .iter() + .filter(|named_object| matches!(named_object.object, CapDLObject::CNode(_))); + for named_cnode_object in cnode_objects { + report_file + .write_all(format!("\t- CNode: '{}'\n", named_cnode_object.name).as_bytes()) + .unwrap(); + for (cap_addr, cap) in named_cnode_object.object.get_cap_entries().unwrap() { + let to_object = spec.get_root_object(cap.obj()).unwrap(); + report_file + .write_all(format!("\t\t* Slot: {cap_addr}\n").as_bytes()) + .unwrap(); + + report_file + .write_all(format!("\t\t\t-> Object: '{}'\n", to_object.name).as_bytes()) + .unwrap(); + // @billn revisit + // let rights_maybe = cap.rights(); + // if rights_maybe.is_some() { + // report_file + // .write_all( + // format!("\t\t\t-> Rights: {}\n", "")// @billn todo rights_maybe.unwrap().human_repr()) + // .as_bytes(), + // ) + // .unwrap(); + // } + // let badge_maybe = cap.badge(); + // if badge_maybe.is_some() { + // report_file + // .write_all(format!("\t\t\t-> Badge: 0x{:x}\n", badge_maybe.unwrap()).as_bytes()) + // .unwrap(); + // } + } + report_file.write_all(b"\n").unwrap(); + } + + report_file + .write_all(b"\n# Architecture Specific Details\n") + .unwrap(); + match kernel_config.arch { + Arch::Aarch64 => { + let is_smc = spec + .objects + .iter() + .filter(|named_object| matches!(named_object.object, CapDLObject::ArmSmc)) + .count() + > 0; + if is_smc { + report_file + .write_all(b"ARM SMC access is granted to userspace.\n") + .unwrap(); + } + } + Arch::X86_64 => { + let ioports = spec + .objects + .iter() + .filter(|named_object| matches!(named_object.object, CapDLObject::IOPorts(_))); + + for named_ioport_object in ioports { + report_file + .write_all( + format!( + "\t- {}: '{}'\n", + named_ioport_object.object.human_name(kernel_config), + named_ioport_object.name + ) + .as_bytes(), + ) + .unwrap(); + + match &named_ioport_object.object { + CapDLObject::IOPorts(ioports) => { + report_file + .write_all( + format!("\t\t* Start Port: 0x{:x}\n", ioports.start_port) + .as_bytes(), + ) + .unwrap(); + report_file + .write_all( + format!("\t\t* End Port: 0x{:x}\n", ioports.end_port).as_bytes(), + ) + .unwrap(); + } + _ => unreachable!("internal bug: object is not x86 I/O ports!"), + } + } + } + Arch::Riscv64 => {} + } + + report_file + .write_all(b"\n# Kernel Objects Details: ID, Type, Name, Physical Address (on ARM and RISC-V only)\n") + .unwrap(); + for (id, named_object) in spec.objects.iter().enumerate() { + if named_object.object.physical_size_bits(kernel_config) > 0 { + if kernel_config.arch == Arch::X86_64 { + report_file + .write_all( + format!( + "\t{} - {}: '{}'\n", + id, + named_object.object.human_name(kernel_config), + named_object.name, + ) + .as_bytes(), + ) + .unwrap(); + } else { + match &named_object.expected_alloc { + Some(allocation_details) => { + report_file + .write_all( + format!( + "\t{} - {}: '{}' @ 0x{:0>12x}\n", + id, + named_object.object.human_name(kernel_config), + named_object.name, + allocation_details.paddr + ) + .as_bytes(), + ) + .unwrap(); + } + None => { + report_file + .write_all( + format!( + "\t{} - {}: '{}' @ \n", + id, + named_object.object.human_name(kernel_config), + named_object.name + ) + .as_bytes(), + ) + .unwrap(); + } + } + } + } + } +} diff --git a/tool/microkit/src/sdf.rs b/tool/microkit/src/sdf.rs index aba9e7524..2a29f3d94 100644 --- a/tool/microkit/src/sdf.rs +++ b/tool/microkit/src/sdf.rs @@ -1,5 +1,5 @@ // -// Copyright 2024, UNSW +// Copyright 2025, UNSW // // SPDX-License-Identifier: BSD-2-Clause // @@ -16,8 +16,10 @@ /// but few seem to be concerned with giving any introspection regarding the parsed /// XML. The roxmltree project allows us to work on a lower-level than something based /// on serde and so we can report proper user errors. -use crate::sel4::{Config, IrqTrigger, PageSize}; -use crate::util::str_to_bool; +use crate::sel4::{ + Arch, ArmRiscvIrqTrigger, Config, PageSize, X86IoapicIrqPolarity, X86IoapicIrqTrigger, +}; +use crate::util::{ranges_overlap, str_to_bool}; use crate::MAX_PDS; use std::path::{Path, PathBuf}; @@ -33,12 +35,15 @@ use std::path::{Path, PathBuf}; const PD_MAX_ID: u64 = 61; const VCPU_MAX_ID: u64 = PD_MAX_ID; -const PD_MAX_PRIORITY: u8 = 254; +pub const MONITOR_PRIORITY: u8 = 254; +const PD_MAX_PRIORITY: u8 = 253; /// In microseconds -const BUDGET_DEFAULT: u64 = 1000; +pub const BUDGET_DEFAULT: u64 = 1000; + +pub const MONITOR_PD_NAME: &str = "monitor"; /// Default to a stack size of 8KiB -const PD_DEFAULT_STACK_SIZE: u64 = 0x2000; +pub const PD_DEFAULT_STACK_SIZE: u64 = 0x2000; const PD_MIN_STACK_SIZE: u64 = 0x1000; const PD_MAX_STACK_SIZE: u64 = 1024 * 1024 * 16; @@ -78,7 +83,7 @@ pub enum SysMapPerms { Execute = 4, } -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct SysMap { pub mr: String, pub vaddr: u64, @@ -89,17 +94,18 @@ pub struct SysMap { pub text_pos: Option, } -#[derive(Debug, PartialEq, Eq, Hash, Clone)] +#[derive(Debug, PartialEq, Eq, Clone)] pub enum SysMemoryRegionKind { User, Elf, Stack, } -#[derive(Debug, PartialEq, Eq, Hash, Clone)] +#[derive(Debug, PartialEq, Eq, Clone)] pub struct SysMemoryRegion { pub name: String, pub size: u64, + page_size_specified_by_user: bool, pub page_size: PageSize, pub page_count: u64, pub phys_addr: Option, @@ -116,7 +122,7 @@ impl SysMemoryRegion { pub fn optimal_page_size(&self, config: &Config) -> u64 { let page_sizes = config.page_sizes(); for i in (0..page_sizes.len()).rev() { - if self.size % page_sizes[i] == 0 { + if self.size.is_multiple_of(page_sizes[i]) { return page_sizes[i]; } } @@ -129,14 +135,55 @@ impl SysMemoryRegion { } } -#[derive(Debug, PartialEq, Eq, Hash)] +#[derive(Debug, PartialEq, Eq)] +pub enum SysIrqKind { + Conventional { + irq: u64, + trigger: ArmRiscvIrqTrigger, + }, + /// x86-64 specific + IOAPIC { + ioapic: u64, + pin: u64, + trigger: X86IoapicIrqTrigger, + polarity: X86IoapicIrqPolarity, + vector: u64, + }, + /// x86-64 specific + MSI { + pci_bus: u64, + pci_dev: u64, + pci_func: u64, + handle: u64, + vector: u64, + }, +} + +#[derive(Debug, PartialEq, Eq)] pub struct SysIrq { - pub irq: u64, pub id: u64, - pub trigger: IrqTrigger, + pub kind: SysIrqKind, +} + +impl SysIrq { + pub fn irq_num(&self) -> u64 { + match self.kind { + SysIrqKind::Conventional { irq, .. } => irq, + SysIrqKind::IOAPIC { vector, .. } => vector, + SysIrqKind::MSI { vector, .. } => vector, + } + } } -#[derive(Debug, PartialEq, Eq, Hash)] +#[derive(Debug, PartialEq, Eq)] +pub struct IOPort { + pub id: u64, + pub addr: u64, + pub size: u64, + pub text_pos: roxmltree::TextPos, +} + +#[derive(Debug, PartialEq, Eq)] pub enum SysSetVarKind { // For size we do not store the size since when we parse mappings // we do not have access to the memory region yet. The size is resolved @@ -146,7 +193,7 @@ pub enum SysSetVarKind { Paddr { region: String }, } -#[derive(Debug, PartialEq, Eq, Hash)] +#[derive(Debug, PartialEq, Eq)] pub struct SysSetVar { pub symbol: String, pub kind: SysSetVarKind, @@ -166,7 +213,7 @@ pub struct Channel { pub end_b: ChannelEnd, } -#[derive(Debug, PartialEq, Eq, Hash)] +#[derive(Debug, PartialEq, Eq)] pub struct ProtectionDomain { /// Only populated for child protection domains pub id: Option, @@ -180,6 +227,7 @@ pub struct ProtectionDomain { pub program_image: PathBuf, pub maps: Vec, pub irqs: Vec, + pub ioports: Vec, pub setvars: Vec, pub virtual_machine: Option, /// Only used when parsing child PDs. All elements will be removed @@ -190,10 +238,10 @@ pub struct ProtectionDomain { /// protection domain exists pub parent: Option, /// Location in the parsed SDF file - text_pos: roxmltree::TextPos, + text_pos: Option, } -#[derive(Debug, PartialEq, Eq, Hash)] +#[derive(Debug, PartialEq, Eq)] pub struct VirtualMachine { pub vcpus: Vec, pub name: String, @@ -203,7 +251,7 @@ pub struct VirtualMachine { pub period: u64, } -#[derive(Debug, PartialEq, Eq, Hash)] +#[derive(Debug, PartialEq, Eq)] pub struct VirtualCpu { pub id: u64, } @@ -346,6 +394,15 @@ impl ProtectionDomain { irqs } + pub fn ioport_bits(&self) -> u64 { + let mut ioports = 0; + for ioport in &self.ioports { + ioports |= 1 << ioport.id; + } + + ioports + } + fn from_xml( config: &Config, xml_sdf: &XmlSystemDescription, @@ -461,7 +518,7 @@ impl ProtectionDomain { )); } - if stack_size % config.page_sizes()[0] != 0 { + if !stack_size.is_multiple_of(config.page_sizes()[0]) { return Err(value_error( xml_sdf, node, @@ -474,6 +531,7 @@ impl ProtectionDomain { let mut maps = Vec::new(); let mut irqs = Vec::new(); + let mut ioports = Vec::new(); let mut setvars: Vec = Vec::new(); let mut child_pds = Vec::new(); @@ -557,10 +615,6 @@ impl ProtectionDomain { maps.push(map); } "irq" => { - check_attributes(xml_sdf, &child, &["irq", "id", "trigger"])?; - let irq = checked_lookup(xml_sdf, &child, "irq")? - .parse::() - .unwrap(); let id = checked_lookup(xml_sdf, &child, "id")? .parse::() .unwrap(); @@ -575,29 +629,278 @@ impl ProtectionDomain { return Err(value_error(xml_sdf, &child, "id must be >= 0".to_string())); } - let trigger = if let Some(trigger_str) = child.attribute("trigger") { - match trigger_str { - "level" => IrqTrigger::Level, - "edge" => IrqTrigger::Edge, - _ => { - return Err(value_error( - xml_sdf, - &child, - "trigger must be either 'level' or 'edge'".to_string(), - )) + if let Some(irq_str) = child.attribute("irq") { + if config.arch == Arch::X86_64 { + return Err(value_error( + xml_sdf, + &child, + "ARM and RISC-V IRQs are not supported on x86".to_string(), + )); + } + + // ARM and RISC-V interrupts must have an "irq" attribute. + check_attributes(xml_sdf, &child, &["irq", "id", "trigger"])?; + let irq = irq_str.parse::().unwrap(); + let trigger = if let Some(trigger_str) = child.attribute("trigger") { + match trigger_str { + "level" => ArmRiscvIrqTrigger::Level, + "edge" => ArmRiscvIrqTrigger::Edge, + _ => { + return Err(value_error( + xml_sdf, + &child, + "trigger must be either 'level' or 'edge'".to_string(), + )) + } } + } else { + // Default to level triggered + ArmRiscvIrqTrigger::Level + }; + let irq = SysIrq { + id: id as u64, + kind: SysIrqKind::Conventional { irq, trigger }, + }; + irqs.push(irq); + } else if let Some(pin_str) = child.attribute("pin") { + if config.arch != Arch::X86_64 { + return Err(value_error( + xml_sdf, + &child, + "x86 I/O APIC IRQ isn't supported on ARM and RISC-V".to_string(), + )); } + + // IOAPIC interrupts (X86_64) must have a "pin" attribute. + check_attributes( + xml_sdf, + &child, + &["id", "ioapic", "pin", "trigger", "polarity", "vector"], + )?; + + let ioapic = if let Some(ioapic_str) = child.attribute("ioapic") { + ioapic_str.parse::().unwrap() + } else { + // Default to the first unit. + 0 + }; + if ioapic < 0 { + return Err(value_error( + xml_sdf, + &child, + "ioapic must be >= 0".to_string(), + )); + } + + let pin = pin_str.parse::().unwrap(); + if pin < 0 { + return Err(value_error( + xml_sdf, + &child, + "pin must be >= 0".to_string(), + )); + } + + let trigger = if let Some(trigger_str) = child.attribute("trigger") { + match trigger_str { + "level" => X86IoapicIrqTrigger::Level, + "edge" => X86IoapicIrqTrigger::Edge, + _ => { + return Err(value_error( + xml_sdf, + &child, + "trigger must be either 'level' or 'edge'".to_string(), + )) + } + } + } else { + // Default to level trigger. + X86IoapicIrqTrigger::Level + }; + let polarity = if let Some(polarity_str) = child.attribute("polarity") { + match polarity_str { + "low" => X86IoapicIrqPolarity::LowTriggered, + "high" => X86IoapicIrqPolarity::HighTriggered, + _ => { + return Err(value_error( + xml_sdf, + &child, + "polarity must be either 'low' or 'high'".to_string(), + )) + } + } + } else { + // Default to normal polarity + X86IoapicIrqPolarity::HighTriggered + }; + let vector = checked_lookup(xml_sdf, &child, "vector")? + .parse::() + .unwrap(); + if vector < 0 { + return Err(value_error( + xml_sdf, + &child, + "vector must be >= 0".to_string(), + )); + } + + let irq = SysIrq { + id: id as u64, + kind: SysIrqKind::IOAPIC { + ioapic: ioapic as u64, + pin: pin as u64, + trigger, + polarity, + vector: vector as u64, + }, + }; + irqs.push(irq); + } else if let Some(pcidev_str) = child.attribute("pcidev") { + if config.arch != Arch::X86_64 { + return Err(value_error( + xml_sdf, + &child, + "x86 MSI IRQ isn't supported on ARM and RISC-V".to_string(), + )); + } + + // MSI interrupts (X86_64) have a "pcidev" attribute. + check_attributes(xml_sdf, &child, &["id", "pcidev", "handle", "vector"])?; + + let pci_parts: Vec = pcidev_str + .split([':', '.']) + .map(str::trim) + .map(|x| { + i64::from_str_radix(x, 16).expect( + "Error: Failed to parse parts of the PCI device address", + ) + }) + .collect(); + if pci_parts.len() != 3 { + return Err(format!( + "Error: failed to parse PCI address '{}' on element '{}'", + pcidev_str, + child.tag_name().name() + )); + } + if pci_parts[0] < 0 { + return Err(value_error( + xml_sdf, + &child, + "PCI bus must be >= 0".to_string(), + )); + } + if pci_parts[1] < 0 { + return Err(value_error( + xml_sdf, + &child, + "PCI device must be >= 0".to_string(), + )); + } + if pci_parts[2] < 0 { + return Err(value_error( + xml_sdf, + &child, + "PCI function must be >= 0".to_string(), + )); + } + + let handle = checked_lookup(xml_sdf, &child, "handle")? + .parse::() + .unwrap(); + if handle < 0 { + return Err(value_error( + xml_sdf, + &child, + "handle must be >= 0".to_string(), + )); + } + + let vector = checked_lookup(xml_sdf, &child, "vector")? + .parse::() + .unwrap(); + if vector < 0 { + return Err(value_error( + xml_sdf, + &child, + "vector must be >= 0".to_string(), + )); + } + + let irq = SysIrq { + id: id as u64, + kind: SysIrqKind::MSI { + pci_bus: pci_parts[0] as u64, + pci_dev: pci_parts[1] as u64, + pci_func: pci_parts[2] as u64, + handle: handle as u64, + vector: vector as u64, + }, + }; + irqs.push(irq); + } else { + // We can't figure out what type interrupt is specified. + // Trigger an error. + match config.arch { + Arch::Aarch64 | Arch::Riscv64 => { + checked_lookup(xml_sdf, &child, "irq")? + } + Arch::X86_64 => { + checked_lookup(xml_sdf, &child, "pin")?; + checked_lookup(xml_sdf, &child, "pcidev")? + } + }; + } + } + "ioport" => { + if let Arch::X86_64 = config.arch { + check_attributes(xml_sdf, &child, &["id", "addr", "size"])?; + + let id = checked_lookup(xml_sdf, &child, "id")? + .parse::() + .unwrap(); + if id > PD_MAX_ID as i64 { + return Err(value_error( + xml_sdf, + &child, + format!("id must be < {}", PD_MAX_ID + 1), + )); + } + if id < 0 { + return Err(value_error( + xml_sdf, + &child, + "id must be >= 0".to_string(), + )); + } + + let addr = + sdf_parse_number(checked_lookup(xml_sdf, &child, "addr")?, &child)?; + + let size = checked_lookup(xml_sdf, &child, "size")? + .parse::() + .unwrap(); + if size <= 0 { + return Err(value_error( + xml_sdf, + &child, + "size must be > 0".to_string(), + )); + } + + ioports.push(IOPort { + id: id as u64, + addr, + size: size as u64, + text_pos: xml_sdf.doc.text_pos_at(node.range().start), + }) } else { - // Default the level triggered - IrqTrigger::Level - }; - - let irq = SysIrq { - irq, - id: id as u64, - trigger, - }; - irqs.push(irq); + return Err(value_error( + xml_sdf, + node, + "I/O Ports are only available on x86".to_string(), + )); + } } "setvar" => { check_attributes(xml_sdf, &child, &["symbol", "region_paddr"])?; @@ -622,6 +925,13 @@ impl ProtectionDomain { child_pds.push(ProtectionDomain::from_xml(config, xml_sdf, &child, true)?) } "virtual_machine" => { + if !config.hypervisor { + return Err(value_error( + xml_sdf, + node, + "seL4 has not been built as a hypervisor, virtual machiens are disabled".to_string() + )); + } if virtual_machine.is_some() { return Err(value_error( xml_sdf, @@ -665,12 +975,13 @@ impl ProtectionDomain { program_image: program_image.unwrap(), maps, irqs, + ioports, setvars, child_pds, virtual_machine, has_children, parent: None, - text_pos: xml_sdf.doc.text_pos_at(node.range().start), + text_pos: Some(xml_sdf.doc.text_pos_at(node.range().start)), }) } } @@ -792,8 +1103,10 @@ impl SysMemoryRegion { let name = checked_lookup(xml_sdf, node, "name")?; let size = sdf_parse_number(checked_lookup(xml_sdf, node, "size")?, node)?; + let mut page_size_specified_by_user = false; let page_size = if let Some(xml_page_size) = node.attribute("page_size") { + page_size_specified_by_user = true; sdf_parse_number(xml_page_size, node)? } else { config.page_sizes()[0] @@ -808,7 +1121,7 @@ impl SysMemoryRegion { )); } - if size % page_size != 0 { + if !size.is_multiple_of(page_size) { return Err(value_error( xml_sdf, node, @@ -822,7 +1135,7 @@ impl SysMemoryRegion { None }; - if phys_addr.is_some() && phys_addr.unwrap() % page_size != 0 { + if phys_addr.is_some() && !phys_addr.unwrap().is_multiple_of(page_size) { return Err(value_error( xml_sdf, node, @@ -836,6 +1149,7 @@ impl SysMemoryRegion { name: name.to_string(), size, page_size: page_size.into(), + page_size_specified_by_user, page_count, phys_addr, text_pos: Some(xml_sdf.doc.text_pos_at(node.range().start)), @@ -976,7 +1290,7 @@ fn check_maps( let pos = map.text_pos.unwrap(); match maybe_mr { Some(mr) => { - if map.vaddr % mr.page_size_bytes() != 0 { + if !map.vaddr.is_multiple_of(mr.page_size_bytes()) { return Err(format!( "Error: invalid vaddr alignment on 'map' @ {}", loc_string(xml_sdf, pos) @@ -1117,7 +1431,7 @@ fn pd_tree_to_list( "Error: duplicate id: {} in protection domain: '{}' @ {}", child_id, pd.name, - loc_string(xml_sdf, child_pd.text_pos) + loc_string(xml_sdf, child_pd.text_pos.unwrap()) )); } // Also check that the child ID does not clash with any vCPU IDs, if the PD has a virtual machine @@ -1125,7 +1439,7 @@ fn pd_tree_to_list( for vcpu in &vm.vcpus { if child_id == vcpu.id { return Err(format!("Error: duplicate id: {} clashes with virtual machine vcpu id in protection domain: '{}' @ {}", - child_id, pd.name, loc_string(xml_sdf, child_pd.text_pos))); + child_id, pd.name, loc_string(xml_sdf, child_pd.text_pos.unwrap()))); } } } @@ -1263,6 +1577,11 @@ pub fn parse(filename: &str, xml: &str, config: &Config) -> Result Result = vec![]; for pd in &pds { if let Some(vm) = &pd.virtual_machine { - if vms.contains(&vm) { + if vms.contains(&&vm.name) { return Err(format!( "Error: duplicate virtual machine name '{}'.", vm.name )); } - vms.push(vm); + vms.push(&vm.name); } } @@ -1291,13 +1610,17 @@ pub fn parse(filename: &str, xml: &str, config: &Config) -> Result Result Result Result Result = Vec::new(); + for ioport in &pd.ioports { + if seen_ioport_ids.contains(&ioport.id) { + return Err(format!( + "Error: duplicate I/O port id: {} in protection domain: '{}' @ {}:{}:{}", + ioport.id, + pd.name, + filename, + pd.text_pos.unwrap().row, + pd.text_pos.unwrap().col + )); + } else { + seen_ioport_ids.push(ioport.id); + } + } + } + + // Ensure I/O Ports' size are valid and they don't overlap. + let mut seen_ioports: Vec<(&str, &IOPort)> = Vec::new(); + for pd in &pds { + for this_ioport in &pd.ioports { + for (seen_pd_name, seen_ioport) in &seen_ioports { + let left_range = this_ioport.addr..this_ioport.addr + this_ioport.size - 1; + let right_range = seen_ioport.addr..seen_ioport.addr + seen_ioport.size - 1; + if ranges_overlap(&left_range, &right_range) { + return Err(format!( + "Error: I/O port id: {}, inclusive range: [{:#x}, {:#x}] in protection domain: '{}' @ {}:{}:{} overlaps with I/O port id: {}, inclusive range: [{:#x}, {:#x}] in protection domain: '{}' @ {}:{}:{}", + this_ioport.id, + left_range.start, + left_range.end, + pd.name, + filename, + this_ioport.text_pos.row, + this_ioport.text_pos.col, + seen_ioport.id, + right_range.start, + right_range.end, + seen_pd_name, + filename, + seen_ioport.text_pos.row, + seen_ioport.text_pos.col + )); + } + } + seen_ioports.push((&pd.name, this_ioport)); + } + } + // Ensure that all maps are correct for pd in &pds { check_maps(&xml_sdf, &mrs, pd, &pd.maps)?; @@ -1412,8 +1797,12 @@ pub fn parse(filename: &str, xml: &str, config: &Config) -> Result Result MemoryRegion { + let segments = kernel_elf.loadable_segments(); + let base = segments[0].phys_addr; + let (ki_end_v, _) = kernel_elf + .find_symbol("ki_end") + .expect("Could not find 'ki_end' symbol"); + let ki_end_p = ki_end_v - segments[0].virt_addr + base; + + MemoryRegion::new(base, ki_end_p) +} + +fn kernel_boot_mem(kernel_elf: &ElfFile) -> MemoryRegion { + let segments = kernel_elf.loadable_segments(); + let base = segments[0].phys_addr; + let (ki_boot_end_v, _) = kernel_elf + .find_symbol("ki_boot_end") + .expect("Could not find 'ki_boot_end' symbol"); + let ki_boot_end_p = ki_boot_end_v - segments[0].virt_addr + base; + + MemoryRegion::new(base, ki_boot_end_p) +} + +/// +/// Emulate what happens during a kernel boot, up to the point +/// where the reserved region is allocated to determine the memory ranges +/// available. Only valid for ARM and RISC-V platforms. +/// +fn kernel_partial_boot(kernel_config: &Config, kernel_elf: &ElfFile) -> KernelPartialBootInfo { + // Determine the untyped caps of the system + // This lets allocations happen correctly. + let mut device_memory = DisjointMemoryRegion::default(); + let mut normal_memory = DisjointMemoryRegion::default(); + + for r in kernel_config.device_regions.as_ref().unwrap().iter() { + device_memory.insert_region(r.start, r.end); + } + for r in kernel_config.normal_regions.as_ref().unwrap().iter() { + normal_memory.insert_region(r.start, r.end); + } + + // Remove the kernel image itself + let self_mem = kernel_self_mem(kernel_elf); + normal_memory.remove_region(self_mem.base, self_mem.end); + + // but get the boot region, we'll add that back later + // @ivanv: Why calculate it now if we add it back later? + let boot_region = kernel_boot_mem(kernel_elf); + + KernelPartialBootInfo { + device_memory, + normal_memory, + boot_region, + } +} + +pub fn emulate_kernel_boot_partial( + kernel_config: &Config, + kernel_elf: &ElfFile, +) -> (DisjointMemoryRegion, MemoryRegion) { + let partial_info = kernel_partial_boot(kernel_config, kernel_elf); + (partial_info.normal_memory, partial_info.boot_region) +} + +fn get_n_paging(region: MemoryRegion, bits: u64) -> u64 { + let start = util::round_down(region.base, 1 << bits); + let end = util::round_up(region.end, 1 << bits); + + (end - start) / (1 << bits) +} + +fn get_arch_n_paging(config: &Config, region: MemoryRegion) -> u64 { + match config.arch { + Arch::Aarch64 => { + const PT_INDEX_OFFSET: u64 = 12; + const PD_INDEX_OFFSET: u64 = PT_INDEX_OFFSET + 9; + const PUD_INDEX_OFFSET: u64 = PD_INDEX_OFFSET + 9; + + if config.aarch64_vspace_s2_start_l1() { + get_n_paging(region, PUD_INDEX_OFFSET) + get_n_paging(region, PD_INDEX_OFFSET) + } else { + const PGD_INDEX_OFFSET: u64 = PUD_INDEX_OFFSET + 9; + get_n_paging(region, PGD_INDEX_OFFSET) + + get_n_paging(region, PUD_INDEX_OFFSET) + + get_n_paging(region, PD_INDEX_OFFSET) + } + } + Arch::Riscv64 => match config.riscv_pt_levels.unwrap() { + RiscvVirtualMemory::Sv39 => { + const PT_INDEX_OFFSET: u64 = 12; + const LVL1_INDEX_OFFSET: u64 = PT_INDEX_OFFSET + 9; + const LVL2_INDEX_OFFSET: u64 = LVL1_INDEX_OFFSET + 9; + + get_n_paging(region, LVL2_INDEX_OFFSET) + get_n_paging(region, LVL1_INDEX_OFFSET) + } + }, + Arch::X86_64 => unreachable!("the kernel boot process should not be emulated for x86!"), + } +} + +fn calculate_rootserver_size(config: &Config, initial_task_region: MemoryRegion) -> u64 { + // FIXME: These constants should ideally come from the config / kernel + // binary not be hard coded here. + // But they are constant so it isn't too bad. + let slot_bits = 5; // seL4_SlotBits + let root_cnode_bits = config.init_cnode_bits; // CONFIG_ROOT_CNODE_SIZE_BITS + let tcb_bits = ObjectType::Tcb.fixed_size_bits(config).unwrap(); // seL4_TCBBits + let page_bits = ObjectType::SmallPage.fixed_size_bits(config).unwrap(); // seL4_PageBits + let asid_pool_bits = 12; // seL4_ASIDPoolBits + let vspace_bits = ObjectType::VSpace.fixed_size_bits(config).unwrap(); // seL4_VSpaceBits + let page_table_bits = ObjectType::PageTable.fixed_size_bits(config).unwrap(); // seL4_PageTableBits + let min_sched_context_bits = 7; // seL4_MinSchedContextBits + + let mut size = 0; + size += 1 << (root_cnode_bits + slot_bits); + size += 1 << (tcb_bits); + size += 2 * (1 << page_bits); + size += 1 << asid_pool_bits; + size += 1 << vspace_bits; + size += get_arch_n_paging(config, initial_task_region) * (1 << page_table_bits); + size += 1 << min_sched_context_bits; + + size +} + +fn rootserver_max_size_bits(config: &Config) -> u64 { + let slot_bits = 5; // seL4_SlotBits + let root_cnode_bits = config.init_cnode_bits; // CONFIG_ROOT_CNODE_SIZE_BITS + let vspace_bits = ObjectType::VSpace.fixed_size_bits(config).unwrap(); + + let cnode_size_bits = root_cnode_bits + slot_bits; + max(cnode_size_bits, vspace_bits) +} + +/// Emulate what happens during a kernel boot, generating a +/// representation of the BootInfo struct. +pub fn emulate_kernel_boot( + config: &Config, + kernel_elf: &ElfFile, + initial_task_phys_region: MemoryRegion, + initial_task_virt_region: MemoryRegion, +) -> BootInfo { + assert!(initial_task_phys_region.size() == initial_task_virt_region.size()); + let partial_info = kernel_partial_boot(config, kernel_elf); + let mut normal_memory = partial_info.normal_memory; + let device_memory = partial_info.device_memory; + let boot_region = partial_info.boot_region; + + normal_memory.remove_region(initial_task_phys_region.base, initial_task_phys_region.end); + + // Now, the tricky part! determine which memory is used for the initial task objects + let initial_objects_size = calculate_rootserver_size(config, initial_task_virt_region); + let initial_objects_align = rootserver_max_size_bits(config); + + // Find an appropriate region of normal memory to allocate the objects + // from; this follows the same algorithm used within the kernel boot code + // (or at least we hope it does!) + // TODO: this loop could be done better in a functional way? + let mut region_to_remove: Option = None; + for region in normal_memory.regions.iter().rev() { + let start = util::round_down( + region.end - initial_objects_size, + 1 << initial_objects_align, + ); + if start >= region.base { + region_to_remove = Some(start); + break; + } + } + if let Some(start) = region_to_remove { + normal_memory.remove_region(start, start + initial_objects_size); + } else { + panic!("Couldn't find appropriate region for initial task kernel objects"); + } + + let fixed_cap_count = 0x10; + let sched_control_cap_count = 1; + let paging_cap_count = get_arch_n_paging(config, initial_task_virt_region); + let page_cap_count = initial_task_virt_region.size() / config.minimum_page_size; + let first_untyped_cap = + fixed_cap_count + paging_cap_count + sched_control_cap_count + page_cap_count; + let sched_control_cap = fixed_cap_count + paging_cap_count; + + let max_bits = match config.arch { + Arch::Aarch64 => 47, + Arch::Riscv64 => 38, + Arch::X86_64 => unreachable!("the kernel boot process should not be emulated for x86!"), + }; + let device_regions: Vec = + [device_memory.aligned_power_of_two_regions(config, max_bits)].concat(); + let normal_regions: Vec = [ + boot_region.aligned_power_of_two_regions(config, max_bits), + normal_memory.aligned_power_of_two_regions(config, max_bits), + ] + .concat(); + let mut untyped_objects = Vec::new(); + for (i, r) in device_regions.iter().enumerate() { + let cap = i as u64 + first_untyped_cap; + untyped_objects.push(UntypedObject::new(cap, *r, true)); + } + let normal_regions_start_cap = first_untyped_cap + device_regions.len() as u64; + for (i, r) in normal_regions.iter().enumerate() { + let cap = i as u64 + normal_regions_start_cap; + untyped_objects.push(UntypedObject::new(cap, *r, false)); + } + + let first_available_cap = + first_untyped_cap + device_regions.len() as u64 + normal_regions.len() as u64; + BootInfo { + fixed_cap_count, + paging_cap_count, + page_cap_count, + sched_control_cap, + first_available_cap, + untyped_objects, + } +} + #[derive(Deserialize)] pub struct PlatformConfigRegion { pub start: u64, @@ -31,24 +254,6 @@ pub struct PlatformConfig { pub memory: Vec, } -/// Represents an allocated kernel object. -/// -/// Kernel objects can have multiple caps (and caps can have multiple addresses). -/// The cap referred to here is the original cap that is allocated when the -/// kernel object is first allocate. -/// The cap_slot refers to the specific slot in which this cap resides. -/// The cap_address refers to a cap address that addresses this cap. -/// The cap_address is is intended to be valid within the context of the -/// initial task. -#[derive(Copy, Clone)] -pub struct Object { - /// Type of kernel object - pub object_type: ObjectType, - pub cap_addr: u64, - /// Physical memory address of the kernel object - pub phys_addr: u64, -} - pub struct Config { pub arch: Arch, pub word_size: u64, @@ -58,6 +263,7 @@ pub struct Config { pub init_cnode_bits: u64, pub cap_address_bits: u64, pub fan_out_limit: u64, + pub max_num_bootinfo_untypeds: u64, pub hypervisor: bool, pub benchmark: bool, pub fpu: bool, @@ -69,9 +275,12 @@ pub struct Config { pub arm_smc: Option, /// RISC-V specific, what kind of virtual memory system (e.g Sv39) pub riscv_pt_levels: Option, + /// x86 specific, user context size + pub x86_xsave_size: Option, pub invocations_labels: serde_json::Value, - pub device_regions: Vec, - pub normal_regions: Vec, + /// The two remaining fields are only valid on ARM and RISC-V + pub device_regions: Option>, + pub normal_regions: Option>, } impl Config { @@ -86,25 +295,26 @@ impl Config { false => 0x800000000000, }, Arch::Riscv64 => 0x0000003ffffff000, + Arch::X86_64 => 0x7ffffffff000, } } pub fn virtual_base(&self) -> u64 { - // These match the PPTR_BASE define in the kernel source. match self.arch { Arch::Aarch64 => match self.hypervisor { true => 0x0000008000000000, - false => 0xffffff8000000000, + false => u64::pow(2, 64) - u64::pow(2, 39), }, Arch::Riscv64 => match self.riscv_pt_levels.unwrap() { - RiscvVirtualMemory::Sv39 => 0xffffffc000000000, + RiscvVirtualMemory::Sv39 => u64::pow(2, 64) - u64::pow(2, 38), }, + Arch::X86_64 => u64::pow(2, 64) - u64::pow(2, 39), } } pub fn page_sizes(&self) -> [u64; 2] { match self.arch { - Arch::Aarch64 | Arch::Riscv64 => [0x1000, 0x200_000], + Arch::Aarch64 | Arch::Riscv64 | Arch::X86_64 => [0x1000, 0x200_000], } } @@ -148,11 +358,22 @@ impl Config { _ => panic!("internal error"), } } + + pub fn num_page_table_levels(&self) -> usize { + match self.arch { + Arch::Aarch64 => 4, + Arch::Riscv64 => self.riscv_pt_levels.unwrap().levels(), + // seL4 only supports 4-level page table on x86-64. + Arch::X86_64 => 4, + } + } } +#[derive(PartialEq, Eq)] pub enum Arch { Aarch64, Riscv64, + X86_64, } /// RISC-V supports multiple virtual memory systems and so we use this enum @@ -171,7 +392,7 @@ impl RiscvVirtualMemory { } } -#[derive(Debug, Hash, Eq, PartialEq, Copy, Clone)] +#[derive(Debug, Hash, Eq, PartialEq, Clone)] pub enum ObjectType { Untyped, Tcb, @@ -186,6 +407,7 @@ pub enum ObjectType { LargePage, PageTable, Vcpu, + AsidPool, } impl ObjectType { @@ -199,6 +421,14 @@ impl ObjectType { true => Some(11), false => Some(10), }, + Arch::X86_64 => { + // matches seL4/libsel4/sel4_arch_include/x86_64/sel4/sel4_arch/constants.h + if config.x86_xsave_size.unwrap() >= 832 { + Some(12) + } else { + Some(11) + } + } }, ObjectType::Endpoint => Some(4), ObjectType::Notification => Some(6), @@ -214,7 +444,7 @@ impl ObjectType { }, false => Some(12), }, - Arch::Riscv64 => Some(12), + _ => Some(12), }, ObjectType::PageTable => Some(12), ObjectType::HugePage => Some(30), @@ -222,8 +452,10 @@ impl ObjectType { ObjectType::SmallPage => Some(12), ObjectType::Vcpu => match config.arch { Arch::Aarch64 => Some(12), + Arch::X86_64 => Some(14), _ => panic!("Unexpected architecture asking for vCPU size bits"), }, + ObjectType::AsidPool => Some(12), _ => None, } } @@ -231,79 +463,10 @@ impl ObjectType { pub fn fixed_size(self, config: &Config) -> Option { self.fixed_size_bits(config).map(|bits| 1 << bits) } - - pub fn to_str(self) -> &'static str { - match self { - ObjectType::Untyped => "SEL4_UNTYPED_OBJECT", - ObjectType::Tcb => "SEL4_TCB_OBJECT", - ObjectType::Endpoint => "SEL4_ENDPOINT_OBJECT", - ObjectType::Notification => "SEL4_NOTIFICATION_OBJECT", - ObjectType::CNode => "SEL4_CNODE_OBJECT", - ObjectType::SchedContext => "SEL4_SCHEDCONTEXT_OBJECT", - ObjectType::Reply => "SEL4_REPLY_OBJECT", - ObjectType::HugePage => "SEL4_HUGE_PAGE_OBJECT", - ObjectType::VSpace => "SEL4_VSPACE_OBJECT", - ObjectType::SmallPage => "SEL4_SMALL_PAGE_OBJECT", - ObjectType::LargePage => "SEL4_LARGE_PAGE_OBJECT", - ObjectType::PageTable => "SEL4_PAGE_TABLE_OBJECT", - ObjectType::Vcpu => "SEL4_VCPU_OBJECT", - } - } - - /// The kernel associates each kernel object with an identifier, which - /// also depends on the configuration of the kernel. - /// When generating the raw invocation to be given to the initial task, - /// this method must be called for any UntypedRetype invocations. - pub fn value(self, config: &Config) -> u64 { - match self { - ObjectType::Untyped => 0, - ObjectType::Tcb => 1, - ObjectType::Endpoint => 2, - ObjectType::Notification => 3, - ObjectType::CNode => 4, - ObjectType::SchedContext => 5, - ObjectType::Reply => 6, - ObjectType::HugePage => 7, - ObjectType::VSpace => match config.arch { - Arch::Aarch64 => 8, - Arch::Riscv64 => 10, - }, - ObjectType::SmallPage => match config.arch { - Arch::Aarch64 => 9, - Arch::Riscv64 => 8, - }, - ObjectType::LargePage => match config.arch { - Arch::Aarch64 => 10, - Arch::Riscv64 => 9, - }, - ObjectType::PageTable => match config.arch { - Arch::Aarch64 => 11, - Arch::Riscv64 => 10, - }, - ObjectType::Vcpu => match config.arch { - Arch::Aarch64 => 12, - _ => panic!("Unknown vCPU object type value for given kernel config"), - }, - } - } - - pub fn format(&self, config: &Config) -> String { - let object_size = if let Some(fixed_size) = self.fixed_size(config) { - format!("0x{fixed_size:x}") - } else { - "variable size".to_string() - }; - format!( - " object_type {} ({} - {})", - self.value(config), - self.to_str(), - object_size - ) - } } #[repr(u64)] -#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)] +#[derive(Debug, PartialEq, Eq, Copy, Clone)] pub enum PageSize { Small = 0x1000, Large = 0x200_000, @@ -319,1115 +482,92 @@ impl From for PageSize { } } -/// Virtual memory attributes for ARM -/// The values for each enum variant corresponds to what seL4 -/// expects when doing a virtual memory invocation. -#[repr(u64)] -pub enum ArmVmAttributes { - Cacheable = 1, - ParityEnabled = 2, - ExecuteNever = 4, -} - -/// Virtual memory attributes for RISC-V -/// The values for each enum variant corresponds to what seL4 -/// expects when doing a virtual memory invocation. -#[repr(u64)] -pub enum RiscvVmAttributes { - ExecuteNever = 1, -} - -impl ArmVmAttributes { - #[allow(clippy::should_implement_trait)] // Default::default would return Self, not u64 - pub fn default() -> u64 { - ArmVmAttributes::Cacheable as u64 | ArmVmAttributes::ParityEnabled as u64 - } -} - -impl RiscvVmAttributes { - #[allow(clippy::should_implement_trait)] // Default::default would return Self, not u64 - pub fn default() -> u64 { - 0 - } -} - -pub fn default_vm_attr(config: &Config) -> u64 { - match config.arch { - Arch::Aarch64 => ArmVmAttributes::default(), - Arch::Riscv64 => RiscvVmAttributes::default(), +impl PageSize { + pub fn fixed_size_bits(&self, sel4_config: &Config) -> u64 { + match self { + PageSize::Small => ObjectType::SmallPage.fixed_size_bits(sel4_config).unwrap(), + PageSize::Large => ObjectType::LargePage.fixed_size_bits(sel4_config).unwrap(), + } } } -#[repr(u32)] -#[derive(Copy, Clone)] -#[allow(dead_code)] -pub enum Rights { - None = 0x0, - Write = 0x1, - Read = 0x2, - Grant = 0x4, - GrantReply = 0x8, - All = 0xf, -} - -#[repr(u32)] -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -/// The same values apply to all kernel architectures -pub enum IrqTrigger { +// @merge: I would rather have the duplication of ARM and RISC-V +// rather than a type that tries to unify both. +#[repr(u64)] +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +/// The same values apply to ARM and RISC-V +pub enum ArmRiscvIrqTrigger { Level = 0, Edge = 1, } -#[repr(u32)] -#[derive(Debug, Clone, Copy)] -#[allow(dead_code)] -enum InvocationLabel { - // Untyped - UntypedRetype, - // TCB - TCBReadRegisters, - TCBWriteRegisters, - TCBCopyRegisters, - TCBConfigure, - TCBSetPriority, - TCBSetMCPriority, - TCBSetSchedParams, - TCBSetTimeoutEndpoint, - TCBSetIPCBuffer, - TCBSetSpace, - TCBSuspend, - TCBResume, - TCBBindNotification, - TCBUnbindNotification, - TCBSetTLSBase, - // CNode - CNodeRevoke, - CNodeDelete, - CNodeCancelBadgedSends, - CNodeCopy, - CNodeMint, - CNodeMove, - CNodeMutate, - CNodeRotate, - // IRQ - IRQIssueIRQHandler, - IRQAckIRQ, - IRQSetIRQHandler, - IRQClearIRQHandler, - // Domain - DomainSetSet, - // Scheduling - SchedControlConfigureFlags, - SchedContextBind, - SchedContextUnbind, - SchedContextUnbindObject, - SchedContextConsume, - SchedContextYieldTo, - // ARM VSpace - ARMVSpaceCleanData, - ARMVSpaceInvalidateData, - ARMVSpaceCleanInvalidateData, - ARMVSpaceUnifyInstruction, - // ARM SMC - ARMSMCCall, - // ARM Page table - ARMPageTableMap, - ARMPageTableUnmap, - // ARM Page - ARMPageMap, - ARMPageUnmap, - ARMPageCleanData, - ARMPageInvalidateData, - ARMPageCleanInvalidateData, - ARMPageUnifyInstruction, - ARMPageGetAddress, - // ARM Asid - ARMASIDControlMakePool, - ARMASIDPoolAssign, - // ARM vCPU - ARMVCPUSetTCB, - ARMVCPUInjectIRQ, - ARMVCPUReadReg, - ARMVCPUWriteReg, - ARMVCPUAckVppi, - // ARM IRQ - ARMIRQIssueIRQHandlerTrigger, - // RISC-V Page Table - RISCVPageTableMap, - RISCVPageTableUnmap, - // RISC-V Page - RISCVPageMap, - RISCVPageUnmap, - RISCVPageGetAddress, - // RISC-V ASID - RISCVASIDControlMakePool, - RISCVASIDPoolAssign, - // RISC-V IRQ - RISCVIRQIssueIRQHandlerTrigger, -} - -impl std::fmt::Display for InvocationLabel { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "{self:?}") - } -} - -#[derive(Copy, Clone, Default)] -#[allow(dead_code)] -pub struct Riscv64Regs { - pub pc: u64, - pub ra: u64, - pub sp: u64, - pub gp: u64, - pub s0: u64, - pub s1: u64, - pub s2: u64, - pub s3: u64, - pub s4: u64, - pub s5: u64, - pub s6: u64, - pub s7: u64, - pub s8: u64, - pub s9: u64, - pub s10: u64, - pub s11: u64, - pub a0: u64, - pub a1: u64, - pub a2: u64, - pub a3: u64, - pub a4: u64, - pub a5: u64, - pub a6: u64, - pub a7: u64, - pub t0: u64, - pub t1: u64, - pub t2: u64, - pub t3: u64, - pub t4: u64, - pub t5: u64, - pub t6: u64, - pub tp: u64, -} - -impl Riscv64Regs { - pub fn field_names(&self) -> Vec<(&'static str, u64)> { - vec![ - ("pc", self.pc), - ("ra", self.ra), - ("sp", self.sp), - ("gp", self.gp), - ("s0", self.s0), - ("s1", self.s1), - ("s2", self.s2), - ("s3", self.s3), - ("s4", self.s4), - ("s5", self.s5), - ("s6", self.s6), - ("s7", self.s7), - ("s8", self.s8), - ("s9", self.s9), - ("s10", self.s10), - ("s11", self.s11), - ("a0", self.a0), - ("a1", self.a1), - ("a2", self.a2), - ("a3", self.a3), - ("a4", self.a4), - ("a5", self.a5), - ("a6", self.a6), - ("a7", self.a7), - ("t0", self.t0), - ("t1", self.t1), - ("t2", self.t2), - ("t3", self.t3), - ("t4", self.t4), - ("t5", self.t5), - ("t6", self.t6), - ("tp", self.tp), - ] - } - - pub fn as_slice(&self) -> Vec { - vec![ - self.pc, self.ra, self.sp, self.gp, self.s0, self.s1, self.s2, self.s3, self.s4, - self.s5, self.s6, self.s7, self.s8, self.s9, self.s10, self.s11, self.a0, self.a1, - self.a2, self.a3, self.a4, self.a5, self.a6, self.a7, self.t0, self.t1, self.t2, - self.t3, self.t4, self.t5, self.t6, self.tp, - ] +impl From for ArmRiscvIrqTrigger { + fn from(item: u64) -> ArmRiscvIrqTrigger { + match item { + 0 => ArmRiscvIrqTrigger::Level, + 1 => ArmRiscvIrqTrigger::Edge, + _ => panic!("Unknown ARM/RISC-V IRQ trigger {item:x}"), + } } - - /// Number of registers - pub const LEN: usize = 32; } -#[derive(Copy, Clone, Default)] -#[allow(dead_code)] -pub struct Aarch64Regs { - pub pc: u64, - pub sp: u64, - pub spsr: u64, - pub x0: u64, - pub x1: u64, - pub x2: u64, - pub x3: u64, - pub x4: u64, - pub x5: u64, - pub x6: u64, - pub x7: u64, - pub x8: u64, - pub x16: u64, - pub x17: u64, - pub x18: u64, - pub x29: u64, - pub x30: u64, - pub x9: u64, - pub x10: u64, - pub x11: u64, - pub x12: u64, - pub x13: u64, - pub x14: u64, - pub x15: u64, - pub x19: u64, - pub x20: u64, - pub x21: u64, - pub x22: u64, - pub x23: u64, - pub x24: u64, - pub x25: u64, - pub x26: u64, - pub x27: u64, - pub x28: u64, - pub tpidr_el0: u64, - pub tpidrro_el0: u64, -} - -impl Aarch64Regs { - pub fn field_names(&self) -> Vec<(&'static str, u64)> { - vec![ - ("pc", self.pc), - ("sp", self.sp), - ("spsr", self.spsr), - ("x0", self.x0), - ("x1", self.x1), - ("x2", self.x2), - ("x3", self.x3), - ("x4", self.x4), - ("x5", self.x5), - ("x6", self.x6), - ("x7", self.x7), - ("x8", self.x8), - ("x16", self.x16), - ("x17", self.x17), - ("x18", self.x18), - ("x29", self.x29), - ("x30", self.x30), - ("x9", self.x9), - ("x10", self.x10), - ("x11", self.x11), - ("x12", self.x12), - ("x13", self.x13), - ("x14", self.x14), - ("x15", self.x15), - ("x19", self.x19), - ("x20", self.x20), - ("x21", self.x21), - ("x22", self.x22), - ("x23", self.x23), - ("x24", self.x24), - ("x25", self.x25), - ("x26", self.x26), - ("x27", self.x27), - ("x28", self.x28), - ("tpidr_el0", self.tpidr_el0), - ("tpidrro_el0", self.tpidrro_el0), - ] - } - - pub fn as_slice(&self) -> Vec { - vec![ - self.pc, - self.sp, - self.spsr, - self.x0, - self.x1, - self.x2, - self.x3, - self.x4, - self.x5, - self.x6, - self.x7, - self.x8, - self.x16, - self.x17, - self.x18, - self.x29, - self.x30, - self.x9, - self.x10, - self.x11, - self.x12, - self.x13, - self.x14, - self.x15, - self.x19, - self.x20, - self.x21, - self.x22, - self.x23, - self.x24, - self.x25, - self.x26, - self.x27, - self.x28, - self.tpidr_el0, - self.tpidrro_el0, - ] +impl ArmRiscvIrqTrigger { + pub fn human_name(&self) -> &str { + match self { + ArmRiscvIrqTrigger::Level => "level", + ArmRiscvIrqTrigger::Edge => "edge", + } } - - /// Number of registers - pub const LEN: usize = 36; } -pub struct Invocation { - /// There is some careful context to be aware of when using this field. - /// The 'InvocationLabel' is abstract and does not represent the actual - /// value that seL4 system calls use as it is dependent on the kernel - /// configuration. When we convert this invocation to a list of bytes, we - /// need to use 'label_raw' instead. - label: InvocationLabel, - label_raw: u32, - args: InvocationArgs, - repeat: Option<(u32, InvocationArgs)>, +#[repr(u64)] +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum X86IoapicIrqTrigger { + Level = 1, + Edge = 0, } -impl Invocation { - pub fn new(config: &Config, args: InvocationArgs) -> Invocation { - let label = args.to_label(config); - Invocation { - label, - label_raw: config.invocations_labels[label.to_string()] - .as_number() - .expect("Invocation is not a number") - .as_u64() - .expect("Invocation is not u64") - .try_into() - .expect("Invocation is not u32"), - args, - repeat: None, - } - } - - /// Convert our higher-level representation of a seL4 invocation - /// into raw bytes that will be given to the monitor to interpret - /// at runtime. - /// Appends to the given data - pub fn add_raw_invocation(&self, config: &Config, data: &mut Vec) { - let (service, args, extra_caps): (u64, Vec, Vec) = - self.args.clone().get_args(config); - - // To potentionally save some allocation, we reserve enough space for all the invocation args - data.reserve(2 + args.len() * 8 + extra_caps.len() * 8); - - let mut tag = Invocation::message_info_new( - self.label_raw as u64, - 0, - extra_caps.len() as u64, - args.len() as u64, - ); - if let Some((count, _)) = self.repeat { - tag |= ((count - 1) as u64) << 32; - } - - data.extend(tag.to_le_bytes()); - data.extend(service.to_le_bytes()); - for arg in extra_caps { - data.extend(arg.to_le_bytes()); - } - for arg in args { - data.extend(arg.to_le_bytes()); - } - - if let Some((_, repeat)) = self.repeat.clone() { - // Assert that the variant of the invocation arguments is the - // same as the repeat invocation argument variant. - assert!(std::mem::discriminant(&self.args) == std::mem::discriminant(&repeat)); - - let (repeat_service, repeat_args, repeat_extra_caps) = repeat.get_args(config); - data.extend(repeat_service.to_le_bytes()); - for cap in repeat_extra_caps { - data.extend(cap.to_le_bytes()); - } - for arg in repeat_args { - data.extend(arg.to_le_bytes()); - } - } - } - - /// With how count is used when we convert the invocation, it is limited to a u32. - pub fn repeat(&mut self, count: u32, repeat_args: InvocationArgs) { - assert!(self.repeat.is_none()); - if count > 1 { - self.repeat = Some((count, repeat_args)); - } - } - - pub fn message_info_new(label: u64, caps: u64, extra_caps: u64, length: u64) -> u64 { - assert!(label < (1 << 50)); - assert!(caps < 8); - assert!(extra_caps < 8); - assert!(length < 0x80); - - (label << 12) | (caps << 9) | (extra_caps << 7) | length - } - - fn fmt_field(field_name: &'static str, value: u64) -> String { - format!(" {field_name:<20} {value}") - } - - fn fmt_field_str(field_name: &'static str, value: String) -> String { - format!(" {field_name:<20} {value}") - } - - fn fmt_field_hex(field_name: &'static str, value: u64) -> String { - format!(" {field_name:<20} 0x{value:x}") - } - - fn fmt_field_reg(reg: &'static str, value: u64) -> String { - format!("{reg}: 0x{value:016x}") - } - - fn fmt_field_bool(field_name: &'static str, value: bool) -> String { - format!(" {field_name:<20} {value}") - } - - fn fmt_field_cap( - field_name: &'static str, - cap: u64, - cap_lookup: &HashMap, - ) -> String { - let s = if let Some(name) = cap_lookup.get(&cap) { - name - } else { - "None" - }; - let field = format!("{field_name} (cap)"); - format!(" {field:<20} 0x{cap:016x} ({s})") - } - - // This function is not particularly elegant. What is happening is that we are formatting - // each invocation and its arguments depending on the kind of argument. - // We do this in an explicit way due to there only being a dozen or so invocations rather - // than involving some complicated macros, although maybe there is a better way I am not - // aware of. - pub fn report_fmt( - &self, - f: &mut BufWriter, - config: &Config, - cap_lookup: &HashMap, - ) { - let mut arg_strs = Vec::new(); - let (service, service_str): (u64, &str) = match self.args { - InvocationArgs::UntypedRetype { - untyped, - object_type, - size_bits, - root, - node_index, - node_depth, - node_offset, - num_objects, - } => { - arg_strs.push(object_type.format(config)); - let sz_fmt = if size_bits == 0 { - String::from("N/A") - } else { - format!("0x{:x}", 1 << size_bits) - }; - arg_strs.push(Invocation::fmt_field_str( - "size_bits", - format!("{size_bits} ({sz_fmt})"), - )); - arg_strs.push(Invocation::fmt_field_cap("root", root, cap_lookup)); - arg_strs.push(Invocation::fmt_field("node_index", node_index)); - arg_strs.push(Invocation::fmt_field("node_depth", node_depth)); - arg_strs.push(Invocation::fmt_field("node_offset", node_offset)); - arg_strs.push(Invocation::fmt_field("num_objects", num_objects)); - (untyped, &cap_lookup[&untyped]) - } - InvocationArgs::TcbSetSchedParams { - tcb, - authority, - mcp, - priority, - sched_context, - fault_ep, - } => { - arg_strs.push(Invocation::fmt_field_cap( - "authority", - authority, - cap_lookup, - )); - arg_strs.push(Invocation::fmt_field("mcp", mcp)); - arg_strs.push(Invocation::fmt_field("priority", priority)); - arg_strs.push(Invocation::fmt_field_cap( - "sched_context", - sched_context, - cap_lookup, - )); - arg_strs.push(Invocation::fmt_field_cap("fault_ep", fault_ep, cap_lookup)); - (tcb, &cap_lookup[&tcb]) - } - InvocationArgs::TcbSetSpace { - tcb, - fault_ep, - cspace_root, - cspace_root_data, - vspace_root, - vspace_root_data, - } => { - arg_strs.push(Invocation::fmt_field_cap("fault_ep", fault_ep, cap_lookup)); - arg_strs.push(Invocation::fmt_field_cap( - "cspace_root", - cspace_root, - cap_lookup, - )); - arg_strs.push(Invocation::fmt_field("cspace_root_data", cspace_root_data)); - arg_strs.push(Invocation::fmt_field_cap( - "vspace_root", - vspace_root, - cap_lookup, - )); - arg_strs.push(Invocation::fmt_field("vspace_root_data", vspace_root_data)); - (tcb, &cap_lookup[&tcb]) - } - InvocationArgs::TcbSetIpcBuffer { - tcb, - buffer, - buffer_frame, - } => { - arg_strs.push(Invocation::fmt_field_hex("buffer", buffer)); - arg_strs.push(Invocation::fmt_field_cap( - "buffer_frame", - buffer_frame, - cap_lookup, - )); - (tcb, &cap_lookup[&tcb]) - } - InvocationArgs::TcbResume { tcb } => (tcb, &cap_lookup[&tcb]), - InvocationArgs::TcbWriteRegisters { - tcb, - resume, - arch_flags, - ref regs, - .. - } => { - arg_strs.push(Invocation::fmt_field_bool("resume", resume)); - arg_strs.push(Invocation::fmt_field("arch_flags", arch_flags as u64)); - - let reg_strs = regs - .iter() - .map(|(field, val)| Invocation::fmt_field_reg(field, *val)) - .collect::>(); - arg_strs.push(Invocation::fmt_field_str("regs", reg_strs[0].clone())); - for s in ®_strs[1..] { - arg_strs.push(format!(" {s}")); - } - - (tcb, &cap_lookup[&tcb]) - } - InvocationArgs::TcbBindNotification { tcb, notification } => { - arg_strs.push(Invocation::fmt_field_cap( - "notification", - notification, - cap_lookup, - )); - (tcb, &cap_lookup[&tcb]) - } - InvocationArgs::AsidPoolAssign { asid_pool, vspace } => { - arg_strs.push(Invocation::fmt_field_cap("vspace", vspace, cap_lookup)); - (asid_pool, &cap_lookup[&asid_pool]) - } - InvocationArgs::IrqControlGetTrigger { - irq_control, - irq, - trigger, - dest_root, - dest_index, - dest_depth, - } => { - arg_strs.push(Invocation::fmt_field("irq", irq)); - arg_strs.push(Invocation::fmt_field("trigger", trigger as u64)); - arg_strs.push(Invocation::fmt_field_cap( - "dest_root", - dest_root, - cap_lookup, - )); - arg_strs.push(Invocation::fmt_field("dest_index", dest_index)); - arg_strs.push(Invocation::fmt_field("dest_depth", dest_depth)); - (irq_control, &cap_lookup[&irq_control]) - } - InvocationArgs::IrqHandlerSetNotification { - irq_handler, - notification, - } => { - arg_strs.push(Invocation::fmt_field_cap( - "notification", - notification, - cap_lookup, - )); - (irq_handler, &cap_lookup[&irq_handler]) - } - InvocationArgs::PageTableMap { - page_table, - vspace, - vaddr, - attr, - } => { - arg_strs.push(Invocation::fmt_field_cap("vspace", vspace, cap_lookup)); - arg_strs.push(Invocation::fmt_field_hex("vaddr", vaddr)); - arg_strs.push(Invocation::fmt_field("attr", attr)); - (page_table, &cap_lookup[&page_table]) - } - InvocationArgs::PageMap { - page, - vspace, - vaddr, - rights, - attr, - } => { - arg_strs.push(Invocation::fmt_field_cap("vspace", vspace, cap_lookup)); - arg_strs.push(Invocation::fmt_field_hex("vaddr", vaddr)); - arg_strs.push(Invocation::fmt_field("rights", rights)); - arg_strs.push(Invocation::fmt_field("attr", attr)); - (page, &cap_lookup[&page]) - } - InvocationArgs::CnodeCopy { - cnode, - dest_index, - dest_depth, - src_root, - src_obj, - src_depth, - rights, - } => { - arg_strs.push(Invocation::fmt_field("dest_index", dest_index)); - arg_strs.push(Invocation::fmt_field("dest_depth", dest_depth)); - arg_strs.push(Invocation::fmt_field_cap("src_root", src_root, cap_lookup)); - arg_strs.push(Invocation::fmt_field_cap("src_obj", src_obj, cap_lookup)); - arg_strs.push(Invocation::fmt_field("src_depth", src_depth)); - arg_strs.push(Invocation::fmt_field("rights", rights)); - (cnode, &cap_lookup[&cnode]) - } - InvocationArgs::CnodeMint { - cnode, - dest_index, - dest_depth, - src_root, - src_obj, - src_depth, - rights, - badge, - } => { - arg_strs.push(Invocation::fmt_field("dest_index", dest_index)); - arg_strs.push(Invocation::fmt_field("dest_depth", dest_depth)); - arg_strs.push(Invocation::fmt_field_cap("src_root", src_root, cap_lookup)); - arg_strs.push(Invocation::fmt_field_cap("src_obj", src_obj, cap_lookup)); - arg_strs.push(Invocation::fmt_field("src_depth", src_depth)); - arg_strs.push(Invocation::fmt_field("rights", rights)); - arg_strs.push(Invocation::fmt_field("badge", badge)); - (cnode, &cap_lookup[&cnode]) - } - InvocationArgs::SchedControlConfigureFlags { - sched_control, - sched_context, - budget, - period, - extra_refills, - badge, - flags, - } => { - arg_strs.push(Invocation::fmt_field_cap( - "schedcontext", - sched_context, - cap_lookup, - )); - arg_strs.push(Invocation::fmt_field("budget", budget)); - arg_strs.push(Invocation::fmt_field("period", period)); - arg_strs.push(Invocation::fmt_field("extra_refills", extra_refills)); - arg_strs.push(Invocation::fmt_field("badge", badge)); - arg_strs.push(Invocation::fmt_field("flags", flags)); - (sched_control, "None") - } - InvocationArgs::ArmVcpuSetTcb { vcpu, tcb } => { - arg_strs.push(Invocation::fmt_field_cap("tcb", tcb, cap_lookup)); - (vcpu, &cap_lookup[&vcpu]) - } - }; - _ = writeln!( - f, - "{:<20} - {:<17} - 0x{:016x} ({})\n{}", - self.object_type(), - self.method_name(), - service, - service_str, - arg_strs.join("\n") - ); - if let Some((count, _)) = self.repeat { - _ = writeln!(f, " REPEAT: count={count}"); +impl From for X86IoapicIrqTrigger { + fn from(item: u64) -> X86IoapicIrqTrigger { + match item { + 0 => X86IoapicIrqTrigger::Edge, + 1 => X86IoapicIrqTrigger::Level, + _ => panic!("Unknown x86 IOAPIC IRQ trigger {item:x}"), } } +} - fn object_type(&self) -> &'static str { - match self.label { - InvocationLabel::UntypedRetype => "Untyped", - InvocationLabel::TCBSetSchedParams - | InvocationLabel::TCBSetSpace - | InvocationLabel::TCBSetIPCBuffer - | InvocationLabel::TCBResume - | InvocationLabel::TCBWriteRegisters - | InvocationLabel::TCBBindNotification => "TCB", - InvocationLabel::ARMASIDPoolAssign | InvocationLabel::RISCVASIDPoolAssign => { - "ASID Pool" - } - InvocationLabel::ARMIRQIssueIRQHandlerTrigger - | InvocationLabel::RISCVIRQIssueIRQHandlerTrigger => "IRQ Control", - InvocationLabel::IRQSetIRQHandler => "IRQ Handler", - InvocationLabel::ARMPageTableMap | InvocationLabel::RISCVPageTableMap => "Page Table", - InvocationLabel::ARMPageMap | InvocationLabel::RISCVPageMap => "Page", - InvocationLabel::CNodeCopy | InvocationLabel::CNodeMint => "CNode", - InvocationLabel::SchedControlConfigureFlags => "SchedControl", - InvocationLabel::ARMVCPUSetTCB => "VCPU", - _ => panic!( - "Internal error: unexpected label when getting object type '{:?}'", - self.label - ), +impl X86IoapicIrqTrigger { + pub fn human_name(&self) -> &str { + match self { + X86IoapicIrqTrigger::Level => "level", + X86IoapicIrqTrigger::Edge => "edge", } } +} - fn method_name(&self) -> &'static str { - match self.label { - InvocationLabel::UntypedRetype => "Retype", - InvocationLabel::TCBSetSchedParams => "SetSchedParams", - InvocationLabel::TCBSetSpace => "SetSpace", - InvocationLabel::TCBSetIPCBuffer => "SetIPCBuffer", - InvocationLabel::TCBResume => "Resume", - InvocationLabel::TCBWriteRegisters => "WriteRegisters", - InvocationLabel::TCBBindNotification => "BindNotification", - InvocationLabel::ARMASIDPoolAssign | InvocationLabel::RISCVASIDPoolAssign => "Assign", - InvocationLabel::ARMIRQIssueIRQHandlerTrigger - | InvocationLabel::RISCVIRQIssueIRQHandlerTrigger => "Get", - InvocationLabel::IRQSetIRQHandler => "SetNotification", - InvocationLabel::ARMPageTableMap - | InvocationLabel::ARMPageMap - | InvocationLabel::RISCVPageTableMap - | InvocationLabel::RISCVPageMap => "Map", - InvocationLabel::CNodeCopy => "Copy", - InvocationLabel::CNodeMint => "Mint", - InvocationLabel::SchedControlConfigureFlags => "ConfigureFlags", - InvocationLabel::ARMVCPUSetTCB => "VCPUSetTcb", - _ => panic!( - "Internal error: unexpected label when getting method name '{:?}'", - self.label - ), - } - } +#[repr(u64)] +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum X86IoapicIrqPolarity { + LowTriggered = 0, + HighTriggered = 1, } -impl InvocationArgs { - fn to_label(&self, config: &Config) -> InvocationLabel { - match self { - InvocationArgs::UntypedRetype { .. } => InvocationLabel::UntypedRetype, - InvocationArgs::TcbSetSchedParams { .. } => InvocationLabel::TCBSetSchedParams, - InvocationArgs::TcbSetSpace { .. } => InvocationLabel::TCBSetSpace, - InvocationArgs::TcbSetIpcBuffer { .. } => InvocationLabel::TCBSetIPCBuffer, - InvocationArgs::TcbResume { .. } => InvocationLabel::TCBResume, - InvocationArgs::TcbWriteRegisters { .. } => InvocationLabel::TCBWriteRegisters, - InvocationArgs::TcbBindNotification { .. } => InvocationLabel::TCBBindNotification, - InvocationArgs::AsidPoolAssign { .. } => match config.arch { - Arch::Aarch64 => InvocationLabel::ARMASIDPoolAssign, - Arch::Riscv64 => InvocationLabel::RISCVASIDPoolAssign, - }, - InvocationArgs::IrqControlGetTrigger { .. } => match config.arch { - Arch::Aarch64 => InvocationLabel::ARMIRQIssueIRQHandlerTrigger, - Arch::Riscv64 => InvocationLabel::RISCVIRQIssueIRQHandlerTrigger, - }, - InvocationArgs::IrqHandlerSetNotification { .. } => InvocationLabel::IRQSetIRQHandler, - InvocationArgs::PageTableMap { .. } => match config.arch { - Arch::Aarch64 => InvocationLabel::ARMPageTableMap, - Arch::Riscv64 => InvocationLabel::RISCVPageTableMap, - }, - InvocationArgs::PageMap { .. } => match config.arch { - Arch::Aarch64 => InvocationLabel::ARMPageMap, - Arch::Riscv64 => InvocationLabel::RISCVPageMap, - }, - InvocationArgs::CnodeCopy { .. } => InvocationLabel::CNodeCopy, - InvocationArgs::CnodeMint { .. } => InvocationLabel::CNodeMint, - InvocationArgs::SchedControlConfigureFlags { .. } => { - InvocationLabel::SchedControlConfigureFlags - } - InvocationArgs::ArmVcpuSetTcb { .. } => InvocationLabel::ARMVCPUSetTCB, +impl From for X86IoapicIrqPolarity { + fn from(item: u64) -> X86IoapicIrqPolarity { + match item { + 0 => X86IoapicIrqPolarity::LowTriggered, + 1 => X86IoapicIrqPolarity::HighTriggered, + _ => panic!("Unknown x86 IOAPIC IRQ polarity {item:x}"), } } +} - fn get_args(self, config: &Config) -> (u64, Vec, Vec) { +impl X86IoapicIrqPolarity { + pub fn human_name(&self) -> &str { match self { - InvocationArgs::UntypedRetype { - untyped, - object_type, - size_bits, - root, - node_index, - node_depth, - node_offset, - num_objects, - } => ( - untyped, - vec![ - object_type.value(config), - size_bits, - node_index, - node_depth, - node_offset, - num_objects, - ], - vec![root], - ), - InvocationArgs::TcbSetSchedParams { - tcb, - authority, - mcp, - priority, - sched_context, - fault_ep, - } => ( - tcb, - vec![mcp, priority], - vec![authority, sched_context, fault_ep], - ), - InvocationArgs::TcbSetSpace { - tcb, - fault_ep, - cspace_root, - cspace_root_data, - vspace_root, - vspace_root_data, - } => ( - tcb, - vec![cspace_root_data, vspace_root_data], - vec![fault_ep, cspace_root, vspace_root], - ), - InvocationArgs::TcbSetIpcBuffer { - tcb, - buffer, - buffer_frame, - } => (tcb, vec![buffer], vec![buffer_frame]), - InvocationArgs::TcbResume { tcb } => (tcb, vec![], vec![]), - InvocationArgs::TcbWriteRegisters { - tcb, - resume, - arch_flags, - regs, - count, - } => { - // Here there are a couple of things going on. - // The invocation arguments to do not correspond one-to-one to word size, - // so we have to do some packing first. - // This means that the resume and arch_flags arguments need to be packed into - // a single word. We then add all the registers which are each the size of a word. - let resume_byte = if resume { 1 } else { 0 }; - let flags: u64 = ((arch_flags as u64) << 8) | resume_byte; - let mut args = vec![flags, count]; - let regs_values = regs.into_iter().map(|(_, value)| value); - args.extend(regs_values); - (tcb, args, vec![]) - } - InvocationArgs::TcbBindNotification { tcb, notification } => { - (tcb, vec![], vec![notification]) - } - InvocationArgs::AsidPoolAssign { asid_pool, vspace } => { - (asid_pool, vec![], vec![vspace]) - } - InvocationArgs::IrqControlGetTrigger { - irq_control, - irq, - trigger, - dest_root, - dest_index, - dest_depth, - } => ( - irq_control, - vec![irq, trigger as u64, dest_index, dest_depth], - vec![dest_root], - ), - InvocationArgs::IrqHandlerSetNotification { - irq_handler, - notification, - } => (irq_handler, vec![], vec![notification]), - InvocationArgs::PageTableMap { - page_table, - vspace, - vaddr, - attr, - } => (page_table, vec![vaddr, attr], vec![vspace]), - InvocationArgs::PageMap { - page, - vspace, - vaddr, - rights, - attr, - } => (page, vec![vaddr, rights, attr], vec![vspace]), - InvocationArgs::CnodeCopy { - cnode, - dest_index, - dest_depth, - src_root, - src_obj, - src_depth, - rights, - } => ( - cnode, - vec![dest_index, dest_depth, src_obj, src_depth, rights], - vec![src_root], - ), - InvocationArgs::CnodeMint { - cnode, - dest_index, - dest_depth, - src_root, - src_obj, - src_depth, - rights, - badge, - } => ( - cnode, - vec![dest_index, dest_depth, src_obj, src_depth, rights, badge], - vec![src_root], - ), - InvocationArgs::SchedControlConfigureFlags { - sched_control, - sched_context, - budget, - period, - extra_refills, - badge, - flags, - } => ( - sched_control, - vec![budget, period, extra_refills, badge, flags], - vec![sched_context], - ), - InvocationArgs::ArmVcpuSetTcb { vcpu, tcb } => (vcpu, vec![], vec![tcb]), + X86IoapicIrqPolarity::LowTriggered => "low-triggered", + X86IoapicIrqPolarity::HighTriggered => "high-triggered", } } } - -#[derive(Clone)] -#[allow(dead_code, clippy::large_enum_variant)] -pub enum InvocationArgs { - UntypedRetype { - untyped: u64, - object_type: ObjectType, - size_bits: u64, - root: u64, - node_index: u64, - node_depth: u64, - node_offset: u64, - num_objects: u64, - }, - TcbSetSchedParams { - tcb: u64, - authority: u64, - mcp: u64, - priority: u64, - sched_context: u64, - fault_ep: u64, - }, - TcbSetSpace { - tcb: u64, - fault_ep: u64, - cspace_root: u64, - cspace_root_data: u64, - vspace_root: u64, - vspace_root_data: u64, - }, - TcbSetIpcBuffer { - tcb: u64, - buffer: u64, - buffer_frame: u64, - }, - TcbResume { - tcb: u64, - }, - TcbWriteRegisters { - tcb: u64, - resume: bool, - arch_flags: u8, - count: u64, - regs: Vec<(&'static str, u64)>, - }, - TcbBindNotification { - tcb: u64, - notification: u64, - }, - AsidPoolAssign { - asid_pool: u64, - vspace: u64, - }, - IrqControlGetTrigger { - irq_control: u64, - irq: u64, - trigger: IrqTrigger, - dest_root: u64, - dest_index: u64, - dest_depth: u64, - }, - IrqHandlerSetNotification { - irq_handler: u64, - notification: u64, - }, - PageTableMap { - page_table: u64, - vspace: u64, - vaddr: u64, - attr: u64, - }, - PageMap { - page: u64, - vspace: u64, - vaddr: u64, - rights: u64, - attr: u64, - }, - CnodeCopy { - cnode: u64, - dest_index: u64, - dest_depth: u64, - src_root: u64, - src_obj: u64, - src_depth: u64, - rights: u64, - }, - CnodeMint { - cnode: u64, - dest_index: u64, - dest_depth: u64, - src_root: u64, - src_obj: u64, - src_depth: u64, - rights: u64, - badge: u64, - }, - SchedControlConfigureFlags { - sched_control: u64, - sched_context: u64, - budget: u64, - period: u64, - extra_refills: u64, - badge: u64, - flags: u64, - }, - ArmVcpuSetTcb { - vcpu: u64, - tcb: u64, - }, -} diff --git a/tool/microkit/src/symbols.rs b/tool/microkit/src/symbols.rs new file mode 100644 index 000000000..2febdd3c9 --- /dev/null +++ b/tool/microkit/src/symbols.rs @@ -0,0 +1,182 @@ +// +// Copyright 2025, UNSW +// +// SPDX-License-Identifier: BSD-2-Clause +// + +use std::{cmp::min, collections::HashMap}; + +use crate::{ + elf::ElfFile, + sdf::{self, SysMemoryRegion, SystemDescription}, + sel4::{Arch, Config}, + util::{monitor_serialise_names, monitor_serialise_u64_vec}, + MAX_PDS, MAX_VMS, PD_MAX_NAME_LENGTH, VM_MAX_NAME_LENGTH, +}; + +/// Patch all the required symbols in the Monitor and children PDs according to +/// the Microkit's requirements +pub fn patch_symbols( + kernel_config: &Config, + pd_elf_files: &mut [ElfFile], + monitor_elf: &mut ElfFile, + system: &SystemDescription, +) -> Result<(), String> { + // ********************************* + // Step 1. Write ELF symbols in the monitor. + // ********************************* + let pd_names: Vec = system + .protection_domains + .iter() + .map(|pd| pd.name.clone()) + .collect(); + monitor_elf + .write_symbol( + "pd_names_len", + &system.protection_domains.len().to_le_bytes(), + ) + .unwrap(); + monitor_elf + .write_symbol( + "pd_names", + &monitor_serialise_names(&pd_names, MAX_PDS, PD_MAX_NAME_LENGTH), + ) + .unwrap(); + + let vm_names: Vec = system + .protection_domains + .iter() + .filter(|pd| pd.virtual_machine.is_some()) + .flat_map(|pd_with_vm| { + let vm = pd_with_vm.virtual_machine.as_ref().unwrap(); + let num_vcpus = vm.vcpus.len(); + std::iter::repeat_n(vm.name.clone(), num_vcpus) + }) + .collect(); + + let vm_names_len = match kernel_config.arch { + Arch::Aarch64 | Arch::Riscv64 => vm_names.len(), + // VM on x86 doesn't have a separate TCB. + Arch::X86_64 => 0, + }; + monitor_elf + .write_symbol("vm_names_len", &vm_names_len.to_le_bytes()) + .unwrap(); + monitor_elf + .write_symbol( + "vm_names", + &monitor_serialise_names(&vm_names, MAX_VMS, VM_MAX_NAME_LENGTH), + ) + .unwrap(); + + let mut pd_stack_bottoms: Vec = Vec::new(); + for pd in system.protection_domains.iter() { + let cur_stack_vaddr = kernel_config.pd_stack_bottom(pd.stack_size); + pd_stack_bottoms.push(cur_stack_vaddr); + } + monitor_elf + .write_symbol( + "pd_stack_bottom_addrs", + &monitor_serialise_u64_vec(&pd_stack_bottoms), + ) + .unwrap(); + + // ********************************* + // Step 2. Write ELF symbols for each PD + // ********************************* + let mut mr_name_to_desc: HashMap<&String, &SysMemoryRegion> = HashMap::new(); + for mr in system.memory_regions.iter() { + mr_name_to_desc.insert(&mr.name, mr); + } + + for (pd_global_idx, pd) in system.protection_domains.iter().enumerate() { + let elf_obj = &mut pd_elf_files[pd_global_idx]; + + let name = pd.name.as_bytes(); + let name_length = min(name.len(), PD_MAX_NAME_LENGTH); + elf_obj + .write_symbol("microkit_name", &name[..name_length]) + .unwrap(); + elf_obj + .write_symbol("microkit_passive", &[pd.passive as u8]) + .unwrap(); + + let mut notification_bits: u64 = 0; + let mut pp_bits: u64 = 0; + for channel in system.channels.iter() { + if channel.end_a.pd == pd_global_idx { + if channel.end_a.notify { + notification_bits |= 1 << channel.end_a.id; + } + if channel.end_a.pp { + pp_bits |= 1 << channel.end_a.id; + } + } + if channel.end_b.pd == pd_global_idx { + if channel.end_b.notify { + notification_bits |= 1 << channel.end_b.id; + } + if channel.end_b.pp { + pp_bits |= 1 << channel.end_b.id; + } + } + } + elf_obj + .write_symbol("microkit_irqs", &pd.irq_bits().to_le_bytes()) + .unwrap(); + elf_obj + .write_symbol("microkit_notifications", ¬ification_bits.to_le_bytes()) + .unwrap(); + elf_obj + .write_symbol("microkit_pps", &pp_bits.to_le_bytes()) + .unwrap(); + elf_obj + .write_symbol("microkit_ioports", &pd.ioport_bits().to_le_bytes()) + .unwrap(); + + let mut symbols_to_write: Vec<(&String, u64)> = Vec::new(); + for setvar in pd.setvars.iter() { + // Check that the symbol exists in the ELF + match elf_obj.find_symbol(&setvar.symbol) { + Ok(sym_info) => { + // Sanity check that the symbol is of word size so we dont overwrite anything. + if sym_info.1 != (kernel_config.word_size / 8) { + return Err(format!( + "setvar to non word size symbol '{}', which is of size {} bytes", + setvar.symbol, sym_info.1 + )); + } + let data = match &setvar.kind { + sdf::SysSetVarKind::Size { mr } => mr_name_to_desc.get(mr).unwrap().size, + sdf::SysSetVarKind::Vaddr { address } => *address, + sdf::SysSetVarKind::Paddr { region } => { + match mr_name_to_desc.get(region).unwrap().phys_addr { + Some(specified_paddr) => specified_paddr, + None => { + if kernel_config.arch == Arch::X86_64 { + return Err( + "setvar with 'region_paddr' for MR without a specified paddr is unsupported on x86_64." + .to_string(), + ); + } else { + panic!("setvar with 'region_paddr' for MR without a specified paddr is currently unimplemented!"); + } + } + } + } + }; + symbols_to_write.push((&setvar.symbol, data)); + } + Err(err) => return Err(err), + } + } + let elf_obj = pd_elf_files.get_mut(pd_global_idx).unwrap(); + for (sym_name, value) in symbols_to_write.iter() { + elf_obj + .write_symbol(sym_name, &value.to_le_bytes()) + .unwrap(); + } + } + + Ok(()) +} diff --git a/tool/microkit/src/util.rs b/tool/microkit/src/util.rs index 219159e8e..07b9f7498 100644 --- a/tool/microkit/src/util.rs +++ b/tool/microkit/src/util.rs @@ -4,7 +4,8 @@ // SPDX-License-Identifier: BSD-2-Clause // -use crate::sel4::Object; +use std::ops::Range; + use serde_json; pub fn msb(x: u64) -> u64 { @@ -67,17 +68,9 @@ pub fn mask(n: u64) -> u64 { (1 << n) - 1 } -/// Check that all objects in the list are adjacent -pub fn objects_adjacent(objects: &[Object]) -> bool { - let mut prev_cap_addr = objects[0].cap_addr; - for obj in &objects[1..] { - if obj.cap_addr != prev_cap_addr + 1 { - return false; - } - prev_cap_addr = obj.cap_addr; - } - - true +/// Returns true if two ranges overlap. +pub fn ranges_overlap(left: &Range, right: &Range) -> bool { + left.start <= right.end && right.start <= left.end } /// Product a 'human readable' string for the size. @@ -85,7 +78,7 @@ pub fn objects_adjacent(objects: &[Object]) -> bool { /// 'strict' means that it must be simply represented. /// Specifically, it must be a multiple of standard power-of-two. /// (e.g. KiB, MiB, GiB, TiB, PiB, EiB) -pub fn human_size_strict(size: u64) -> (String, &'static str) { +pub fn human_size_strict(size: u64) -> String { for (bits, label) in [ (60, "EiB"), (50, "PiB"), @@ -102,12 +95,12 @@ pub fn human_size_strict(size: u64) -> (String, &'static str) { let (d_count, extra) = divmod(size, base); count = d_count; if extra != 0 { - return (format!("{:.2}", size as f64 / base as f64), label); + return format!("{:.2} {}", size as f64 / base as f64, label); } } else { count = size; } - return (comma_sep_u64(count), label); + return format!("{} {}", comma_sep_u64(count), label); } } @@ -119,7 +112,7 @@ pub fn human_size_strict(size: u64) -> (String, &'static str) { pub fn comma_sep_u64(n: u64) -> String { let mut s = String::new(); for (i, val) in n.to_string().chars().rev().enumerate() { - if i != 0 && i % 3 == 0 { + if i != 0 && i.is_multiple_of(3) { s.insert(0, ','); } s.insert(0, val); @@ -178,12 +171,11 @@ pub unsafe fn bytes_to_struct(bytes: &[u8]) -> &T { &body[0] } -/// Serialise an array of u64 to a Vector of bytes. Pads the Vector of bytes -/// such that the first entry is empty. +/// Serialise an array of u64 to a Vector of bytes. pub fn monitor_serialise_u64_vec(vec: &[u64]) -> Vec { let mut bytes = vec![0; (1 + vec.len()) * 8]; for (i, value) in vec.iter().enumerate() { - let start = (i + 1) * 8; + let start = i * 8; let end = start + 8; bytes[start..end].copy_from_slice(&value.to_le_bytes()); } @@ -191,19 +183,12 @@ pub fn monitor_serialise_u64_vec(vec: &[u64]) -> Vec { bytes } -/// For serialising an array of PD or VM names. Pads the Vector of bytes such that -/// the first entry is empty. -pub fn monitor_serialise_names( - names: Vec<&String>, - max_len: usize, - max_name_len: usize, -) -> Vec { +/// For serialising an array of PD or VM names +pub fn monitor_serialise_names(names: &[String], max_len: usize, max_name_len: usize) -> Vec { let mut names_bytes = vec![0; (max_len + 1) * max_name_len]; for (i, name) in names.iter().enumerate() { - // The monitor will index into the array of names based on the badge, which - // starts at 1 and hence we cannot use the 0th entry in the array. let name_bytes = name.as_bytes(); - let start = (i + 1) * max_name_len; + let start = i * max_name_len; // Here instead of giving an error we simply take the minimum of the name // and how large of a name we can encode. The name length is one less than // the maximum since we still have to add the null terminator. diff --git a/tool/microkit/tests/sdf/irq_arm_on_x86.system b/tool/microkit/tests/sdf/irq_arm_on_x86.system new file mode 100644 index 000000000..0a652fac8 --- /dev/null +++ b/tool/microkit/tests/sdf/irq_arm_on_x86.system @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/tool/microkit/tests/sdf/irq_id_less_than_0.system b/tool/microkit/tests/sdf/irq_id_less_than_0.system index 2ac61b00a..5019f5360 100644 --- a/tool/microkit/tests/sdf/irq_id_less_than_0.system +++ b/tool/microkit/tests/sdf/irq_id_less_than_0.system @@ -1,6 +1,6 @@ diff --git a/tool/microkit/tests/sdf/irq_ioapic_invalid_polarity.system b/tool/microkit/tests/sdf/irq_ioapic_invalid_polarity.system new file mode 100644 index 000000000..764b72b23 --- /dev/null +++ b/tool/microkit/tests/sdf/irq_ioapic_invalid_polarity.system @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/tool/microkit/tests/sdf/irq_ioapic_invalid_trigger.system b/tool/microkit/tests/sdf/irq_ioapic_invalid_trigger.system new file mode 100644 index 000000000..ca459f744 --- /dev/null +++ b/tool/microkit/tests/sdf/irq_ioapic_invalid_trigger.system @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/tool/microkit/tests/sdf/irq_ioapic_less_than_0.system b/tool/microkit/tests/sdf/irq_ioapic_less_than_0.system new file mode 100644 index 000000000..c7ad442ea --- /dev/null +++ b/tool/microkit/tests/sdf/irq_ioapic_less_than_0.system @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/tool/microkit/tests/sdf/irq_ioapic_on_arm.system b/tool/microkit/tests/sdf/irq_ioapic_on_arm.system new file mode 100644 index 000000000..5301f857b --- /dev/null +++ b/tool/microkit/tests/sdf/irq_ioapic_on_arm.system @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/tool/microkit/tests/sdf/irq_ioapic_pin_less_than_0.system b/tool/microkit/tests/sdf/irq_ioapic_pin_less_than_0.system new file mode 100644 index 000000000..431219cd7 --- /dev/null +++ b/tool/microkit/tests/sdf/irq_ioapic_pin_less_than_0.system @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/tool/microkit/tests/sdf/irq_ioapic_vector_less_than_0.system b/tool/microkit/tests/sdf/irq_ioapic_vector_less_than_0.system new file mode 100644 index 000000000..bf9a8ea7a --- /dev/null +++ b/tool/microkit/tests/sdf/irq_ioapic_vector_less_than_0.system @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/tool/microkit/tests/sdf/irq_msi_handle_less_than_0.system b/tool/microkit/tests/sdf/irq_msi_handle_less_than_0.system new file mode 100644 index 000000000..2a0f9fcd1 --- /dev/null +++ b/tool/microkit/tests/sdf/irq_msi_handle_less_than_0.system @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/tool/microkit/tests/sdf/irq_msi_on_arm.system b/tool/microkit/tests/sdf/irq_msi_on_arm.system new file mode 100644 index 000000000..683b06ff5 --- /dev/null +++ b/tool/microkit/tests/sdf/irq_msi_on_arm.system @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/tool/microkit/tests/sdf/irq_msi_pci_bus_less_than_0.system b/tool/microkit/tests/sdf/irq_msi_pci_bus_less_than_0.system new file mode 100644 index 000000000..fede69733 --- /dev/null +++ b/tool/microkit/tests/sdf/irq_msi_pci_bus_less_than_0.system @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/tool/microkit/tests/sdf/irq_msi_pci_dev_less_than_0.system b/tool/microkit/tests/sdf/irq_msi_pci_dev_less_than_0.system new file mode 100644 index 000000000..ba782e381 --- /dev/null +++ b/tool/microkit/tests/sdf/irq_msi_pci_dev_less_than_0.system @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/tool/microkit/tests/sdf/irq_msi_pci_func_less_than_0.system b/tool/microkit/tests/sdf/irq_msi_pci_func_less_than_0.system new file mode 100644 index 000000000..933a19cac --- /dev/null +++ b/tool/microkit/tests/sdf/irq_msi_pci_func_less_than_0.system @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/tool/microkit/tests/sdf/irq_msi_vector_less_than_0.system b/tool/microkit/tests/sdf/irq_msi_vector_less_than_0.system new file mode 100644 index 000000000..5f9f7a34a --- /dev/null +++ b/tool/microkit/tests/sdf/irq_msi_vector_less_than_0.system @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/tool/microkit/tests/sdf/pd_invalid_x86_io_port_size.system b/tool/microkit/tests/sdf/pd_invalid_x86_io_port_size.system new file mode 100644 index 000000000..7f3512346 --- /dev/null +++ b/tool/microkit/tests/sdf/pd_invalid_x86_io_port_size.system @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/tool/microkit/tests/sdf/pd_overlapping_maps.system b/tool/microkit/tests/sdf/pd_overlapping_maps.system index c499cce94..1cdb4df50 100644 --- a/tool/microkit/tests/sdf/pd_overlapping_maps.system +++ b/tool/microkit/tests/sdf/pd_overlapping_maps.system @@ -7,7 +7,7 @@ - + diff --git a/tool/microkit/tests/sdf/pd_overlapping_x86_io_ports_1.system b/tool/microkit/tests/sdf/pd_overlapping_x86_io_ports_1.system new file mode 100644 index 000000000..b05830d17 --- /dev/null +++ b/tool/microkit/tests/sdf/pd_overlapping_x86_io_ports_1.system @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/tool/microkit/tests/sdf/pd_overlapping_x86_io_ports_2.system b/tool/microkit/tests/sdf/pd_overlapping_x86_io_ports_2.system new file mode 100644 index 000000000..e9ae55788 --- /dev/null +++ b/tool/microkit/tests/sdf/pd_overlapping_x86_io_ports_2.system @@ -0,0 +1,17 @@ + + + + + + + + + + + + + diff --git a/tool/microkit/tests/sdf/vm_duplicate_name.system b/tool/microkit/tests/sdf/vm_duplicate_name.system index d7343cb59..7a7d660ac 100644 --- a/tool/microkit/tests/sdf/vm_duplicate_name.system +++ b/tool/microkit/tests/sdf/vm_duplicate_name.system @@ -14,7 +14,7 @@ - + diff --git a/tool/microkit/tests/sdf/vm_missing_mr.system b/tool/microkit/tests/sdf/vm_missing_mr.system index 75829a257..7d05e3fda 100644 --- a/tool/microkit/tests/sdf/vm_missing_mr.system +++ b/tool/microkit/tests/sdf/vm_missing_mr.system @@ -5,7 +5,7 @@ SPDX-License-Identifier: BSD-2-Clause --> - + diff --git a/tool/microkit/tests/sdf/vm_overlapping_maps.system b/tool/microkit/tests/sdf/vm_overlapping_maps.system index c42730011..12d265239 100644 --- a/tool/microkit/tests/sdf/vm_overlapping_maps.system +++ b/tool/microkit/tests/sdf/vm_overlapping_maps.system @@ -7,7 +7,7 @@ - + diff --git a/tool/microkit/tests/test.rs b/tool/microkit/tests/test.rs index 9cee8a131..d989d3a9b 100644 --- a/tool/microkit/tests/test.rs +++ b/tool/microkit/tests/test.rs @@ -4,10 +4,13 @@ // SPDX-License-Identifier: BSD-2-Clause // -use microkit_tool::{sdf, sel4}; +use microkit_tool::{ + sdf, + sel4::{self}, +}; use serde_json::json; -const DEFAULT_KERNEL_CONFIG: sel4::Config = sel4::Config { +const DEFAULT_AARCH64_KERNEL_CONFIG: sel4::Config = sel4::Config { arch: sel4::Arch::Aarch64, word_size: 64, minimum_page_size: 4096, @@ -15,6 +18,7 @@ const DEFAULT_KERNEL_CONFIG: sel4::Config = sel4::Config { kernel_frame_size: 1 << 12, init_cnode_bits: 12, cap_address_bits: 64, + max_num_bootinfo_untypeds: 230, fan_out_limit: 256, hypervisor: true, benchmark: false, @@ -22,18 +26,42 @@ const DEFAULT_KERNEL_CONFIG: sel4::Config = sel4::Config { arm_pa_size_bits: Some(40), arm_smc: None, riscv_pt_levels: None, + x86_xsave_size: None, // Not necessary for SDF parsing invocations_labels: json!(null), - device_regions: vec![], - normal_regions: vec![], + device_regions: None, + normal_regions: None, }; -fn check_error(test_name: &str, expected_err: &str) { +const DEFAULT_X86_64_KERNEL_CONFIG: sel4::Config = sel4::Config { + arch: sel4::Arch::X86_64, + word_size: 64, + minimum_page_size: 4096, + paddr_user_device_top: 1 << 40, + kernel_frame_size: 1 << 12, + init_cnode_bits: 12, + cap_address_bits: 64, + max_num_bootinfo_untypeds: 230, + fan_out_limit: 256, + hypervisor: true, + benchmark: false, + fpu: true, + arm_pa_size_bits: None, + arm_smc: None, + riscv_pt_levels: None, + x86_xsave_size: None, + // Not necessary for SDF parsing + invocations_labels: json!(null), + device_regions: None, + normal_regions: None, +}; + +fn check_error(kernel_config: &sel4::Config, test_name: &str, expected_err: &str) { let mut path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")); path.push("tests/sdf/"); path.push(test_name); let sdf = std::fs::read_to_string(path).unwrap(); - let parse_err = sdf::parse(test_name, &sdf, &DEFAULT_KERNEL_CONFIG).unwrap_err(); + let parse_err = sdf::parse(test_name, &sdf, kernel_config).unwrap_err(); if !parse_err.starts_with(expected_err) { eprintln!("Expected error:\n{expected_err}\nGot error:\n{parse_err}\n"); @@ -42,10 +70,10 @@ fn check_error(test_name: &str, expected_err: &str) { assert!(parse_err.starts_with(expected_err)); } -fn check_missing(test_name: &str, attr: &str, element: &str) { +fn check_missing(kernel_config: &sel4::Config, test_name: &str, attr: &str, element: &str) { let expected_error = format!("Error: Missing required attribute '{attr}' on element '{element}'"); - check_error(test_name, expected_error.as_str()); + check_error(kernel_config, test_name, expected_error.as_str()); } #[cfg(test)] @@ -54,12 +82,13 @@ mod memory_region { #[test] fn test_malformed_size() { - check_error("mr_malformed_size.system", "Error: failed to parse integer '0x200_000sd' on element 'memory_region': invalid digit found in string") + check_error(&DEFAULT_AARCH64_KERNEL_CONFIG, "mr_malformed_size.system", "Error: failed to parse integer '0x200_000sd' on element 'memory_region': invalid digit found in string") } #[test] fn test_unsupported_page_size() { check_error( + &DEFAULT_AARCH64_KERNEL_CONFIG, "mr_unsupported_page_size.system", "Error: page size 0x200001 not supported on element 'memory_region'", ) @@ -68,6 +97,7 @@ mod memory_region { #[test] fn test_size_not_multiple_of_page_size() { check_error( + &DEFAULT_AARCH64_KERNEL_CONFIG, "mr_size_not_multiple_of_page_size.system", "Error: size is not a multiple of the page size on element 'memory_region'", ) @@ -76,6 +106,7 @@ mod memory_region { #[test] fn test_addr_not_aligned_to_page_size() { check_error( + &DEFAULT_AARCH64_KERNEL_CONFIG, "mr_addr_not_aligned_to_page_size.system", "Error: phys_addr is not aligned to the page size on element 'memory_region'", ) @@ -83,17 +114,28 @@ mod memory_region { #[test] fn test_missing_size() { - check_missing("mr_missing_size.system", "size", "memory_region") + check_missing( + &DEFAULT_AARCH64_KERNEL_CONFIG, + "mr_missing_size.system", + "size", + "memory_region", + ) } #[test] fn test_missing_name() { - check_missing("mr_missing_name.system", "name", "memory_region") + check_missing( + &DEFAULT_AARCH64_KERNEL_CONFIG, + "mr_missing_name.system", + "name", + "memory_region", + ) } #[test] fn test_invalid_attrs() { check_error( + &DEFAULT_AARCH64_KERNEL_CONFIG, "mr_invalid_attrs.system", "Error: invalid attribute 'page_count' on element 'memory_region': ", ) @@ -101,7 +143,7 @@ mod memory_region { #[test] fn test_overlapping_phys_addr() { - check_error( + check_error(&DEFAULT_AARCH64_KERNEL_CONFIG, "mr_overlapping_phys_addr.system", "Error: memory region 'mr2' physical address range [0x9001000..0x9002000) overlaps with another memory region 'mr1' [0x9000000..0x9002000) @ ", ) @@ -114,12 +156,18 @@ mod protection_domain { #[test] fn test_missing_name() { - check_missing("pd_missing_name.system", "name", "protection_domain") + check_missing( + &DEFAULT_AARCH64_KERNEL_CONFIG, + "pd_missing_name.system", + "name", + "protection_domain", + ) } #[test] fn test_missing_program_image() { check_error( + &DEFAULT_AARCH64_KERNEL_CONFIG, "pd_missing_program_image.system", "Error: missing 'program_image' element on protection_domain: ", ) @@ -127,42 +175,78 @@ mod protection_domain { #[test] fn test_missing_path() { - check_missing("pd_missing_path.system", "path", "program_image") + check_missing( + &DEFAULT_AARCH64_KERNEL_CONFIG, + "pd_missing_path.system", + "path", + "program_image", + ) } #[test] fn test_missing_mr() { - check_missing("pd_missing_mr.system", "mr", "map") + check_missing( + &DEFAULT_AARCH64_KERNEL_CONFIG, + "pd_missing_mr.system", + "mr", + "map", + ) } #[test] fn test_missing_vaddr() { - check_missing("pd_missing_vaddr.system", "vaddr", "map") + check_missing( + &DEFAULT_AARCH64_KERNEL_CONFIG, + "pd_missing_vaddr.system", + "vaddr", + "map", + ) } #[test] - fn test_missing_irq() { - check_missing("pd_missing_irq.system", "irq", "irq") + fn test_missing_irq_arm() { + check_missing( + &DEFAULT_AARCH64_KERNEL_CONFIG, + "pd_missing_irq.system", + "irq", + "irq", + ); } #[test] fn test_missing_id() { - check_missing("pd_missing_id.system", "id", "irq") + check_missing( + &DEFAULT_AARCH64_KERNEL_CONFIG, + "pd_missing_id.system", + "id", + "irq", + ) } #[test] fn test_missing_symbol() { - check_missing("pd_missing_symbol.system", "symbol", "setvar") + check_missing( + &DEFAULT_AARCH64_KERNEL_CONFIG, + "pd_missing_symbol.system", + "symbol", + "setvar", + ) } #[test] fn test_missing_region_paddr() { - check_missing("pd_missing_region_paddr.system", "region_paddr", "setvar") + check_missing( + &DEFAULT_AARCH64_KERNEL_CONFIG, + "pd_missing_region_paddr.system", + "region_paddr", + "setvar", + ) } #[test] fn test_duplicate_setvar() { check_error( + &DEFAULT_AARCH64_KERNEL_CONFIG, "pd_duplicate_setvar.system", "Error: setvar on symbol 'test' already exists on element 'setvar': ", ) @@ -171,6 +255,7 @@ mod protection_domain { #[test] fn test_duplicate_program_image() { check_error( + &DEFAULT_AARCH64_KERNEL_CONFIG, "pd_duplicate_program_image.system", "Error: program_image must only be specified once on element 'protection_domain': ", ) @@ -179,6 +264,7 @@ mod protection_domain { #[test] fn test_invalid_attrs() { check_error( + &DEFAULT_AARCH64_KERNEL_CONFIG, "pd_invalid_attrs.system", "Error: invalid attribute 'foo' on element 'protection_domain': ", ) @@ -187,6 +273,7 @@ mod protection_domain { #[test] fn test_program_image_invalid_attrs() { check_error( + &DEFAULT_AARCH64_KERNEL_CONFIG, "pd_program_image_invalid_attrs.system", "Error: invalid attribute 'foo' on element 'program_image': ", ) @@ -194,12 +281,13 @@ mod protection_domain { #[test] fn test_budget_gt_period() { - check_error("pd_budget_gt_period.system", "Error: budget (1000) must be less than, or equal to, period (100) on element 'protection_domain':") + check_error(&DEFAULT_AARCH64_KERNEL_CONFIG, "pd_budget_gt_period.system", "Error: budget (1000) must be less than, or equal to, period (100) on element 'protection_domain':") } #[test] fn test_irq_greater_than_max() { check_error( + &DEFAULT_AARCH64_KERNEL_CONFIG, "irq_id_greater_than_max.system", "Error: id must be < 62 on element 'irq'", ) @@ -208,6 +296,7 @@ mod protection_domain { #[test] fn test_irq_less_than_0() { check_error( + &DEFAULT_AARCH64_KERNEL_CONFIG, "irq_id_less_than_0.system", "Error: id must be >= 0 on element 'irq'", ) @@ -216,6 +305,7 @@ mod protection_domain { #[test] fn test_write_only_mr() { check_error( + &DEFAULT_AARCH64_KERNEL_CONFIG, "pd_write_only_mr.system", "Error: perms must not be 'w', write-only mappings are not allowed on element 'map':", ) @@ -224,14 +314,133 @@ mod protection_domain { #[test] fn test_irq_invalid_trigger() { check_error( + &DEFAULT_AARCH64_KERNEL_CONFIG, "irq_invalid_trigger.system", "Error: trigger must be either 'level' or 'edge' on element 'irq'", ) } + #[test] + fn test_irq_ioapic_on_arm() { + check_error( + &DEFAULT_AARCH64_KERNEL_CONFIG, + "irq_ioapic_on_arm.system", + "Error: x86 I/O APIC IRQ isn't supported on ARM and RISC-V on element 'irq'", + ) + } + + #[test] + fn test_irq_msi_on_arm() { + check_error( + &DEFAULT_AARCH64_KERNEL_CONFIG, + "irq_msi_on_arm.system", + "Error: x86 MSI IRQ isn't supported on ARM and RISC-V on element 'irq'", + ) + } + + #[test] + fn test_irq_arm_on_x86() { + check_error( + &DEFAULT_X86_64_KERNEL_CONFIG, + "irq_arm_on_x86.system", + "Error: ARM and RISC-V IRQs are not supported on x86 on element 'irq'", + ) + } + + #[test] + fn test_irq_ioapic_less_than_0() { + check_error( + &DEFAULT_X86_64_KERNEL_CONFIG, + "irq_ioapic_less_than_0.system", + "Error: ioapic must be >= 0 on element 'irq'", + ) + } + + #[test] + fn test_irq_ioapic_pin_less_than_0() { + check_error( + &DEFAULT_X86_64_KERNEL_CONFIG, + "irq_ioapic_pin_less_than_0.system", + "Error: pin must be >= 0 on element 'irq'", + ) + } + + #[test] + fn test_irq_ioapic_invalid_trigger() { + check_error( + &DEFAULT_X86_64_KERNEL_CONFIG, + "irq_ioapic_invalid_trigger.system", + "Error: trigger must be either 'level' or 'edge' on element 'irq'", + ) + } + + #[test] + fn test_irq_ioapic_invalid_polarity() { + check_error( + &DEFAULT_X86_64_KERNEL_CONFIG, + "irq_ioapic_invalid_polarity.system", + "Error: polarity must be either 'low' or 'high' on element 'irq'", + ) + } + + #[test] + fn test_irq_ioapic_vector_less_than_0() { + check_error( + &DEFAULT_X86_64_KERNEL_CONFIG, + "irq_ioapic_vector_less_than_0.system", + "Error: vector must be >= 0 on element 'irq'", + ) + } + + #[test] + fn test_irq_msi_pci_bus_less_than_0() { + check_error( + &DEFAULT_X86_64_KERNEL_CONFIG, + "irq_msi_pci_bus_less_than_0.system", + "Error: PCI bus must be >= 0 on element 'irq'", + ) + } + + #[test] + fn test_irq_msi_pci_dev_less_than_0() { + check_error( + &DEFAULT_X86_64_KERNEL_CONFIG, + "irq_msi_pci_dev_less_than_0.system", + "Error: PCI device must be >= 0 on element 'irq'", + ) + } + + #[test] + fn test_irq_msi_pci_func_less_than_0() { + check_error( + &DEFAULT_X86_64_KERNEL_CONFIG, + "irq_msi_pci_func_less_than_0.system", + "Error: PCI function must be >= 0 on element 'irq'", + ) + } + + #[test] + fn test_irq_msi_handle_less_than_0() { + check_error( + &DEFAULT_X86_64_KERNEL_CONFIG, + "irq_msi_handle_less_than_0.system", + "Error: handle must be >= 0 on element 'irq'", + ) + } + + #[test] + fn test_irq_msi_vector_less_than_0() { + check_error( + &DEFAULT_X86_64_KERNEL_CONFIG, + "irq_msi_vector_less_than_0.system", + "Error: vector must be >= 0 on element 'irq'", + ) + } + #[test] fn test_parent_has_id() { check_error( + &DEFAULT_AARCH64_KERNEL_CONFIG, "pd_parent_has_id.system", "Error: invalid attribute 'id' on element 'protection_domain': ", ) @@ -239,12 +448,18 @@ mod protection_domain { #[test] fn test_child_missing_id() { - check_missing("pd_child_missing_id.system", "id", "protection_domain") + check_missing( + &DEFAULT_AARCH64_KERNEL_CONFIG, + "pd_child_missing_id.system", + "id", + "protection_domain", + ) } #[test] fn test_duplicate_child_id() { check_error( + &DEFAULT_AARCH64_KERNEL_CONFIG, "pd_duplicate_child_id.system", "Error: duplicate id: 0 in protection domain: 'parent' @", ) @@ -252,7 +467,7 @@ mod protection_domain { #[test] fn test_duplicate_child_id_vcpu() { - check_error( + check_error(&DEFAULT_AARCH64_KERNEL_CONFIG, "pd_duplicate_child_id_vcpu.system", "Error: duplicate id: 0 clashes with virtual machine vcpu id in protection domain: 'parent' @", ) @@ -261,6 +476,7 @@ mod protection_domain { #[test] fn test_small_stack_size() { check_error( + &DEFAULT_AARCH64_KERNEL_CONFIG, "pd_small_stack_size.system", "Error: stack size must be between", ) @@ -269,6 +485,7 @@ mod protection_domain { #[test] fn test_unaligned_stack_size() { check_error( + &DEFAULT_AARCH64_KERNEL_CONFIG, "pd_unaligned_stack_size.system", "Error: stack size must be aligned to the smallest page size", ) @@ -276,11 +493,36 @@ mod protection_domain { #[test] fn test_overlapping_maps() { - check_error( + check_error(&DEFAULT_AARCH64_KERNEL_CONFIG, "pd_overlapping_maps.system", "Error: map for 'mr2' has virtual address range [0x1000000..0x1001000) which overlaps with map for 'mr1' [0x1000000..0x1001000) in protection domain 'hello' @" ) } + + #[test] + fn test_overlapping_x86_io_ports_1() { + check_error(&DEFAULT_X86_64_KERNEL_CONFIG, + "pd_overlapping_x86_io_ports_1.system", + "Error: I/O port id: 1, inclusive range: [0x3ff, 0x406] in protection domain: 'test1' @ pd_overlapping_x86_io_ports_1.system:8:5 overlaps with I/O port id: 0, inclusive range: [0x3f8, 0x3ff] in protection domain: 'test1' @ pd_overlapping_x86_io_ports_1.system:8:5" + ) + } + + #[test] + fn test_overlapping_x86_io_ports_2() { + check_error(&DEFAULT_X86_64_KERNEL_CONFIG, + "pd_overlapping_x86_io_ports_2.system", + "Error: I/O port id: 0, inclusive range: [0x3ff, 0x406] in protection domain: 'test2' @ pd_overlapping_x86_io_ports_2.system:13:5 overlaps with I/O port id: 0, inclusive range: [0x3f8, 0x3ff] in protection domain: 'test1' @ pd_overlapping_x86_io_ports_2.system:8:5" + ) + } + + #[test] + fn test_invalid_x86_io_port_size() { + check_error( + &DEFAULT_X86_64_KERNEL_CONFIG, + "pd_invalid_x86_io_port_size.system", + "Error: size must be > 0 on element 'ioport':", + ) + } } #[cfg(test)] @@ -290,6 +532,7 @@ mod virtual_machine { #[test] fn test_vm_not_child() { check_error( + &DEFAULT_AARCH64_KERNEL_CONFIG, "vm_not_child.system", "Error: virtual machine must be a child of a protection domain", ) @@ -298,6 +541,7 @@ mod virtual_machine { #[test] fn test_duplicate_name() { check_error( + &DEFAULT_AARCH64_KERNEL_CONFIG, "vm_duplicate_name.system", "Error: duplicate virtual machine name 'guest'", ) @@ -306,6 +550,7 @@ mod virtual_machine { #[test] fn test_missing_vcpu() { check_error( + &DEFAULT_AARCH64_KERNEL_CONFIG, "vm_missing_vcpu.system", "Error: missing 'vcpu' element on virtual_machine: ", ) @@ -313,12 +558,18 @@ mod virtual_machine { #[test] fn test_missing_vcpu_id() { - check_missing("vm_missing_vcpu_id.system", "id", "vcpu") + check_missing( + &DEFAULT_AARCH64_KERNEL_CONFIG, + "vm_missing_vcpu_id.system", + "id", + "vcpu", + ) } #[test] fn test_invalid_vcpu_id() { check_error( + &DEFAULT_AARCH64_KERNEL_CONFIG, "vm_invalid_vcpu_id.system", "Error: id must be < 62 on element 'vcpu'", ) @@ -326,7 +577,7 @@ mod virtual_machine { #[test] fn test_overlapping_maps() { - check_error( + check_error(&DEFAULT_AARCH64_KERNEL_CONFIG, "vm_overlapping_maps.system", "Error: map for 'mr2' has virtual address range [0x1000000..0x1001000) which overlaps with map for 'mr1' [0x1000000..0x1001000) in virtual machine 'guest' @" ) @@ -335,6 +586,7 @@ mod virtual_machine { #[test] fn test_missing_mr() { check_error( + &DEFAULT_AARCH64_KERNEL_CONFIG, "vm_missing_mr.system", "Error: invalid memory region name 'mr1' on 'map' @", ) @@ -347,12 +599,18 @@ mod channel { #[test] fn test_missing_id() { - check_missing("ch_missing_id.system", "id", "end") + check_missing( + &DEFAULT_AARCH64_KERNEL_CONFIG, + "ch_missing_id.system", + "id", + "end", + ) } #[test] fn test_id_greater_than_max() { check_error( + &DEFAULT_AARCH64_KERNEL_CONFIG, "ch_id_greater_than_max.system", "Error: id must be < 62 on element 'end'", ) @@ -361,6 +619,7 @@ mod channel { #[test] fn test_id_less_than_0() { check_error( + &DEFAULT_AARCH64_KERNEL_CONFIG, "ch_id_less_than_0.system", "Error: id must be >= 0 on element 'end'", ) @@ -369,6 +628,7 @@ mod channel { #[test] fn test_invalid_attrs() { check_error( + &DEFAULT_AARCH64_KERNEL_CONFIG, "ch_invalid_attrs.system", "Error: invalid attribute 'foo' on element 'channel': ", ) @@ -377,6 +637,7 @@ mod channel { #[test] fn test_channel_invalid_pd() { check_error( + &DEFAULT_AARCH64_KERNEL_CONFIG, "ch_invalid_pd.system", "Error: invalid PD name 'invalidpd' on element 'end': ", ) @@ -385,6 +646,7 @@ mod channel { #[test] fn test_invalid_element() { check_error( + &DEFAULT_AARCH64_KERNEL_CONFIG, "ch_invalid_element.system", "Error: invalid XML element 'ending': ", ) @@ -393,6 +655,7 @@ mod channel { #[test] fn test_not_enough_ends() { check_error( + &DEFAULT_AARCH64_KERNEL_CONFIG, "ch_not_enough_ends.system", "Error: exactly two end elements must be specified on element 'channel': ", ) @@ -401,6 +664,7 @@ mod channel { #[test] fn test_too_many_ends() { check_error( + &DEFAULT_AARCH64_KERNEL_CONFIG, "ch_too_many_ends.system", "Error: exactly two end elements must be specified on element 'channel': ", ) @@ -409,6 +673,7 @@ mod channel { #[test] fn test_end_invalid_pp() { check_error( + &DEFAULT_AARCH64_KERNEL_CONFIG, "ch_end_invalid_pp.system", "Error: pp must be 'true' or 'false' on element 'end': ", ) @@ -417,6 +682,7 @@ mod channel { #[test] fn test_end_invalid_notify() { check_error( + &DEFAULT_AARCH64_KERNEL_CONFIG, "ch_end_invalid_notify.system", "Error: notify must be 'true' or 'false' on element 'end': ", ) @@ -425,6 +691,7 @@ mod channel { #[test] fn test_bidirectional_ppc() { check_error( + &DEFAULT_AARCH64_KERNEL_CONFIG, "ch_bidirectional_ppc.system", "Error: cannot ppc bidirectionally on element 'channel': ", ) @@ -432,7 +699,7 @@ mod channel { #[test] fn test_ppcall_priority() { - check_error( + check_error(&DEFAULT_AARCH64_KERNEL_CONFIG, "ch_ppcall_priority.system", "Error: PPCs must be to protection domains of strictly higher priorities; channel with PPC exists from pd test1 (priority: 2) to pd test2 (priority: 1)", ) @@ -446,6 +713,7 @@ mod system { #[test] fn test_duplicate_pd_names() { check_error( + &DEFAULT_AARCH64_KERNEL_CONFIG, "sys_duplicate_pd_name.system", "Error: duplicate protection domain name 'test'.", ) @@ -454,6 +722,7 @@ mod system { #[test] fn test_duplicate_mr_names() { check_error( + &DEFAULT_AARCH64_KERNEL_CONFIG, "sys_duplicate_mr_name.system", "Error: duplicate memory region name 'test'.", ) @@ -462,6 +731,7 @@ mod system { #[test] fn test_duplicate_irq_number() { check_error( + &DEFAULT_AARCH64_KERNEL_CONFIG, "sys_duplicate_irq_number.system", "Error: duplicate irq: 112 in protection domain: 'test2' @ ", ) @@ -470,6 +740,7 @@ mod system { #[test] fn test_duplicate_irq_id() { check_error( + &DEFAULT_AARCH64_KERNEL_CONFIG, "sys_duplicate_irq_id.system", "Error: duplicate channel id: 3 in protection domain: 'test1' @", ) @@ -478,6 +749,7 @@ mod system { #[test] fn test_channel_duplicate_a_id() { check_error( + &DEFAULT_AARCH64_KERNEL_CONFIG, "sys_channel_duplicate_a_id.system", "Error: duplicate channel id: 5 in protection domain: 'test1' @", ) @@ -486,6 +758,7 @@ mod system { #[test] fn test_channel_duplicate_b_id() { check_error( + &DEFAULT_AARCH64_KERNEL_CONFIG, "sys_channel_duplicate_b_id.system", "Error: duplicate channel id: 5 in protection domain: 'test2' @", ) @@ -494,6 +767,7 @@ mod system { #[test] fn test_no_protection_domains() { check_error( + &DEFAULT_AARCH64_KERNEL_CONFIG, "sys_no_protection_domains.system", "Error: at least one protection domain must be defined", ) @@ -502,6 +776,7 @@ mod system { #[test] fn test_text_elements() { check_error( + &DEFAULT_AARCH64_KERNEL_CONFIG, "sys_text_elements.system", "Error: unexpected text found in element 'system' @", ) @@ -510,6 +785,7 @@ mod system { #[test] fn test_map_invalid_mr() { check_error( + &DEFAULT_AARCH64_KERNEL_CONFIG, "sys_map_invalid_mr.system", "Error: invalid memory region name 'foos' on 'map' @ ", ) @@ -518,6 +794,7 @@ mod system { #[test] fn test_map_not_aligned() { check_error( + &DEFAULT_AARCH64_KERNEL_CONFIG, "sys_map_not_aligned.system", "Error: invalid vaddr alignment on 'map' @ ", ) @@ -526,6 +803,7 @@ mod system { #[test] fn test_map_too_high() { check_error( + &DEFAULT_AARCH64_KERNEL_CONFIG, "sys_map_too_high.system", "Error: vaddr (0x1000000000000000) must be less than 0xffffffe000 on element 'map'", ) @@ -534,6 +812,7 @@ mod system { #[test] fn test_too_many_pds() { check_error( + &DEFAULT_AARCH64_KERNEL_CONFIG, "sys_too_many_pds.system", "Error: too many protection domains (64) defined. Maximum is 63.", )