├── .buildkite ├── build_resources.py └── custom-tests.json ├── .cargo └── config.toml ├── .github └── dependabot.yml ├── .gitignore ├── .gitmodules ├── CODEOWNERS ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-BSD-3-CLAUSE ├── README.md ├── coverage_config_x86_64.json ├── docs ├── DESIGN.md ├── getting-started.md ├── overview.png └── testing.png ├── resources ├── CONTAINER_VERSION ├── disk │ ├── install_system.sh │ └── make_rootfs.sh ├── kernel │ ├── busybox_1_32_1_static_config │ ├── configs.sh │ ├── make_busybox.sh │ ├── make_kernel.sh │ ├── make_kernel_busybox_image.sh │ ├── make_kernel_image_deb.sh │ ├── microvm-kernel-5.4-aarch64.config │ ├── microvm-kernel-5.4-x86_64.config │ ├── microvm-kernel-initramfs-hello-aarch64.config │ └── microvm-kernel-initramfs-hello-x86_64.config └── make_test_resources.sh ├── src ├── api │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── arch │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── devices │ ├── Cargo.toml │ └── src │ │ ├── legacy │ │ ├── i8042.rs │ │ ├── mod.rs │ │ ├── rtc.rs │ │ └── serial.rs │ │ ├── lib.rs │ │ └── virtio │ │ ├── block │ │ ├── device.rs │ │ ├── inorder_handler.rs │ │ ├── mod.rs │ │ └── queue_handler.rs │ │ ├── mod.rs │ │ └── net │ │ ├── bindings.rs │ │ ├── device.rs │ │ ├── mod.rs │ │ ├── queue_handler.rs │ │ ├── simple_handler.rs │ │ └── tap.rs ├── main.rs ├── utils │ ├── Cargo.toml │ └── src │ │ ├── lib.rs │ │ └── resource_download.rs ├── vm-vcpu-ref │ ├── .buildkite │ │ └── custom-tests.json │ ├── Cargo.toml │ ├── README.md │ ├── coverage_config_x86_64.json │ ├── rust-vmm-ci │ └── src │ │ ├── aarch64 │ │ ├── interrupts.rs │ │ ├── mod.rs │ │ └── regs │ │ │ ├── dist.rs │ │ │ ├── icc.rs │ │ │ ├── mod.rs │ │ │ └── redist.rs │ │ ├── lib.rs │ │ └── x86_64 │ │ ├── cpuid.rs │ │ ├── gdt.rs │ │ ├── interrupts.rs │ │ ├── mod.rs │ │ ├── mpspec.rs │ │ ├── mptable.rs │ │ ├── msr_index.rs │ │ └── msrs.rs ├── vm-vcpu │ ├── Cargo.toml │ └── src │ │ ├── lib.rs │ │ ├── vcpu │ │ ├── mod.rs │ │ └── regs.rs │ │ └── vm.rs └── vmm │ ├── Cargo.toml │ ├── src │ ├── boot.rs │ ├── config │ │ ├── arg_parser.rs │ │ ├── builder.rs │ │ └── mod.rs │ ├── irq_allocator.rs │ └── lib.rs │ └── tests │ └── integration_tests.rs └── tests ├── test_build_deps.py ├── test_run_reference_vmm.py └── tools ├── s3 ├── __init__.py ├── resource_manifest.json └── utils.py └── s3_download.py /.buildkite/build_resources.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause 5 | import os 6 | import sys 7 | import yaml 8 | import http.client 9 | import json 10 | 11 | pipeline = { 12 | 'steps': [ 13 | { 14 | 'label': 'trigger-build-resources', 15 | 'trigger': 'vmm-reference-build-resources', 16 | 'skip': 'No changes in resources or not a Pull Request', 17 | 'build': { 18 | 'message': '"${BUILDKITE_MESSAGE}"', 19 | 'commit': '"${BUILDKITE_COMMIT}"', 20 | 'branch': '"${BUILDKITE_BRANCH}"' 21 | }, 22 | } 23 | ] 24 | } 25 | 26 | pull_request = os.environ.get('BUILDKITE_PULL_REQUEST') 27 | # the isnumeric() check is needed because the value 28 | # will be 'false' if it's not a pull requests. See 29 | # https://buildkite.com/docs/pipelines/environment-variables#bk-env-vars-buildkite-pull-request 30 | if pull_request and pull_request.isnumeric(): 31 | try: 32 | conn = http.client.HTTPSConnection("api.github.com", timeout=2) 33 | path = f"/repos/rust-vmm/vmm-reference/pulls/{pull_request}/files" 34 | conn.request('GET', path, headers={'user-agent': 'py.http/3'}) 35 | content = conn.getresponse().read().decode('utf-8') 36 | for v in json.loads(content): 37 | if v['filename'].startswith('resources/'): 38 | pipeline['steps'][0]['skip'] = False 39 | # We are doing this to override variable to point to the branch/PR 40 | # from where the changes were pushed. We can't do that directly because 41 | # yaml.dump() does not escape the ' " ' (quotes) properly from the env variable. 42 | pipeline['steps'][0]['build']['message'] = os.environ.get('BUILDKITE_MESSAGE') 43 | pipeline['steps'][0]['build']['commit'] = os.environ.get('BUILDKITE_COMMIT') 44 | pipeline['steps'][0]['build']['branch'] = os.environ.get('BUILDKITE_BRANCH') 45 | except Exception as e: 46 | print(e, file=sys.stderr) 47 | 48 | yaml.dump(pipeline, sys.stdout, sort_keys=False) 49 | -------------------------------------------------------------------------------- /.buildkite/custom-tests.json: -------------------------------------------------------------------------------- 1 | { 2 | "tests": [ 3 | { 4 | "test_name": "run", 5 | "command": "pytest tests/test_run_reference_vmm.py", 6 | "platform": [ 7 | "x86_64", 8 | "aarch64" 9 | ] 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | # This workaround is needed because the linker is unable to find __addtf3, 2 | # __multf3 and __subtf3. 3 | # Related issue: https://github.com/rust-lang/compiler-builtins/issues/201 4 | [target.aarch64-unknown-linux-musl] 5 | rustflags = [ "-C", "target-feature=+crt-static", "-C", "link-arg=-lgcc"] 6 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: gitsubmodule 4 | directory: "/" 5 | schedule: 6 | interval: weekly 7 | open-pull-requests-limit: 10 8 | 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | vmlinux-* 3 | bzimage-* 4 | *.ext4 5 | __pycache__ 6 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "rust-vmm-ci"] 2 | path = rust-vmm-ci 3 | url = https://github.com/rust-vmm/rust-vmm-ci.git 4 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Add the list of code owners here (using their GitHub username) 2 | * @alexandruag @andreeaflorescu @gsserge @lauralt 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vmm-reference" 3 | version = "0.1.0" 4 | authors = ["rust-vmm AWS maintainers "] 5 | edition = "2018" 6 | license = "Apache-2.0 OR BSD-3-Clause" 7 | 8 | [dependencies] 9 | vmm = { path = "src/vmm" } 10 | api = { path = "src/api" } 11 | 12 | [workspace] 13 | members = ["src/vm-vcpu-ref"] 14 | 15 | [profile.dev] 16 | panic = "abort" 17 | 18 | [profile.release] 19 | codegen-units = 1 20 | lto = true 21 | panic = "abort" 22 | 23 | [patch.crates-io] 24 | # TODO: Using this patch until a version > 4.0 gets published. 25 | linux-loader = { git = "https://github.com/rust-vmm/linux-loader.git", rev = "9a9f071" } 26 | -------------------------------------------------------------------------------- /LICENSE-BSD-3-CLAUSE: -------------------------------------------------------------------------------- 1 | Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, 7 | this list of conditions and the following disclaimer. 8 | 9 | 2. Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 13 | 3. Neither the name of the copyright holder nor the names of its contributors 14 | may be used to endorse or promote products derived from this software without 15 | specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 21 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 23 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 24 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 25 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `vmm-reference` 2 | 3 | :exclamation: `vmm-reference` is for experimental purposes and should *NOT* be 4 | used in production. :exclamation: 5 | 6 | ## Design 7 | 8 | The purpose of the reference VMM is twofold: 9 | 10 | 1. To validate the `rust-vmm` crates that compose it and demonstrate their 11 | functionality in a use-case-agnostic, end-to-end VMM. 12 | 1. To serve as a starting point in the creation of tailor-made VMMs that users 13 | build according to their needs. Users can fork the reference VMM, mix and 14 | match its components and UI to create a functional VMM with a minimal attack 15 | surface and resource footprint, custom-made to suit their isolation 16 | requirements. 17 | 18 | The reference VMM consists of `rust-vmm` crates and minimal glue code that 19 | sticks them together. The end result is a binary, roughly split between a 20 | simple CLI and a `vmm` crate, which ingests all the available `rust-vmm` 21 | building blocks compiled with all their available features. As crate 22 | development progresses, in the future, we may have feature `X` in crate `A` 23 | mutually incompatible with feature `Y` in crate `B` - therefore the reference 24 | VMM, which depends on both crates `A` and `B`, will no longer support features 25 | `X` and `Y` simultaneously. If and when this situation occurs, multiple 26 | binaries for the reference VMM will be supplied. 27 | 28 | The `vmm` crate allows for pluggable UIs via a `VMMConfig` structure. A 29 | basic command line parser demonstrates how a frontend can be stitched to the 30 | VMM. 31 | 32 | For more details, see [`DESIGN.md`](docs/DESIGN.md). 33 | 34 | ## Usage 35 | 36 | The reference VMM can be used out of the box as a `hello-world` example of a 37 | fully functional VMM built with `rust-vmm` crates. 38 | 39 | To start a basic VM with one vCPU and 256 MiB memory, you can use the following 40 | command: 41 | 42 | ```bash 43 | vmm-reference \ 44 | --kernel path=/path/to/vmlinux \ 45 | [--block - TBD] 46 | [--net - TBD] 47 | ``` 48 | 49 | The default configuration can be updated through the 50 | [command line](#cli-reference). 51 | 52 | The crate's [`Cargo.toml`](Cargo.toml) controls which VMM functionalities are 53 | available. By default, all rust-vmm crates are listed as dependencies and 54 | therefore included. Users can play freely with the building blocks by modifying 55 | the TOML, and the prepackaged CLI can quickly validate the altered 56 | configurations. Advanced users can, of course, plug in their own front-end. 57 | 58 | ## CLI reference 59 | 60 | * `memory` - guest memory configurations 61 | * `size_mib` - `u32`, guest memory size in MiB (decimal) 62 | * default: 256 MiB 63 | * `kernel` - guest kernel configurations 64 | * `path` - `String`, path to the guest kernel image 65 | * `cmdline` - `String`, kernel command line 66 | * default: "console=ttyS0 i8042.nokbd reboot=t panic=1 pci=off" 67 | * `kernel_load_addr` - `u64`, start address for high memory (decimal) 68 | * default: 0x100000 69 | * `vcpus` - vCPU configurations 70 | * `num` - `u8`, number of vCPUs (decimal) 71 | * default: 1 72 | * `block` - block device configuration 73 | * `path` - `String`, path to the root filesystem 74 | * `net` - network device configuration 75 | * `tap` - `String`, tap name, only the API support is added for now, 76 | an actual network device configuration is done in the 77 | [following PR under review](https://github.com/rust-vmm/vmm-reference/pull/49). 78 | 79 | *Note*: For now, only the path to the root block device can be configured 80 | via command line. The block device will implicitly be read-write and with 81 | `cache flush` command supported. Passing the `block` argument is optional, 82 | if you want to skip it, make sure you pass to the `path` argument of the 83 | `kernel` configuration, a suitable image (for example a Busybox one). 84 | We plan on extending the API to be able to configure more block devices and 85 | more parameters for those (not just the `path`). 86 | We also want to offer the same support in the near future for network and 87 | vsock devices. 88 | 89 | ### Example: Override the kernel command line 90 | 91 | ```bash 92 | vmm-reference \ 93 | --kernel path=/path/to/kernel/image,cmdline="reboot=t panic=1 pci=off" 94 | ``` 95 | 96 | ### Example: VM with 2 vCPUs and 1 GiB memory 97 | 98 | ```bash 99 | vmm-reference \ 100 | --memory size_mib=1024 \ 101 | --vcpu num=2 \ 102 | --kernel path=/path/to/kernel/image 103 | ``` 104 | 105 | ## Testing 106 | 107 | The reference VMM is, first and foremost, a vehicle for end-to-end testing of 108 | `rust-vmm` crates. Each crate must contain individual functional and 109 | performance tests that exercise as wide a range of use cases as possible; the 110 | reference VMM is not meant to reiterate on that, but to validate all the pieces 111 | put together. 112 | The Rust unit tests are testing modules in isolation and private interfaces, 113 | while the two Rust integration tests (one for each supported kernel image 114 | format, i.e. ELF and bzImage) exercise the only public function of the `Vmm` 115 | object, `run()`. 116 | The Python integration tests make use of the VMM in varied configurations that 117 | aren’t overly complex and illustrate realistic use cases (e.g. one test runs a 118 | VMM with a virtio block device, one test will run a VMM with PCI, etc.). 119 | 120 | To be able to successfully run all the tests in this repo, pre-created 121 | resources are stored in S3. The resources are downloaded locally inside the 122 | `resources` directory. This is handled transparently by the test cases. 123 | Note: The resources once downloaded are cached, so they are not downloaded on 124 | every run. 125 | 126 | ### Running Tests 127 | 128 | Recommended way is to run the tests inside a container using the `rustvmm/dev` 129 | docker image as below. (Note: You may need to run `docker` as `sudo`.) 130 | 131 | ```shell 132 | docker run --device=/dev/kvm -it \ 133 | --security-opt seccomp=unconfined \ 134 | --volume $(pwd):/vmm-reference rustvmm/dev:v11 135 | 136 | ``` 137 | 138 | Inside the container, to run the tests, first `cd vmm-reference` and then follow 139 | the instructions as follows. 140 | 141 | `vmm-reference` is a 142 | [workspace](https://doc.rust-lang.org/book/ch14-03-cargo-workspaces.html), so to 143 | run all the Rust tests, the following command should be used: 144 | 145 | ```bash 146 | cargo test --workspace 147 | ``` 148 | 149 | There is no single command yet for running all the Python integration tests in 150 | one shot. To run the tests from a single file, you can use: 151 | 152 | ```bash 153 | pytest 154 | ``` 155 | For example: 156 | 157 | ```bash 158 | pytest tests/test_run_reference_vmm.py 159 | ``` 160 | 161 | A single Python test can be run as well, as shown below: 162 | 163 | ```bash 164 | pytest :: 165 | ``` 166 | For example: 167 | 168 | ```bash 169 | pytest tests/test_run_reference_vmm.py::test_reference_vmm_with_disk 170 | ``` 171 | 172 | ## License 173 | 174 | This project is licensed under either of: 175 | 176 | * [Apache License](LICENSE-APACHE), Version 2.0 177 | * [BSD-3-Clause License](LICENSE-BSD-3-CLAUSE) 178 | -------------------------------------------------------------------------------- /coverage_config_x86_64.json: -------------------------------------------------------------------------------- 1 | { 2 | "coverage_score": 65.8, 3 | "exclude_path": "src/vm-vcpu-ref/,tests/,bindings.rs", 4 | "crate_features": "" 5 | } 6 | -------------------------------------------------------------------------------- /docs/getting-started.md: -------------------------------------------------------------------------------- 1 | # Getting Started with the Reference VMM 2 | 3 | ## Contents 4 | 5 | - [Getting Started with the Reference VMM](#getting-started-with-the-reference-vmm) 6 | - [Contents](#contents) 7 | - [Prerequisites](#prerequisites) 8 | - [OS & Hypervisor](#os--hypervisor) 9 | - [Build the Reference VMM](#build-the-reference-vmm) 10 | - [Run the Reference VMM](#run-the-reference-vmm) 11 | - [Kernel](#kernel) 12 | - [Devices](#devices) 13 | - [Block Device](#block-device) 14 | - [Putting It All Together](#putting-it-all-together) 15 | 16 | ## Prerequisites 17 | 18 | ### OS & Hypervisor 19 | 20 | Currently, the reference VMM runs on Linux **x86_64** hosts, using the **KVM** 21 | hypervisor. To make sure KVM is accessible to your user, run: 22 | 23 | ```bash 24 | [ -r /dev/kvm ] && [ -w /dev/kvm ] && echo "OK" || echo "FAIL" 25 | ``` 26 | 27 | To grant your user access to KVM, either: 28 | 29 | 1. If you have the ACL package for your distro installed: 30 | 31 | ```bash 32 | sudo setfacl -m u:${USER}:rw /dev/kvm 33 | ``` 34 | 35 | or 36 | 37 | 2. If your distribution uses the `kvm` group to manage access to `/dev/kvm`: 38 | 39 | ```bash 40 | [ $(stat -c "%G" /dev/kvm) = kvm ] && sudo usermod -aG kvm ${USER} 41 | ``` 42 | 43 | Then log out and back in. 44 | 45 | ## Build the Reference VMM 46 | 47 | To build the reference VMM from source, you need to have the Rust compiler and 48 | `cargo` installed on your system. The following toolchains are supported: 49 | 50 | - `x86_64-unknown-linux-gnu` (Linux with `glibc`, **default**) 51 | - `x86_64-unknown-linux-musl` (Linux with `musl libc`) 52 | 53 | As the reference VMM does not yet have any compile-time features, building it 54 | is as simple as: 55 | 56 | ```bash 57 | cargo build [--release] 58 | ``` 59 | 60 | This will produce a binary called `vmm-reference` in the `cargo` build 61 | directory (default: `target/${toolchain}/${mode}`, where mode can be `debug` or 62 | `release`). 63 | 64 | ## Run the Reference VMM 65 | 66 | ### Kernel 67 | 68 | To build a kernel for the reference VMM to boot, check out the scripts in 69 | [resources/kernel](../resources/kernel). 70 | 71 | - [`make_kernel_busybox_image.sh`](../resources/kernel/make_kernel_busybox_image.sh) 72 | builds an ELF or bzImage kernel with a baked-in initramfs running 73 | [Busybox](https://busybox.net/). It uses a stripped-down 74 | [kernel config](../resources/kernel/microvm-kernel-initramfs-hello-x86_64.config) 75 | and a statically linked [config](../resources/kernel/busybox_static_config) 76 | for the Busybox initramfs. 77 | 78 | Example: 79 | 80 | ```bash 81 | sudo ./make_kernel_busybox_image.sh -f elf -k vmlinux-hello-busybox -w /tmp/kernel 82 | ``` 83 | 84 | produces a binary image called `vmlinux-hello-busybox` in the `/tmp/kernel` 85 | directory. Root privileges are needed to create device nodes. 86 | 87 | Run `./make_kernel_busybox_image.sh` with no arguments to see the help. 88 | 89 | - [`make_kernel_image_deb.sh`](../resources/kernel/make_kernel_image_deb.sh) 90 | builds an ELF or bzImage kernel compatible with Ubuntu 20.04 from a 91 | stripped-down 92 | [kernel config](../resources/kernel/microvm-kernel-5.4-x86_64.config), as 93 | well as `.deb` packages containing the Linux kernel image and modules, to be 94 | installed in the guest. By default, the script downloads the `.deb` packages 95 | from an 96 | [official Ubuntu mirror](http://security.ubuntu.com/ubuntu/pool/main/l/linux-hwe-5.4), 97 | but it can build them from the same sources as the kernel instead. Users can 98 | opt in for this behavior by setting the `MAKEDEB` environment variable 99 | before running the script. 100 | 101 | Example: 102 | 103 | ```bash 104 | ./make_kernel_image_deb.sh -f bzimage -j 2 -k bzimage-focal -w /tmp/ubuntu-focal 105 | ``` 106 | 107 | produces a binary image called `bzimage-focal` in the `/tmp/ubuntu-focal` 108 | directory. It downloads the `linux-modules` and `linux-image-unsigned` 109 | packages and places them inside the kernel source directory within 110 | `/tmp/ubuntu-focal` (the exact location is displayed at the end). 111 | 112 | Run `./make_kernel_image_deb.sh` with no arguments to see the help. 113 | 114 | For more usage examples, see the 115 | [Buildkite pipeline](../.buildkite/deps-pipeline.yml) that calls these scripts 116 | as part of the CI. 117 | 118 | ### Devices 119 | 120 | The reference VMM only supports a serial console device for now. This section 121 | will be expanded as other devices are added. Block devices are in the works. 122 | 123 | #### Block Device 124 | 125 | To build a block device with a root filesystem in it containing an OS for the 126 | reference VMM, check out the scripts in [resources/disk](../resources/disk). 127 | 128 | - [`make_rootfs.sh`](../resources/disk/make_rootfs.sh) builds a 1 GiB disk 129 | image containing an `ext4` filesystem with an Ubuntu 20.04 image. 130 | 131 | Example: 132 | 133 | ```bash 134 | sudo resources/disk/make_rootfs.sh -d /tmp/ubuntu-focal/deb -w /tmp/ubuntu-focal 135 | ``` 136 | 137 | produces a file called `rootfs.ext4` inside `/tmp/ubuntu-focal` containing 138 | the Ubuntu 20.04 image and the kernel image installed from the `.deb` 139 | packages expected in `/tmp/ubuntu-focal/deb`. At the very least, the OS needs 140 | the `linux-image` and `linux-modules` packages. These can either be 141 | downloaded or built from sources. See [this section][#kernel] for examples on 142 | how to acquire these packages using scripts from this repo. 143 | 144 | Root privileges are needed to manage mountpoints. 145 | 146 | ### Putting It All Together 147 | 148 | Once all the prerequisites are met, the reference VMM can be run either 149 | directly through `cargo`, passing on its specific 150 | [command line arguments](../README.md#cli-reference), or after building it with 151 | `cargo build`. 152 | 153 | ```wrap 154 | cargo run --release -- \ 155 | --memory size_mib=1024 \ 156 | --kernel path=${KERNEL_PATH} \ 157 | --vcpu num=1 158 | ``` 159 | 160 | ```wrap 161 | cargo build --release 162 | target/release/vmm-reference \ 163 | --memory size_mib=1024 \ 164 | --kernel path=${KERNEL_PATH} \ 165 | --vcpu num=1 166 | ``` 167 | -------------------------------------------------------------------------------- /docs/overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-vmm/vmm-reference/89e4c8ba56b553eeefe53ab318a67b870a8b8e41/docs/overview.png -------------------------------------------------------------------------------- /docs/testing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-vmm/vmm-reference/89e4c8ba56b553eeefe53ab318a67b870a8b8e41/docs/testing.png -------------------------------------------------------------------------------- /resources/CONTAINER_VERSION: -------------------------------------------------------------------------------- 1 | 15 2 | -------------------------------------------------------------------------------- /resources/disk/install_system.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause 5 | 6 | # This script illustrates the steps for installing an OS image in a disk used 7 | # by the reference VMM. 8 | # It's called from inside a chroot in the disk image being provisioned. 9 | 10 | # We expect .deb packages containing the Linux image to be present in 11 | # /mnt/root. Install them from there. 12 | DEBIAN_FRONTEND=noninteractive apt-get -y install /mnt/root/linux*.deb 13 | 14 | # Delete root's password. 15 | passwd -d root 16 | 17 | exit 0 18 | -------------------------------------------------------------------------------- /resources/disk/make_rootfs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause 5 | 6 | # This script illustrates the build steps for disk images used with the 7 | # reference VMM. 8 | 9 | set -e 10 | 11 | SOURCE=$(readlink -f "$0") 12 | TEST_RESOURCE_DIR="$(dirname "$SOURCE")" 13 | 14 | # Reset index for cmdline arguments for the following `getopts`. 15 | OPTIND=1 16 | # Flag for optionally cleaning the workdir and recompiling the kernel. 17 | CLEAN= 18 | # Working directory. Defaults to a unique tmpdir. 19 | WORKDIR=$(mktemp -d) 20 | # Name of the resulting disk file. Defaults to "rootfs.ext4". 21 | DISKFILE="rootfs.ext4" 22 | # Directory containing .deb packages for the Linux image. 23 | DEBDIR= 24 | # Disk size. Currently hardcoded to 1 GiB. 25 | DISKSIZE="1G" 26 | # Disk mountpoint. The disk file will be mounted here and filled with data. 27 | DISKMNT="mnt/rootfs" 28 | # The Ubuntu release we'll use to build the rootfs. Hardcoded to focal (fossa, 20.04). 29 | UBUNTUVER="focal" 30 | # Hostname for the guest image we're building. 31 | HOSTNAME="ubuntu-rust-vmm" 32 | # Installation script. 33 | INSTALL_SCRIPT="$TEST_RESOURCE_DIR/install_system.sh" 34 | 35 | USAGE=" 36 | Usage: $(basename $SOURCE) -d debdir [-w workdir] [-o diskfile] [-v version] [-s size] [-c] [-n] 37 | 38 | Options: 39 | -d debdir Directory containing .deb packages for the Linux image. 40 | -w workdir Working directory for the kernel build. 41 | -o diskfile Name of the resulting disk file. 42 | -v version The Ubuntu version desired. Defaults to focal. 43 | -s disk size The size of the resulting rootfs. This needs to be 44 | an integer followed by an unit (10K, 500M, 1G). 45 | -c Clean up the working directory after the build. 46 | -n Do not perform default system installation. 47 | " 48 | export USAGE 49 | 50 | while getopts ":cd:w:o:v:s:n" opt; do 51 | case "$opt" in 52 | c) CLEAN=1 53 | ;; 54 | d) DEBDIR="$OPTARG" 55 | ;; 56 | w) rm -rf "$WORKDIR" 57 | WORKDIR="$OPTARG" 58 | ;; 59 | o) DISKFILE="$OPTARG" 60 | ;; 61 | v) UBUNTUVER="$OPTARG" 62 | ;; 63 | s) DISKSIZE="$OPTARG" 64 | ;; 65 | n) NOINSTALL=1 66 | ;; 67 | *) echo "$USAGE" 68 | exit 1 69 | esac 70 | done 71 | shift $((OPTIND-1)) 72 | 73 | die() { 74 | echo "[ERROR] $1" 75 | echo "$USAGE" 76 | exit 1 77 | } 78 | 79 | cleanup() { 80 | if [ -n "$CLEAN" ]; then 81 | echo "Cleaning $WORKDIR..." 82 | rm -rf "$WORKDIR" 83 | fi 84 | } 85 | 86 | cleanup 87 | 88 | # Create an empty file for the disk. 89 | mkdir -p "$WORKDIR" 90 | truncate -s "$DISKSIZE" "$WORKDIR/$DISKFILE" 91 | mkfs.ext4 -F "$WORKDIR/$DISKFILE" 92 | 93 | # Create a mountpoint for the disk. 94 | mkdir -p "$WORKDIR/$DISKMNT" 95 | 96 | # Mount. 97 | mount "$WORKDIR/$DISKFILE" "$WORKDIR/$DISKMNT" # Needs to be root. 98 | 99 | # Download Ubuntu packages inside the mountpoint. We'll use the focal fossa (20.04) release. 100 | # Needs to be root. 101 | debootstrap --include openssh-server "$UBUNTUVER" "$WORKDIR/$DISKMNT" 102 | 103 | # Set a hostname. 104 | echo "$HOSTNAME" > "$WORKDIR/$DISKMNT/etc/hostname" 105 | 106 | # The serial getty service hooks up the login prompt to the kernel console at 107 | # ttyS0 (where the reference VMM connects its serial console). 108 | # We'll set it up for autologin to avoid the login prompt. 109 | mkdir "$WORKDIR/$DISKMNT/etc/systemd/system/serial-getty@ttyS0.service.d/" 110 | cat < "$WORKDIR/$DISKMNT/etc/systemd/system/serial-getty@ttyS0.service.d/autologin.conf" 111 | [Service] 112 | ExecStart= 113 | ExecStart=-/sbin/agetty --autologin root -o '-p -- \\u' --keep-baud 115200,38400,9600 %I $TERM 114 | EOF 115 | 116 | if [ -z "$NOINSTALL" ]; then 117 | [ ! -d "$DEBDIR" ] && die "$DEBDIR does not exist." 118 | 119 | # OS is bootstrapped now, time to install the kernel packages. 120 | # This is done from inside a chroot, to trick dpkg. 121 | # First, copy the .deb packages inside the chroot folder, in /mnt/root. 122 | mkdir -p "$WORKDIR/$DISKMNT/mnt/root/" 123 | cp "$DEBDIR"/*.deb "$WORKDIR/$DISKMNT/mnt/root/" 124 | 125 | # Copy the script that calls dpkg (and some other things) inside the chroot. 126 | cp "$INSTALL_SCRIPT" "$WORKDIR/$DISKMNT/install_system.sh" 127 | 128 | # Chroot. 129 | chroot "$WORKDIR/$DISKMNT" /bin/bash "/install_system.sh" 130 | fi 131 | 132 | # Unmount. 133 | umount "$WORKDIR/$DISKMNT" 134 | 135 | echo "Done!" 136 | echo "Disk placed in $WORKDIR/$DISKFILE." 137 | 138 | cleanup 139 | exit 0 140 | -------------------------------------------------------------------------------- /resources/kernel/configs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause 5 | 6 | # The busybox version, kernel version and config parameters are exported 7 | # from this script. The current busybox version is compatible with glibc 8 | # version > 2.31. 9 | 10 | arch=$(uname -m) 11 | KERNEL_VERSION="5.4.81" 12 | 13 | if [[ $arch = "x86_64" ]]; then 14 | KERNEL_CFG="microvm-kernel-initramfs-hello-x86_64.config" 15 | elif [[ $arch = "aarch64" ]]; then 16 | KERNEL_CFG="microvm-kernel-initramfs-hello-aarch64.config" 17 | fi 18 | 19 | BUSYBOX_CFG="busybox_1_32_1_static_config" 20 | BUSYBOX_VERSION="1.32.1" 21 | 22 | echo "Busybox Version: $BUSYBOX_VERSION" 23 | echo "Config: $BUSYBOX_CFG" 24 | -------------------------------------------------------------------------------- /resources/kernel/make_busybox.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause 5 | 6 | # This script contains functions for compiling a Busybox initramfs. 7 | 8 | die() { 9 | echo "[ERROR] $1" 10 | echo "$USAGE" # To be filled by the caller. 11 | # Kill the caller. 12 | if [ -n "$TOP_PID" ]; then kill -s TERM "$TOP_PID"; else exit 1; fi 13 | } 14 | 15 | pushd_quiet() { 16 | pushd "$1" &>/dev/null || die "Failed to enter $1." 17 | } 18 | 19 | popd_quiet() { 20 | popd &>/dev/null || die "Failed to return to previous directory." 21 | } 22 | 23 | # Usage: 24 | # make_busybox \ 25 | # /path/to/busybox/workdir \ 26 | # /path/to/busybox/config \ 27 | # busybox_version \ 28 | # [num_cpus_build] 29 | make_busybox() { 30 | workdir="$1" 31 | config="$2" 32 | busybox_version="$3" 33 | nprocs="$4" 34 | 35 | [ -z "$workdir" ] && die "Workdir for busybox build not specified." 36 | [ -z "$config" ] && die "Busybox config file not specified." 37 | [ ! -f "$config" ] && die "Busybox config file not found." 38 | [ -z "$busybox_version" ] && die "Busybox version not specified." 39 | [ -z "$nprocs" ] && nprocs=1 40 | 41 | busybox="busybox-$busybox_version" 42 | busybox_archive="$busybox.tar.bz2" 43 | busybox_url="https://busybox.net/downloads/$busybox_archive" 44 | 45 | # Move to the work directory. 46 | mkdir -p "$workdir" 47 | echo "Changing working directory to $workdir..." 48 | pushd_quiet "$workdir" 49 | 50 | # Prepare busybox. 51 | echo "Downloading busybox..." 52 | mkdir -p busybox_rootfs 53 | [ -f "$busybox_archive" ] || curl "$busybox_url" > "$busybox_archive" 54 | 55 | echo "Extracting busybox..." 56 | tar --skip-old-files -xf "$busybox_archive" 57 | # Move to the busybox sources directory. 58 | pushd_quiet "$busybox" 59 | 60 | # Build statically linked busybox. 61 | cp "$config" .config 62 | echo "Building busybox..." 63 | make -j "$nprocs" 64 | # Package all artefacts somewhere else. 65 | 66 | echo "Packaging busybox..." 67 | make CONFIG_PREFIX=../busybox_rootfs install 68 | 69 | # Back to workdir. 70 | popd_quiet 71 | 72 | # Back to wherever we were before. 73 | popd_quiet 74 | } 75 | 76 | # Usage: 77 | # make_init [halt_value] 78 | make_init() { 79 | halt="$1" 80 | # Make an init script. 81 | cd .. 82 | echo "Creating init script..." 83 | cat >init </dev/ttyS0 2>&1'" >>init 100 | else 101 | echo "exec /sbin/halt" >>init 102 | fi 103 | } 104 | -------------------------------------------------------------------------------- /resources/kernel/make_kernel.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause 5 | 6 | # This script contains functions for compiling a Linux kernel and initramfs. 7 | 8 | KERNEL_URL_BASE="https://cdn.kernel.org/pub/linux/kernel" 9 | 10 | die() { 11 | echo "[ERROR] $1" >&2 12 | echo "$USAGE" >&2 # To be filled by the caller. 13 | # Kill the caller. 14 | if [ -n "$TOP_PID" ]; then kill -s TERM "$TOP_PID"; else exit 1; fi 15 | } 16 | 17 | pushd_quiet() { 18 | pushd "$1" &>/dev/null || die "Failed to enter $1." 19 | } 20 | 21 | popd_quiet() { 22 | popd &>/dev/null || die "Failed to return to previous directory." 23 | } 24 | 25 | # Usage: 26 | # extract_kernel_srcs kernel_version 27 | extract_kernel_srcs() { 28 | kernel_version="$1" 29 | 30 | [ -z "$kernel_version" ] && die "Kernel version not specified." 31 | 32 | # This magic trick gets the major component of the version number. 33 | kernel_major="${kernel_version%%.*}" 34 | kernel_archive="linux-$kernel_version.tar.xz" 35 | kernel_url="$KERNEL_URL_BASE/v$kernel_major.x/$kernel_archive" 36 | 37 | echo "Starting kernel build." 38 | # Download kernel sources. 39 | echo "Downloading kernel from $kernel_url" 40 | [ -f "$kernel_archive" ] || curl "$kernel_url" > "$kernel_archive" 41 | echo "Extracting kernel sources..." 42 | tar --skip-old-files -xf "$kernel_archive" 43 | } 44 | 45 | # Usage: 46 | # make_kernel_config /path/to/source/config /path/to/kernel 47 | make_kernel_config() { 48 | # Copy base kernel config. 49 | # Add any custom config options, if necessary (currently N/A). 50 | kernel_config="$1" 51 | kernel_dir="$2" 52 | 53 | [ -z "$kernel_config" ] && die "Kernel config file not specified." 54 | [ ! -f "$kernel_config" ] && die "Kernel config file not found." 55 | [ -z "$kernel_dir" ] && die "Kernel directory not specified." 56 | [ ! -d "$kernel_dir" ] && die "Kernel directory not found." 57 | 58 | echo "Copying kernel config..." 59 | cp "$kernel_config" "$kernel_dir/.config" 60 | } 61 | 62 | # Usage: 63 | # make_initramfs \ 64 | # /path/to/kernel/dir \ 65 | # /path/to/busybox/rootfs \ 66 | # [halt_value] 67 | make_initramfs() { 68 | kernel_dir="$1" 69 | busybox_rootfs="$2" 70 | halt="$3" 71 | 72 | [ -z "$kernel_dir" ] && die "Kernel directory not specified." 73 | [ ! -d "$kernel_dir" ] && die "Kernel directory not found." 74 | [ -z "$busybox_rootfs" ] && die "Busybox rootfs not specified." 75 | [ ! -d "$busybox_rootfs" ] && die "Busybox rootfs directory not found." 76 | 77 | # Move to the directory with the kernel sources. 78 | pushd_quiet "$kernel_dir" 79 | 80 | # Prepare initramfs directory. 81 | mkdir -p initramfs/{bin,dev,etc,home,mnt,proc,sys,usr} 82 | # Copy busybox. 83 | echo "Copying busybox to the initramfs directory..." 84 | cp -r "$busybox_rootfs"/* initramfs/ 85 | 86 | # Make a block device and a console. 87 | pushd_quiet initramfs/dev 88 | echo "Creating device nodes..." 89 | rm -f sda && mknod sda b 8 0 90 | rm -f console && mknod console c 5 1 91 | rm -f ttyS0 && mknod ttyS0 c 4 64 92 | 93 | make_init "$halt" 94 | 95 | chmod +x init 96 | fakeroot chown root init 97 | 98 | # Pack it up... 99 | echo "Packing initramfs.cpio..." 100 | find . | cpio -H newc -o > ../initramfs.cpio 101 | fakeroot chown root ../initramfs.cpio 102 | 103 | # Return to kernel srcdir. 104 | popd_quiet 105 | # Return to previous directory. 106 | popd_quiet 107 | } 108 | 109 | # Usage: validate_kernel_format format 110 | # Prints the lowercase format name, if one of "elf" or "bzimage" 111 | # for x86 or "pe" for aarch64. 112 | # Exits with error if any other format is specified. 113 | validate_kernel_format() { 114 | format="$1" 115 | arch=$(uname -m) 116 | 117 | kernel_fmt=$(echo "$format" | tr '[:upper:]' '[:lower:]') 118 | if [ $arch = "x86_64" ]; then 119 | if [ "$kernel_fmt" != "elf" ] && [ "$kernel_fmt" != "bzimage" ]; then 120 | die "Invalid kernel binary format: $kernel_fmt for this type of architecture." 121 | fi 122 | elif [ $arch = "aarch64" ]; then 123 | if [ "$kernel_fmt" != "pe" ]; then 124 | die "Invalid kernel binary format: $kernel_fmt for this type of architecture." 125 | fi 126 | else 127 | die "Unsupported architecture!" 128 | fi 129 | echo "$kernel_fmt" 130 | } 131 | 132 | # Usage: kernel_target format 133 | # Prints the `make` target that builds a kernel of the specified format. 134 | kernel_target() { 135 | format=$(validate_kernel_format "$1") 136 | 137 | case "$format" in 138 | elf) echo "vmlinux" 139 | ;; 140 | bzimage) # This is the default target. 141 | ;; 142 | pe) echo "Image" 143 | ;; 144 | esac 145 | } 146 | 147 | # Usage: kernel_binary format 148 | # Prints the name of the generated kernel binary. 149 | kernel_binary() { 150 | format=$(validate_kernel_format "$1") 151 | arch=$(uname -m) 152 | 153 | if [ $arch = "x86_64" ]; then 154 | case "$format" in 155 | elf) echo "vmlinux" 156 | ;; 157 | bzimage) echo "arch/x86/boot/bzImage" 158 | ;; 159 | esac 160 | elif [ $arch = "aarch64" ]; then 161 | case "$format" in 162 | pe) echo "arch/arm64/boot/Image" 163 | ;; 164 | esac 165 | else 166 | die "Unsupported architecture!" 167 | fi 168 | } 169 | 170 | # Usage: 171 | # make_kernel 172 | # /path/to/kernel/dir \ 173 | # format \ 174 | # [target] \ 175 | # [num_cpus_build] \ 176 | # [/path/to/kernel/destination] 177 | make_kernel() { 178 | kernel_dir="$1" 179 | format="$2" 180 | target="$3" 181 | nprocs="$4" 182 | dst="$5" 183 | 184 | [ -z "$kernel_dir" ] && die "Kernel directory not specified." 185 | [ ! -d "$kernel_dir" ] && die "Kernel directory not found." 186 | [ -z "$format" ] && die "Kernel format not specified." 187 | [ -z "$nprocs" ] && nprocs=1 188 | 189 | kernel_binary=$(kernel_binary "$format") 190 | 191 | # Move to the directory with the kernel sources. 192 | pushd_quiet "$kernel_dir" 193 | 194 | # Build kernel. 195 | echo "Building kernel..." 196 | # Missing the quotes for $target is intentional: if the target is empty, 197 | # quotes will cause it to be passed as an empty string, which `make` 198 | # doesn't understand. No quotes => no empty string. 199 | make -j "$nprocs" $target 200 | 201 | if [ -n "$dst" ] && [ "$kernel_binary" != "$dst" ]; then 202 | # Copy to destination. 203 | cp "$kernel_binary" "$dst" 204 | fi 205 | 206 | # Return to previous directory. 207 | popd_quiet 208 | } 209 | -------------------------------------------------------------------------------- /resources/kernel/make_kernel_busybox_image.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause 5 | 6 | # This script illustrates the build steps for kernel images used with the 7 | # reference VMM. 8 | 9 | set -e 10 | 11 | SOURCE=$(readlink -f "$0") 12 | TEST_RESOURCE_DIR="$(dirname "$SOURCE")" 13 | 14 | trap "exit 1" TERM 15 | export TOP_PID=$$ 16 | 17 | source "$TEST_RESOURCE_DIR/make_busybox.sh" 18 | source "$TEST_RESOURCE_DIR/make_kernel.sh" 19 | source "$TEST_RESOURCE_DIR/configs.sh" 20 | 21 | # Reset index for cmdline arguments for the following `getopts`. 22 | OPTIND=1 23 | # Flag for optionally building a guest that halts. 24 | HALT= 25 | # Number of CPUs to use during the kernel build process. 26 | MAKEPROCS=1 27 | # Flag for optionally cleaning the workdir and recompiling the kernel. 28 | CLEAN= 29 | # Working directory. Defaults to a unique tmpdir. 30 | WORKDIR=$(mktemp -d) 31 | # Kernel binary format. 32 | KERNEL_FMT= 33 | # Destination kernel binary name. 34 | KERNEL_BINARY_NAME= 35 | 36 | USAGE=" 37 | Usage: $(basename $SOURCE) -f (elf|bzimage|pe) [-j nprocs] [-k kernel] [-w workdir] [-c] [-h] 38 | 39 | Options: 40 | -f elf|bzimage|pe Kernel image format (either elf, bzimage or pe). 41 | -j nprocs Number of CPUs to use for the kernel build. 42 | -k kernel Name of the resulting kernel image. Has the '-halt' suffix if '-h' is passed. 43 | -w workdir Working directory for the kernel build. 44 | -c Clean up the working directory after the build. 45 | -h Create a kernel image that halts immediately after boot. 46 | " 47 | export USAGE 48 | 49 | while getopts ":chf:j:k:w:" opt; do 50 | case "$opt" in 51 | c) CLEAN=1 52 | ;; 53 | h) HALT=1 54 | ;; 55 | f) KERNEL_FMT=$(validate_kernel_format "$OPTARG") 56 | ;; 57 | j) MAKEPROCS=$OPTARG 58 | ;; 59 | k) KERNEL_BINARY_NAME=$OPTARG 60 | ;; 61 | w) rm -rf "$WORKDIR" 62 | WORKDIR=$OPTARG 63 | ;; 64 | *) echo "$USAGE" 65 | exit 1 66 | esac 67 | done 68 | shift $((OPTIND-1)) 69 | 70 | cleanup() { 71 | if [ -n "$CLEAN" ]; then 72 | echo "Cleaning $WORKDIR..." 73 | rm -rf "$WORKDIR" 74 | fi 75 | } 76 | 77 | # Step 0: clean up the workdir, if the user wants to. 78 | cleanup 79 | 80 | # Step 1: what are we building? 81 | [ -z "$KERNEL_BINARY_NAME" ] && KERNEL_BINARY_NAME=$(kernel_binary "$KERNEL_FMT") 82 | [ -n "$HALT" ] && KERNEL_BINARY_NAME="$KERNEL_BINARY_NAME-halt" 83 | 84 | # Step 2: start from scratch. 85 | mkdir -p "$WORKDIR" && cd "$WORKDIR" 86 | 87 | # Step 3: acquire kernel sources & config. 88 | extract_kernel_srcs "$KERNEL_VERSION" 89 | kernel_dir="$WORKDIR/linux-$KERNEL_VERSION" 90 | make_kernel_config "$TEST_RESOURCE_DIR/$KERNEL_CFG" "$kernel_dir" 91 | 92 | # Step 4: make the initramfs. 93 | make_busybox "$WORKDIR" "$TEST_RESOURCE_DIR/$BUSYBOX_CFG" \ 94 | "$BUSYBOX_VERSION" "$MAKEPROCS" 95 | make_initramfs "$kernel_dir" "$WORKDIR/busybox_rootfs" "$HALT" 96 | 97 | # Step 5: put them together. 98 | target=$(kernel_target "$KERNEL_FMT") 99 | make_kernel "$kernel_dir" "$KERNEL_FMT" "$target" "$MAKEPROCS" "$KERNEL_BINARY_NAME" 100 | 101 | # Final step: profit! 102 | echo "Done!" 103 | echo "Kernel binary placed in: $kernel_dir/$KERNEL_BINARY_NAME" 104 | cleanup 105 | exit 0 106 | -------------------------------------------------------------------------------- /resources/kernel/make_kernel_image_deb.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause 5 | 6 | # This script illustrates the build steps for kernel images used with the 7 | # reference VMM. 8 | 9 | set -e 10 | 11 | arch=$(uname -m) 12 | SOURCE=$(readlink -f "$0") 13 | TEST_RESOURCE_DIR="$(dirname "$SOURCE")" 14 | 15 | trap "exit 1" TERM 16 | export TOP_PID=$$ 17 | 18 | source "$TEST_RESOURCE_DIR/make_kernel.sh" 19 | 20 | KERNEL_VERSION="5.4.81" 21 | 22 | if [[ $arch = "x86_64" ]]; then 23 | KERNEL_CFG="microvm-kernel-5.4-x86_64.config" 24 | elif [[ $arch = "aarch64" ]]; then 25 | KERNEL_CFG="microvm-kernel-5.4-aarch64.config" 26 | 27 | fi 28 | 29 | # Reset index for cmdline arguments for the following `getopts`. 30 | OPTIND=1 31 | # Flag for optionally building a guest that halts. 32 | HALT= 33 | # Number of CPUs to use during the kernel build process. 34 | MAKEPROCS=1 35 | # Flag for optionally cleaning the workdir and recompiling the kernel. 36 | CLEAN= 37 | # Working directory. Defaults to a unique tmpdir. 38 | WORKDIR=$(mktemp -d) 39 | # Kernel binary format. 40 | KERNEL_FMT= 41 | # Destination kernel binary name. 42 | KERNEL_BINARY_NAME= 43 | 44 | USAGE=" 45 | Usage: $(basename $SOURCE) -f (elf|bzimage) [-j nprocs] [-k kernel] [-w workdir] [-c] [-h] 46 | 47 | Options: 48 | -f elf|bzimage Kernel image format (either elf or bzimage). 49 | -j nprocs Number of CPUs to use for the kernel build. 50 | -k kernel Name of the resulting kernel image. Has the '-halt' suffix if '-h' is passed. 51 | -w workdir Working directory for the kernel build. 52 | -c Clean up the working directory after the build. 53 | -h Create a kernel image that halts immediately after boot. 54 | " 55 | export USAGE 56 | 57 | while getopts ":chf:j:k:w:" opt; do 58 | case "$opt" in 59 | c) CLEAN=1 60 | ;; 61 | h) HALT=1 62 | ;; 63 | f) KERNEL_FMT=$(validate_kernel_format "$OPTARG") 64 | ;; 65 | j) MAKEPROCS=$OPTARG 66 | ;; 67 | k) KERNEL_BINARY_NAME=$OPTARG 68 | ;; 69 | w) rm -rf "$WORKDIR" 70 | WORKDIR=$OPTARG 71 | ;; 72 | *) echo "$USAGE" 73 | exit 1 74 | esac 75 | done 76 | shift $((OPTIND-1)) 77 | 78 | cleanup() { 79 | if [ -n "$CLEAN" ]; then 80 | echo "Cleaning $WORKDIR..." 81 | rm -rf "$WORKDIR" 82 | fi 83 | } 84 | 85 | # Step 0: clean up the workdir, if the user wants to. 86 | cleanup 87 | 88 | # Step 1: what are we building? 89 | [ -z "$KERNEL_BINARY_NAME" ] && KERNEL_BINARY_NAME=$(kernel_binary "$KERNEL_FMT") 90 | [ -n "$HALT" ] && KERNEL_BINARY_NAME="$KERNEL_BINARY_NAME-halt" 91 | 92 | # Step 2: start from scratch. 93 | mkdir -p "$WORKDIR" && cd "$WORKDIR" 94 | 95 | # Step 3: acquire kernel sources & config. 96 | extract_kernel_srcs "$KERNEL_VERSION" 97 | kernel_dir="$WORKDIR/linux-$KERNEL_VERSION" 98 | make_kernel_config "$TEST_RESOURCE_DIR/$KERNEL_CFG" "$kernel_dir" 99 | 100 | # Step 4: build *.deb packages. 101 | # We could build them from the kernel sources with `make deb-pkg`, but the 102 | # `deb-pkg` target forces a `make clean`, so we'll just download them instead 103 | # of building the entire kernel over and over again. 104 | # If, however, you want to build them from scratch, set the `MAKEDEB` env var. 105 | mkdir -p "$kernel_dir/deb" 106 | if [ -n "$MAKEDEB" ]; then 107 | echo "Building deb packages..." 108 | make_kernel "$kernel_dir" "$KERNEL_FMT" "deb-pkg" "$MAKEPROCS" 109 | cp "$kernel_dir"/../*.deb "$kernel_dir/deb" 110 | else 111 | echo "Downloading deb packages..." 112 | if [[ $arch = "x86_64" ]]; then 113 | DEB_URL="http://security.ubuntu.com/ubuntu/pool/main/l/linux-hwe-5.4" 114 | DEBS=( 115 | "linux-modules-5.4.0-42-generic_5.4.0-42.46~18.04.1_amd64.deb" 116 | "linux-image-unsigned-5.4.0-42-generic_5.4.0-42.46~18.04.1_amd64.deb" 117 | ) 118 | elif [[ $arch = "aarch64" ]]; then 119 | DEB_URL="http://ports.ubuntu.com/pool/main/l/linux-hwe-5.4" 120 | DEBS=( 121 | "linux-modules-5.4.0-42-generic_5.4.0-42.46~18.04.1_arm64.deb" 122 | "linux-image-unsigned-5.4.0-42-generic_5.4.0-42.46~18.04.1_arm64.deb" 123 | ) 124 | fi 125 | for deb in "${DEBS[@]}"; do 126 | wget -nc -P "$kernel_dir/deb" "$DEB_URL/$deb" 127 | done 128 | fi 129 | 130 | # Step 5: build kernel. 131 | target=$(kernel_target "$KERNEL_FMT") 132 | make_kernel "$kernel_dir" "$KERNEL_FMT" "$target" "$MAKEPROCS" "$KERNEL_BINARY_NAME" 133 | 134 | # Final step: profit! 135 | echo "Done!" 136 | echo "Kernel binary placed in: $kernel_dir/$KERNEL_BINARY_NAME" 137 | echo ".deb packages placed in:" 138 | ls -1 "$kernel_dir"/deb/*.deb 139 | 140 | cleanup 141 | exit 0 142 | -------------------------------------------------------------------------------- /resources/make_test_resources.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause 5 | 6 | # This script illustrates the build steps for all the kernel and disk images 7 | # used by tests in vmm-reference. 8 | 9 | set -e 10 | 11 | SOURCE=$(readlink -f "$0") 12 | TEST_RESOURCE_DIR="$(dirname "$SOURCE")" 13 | cd $TEST_RESOURCE_DIR 14 | 15 | # If the user doesn't provide a value for a variable, use the default one. 16 | # We build the images in /tmp, so they don't pollute the vmm-reference 17 | # repository, and then move them at the locations expected by the tests. 18 | TMP_KERNEL_DIR=${TMP_KERNEL_DIR:="/tmp/vmlinux_busybox"} 19 | TMP_DEB_DIR=${TMP_DEB_DIR:="/tmp/ubuntu-focal"} 20 | DEST_KERNEL_DIR=${DEST_KERNEL_DIR:="$TEST_RESOURCE_DIR/kernel"} 21 | DEST_DISK_DIR=${DEST_DISK_DIR:="$TEST_RESOURCE_DIR/disk"} 22 | DEST_INITRD_DIR=${DEST_INITRD_DIR:="$TEST_RESOURCE_DIR/initrd"} 23 | 24 | # By default use 2 CPUs. User can change this to speed up the builds. 25 | NPROC=${NPROC:=2} 26 | 27 | arch=$(uname -m) 28 | if [[ $arch = "x86_64" ]]; then 29 | ./kernel/make_kernel_busybox_image.sh -f elf -k vmlinux-hello-busybox -w $TMP_KERNEL_DIR -j $NPROC 30 | ./kernel/make_kernel_busybox_image.sh -f elf -k vmlinux-hello-busybox -w $TMP_KERNEL_DIR -j $NPROC -h 31 | ./kernel/make_kernel_busybox_image.sh -f bzimage -k bzimage-hello-busybox -w $TMP_KERNEL_DIR -j $NPROC 32 | ./kernel/make_kernel_busybox_image.sh -f bzimage -k bzimage-hello-busybox -w $TMP_KERNEL_DIR -j $NPROC -h 33 | 34 | # We are moving the built busybox images to where they are expected to be 35 | # found by the vmm-reference tests. This is also making it easier to upload 36 | # the whole `resources` folder to S3. 37 | mv $TMP_KERNEL_DIR/linux-5.4.81/vmlinux-hello-busybox $TMP_KERNEL_DIR/linux-5.4.81/vmlinux-hello-busybox-halt \ 38 | $TMP_KERNEL_DIR/linux-5.4.81/bzimage-hello-busybox $TMP_KERNEL_DIR/linux-5.4.81/bzimage-hello-busybox-halt \ 39 | $DEST_KERNEL_DIR 40 | 41 | ./kernel/make_kernel_image_deb.sh -f elf -k vmlinux-focal -w $TMP_DEB_DIR -j $NPROC 42 | ./kernel/make_kernel_image_deb.sh -f bzimage -k bzimage-focal -w $TMP_DEB_DIR -j $NPROC 43 | 44 | # Same applies to the Ubuntu images. 45 | mv $TMP_DEB_DIR/linux-5.4.81/vmlinux-focal $TMP_DEB_DIR/linux-5.4.81/bzimage-focal $DEST_KERNEL_DIR 46 | 47 | ./disk/make_rootfs.sh -d $TMP_DEB_DIR/linux-5.4.81/deb/ -w $DEST_DISK_DIR -o ubuntu-focal-rootfs-x86_64.ext4 48 | elif [[ $arch = "aarch64" ]]; then 49 | ./kernel/make_kernel_busybox_image.sh -f pe -k pe-hello-busybox -w $TMP_KERNEL_DIR -j $NPROC 50 | ./kernel/make_kernel_busybox_image.sh -f pe -k pe-hello-busybox -w $TMP_KERNEL_DIR -j $NPROC -h 51 | 52 | # And same to the aarch64 images. 53 | mv $TMP_KERNEL_DIR/linux-5.4.81/pe-hello-busybox $TMP_KERNEL_DIR/linux-5.4.81/pe-hello-busybox-halt $DEST_KERNEL_DIR 54 | mkdir -p $DEST_INITRD_DIR 55 | mv $TMP_KERNEL_DIR/linux-5.4.81/initramfs.cpio $DEST_INITRD_DIR/aarch64-initramfs.cpio 56 | 57 | # making debian based image 58 | ./kernel/make_kernel_image_deb.sh -f pe -k pe-focal -w $TMP_DEB_DIR -j $NPROC 59 | 60 | # move them to correct folder 61 | mv $TMP_DEB_DIR/linux-5.4.81/pe-focal $DEST_KERNEL_DIR 62 | 63 | ./disk/make_rootfs.sh -d $TMP_DEB_DIR/linux-5.4.81/deb/ -w $DEST_DISK_DIR -o ubuntu-focal-rootfs-aarch64.ext4 64 | 65 | fi 66 | -------------------------------------------------------------------------------- /src/api/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "api" 3 | version = "0.1.0" 4 | authors = ["rust-vmm AWS maintainers "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | clap = "3.2.17" 9 | 10 | vmm = { path = "../vmm" } 11 | 12 | [dev-dependencies] 13 | linux-loader = "0.4.0" 14 | -------------------------------------------------------------------------------- /src/api/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause 3 | //! CLI for the Reference VMM. 4 | 5 | #![deny(missing_docs)] 6 | use std::result; 7 | 8 | use clap::{App, Arg}; 9 | use vmm::VMMConfig; 10 | 11 | /// Command line parser. 12 | pub struct Cli; 13 | 14 | impl Cli { 15 | /// Parses the command line options into VMM configurations. 16 | /// 17 | /// # Arguments 18 | /// 19 | /// * `cmdline_args` - command line arguments passed to the application. 20 | pub fn launch(cmdline_args: Vec<&str>) -> result::Result { 21 | let mut app = App::new(cmdline_args[0].to_string()) 22 | .arg( 23 | Arg::with_name("memory") 24 | .long("memory") 25 | .takes_value(true) 26 | .help("Guest memory configuration.\n\tFormat: \"size_mib=\""), 27 | ) 28 | .arg( 29 | Arg::with_name("vcpu") 30 | .long("vcpu") 31 | .takes_value(true) 32 | .help("vCPU configuration.\n\tFormat: \"num=\""), 33 | ) 34 | .arg( 35 | Arg::with_name("kernel") 36 | .long("kernel") 37 | .required(true) 38 | .takes_value(true) 39 | .help("Kernel configuration.\n\tFormat: \"path=[,cmdline=,kernel_load_addr=]\""), 40 | ) 41 | .arg( 42 | Arg::with_name("net") 43 | .long("net") 44 | .takes_value(true) 45 | .help("Network device configuration. \n\tFormat: \"tap=\"") 46 | ) 47 | .arg( 48 | Arg::with_name("block") 49 | .long("block") 50 | .required(false) 51 | .takes_value(true) 52 | .help("Block device configuration. \n\tFormat: \"path=\"") 53 | ); 54 | 55 | // Save the usage beforehand as a string, because `get_matches` consumes the `App`. 56 | let mut help_msg_buf: Vec = vec![]; 57 | // If the write fails, we'll just have an empty help message. 58 | let _ = app.write_long_help(&mut help_msg_buf); 59 | let help_msg = String::from_utf8_lossy(&help_msg_buf); 60 | 61 | let matches = app.get_matches_from_safe(cmdline_args).map_err(|e| { 62 | eprintln!("{}", help_msg); 63 | format!("Invalid command line arguments: {}", e) 64 | })?; 65 | 66 | VMMConfig::builder() 67 | .memory_config(matches.value_of("memory")) 68 | .kernel_config(matches.value_of("kernel")) 69 | .vcpu_config(matches.value_of("vcpu")) 70 | .net_config(matches.value_of("net")) 71 | .block_config(matches.value_of("block")) 72 | .build() 73 | .map_err(|e| format!("{:?}", e)) 74 | } 75 | } 76 | 77 | #[cfg(test)] 78 | mod tests { 79 | use super::*; 80 | 81 | use std::path::PathBuf; 82 | 83 | use linux_loader::cmdline::Cmdline; 84 | 85 | use vmm::{KernelConfig, MemoryConfig, VcpuConfig, DEFAULT_KERNEL_LOAD_ADDR}; 86 | 87 | #[test] 88 | fn test_launch() { 89 | // Missing command line arguments. 90 | assert!(Cli::launch(vec!["foobar"]).is_err()); 91 | 92 | // Invalid extra command line parameter. 93 | assert!(Cli::launch(vec![ 94 | "foobar", 95 | "--memory", 96 | "size_mib=128", 97 | "--vcpu", 98 | "num=1", 99 | "--kernel", 100 | "path=/foo/bar,cmdline=\"foo=bar\",kernel_load_addr=42", 101 | "foobar", 102 | ]) 103 | .is_err()); 104 | 105 | // Invalid memory config: invalid value for `size_mib`. 106 | assert!(Cli::launch(vec![ 107 | "foobar", 108 | "--memory", 109 | "size_mib=foobar", 110 | "--vcpu", 111 | "num=1", 112 | "--kernel", 113 | "path=/foo/bar,cmdline=\"foo=bar\",kernel_load_addr=42", 114 | ]) 115 | .is_err()); 116 | 117 | // Memory config: missing value for `size_mib`, use the default 118 | assert!(Cli::launch(vec![ 119 | "foobar", 120 | "--memory", 121 | "size_mib=", 122 | "--vcpu", 123 | "num=1", 124 | "--kernel", 125 | "path=/foo/bar,cmdline=\"foo=bar\",kernel_load_addr=42", 126 | ]) 127 | .is_ok()); 128 | 129 | // Invalid memory config: unexpected parameter `foobar`. 130 | assert!(Cli::launch(vec![ 131 | "foobar", 132 | "--memory", 133 | "foobar=1024", 134 | "--vcpu", 135 | "num=1", 136 | "--kernel", 137 | "path=/foo/bar,cmdline=\"foo=bar\",kernel_load_addr=42", 138 | ]) 139 | .is_err()); 140 | 141 | // Invalid kernel config: invalid value for `kernel_load_addr`. 142 | // TODO: harden cmdline check. 143 | assert!(Cli::launch(vec![ 144 | "foobar", 145 | "--memory", 146 | "size_mib=128", 147 | "--vcpu", 148 | "num=1", 149 | "--kernel", 150 | "path=/foo/bar,cmdline=\"foo=bar\",kernel_load_addr=foobar", 151 | ]) 152 | .is_err()); 153 | 154 | // Kernel config: missing value for `kernel_load_addr`, use default 155 | assert!(Cli::launch(vec![ 156 | "foobar", 157 | "--memory", 158 | "size_mib=128", 159 | "--vcpu", 160 | "num=1", 161 | "--kernel", 162 | "path=/foo/bar,cmdline=\"foo=bar\",kernel_load_addr=", 163 | ]) 164 | .is_ok()); 165 | 166 | // Invalid kernel config: unexpected parameter `foobar`. 167 | assert!(Cli::launch(vec![ 168 | "foobar", 169 | "--memory", 170 | "size_mib=128", 171 | "--vcpu", 172 | "num=1", 173 | "--kernel", 174 | "path=/foo/bar,cmdline=\"foo=bar\",kernel_load_addr=42,foobar=42", 175 | ]) 176 | .is_err()); 177 | 178 | // Invalid vCPU config: invalid value for `num_vcpus`. 179 | assert!(Cli::launch(vec![ 180 | "foobar", 181 | "--memory", 182 | "size_mib=128", 183 | "--vcpu", 184 | "num=foobar", 185 | "--kernel", 186 | "path=/foo/bar,cmdline=\"foo=bar\",kernel_load_addr=42", 187 | ]) 188 | .is_err()); 189 | 190 | // vCPU config: missing value for `num_vcpus`, use default 191 | assert!(Cli::launch(vec![ 192 | "foobar", 193 | "--memory", 194 | "size_mib=128", 195 | "--vcpu", 196 | "num=", 197 | "--kernel", 198 | "path=/foo/bar,cmdline=\"foo=bar\",kernel_load_addr=42", 199 | ]) 200 | .is_ok()); 201 | 202 | // Invalid vCPU config: unexpected parameter `foobar`. 203 | assert!(Cli::launch(vec![ 204 | "foobar", 205 | "--memory", 206 | "size_mib=128", 207 | "--vcpu", 208 | "foobar=1", 209 | "--kernel", 210 | "path=/foo/bar,cmdline=\"foo=bar\",kernel_load_addr=42", 211 | ]) 212 | .is_err()); 213 | 214 | let mut foo_cmdline = Cmdline::new(4096); 215 | foo_cmdline.insert_str("\"foo=bar bar=foo\"").unwrap(); 216 | 217 | // OK. 218 | assert_eq!( 219 | Cli::launch(vec![ 220 | "foobar", 221 | "--memory", 222 | "size_mib=128", 223 | "--vcpu", 224 | "num=1", 225 | "--kernel", 226 | "path=/foo/bar,cmdline=\"foo=bar bar=foo\",kernel_load_addr=42", 227 | ]) 228 | .unwrap(), 229 | VMMConfig { 230 | kernel_config: KernelConfig { 231 | path: PathBuf::from("/foo/bar"), 232 | cmdline: foo_cmdline, 233 | load_addr: 42, 234 | }, 235 | memory_config: MemoryConfig { size_mib: 128 }, 236 | vcpu_config: VcpuConfig { num: 1 }, 237 | block_config: None, 238 | net_config: None, 239 | } 240 | ); 241 | 242 | // Test default values. 243 | assert_eq!( 244 | Cli::launch(vec!["foobar", "--kernel", "path=/foo/bar",]).unwrap(), 245 | VMMConfig { 246 | kernel_config: KernelConfig { 247 | path: PathBuf::from("/foo/bar"), 248 | cmdline: KernelConfig::default_cmdline(), 249 | load_addr: DEFAULT_KERNEL_LOAD_ADDR, 250 | }, 251 | memory_config: MemoryConfig { size_mib: 256 }, 252 | vcpu_config: VcpuConfig { num: 1 }, 253 | block_config: None, 254 | net_config: None, 255 | } 256 | ); 257 | } 258 | } 259 | -------------------------------------------------------------------------------- /src/arch/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "arch" 3 | version = "0.1.0" 4 | authors = ["rust-vmm AWS maintainers "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | vm-fdt = "0.2.0" 11 | vm-memory = "0.7.0" 12 | -------------------------------------------------------------------------------- /src/devices/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "devices" 3 | version = "0.1.0" 4 | authors = ["rust-vmm AWS maintainers "] 5 | edition = "2018" 6 | license = "Apache-2.0 OR BSD-3-Clause" 7 | 8 | [dependencies] 9 | event-manager = { version = "0.2.1", features = ["remote_endpoint"] } 10 | kvm-ioctls = "0.11.0" 11 | libc = "0.2.76" 12 | linux-loader = "0.4.0" 13 | log = "0.4.6" 14 | vm-memory = "0.7.0" 15 | vm-superio = "0.5.0" 16 | vmm-sys-util = "0.8.0" 17 | vm-device = "0.1.0" 18 | 19 | virtio-blk = { git = "https://github.com/rust-vmm/vm-virtio.git", features = ["backend-stdio"] } 20 | virtio-device = { git = "https://github.com/rust-vmm/vm-virtio.git"} 21 | virtio-queue = { git = "https://github.com/rust-vmm/vm-virtio.git"} 22 | 23 | utils = { path = "../utils" } 24 | 25 | [dev-dependencies] 26 | vm-memory = { version = "0.7.0", features = ["backend-mmap"] } 27 | kvm-bindings = "0.5.0" 28 | -------------------------------------------------------------------------------- /src/devices/src/legacy/i8042.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause 3 | 4 | use std::convert::TryInto; 5 | use vm_device::bus::{PioAddress, PioAddressOffset}; 6 | use vm_device::MutDevicePio; 7 | use vm_superio::I8042Device; 8 | 9 | use utils::debug; 10 | 11 | use super::EventFdTrigger; 12 | pub struct I8042Wrapper(pub I8042Device); 13 | 14 | impl MutDevicePio for I8042Wrapper { 15 | fn pio_read(&mut self, _base: PioAddress, offset: PioAddressOffset, data: &mut [u8]) { 16 | if data.len() != 1 { 17 | debug!("Invalid I8042 data length on read: {}", data.len()); 18 | return; 19 | } 20 | match offset.try_into() { 21 | Ok(offset) => { 22 | self.0.read(offset); 23 | } 24 | Err(_) => debug!("Invalid I8042 read offset."), 25 | } 26 | } 27 | 28 | fn pio_write(&mut self, _base: PioAddress, offset: PioAddressOffset, data: &[u8]) { 29 | if data.len() != 1 { 30 | debug!("Invalid I8042 data length on write: {}", data.len()); 31 | return; 32 | } 33 | match offset.try_into() { 34 | Ok(offset) => { 35 | if self.0.write(offset, data[0]).is_err() { 36 | debug!("Failed to write to I8042."); 37 | } 38 | } 39 | Err(_) => debug!("Invalid I8042 write offset"), 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/devices/src/legacy/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause 3 | #[cfg(target_arch = "x86_64")] 4 | mod i8042; 5 | #[cfg(target_arch = "aarch64")] 6 | mod rtc; 7 | mod serial; 8 | #[cfg(target_arch = "x86_64")] 9 | pub use i8042::I8042Wrapper; 10 | #[cfg(target_arch = "aarch64")] 11 | pub use rtc::RtcWrapper; 12 | pub use serial::Error as SerialError; 13 | pub use serial::SerialWrapper; 14 | use std::io; 15 | use std::ops::Deref; 16 | 17 | use vm_superio::Trigger; 18 | use vmm_sys_util::eventfd::EventFd; 19 | 20 | /// Newtype for implementing the trigger functionality for `EventFd`. 21 | /// 22 | /// The trigger is used for handling events in the legacy devices. 23 | pub struct EventFdTrigger(EventFd); 24 | 25 | impl Trigger for EventFdTrigger { 26 | type E = io::Error; 27 | 28 | fn trigger(&self) -> io::Result<()> { 29 | self.write(1) 30 | } 31 | } 32 | impl Deref for EventFdTrigger { 33 | type Target = EventFd; 34 | fn deref(&self) -> &Self::Target { 35 | &self.0 36 | } 37 | } 38 | impl EventFdTrigger { 39 | pub fn try_clone(&self) -> io::Result { 40 | Ok(EventFdTrigger((**self).try_clone()?)) 41 | } 42 | pub fn new(flag: i32) -> io::Result { 43 | let event_fd = EventFd::new(flag)?; 44 | Ok(EventFdTrigger(event_fd)) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/devices/src/legacy/rtc.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause 3 | 4 | use std::convert::TryInto; 5 | use vm_device::bus::MmioAddress; 6 | use vm_device::MutDeviceMmio; 7 | use vm_superio::{rtc_pl031::NoEvents, Rtc}; 8 | 9 | use utils::debug; 10 | 11 | pub struct RtcWrapper(pub Rtc); 12 | 13 | impl MutDeviceMmio for RtcWrapper { 14 | fn mmio_read(&mut self, _base: MmioAddress, offset: u64, data: &mut [u8]) { 15 | if data.len() != 4 { 16 | debug!("RTC invalid data length on read: {}", data.len()); 17 | return; 18 | } 19 | 20 | match offset.try_into() { 21 | // The unwrap() is safe because we checked that `data` has length 4. 22 | Ok(offset) => self.0.read(offset, data.try_into().unwrap()), 23 | Err(_) => debug!("Invalid RTC read offset."), 24 | } 25 | } 26 | 27 | fn mmio_write(&mut self, _base: MmioAddress, offset: u64, data: &[u8]) { 28 | if data.len() != 4 { 29 | debug!("RTC invalid data length on write: {}", data.len()); 30 | return; 31 | } 32 | 33 | match offset.try_into() { 34 | // The unwrap() is safe because we checked that `data` has length 4. 35 | Ok(offset) => self.0.write(offset, data.try_into().unwrap()), 36 | Err(_) => debug!("Invalid RTC write offset."), 37 | } 38 | } 39 | } 40 | 41 | #[cfg(test)] 42 | mod tests { 43 | use super::*; 44 | 45 | #[test] 46 | fn test_invalid_requests() { 47 | let mut rtc = RtcWrapper(Rtc::new()); 48 | 49 | // Check that passing invalid data does not result in a crash. 50 | let mut invalid_data = [0; 3]; 51 | let valid_offset = 0x0; 52 | rtc.mmio_read(MmioAddress(0), valid_offset, invalid_data.as_mut()); 53 | rtc.mmio_write(MmioAddress(0), valid_offset, &invalid_data); 54 | 55 | // Check that passing an invalid offset does not result in a crash. 56 | let valid_data = [0; 4]; 57 | let invalid_offset = u64::MAX; 58 | rtc.mmio_write(MmioAddress(0), invalid_offset, &valid_data); 59 | } 60 | 61 | #[test] 62 | fn test_valid_read() { 63 | use core::time::Duration; 64 | use std::thread; 65 | 66 | let mut rtc = RtcWrapper(Rtc::new()); 67 | let mut data = [0; 4]; 68 | let offset = 0x0; 69 | 70 | // Read the data register. 71 | rtc.mmio_read(MmioAddress(0), offset, data.as_mut()); 72 | let first_read = u32::from_le_bytes(data); 73 | 74 | // Sleep for 1.5 seconds. 75 | let delay = Duration::from_millis(1500); 76 | thread::sleep(delay); 77 | 78 | // Read the data register again. 79 | rtc.mmio_read(MmioAddress(0), offset, data.as_mut()); 80 | let second_read = u32::from_le_bytes(data); 81 | 82 | assert!(second_read > first_read); 83 | } 84 | 85 | #[test] 86 | fn test_valid_write() { 87 | let mut rtc = RtcWrapper(Rtc::new()); 88 | let write_data = [1; 4]; 89 | let mut read_data = [0; 4]; 90 | let offset = 0x8; 91 | 92 | // Write to and read from the load register. 93 | rtc.mmio_write(MmioAddress(0), offset, &write_data); 94 | rtc.mmio_read(MmioAddress(0), offset, read_data.as_mut()); 95 | 96 | assert_eq!( 97 | u32::from_le_bytes(write_data), 98 | u32::from_le_bytes(read_data) 99 | ); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/devices/src/legacy/serial.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause 3 | 4 | use std::convert::TryInto; 5 | use std::io::{self, stdin, Read, Write}; 6 | 7 | use event_manager::{EventOps, Events, MutEventSubscriber}; 8 | #[cfg(target_arch = "aarch64")] 9 | use vm_device::{bus::MmioAddress, MutDeviceMmio}; 10 | #[cfg(target_arch = "x86_64")] 11 | use vm_device::{ 12 | bus::{PioAddress, PioAddressOffset}, 13 | MutDevicePio, 14 | }; 15 | use vm_superio::serial::{NoEvents, SerialEvents}; 16 | use vm_superio::{Serial, Trigger}; 17 | use vmm_sys_util::epoll::EventSet; 18 | 19 | use utils::debug; 20 | 21 | /// Newtype for implementing `event-manager` functionalities. 22 | pub struct SerialWrapper(pub Serial); 23 | 24 | impl MutEventSubscriber for SerialWrapper { 25 | fn process(&mut self, events: Events, ops: &mut EventOps) { 26 | // Respond to stdin events. 27 | // `EventSet::IN` => send what's coming from stdin to the guest. 28 | // `EventSet::HANG_UP` or `EventSet::ERROR` => deregister the serial input. 29 | let mut out = [0u8; 32]; 30 | match stdin().read(&mut out) { 31 | Err(e) => { 32 | eprintln!("Error while reading stdin: {:?}", e); 33 | } 34 | Ok(count) => { 35 | let event_set = events.event_set(); 36 | let unregister_condition = 37 | event_set.contains(EventSet::ERROR) | event_set.contains(EventSet::HANG_UP); 38 | if count > 0 { 39 | if self.0.enqueue_raw_bytes(&out[..count]).is_err() { 40 | eprintln!("Failed to send bytes to the guest via serial input"); 41 | } 42 | } else if unregister_condition { 43 | // Got 0 bytes from serial input; is it a hang-up or error? 44 | ops.remove(events) 45 | .expect("Failed to unregister serial input"); 46 | } 47 | } 48 | } 49 | } 50 | 51 | fn init(&mut self, ops: &mut EventOps) { 52 | // Hook to stdin events. 53 | ops.add(Events::new(&stdin(), EventSet::IN)) 54 | .expect("Failed to register serial input event"); 55 | } 56 | } 57 | 58 | impl, W: Write> SerialWrapper { 59 | fn bus_read(&mut self, offset: u8, data: &mut [u8]) { 60 | if data.len() != 1 { 61 | debug!("Serial console invalid data length on read: {}", data.len()); 62 | return; 63 | } 64 | 65 | // This is safe because we checked that `data` has length 1. 66 | data[0] = self.0.read(offset); 67 | } 68 | 69 | fn bus_write(&mut self, offset: u8, data: &[u8]) { 70 | if data.len() != 1 { 71 | debug!( 72 | "Serial console invalid data length on write: {}", 73 | data.len() 74 | ); 75 | return; 76 | } 77 | 78 | // This is safe because we checked that `data` has length 1. 79 | let res = self.0.write(offset, data[0]); 80 | if res.is_err() { 81 | debug!("Error writing to serial console: {:#?}", res.unwrap_err()); 82 | } 83 | } 84 | } 85 | 86 | #[cfg(target_arch = "x86_64")] 87 | impl, W: Write> MutDevicePio for SerialWrapper { 88 | fn pio_read(&mut self, _base: PioAddress, offset: PioAddressOffset, data: &mut [u8]) { 89 | // TODO: this function can't return an Err, so we'll mark error conditions 90 | // (data being more than 1 byte, offset overflowing an u8) with logs & metrics. 91 | 92 | match offset.try_into() { 93 | Ok(offset) => self.bus_read(offset, data), 94 | Err(_) => debug!("Invalid serial console read offset."), 95 | } 96 | } 97 | 98 | fn pio_write(&mut self, _base: PioAddress, offset: PioAddressOffset, data: &[u8]) { 99 | // TODO: this function can't return an Err, so we'll mark error conditions 100 | // (data being more than 1 byte, offset overflowing an u8) with logs & metrics. 101 | 102 | match offset.try_into() { 103 | Ok(offset) => self.bus_write(offset, data), 104 | Err(_) => debug!("Invalid serial console write offset."), 105 | } 106 | } 107 | } 108 | 109 | #[cfg(target_arch = "aarch64")] 110 | impl, W: Write> MutDeviceMmio for SerialWrapper { 111 | fn mmio_read(&mut self, _base: MmioAddress, offset: u64, data: &mut [u8]) { 112 | // TODO: this function can't return an Err, so we'll mark error conditions 113 | // (data being more than 1 byte, offset overflowing an u8) with logs & metrics. 114 | 115 | match offset.try_into() { 116 | Ok(offset) => self.bus_read(offset, data), 117 | Err(_) => debug!("Invalid serial console read offset."), 118 | } 119 | } 120 | 121 | fn mmio_write(&mut self, _base: MmioAddress, offset: u64, data: &[u8]) { 122 | // TODO: this function can't return an Err, so we'll mark error conditions 123 | // (data being more than 1 byte, offset overflowing an u8) with logs & metrics. 124 | 125 | match offset.try_into() { 126 | Ok(offset) => self.bus_write(offset, data), 127 | Err(_) => debug!("Invalid serial console write offset."), 128 | } 129 | } 130 | } 131 | /// Errors encountered during device operation. 132 | #[derive(Debug)] 133 | pub enum Error { 134 | /// Failed to create an event manager for device events. 135 | EventManager(event_manager::Error), 136 | } 137 | 138 | #[cfg(test)] 139 | mod tests { 140 | use super::*; 141 | use crate::legacy::EventFdTrigger; 142 | 143 | use std::io::sink; 144 | 145 | fn check_invalid_data(invalid_data: &mut [u8]) { 146 | let interrupt_evt = EventFdTrigger::new(libc::EFD_NONBLOCK).unwrap(); 147 | let mut serial_console = SerialWrapper(Serial::new(interrupt_evt, sink())); 148 | let valid_iir_offset = 2; 149 | 150 | // Check that passing invalid data does not result in a crash. 151 | #[cfg(target_arch = "x86_64")] 152 | serial_console.pio_read(PioAddress(0), valid_iir_offset, invalid_data); 153 | #[cfg(target_arch = "aarch64")] 154 | serial_console.mmio_read(MmioAddress(0), valid_iir_offset, invalid_data); 155 | 156 | // The same scenario happens for writes. 157 | #[cfg(target_arch = "x86_64")] 158 | serial_console.pio_write(PioAddress(0), valid_iir_offset, invalid_data); 159 | #[cfg(target_arch = "aarch64")] 160 | serial_console.mmio_write(MmioAddress(0), valid_iir_offset, invalid_data); 161 | } 162 | 163 | #[test] 164 | fn test_empty_data() { 165 | check_invalid_data(&mut []); 166 | } 167 | 168 | #[test] 169 | fn test_longer_data() { 170 | check_invalid_data(&mut [0; 2]); 171 | } 172 | 173 | #[test] 174 | fn test_invalid_offset() { 175 | let interrupt_evt = EventFdTrigger::new(libc::EFD_NONBLOCK).unwrap(); 176 | let mut serial_console = SerialWrapper(Serial::new(interrupt_evt, sink())); 177 | let data = [0]; 178 | 179 | // Check that passing an invalid offset does not result in a crash. 180 | #[cfg(target_arch = "x86_64")] 181 | { 182 | let invalid_offset = PioAddressOffset::MAX; 183 | serial_console.pio_write(PioAddress(0), invalid_offset, &data); 184 | } 185 | #[cfg(target_arch = "aarch64")] 186 | { 187 | let invalid_offset = u64::MAX; 188 | serial_console.mmio_write(MmioAddress(0), invalid_offset, &data); 189 | } 190 | } 191 | 192 | #[test] 193 | fn test_valid_write_and_read() { 194 | let interrupt_evt = EventFdTrigger::new(libc::EFD_NONBLOCK).unwrap(); 195 | let mut serial_console = SerialWrapper(Serial::new(interrupt_evt, sink())); 196 | let write_data = [5]; 197 | let mut read_data = [0]; 198 | let offset = 7; 199 | 200 | // Write on and read from the serial console. 201 | #[cfg(target_arch = "x86_64")] 202 | { 203 | serial_console.pio_write(PioAddress(0), offset, &write_data); 204 | serial_console.pio_read(PioAddress(0), offset, read_data.as_mut()); 205 | } 206 | #[cfg(target_arch = "aarch64")] 207 | { 208 | serial_console.mmio_write(MmioAddress(0), offset, &write_data); 209 | serial_console.mmio_read(MmioAddress(0), offset, read_data.as_mut()); 210 | } 211 | 212 | assert_eq!(&write_data, &read_data); 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /src/devices/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause 3 | 4 | // This crate holds devices used by the VMM. They are reusing rust-vmm generic components, and 5 | // we are striving to turn as much of the local code as possible into reusable building blocks 6 | // going forward. 7 | 8 | pub mod legacy; 9 | pub mod virtio; 10 | -------------------------------------------------------------------------------- /src/devices/src/virtio/block/device.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause 3 | 4 | use std::borrow::{Borrow, BorrowMut}; 5 | use std::fs::OpenOptions; 6 | use std::ops::DerefMut; 7 | use std::path::PathBuf; 8 | use std::sync::{Arc, Mutex}; 9 | 10 | use virtio_blk::stdio_executor::StdIoBackend; 11 | use virtio_device::{VirtioConfig, VirtioDeviceActions, VirtioDeviceType, VirtioMmioDevice}; 12 | use virtio_queue::Queue; 13 | use vm_device::bus::MmioAddress; 14 | use vm_device::device_manager::MmioManager; 15 | use vm_device::{DeviceMmio, MutDeviceMmio}; 16 | use vm_memory::GuestAddressSpace; 17 | 18 | use crate::virtio::block::{BLOCK_DEVICE_ID, VIRTIO_BLK_F_RO}; 19 | use crate::virtio::{CommonConfig, Env, SingleFdSignalQueue, QUEUE_MAX_SIZE}; 20 | 21 | use super::inorder_handler::InOrderQueueHandler; 22 | use super::queue_handler::QueueHandler; 23 | use super::{build_config_space, BlockArgs, Error, Result}; 24 | 25 | // This Block device can only use the MMIO transport for now, but we plan to reuse large parts of 26 | // the functionality when we implement virtio PCI as well, for example by having a base generic 27 | // type, and then separate concrete instantiations for `MmioConfig` and `PciConfig`. 28 | pub struct Block { 29 | cfg: CommonConfig, 30 | file_path: PathBuf, 31 | read_only: bool, 32 | // We'll prob need to remember this for state save/restore unless we pass the info from 33 | // the outside. 34 | _root_device: bool, 35 | } 36 | 37 | impl Block 38 | where 39 | M: GuestAddressSpace + Clone + Send + 'static, 40 | { 41 | // Helper method that only creates a `Block` object. 42 | fn create_block(env: &mut Env, args: &BlockArgs) -> Result { 43 | let device_features = args.device_features(); 44 | 45 | // A block device has a single queue. 46 | let queues = vec![Queue::new(env.mem.clone(), QUEUE_MAX_SIZE)]; 47 | let config_space = build_config_space(&args.file_path)?; 48 | let virtio_cfg = VirtioConfig::new(device_features, queues, config_space); 49 | 50 | let common_cfg = CommonConfig::new(virtio_cfg, env).map_err(Error::Virtio)?; 51 | 52 | Ok(Block { 53 | cfg: common_cfg, 54 | file_path: args.file_path.clone(), 55 | read_only: args.read_only, 56 | _root_device: args.root_device, 57 | }) 58 | } 59 | 60 | // Create `Block` object, register it on the MMIO bus, and add any extra required info to 61 | // the kernel cmdline from the environment. 62 | pub fn new(env: &mut Env, args: &BlockArgs) -> Result>> 63 | where 64 | // We're using this (more convoluted) bound so we can pass both references and smart 65 | // pointers such as mutex guards here. 66 | B: DerefMut, 67 | B::Target: MmioManager>, 68 | { 69 | let block = Arc::new(Mutex::new(Self::create_block(env, args)?)); 70 | 71 | // Register the device on the MMIO bus. 72 | env.register_mmio_device(block.clone()) 73 | .map_err(Error::Virtio)?; 74 | 75 | env.insert_cmdline_str(args.cmdline_config_substring()) 76 | .map_err(Error::Virtio)?; 77 | 78 | Ok(block) 79 | } 80 | } 81 | 82 | impl Borrow> for Block { 83 | fn borrow(&self) -> &VirtioConfig { 84 | &self.cfg.virtio 85 | } 86 | } 87 | 88 | impl BorrowMut> for Block { 89 | fn borrow_mut(&mut self) -> &mut VirtioConfig { 90 | &mut self.cfg.virtio 91 | } 92 | } 93 | 94 | impl VirtioDeviceType for Block { 95 | fn device_type(&self) -> u32 { 96 | BLOCK_DEVICE_ID 97 | } 98 | } 99 | 100 | impl VirtioDeviceActions for Block { 101 | type E = Error; 102 | 103 | fn activate(&mut self) -> Result<()> { 104 | let file = OpenOptions::new() 105 | .read(true) 106 | .write(!self.read_only) 107 | .open(&self.file_path) 108 | .map_err(Error::OpenFile)?; 109 | 110 | let mut features = self.cfg.virtio.driver_features; 111 | if self.read_only { 112 | // Not sure if the driver is expected to explicitly acknowledge the `RO` feature, 113 | // so adding it explicitly here when present just in case. 114 | features |= 1 << VIRTIO_BLK_F_RO; 115 | } 116 | 117 | // TODO: Create the backend earlier (as part of `Block::new`)? 118 | let disk = StdIoBackend::new(file, features).map_err(Error::Backend)?; 119 | 120 | let driver_notify = SingleFdSignalQueue { 121 | irqfd: self.cfg.irqfd.clone(), 122 | interrupt_status: self.cfg.virtio.interrupt_status.clone(), 123 | }; 124 | 125 | let mut ioevents = self.cfg.prepare_activate().map_err(Error::Virtio)?; 126 | 127 | let inner = InOrderQueueHandler { 128 | driver_notify, 129 | queue: self.cfg.virtio.queues.remove(0), 130 | disk, 131 | }; 132 | 133 | let handler = Arc::new(Mutex::new(QueueHandler { 134 | inner, 135 | ioeventfd: ioevents.remove(0), 136 | })); 137 | 138 | self.cfg.finalize_activate(handler).map_err(Error::Virtio) 139 | } 140 | 141 | fn reset(&mut self) -> Result<()> { 142 | // Not implemented for now. 143 | Ok(()) 144 | } 145 | } 146 | 147 | impl VirtioMmioDevice for Block {} 148 | 149 | impl MutDeviceMmio for Block { 150 | fn mmio_read(&mut self, _base: MmioAddress, offset: u64, data: &mut [u8]) { 151 | self.read(offset, data); 152 | } 153 | 154 | fn mmio_write(&mut self, _base: MmioAddress, offset: u64, data: &[u8]) { 155 | self.write(offset, data); 156 | } 157 | } 158 | 159 | #[cfg(test)] 160 | mod tests { 161 | use vmm_sys_util::tempfile::TempFile; 162 | 163 | use crate::virtio::tests::EnvMock; 164 | 165 | use super::super::VIRTIO_BLK_F_FLUSH; 166 | use super::*; 167 | #[test] 168 | fn test_device() { 169 | let tmp = TempFile::new().unwrap(); 170 | 171 | let mut mock = EnvMock::new(); 172 | let mut env = mock.env(); 173 | let args = BlockArgs { 174 | file_path: tmp.as_path().to_path_buf(), 175 | read_only: true, 176 | root_device: true, 177 | advertise_flush: true, 178 | }; 179 | 180 | let block_mutex = Block::new(&mut env, &args).unwrap(); 181 | let block = block_mutex.lock().unwrap(); 182 | 183 | assert_eq!(block.device_type(), BLOCK_DEVICE_ID); 184 | 185 | assert_eq!( 186 | mock.kernel_cmdline.as_str(), 187 | format!( 188 | "virtio_mmio.device=4K@0x{:x}:{} root=/dev/vda ro", 189 | mock.mmio_cfg.range.base().0, 190 | mock.mmio_cfg.gsi 191 | ) 192 | ); 193 | 194 | assert_ne!(block.cfg.virtio.device_features & (1 << VIRTIO_BLK_F_RO), 0); 195 | assert_ne!( 196 | block.cfg.virtio.device_features & (1 << VIRTIO_BLK_F_FLUSH), 197 | 0 198 | ); 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /src/devices/src/virtio/block/inorder_handler.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause 3 | 4 | use std::fs::File; 5 | use std::result; 6 | 7 | use log::warn; 8 | use virtio_blk::request::Request; 9 | use virtio_blk::stdio_executor::{self, StdIoBackend}; 10 | use virtio_queue::{DescriptorChain, Queue}; 11 | use vm_memory::{self, GuestAddressSpace}; 12 | 13 | use crate::virtio::SignalUsedQueue; 14 | 15 | #[derive(Debug)] 16 | pub enum Error { 17 | GuestMemory(vm_memory::GuestMemoryError), 18 | Queue(virtio_queue::Error), 19 | ProcessRequest(stdio_executor::ProcessReqError), 20 | } 21 | 22 | impl From for Error { 23 | fn from(e: vm_memory::GuestMemoryError) -> Self { 24 | Error::GuestMemory(e) 25 | } 26 | } 27 | 28 | impl From for Error { 29 | fn from(e: virtio_queue::Error) -> Self { 30 | Error::Queue(e) 31 | } 32 | } 33 | 34 | impl From for Error { 35 | fn from(e: stdio_executor::ProcessReqError) -> Self { 36 | Error::ProcessRequest(e) 37 | } 38 | } 39 | 40 | // This object is used to process the queue of a block device without making any assumptions 41 | // about the notification mechanism. We're using a specific backend for now (the `StdIoBackend` 42 | // object), but the aim is to have a way of working with generic backends and turn this into 43 | // a more flexible building block. The name comes from processing and returning descriptor 44 | // chains back to the device in the same order they are received. 45 | pub struct InOrderQueueHandler { 46 | pub driver_notify: S, 47 | pub queue: Queue, 48 | pub disk: StdIoBackend, 49 | } 50 | 51 | impl InOrderQueueHandler 52 | where 53 | M: GuestAddressSpace, 54 | S: SignalUsedQueue, 55 | { 56 | fn process_chain(&mut self, mut chain: DescriptorChain) -> result::Result<(), Error> { 57 | let used_len = match Request::parse(&mut chain) { 58 | Ok(request) => self.disk.process_request(chain.memory(), &request)?, 59 | Err(e) => { 60 | warn!("block request parse error: {:?}", e); 61 | 0 62 | } 63 | }; 64 | 65 | self.queue.add_used(chain.head_index(), used_len)?; 66 | 67 | if self.queue.needs_notification()? { 68 | self.driver_notify.signal_used_queue(0); 69 | } 70 | 71 | Ok(()) 72 | } 73 | 74 | pub fn process_queue(&mut self) -> result::Result<(), Error> { 75 | // To see why this is done in a loop, please look at the `Queue::enable_notification` 76 | // comments in `virtio_queue`. 77 | loop { 78 | self.queue.disable_notification()?; 79 | 80 | while let Some(chain) = self.queue.iter()?.next() { 81 | self.process_chain(chain)?; 82 | } 83 | 84 | if !self.queue.enable_notification()? { 85 | break; 86 | } 87 | } 88 | 89 | Ok(()) 90 | } 91 | } 92 | 93 | // TODO: Figure out which unit tests make sense to add after implementing a generic backend 94 | // abstraction for `InOrderHandler`. 95 | -------------------------------------------------------------------------------- /src/devices/src/virtio/block/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause 3 | 4 | mod device; 5 | mod inorder_handler; 6 | mod queue_handler; 7 | 8 | use std::fs::File; 9 | use std::io::{self, Seek, SeekFrom}; 10 | use std::path::{Path, PathBuf}; 11 | 12 | use virtio_blk::stdio_executor; 13 | 14 | use crate::virtio::features::{VIRTIO_F_IN_ORDER, VIRTIO_F_RING_EVENT_IDX, VIRTIO_F_VERSION_1}; 15 | 16 | pub use device::Block; 17 | 18 | // TODO: Move relevant defines to vm-virtio crate. 19 | 20 | // Block device ID as defined by the standard. 21 | pub const BLOCK_DEVICE_ID: u32 = 2; 22 | 23 | // Block device read-only feature. 24 | pub const VIRTIO_BLK_F_RO: u64 = 5; 25 | // Block device FLUSH feature. 26 | pub const VIRTIO_BLK_F_FLUSH: u64 = 9; 27 | 28 | // The sector size is 512 bytes (1 << 9). 29 | const SECTOR_SHIFT: u8 = 9; 30 | 31 | #[derive(Debug)] 32 | pub enum Error { 33 | Backend(stdio_executor::Error), 34 | Virtio(crate::virtio::Error), 35 | OpenFile(io::Error), 36 | Seek(io::Error), 37 | } 38 | 39 | pub type Result = std::result::Result; 40 | 41 | // TODO: Add a helper abstraction to rust-vmm for building the device configuration space. 42 | // The one we build below for the block device contains the minimally required `capacity` member, 43 | // but other fields can be present as well depending on the negotiated features. 44 | fn build_config_space>(path: P) -> Result> { 45 | // TODO: right now, the file size is computed by the StdioBackend as well. Maybe we should 46 | // create the backend as early as possible, and get the size information from there. 47 | let file_size = File::open(path) 48 | .map_err(Error::OpenFile)? 49 | .seek(SeekFrom::End(0)) 50 | .map_err(Error::Seek)?; 51 | // If the file size is actually not a multiple of sector size, then data at the very end 52 | // will be ignored. 53 | let num_sectors = file_size >> SECTOR_SHIFT; 54 | // This has to be in little endian btw. 55 | Ok(num_sectors.to_le_bytes().to_vec()) 56 | } 57 | 58 | // Arguments required when building a block device. 59 | pub struct BlockArgs { 60 | pub file_path: PathBuf, 61 | pub read_only: bool, 62 | pub root_device: bool, 63 | pub advertise_flush: bool, 64 | } 65 | 66 | impl BlockArgs { 67 | // Generate device features based on the configuration options. 68 | pub fn device_features(&self) -> u64 { 69 | // The queue handling logic for the device uses the buffers in order, so we enable the 70 | // corresponding feature as well. 71 | let mut features = 72 | 1 << VIRTIO_F_VERSION_1 | 1 << VIRTIO_F_IN_ORDER | 1 << VIRTIO_F_RING_EVENT_IDX; 73 | 74 | if self.read_only { 75 | features |= 1 << VIRTIO_BLK_F_RO; 76 | } 77 | 78 | if self.advertise_flush { 79 | features |= 1 << VIRTIO_BLK_F_FLUSH; 80 | } 81 | 82 | features 83 | } 84 | 85 | // Generate additional info that needs to be appended to the kernel command line based 86 | // on the current arg configuration. 87 | pub fn cmdline_config_substring(&self) -> String { 88 | let mut s = String::new(); 89 | if self.root_device { 90 | s.push_str("root=/dev/vda"); 91 | 92 | if self.read_only { 93 | s.push_str(" ro"); 94 | } else { 95 | s.push_str(" rw"); 96 | } 97 | } 98 | s 99 | } 100 | } 101 | 102 | #[cfg(test)] 103 | mod tests { 104 | use std::io::Write; 105 | use std::mem::size_of; 106 | 107 | use vmm_sys_util::tempfile::TempFile; 108 | 109 | use super::*; 110 | 111 | impl Default for BlockArgs { 112 | fn default() -> Self { 113 | BlockArgs { 114 | file_path: "".into(), 115 | read_only: false, 116 | root_device: false, 117 | advertise_flush: false, 118 | } 119 | } 120 | } 121 | 122 | #[test] 123 | fn test_build_config_space() { 124 | let tmp = TempFile::new().unwrap(); 125 | 126 | let sector = [1u8; 512]; 127 | let num_sectors = 1024u64; 128 | 129 | for _ in 0..num_sectors { 130 | tmp.as_file().write_all(§or).unwrap(); 131 | } 132 | 133 | { 134 | let config_space = build_config_space(tmp.as_path()).unwrap(); 135 | 136 | // The config space is only populated with the `capacity` field for now. 137 | assert_eq!(config_space.len(), size_of::()); 138 | assert_eq!(config_space[..8], num_sectors.to_le_bytes()); 139 | } 140 | 141 | // Let's write some more bytes to the file, such that the size is no longer a multiple 142 | // of the sector size. 143 | tmp.as_file().write_all(&[1u8, 2, 3]).unwrap(); 144 | 145 | { 146 | let config_space = build_config_space(tmp.as_path()).unwrap(); 147 | // We should get the same value of capacity, as the extra bytes are ignored. 148 | assert_eq!(config_space[..8], num_sectors.to_le_bytes()); 149 | } 150 | } 151 | 152 | #[test] 153 | fn test_device_features() { 154 | let mut args = BlockArgs::default(); 155 | 156 | let base = 157 | 1u64 << VIRTIO_F_VERSION_1 | 1 << VIRTIO_F_IN_ORDER | 1 << VIRTIO_F_RING_EVENT_IDX; 158 | 159 | assert_eq!(args.device_features(), base); 160 | 161 | args.read_only = true; 162 | assert_eq!(args.device_features(), base | 1 << VIRTIO_BLK_F_RO); 163 | 164 | args.read_only = false; 165 | args.advertise_flush = true; 166 | assert_eq!(args.device_features(), base | 1 << VIRTIO_BLK_F_FLUSH); 167 | } 168 | 169 | #[test] 170 | fn test_cmdline_string() { 171 | let mut args = BlockArgs::default(); 172 | 173 | assert_eq!(args.cmdline_config_substring(), ""); 174 | 175 | args.read_only = true; 176 | // There's no effect unless `root_device` is also `true`. 177 | assert_eq!(args.cmdline_config_substring(), ""); 178 | 179 | args.root_device = true; 180 | assert_eq!(args.cmdline_config_substring(), "root=/dev/vda ro"); 181 | 182 | args.read_only = false; 183 | assert_eq!(args.cmdline_config_substring(), "root=/dev/vda rw"); 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /src/devices/src/virtio/block/queue_handler.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause 3 | 4 | use event_manager::{EventOps, Events, MutEventSubscriber}; 5 | use log::error; 6 | use vm_memory::GuestAddressSpace; 7 | use vmm_sys_util::epoll::EventSet; 8 | use vmm_sys_util::eventfd::EventFd; 9 | 10 | use crate::virtio::block::inorder_handler::InOrderQueueHandler; 11 | use crate::virtio::SingleFdSignalQueue; 12 | 13 | const IOEVENT_DATA: u32 = 0; 14 | 15 | // This object simply combines the more generic `InOrderQueueHandler` with a concrete queue 16 | // signalling implementation based on `EventFd`s, and then also implements `MutEventSubscriber` 17 | // to interact with the event manager. `ioeventfd` is the `EventFd` connected to queue 18 | // notifications coming from the driver. 19 | pub(crate) struct QueueHandler { 20 | pub inner: InOrderQueueHandler, 21 | pub ioeventfd: EventFd, 22 | } 23 | 24 | impl MutEventSubscriber for QueueHandler { 25 | fn process(&mut self, events: Events, ops: &mut EventOps) { 26 | let mut error = true; 27 | 28 | // TODO: Have a look at any potential performance impact caused by these conditionals 29 | // just to be sure. 30 | if events.event_set() != EventSet::IN { 31 | error!("unexpected event_set"); 32 | } else if events.data() != IOEVENT_DATA { 33 | error!("unexpected events data {}", events.data()); 34 | } else if self.ioeventfd.read().is_err() { 35 | error!("ioeventfd read error") 36 | } else if let Err(e) = self.inner.process_queue() { 37 | error!("error processing block queue {:?}", e); 38 | } else { 39 | error = false; 40 | } 41 | 42 | if error { 43 | ops.remove(events) 44 | .expect("Failed to remove fd from event handling loop"); 45 | } 46 | } 47 | 48 | fn init(&mut self, ops: &mut EventOps) { 49 | ops.add(Events::with_data( 50 | &self.ioeventfd, 51 | IOEVENT_DATA, 52 | EventSet::IN, 53 | )) 54 | .expect("Failed to init block queue handler"); 55 | } 56 | } 57 | 58 | // TODO: Figure out if unit tests make sense here as well after implementing a generic backend 59 | // abstraction for the `InOrderHandler`. 60 | -------------------------------------------------------------------------------- /src/devices/src/virtio/net/bindings.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | // 4 | // Portions Copyright 2017 The Chromium OS Authors. All rights reserved. 5 | // Use of this source code is governed by a BSD-style license that can be 6 | // found in the THIRD-PARTY file. 7 | 8 | // The following are manually copied from crosvm/firecracker. In the latter, they can be found as 9 | // part of the `net_gen` local crate. We should figure out how to proceed going forward (i.e. 10 | // create some bindings of our own, put them in a common crate, etc). 11 | 12 | #![allow(clippy::all)] 13 | #![allow(non_upper_case_globals)] 14 | #![allow(non_camel_case_types)] 15 | #![allow(non_snake_case)] 16 | 17 | pub const TUN_F_CSUM: ::std::os::raw::c_uint = 1; 18 | pub const TUN_F_TSO4: ::std::os::raw::c_uint = 2; 19 | pub const TUN_F_TSO6: ::std::os::raw::c_uint = 4; 20 | pub const TUN_F_UFO: ::std::os::raw::c_uint = 16; 21 | 22 | #[repr(C)] 23 | pub struct __BindgenUnionField(::std::marker::PhantomData); 24 | impl __BindgenUnionField { 25 | #[inline] 26 | pub fn new() -> Self { 27 | __BindgenUnionField(::std::marker::PhantomData) 28 | } 29 | #[inline] 30 | pub unsafe fn as_ref(&self) -> &T { 31 | ::std::mem::transmute(self) 32 | } 33 | #[inline] 34 | pub unsafe fn as_mut(&mut self) -> &mut T { 35 | ::std::mem::transmute(self) 36 | } 37 | } 38 | impl ::std::default::Default for __BindgenUnionField { 39 | #[inline] 40 | fn default() -> Self { 41 | Self::new() 42 | } 43 | } 44 | impl ::std::clone::Clone for __BindgenUnionField { 45 | #[inline] 46 | fn clone(&self) -> Self { 47 | Self::new() 48 | } 49 | } 50 | impl ::std::marker::Copy for __BindgenUnionField {} 51 | impl ::std::fmt::Debug for __BindgenUnionField { 52 | fn fmt(&self, fmt: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { 53 | fmt.write_str("__BindgenUnionField") 54 | } 55 | } 56 | 57 | #[repr(C)] 58 | #[derive(Debug, Default, Copy)] 59 | pub struct ifreq { 60 | pub ifr_ifrn: ifreq__bindgen_ty_1, 61 | pub ifr_ifru: ifreq__bindgen_ty_2, 62 | } 63 | 64 | impl Clone for ifreq { 65 | fn clone(&self) -> Self { 66 | *self 67 | } 68 | } 69 | 70 | #[repr(C)] 71 | #[derive(Debug, Default, Copy)] 72 | pub struct ifreq__bindgen_ty_1 { 73 | pub ifrn_name: __BindgenUnionField<[::std::os::raw::c_uchar; 16usize]>, 74 | pub bindgen_union_field: [u8; 16usize], 75 | } 76 | 77 | impl Clone for ifreq__bindgen_ty_1 { 78 | fn clone(&self) -> Self { 79 | *self 80 | } 81 | } 82 | 83 | #[repr(C)] 84 | #[derive(Debug, Default, Copy)] 85 | pub struct ifreq__bindgen_ty_2 { 86 | pub ifru_addr: __BindgenUnionField, 87 | pub ifru_dstaddr: __BindgenUnionField, 88 | pub ifru_broadaddr: __BindgenUnionField, 89 | pub ifru_netmask: __BindgenUnionField, 90 | pub ifru_hwaddr: __BindgenUnionField, 91 | pub ifru_flags: __BindgenUnionField<::std::os::raw::c_short>, 92 | pub ifru_ivalue: __BindgenUnionField<::std::os::raw::c_int>, 93 | pub ifru_mtu: __BindgenUnionField<::std::os::raw::c_int>, 94 | pub ifru_map: __BindgenUnionField, 95 | pub ifru_slave: __BindgenUnionField<[::std::os::raw::c_char; 16usize]>, 96 | pub ifru_newname: __BindgenUnionField<[::std::os::raw::c_char; 16usize]>, 97 | pub ifru_data: __BindgenUnionField<*mut ::std::os::raw::c_void>, 98 | pub ifru_settings: __BindgenUnionField, 99 | pub bindgen_union_field: [u64; 3usize], 100 | } 101 | 102 | impl Clone for ifreq__bindgen_ty_2 { 103 | fn clone(&self) -> Self { 104 | *self 105 | } 106 | } 107 | 108 | pub type sa_family_t = ::std::os::raw::c_ushort; 109 | 110 | #[repr(C)] 111 | #[derive(Debug, Default, Copy)] 112 | pub struct sockaddr { 113 | pub sa_family: sa_family_t, 114 | pub sa_data: [::std::os::raw::c_char; 14usize], 115 | } 116 | 117 | impl Clone for sockaddr { 118 | fn clone(&self) -> Self { 119 | *self 120 | } 121 | } 122 | 123 | #[repr(C)] 124 | #[derive(Debug, Default, Copy)] 125 | pub struct if_settings { 126 | pub type_: ::std::os::raw::c_uint, 127 | pub size: ::std::os::raw::c_uint, 128 | pub ifs_ifsu: if_settings__bindgen_ty_1, 129 | } 130 | 131 | impl Clone for if_settings { 132 | fn clone(&self) -> Self { 133 | *self 134 | } 135 | } 136 | 137 | #[repr(C)] 138 | #[derive(Debug, Default, Copy)] 139 | pub struct if_settings__bindgen_ty_1 { 140 | pub raw_hdlc: __BindgenUnionField<*mut raw_hdlc_proto>, 141 | pub cisco: __BindgenUnionField<*mut cisco_proto>, 142 | pub fr: __BindgenUnionField<*mut fr_proto>, 143 | pub fr_pvc: __BindgenUnionField<*mut fr_proto_pvc>, 144 | pub fr_pvc_info: __BindgenUnionField<*mut fr_proto_pvc_info>, 145 | pub sync: __BindgenUnionField<*mut sync_serial_settings>, 146 | pub te1: __BindgenUnionField<*mut te1_settings>, 147 | pub bindgen_union_field: u64, 148 | } 149 | 150 | impl Clone for if_settings__bindgen_ty_1 { 151 | fn clone(&self) -> Self { 152 | *self 153 | } 154 | } 155 | 156 | #[repr(C)] 157 | #[derive(Debug, Default, Copy)] 158 | pub struct ifmap { 159 | pub mem_start: ::std::os::raw::c_ulong, 160 | pub mem_end: ::std::os::raw::c_ulong, 161 | pub base_addr: ::std::os::raw::c_ushort, 162 | pub irq: ::std::os::raw::c_uchar, 163 | pub dma: ::std::os::raw::c_uchar, 164 | pub port: ::std::os::raw::c_uchar, 165 | } 166 | 167 | impl Clone for ifmap { 168 | fn clone(&self) -> Self { 169 | *self 170 | } 171 | } 172 | 173 | #[repr(C)] 174 | #[derive(Debug, Default, Copy)] 175 | pub struct raw_hdlc_proto { 176 | pub encoding: ::std::os::raw::c_ushort, 177 | pub parity: ::std::os::raw::c_ushort, 178 | } 179 | 180 | impl Clone for raw_hdlc_proto { 181 | fn clone(&self) -> Self { 182 | *self 183 | } 184 | } 185 | 186 | #[repr(C)] 187 | #[derive(Debug, Default, Copy)] 188 | pub struct cisco_proto { 189 | pub interval: ::std::os::raw::c_uint, 190 | pub timeout: ::std::os::raw::c_uint, 191 | } 192 | 193 | impl Clone for cisco_proto { 194 | fn clone(&self) -> Self { 195 | *self 196 | } 197 | } 198 | 199 | #[repr(C)] 200 | #[derive(Debug, Default, Copy)] 201 | pub struct fr_proto { 202 | pub t391: ::std::os::raw::c_uint, 203 | pub t392: ::std::os::raw::c_uint, 204 | pub n391: ::std::os::raw::c_uint, 205 | pub n392: ::std::os::raw::c_uint, 206 | pub n393: ::std::os::raw::c_uint, 207 | pub lmi: ::std::os::raw::c_ushort, 208 | pub dce: ::std::os::raw::c_ushort, 209 | } 210 | 211 | impl Clone for fr_proto { 212 | fn clone(&self) -> Self { 213 | *self 214 | } 215 | } 216 | 217 | #[repr(C)] 218 | #[derive(Debug, Default, Copy)] 219 | pub struct fr_proto_pvc { 220 | pub dlci: ::std::os::raw::c_uint, 221 | } 222 | 223 | impl Clone for fr_proto_pvc { 224 | fn clone(&self) -> Self { 225 | *self 226 | } 227 | } 228 | 229 | #[repr(C)] 230 | #[derive(Debug, Default, Copy)] 231 | pub struct fr_proto_pvc_info { 232 | pub dlci: ::std::os::raw::c_uint, 233 | pub master: [::std::os::raw::c_char; 16usize], 234 | } 235 | 236 | impl Clone for fr_proto_pvc_info { 237 | fn clone(&self) -> Self { 238 | *self 239 | } 240 | } 241 | 242 | #[repr(C)] 243 | #[derive(Debug, Default, Copy)] 244 | pub struct sync_serial_settings { 245 | pub clock_rate: ::std::os::raw::c_uint, 246 | pub clock_type: ::std::os::raw::c_uint, 247 | pub loopback: ::std::os::raw::c_ushort, 248 | } 249 | 250 | impl Clone for sync_serial_settings { 251 | fn clone(&self) -> Self { 252 | *self 253 | } 254 | } 255 | 256 | #[repr(C)] 257 | #[derive(Debug, Default, Copy)] 258 | pub struct te1_settings { 259 | pub clock_rate: ::std::os::raw::c_uint, 260 | pub clock_type: ::std::os::raw::c_uint, 261 | pub loopback: ::std::os::raw::c_ushort, 262 | pub slot_map: ::std::os::raw::c_uint, 263 | } 264 | 265 | impl Clone for te1_settings { 266 | fn clone(&self) -> Self { 267 | *self 268 | } 269 | } 270 | -------------------------------------------------------------------------------- /src/devices/src/virtio/net/device.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause 3 | 4 | use std::borrow::{Borrow, BorrowMut}; 5 | use std::ops::DerefMut; 6 | use std::sync::{Arc, Mutex}; 7 | 8 | use virtio_device::{VirtioConfig, VirtioDeviceActions, VirtioDeviceType, VirtioMmioDevice}; 9 | use virtio_queue::Queue; 10 | use vm_device::bus::MmioAddress; 11 | use vm_device::device_manager::MmioManager; 12 | use vm_device::{DeviceMmio, MutDeviceMmio}; 13 | use vm_memory::GuestAddressSpace; 14 | 15 | use crate::virtio::features::{VIRTIO_F_IN_ORDER, VIRTIO_F_RING_EVENT_IDX, VIRTIO_F_VERSION_1}; 16 | use crate::virtio::net::features::*; 17 | use crate::virtio::net::{Error, NetArgs, Result, NET_DEVICE_ID, VIRTIO_NET_HDR_SIZE}; 18 | use crate::virtio::{CommonConfig, Env, SingleFdSignalQueue, QUEUE_MAX_SIZE}; 19 | 20 | use super::bindings; 21 | use super::queue_handler::QueueHandler; 22 | use super::simple_handler::SimpleHandler; 23 | use super::tap::Tap; 24 | 25 | pub struct Net { 26 | cfg: CommonConfig, 27 | tap_name: String, 28 | } 29 | 30 | impl Net 31 | where 32 | M: GuestAddressSpace + Clone + Send + 'static, 33 | { 34 | pub fn new(env: &mut Env, args: &NetArgs) -> Result>> 35 | where 36 | // We're using this (more convoluted) bound so we can pass both references and smart 37 | // pointers such as mutex guards here. 38 | B: DerefMut, 39 | B::Target: MmioManager>, 40 | { 41 | let device_features = (1 << VIRTIO_F_VERSION_1) 42 | | (1 << VIRTIO_F_RING_EVENT_IDX) 43 | | (1 << VIRTIO_F_IN_ORDER) 44 | | (1 << VIRTIO_NET_F_CSUM) 45 | | (1 << VIRTIO_NET_F_GUEST_CSUM) 46 | | (1 << VIRTIO_NET_F_GUEST_TSO4) 47 | | (1 << VIRTIO_NET_F_GUEST_TSO6) 48 | | (1 << VIRTIO_NET_F_GUEST_UFO) 49 | | (1 << VIRTIO_NET_F_HOST_TSO4) 50 | | (1 << VIRTIO_NET_F_HOST_TSO6) 51 | | (1 << VIRTIO_NET_F_HOST_UFO); 52 | 53 | // An rx/tx queue pair. 54 | let queues = vec![ 55 | Queue::new(env.mem.clone(), QUEUE_MAX_SIZE), 56 | Queue::new(env.mem.clone(), QUEUE_MAX_SIZE), 57 | ]; 58 | 59 | // TODO: We'll need a minimal config space to support setting an explicit MAC addr 60 | // on the guest interface at least. We use an empty one for now. 61 | let config_space = Vec::new(); 62 | let virtio_cfg = VirtioConfig::new(device_features, queues, config_space); 63 | 64 | let common_cfg = CommonConfig::new(virtio_cfg, env).map_err(Error::Virtio)?; 65 | 66 | let net = Arc::new(Mutex::new(Net { 67 | cfg: common_cfg, 68 | tap_name: args.tap_name.clone(), 69 | })); 70 | 71 | env.register_mmio_device(net.clone()) 72 | .map_err(Error::Virtio)?; 73 | 74 | Ok(net) 75 | } 76 | } 77 | 78 | impl VirtioDeviceType for Net { 79 | fn device_type(&self) -> u32 { 80 | NET_DEVICE_ID 81 | } 82 | } 83 | 84 | impl Borrow> for Net { 85 | fn borrow(&self) -> &VirtioConfig { 86 | &self.cfg.virtio 87 | } 88 | } 89 | 90 | impl BorrowMut> for Net { 91 | fn borrow_mut(&mut self) -> &mut VirtioConfig { 92 | &mut self.cfg.virtio 93 | } 94 | } 95 | 96 | impl VirtioDeviceActions for Net { 97 | type E = Error; 98 | 99 | fn activate(&mut self) -> Result<()> { 100 | let tap = Tap::open_named(self.tap_name.as_str()).map_err(Error::Tap)?; 101 | 102 | // Set offload flags to match the relevant virtio features of the device (for now, 103 | // statically set in the constructor. 104 | tap.set_offload( 105 | bindings::TUN_F_CSUM 106 | | bindings::TUN_F_UFO 107 | | bindings::TUN_F_TSO4 108 | | bindings::TUN_F_TSO6, 109 | ) 110 | .map_err(Error::Tap)?; 111 | 112 | // The layout of the header is specified in the standard and is 12 bytes in size. We 113 | // should define this somewhere. 114 | tap.set_vnet_hdr_size(VIRTIO_NET_HDR_SIZE as i32) 115 | .map_err(Error::Tap)?; 116 | 117 | let driver_notify = SingleFdSignalQueue { 118 | irqfd: self.cfg.irqfd.clone(), 119 | interrupt_status: self.cfg.virtio.interrupt_status.clone(), 120 | }; 121 | 122 | let mut ioevents = self.cfg.prepare_activate().map_err(Error::Virtio)?; 123 | 124 | let rxq = self.cfg.virtio.queues.remove(0); 125 | let txq = self.cfg.virtio.queues.remove(0); 126 | let inner = SimpleHandler::new(driver_notify, rxq, txq, tap); 127 | 128 | let handler = Arc::new(Mutex::new(QueueHandler { 129 | inner, 130 | rx_ioevent: ioevents.remove(0), 131 | tx_ioevent: ioevents.remove(0), 132 | })); 133 | 134 | self.cfg.finalize_activate(handler).map_err(Error::Virtio) 135 | } 136 | 137 | fn reset(&mut self) -> std::result::Result<(), Error> { 138 | // Not implemented for now. 139 | Ok(()) 140 | } 141 | } 142 | 143 | impl VirtioMmioDevice for Net {} 144 | 145 | impl MutDeviceMmio for Net { 146 | fn mmio_read(&mut self, _base: MmioAddress, offset: u64, data: &mut [u8]) { 147 | self.read(offset, data); 148 | } 149 | 150 | fn mmio_write(&mut self, _base: MmioAddress, offset: u64, data: &[u8]) { 151 | self.write(offset, data); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/devices/src/virtio/net/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause 3 | 4 | mod bindings; 5 | mod device; 6 | mod queue_handler; 7 | mod simple_handler; 8 | pub mod tap; 9 | 10 | pub use device::Net; 11 | 12 | // TODO: Move relevant defines to vm-virtio crate. 13 | 14 | // Values taken from the virtio standard (section 5.1.3 of the 1.1 version). 15 | pub mod features { 16 | pub const VIRTIO_NET_F_CSUM: u64 = 0; 17 | pub const VIRTIO_NET_F_GUEST_CSUM: u64 = 1; 18 | pub const VIRTIO_NET_F_GUEST_TSO4: u64 = 7; 19 | pub const VIRTIO_NET_F_GUEST_TSO6: u64 = 8; 20 | pub const VIRTIO_NET_F_GUEST_UFO: u64 = 10; 21 | pub const VIRTIO_NET_F_HOST_TSO4: u64 = 11; 22 | pub const VIRTIO_NET_F_HOST_TSO6: u64 = 12; 23 | pub const VIRTIO_NET_F_HOST_UFO: u64 = 14; 24 | } 25 | 26 | // Size of the `virtio_net_hdr` structure defined by the standard. 27 | pub const VIRTIO_NET_HDR_SIZE: usize = 12; 28 | 29 | // Net device ID as defined by the standard. 30 | pub const NET_DEVICE_ID: u32 = 1; 31 | 32 | // Prob have to find better names here, but these basically represent the order of the queues. 33 | // If the net device has a single RX/TX pair, then the former has index 0 and the latter 1. When 34 | // the device has multiqueue support, then RX queues have indices 2k, and TX queues 2k+1. 35 | const RXQ_INDEX: u16 = 0; 36 | const TXQ_INDEX: u16 = 1; 37 | 38 | #[derive(Debug)] 39 | pub enum Error { 40 | Virtio(crate::virtio::Error), 41 | Tap(tap::Error), 42 | } 43 | 44 | pub type Result = std::result::Result; 45 | 46 | pub struct NetArgs { 47 | pub tap_name: String, 48 | } 49 | -------------------------------------------------------------------------------- /src/devices/src/virtio/net/queue_handler.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause 3 | 4 | use event_manager::{EventOps, Events, MutEventSubscriber}; 5 | use log::error; 6 | use vm_memory::GuestAddressSpace; 7 | use vmm_sys_util::epoll::EventSet; 8 | use vmm_sys_util::eventfd::EventFd; 9 | 10 | use crate::virtio::SingleFdSignalQueue; 11 | 12 | use super::simple_handler::SimpleHandler; 13 | 14 | const TAPFD_DATA: u32 = 0; 15 | const RX_IOEVENT_DATA: u32 = 1; 16 | const TX_IOEVENT_DATA: u32 = 2; 17 | 18 | pub struct QueueHandler { 19 | pub inner: SimpleHandler, 20 | pub rx_ioevent: EventFd, 21 | pub tx_ioevent: EventFd, 22 | } 23 | 24 | impl QueueHandler { 25 | // Helper method that receives an error message to be logged and the `ops` handle 26 | // which is used to unregister all events. 27 | fn handle_error>(&self, s: S, ops: &mut EventOps) { 28 | error!("{}", s.as_ref()); 29 | ops.remove(Events::empty(&self.rx_ioevent)) 30 | .expect("Failed to remove rx ioevent"); 31 | ops.remove(Events::empty(&self.tx_ioevent)) 32 | .expect("Failed to remove tx ioevent"); 33 | ops.remove(Events::empty(&self.inner.tap)) 34 | .expect("Failed to remove tap event"); 35 | } 36 | } 37 | 38 | impl MutEventSubscriber for QueueHandler { 39 | fn process(&mut self, events: Events, ops: &mut EventOps) { 40 | // TODO: We can also consider panicking on the errors that cannot be generated 41 | // or influenced. 42 | 43 | if events.event_set() != EventSet::IN { 44 | self.handle_error("Unexpected event_set", ops); 45 | return; 46 | } 47 | 48 | match events.data() { 49 | TAPFD_DATA => { 50 | if let Err(e) = self.inner.process_tap() { 51 | self.handle_error(format!("Process tap error {:?}", e), ops); 52 | } 53 | } 54 | RX_IOEVENT_DATA => { 55 | if self.rx_ioevent.read().is_err() { 56 | self.handle_error("Rx ioevent read", ops); 57 | } else if let Err(e) = self.inner.process_rxq() { 58 | self.handle_error(format!("Process rx error {:?}", e), ops); 59 | } 60 | } 61 | TX_IOEVENT_DATA => { 62 | if self.tx_ioevent.read().is_err() { 63 | self.handle_error("Tx ioevent read", ops); 64 | } 65 | if let Err(e) = self.inner.process_txq() { 66 | self.handle_error(format!("Process tx error {:?}", e), ops); 67 | } 68 | } 69 | _ => self.handle_error("Unexpected data", ops), 70 | } 71 | } 72 | 73 | fn init(&mut self, ops: &mut EventOps) { 74 | ops.add(Events::with_data( 75 | &self.inner.tap, 76 | TAPFD_DATA, 77 | EventSet::IN | EventSet::EDGE_TRIGGERED, 78 | )) 79 | .expect("Unable to add tapfd"); 80 | 81 | ops.add(Events::with_data( 82 | &self.rx_ioevent, 83 | RX_IOEVENT_DATA, 84 | EventSet::IN, 85 | )) 86 | .expect("Unable to add rxfd"); 87 | 88 | ops.add(Events::with_data( 89 | &self.tx_ioevent, 90 | TX_IOEVENT_DATA, 91 | EventSet::IN, 92 | )) 93 | .expect("Unable to add txfd"); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/devices/src/virtio/net/simple_handler.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause 3 | 4 | use std::cmp; 5 | use std::io::{self, Read, Write}; 6 | use std::result; 7 | 8 | use log::warn; 9 | use virtio_queue::{DescriptorChain, Queue}; 10 | use vm_memory::{Bytes, GuestAddressSpace}; 11 | 12 | use crate::virtio::net::tap::Tap; 13 | use crate::virtio::net::{RXQ_INDEX, TXQ_INDEX}; 14 | use crate::virtio::SignalUsedQueue; 15 | 16 | // According to the standard: "If the VIRTIO_NET_F_GUEST_TSO4, VIRTIO_NET_F_GUEST_TSO6 or 17 | // VIRTIO_NET_F_GUEST_UFO features are used, the maximum incoming packet will be to 65550 18 | // bytes long (the maximum size of a TCP or UDP packet, plus the 14 byte ethernet header), 19 | // otherwise 1514 bytes. The 12-byte struct virtio_net_hdr is prepended to this, making for 20 | // 65562 or 1526 bytes." For transmission, the standard states "The header and packet are added 21 | // as one output descriptor to the transmitq, and the device is notified of the new entry". 22 | // We assume the TX frame will not exceed this size either. 23 | const MAX_BUFFER_SIZE: usize = 65562; 24 | 25 | #[derive(Debug)] 26 | pub enum Error { 27 | GuestMemory(vm_memory::GuestMemoryError), 28 | Queue(virtio_queue::Error), 29 | Tap(io::Error), 30 | } 31 | 32 | impl From for Error { 33 | fn from(e: virtio_queue::Error) -> Self { 34 | Error::Queue(e) 35 | } 36 | } 37 | 38 | // A simple handler implementation for a RX/TX queue pair, which does not make assumptions about 39 | // the way queue notification is implemented. The backend is not yet generic (we always assume a 40 | // `Tap` object), but we're looking at improving that going forward. 41 | // TODO: Find a better name. 42 | pub struct SimpleHandler { 43 | pub driver_notify: S, 44 | pub rxq: Queue, 45 | pub rxbuf_current: usize, 46 | pub rxbuf: [u8; MAX_BUFFER_SIZE], 47 | pub txq: Queue, 48 | pub txbuf: [u8; MAX_BUFFER_SIZE], 49 | pub tap: Tap, 50 | } 51 | 52 | impl SimpleHandler { 53 | pub fn new(driver_notify: S, rxq: Queue, txq: Queue, tap: Tap) -> Self { 54 | SimpleHandler { 55 | driver_notify, 56 | rxq, 57 | rxbuf_current: 0, 58 | rxbuf: [0u8; MAX_BUFFER_SIZE], 59 | txq, 60 | txbuf: [0u8; MAX_BUFFER_SIZE], 61 | tap, 62 | } 63 | } 64 | 65 | // Have to see how to approach error handling for the `Queue` implementation in particular, 66 | // because many situations are not really recoverable. We should consider reporting them based 67 | // on the metrics/events solution when they appear, and not propagate them further unless 68 | // it's really useful/necessary. 69 | fn write_frame_to_guest(&mut self) -> result::Result { 70 | let num_bytes = self.rxbuf_current; 71 | 72 | let mut chain = match self.rxq.iter()?.next() { 73 | Some(c) => c, 74 | _ => return Ok(false), 75 | }; 76 | 77 | let mut count = 0; 78 | let buf = &mut self.rxbuf[..num_bytes]; 79 | 80 | while let Some(desc) = chain.next() { 81 | let left = buf.len() - count; 82 | 83 | if left == 0 { 84 | break; 85 | } 86 | 87 | let len = cmp::min(left, desc.len() as usize); 88 | chain 89 | .memory() 90 | .write_slice(&buf[count..count + len], desc.addr()) 91 | .map_err(Error::GuestMemory)?; 92 | 93 | count += len; 94 | } 95 | 96 | if count != buf.len() { 97 | // The frame was too large for the chain. 98 | warn!("rx frame too large"); 99 | } 100 | 101 | self.rxq.add_used(chain.head_index(), count as u32)?; 102 | 103 | self.rxbuf_current = 0; 104 | 105 | Ok(true) 106 | } 107 | 108 | pub fn process_tap(&mut self) -> result::Result<(), Error> { 109 | loop { 110 | if self.rxbuf_current == 0 { 111 | match self.tap.read(&mut self.rxbuf) { 112 | Ok(n) => self.rxbuf_current = n, 113 | Err(_) => { 114 | // TODO: Do something (logs, metrics, etc.) in response to an error when 115 | // reading from tap. EAGAIN means there's nothing available to read anymore 116 | // (because we open the TAP as non-blocking). 117 | break; 118 | } 119 | } 120 | } 121 | 122 | if !self.write_frame_to_guest()? && !self.rxq.enable_notification()? { 123 | break; 124 | } 125 | } 126 | 127 | if self.rxq.needs_notification()? { 128 | self.driver_notify.signal_used_queue(RXQ_INDEX); 129 | } 130 | 131 | Ok(()) 132 | } 133 | 134 | fn send_frame_from_chain( 135 | &mut self, 136 | chain: &mut DescriptorChain, 137 | ) -> result::Result { 138 | let mut count = 0; 139 | 140 | while let Some(desc) = chain.next() { 141 | let left = self.txbuf.len() - count; 142 | let len = desc.len() as usize; 143 | 144 | if len > left { 145 | warn!("tx frame too large"); 146 | break; 147 | } 148 | 149 | chain 150 | .memory() 151 | .read_slice(&mut self.txbuf[count..count + len], desc.addr()) 152 | .map_err(Error::GuestMemory)?; 153 | 154 | count += len; 155 | } 156 | 157 | self.tap.write(&self.txbuf[..count]).map_err(Error::Tap)?; 158 | 159 | Ok(count as u32) 160 | } 161 | 162 | pub fn process_txq(&mut self) -> result::Result<(), Error> { 163 | loop { 164 | self.txq.disable_notification()?; 165 | 166 | while let Some(mut chain) = self.txq.iter()?.next() { 167 | self.send_frame_from_chain(&mut chain)?; 168 | 169 | self.txq.add_used(chain.head_index(), 0)?; 170 | 171 | if self.txq.needs_notification()? { 172 | self.driver_notify.signal_used_queue(TXQ_INDEX); 173 | } 174 | } 175 | 176 | if !self.txq.enable_notification()? { 177 | return Ok(()); 178 | } 179 | } 180 | } 181 | 182 | pub fn process_rxq(&mut self) -> result::Result<(), Error> { 183 | self.rxq.disable_notification()?; 184 | self.process_tap() 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /src/devices/src/virtio/net/tap.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | // 4 | // Portions Copyright 2017 The Chromium OS Authors. All rights reserved. 5 | // Use of this source code is governed by a BSD-style license that can be 6 | // found in the THIRD-PARTY file. 7 | 8 | // We should add a tap abstraction to rust-vmm as well. Using this one, which is copied from 9 | // Firecracker until then. 10 | 11 | use std::fs::File; 12 | use std::io::{Error as IoError, Read, Result as IoResult, Write}; 13 | use std::os::raw::{c_char, c_int, c_uint, c_ulong}; 14 | use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; 15 | 16 | use vmm_sys_util::ioctl::{ioctl_with_mut_ref, ioctl_with_ref, ioctl_with_val}; 17 | use vmm_sys_util::{ioctl_expr, ioctl_ioc_nr, ioctl_iow_nr}; 18 | 19 | use super::bindings::ifreq; 20 | 21 | // As defined in the Linux UAPI: 22 | // https://elixir.bootlin.com/linux/v4.17/source/include/uapi/linux/if.h#L33 23 | const IFACE_NAME_MAX_LEN: usize = 16; 24 | 25 | // Taken from firecracker net_gen/if_tun.rs ... we should see what to do about the net related 26 | // bindings overall for rust-vmm. 27 | const IFF_TAP: ::std::os::raw::c_uint = 2; 28 | const IFF_NO_PI: ::std::os::raw::c_uint = 4096; 29 | const IFF_VNET_HDR: ::std::os::raw::c_uint = 16384; 30 | 31 | /// List of errors the tap implementation can throw. 32 | #[derive(Debug)] 33 | pub enum Error { 34 | /// Unable to create tap interface. 35 | CreateTap(IoError), 36 | /// Invalid interface name. 37 | InvalidIfname, 38 | /// ioctl failed. 39 | IoctlError(IoError), 40 | /// Couldn't open /dev/net/tun. 41 | OpenTun(IoError), 42 | } 43 | 44 | pub type Result = ::std::result::Result; 45 | 46 | const TUNTAP: ::std::os::raw::c_uint = 84; 47 | ioctl_iow_nr!(TUNSETIFF, TUNTAP, 202, ::std::os::raw::c_int); 48 | ioctl_iow_nr!(TUNSETOFFLOAD, TUNTAP, 208, ::std::os::raw::c_uint); 49 | ioctl_iow_nr!(TUNSETVNETHDRSZ, TUNTAP, 216, ::std::os::raw::c_int); 50 | 51 | /// Handle for a network tap interface. 52 | /// 53 | /// For now, this simply wraps the file descriptor for the tap device so methods 54 | /// can run ioctls on the interface. The tap interface fd will be closed when 55 | /// Tap goes out of scope, and the kernel will clean up the interface automatically. 56 | #[derive(Debug)] 57 | pub struct Tap { 58 | tap_file: File, 59 | pub(crate) if_name: [u8; IFACE_NAME_MAX_LEN], 60 | } 61 | 62 | // Returns a byte vector representing the contents of a null terminated C string which 63 | // contains if_name. 64 | fn build_terminated_if_name(if_name: &str) -> Result<[u8; IFACE_NAME_MAX_LEN]> { 65 | // Convert the string slice to bytes, and shadow the variable, 66 | // since we no longer need the &str version. 67 | let if_name = if_name.as_bytes(); 68 | 69 | if if_name.len() >= IFACE_NAME_MAX_LEN { 70 | return Err(Error::InvalidIfname); 71 | } 72 | 73 | let mut terminated_if_name = [b'\0'; IFACE_NAME_MAX_LEN]; 74 | terminated_if_name[..if_name.len()].copy_from_slice(if_name); 75 | 76 | Ok(terminated_if_name) 77 | } 78 | 79 | pub struct IfReqBuilder(ifreq); 80 | 81 | impl IfReqBuilder { 82 | #[allow(clippy::new_without_default)] 83 | pub fn new() -> Self { 84 | Self(Default::default()) 85 | } 86 | 87 | pub fn if_name(mut self, if_name: &[u8; IFACE_NAME_MAX_LEN]) -> Self { 88 | // Since we don't call as_mut on the same union field more than once, this block is safe. 89 | let ifrn_name = unsafe { self.0.ifr_ifrn.ifrn_name.as_mut() }; 90 | ifrn_name.copy_from_slice(if_name.as_ref()); 91 | 92 | self 93 | } 94 | 95 | pub(crate) fn flags(mut self, flags: i16) -> Self { 96 | // Since we don't call as_mut on the same union field more than once, this block is safe. 97 | let ifru_flags = unsafe { self.0.ifr_ifru.ifru_flags.as_mut() }; 98 | *ifru_flags = flags; 99 | 100 | self 101 | } 102 | 103 | pub(crate) fn execute(mut self, socket: &F, ioctl: u64) -> Result { 104 | // ioctl is safe. Called with a valid socket fd, and we check the return. 105 | let ret = unsafe { ioctl_with_mut_ref(socket, ioctl, &mut self.0) }; 106 | if ret < 0 { 107 | return Err(Error::IoctlError(IoError::last_os_error())); 108 | } 109 | 110 | Ok(self.0) 111 | } 112 | } 113 | 114 | impl Tap { 115 | /// Create a TUN/TAP device given the interface name. 116 | /// # Arguments 117 | /// 118 | /// * `if_name` - the name of the interface. 119 | pub fn open_named(if_name: &str) -> Result { 120 | let terminated_if_name = build_terminated_if_name(if_name)?; 121 | 122 | let fd = unsafe { 123 | // Open calls are safe because we give a constant null-terminated 124 | // string and verify the result. 125 | libc::open( 126 | b"/dev/net/tun\0".as_ptr() as *const c_char, 127 | libc::O_RDWR | libc::O_NONBLOCK | libc::O_CLOEXEC, 128 | ) 129 | }; 130 | if fd < 0 { 131 | return Err(Error::OpenTun(IoError::last_os_error())); 132 | } 133 | // We just checked that the fd is valid. 134 | let tuntap = unsafe { File::from_raw_fd(fd) }; 135 | 136 | let ifreq = IfReqBuilder::new() 137 | .if_name(&terminated_if_name) 138 | .flags((IFF_TAP | IFF_NO_PI | IFF_VNET_HDR) as i16) 139 | .execute(&tuntap, TUNSETIFF())?; 140 | 141 | // Safe since only the name is accessed, and it's cloned out. 142 | Ok(Tap { 143 | tap_file: tuntap, 144 | if_name: unsafe { *ifreq.ifr_ifrn.ifrn_name.as_ref() }, 145 | }) 146 | } 147 | 148 | pub fn if_name_as_str(&self) -> &str { 149 | let len = self 150 | .if_name 151 | .iter() 152 | .position(|x| *x == 0) 153 | .unwrap_or(IFACE_NAME_MAX_LEN); 154 | std::str::from_utf8(&self.if_name[..len]).unwrap_or("") 155 | } 156 | 157 | /// Set the offload flags for the tap interface. 158 | pub fn set_offload(&self, flags: c_uint) -> Result<()> { 159 | // ioctl is safe. Called with a valid tap fd, and we check the return. 160 | let ret = unsafe { ioctl_with_val(&self.tap_file, TUNSETOFFLOAD(), c_ulong::from(flags)) }; 161 | if ret < 0 { 162 | return Err(Error::IoctlError(IoError::last_os_error())); 163 | } 164 | 165 | Ok(()) 166 | } 167 | 168 | /// Set the size of the vnet hdr. 169 | pub fn set_vnet_hdr_size(&self, size: c_int) -> Result<()> { 170 | // ioctl is safe. Called with a valid tap fd, and we check the return. 171 | let ret = unsafe { ioctl_with_ref(&self.tap_file, TUNSETVNETHDRSZ(), &size) }; 172 | if ret < 0 { 173 | return Err(Error::IoctlError(IoError::last_os_error())); 174 | } 175 | 176 | Ok(()) 177 | } 178 | } 179 | 180 | impl Read for Tap { 181 | fn read(&mut self, buf: &mut [u8]) -> IoResult { 182 | self.tap_file.read(buf) 183 | } 184 | } 185 | 186 | impl Write for Tap { 187 | fn write(&mut self, buf: &[u8]) -> IoResult { 188 | self.tap_file.write(buf) 189 | } 190 | 191 | fn flush(&mut self) -> IoResult<()> { 192 | Ok(()) 193 | } 194 | } 195 | 196 | impl AsRawFd for Tap { 197 | fn as_raw_fd(&self) -> RawFd { 198 | self.tap_file.as_raw_fd() 199 | } 200 | } 201 | 202 | // TODO: If we don't end up using an external abstraction for `Tap` interfaces, add unit tests 203 | // based on a mock framework that do not require elevated privileges to run. 204 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause 3 | use std::convert::TryFrom; 4 | use std::env; 5 | 6 | use api::Cli; 7 | use vmm::Vmm; 8 | 9 | fn main() { 10 | match Cli::launch( 11 | env::args() 12 | .collect::>() 13 | .iter() 14 | .map(|s| s.as_str()) 15 | .collect(), 16 | ) { 17 | Ok(vmm_config) => { 18 | let mut vmm = 19 | Vmm::try_from(vmm_config).expect("Failed to create VMM from configurations"); 20 | // For now we are just unwrapping here, in the future we might use a nicer way of 21 | // handling errors such as pretty printing them. 22 | vmm.run().unwrap(); 23 | } 24 | Err(e) => { 25 | eprintln!("Failed to parse command line options. {}", e); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/utils/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "utils" 3 | version = "0.1.0" 4 | authors = ["rust-vmm AWS maintainers "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | -------------------------------------------------------------------------------- /src/utils/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause 3 | 4 | pub mod resource_download; 5 | 6 | /// A macro for printing errors only in debug mode. 7 | #[macro_export] 8 | #[cfg(debug_assertions)] 9 | macro_rules! debug { 10 | ($( $args:expr ),*) => { eprintln!( $( $args ),* ); } 11 | } 12 | 13 | /// A macro that allows printing to be ignored in release mode. 14 | #[macro_export] 15 | #[cfg(not(debug_assertions))] 16 | macro_rules! debug { 17 | ($( $args:expr ),*) => { 18 | () 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /src/utils/src/resource_download.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause 3 | 4 | use std::path::PathBuf; 5 | use std::process::Command; 6 | 7 | #[derive(Debug, PartialEq, Eq)] 8 | pub enum Error { 9 | DownloadError(String), 10 | } 11 | 12 | /// Downloads from S3 the first resource that match the parameters: 13 | /// - `r_type`: the resource type; e.g. "kernel", "disk". 14 | /// - `r_tags`: optional tags to filter the resources; e.g. "{\"halt-after-boot\": true}" 15 | pub fn s3_download(r_type: &str, r_tags: Option<&str>) -> Result { 16 | let dld_script = format!( 17 | "{}/../../tests/tools/s3_download.py", 18 | env!("CARGO_MANIFEST_DIR") 19 | ); 20 | 21 | let output = Command::new(dld_script.as_str()) 22 | .arg("-t") 23 | .arg(r_type) 24 | .arg("--tags") 25 | .arg(r_tags.unwrap_or("{}")) 26 | .arg("-1") 27 | .output() 28 | .expect("failed to execute process"); 29 | 30 | if !output.status.success() { 31 | return Err(Error::DownloadError( 32 | String::from_utf8(output.stderr).unwrap(), 33 | )); 34 | } 35 | 36 | let res: String = String::from_utf8(output.stdout) 37 | .unwrap() 38 | .split('\n') 39 | .map(String::from) 40 | .next() 41 | .ok_or_else(|| Error::DownloadError(String::from("Not found.")))?; 42 | Ok(PathBuf::from(res)) 43 | } 44 | 45 | #[cfg(test)] 46 | mod tests { 47 | use super::*; 48 | 49 | #[test] 50 | fn test_error_cases() { 51 | assert!(matches!( 52 | s3_download("", None).unwrap_err(), 53 | Error::DownloadError(e) if e.contains("Missing required parameter") 54 | )); 55 | 56 | assert!(matches!( 57 | s3_download("random", None).unwrap_err(), 58 | Error::DownloadError(e) if e.contains("No resources found") 59 | )); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/vm-vcpu-ref/.buildkite/custom-tests.json: -------------------------------------------------------------------------------- 1 | { 2 | "tests": [ 3 | { 4 | "test_name": "vm-vcpu_coverage", 5 | "command": "cd src/vm-vcpu-ref/ && pytest rust-vmm-ci/integration_tests/test_coverage.py --test-scope crate", 6 | "platform": [ 7 | "x86_64" 8 | ] 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /src/vm-vcpu-ref/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vm-vcpu-ref" 3 | version = "0.1.0" 4 | authors = ["rust-vmm AWS maintainers "] 5 | edition = "2018" 6 | description = "Wrappers for setting up a VM for booting" 7 | repository = "https://github.com/rust-vmm/vmm-reference" 8 | readme = "README.md" 9 | license = "Apache-2.0 OR BSD-3-Clause" 10 | keywords = ["virt", "kvm", "vm"] 11 | 12 | [dependencies] 13 | thiserror = "1.0.30" 14 | kvm-ioctls = { version = "0.11.0" } 15 | kvm-bindings = { version = "0.5.0", features = ["fam-wrappers"] } 16 | vm-memory = "0.7.0" 17 | libc = "0.2.76" 18 | 19 | [dev-dependencies] 20 | vm-memory = { version = "0.7.0", features = ["backend-mmap"] } 21 | vmm-sys-util = "0.8.0" 22 | -------------------------------------------------------------------------------- /src/vm-vcpu-ref/README.md: -------------------------------------------------------------------------------- 1 | # Crate Name 2 | 3 | ## Design 4 | 5 | TODO: This section should have a high-level design of the crate. 6 | 7 | Some questions that might help in writing this section: 8 | - What is the purpose of this crate? 9 | - What are the main components of the crate? How do they interact which each 10 | other? 11 | 12 | ## Usage 13 | 14 | TODO: This section describes how the crate is used. 15 | 16 | Some questions that might help in writing this section: 17 | - What traits do users need to implement? 18 | - Does the crate have any default/optional features? What is each feature 19 | doing? 20 | - Is this crate used by other rust-vmm components? If yes, how? 21 | 22 | ## Examples 23 | 24 | TODO: Usage examples. 25 | 26 | ```rust 27 | use my_crate; 28 | 29 | ... 30 | ``` 31 | 32 | ## License 33 | 34 | **!!!NOTICE**: The BSD-3-Clause license is not included in this template. 35 | The license needs to be manually added because the text of the license file 36 | also includes the copyright. The copyright can be different for different 37 | crates. If the crate contains code from CrosVM, the crate must add the 38 | CrosVM copyright which can be found 39 | [here](https://chromium.googlesource.com/chromiumos/platform/crosvm/+/master/LICENSE). 40 | For crates developed from scratch, the copyright is different and depends on 41 | the contributors. 42 | -------------------------------------------------------------------------------- /src/vm-vcpu-ref/coverage_config_x86_64.json: -------------------------------------------------------------------------------- 1 | { 2 | "coverage_score": 88.9, 3 | "exclude_path": "mpspec.rs", 4 | "crate_features": "" 5 | } 6 | -------------------------------------------------------------------------------- /src/vm-vcpu-ref/rust-vmm-ci: -------------------------------------------------------------------------------- 1 | ../../rust-vmm-ci -------------------------------------------------------------------------------- /src/vm-vcpu-ref/src/aarch64/mod.rs: -------------------------------------------------------------------------------- 1 | #![cfg(target_arch = "aarch64")] 2 | 3 | /// Helpers for setting up the interrupt controller. 4 | pub mod interrupts; 5 | mod regs; 6 | -------------------------------------------------------------------------------- /src/vm-vcpu-ref/src/aarch64/regs/dist.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | use kvm_bindings::KVM_DEV_ARM_VGIC_GRP_DIST_REGS; 4 | use kvm_ioctls::DeviceFd; 5 | use std::ops::Range; 6 | 7 | use super::{get_regs_data, set_regs_data, GicRegState, MmioReg, Result, SimpleReg}; 8 | 9 | // As per virt/kvm/arm/vgic/vgic-kvm-device.c we need 10 | // the number of interrupts our GIC will support to be: 11 | // * bigger than 32 12 | // * less than 1023 and 13 | // * a multiple of 32. 14 | /// The highest usable SPI on aarch64. 15 | const IRQ_MAX: u32 = 128; 16 | 17 | /// First usable interrupt on aarch64. 18 | const IRQ_BASE: u32 = 32; 19 | 20 | // Distributor registers as detailed at page 456 from 21 | // https://developer.arm.com/documentation/ihi0069/c/. 22 | // Address offsets are relative to the Distributor base 23 | // address defined by the system memory map. 24 | const GICD_CTLR: DistReg = DistReg::simple(0x0, 4); 25 | const GICD_STATUSR: DistReg = DistReg::simple(0x0010, 4); 26 | const GICD_IGROUPR: DistReg = DistReg::shared_irq(0x0080, 1); 27 | const GICD_ISENABLER: DistReg = DistReg::shared_irq(0x0100, 1); 28 | const GICD_ICENABLER: DistReg = DistReg::shared_irq(0x0180, 1); 29 | const GICD_ISPENDR: DistReg = DistReg::shared_irq(0x0200, 1); 30 | const GICD_ICPENDR: DistReg = DistReg::shared_irq(0x0280, 1); 31 | const GICD_ISACTIVER: DistReg = DistReg::shared_irq(0x0300, 1); 32 | const GICD_ICACTIVER: DistReg = DistReg::shared_irq(0x0380, 1); 33 | const GICD_IPRIORITYR: DistReg = DistReg::shared_irq(0x0400, 8); 34 | const GICD_ICFGR: DistReg = DistReg::shared_irq(0x0C00, 2); 35 | const GICD_IROUTER: DistReg = DistReg::shared_irq(0x6000, 64); 36 | 37 | static VGIC_DIST_REGS: &[DistReg] = &[ 38 | GICD_CTLR, 39 | GICD_STATUSR, 40 | GICD_ICENABLER, 41 | GICD_ISENABLER, 42 | GICD_IGROUPR, 43 | GICD_IROUTER, 44 | GICD_ICFGR, 45 | GICD_ICPENDR, 46 | GICD_ISPENDR, 47 | GICD_ICACTIVER, 48 | GICD_ISACTIVER, 49 | GICD_IPRIORITYR, 50 | ]; 51 | 52 | /// Get distributor registers. 53 | pub fn dist_regs(fd: &DeviceFd) -> Result>> { 54 | get_regs_data( 55 | fd, 56 | VGIC_DIST_REGS.iter(), 57 | KVM_DEV_ARM_VGIC_GRP_DIST_REGS, 58 | 0, 59 | 0, 60 | ) 61 | } 62 | 63 | /// Set distributor registers. 64 | pub fn set_dist_regs(fd: &DeviceFd, dist: &[GicRegState]) -> Result<()> { 65 | set_regs_data( 66 | fd, 67 | VGIC_DIST_REGS.iter(), 68 | KVM_DEV_ARM_VGIC_GRP_DIST_REGS, 69 | dist, 70 | 0, 71 | 0, 72 | ) 73 | } 74 | 75 | enum DistReg { 76 | Simple(SimpleReg), 77 | SharedIrq(SharedIrqReg), 78 | } 79 | 80 | impl DistReg { 81 | const fn simple(offset: u64, size: u16) -> DistReg { 82 | DistReg::Simple(SimpleReg { offset, size }) 83 | } 84 | 85 | const fn shared_irq(offset: u64, bits_per_irq: u8) -> DistReg { 86 | DistReg::SharedIrq(SharedIrqReg { 87 | offset, 88 | bits_per_irq, 89 | }) 90 | } 91 | } 92 | 93 | impl MmioReg for DistReg { 94 | fn range(&self) -> Range { 95 | match self { 96 | DistReg::Simple(reg) => reg.range(), 97 | DistReg::SharedIrq(reg) => reg.range(), 98 | } 99 | } 100 | } 101 | 102 | /// Some registers have variable lengths since they dedicate a specific number of bits to 103 | /// each interrupt. So, their length depends on the number of interrupts. 104 | /// (i.e the ones that are represented as GICD_REG) in the documentation mentioned above. 105 | struct SharedIrqReg { 106 | /// The offset from the component address. The register is memory mapped here. 107 | offset: u64, 108 | /// Number of bits per interrupt. 109 | bits_per_irq: u8, 110 | } 111 | 112 | impl MmioReg for SharedIrqReg { 113 | fn range(&self) -> Range { 114 | // The ARM® TrustZone® implements a protection logic which contains a 115 | // read-as-zero/write-ignore (RAZ/WI) policy. 116 | // The first part of a shared-irq register, the one corresponding to the 117 | // SGI and PPI IRQs (0-32) is RAZ/WI, so we skip it. 118 | // 119 | // It's technically possible for this operation to overflow. 120 | // However, SharedIrqReg is only used to define register descriptors 121 | // with constant offsets and bits_per_irq, so any overflow would be detected 122 | // during testing. 123 | let start = self.offset + u64::from(IRQ_BASE) * u64::from(self.bits_per_irq) / 8; 124 | 125 | let size_in_bits = u64::from(self.bits_per_irq) * u64::from(IRQ_MAX - IRQ_BASE); 126 | let mut size_in_bytes = size_in_bits / 8; 127 | if size_in_bits % 8 > 0 { 128 | size_in_bytes += 1; 129 | } 130 | 131 | start..start + size_in_bytes 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/vm-vcpu-ref/src/aarch64/regs/icc.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | use kvm_bindings::{KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS, KVM_DEV_ARM_VGIC_V3_MPIDR_MASK}; 4 | use kvm_bindings::{ 5 | KVM_REG_ARM64_SYSREG_CRM_MASK, KVM_REG_ARM64_SYSREG_CRM_SHIFT, KVM_REG_ARM64_SYSREG_CRN_MASK, 6 | KVM_REG_ARM64_SYSREG_CRN_SHIFT, KVM_REG_ARM64_SYSREG_OP0_MASK, KVM_REG_ARM64_SYSREG_OP0_SHIFT, 7 | KVM_REG_ARM64_SYSREG_OP1_MASK, KVM_REG_ARM64_SYSREG_OP1_SHIFT, KVM_REG_ARM64_SYSREG_OP2_MASK, 8 | KVM_REG_ARM64_SYSREG_OP2_SHIFT, 9 | }; 10 | use kvm_ioctls::DeviceFd; 11 | 12 | use super::{ 13 | get_reg_data, get_regs_data, set_reg_data, set_regs_data, Error, GicRegState, Result, SimpleReg, 14 | }; 15 | 16 | const SYS_ICC_SRE_EL1: SimpleReg = SimpleReg::gic_sys_reg(3, 0, 12, 12, 5); 17 | const SYS_ICC_CTLR_EL1: SimpleReg = SimpleReg::gic_sys_reg(3, 0, 12, 12, 4); 18 | const SYS_ICC_IGRPEN0_EL1: SimpleReg = SimpleReg::gic_sys_reg(3, 0, 12, 12, 6); 19 | const SYS_ICC_IGRPEN1_EL1: SimpleReg = SimpleReg::gic_sys_reg(3, 0, 12, 12, 7); 20 | const SYS_ICC_PMR_EL1: SimpleReg = SimpleReg::gic_sys_reg(3, 0, 4, 6, 0); 21 | const SYS_ICC_BPR0_EL1: SimpleReg = SimpleReg::gic_sys_reg(3, 0, 12, 8, 3); 22 | const SYS_ICC_BPR1_EL1: SimpleReg = SimpleReg::gic_sys_reg(3, 0, 12, 12, 3); 23 | 24 | static MAIN_GIC_ICC_REGS: &[SimpleReg] = &[ 25 | SYS_ICC_SRE_EL1, 26 | SYS_ICC_CTLR_EL1, 27 | SYS_ICC_IGRPEN0_EL1, 28 | SYS_ICC_IGRPEN1_EL1, 29 | SYS_ICC_PMR_EL1, 30 | SYS_ICC_BPR0_EL1, 31 | SYS_ICC_BPR1_EL1, 32 | ]; 33 | 34 | const SYS_ICC_AP0R0_EL1: SimpleReg = SimpleReg::sys_icc_ap0rn_el1(0); 35 | const SYS_ICC_AP0R1_EL1: SimpleReg = SimpleReg::sys_icc_ap0rn_el1(1); 36 | const SYS_ICC_AP0R2_EL1: SimpleReg = SimpleReg::sys_icc_ap0rn_el1(2); 37 | const SYS_ICC_AP0R3_EL1: SimpleReg = SimpleReg::sys_icc_ap0rn_el1(3); 38 | const SYS_ICC_AP1R0_EL1: SimpleReg = SimpleReg::sys_icc_ap1rn_el1(0); 39 | const SYS_ICC_AP1R1_EL1: SimpleReg = SimpleReg::sys_icc_ap1rn_el1(1); 40 | const SYS_ICC_AP1R2_EL1: SimpleReg = SimpleReg::sys_icc_ap1rn_el1(2); 41 | const SYS_ICC_AP1R3_EL1: SimpleReg = SimpleReg::sys_icc_ap1rn_el1(3); 42 | 43 | static AP_GIC_ICC_REGS: &[SimpleReg] = &[ 44 | SYS_ICC_AP0R0_EL1, 45 | SYS_ICC_AP0R1_EL1, 46 | SYS_ICC_AP0R2_EL1, 47 | SYS_ICC_AP0R3_EL1, 48 | SYS_ICC_AP1R0_EL1, 49 | SYS_ICC_AP1R1_EL1, 50 | SYS_ICC_AP1R2_EL1, 51 | SYS_ICC_AP1R3_EL1, 52 | ]; 53 | 54 | const ICC_CTLR_EL1_PRIBITS_SHIFT: u64 = 8; 55 | const ICC_CTLR_EL1_PRIBITS_MASK: u64 = 7 << ICC_CTLR_EL1_PRIBITS_SHIFT; 56 | 57 | /// Structure for serializing the state of the GIC ICC regs 58 | #[derive(Clone, Debug, PartialEq, Eq, Default)] 59 | pub struct GicSysRegsState { 60 | main_icc_regs: Vec>, 61 | ap_icc_regs: Vec>>, 62 | } 63 | 64 | impl SimpleReg { 65 | const fn gic_sys_reg(op0: u64, op1: u64, crn: u64, crm: u64, op2: u64) -> SimpleReg { 66 | let offset = (((op0 as u64) << KVM_REG_ARM64_SYSREG_OP0_SHIFT) 67 | & KVM_REG_ARM64_SYSREG_OP0_MASK as u64) 68 | | (((op1 as u64) << KVM_REG_ARM64_SYSREG_OP1_SHIFT) 69 | & KVM_REG_ARM64_SYSREG_OP1_MASK as u64) 70 | | (((crn as u64) << KVM_REG_ARM64_SYSREG_CRN_SHIFT) 71 | & KVM_REG_ARM64_SYSREG_CRN_MASK as u64) 72 | | (((crm as u64) << KVM_REG_ARM64_SYSREG_CRM_SHIFT) 73 | & KVM_REG_ARM64_SYSREG_CRM_MASK as u64) 74 | | (((op2 as u64) << KVM_REG_ARM64_SYSREG_OP2_SHIFT) 75 | & KVM_REG_ARM64_SYSREG_OP2_MASK as u64); 76 | 77 | SimpleReg { offset, size: 8 } 78 | } 79 | 80 | const fn sys_icc_ap0rn_el1(n: u64) -> SimpleReg { 81 | Self::gic_sys_reg(3, 0, 12, 8, 4 | n) 82 | } 83 | 84 | const fn sys_icc_ap1rn_el1(n: u64) -> SimpleReg { 85 | Self::gic_sys_reg(3, 0, 12, 9, n) 86 | } 87 | } 88 | 89 | /// Get vCPU GIC system registers. 90 | pub fn icc_regs(fd: &DeviceFd, mpidr: u64) -> Result { 91 | let main_icc_regs = get_regs_data( 92 | fd, 93 | MAIN_GIC_ICC_REGS.iter(), 94 | KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS, 95 | mpidr, 96 | KVM_DEV_ARM_VGIC_V3_MPIDR_MASK as u64, 97 | )?; 98 | 99 | let num_priority_bits = num_priority_bits(fd, mpidr)?; 100 | 101 | let mut ap_icc_regs = Vec::with_capacity(AP_GIC_ICC_REGS.len()); 102 | for reg in AP_GIC_ICC_REGS { 103 | if is_ap_reg_available(reg, num_priority_bits) { 104 | ap_icc_regs.push(Some(get_reg_data( 105 | fd, 106 | reg, 107 | KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS, 108 | mpidr, 109 | KVM_DEV_ARM_VGIC_V3_MPIDR_MASK as u64, 110 | )?)); 111 | } else { 112 | ap_icc_regs.push(None); 113 | } 114 | } 115 | 116 | Ok(GicSysRegsState { 117 | main_icc_regs, 118 | ap_icc_regs, 119 | }) 120 | } 121 | 122 | /// Set vCPU GIC system registers. 123 | pub fn set_icc_regs(fd: &DeviceFd, state: &GicSysRegsState, mpidr: u64) -> Result<()> { 124 | set_regs_data( 125 | fd, 126 | MAIN_GIC_ICC_REGS.iter(), 127 | KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS, 128 | &state.main_icc_regs, 129 | mpidr, 130 | KVM_DEV_ARM_VGIC_V3_MPIDR_MASK as u64, 131 | )?; 132 | 133 | let num_priority_bits = num_priority_bits(fd, mpidr)?; 134 | 135 | for (reg, maybe_reg_data) in AP_GIC_ICC_REGS.iter().zip(&state.ap_icc_regs) { 136 | if is_ap_reg_available(reg, num_priority_bits) != maybe_reg_data.is_some() { 137 | return Err(Error::InvalidGicSysRegState); 138 | } 139 | 140 | if let Some(reg_data) = maybe_reg_data { 141 | set_reg_data( 142 | fd, 143 | reg, 144 | KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS, 145 | reg_data, 146 | mpidr, 147 | KVM_DEV_ARM_VGIC_V3_MPIDR_MASK as u64, 148 | )?; 149 | } 150 | } 151 | 152 | Ok(()) 153 | } 154 | 155 | fn num_priority_bits(fd: &DeviceFd, mpidr: u64) -> Result { 156 | let reg_val: u64 = get_reg_data( 157 | fd, 158 | &SYS_ICC_CTLR_EL1, 159 | KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS, 160 | mpidr, 161 | KVM_DEV_ARM_VGIC_V3_MPIDR_MASK as u64, 162 | )? 163 | .chunks[0]; 164 | 165 | Ok(((reg_val & ICC_CTLR_EL1_PRIBITS_MASK) >> ICC_CTLR_EL1_PRIBITS_SHIFT) + 1) 166 | } 167 | 168 | fn is_ap_reg_available(reg: &SimpleReg, num_priority_bits: u64) -> bool { 169 | // As per ARMv8 documentation: 170 | // https://developer.arm.com/documentation/ihi0069/c/ 171 | // page 178, 172 | // ICC_AP0R1_EL1 is only implemented in implementations that support 6 or more bits of 173 | // priority. 174 | // ICC_AP0R2_EL1 and ICC_AP0R3_EL1 are only implemented in implementations that support 175 | // 7 bits of priority. 176 | if (reg == &SYS_ICC_AP0R1_EL1 || reg == &SYS_ICC_AP1R1_EL1) && num_priority_bits < 6 { 177 | return false; 178 | } 179 | if (reg == &SYS_ICC_AP0R2_EL1 180 | || reg == &SYS_ICC_AP0R3_EL1 181 | || reg == &SYS_ICC_AP1R2_EL1 182 | || reg == &SYS_ICC_AP1R3_EL1) 183 | && num_priority_bits != 7 184 | { 185 | return false; 186 | } 187 | 188 | true 189 | } 190 | -------------------------------------------------------------------------------- /src/vm-vcpu-ref/src/aarch64/regs/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | use kvm_bindings::{ 4 | kvm_device_attr, KVM_DEV_ARM_VGIC_GRP_CTRL, KVM_DEV_ARM_VGIC_SAVE_PENDING_TABLES, 5 | }; 6 | use kvm_ioctls::DeviceFd; 7 | use std::iter::StepBy; 8 | use std::ops::Range; 9 | 10 | use super::interrupts::{Error, Result}; 11 | pub use dist::{dist_regs, set_dist_regs}; 12 | pub use icc::{icc_regs, set_icc_regs, GicSysRegsState}; 13 | pub use redist::{redist_regs, set_redist_regs}; 14 | 15 | mod dist; 16 | mod icc; 17 | mod redist; 18 | 19 | /// Generic GIC register state, 20 | #[derive(Clone, Debug, PartialEq, Eq)] 21 | pub struct GicRegState { 22 | pub(crate) chunks: Vec, 23 | } 24 | 25 | /// Function that flushes RDIST pending tables into guest RAM. 26 | /// 27 | /// The tables get flushed to guest RAM whenever the VM gets stopped. 28 | pub fn save_pending_tables(fd: &DeviceFd) -> Result<()> { 29 | let init_gic_attr = kvm_device_attr { 30 | group: KVM_DEV_ARM_VGIC_GRP_CTRL, 31 | attr: KVM_DEV_ARM_VGIC_SAVE_PENDING_TABLES as u64, 32 | addr: 0, 33 | flags: 0, 34 | }; 35 | fd.set_device_attr(&init_gic_attr)?; 36 | Ok(()) 37 | } 38 | 39 | /// Process the content of the MPIDR_EL1 register in order to be able to pass it to KVM 40 | /// 41 | /// The kernel expects to find the four affinity levels of the MPIDR in the first 32 bits of the 42 | /// VGIC register attribute: 43 | /// https://elixir.free-electrons.com/linux/v4.14.203/source/virt/kvm/arm/vgic/vgic-kvm-device.c#L445. 44 | /// 45 | /// The format of the MPIDR_EL1 register is: 46 | /// | 39 .... 32 | 31 .... 24 | 23 .... 16 | 15 .... 8 | 7 .... 0 | 47 | /// | Aff3 | Other | Aff2 | Aff1 | Aff0 | 48 | /// 49 | /// The KVM mpidr format is: 50 | /// | 63 .... 56 | 55 .... 48 | 47 .... 40 | 39 .... 32 | 51 | /// | Aff3 | Aff2 | Aff1 | Aff0 | 52 | /// As specified in the linux kernel: Documentation/virt/kvm/devices/arm-vgic-v3.rst 53 | pub fn convert_to_kvm_mpidrs(mut mpidrs: Vec) -> Vec { 54 | for mpidr in mpidrs.iter_mut() { 55 | let cpu_affid = ((*mpidr & 0xFF_0000_0000) >> 8) | (*mpidr & 0xFF_FFFF); 56 | *mpidr = cpu_affid << 32; 57 | } 58 | mpidrs 59 | } 60 | 61 | // Helper trait for working with the different types of the GIC registers 62 | // in a unified manner. 63 | trait MmioReg { 64 | fn range(&self) -> Range; 65 | 66 | fn iter(&self) -> StepBy> 67 | where 68 | Self: Sized, 69 | { 70 | self.range().step_by(std::mem::size_of::()) 71 | } 72 | } 73 | 74 | fn set_regs_data<'a, Reg, RegChunk>( 75 | fd: &DeviceFd, 76 | regs: impl Iterator, 77 | group: u32, 78 | data: &[GicRegState], 79 | mpidr: u64, 80 | mpidr_mask: u64, 81 | ) -> Result<()> 82 | where 83 | Reg: MmioReg + 'a, 84 | RegChunk: Clone, 85 | { 86 | for (reg, reg_data) in regs.zip(data) { 87 | set_reg_data(fd, reg, group, reg_data, mpidr, mpidr_mask)?; 88 | } 89 | Ok(()) 90 | } 91 | 92 | fn set_reg_data( 93 | fd: &DeviceFd, 94 | reg: &Reg, 95 | group: u32, 96 | data: &GicRegState, 97 | mpidr: u64, 98 | mpidr_mask: u64, 99 | ) -> Result<()> 100 | where 101 | Reg: MmioReg, 102 | RegChunk: Clone, 103 | { 104 | for (offset, val) in reg.iter::().zip(&data.chunks) { 105 | let mut tmp = (*val).clone(); 106 | fd.set_device_attr(&kvm_device_attr(group, offset, &mut tmp, mpidr, mpidr_mask))?; 107 | } 108 | 109 | Ok(()) 110 | } 111 | 112 | fn get_regs_data<'a, Reg, RegChunk>( 113 | fd: &DeviceFd, 114 | regs: impl Iterator, 115 | group: u32, 116 | mpidr: u64, 117 | mpidr_mask: u64, 118 | ) -> Result>> 119 | where 120 | Reg: MmioReg + 'a, 121 | RegChunk: Default, 122 | { 123 | let mut data = Vec::new(); 124 | for reg in regs { 125 | data.push(get_reg_data(fd, reg, group, mpidr, mpidr_mask)?); 126 | } 127 | 128 | Ok(data) 129 | } 130 | 131 | fn get_reg_data( 132 | fd: &DeviceFd, 133 | reg: &Reg, 134 | group: u32, 135 | mpidr: u64, 136 | mpidr_mask: u64, 137 | ) -> Result> 138 | where 139 | Reg: MmioReg, 140 | RegChunk: Default, 141 | { 142 | let mut data = Vec::with_capacity(reg.iter::().count()); 143 | for offset in reg.iter::() { 144 | let mut val = RegChunk::default(); 145 | fd.get_device_attr(&mut kvm_device_attr( 146 | group, offset, &mut val, mpidr, mpidr_mask, 147 | ))?; 148 | data.push(val); 149 | } 150 | 151 | Ok(GicRegState { chunks: data }) 152 | } 153 | 154 | fn kvm_device_attr( 155 | group: u32, 156 | offset: u64, 157 | val: &mut RegChunk, 158 | mpidr: u64, 159 | mpidr_mask: u64, 160 | ) -> kvm_device_attr { 161 | kvm_device_attr { 162 | group, 163 | attr: (mpidr & mpidr_mask) | offset, 164 | addr: val as *mut RegChunk as u64, 165 | flags: 0, 166 | } 167 | } 168 | 169 | /// Structure representing a simple register. 170 | #[derive(PartialEq, Eq)] 171 | struct SimpleReg { 172 | /// The offset from the component address. The register is memory mapped here. 173 | offset: u64, 174 | /// Size in bytes. 175 | size: u16, 176 | } 177 | 178 | impl SimpleReg { 179 | const fn new(offset: u64, size: u16) -> Self { 180 | Self { offset, size } 181 | } 182 | } 183 | 184 | impl MmioReg for SimpleReg { 185 | fn range(&self) -> Range { 186 | // It's technically possible for this addition to overflow. 187 | // However, SimpleReg is only used to define register descriptors 188 | // with constant offsets and sizes, so any overflow would be detected 189 | // during testing. 190 | self.offset..self.offset + u64::from(self.size) 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /src/vm-vcpu-ref/src/aarch64/regs/redist.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | use kvm_bindings::{KVM_DEV_ARM_VGIC_GRP_REDIST_REGS, KVM_DEV_ARM_VGIC_V3_MPIDR_MASK}; 4 | use kvm_ioctls::DeviceFd; 5 | 6 | use super::{get_regs_data, set_regs_data, GicRegState, Result, SimpleReg}; 7 | 8 | // Relevant PPI redistributor registers that we want to save/restore. 9 | const GICR_CTLR: SimpleReg = SimpleReg::new(0x0000, 4); 10 | const GICR_STATUSR: SimpleReg = SimpleReg::new(0x0010, 4); 11 | const GICR_WAKER: SimpleReg = SimpleReg::new(0x0014, 4); 12 | const GICR_PROPBASER: SimpleReg = SimpleReg::new(0x0070, 8); 13 | const GICR_PENDBASER: SimpleReg = SimpleReg::new(0x0078, 8); 14 | 15 | // Relevant SGI redistributor registers that we want to save/restore. 16 | const GICR_SGI_OFFSET: u64 = 0x0001_0000; 17 | const GICR_IGROUPR0: SimpleReg = SimpleReg::new(GICR_SGI_OFFSET + 0x0080, 4); 18 | const GICR_ISENABLER0: SimpleReg = SimpleReg::new(GICR_SGI_OFFSET + 0x0100, 4); 19 | const GICR_ICENABLER0: SimpleReg = SimpleReg::new(GICR_SGI_OFFSET + 0x0180, 4); 20 | const GICR_ISPENDR0: SimpleReg = SimpleReg::new(GICR_SGI_OFFSET + 0x0200, 4); 21 | const GICR_ICPENDR0: SimpleReg = SimpleReg::new(GICR_SGI_OFFSET + 0x0280, 4); 22 | const GICR_ISACTIVER0: SimpleReg = SimpleReg::new(GICR_SGI_OFFSET + 0x0300, 4); 23 | const GICR_ICACTIVER0: SimpleReg = SimpleReg::new(GICR_SGI_OFFSET + 0x0380, 4); 24 | const GICR_IPRIORITYR0: SimpleReg = SimpleReg::new(GICR_SGI_OFFSET + 0x0400, 32); 25 | const GICR_ICFGR0: SimpleReg = SimpleReg::new(GICR_SGI_OFFSET + 0x0C00, 8); 26 | 27 | // List with relevant redistributor registers and SGI associated redistributor 28 | // registers that we will be restoring. 29 | static VGIC_RDIST_AND_SGI_REGS: &[SimpleReg] = &[ 30 | GICR_CTLR, 31 | GICR_STATUSR, 32 | GICR_WAKER, 33 | GICR_PROPBASER, 34 | GICR_PENDBASER, 35 | GICR_IGROUPR0, 36 | GICR_ICENABLER0, 37 | GICR_ISENABLER0, 38 | GICR_ICFGR0, 39 | GICR_ICPENDR0, 40 | GICR_ISPENDR0, 41 | GICR_ICACTIVER0, 42 | GICR_ISACTIVER0, 43 | GICR_IPRIORITYR0, 44 | ]; 45 | 46 | /// Get vCPU redistributor registers. 47 | pub fn redist_regs(fd: &DeviceFd, mpidr: u64) -> Result>> { 48 | get_regs_data( 49 | fd, 50 | VGIC_RDIST_AND_SGI_REGS.iter(), 51 | KVM_DEV_ARM_VGIC_GRP_REDIST_REGS, 52 | mpidr, 53 | KVM_DEV_ARM_VGIC_V3_MPIDR_MASK as u64, 54 | ) 55 | } 56 | 57 | /// Set vCPU redistributor registers. 58 | pub fn set_redist_regs(fd: &DeviceFd, redist: &[GicRegState], mpidr: u64) -> Result<()> { 59 | set_regs_data( 60 | fd, 61 | VGIC_RDIST_AND_SGI_REGS.iter(), 62 | KVM_DEV_ARM_VGIC_GRP_REDIST_REGS, 63 | redist, 64 | mpidr, 65 | KVM_DEV_ARM_VGIC_V3_MPIDR_MASK as u64, 66 | )?; 67 | Ok(()) 68 | } 69 | -------------------------------------------------------------------------------- /src/vm-vcpu-ref/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | #![deny(missing_docs)] 4 | //! The `vm-vcpu-ref` crate provides abstractions for setting up the `VM` and `vCPUs` for booting. 5 | //! The high level interface exported by this crate is uniform on both supported platforms 6 | //! (x86_64 and aarch64). Differences only arise in configuration parameters as there are 7 | //! features only supported on one platform (i.e. CPUID on x86_64), and in the saved/restored 8 | //! state as both platforms define registers and VM/vCPU specific features differently. 9 | 10 | /// Helpers for setting up the `VM` for running on x86_64. 11 | pub mod x86_64; 12 | 13 | /// Helpers for setting up the `VM` for running on aarch64. 14 | pub mod aarch64; 15 | -------------------------------------------------------------------------------- /src/vm-vcpu-ref/src/x86_64/cpuid.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // Copyright 2017 The Chromium OS Authors. All rights reserved. 3 | // SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause 4 | 5 | use kvm_bindings::CpuId; 6 | use kvm_ioctls::{Cap::TscDeadlineTimer, Kvm}; 7 | 8 | // CPUID bits in ebx, ecx, and edx. 9 | const EBX_CLFLUSH_CACHELINE: u32 = 8; // Flush a cache line size. 10 | const EBX_CLFLUSH_SIZE_SHIFT: u32 = 8; // Bytes flushed when executing CLFLUSH. 11 | const EBX_CPU_COUNT_SHIFT: u32 = 16; // Index of this CPU. 12 | const EBX_CPUID_SHIFT: u32 = 24; // Index of this CPU. 13 | const ECX_EPB_SHIFT: u32 = 3; // "Energy Performance Bias" bit. 14 | const ECX_TSC_DEADLINE_TIMER_SHIFT: u32 = 24; // TSC deadline mode of APIC timer 15 | const ECX_HYPERVISOR_SHIFT: u32 = 31; // Flag to be set when the cpu is running on a hypervisor. 16 | const EDX_HTT_SHIFT: u32 = 28; // Hyper Threading Enabled. 17 | 18 | /// Updates the passed `cpuid` such that it can be used for configuring a vCPU 19 | /// for running. 20 | /// 21 | /// # Example 22 | /// 23 | /// We are recommending the `cpuid` to be created from the supported CPUID on 24 | /// the running host. 25 | /// 26 | /// ```rust 27 | /// use kvm_bindings::CpuId; 28 | /// use kvm_ioctls::{Error, Kvm}; 29 | /// use vm_vcpu_ref::x86_64::cpuid::filter_cpuid; 30 | /// 31 | /// fn default_cpuid(cpu_index: u8, num_vcpus: u8) -> Result { 32 | /// let kvm = Kvm::new()?; 33 | /// let mut cpuid = kvm.get_supported_cpuid(kvm_bindings::KVM_MAX_CPUID_ENTRIES)?; 34 | /// filter_cpuid(&kvm, cpu_index, num_vcpus, &mut cpuid); 35 | /// Ok(cpuid) 36 | /// } 37 | /// 38 | /// # default_cpuid(0, 1).unwrap(); 39 | /// ``` 40 | pub fn filter_cpuid(kvm: &Kvm, vcpu_id: u8, cpu_count: u8, cpuid: &mut CpuId) { 41 | for entry in cpuid.as_mut_slice().iter_mut() { 42 | match entry.function { 43 | 0x01 => { 44 | // X86 hypervisor feature. 45 | if entry.index == 0 { 46 | entry.ecx |= 1 << ECX_HYPERVISOR_SHIFT; 47 | } 48 | if kvm.check_extension(TscDeadlineTimer) { 49 | entry.ecx |= 1 << ECX_TSC_DEADLINE_TIMER_SHIFT; 50 | } 51 | entry.ebx = ((vcpu_id as u32) << EBX_CPUID_SHIFT) as u32 52 | | (EBX_CLFLUSH_CACHELINE << EBX_CLFLUSH_SIZE_SHIFT); 53 | if cpu_count > 1 { 54 | entry.ebx |= (cpu_count as u32) << EBX_CPU_COUNT_SHIFT; 55 | entry.edx |= 1 << EDX_HTT_SHIFT; 56 | } 57 | } 58 | 0x06 => { 59 | // Clear X86 EPB feature. No frequency selection in the hypervisor. 60 | entry.ecx &= !(1 << ECX_EPB_SHIFT); 61 | } 62 | 0x0B => { 63 | // EDX bits 31..0 contain x2APIC ID of current logical processor. 64 | entry.edx = vcpu_id as u32; 65 | } 66 | _ => (), 67 | } 68 | } 69 | } 70 | 71 | #[cfg(test)] 72 | mod tests { 73 | use super::*; 74 | use vmm_sys_util::fam::FamStruct; 75 | 76 | #[test] 77 | fn test_filter_cpuid() { 78 | // This is a bit of an artificial test because there's not much we can 79 | // validate at the unit test level. 80 | let vcpu_id = 0; 81 | let kvm = Kvm::new().unwrap(); 82 | 83 | let mut cpuid = kvm 84 | .get_supported_cpuid(kvm_bindings::KVM_MAX_CPUID_ENTRIES) 85 | .unwrap(); 86 | let before_len = cpuid.as_fam_struct_ref().len(); 87 | filter_cpuid(&kvm, vcpu_id, 1, &mut cpuid); 88 | 89 | // Check that no new entries than the supported ones are added. 90 | assert_eq!(cpuid.as_fam_struct_ref().len(), before_len); 91 | 92 | // Check that setting this cpuid to a vcpu does not yield an error. 93 | let vm = kvm.create_vm().unwrap(); 94 | let vcpu = vm.create_vcpu(0).unwrap(); 95 | vcpu.set_cpuid2(&cpuid).unwrap(); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/vm-vcpu-ref/src/x86_64/interrupts.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | // 4 | // Portions Copyright 2017 The Chromium OS Authors. All rights reserved. 5 | // Use of this source code is governed by a BSD-style license that can be 6 | // found in the THIRD-PARTY file. 7 | #![cfg(target_arch = "x86_64")] 8 | use kvm_bindings::kvm_lapic_state; 9 | 10 | // Helper function that writes the input (which must have 4 bytes) into an i32. 11 | fn read_le_i32(input: &[i8]) -> i32 { 12 | assert_eq!(input.len(), 4); 13 | let mut array = [0u8; 4]; 14 | for (byte, read) in array.iter_mut().zip(input.iter().cloned()) { 15 | *byte = read as u8; 16 | } 17 | i32::from_le_bytes(array) 18 | } 19 | 20 | // Helper function that writes bytes from an i32 into `buf`. 21 | fn write_le_i32(buf: &mut [i8], n: i32) { 22 | for (byte, read) in buf.iter_mut().zip(i32::to_le_bytes(n).iter().cloned()) { 23 | *byte = read as i8; 24 | } 25 | } 26 | 27 | // Defines poached from apicdef.h kernel header. 28 | /// The register offset corresponding to the APIC Local Vector Table for LINT0. 29 | pub const APIC_LVT0_REG_OFFSET: usize = 0x350; 30 | /// The register offset corresponding to the APIC Local Vector Table for LINT1. 31 | pub const APIC_LVT1_REG_OFFSET: usize = 0x360; 32 | 33 | /// Specifies the type of interrupt to be sent to the processor. 34 | #[derive(Debug, PartialEq, Eq)] 35 | // We user upper case acronyms so that we can stick to the names as they're 36 | // defined in the Intel Manual. 37 | #[allow(clippy::upper_case_acronyms)] 38 | pub enum DeliveryMode { 39 | /// A fixed interrupt - delivers the interrupt specified in the vector field. 40 | Fixed = 0b000, 41 | /// System Management Interrupt. 42 | SMI = 0b010, 43 | /// Non Maskable Interrupt. 44 | NMI = 0b100, 45 | /// This type of interrupt causes the processor to perform an INIT. 46 | INIT = 0b101, 47 | /// External Interrupt. 48 | ExtINT = 0b111, 49 | } 50 | 51 | /// Errors associated with operations related to interrupts. 52 | #[derive(Debug, PartialEq, Eq, thiserror::Error)] 53 | pub enum Error { 54 | /// The register offset is invalid. 55 | #[error("The register offset is invalid.")] 56 | InvalidRegisterOffset, 57 | } 58 | 59 | /// Specialized result type for operations related to interrupts. 60 | pub type Result = std::result::Result; 61 | 62 | /// Return the value of the register specified by `reg_offset`. Returns an error when the 63 | /// offset is invalid. 64 | pub fn get_klapic_reg(klapic: &kvm_lapic_state, reg_offset: usize) -> Result { 65 | let range = reg_offset..reg_offset + 4; 66 | let reg = klapic.regs.get(range).ok_or(Error::InvalidRegisterOffset)?; 67 | Ok(read_le_i32(reg)) 68 | } 69 | 70 | /// Set the `value` of the register located at `reg_offset`. Returns an error when the offset is 71 | /// invalid. 72 | pub fn set_klapic_reg(klapic: &mut kvm_lapic_state, reg_offset: usize, value: i32) -> Result<()> { 73 | // The value that we are setting is a u32, which needs 4 bytes of space. 74 | // We're here creating a range that can fit the whole value. 75 | let range = reg_offset..reg_offset + 4; 76 | let reg = klapic 77 | .regs 78 | .get_mut(range) 79 | .ok_or(Error::InvalidRegisterOffset)?; 80 | write_le_i32(reg, value); 81 | Ok(()) 82 | } 83 | 84 | fn set_apic_delivery_mode(reg: i32, mode: i32) -> i32 { 85 | ((reg) & !0x700) | ((mode) << 8) 86 | } 87 | 88 | /// Set the Local APIC delivery mode. Returns an error when the register configuration 89 | /// is invalid. 90 | /// 91 | /// **Note**: setting the Local APIC (using the kvm function `set_lapic`) MUST happen 92 | /// after creating the IRQ chip. Otherwise, KVM returns an invalid argument (errno 22). 93 | /// 94 | /// # Arguments 95 | /// * `klapic`: The corresponding `kvm_lapic_state` for which to set the delivery mode. 96 | /// * `reg_offset`: The offset that identifies the register for which to set the delivery mode. 97 | /// Available options exported by this module are: [APIC_LVT0_REG_OFFSET] and 98 | /// [APIC_LVT1_REG_OFFSET]. 99 | /// * `mode`: The APIC mode to set. 100 | /// 101 | /// # Example - Configure LAPIC with KVM 102 | /// ```rust 103 | /// # use kvm_ioctls::Kvm; 104 | /// use kvm_ioctls::{Error, VcpuFd}; 105 | /// use vm_vcpu_ref::x86_64::interrupts::{ 106 | /// set_klapic_delivery_mode, DeliveryMode, APIC_LVT0_REG_OFFSET, APIC_LVT1_REG_OFFSET, 107 | /// }; 108 | /// 109 | /// fn configure_default_lapic(vcpu_fd: &mut VcpuFd) -> Result<(), Error> { 110 | /// let mut klapic = vcpu_fd.get_lapic()?; 111 | /// 112 | /// set_klapic_delivery_mode(&mut klapic, APIC_LVT0_REG_OFFSET, DeliveryMode::ExtINT).unwrap(); 113 | /// set_klapic_delivery_mode(&mut klapic, APIC_LVT1_REG_OFFSET, DeliveryMode::NMI).unwrap(); 114 | /// vcpu_fd.set_lapic(&klapic) 115 | /// } 116 | /// 117 | /// # let kvm = Kvm::new().unwrap(); 118 | /// # let vm = kvm.create_vm().unwrap(); 119 | /// # vm.create_irq_chip().unwrap(); 120 | /// # let mut vcpu = vm.create_vcpu(0).unwrap(); 121 | /// # configure_default_lapic(&mut vcpu).unwrap(); 122 | /// ``` 123 | pub fn set_klapic_delivery_mode( 124 | klapic: &mut kvm_lapic_state, 125 | reg_offset: usize, 126 | mode: DeliveryMode, 127 | ) -> Result<()> { 128 | let reg_value = get_klapic_reg(klapic, reg_offset)?; 129 | set_klapic_reg( 130 | klapic, 131 | reg_offset, 132 | set_apic_delivery_mode(reg_value, mode as i32), 133 | ) 134 | } 135 | 136 | #[cfg(test)] 137 | mod tests { 138 | use crate::x86_64::interrupts::{ 139 | get_klapic_reg, set_klapic_delivery_mode, DeliveryMode, Error, 140 | }; 141 | use kvm_bindings::kvm_lapic_state; 142 | 143 | #[test] 144 | fn test_reg_offset() { 145 | // The size of `regs` in `kvm_lapic_state` is 1024. Since we're setting a value of 146 | // 4 bytes, if we want to set it at offset = 1020 it should fit. 147 | let offset = 1020; 148 | let mut klapic = kvm_lapic_state::default(); 149 | assert!(set_klapic_delivery_mode(&mut klapic, offset, DeliveryMode::ExtINT).is_ok()); 150 | assert!(get_klapic_reg(&klapic, offset).is_ok()); 151 | 152 | // Setting at the offset og 1021 does not work because 4 bytes don't fit. 153 | let offset = 1021; 154 | let mut klapic = kvm_lapic_state::default(); 155 | assert_eq!( 156 | set_klapic_delivery_mode(&mut klapic, offset, DeliveryMode::ExtINT).unwrap_err(), 157 | Error::InvalidRegisterOffset 158 | ); 159 | assert_eq!( 160 | get_klapic_reg(&klapic, offset).unwrap_err(), 161 | Error::InvalidRegisterOffset 162 | ); 163 | } 164 | 165 | #[test] 166 | fn test_delivery_mode() { 167 | assert_eq!(DeliveryMode::Fixed as i32, 0); 168 | assert_eq!(DeliveryMode::SMI as i32, 0x2); 169 | assert_eq!(DeliveryMode::NMI as i32, 0x4); 170 | assert_eq!(DeliveryMode::INIT as i32, 0x5); 171 | assert_eq!(DeliveryMode::ExtINT as i32, 0x7); 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /src/vm-vcpu-ref/src/x86_64/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | #![cfg(target_arch = "x86_64")] 4 | /// Abstractions for a basic filtering of `CPUID`. 5 | pub mod cpuid; 6 | /// Abstractions for building a Global Descriptor Table (GDT). 7 | pub mod gdt; 8 | /// Helpers for setting up interrupt related registers. 9 | pub mod interrupts; 10 | /// MP Table defines autogenerated from Linux: `arch/x86/include/asm/mpspec_def.h`. 11 | pub mod mpspec; 12 | /// Abstractions for building a Multi Processor (MP) Table. 13 | pub mod mptable; 14 | /// MSR defines autogenerated from Linux: `arch/x86/include/asm/msr-index.h`. 15 | pub mod msr_index; 16 | /// Abstractions for building and filtering MSR (Model Specific Register). 17 | pub mod msrs; 18 | -------------------------------------------------------------------------------- /src/vm-vcpu-ref/src/x86_64/msrs.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause 3 | use kvm_bindings::{kvm_msr_entry, Msrs}; 4 | use kvm_ioctls::Kvm; 5 | 6 | use crate::x86_64::msr_index::*; 7 | 8 | /// Errors associated with operations on MSRs. 9 | #[derive(Debug, PartialEq)] 10 | pub enum Error { 11 | /// Failed to initialize MSRS. 12 | CreateMsrs, 13 | /// Failed to get supported MSRs. 14 | GetSupportedMSR(kvm_ioctls::Error), 15 | } 16 | /// Specialized result type for operations on MSRs. 17 | pub type Result = std::result::Result; 18 | 19 | /// Base MSR for APIC 20 | const APIC_BASE_MSR: u32 = 0x800; 21 | 22 | /// Number of APIC MSR indexes 23 | const APIC_MSR_INDEXES: u32 = 0x400; 24 | 25 | /// Custom MSRs fall in the range 0x4b564d00-0x4b564dff 26 | const MSR_KVM_WALL_CLOCK_NEW: u32 = 0x4b56_4d00; 27 | const MSR_KVM_SYSTEM_TIME_NEW: u32 = 0x4b56_4d01; 28 | const MSR_KVM_ASYNC_PF_EN: u32 = 0x4b56_4d02; 29 | const MSR_KVM_STEAL_TIME: u32 = 0x4b56_4d03; 30 | const MSR_KVM_PV_EOI_EN: u32 = 0x4b56_4d04; 31 | 32 | /// Taken from arch/x86/include/asm/msr-index.h 33 | const MSR_IA32_SPEC_CTRL: u32 = 0x0000_0048; 34 | const MSR_IA32_PRED_CMD: u32 = 0x0000_0049; 35 | 36 | /// Creates and populates required MSR entries for booting Linux on X86_64. 37 | /// 38 | /// # Example - Set boot MSRs 39 | /// 40 | /// ```rust 41 | /// use kvm_ioctls::Kvm; 42 | /// use vm_vcpu_ref::x86_64::msrs::create_boot_msr_entries; 43 | /// 44 | /// let kvm = Kvm::new().unwrap(); 45 | /// let vm = kvm.create_vm().unwrap(); 46 | /// let vcpu = vm.create_vcpu(0).unwrap(); 47 | /// 48 | /// vcpu.set_msrs(&create_boot_msr_entries().unwrap()).unwrap(); 49 | /// ``` 50 | pub fn create_boot_msr_entries() -> Result { 51 | let msr_entry_default = |msr| kvm_msr_entry { 52 | index: msr, 53 | data: 0x0, 54 | ..Default::default() 55 | }; 56 | 57 | let raw_msrs = vec![ 58 | msr_entry_default(MSR_IA32_SYSENTER_CS), 59 | msr_entry_default(MSR_IA32_SYSENTER_ESP), 60 | msr_entry_default(MSR_IA32_SYSENTER_EIP), 61 | // x86_64 specific msrs, we only run on x86_64 not x86. 62 | msr_entry_default(MSR_STAR), 63 | msr_entry_default(MSR_CSTAR), 64 | msr_entry_default(MSR_KERNEL_GS_BASE), 65 | msr_entry_default(MSR_SYSCALL_MASK), 66 | msr_entry_default(MSR_LSTAR), 67 | // end of x86_64 specific code 68 | msr_entry_default(MSR_IA32_TSC), 69 | kvm_msr_entry { 70 | index: MSR_IA32_MISC_ENABLE, 71 | data: u64::from(MSR_IA32_MISC_ENABLE_FAST_STRING), 72 | ..Default::default() 73 | }, 74 | ]; 75 | 76 | Msrs::from_entries(&raw_msrs).map_err(|_| Error::CreateMsrs) 77 | } 78 | 79 | /// MSR range 80 | struct MsrRange { 81 | /// Base MSR address 82 | base: u32, 83 | /// Number of MSRs 84 | nmsrs: u32, 85 | } 86 | 87 | impl MsrRange { 88 | /// Returns whether `msr` is contained in this MSR range. 89 | fn contains(&self, msr: u32) -> bool { 90 | self.base <= msr && msr < self.base + self.nmsrs 91 | } 92 | } 93 | 94 | // Creates a MsrRange of one msr given as argument. 95 | macro_rules! SINGLE_MSR { 96 | ($msr:expr) => { 97 | MsrRange { 98 | base: $msr, 99 | nmsrs: 1, 100 | } 101 | }; 102 | } 103 | 104 | // Creates a MsrRange of with msr base and count given as arguments. 105 | macro_rules! MSR_RANGE { 106 | ($first:expr, $count:expr) => { 107 | MsrRange { 108 | base: $first, 109 | nmsrs: $count, 110 | } 111 | }; 112 | } 113 | 114 | // List of MSRs that can be serialized. List is sorted in ascending order of MSRs addresses. 115 | static ALLOWED_MSR_RANGES: &[MsrRange] = &[ 116 | SINGLE_MSR!(MSR_IA32_P5_MC_ADDR), 117 | SINGLE_MSR!(MSR_IA32_P5_MC_TYPE), 118 | SINGLE_MSR!(MSR_IA32_TSC), 119 | SINGLE_MSR!(MSR_IA32_PLATFORM_ID), 120 | SINGLE_MSR!(MSR_IA32_APICBASE), 121 | SINGLE_MSR!(MSR_IA32_EBL_CR_POWERON), 122 | SINGLE_MSR!(MSR_EBC_FREQUENCY_ID), 123 | SINGLE_MSR!(MSR_SMI_COUNT), 124 | SINGLE_MSR!(MSR_IA32_FEATURE_CONTROL), 125 | SINGLE_MSR!(MSR_IA32_TSC_ADJUST), 126 | SINGLE_MSR!(MSR_IA32_SPEC_CTRL), 127 | SINGLE_MSR!(MSR_IA32_PRED_CMD), 128 | SINGLE_MSR!(MSR_IA32_UCODE_WRITE), 129 | SINGLE_MSR!(MSR_IA32_UCODE_REV), 130 | SINGLE_MSR!(MSR_IA32_SMBASE), 131 | SINGLE_MSR!(MSR_FSB_FREQ), 132 | SINGLE_MSR!(MSR_PLATFORM_INFO), 133 | SINGLE_MSR!(MSR_PKG_CST_CONFIG_CONTROL), 134 | SINGLE_MSR!(MSR_IA32_MPERF), 135 | SINGLE_MSR!(MSR_IA32_APERF), 136 | SINGLE_MSR!(MSR_MTRRcap), 137 | SINGLE_MSR!(MSR_IA32_BBL_CR_CTL3), 138 | SINGLE_MSR!(MSR_IA32_SYSENTER_CS), 139 | SINGLE_MSR!(MSR_IA32_SYSENTER_ESP), 140 | SINGLE_MSR!(MSR_IA32_SYSENTER_EIP), 141 | SINGLE_MSR!(MSR_IA32_MCG_CAP), 142 | SINGLE_MSR!(MSR_IA32_MCG_STATUS), 143 | SINGLE_MSR!(MSR_IA32_MCG_CTL), 144 | SINGLE_MSR!(MSR_IA32_PERF_STATUS), 145 | SINGLE_MSR!(MSR_IA32_MISC_ENABLE), 146 | SINGLE_MSR!(MSR_MISC_FEATURE_CONTROL), 147 | SINGLE_MSR!(MSR_MISC_PWR_MGMT), 148 | SINGLE_MSR!(MSR_TURBO_RATIO_LIMIT), 149 | SINGLE_MSR!(MSR_TURBO_RATIO_LIMIT1), 150 | SINGLE_MSR!(MSR_IA32_DEBUGCTLMSR), 151 | SINGLE_MSR!(MSR_IA32_LASTBRANCHFROMIP), 152 | SINGLE_MSR!(MSR_IA32_LASTBRANCHTOIP), 153 | SINGLE_MSR!(MSR_IA32_LASTINTFROMIP), 154 | SINGLE_MSR!(MSR_IA32_LASTINTTOIP), 155 | SINGLE_MSR!(MSR_IA32_POWER_CTL), 156 | MSR_RANGE!( 157 | // IA32_MTRR_PHYSBASE0 158 | 0x200, 0x100 159 | ), 160 | MSR_RANGE!( 161 | // MSR_CORE_C3_RESIDENCY 162 | // MSR_CORE_C6_RESIDENCY 163 | // MSR_CORE_C7_RESIDENCY 164 | MSR_CORE_C3_RESIDENCY, 165 | 3 166 | ), 167 | MSR_RANGE!(MSR_IA32_MC0_CTL, 0x80), 168 | SINGLE_MSR!(MSR_RAPL_POWER_UNIT), 169 | MSR_RANGE!( 170 | // MSR_PKGC3_IRTL 171 | // MSR_PKGC6_IRTL 172 | // MSR_PKGC7_IRTL 173 | MSR_PKGC3_IRTL, 174 | 3 175 | ), 176 | SINGLE_MSR!(MSR_PKG_POWER_LIMIT), 177 | SINGLE_MSR!(MSR_PKG_ENERGY_STATUS), 178 | SINGLE_MSR!(MSR_PKG_PERF_STATUS), 179 | SINGLE_MSR!(MSR_PKG_POWER_INFO), 180 | SINGLE_MSR!(MSR_DRAM_POWER_LIMIT), 181 | SINGLE_MSR!(MSR_DRAM_ENERGY_STATUS), 182 | SINGLE_MSR!(MSR_DRAM_PERF_STATUS), 183 | SINGLE_MSR!(MSR_DRAM_POWER_INFO), 184 | SINGLE_MSR!(MSR_CONFIG_TDP_NOMINAL), 185 | SINGLE_MSR!(MSR_CONFIG_TDP_LEVEL_1), 186 | SINGLE_MSR!(MSR_CONFIG_TDP_LEVEL_2), 187 | SINGLE_MSR!(MSR_CONFIG_TDP_CONTROL), 188 | SINGLE_MSR!(MSR_TURBO_ACTIVATION_RATIO), 189 | SINGLE_MSR!(MSR_IA32_TSCDEADLINE), 190 | MSR_RANGE!(APIC_BASE_MSR, APIC_MSR_INDEXES), 191 | SINGLE_MSR!(MSR_IA32_BNDCFGS), 192 | SINGLE_MSR!(MSR_KVM_WALL_CLOCK_NEW), 193 | SINGLE_MSR!(MSR_KVM_SYSTEM_TIME_NEW), 194 | SINGLE_MSR!(MSR_KVM_ASYNC_PF_EN), 195 | SINGLE_MSR!(MSR_KVM_STEAL_TIME), 196 | SINGLE_MSR!(MSR_KVM_PV_EOI_EN), 197 | SINGLE_MSR!(MSR_EFER), 198 | SINGLE_MSR!(MSR_STAR), 199 | SINGLE_MSR!(MSR_LSTAR), 200 | SINGLE_MSR!(MSR_CSTAR), 201 | SINGLE_MSR!(MSR_SYSCALL_MASK), 202 | SINGLE_MSR!(MSR_FS_BASE), 203 | SINGLE_MSR!(MSR_GS_BASE), 204 | SINGLE_MSR!(MSR_KERNEL_GS_BASE), 205 | SINGLE_MSR!(MSR_TSC_AUX), 206 | ]; 207 | 208 | /// Specifies whether a particular MSR should be included in vcpu serialization. 209 | /// 210 | /// # Arguments 211 | /// 212 | /// * `index` - The index of the MSR that is checked whether it's needed for serialization. 213 | fn msr_should_serialize(index: u32) -> bool { 214 | // Denied MSRs not exported by Linux: IA32_FEATURE_CONTROL and IA32_MCG_CTL 215 | if index == MSR_IA32_FEATURE_CONTROL || index == MSR_IA32_MCG_CTL { 216 | return false; 217 | }; 218 | ALLOWED_MSR_RANGES.iter().any(|range| range.contains(index)) 219 | } 220 | 221 | /// Returns the list of supported, serializable MSRs. 222 | /// 223 | /// # Arguments 224 | /// 225 | /// * `kvm_fd` - Structure that holds the KVM's fd. 226 | pub fn supported_guest_msrs(kvm_fd: &Kvm) -> Result { 227 | let mut msr_list = kvm_fd 228 | .get_msr_index_list() 229 | .map_err(Error::GetSupportedMSR)?; 230 | 231 | msr_list.retain(|msr_index| msr_should_serialize(*msr_index)); 232 | 233 | let mut msrs = 234 | Msrs::new(msr_list.as_fam_struct_ref().nmsrs as usize).map_err(|_| Error::CreateMsrs)?; 235 | let indices = msr_list.as_slice(); 236 | let msr_entries = msrs.as_mut_slice(); 237 | // We created the msrs from the msr_list. If the size is not the same, 238 | // there is a fatal programming error. 239 | assert_eq!(indices.len(), msr_entries.len()); 240 | for (pos, index) in indices.iter().enumerate() { 241 | msr_entries[pos].index = *index; 242 | } 243 | 244 | Ok(msrs) 245 | } 246 | 247 | #[cfg(test)] 248 | mod tests { 249 | use crate::x86_64::msrs::{create_boot_msr_entries, supported_guest_msrs}; 250 | use kvm_ioctls::Kvm; 251 | 252 | #[test] 253 | fn test_create_boot_msrs() { 254 | // This is a rather dummy test to check that creating the MSRs that we 255 | // need for booting can be initialized into the `Msrs` type without 256 | // yielding any error. 257 | let kvm = Kvm::new().unwrap(); 258 | let vm = kvm.create_vm().unwrap(); 259 | let vcpu = vm.create_vcpu(0).unwrap(); 260 | 261 | let boot_msrs = create_boot_msr_entries().unwrap(); 262 | assert!(vcpu.set_msrs(&boot_msrs).is_ok()) 263 | } 264 | 265 | #[test] 266 | fn test_supported_guest_msrs() { 267 | // This is a rather dummy test to check that with a basic initialization 268 | // we don't hit any errors. There is not much we can test here. 269 | let kvm = Kvm::new().unwrap(); 270 | let vm = kvm.create_vm().unwrap(); 271 | let vcpu = vm.create_vcpu(0).unwrap(); 272 | 273 | let mut msrs = supported_guest_msrs(&kvm).unwrap(); 274 | let expected_nmsrs = msrs.as_fam_struct_ref().nmsrs as usize; 275 | let actual_nmsrs = vcpu.get_msrs(&mut msrs).unwrap(); 276 | assert_eq!(expected_nmsrs, actual_nmsrs); 277 | } 278 | } 279 | -------------------------------------------------------------------------------- /src/vm-vcpu/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vm-vcpu" 3 | version = "0.1.0" 4 | authors = ["rust-vmm AWS maintainers "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | thiserror = "1.0.30" 11 | libc = "0.2.76" 12 | kvm-bindings = { version = "0.5.0", features = ["fam-wrappers"] } 13 | kvm-ioctls = "0.11.0" 14 | vm-memory = "0.7.0" 15 | vmm-sys-util = ">=0.8.0" 16 | vm-device = "0.1.0" 17 | 18 | utils = { path = "../utils" } 19 | vm-vcpu-ref = { path = "../vm-vcpu-ref" } 20 | arch = { path = "../arch" } 21 | 22 | [dev-dependencies] 23 | vm-memory = { version = "0.7.0", features = ["backend-mmap"] } 24 | -------------------------------------------------------------------------------- /src/vm-vcpu/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause 3 | pub mod vcpu; 4 | pub mod vm; 5 | -------------------------------------------------------------------------------- /src/vm-vcpu/src/vcpu/regs.rs: -------------------------------------------------------------------------------- 1 | use kvm_bindings::*; 2 | use kvm_ioctls::VcpuFd; 3 | 4 | use super::Error; 5 | 6 | macro_rules! arm64_core_reg { 7 | ($reg: tt) => { 8 | KVM_REG_ARM64 9 | | KVM_REG_SIZE_U64 10 | | u64::from(KVM_REG_ARM_CORE) 11 | | ((offset__of!(kvm_bindings::user_pt_regs, $reg) / 4) as u64) 12 | }; 13 | } 14 | 15 | // This macro gets the offset of a structure (i.e `str`) member (i.e `field`) without having 16 | // an instance of that structure. 17 | #[macro_export] 18 | macro_rules! offset__of { 19 | ($str:ty, $($field:ident)+) => ({ 20 | let tmp: std::mem::MaybeUninit<$str> = std::mem::MaybeUninit::uninit(); 21 | // Safe because we are not using the value of tmp. 22 | let tmp = unsafe { tmp.assume_init() }; 23 | let base = &tmp as *const _ as usize; 24 | let member = &tmp.$($field)* as *const _ as usize; 25 | 26 | member - base 27 | }); 28 | } 29 | 30 | // Compute the ID of a specific ARM64 system register similar to how 31 | // the kernel C macro does. 32 | // https://elixir.bootlin.com/linux/v4.20.17/source/arch/arm64/include/uapi/asm/kvm.h#L203 33 | const fn arm64_sys_reg(op0: u64, op1: u64, crn: u64, crm: u64, op2: u64) -> u64 { 34 | KVM_REG_ARM64 35 | | KVM_REG_SIZE_U64 36 | | KVM_REG_ARM64_SYSREG as u64 37 | | ((op0 << KVM_REG_ARM64_SYSREG_OP0_SHIFT) & KVM_REG_ARM64_SYSREG_OP0_MASK as u64) 38 | | ((op1 << KVM_REG_ARM64_SYSREG_OP1_SHIFT) & KVM_REG_ARM64_SYSREG_OP1_MASK as u64) 39 | | ((crn << KVM_REG_ARM64_SYSREG_CRN_SHIFT) & KVM_REG_ARM64_SYSREG_CRN_MASK as u64) 40 | | ((crm << KVM_REG_ARM64_SYSREG_CRM_SHIFT) & KVM_REG_ARM64_SYSREG_CRM_MASK as u64) 41 | | ((op2 << KVM_REG_ARM64_SYSREG_OP2_SHIFT) & KVM_REG_ARM64_SYSREG_OP2_MASK as u64) 42 | } 43 | 44 | // The MPIDR_EL1 register ID is defined in the kernel: 45 | // https://elixir.bootlin.com/linux/v4.20.17/source/arch/arm64/include/asm/sysreg.h#L135 46 | const MPIDR_EL1: u64 = arm64_sys_reg(3, 0, 0, 0, 5); 47 | 48 | // Get the values of all vCPU registers. The value of MPIDR register is also 49 | // returned as the second element of the tuple as an optimization to prevent 50 | // linear scan. This value is needed when saving the GIC state. 51 | pub fn get_regs_and_mpidr(vcpu_fd: &VcpuFd) -> Result<(Vec, u64), Error> { 52 | // Get IDs of all registers available to the guest. 53 | // For ArmV8 there are less than 500 registers. 54 | let mut reg_id_list = RegList::new(500).map_err(Error::FamError)?; 55 | vcpu_fd 56 | .get_reg_list(&mut reg_id_list) 57 | .map_err(Error::VcpuGetRegList)?; 58 | 59 | let mut mpidr = None; 60 | let mut regs = Vec::with_capacity(reg_id_list.as_slice().len()); 61 | for &id in reg_id_list.as_slice() { 62 | let addr = vcpu_fd.get_one_reg(id).map_err(Error::VcpuGetReg)?; 63 | regs.push(kvm_one_reg { id, addr }); 64 | 65 | if id == MPIDR_EL1 { 66 | mpidr = Some(addr); 67 | } 68 | } 69 | 70 | if mpidr.is_none() { 71 | return Err(Error::VcpuGetMpidrReg); 72 | } 73 | 74 | // unwrap() is safe because of the is_none() check above 75 | Ok((regs, mpidr.unwrap())) 76 | } 77 | -------------------------------------------------------------------------------- /src/vmm/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vmm" 3 | version = "0.1.0" 4 | authors = ["rust-vmm AWS maintainers "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | event-manager = "0.2.1" 9 | kvm-bindings = { version = "0.5.0", features = ["fam-wrappers"] } 10 | kvm-ioctls = "0.11.0" 11 | libc = "0.2.91" 12 | linux-loader = { version = "0.4.0", features = ["bzimage", "elf"] } 13 | vm-allocator = "0.1.0" 14 | vm-memory = { version = "0.7.0", features = ["backend-mmap"] } 15 | vm-superio = "0.5.0" 16 | vmm-sys-util = "0.8.0" 17 | vm-device = "0.1.0" 18 | 19 | devices = { path = "../devices" } 20 | vm-vcpu = { path = "../vm-vcpu" } 21 | vm-vcpu-ref = { path = "../vm-vcpu-ref" } 22 | utils = { path = "../utils" } 23 | arch = { path = "../arch" } 24 | -------------------------------------------------------------------------------- /src/vmm/src/boot.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause 3 | #![cfg(target_arch = "x86_64")] 4 | use std::result; 5 | 6 | use linux_loader::{bootparam::boot_params, loader::KernelLoaderResult}; 7 | use vm_memory::{Address, GuestAddress, GuestMemory, GuestMemoryMmap}; 8 | 9 | // x86_64 boot constants. See https://www.kernel.org/doc/Documentation/x86/boot.txt for the full 10 | // documentation. 11 | // Header field: `boot_flag`. Must contain 0xaa55. This is the closest thing old Linux kernels 12 | // have to a magic number. 13 | const KERNEL_BOOT_FLAG_MAGIC: u16 = 0xaa55; 14 | // Header field: `header`. Must contain the magic number `HdrS` (0x5372_6448). 15 | const KERNEL_HDR_MAGIC: u32 = 0x5372_6448; 16 | // Header field: `type_of_loader`. Unless using a pre-registered bootloader (which we aren't), this 17 | // field must be set to 0xff. 18 | const KERNEL_LOADER_OTHER: u8 = 0xff; 19 | // Header field: `kernel_alignment`. Alignment unit required by a relocatable kernel. 20 | const KERNEL_MIN_ALIGNMENT_BYTES: u32 = 0x0100_0000; 21 | 22 | // Start address for the EBDA (Extended Bios Data Area). Older computers (like the one this VMM 23 | // emulates) typically use 1 KiB for the EBDA, starting at 0x9fc00. 24 | // See https://wiki.osdev.org/Memory_Map_(x86) for more information. 25 | const EBDA_START: u64 = 0x0009_fc00; 26 | // RAM memory type. 27 | // TODO: this should be bindgen'ed and exported by linux-loader. 28 | // See https://github.com/rust-vmm/linux-loader/issues/51 29 | const E820_RAM: u32 = 1; 30 | 31 | #[derive(Debug, PartialEq, Eq)] 32 | /// Errors pertaining to boot parameter setup. 33 | pub enum Error { 34 | /// Invalid E820 configuration. 35 | E820Configuration, 36 | /// Highmem start address is past the guest memory end. 37 | HimemStartPastMemEnd, 38 | /// Highmem start address is past the MMIO gap start. 39 | HimemStartPastMmioGapStart, 40 | /// The MMIO gap end is past the guest memory end. 41 | MmioGapPastMemEnd, 42 | /// The MMIO gap start is past the gap end. 43 | MmioGapStartPastMmioGapEnd, 44 | } 45 | 46 | fn add_e820_entry( 47 | params: &mut boot_params, 48 | addr: u64, 49 | size: u64, 50 | mem_type: u32, 51 | ) -> result::Result<(), Error> { 52 | if params.e820_entries >= params.e820_table.len() as u8 { 53 | return Err(Error::E820Configuration); 54 | } 55 | 56 | params.e820_table[params.e820_entries as usize].addr = addr; 57 | params.e820_table[params.e820_entries as usize].size = size; 58 | params.e820_table[params.e820_entries as usize].type_ = mem_type; 59 | params.e820_entries += 1; 60 | 61 | Ok(()) 62 | } 63 | 64 | /// Build boot parameters for ELF kernels following the Linux boot protocol. 65 | /// 66 | /// # Arguments 67 | /// 68 | /// * `guest_memory` - guest memory. 69 | /// * `kernel_load` - result of loading the kernel in guest memory. 70 | /// * `himem_start` - address where high memory starts. 71 | /// * `mmio_gap_start` - address where the MMIO gap starts. 72 | /// * `mmio_gap_end` - address where the MMIO gap ends. 73 | pub fn build_bootparams( 74 | guest_memory: &GuestMemoryMmap, 75 | kernel_load: &KernelLoaderResult, 76 | himem_start: GuestAddress, 77 | mmio_gap_start: GuestAddress, 78 | mmio_gap_end: GuestAddress, 79 | ) -> result::Result { 80 | if mmio_gap_start >= mmio_gap_end { 81 | return Err(Error::MmioGapStartPastMmioGapEnd); 82 | } 83 | 84 | let mut params = boot_params::default(); 85 | 86 | if let Some(hdr) = kernel_load.setup_header { 87 | params.hdr = hdr; 88 | } else { 89 | params.hdr.boot_flag = KERNEL_BOOT_FLAG_MAGIC; 90 | params.hdr.header = KERNEL_HDR_MAGIC; 91 | params.hdr.kernel_alignment = KERNEL_MIN_ALIGNMENT_BYTES; 92 | } 93 | // If the header copied from the bzImage file didn't set type_of_loader, 94 | // force it to "undefined" so that the guest can boot normally. 95 | // See: https://github.com/cloud-hypervisor/cloud-hypervisor/issues/918 96 | // and: https://www.kernel.org/doc/html/latest/x86/boot.html#details-of-header-fields 97 | if params.hdr.type_of_loader == 0 { 98 | params.hdr.type_of_loader = KERNEL_LOADER_OTHER; 99 | } 100 | 101 | // Add an entry for EBDA itself. 102 | add_e820_entry(&mut params, 0, EBDA_START, E820_RAM)?; 103 | 104 | // Add entries for the usable RAM regions (potentially surrounding the MMIO gap). 105 | let last_addr = guest_memory.last_addr(); 106 | if last_addr < mmio_gap_start { 107 | add_e820_entry( 108 | &mut params, 109 | himem_start.raw_value(), 110 | // The unchecked + 1 is safe because: 111 | // * overflow could only occur if last_addr - himem_start == u64::MAX 112 | // * last_addr is smaller than mmio_gap_start, a valid u64 value 113 | // * last_addr - himem_start is also smaller than mmio_gap_start 114 | last_addr 115 | .checked_offset_from(himem_start) 116 | .ok_or(Error::HimemStartPastMemEnd)? 117 | + 1, 118 | E820_RAM, 119 | )?; 120 | } else { 121 | add_e820_entry( 122 | &mut params, 123 | himem_start.raw_value(), 124 | mmio_gap_start 125 | .checked_offset_from(himem_start) 126 | .ok_or(Error::HimemStartPastMmioGapStart)?, 127 | E820_RAM, 128 | )?; 129 | 130 | if last_addr > mmio_gap_end { 131 | add_e820_entry( 132 | &mut params, 133 | mmio_gap_end.raw_value(), 134 | // The unchecked_offset_from is safe, guaranteed by the `if` condition above. 135 | // The unchecked + 1 is safe because: 136 | // * overflow could only occur if last_addr == u64::MAX and mmio_gap_end == 0 137 | // * mmio_gap_end > mmio_gap_start, which is a valid u64 => mmio_gap_end > 0 138 | last_addr.unchecked_offset_from(mmio_gap_end) + 1, 139 | E820_RAM, 140 | )?; 141 | } 142 | } 143 | 144 | Ok(params) 145 | } 146 | 147 | #[cfg(test)] 148 | mod tests { 149 | use super::*; 150 | 151 | use crate::{DEFAULT_HIGH_RAM_START, MMIO_GAP_END, MMIO_GAP_START}; 152 | use linux_loader::bootparam; 153 | 154 | #[test] 155 | fn test_build_bootparams() { 156 | let guest_memory = GuestMemoryMmap::default(); 157 | let mut kern_load_res = KernelLoaderResult::default(); 158 | 159 | // Error case: MMIO gap start address is past its end address. 160 | assert_eq!( 161 | build_bootparams( 162 | &guest_memory, 163 | &kern_load_res, 164 | GuestAddress(DEFAULT_HIGH_RAM_START), 165 | GuestAddress(MMIO_GAP_START), 166 | GuestAddress(MMIO_GAP_START - 1) 167 | ) 168 | .err(), 169 | Some(Error::MmioGapStartPastMmioGapEnd) 170 | ); 171 | 172 | // Error case: high memory starts after guest memory ends. 173 | let guest_memory = 174 | GuestMemoryMmap::from_ranges(&[(GuestAddress(0), DEFAULT_HIGH_RAM_START as usize - 1)]) 175 | .unwrap(); 176 | assert_eq!( 177 | build_bootparams( 178 | &guest_memory, 179 | &kern_load_res, 180 | GuestAddress(DEFAULT_HIGH_RAM_START), 181 | GuestAddress(MMIO_GAP_START), 182 | GuestAddress(MMIO_GAP_END) 183 | ) 184 | .err(), 185 | Some(Error::HimemStartPastMemEnd) 186 | ); 187 | 188 | // Error case: MMIO gap starts before high memory. 189 | let guest_memory = GuestMemoryMmap::from_ranges(&[ 190 | (GuestAddress(0), MMIO_GAP_START as usize), 191 | (GuestAddress(MMIO_GAP_END), 0x1000), 192 | ]) 193 | .unwrap(); 194 | assert_eq!( 195 | build_bootparams( 196 | &guest_memory, 197 | &kern_load_res, 198 | GuestAddress(MMIO_GAP_START + 1), 199 | GuestAddress(MMIO_GAP_START), 200 | GuestAddress(MMIO_GAP_END) 201 | ) 202 | .err(), 203 | Some(Error::HimemStartPastMmioGapStart) 204 | ); 205 | 206 | // Success case: 2 ranges surrounding the MMIO gap. 207 | // Setup header is specified in the kernel loader result. 208 | kern_load_res.setup_header = Some(bootparam::setup_header::default()); 209 | let params = build_bootparams( 210 | &guest_memory, 211 | &kern_load_res, 212 | GuestAddress(DEFAULT_HIGH_RAM_START), 213 | GuestAddress(MMIO_GAP_START), 214 | GuestAddress(MMIO_GAP_END), 215 | ) 216 | .unwrap(); 217 | 218 | // The kernel loader type should have been modified in the setup header. 219 | let expected_setup_hdr = bootparam::setup_header { 220 | type_of_loader: KERNEL_LOADER_OTHER, 221 | ..Default::default() 222 | }; 223 | assert_eq!(expected_setup_hdr, params.hdr); 224 | 225 | // There should be 3 EBDA entries: EBDA, RAM preceding MMIO gap, RAM succeeding MMIO gap. 226 | assert_eq!(params.e820_entries, 3); 227 | 228 | // Success case: 1 range preceding the MMIO gap. 229 | // Let's skip the setup header this time. 230 | let guest_memory = 231 | GuestMemoryMmap::from_ranges(&[(GuestAddress(0), MMIO_GAP_START as usize)]).unwrap(); 232 | kern_load_res.setup_header = None; 233 | let params = build_bootparams( 234 | &guest_memory, 235 | &kern_load_res, 236 | GuestAddress(DEFAULT_HIGH_RAM_START), 237 | GuestAddress(MMIO_GAP_START), 238 | GuestAddress(MMIO_GAP_END), 239 | ) 240 | .unwrap(); 241 | 242 | // The setup header should be filled in, even though we didn't specify one. 243 | let expected_setup_hdr = bootparam::setup_header { 244 | boot_flag: KERNEL_BOOT_FLAG_MAGIC, 245 | header: KERNEL_HDR_MAGIC, 246 | kernel_alignment: KERNEL_MIN_ALIGNMENT_BYTES, 247 | type_of_loader: KERNEL_LOADER_OTHER, 248 | ..Default::default() 249 | }; 250 | assert_eq!(expected_setup_hdr, params.hdr); 251 | 252 | // There should be 2 EBDA entries: EBDA and RAM. 253 | assert_eq!(params.e820_entries, 2); 254 | } 255 | 256 | #[test] 257 | fn test_add_e820_entry() { 258 | let mut params = boot_params::default(); 259 | assert!(add_e820_entry(&mut params, 0, 0, 0).is_ok()); 260 | params.e820_entries = params.e820_table.len() as u8; 261 | assert_eq!( 262 | add_e820_entry(&mut params, 0, 0, 0).err(), 263 | Some(Error::E820Configuration) 264 | ); 265 | } 266 | } 267 | -------------------------------------------------------------------------------- /src/vmm/src/config/arg_parser.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause 3 | 4 | use std::collections::HashMap; 5 | use std::fmt; 6 | use std::str::FromStr; 7 | 8 | #[derive(Debug, PartialEq)] 9 | pub(super) enum CfgArgParseError { 10 | /// Parsing failed, param and error. 11 | ParsingFailed(&'static str, String), 12 | UnknownArg(String), 13 | } 14 | 15 | impl fmt::Display for CfgArgParseError { 16 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 17 | match self { 18 | Self::ParsingFailed(param, err) => { 19 | write!(f, "Param '{}', parsing failed: {}", param, err) 20 | } 21 | Self::UnknownArg(err) => write!(f, "Unknown arguments found: '{}'", err), 22 | } 23 | } 24 | } 25 | 26 | pub(super) struct CfgArgParser { 27 | args: HashMap, 28 | } 29 | 30 | impl CfgArgParser { 31 | pub(super) fn new(input: &str) -> Self { 32 | let args = input 33 | .split(',') 34 | .filter(|tok| !tok.is_empty()) 35 | .map(|tok| { 36 | let mut iter = tok.splitn(2, '='); 37 | let param_name = iter.next().unwrap(); 38 | let value = iter.next().unwrap_or("").to_string(); 39 | (param_name.to_lowercase(), value) 40 | }) 41 | .collect(); 42 | Self { args } 43 | } 44 | 45 | /// Retrieves the value of `param`, consuming it from `Self`. 46 | pub(super) fn value_of( 47 | &mut self, 48 | param_name: &'static str, 49 | ) -> Result, CfgArgParseError> 50 | where 51 | ::Err: fmt::Display, 52 | { 53 | match self.args.remove(param_name) { 54 | Some(value) if !value.is_empty() => value 55 | .parse::() 56 | .map_err(|err| CfgArgParseError::ParsingFailed(param_name, err.to_string())) 57 | .map(Some), 58 | _ => Ok(None), 59 | } 60 | } 61 | 62 | /// Checks if all params were consumed. 63 | pub(super) fn all_consumed(&self) -> Result<(), CfgArgParseError> { 64 | if self.args.is_empty() { 65 | Ok(()) 66 | } else { 67 | Err(CfgArgParseError::UnknownArg(self.to_string())) 68 | } 69 | } 70 | } 71 | 72 | impl fmt::Display for CfgArgParser { 73 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 74 | write!( 75 | f, 76 | "{}", 77 | self.args 78 | .keys() 79 | .map(|val| val.as_str()) 80 | .collect::>() 81 | .join(", ") 82 | ) 83 | } 84 | } 85 | #[cfg(test)] 86 | mod tests { 87 | use super::*; 88 | use std::num::NonZeroU8; 89 | use std::path::PathBuf; 90 | 91 | #[test] 92 | fn test_cfg_arg_parse() -> Result<(), CfgArgParseError> { 93 | let input_params = "path=/path,string=HelloWorld,int=123,u8=1"; 94 | let mut arg_parser = CfgArgParser::new(input_params); 95 | 96 | // No parameter was consumed yet 97 | assert!(arg_parser.all_consumed().is_err()); 98 | 99 | assert_eq!( 100 | arg_parser.value_of::("path")?.unwrap(), 101 | PathBuf::from("/path") 102 | ); 103 | assert_eq!( 104 | arg_parser.value_of::("string")?.unwrap(), 105 | "HelloWorld".to_string() 106 | ); 107 | assert_eq!( 108 | arg_parser.value_of::("u8")?.unwrap(), 109 | NonZeroU8::new(1).unwrap() 110 | ); 111 | assert_eq!(arg_parser.value_of::("int")?.unwrap(), 123); 112 | 113 | // Params now is empty, use the Default instead. 114 | let default = 12; 115 | assert_eq!(arg_parser.value_of("int")?.unwrap_or(default), default); 116 | 117 | // Params is empty and no Default provided: 118 | assert!(arg_parser.value_of::("int")?.is_none()); 119 | 120 | // All params were consumed: 121 | assert!(arg_parser.all_consumed().is_ok()); 122 | 123 | let input_params = "path="; 124 | assert!(CfgArgParser::new(input_params) 125 | .value_of::("path")? 126 | .is_none()); 127 | 128 | Ok(()) 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/vmm/src/irq_allocator.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause 3 | 4 | use std::fmt; 5 | 6 | #[derive(Debug, PartialEq, Eq)] 7 | pub enum Error { 8 | InvalidValue, 9 | MaxIrq, 10 | IRQOverflowed, 11 | } 12 | 13 | pub type Result = std::result::Result; 14 | 15 | /// An irq allocator which gives next available irq. 16 | /// It is mainly used for non-legacy devices. 17 | // There are a few reserved irq's on x86_64. We just skip all the inital 18 | // reserved irq to make the implementaion simple. This could be later extended 19 | // to cater more complex scenario. 20 | #[derive(Debug)] 21 | pub struct IrqAllocator { 22 | // Tracks the last allocated irq 23 | last_used_irq: u32, 24 | last_irq: u32, 25 | } 26 | 27 | impl IrqAllocator { 28 | pub fn new(last_used_irq: u32, last_irq: u32) -> Result { 29 | if last_used_irq >= last_irq { 30 | return Err(Error::InvalidValue); 31 | } 32 | Ok(IrqAllocator { 33 | last_used_irq, 34 | last_irq, 35 | }) 36 | } 37 | 38 | pub fn next_irq(&mut self) -> Result { 39 | self.last_used_irq 40 | .checked_add(1) 41 | .ok_or(Error::IRQOverflowed) 42 | .and_then(|irq| { 43 | if irq > self.last_irq { 44 | Err(Error::MaxIrq) 45 | } else { 46 | self.last_used_irq = irq; 47 | Ok(irq) 48 | } 49 | }) 50 | } 51 | } 52 | 53 | impl fmt::Display for Error { 54 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 55 | let err = match self { 56 | Error::MaxIrq => "last_irq IRQ limit reached", 57 | Error::IRQOverflowed => "IRQ overflowed", 58 | Error::InvalidValue => { 59 | "Check the value of last_used and last_irq. las_used should be less than last_irq" 60 | } 61 | }; 62 | write!(f, "{}", err) // user-facing output 63 | } 64 | } 65 | 66 | #[cfg(test)] 67 | mod test { 68 | use super::{Error, IrqAllocator}; 69 | #[test] 70 | fn test_new() { 71 | let irq_alloc = IrqAllocator::new(4, 10).unwrap(); 72 | assert_eq!(irq_alloc.last_used_irq, 4); 73 | assert_eq!(irq_alloc.last_irq, 10); 74 | let irq_alloc = IrqAllocator::new(4, 4).unwrap_err(); 75 | assert_eq!(irq_alloc, Error::InvalidValue); 76 | let irq_alloc = IrqAllocator::new(4, 3).unwrap_err(); 77 | assert_eq!(irq_alloc, Error::InvalidValue); 78 | } 79 | #[test] 80 | fn test_next_irq() { 81 | let mut irq_alloc = IrqAllocator::new(4, 7).unwrap(); 82 | assert_eq!(irq_alloc.next_irq(), Ok(5)); 83 | 84 | let _ = irq_alloc.next_irq(); 85 | assert_eq!(irq_alloc.next_irq(), Ok(7)); 86 | 87 | assert_eq!(irq_alloc.next_irq(), Err(Error::MaxIrq)); 88 | 89 | let mut irq_alloc = IrqAllocator::new(u32::MAX - 1, u32::MAX).unwrap(); 90 | assert_eq!(irq_alloc.next_irq(), Ok(u32::MAX)); 91 | assert_eq!(irq_alloc.next_irq(), Err(Error::IRQOverflowed)) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/vmm/tests/integration_tests.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause 3 | use std::convert::TryFrom; 4 | use std::path::PathBuf; 5 | 6 | use utils::resource_download::s3_download; 7 | use vmm::{KernelConfig, MemoryConfig, VMMConfig, VcpuConfig, Vmm, DEFAULT_KERNEL_LOAD_ADDR}; 8 | 9 | fn default_memory_config() -> MemoryConfig { 10 | MemoryConfig { size_mib: 1024 } 11 | } 12 | 13 | fn default_kernel_config(path: PathBuf) -> KernelConfig { 14 | KernelConfig { 15 | path, 16 | load_addr: DEFAULT_KERNEL_LOAD_ADDR, // 1 MB 17 | cmdline: KernelConfig::default_cmdline(), 18 | } 19 | } 20 | 21 | fn default_vcpu_config() -> VcpuConfig { 22 | VcpuConfig { num: 1 } 23 | } 24 | 25 | fn run_vmm(kernel_path: PathBuf) { 26 | let vmm_config = VMMConfig { 27 | kernel_config: default_kernel_config(kernel_path), 28 | memory_config: default_memory_config(), 29 | vcpu_config: default_vcpu_config(), 30 | block_config: None, 31 | net_config: None, 32 | }; 33 | 34 | let mut vmm = Vmm::try_from(vmm_config).unwrap(); 35 | vmm.run().unwrap(); 36 | } 37 | 38 | #[test] 39 | #[cfg(target_arch = "x86_64")] 40 | fn test_dummy_vmm_elf() { 41 | let tags = r#" 42 | { 43 | "halt_after_boot": true, 44 | "image_format": "elf", 45 | "with_disk": false 46 | } 47 | "#; 48 | 49 | let elf_halt = s3_download("kernel", Some(tags)).unwrap(); 50 | run_vmm(elf_halt); 51 | } 52 | 53 | #[test] 54 | #[cfg(target_arch = "x86_64")] 55 | fn test_dummy_vmm_bzimage() { 56 | let tags = r#" 57 | { 58 | "halt_after_boot": true, 59 | "image_format": "bzimage", 60 | "with_disk": false 61 | } 62 | "#; 63 | let bzimage_halt = s3_download("kernel", Some(tags)).unwrap(); 64 | run_vmm(bzimage_halt); 65 | } 66 | 67 | #[test] 68 | #[cfg(target_arch = "aarch64")] 69 | fn test_dummy_vmm_pe() { 70 | let tags = r#" 71 | { 72 | "halt_after_boot": true, 73 | "image_format": "pe", 74 | "with_disk": false 75 | } 76 | "#; 77 | let pe_halt = s3_download("kernel", Some(tags)).unwrap(); 78 | run_vmm(pe_halt); 79 | } 80 | -------------------------------------------------------------------------------- /tests/test_build_deps.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause 3 | """Test the scripts that build kernel images for the reference VMM.""" 4 | 5 | import os, subprocess, sys 6 | import pytest 7 | from tempfile import TemporaryDirectory 8 | 9 | 10 | # Accepted combinations for: 11 | # (kernel_format, kernel_filename, halt_flag) 12 | OK_COMBOS = [ 13 | ("eLf", None, None), 14 | ("elf", "foo", None), 15 | ("elf", "foo", True), 16 | ("bZiMaGe", None, None), 17 | ("bzimage", "bar", None), 18 | ("bzimage", "bar", True), 19 | ] 20 | 21 | 22 | # Malformed combinations for: 23 | # (kernel_format, kernel_filename, halt_flag, expected_error_message) 24 | BAD_COMBOS = [ 25 | ("foo", None, None, '[ERROR] Invalid kernel binary format: foo.'), 26 | ] 27 | 28 | 29 | @pytest.mark.parametrize("fmt,img,hlt", OK_COMBOS) 30 | def test_build_kernel_image(fmt, img, hlt): 31 | """Build kernel images using the provided scripts.""" 32 | workdir = TemporaryDirectory() 33 | build_cmd, expected_kernel = _make_script_cmd(workdir.name, fmt, img, hlt) 34 | try: 35 | subprocess.run(build_cmd, check=True) 36 | assert os.path.isfile(expected_kernel) 37 | finally: 38 | workdir.cleanup() 39 | 40 | 41 | @pytest.mark.parametrize("fmt,img,hlt,errmsg", BAD_COMBOS) 42 | def test_build_kernel_image_err(fmt, img, hlt, errmsg): 43 | """Attempt to build kernel images with invalid parameters.""" 44 | workdir = TemporaryDirectory() 45 | build_cmd, _ = _make_script_cmd(workdir.name, fmt, img, hlt) 46 | try: 47 | subprocess.check_output(build_cmd, stderr=subprocess.PIPE) 48 | except subprocess.CalledProcessError as cpe: 49 | assert errmsg in cpe.stderr.decode(sys.getfilesystemencoding()) 50 | finally: 51 | workdir.cleanup() 52 | 53 | 54 | def _make_script_cmd(workdir, fmt, img, hlt): 55 | """Compose the command line invocation for the kernel build script.""" 56 | kernel_dir = "linux-4.14.176" 57 | expected_image_path = os.path.join(workdir, kernel_dir) 58 | 59 | script_path = os.path.abspath(os.path.join( 60 | os.path.dirname(os.path.realpath(__file__)), 61 | "..", 62 | "resources/kernel/make_kernel_busybox_image.sh" 63 | )) 64 | 65 | # Add format. This argument is mandatory. 66 | script_cmd = [script_path, "-f", fmt, "-w", workdir] 67 | 68 | # Add number of CPUs to use for compilation. Not mandatory, but also not 69 | # easy to verify, so let's always use 2. 70 | script_cmd.extend(["-j", "2"]) 71 | 72 | # Add resulting kernel image name, if specified. 73 | if img: 74 | script_cmd.extend(["-k", img]) 75 | expected_image_path = os.path.join(expected_image_path, img) 76 | else: 77 | expected_image_path = os.path.join( 78 | expected_image_path, 79 | "vmlinux" if fmt.lower() == "elf" else "arch/x86/boot/bzImage" 80 | ) 81 | 82 | # Generate a kernel that halts, if specified. The script will append the 83 | # "-halt" suffix. 84 | if hlt: 85 | script_cmd.extend(["-h"]) 86 | expected_image_path = "{}-halt".format(expected_image_path) 87 | 88 | return script_cmd, expected_image_path 89 | -------------------------------------------------------------------------------- /tests/tools/s3/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause 3 | 4 | import json 5 | import os 6 | import pathlib 7 | 8 | from .utils import S3ResourceFetcher 9 | 10 | 11 | def s3_download(resource_type, resource_name, resource_tags="{}", path=None, first=False): 12 | """Downloads all resources from S3 that correspond to the passed parameters. 13 | 14 | The caller of this function must handle exceptions generated by invalid parameters 15 | or other configuration problems. 16 | """ 17 | resource_tags = json.loads(resource_tags) 18 | 19 | s3_resource_fetcher = S3ResourceFetcher( 20 | "vmm-reference-test-resources", 21 | os.path.join( 22 | pathlib.Path(__file__).parent.absolute(), 23 | "resource_manifest.json" 24 | )) 25 | 26 | res = s3_resource_fetcher.download( 27 | resource_type=resource_type, 28 | resource_name=resource_name, 29 | tags=resource_tags, 30 | download_location=path, 31 | first=first 32 | ) 33 | 34 | return res 35 | -------------------------------------------------------------------------------- /tests/tools/s3/utils.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause 3 | 4 | import json 5 | import logging 6 | import os 7 | import re 8 | import sys 9 | 10 | import boto3 11 | import botocore.client 12 | 13 | 14 | def resource_has_tags(resource, tags={}): 15 | """Checks if a resource defines all `tags`.""" 16 | return all( 17 | tag_key in resource.keys() and resource[tag_key] == tag_value 18 | for (tag_key, tag_value) in tags.items() 19 | ) 20 | 21 | 22 | class DownloadError(RuntimeError): 23 | def __init__(self, arg): 24 | self.args = arg 25 | 26 | 27 | class S3ResourceFetcher: 28 | """A class for fetching vmm-reference test resources from S3.""" 29 | 30 | def __init__( 31 | self, 32 | resource_bucket, 33 | resource_manifest_path, 34 | download_location=None, 35 | ): 36 | """Initializes the S3 client, manifest of test resources and S3 bucket name.""" 37 | with open(resource_manifest_path) as json_file: 38 | self._resource_manifest = json.load(json_file) 39 | self._resource_bucket = resource_bucket 40 | self._s3 = boto3.client( 41 | 's3', 42 | config=botocore.client.Config(signature_version=botocore.UNSIGNED) 43 | ) 44 | if download_location is None: 45 | path = os.path.join( 46 | os.path.dirname(__file__), 47 | "../../../resources") 48 | self._default_download_location = os.path.abspath(path) 49 | else: 50 | self._default_download_location = download_location 51 | self.log = logging.getLogger(__name__) 52 | self.log.info( 53 | "Setting default download location: ", 54 | self._default_download_location 55 | ) 56 | 57 | def download( 58 | self, 59 | resource_type, 60 | resource_name=None, 61 | tags={}, 62 | version=None, 63 | download_location=None, 64 | first=False 65 | ): 66 | """Downloads **all** the resources that match the parameters. 67 | 68 | The default version used when downloading is the latest version as defined in the 69 | resource manifest. 70 | 71 | If only one resource that confirms to the specification (i.e. type & tags) is needed, 72 | then the user needs to set `first` to True. With this option, the first resource 73 | that matches the parameters is downloaded. 74 | """ 75 | version = version or self.get_latest_version() 76 | download_location = download_location or self._default_download_location 77 | 78 | if version in self._resource_manifest.keys(): 79 | resource_parent = self._resource_manifest[version] 80 | else: 81 | raise Exception("Invalid version: {}".format(version)) 82 | 83 | resources = [r for r in resource_parent 84 | if r["resource_type"] == resource_type 85 | and (resource_name is None or r["resource_name"] == resource_name) 86 | and resource_has_tags(r, tags)] 87 | if len(resources) == 0: 88 | raise DownloadError("No resources found") 89 | 90 | if first: 91 | # When only one resource is needed, we don't need to download all 92 | # of them, so we make `resources` a single element array. 93 | resources = resources[:1] 94 | 95 | downloaded_file_paths = [] 96 | for resource in resources: 97 | abs_dir = os.path.join(download_location, resource["relative_path"]) 98 | self.log.info( 99 | "Creating download location if it does not exist: ", 100 | abs_dir 101 | ) 102 | os.makedirs(abs_dir, exist_ok=True) 103 | abs_path = os.path.join(abs_dir, resource["resource_name"]) 104 | 105 | object_key = "{}/{}/{}".format(version, resource_type, resource["resource_name"]) 106 | 107 | if not os.path.exists(abs_path): 108 | self.log.info("Object to download from S3:", object_key) 109 | self.log.info("Downloading file to: ", abs_path) 110 | self._s3.download_file(self._resource_bucket, object_key, abs_path) 111 | else: 112 | print("File already exists locally.", file=sys.stderr) 113 | downloaded_file_paths.append(abs_path) 114 | 115 | if len(downloaded_file_paths) == 0: 116 | raise DownloadError("Failed to download resources from S3") 117 | 118 | if first: 119 | return downloaded_file_paths[0] 120 | else: 121 | return downloaded_file_paths 122 | 123 | def get_latest_version(self): 124 | """Returns the latest version as defined in the resource manifest file.""" 125 | version_re = re.compile("^v[{0-9}]+") 126 | versions = [key for key in self._resource_manifest.keys() if version_re.match(key)] 127 | return "v{}".format(max(map(lambda v: int(v[1:]), versions))) 128 | -------------------------------------------------------------------------------- /tests/tools/s3_download.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause 4 | """Script for downloading the resources required for tests. 5 | It returns the absolute path of the downloaded resource to stdout.""" 6 | 7 | from optparse import OptionParser 8 | 9 | from s3 import s3_download 10 | 11 | 12 | if __name__ == "__main__": 13 | parser = OptionParser() 14 | parser.add_option("-t", "--resource-type", dest="resource_type", 15 | help="Type of resource to download.") 16 | parser.add_option("-n", "--resource-name", 17 | dest="resource_name", 18 | help="Name of resource to download.") 19 | parser.add_option("--tags", 20 | dest="tags", 21 | help="The optional tags that the resource must have as a dictionary.", 22 | default="{}") 23 | parser.add_option("-p", "--path", 24 | dest="path", 25 | help="The directory where to save the downloaded resource. " 26 | "This path represents the root used when creating the " 27 | "structure. The full path is created by joining this path " 28 | "and the relative_path defined in the resource manifest.") 29 | parser.add_option("-1", 30 | dest="first", 31 | default=False, 32 | action="store_true", 33 | help="Download the first resource that matches the parameters.") 34 | 35 | (options, args) = parser.parse_args() 36 | 37 | if not options.resource_type: 38 | parser.error("Missing required parameter: resource_type") 39 | 40 | res = s3_download( 41 | options.resource_type, 42 | options.resource_name, 43 | options.tags, 44 | options.path, 45 | options.first 46 | ) 47 | 48 | # When there is a single resource, this is not returned in an array. 49 | # Create an array with a single element in this case. 50 | res = [res] if options.first is True else res 51 | for resource in res: 52 | print(resource) 53 | --------------------------------------------------------------------------------