mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
Compare commits
13 Commits
v0.9.0
...
stable/v0.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
226d226fef | ||
|
|
293b8fad41 | ||
|
|
54ade15f9e | ||
|
|
03f64d12c3 | ||
|
|
9c3166237d | ||
|
|
3534540e6b | ||
|
|
f444451a04 | ||
|
|
89c25ea00c | ||
|
|
d89ab632e9 | ||
|
|
e32be99d6c | ||
|
|
4a62821e07 | ||
|
|
548ef43ca2 | ||
|
|
27c1b40c83 |
29
.github/workflows/build.yaml
vendored
29
.github/workflows/build.yaml
vendored
@@ -1,29 +0,0 @@
|
||||
name: Cloud Hypervisor Build
|
||||
on: [pull_request, create]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: github.event_name == 'pull_request'
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
rust:
|
||||
- stable
|
||||
- beta
|
||||
- nightly
|
||||
target:
|
||||
- x86_64-unknown-linux-gnu
|
||||
- x86_64-unknown-linux-musl
|
||||
steps:
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v2
|
||||
- name: Install Rust toolchain (${{ matrix.rust }})
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ matrix.rust }}
|
||||
target: ${{ matrix.target }}
|
||||
override: true
|
||||
|
||||
- name: Build
|
||||
run: cargo build --all --release --target=${{ matrix.target }}
|
||||
32
.github/workflows/cross-build.yaml
vendored
32
.github/workflows/cross-build.yaml
vendored
@@ -1,32 +0,0 @@
|
||||
name: Cloud Hypervisor Cross Build
|
||||
on: [pull_request, create]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: github.event_name == 'pull_request'
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
rust:
|
||||
- stable
|
||||
target:
|
||||
- aarch64-unknown-linux-gnu
|
||||
- aarch64-unknown-linux-musl
|
||||
steps:
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v2
|
||||
- name: Install Rust toolchain (${{ matrix.rust }})
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ matrix.rust }}
|
||||
target: ${{ matrix.target }}
|
||||
override: true
|
||||
- name: Install arm64 libfdt
|
||||
run: wget http://ftp.us.debian.org/debian/pool/main/d/device-tree-compiler/libfdt-dev_1.6.0-1_arm64.deb && dpkg-deb -xv libfdt-dev_1.6.0-1_arm64.deb ./tlibfdtdev && sudo mkdir /tmmmp && mkdir target && mkdir target/debug && mkdir target/debug/deps && sudo cp ./tlibfdtdev/usr/lib/aarch64-linux-gnu/libfdt.a target/debug/deps/libfdt.a && echo "libfdt installed"
|
||||
- name: Build
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
use-cross: true
|
||||
command: build
|
||||
args: --target=${{ matrix.target }} --no-default-features --features "mmio,kvm"
|
||||
67
.github/workflows/release.yaml
vendored
67
.github/workflows/release.yaml
vendored
@@ -1,67 +0,0 @@
|
||||
name: Cloud Hypervisor Release
|
||||
on: [create]
|
||||
|
||||
jobs:
|
||||
release:
|
||||
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
||||
name: Release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v2
|
||||
- name: Install Rust toolchain (x86_64-unknown-linux-gnu)
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
target: x86_64-unknown-linux-gnu
|
||||
- name: Install Rust toolchain (x86_64-unknown-linux-musl)
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
target: x86_64-unknown-linux-musl
|
||||
- name: Build
|
||||
run: cargo build --all --release --target=x86_64-unknown-linux-gnu
|
||||
- name: Static Build
|
||||
run: cargo build --all --release --target=x86_64-unknown-linux-musl
|
||||
- name: Strip cloud-hypervisor binaries
|
||||
run: strip target/*/release/cloud-hypervisor
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
uses: actions/create-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ github.ref }}
|
||||
release_name: ${{ github.ref }}
|
||||
draft: true
|
||||
prerelease: true
|
||||
- name: Upload cloud-hypervisor
|
||||
id: upload-release-cloud-hypervisor
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: target/x86_64-unknown-linux-gnu/release/cloud-hypervisor
|
||||
asset_name: cloud-hypervisor
|
||||
asset_content_type: application/octet-stream
|
||||
- name: Upload static cloud-hypervisor
|
||||
id: upload-release-static-cloud-hypervisor
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: target/x86_64-unknown-linux-musl/release/cloud-hypervisor
|
||||
asset_name: cloud-hypervisor-static
|
||||
asset_content_type: application/octet-stream
|
||||
- name: Upload ch-remote
|
||||
id: upload-release-ch-remote
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: target/x86_64-unknown-linux-gnu/release/ch-remote
|
||||
asset_name: ch-remote
|
||||
asset_content_type: application/octet-stream
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,4 +1,3 @@
|
||||
/build
|
||||
/target
|
||||
**/*.rs.bk
|
||||
**/Cargo.lock
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
edition = "2018"
|
||||
16
.travis.yml
Normal file
16
.travis.yml
Normal file
@@ -0,0 +1,16 @@
|
||||
language: rust
|
||||
|
||||
rust:
|
||||
- stable
|
||||
|
||||
script:
|
||||
- cargo build --release
|
||||
|
||||
deploy:
|
||||
provider: releases
|
||||
api_key: $GITHUB_OAUTH_TOKEN
|
||||
file: target/release/cloud-hypervisor
|
||||
skip_cleanup: true
|
||||
draft: true
|
||||
on:
|
||||
tags: true
|
||||
1525
Cargo.lock
generated
1525
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
71
Cargo.toml
71
Cargo.toml
@@ -1,73 +1,68 @@
|
||||
[package]
|
||||
name = "cloud-hypervisor"
|
||||
version = "0.9.0"
|
||||
version = "0.5.1"
|
||||
authors = ["The Cloud Hypervisor Authors"]
|
||||
edition = "2018"
|
||||
default-run = "cloud-hypervisor"
|
||||
build = "build.rs"
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "2.33.2", features = ["wrap_help"] }
|
||||
hypervisor = { path = "hypervisor" }
|
||||
libc = "0.2.74"
|
||||
log = { version = "0.4.11", features = ["std"] }
|
||||
seccomp = { git = "https://github.com/firecracker-microvm/firecracker", tag = "v0.22.0" }
|
||||
serde_json = "1.0.57"
|
||||
arc-swap = "0.4.4"
|
||||
clap = "2.33.0"
|
||||
epoll = "4.1.0"
|
||||
lazy_static = "1.4.0"
|
||||
libc = "0.2.66"
|
||||
log = { version = "0.4.8", features = ["std"] }
|
||||
vhost_user_backend = { path = "vhost_user_backend"}
|
||||
vhost_user_block = { path = "vhost_user_block"}
|
||||
vhost_user_fs = { path = "vhost_user_fs"}
|
||||
vhost_user_net = { path = "vhost_user_net"}
|
||||
virtio-bindings = "0.1.0"
|
||||
vmm = { path = "vmm" }
|
||||
vmm-sys-util = "0.6.1"
|
||||
vm-device = { path = "vm-device" }
|
||||
vm-memory = { git = "https://github.com/rust-vmm/vm-memory" }
|
||||
vmm-sys-util = "0.4.0"
|
||||
vm-virtio = { path = "vm-virtio" }
|
||||
|
||||
[patch.crates-io]
|
||||
vm-memory = { git = "https://github.com/cloud-hypervisor/vm-memory", branch = "ch" }
|
||||
[dependencies.vhost_rs]
|
||||
path = "vhost_rs"
|
||||
features = ["vhost-user-slave"]
|
||||
|
||||
[dev-dependencies]
|
||||
ssh2 = "0.8.2"
|
||||
dirs = "3.0.1"
|
||||
ssh2 = "0.7.1"
|
||||
dirs = "2.0.2"
|
||||
credibility = "0.1.3"
|
||||
tempdir = "0.3.7"
|
||||
tempdir= "0.3.7"
|
||||
lazy_static= "1.4.0"
|
||||
tempfile = "3.1.0"
|
||||
serde_json = "1.0.57"
|
||||
net_util = { path = "net_util" }
|
||||
serde_json = "1.0.48"
|
||||
|
||||
[features]
|
||||
default = ["acpi", "pci", "cmos", "kvm"]
|
||||
default = ["acpi", "pci", "cmos"]
|
||||
acpi = ["vmm/acpi"]
|
||||
pci = ["vmm/pci_support"]
|
||||
mmio = ["vmm/mmio_support"]
|
||||
cmos = ["vmm/cmos"]
|
||||
fwdebug = ["vmm/fwdebug"]
|
||||
kvm = ["vmm/kvm"]
|
||||
io_uring = ["vmm/io_uring"]
|
||||
|
||||
# Integration tests require a special environment to run in
|
||||
integration_tests = []
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
"acpi_tables",
|
||||
"arch",
|
||||
"arch_gen",
|
||||
"block_util",
|
||||
"devices",
|
||||
"hypervisor",
|
||||
"net_gen",
|
||||
"net_util",
|
||||
"option_parser",
|
||||
"pci",
|
||||
"vhost_rs",
|
||||
"qcow",
|
||||
"vhost_user_backend",
|
||||
"pci",
|
||||
"vmm",
|
||||
"vm-virtio",
|
||||
"vm-device",
|
||||
"vhost_user_block",
|
||||
"vhost_user_backend",
|
||||
"vhost_user_fs",
|
||||
"vhost_user_net",
|
||||
"virtio-devices",
|
||||
"vmm",
|
||||
"vfio",
|
||||
"net_util",
|
||||
"acpi_tables",
|
||||
"arch_gen",
|
||||
"net_gen",
|
||||
"vm-allocator",
|
||||
"vm-device",
|
||||
"vm-migration",
|
||||
"vm-virtio"
|
||||
]
|
||||
|
||||
132
Jenkinsfile
vendored
132
Jenkinsfile
vendored
@@ -1,7 +1,7 @@
|
||||
pipeline{
|
||||
agent none
|
||||
stages {
|
||||
stage ('Early checks') {
|
||||
stage ('Master build') {
|
||||
agent { node { label 'master' } }
|
||||
stages {
|
||||
stage ('Check for RFC/WIP builds') {
|
||||
@@ -14,103 +14,55 @@ pipeline{
|
||||
}
|
||||
}
|
||||
stage ('Cancel older builds') {
|
||||
when { not { branch 'master' } }
|
||||
steps {
|
||||
cancelPreviousBuilds()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
stage ('Build') {
|
||||
failFast true
|
||||
parallel {
|
||||
stage ('Master build') {
|
||||
agent { node { label 'master' } }
|
||||
stages {
|
||||
stage ('Checkout') {
|
||||
steps {
|
||||
checkout scm
|
||||
}
|
||||
}
|
||||
stage ('Run Cargo tests') {
|
||||
steps {
|
||||
sh "scripts/dev_cli.sh tests --cargo"
|
||||
}
|
||||
}
|
||||
stage ('Run OpenAPI tests') {
|
||||
steps {
|
||||
sh "scripts/run_openapi_tests.sh"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
stage ('Worker build') {
|
||||
agent { node { label 'bionic' } }
|
||||
options {
|
||||
timeout(time: 1, unit: 'HOURS')
|
||||
}
|
||||
stages {
|
||||
stage ('Checkout') {
|
||||
steps {
|
||||
checkout scm
|
||||
}
|
||||
}
|
||||
stage ('Run unit tests') {
|
||||
steps {
|
||||
sh "scripts/dev_cli.sh tests --unit"
|
||||
}
|
||||
}
|
||||
stage ('Run integration tests') {
|
||||
options {
|
||||
retry(3)
|
||||
}
|
||||
steps {
|
||||
sh "scripts/dev_cli.sh tests --integration"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
stage ('Worker build (musl)') {
|
||||
agent { node { label 'bionic' } }
|
||||
options {
|
||||
timeout(time: 1, unit: 'HOURS')
|
||||
}
|
||||
stages {
|
||||
stage ('Checkout') {
|
||||
steps {
|
||||
checkout scm
|
||||
}
|
||||
}
|
||||
stage ('Run unit tests for musl') {
|
||||
steps {
|
||||
sh "scripts/dev_cli.sh tests --unit --libc musl"
|
||||
}
|
||||
}
|
||||
stage ('Run integration tests for musl') {
|
||||
options {
|
||||
retry(3)
|
||||
}
|
||||
steps {
|
||||
sh "scripts/dev_cli.sh tests --integration --libc musl"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
stage ('Worker build') {
|
||||
agent { node { label 'bionic' } }
|
||||
options {
|
||||
timeout(time: 1, unit: 'HOURS')
|
||||
}
|
||||
}
|
||||
}
|
||||
post {
|
||||
regression {
|
||||
script {
|
||||
if (env.BRANCH_NAME == 'master') {
|
||||
slackSend (color: '#ff0000', message: '"master" branch build is now failing')
|
||||
stages {
|
||||
stage ('Checkout') {
|
||||
steps {
|
||||
checkout scm
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fixed {
|
||||
script {
|
||||
if (env.BRANCH_NAME == 'master') {
|
||||
slackSend (color: '#00ff00', message: '"master" branch build is now fixed')
|
||||
stage ('Install system packages') {
|
||||
steps {
|
||||
sh "sudo DEBIAN_FRONTEND=noninteractive apt-get install -yq build-essential mtools libssl-dev pkg-config"
|
||||
sh "sudo apt-get install -yq flex bison libelf-dev qemu-utils qemu-system libglib2.0-dev libpixman-1-dev libseccomp-dev libcap-ng-dev socat"
|
||||
sh "sudo snap install docker"
|
||||
}
|
||||
}
|
||||
stage ('Install Rust') {
|
||||
steps {
|
||||
sh "nohup curl https://sh.rustup.rs -sSf | sh -s -- -y"
|
||||
}
|
||||
}
|
||||
stage ('Run Cargo tests') {
|
||||
steps {
|
||||
sh "scripts/run_cargo_tests.sh"
|
||||
}
|
||||
}
|
||||
stage ('Run OpenAPI tests') {
|
||||
steps {
|
||||
sh "scripts/run_openapi_tests.sh"
|
||||
}
|
||||
}
|
||||
stage ('Run unit tests') {
|
||||
steps {
|
||||
sh "scripts/run_unit_tests.sh"
|
||||
}
|
||||
}
|
||||
stage ('Run integration tests') {
|
||||
steps {
|
||||
sh "sudo mount -t tmpfs tmpfs /tmp"
|
||||
sh "scripts/run_integration_tests.sh"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
41
README.md
41
README.md
@@ -19,7 +19,6 @@
|
||||
4. [rust-vmm dependency](#4-rust-vmm-dependency)
|
||||
* [Firecracker and crosvm](#firecracker-and-crosvm)
|
||||
5. [Community](#5-community)
|
||||
* [Contribute](#contribute)
|
||||
* [Join us](#join-us)
|
||||
6. [Security](#6-security)
|
||||
|
||||
@@ -128,13 +127,13 @@ Firmware](https://github.com/cloud-hypervisor/rust-hypervisor-firmware) project
|
||||
an ELF
|
||||
formatted KVM firmware for `cloud-hypervisor` to directly boot into.
|
||||
|
||||
We need to get the latest `rust-hypervisor-firmware` release and also a working cloud image. Here we will use a Ubuntu image:
|
||||
We need to get the latest `rust-hypervisor-firmware` release and also a working cloud image. Here we will use a Clear Linux image:
|
||||
|
||||
```shell
|
||||
$ pushd $CLOUDH
|
||||
$ wget https://cloud-images.ubuntu.com/focal/current/focal-server-cloudimg-amd64.img
|
||||
$ qemu-img convert -p -f qcow2 -O raw focal-server-cloudimg-amd64.img focal-server-cloudimg-amd64.raw
|
||||
$ wget https://github.com/cloud-hypervisor/rust-hypervisor-firmware/releases/download/0.2.8/hypervisor-fw
|
||||
$ wget https://download.clearlinux.org/releases/31890/clear/clear-31890-kvm.img.xz
|
||||
$ unxz clear-31890-kvm.img.xz
|
||||
$ wget https://github.com/cloud-hypervisor/rust-hypervisor-firmware/releases/download/0.2.6/hypervisor-fw
|
||||
$ popd
|
||||
```
|
||||
|
||||
@@ -143,7 +142,7 @@ $ pushd $CLOUDH
|
||||
$ sudo setcap cap_net_admin+ep ./cloud-hypervisor/target/release/cloud-hypervisor
|
||||
$ ./cloud-hypervisor/target/release/cloud-hypervisor \
|
||||
--kernel ./hypervisor-fw \
|
||||
--disk path=focal-server-cloudimg-amd64.raw \
|
||||
--disk path=clear-31890-kvm.img \
|
||||
--cpus boot=4 \
|
||||
--memory size=1024M \
|
||||
--net "tap=,mac=,ip=,mask=" \
|
||||
@@ -166,11 +165,11 @@ To build the kernel:
|
||||
|
||||
# Clone the Cloud Hypervisor Linux branch
|
||||
$ pushd $CLOUDH
|
||||
$ git clone --depth 1 https://github.com/cloud-hypervisor/linux.git -b virtio-fs-virtio-iommu-virtio-mem-5.6-rc4 linux-cloud-hypervisor
|
||||
$ git clone --depth 1 https://github.com/cloud-hypervisor/linux.git -b virtio-fs-virtio-iommu-5.5-rc1 linux-cloud-hypervisor
|
||||
$ pushd linux-cloud-hypervisor
|
||||
|
||||
# Use the cloud-hypervisor kernel config to build your kernel
|
||||
$ cp $CLOUDH/cloud-hypervisor/resources/linux-config-x86_64 .config
|
||||
$ cp $CLOUDH/cloud-hypervisor/resources/linux-virtio-fs-virtio-iommu-config .config
|
||||
$ make bzImage -j `nproc`
|
||||
$ popd
|
||||
```
|
||||
@@ -179,18 +178,18 @@ The `vmlinux` kernel image will then be located at `linux-cloud-hypervisor/arch/
|
||||
|
||||
#### Disk image
|
||||
|
||||
For the disk image, we will use a Ubuntu cloud image that contains a root partition:
|
||||
For the disk image, we will use a Clear Linux cloud image that contains a root partition:
|
||||
|
||||
```shell
|
||||
$ pushd $CLOUDH
|
||||
$ wget https://cloud-images.ubuntu.com/focal/current/focal-server-cloudimg-amd64.img
|
||||
$ qemu-img convert -p -f qcow2 -O raw focal-server-cloudimg-amd64.img focal-server-cloudimg-amd64.raw
|
||||
$ wget https://download.clearlinux.org/releases/31890/clear/clear-31890-kvm.img.xz
|
||||
$ unxz clear-31890-kvm.img.xz
|
||||
$ popd
|
||||
```
|
||||
|
||||
#### Booting the guest VM
|
||||
|
||||
Now we can directly boot into our custom kernel and make it use the Ubuntu root partition.
|
||||
Now we can directly boot into our custom kernel and make it use the Clear Linux root partition.
|
||||
If we want to have 4 vCPUs and 512 MBytes of memory:
|
||||
|
||||
```shell
|
||||
@@ -198,8 +197,8 @@ $ pushd $CLOUDH
|
||||
$ sudo setcap cap_net_admin+ep ./cloud-hypervisor/target/release/cloud-hypervisor
|
||||
$ ./cloud-hypervisor/target/release/cloud-hypervisor \
|
||||
--kernel ./linux-cloud-hypervisor/arch/x86/boot/compressed/vmlinux.bin \
|
||||
--disk path=focal-server-cloudimg-amd64.raw \
|
||||
--cmdline "console=hvc0 root=/dev/vda1 rw" \
|
||||
--disk path=clear-31890-kvm.img \
|
||||
--cmdline "console=hvc0 reboot=k panic=1 nomodules i8042.noaux i8042.nomux i8042.nopnp i8042.dumbkbd root=/dev/vda3" \
|
||||
--cpus boot=4 \
|
||||
--memory size=1024M \
|
||||
--net "tap=,mac=,ip=,mask=" \
|
||||
@@ -218,8 +217,8 @@ $ ./cloud-hypervisor/target/release/cloud-hypervisor \
|
||||
--kernel ./linux-cloud-hypervisor/arch/x86/boot/compressed/vmlinux.bin \
|
||||
--console off \
|
||||
--serial tty \
|
||||
--disk path=focal-server-cloudimg-amd64.raw \
|
||||
--cmdline "console=ttyS0 root=/dev/vda1 rw" \
|
||||
--disk path=clear-31890-kvm.img \
|
||||
--cmdline "console=ttyS0 reboot=k panic=1 nomodules i8042.noaux i8042.nomux i8042.nopnp i8042.dumbkbd root=/dev/vda3" \
|
||||
--cpus boot=4 \
|
||||
--memory size=1024M \
|
||||
--net "tap=,mac=,ip=,mask=" \
|
||||
@@ -230,9 +229,10 @@ $ ./cloud-hypervisor/target/release/cloud-hypervisor \
|
||||
|
||||
`cloud-hypervisor` is in a very early, pre-alpha stage. Use at your own risk!
|
||||
|
||||
As of 2020-07-02, the following cloud images are supported:
|
||||
As of 2019-12-12, the following cloud images are supported:
|
||||
* [Clear Linux](https://download.clearlinux.org/current/) (cloudguest and kvm)
|
||||
* [Ubuntu Bionic](https://cloud-images.ubuntu.com/bionic/current/) (cloudimg)
|
||||
* [Ubuntu Focal](https://cloud-images.ubuntu.com/focal/current/) (cloudimg)
|
||||
* [Ubuntu Eoan](https://cloud-images.ubuntu.com/eoan/current/) (cloudimg)
|
||||
|
||||
Direct kernel boot to userspace should work with most rootfs.
|
||||
|
||||
@@ -287,11 +287,6 @@ crates as possible.
|
||||
|
||||
# 5. Community
|
||||
|
||||
The Cloud Hypervisor project follows the governance, and community guidelines described in
|
||||
the [Community](https://github.com/cloud-hypervisor/community) repository.
|
||||
|
||||
## Contribute
|
||||
|
||||
We are working on building a global, diverse and collaborative community around the Cloud Hypervisor project.
|
||||
Anyone who is interested in [contributing](CONTRIBUTING.md) to the project is welcome to participate.
|
||||
|
||||
|
||||
@@ -5,5 +5,5 @@ authors = ["The Cloud Hypervisor Authors"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
vm-memory = "0.2.1"
|
||||
vm-memory = { git = "https://github.com/rust-vmm/vm-memory" }
|
||||
|
||||
|
||||
@@ -13,12 +13,12 @@ pub struct GenericAddress {
|
||||
}
|
||||
|
||||
impl GenericAddress {
|
||||
pub fn io_port_address<T>(address: u16) -> Self {
|
||||
pub fn io_port_address(address: u16) -> Self {
|
||||
GenericAddress {
|
||||
address_space_id: 1,
|
||||
register_bit_width: 8 * std::mem::size_of::<T>() as u8,
|
||||
register_bit_width: 8,
|
||||
register_bit_offset: 0,
|
||||
access_size: std::mem::size_of::<T>() as u8,
|
||||
access_size: 1,
|
||||
address: u64::from(address),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,18 +5,20 @@ authors = ["The Chromium OS Authors"]
|
||||
|
||||
[features]
|
||||
default = []
|
||||
acpi = ["acpi_tables"]
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
byteorder = "1.3.4"
|
||||
hypervisor = { path = "../hypervisor" }
|
||||
libc = "0.2.74"
|
||||
log = "0.4.11"
|
||||
vm-memory = { version = "0.2.1", features = ["backend-mmap"] }
|
||||
kvm-bindings = "0.2.0"
|
||||
kvm-ioctls = "0.5.0"
|
||||
libc = "0.2.66"
|
||||
|
||||
acpi_tables = { path = "../acpi_tables", optional = true }
|
||||
arch_gen = { path = "../arch_gen" }
|
||||
|
||||
[dependencies.vm-memory]
|
||||
git = "https://github.com/rust-vmm/vm-memory"
|
||||
features = ["backend-mmap"]
|
||||
|
||||
[dependencies.linux-loader]
|
||||
git = "https://github.com/rust-vmm/linux-loader"
|
||||
features = ["elf", "bzimage"]
|
||||
|
||||
@@ -1,595 +0,0 @@
|
||||
// Copyright 2020 Arm Limited (or its affiliates). All rights reserved.
|
||||
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the THIRD-PARTY file.
|
||||
|
||||
use libc::{c_char, c_int, c_void};
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::{CStr, CString, NulError};
|
||||
use std::fmt::Debug;
|
||||
use std::ptr::null;
|
||||
use std::{io, result};
|
||||
|
||||
use super::super::DeviceType;
|
||||
use super::super::InitramfsConfig;
|
||||
use super::get_fdt_addr;
|
||||
use super::gic::GICDevice;
|
||||
use super::layout::{
|
||||
FDT_MAX_SIZE, MEM_32BIT_DEVICES_SIZE, MEM_32BIT_DEVICES_START, PCI_MMCONFIG_SIZE,
|
||||
PCI_MMCONFIG_START,
|
||||
};
|
||||
use crate::aarch64::fdt::Error::CstringFDTTransform;
|
||||
use vm_memory::{Address, Bytes, GuestAddress, GuestMemory, GuestMemoryError, GuestMemoryMmap};
|
||||
|
||||
// This is a value for uniquely identifying the FDT node declaring the interrupt controller.
|
||||
const GIC_PHANDLE: u32 = 1;
|
||||
// This is a value for uniquely identifying the FDT node declaring the MSI controller.
|
||||
const MSI_PHANDLE: u32 = 2;
|
||||
// This is a value for uniquely identifying the FDT node containing the clock definition.
|
||||
const CLOCK_PHANDLE: u32 = 3;
|
||||
|
||||
// Read the documentation specified when appending the root node to the FDT.
|
||||
const ADDRESS_CELLS: u32 = 0x2;
|
||||
const SIZE_CELLS: u32 = 0x2;
|
||||
|
||||
// As per kvm tool and
|
||||
// https://www.kernel.org/doc/Documentation/devicetree/bindings/interrupt-controller/arm%2Cgic.txt
|
||||
// Look for "The 1st cell..."
|
||||
const GIC_FDT_IRQ_TYPE_SPI: u32 = 0;
|
||||
const GIC_FDT_IRQ_TYPE_PPI: u32 = 1;
|
||||
|
||||
// From https://elixir.bootlin.com/linux/v4.9.62/source/include/dt-bindings/interrupt-controller/irq.h#L17
|
||||
const IRQ_TYPE_EDGE_RISING: u32 = 1;
|
||||
const IRQ_TYPE_LEVEL_HI: u32 = 4;
|
||||
|
||||
// This links to libfdt which handles the creation of the binary blob
|
||||
// flattened device tree (fdt) that is passed to the kernel and indicates
|
||||
// the hardware configuration of the machine.
|
||||
#[link(name = "fdt")]
|
||||
extern "C" {
|
||||
fn fdt_create(buf: *mut c_void, bufsize: c_int) -> c_int;
|
||||
fn fdt_finish_reservemap(fdt: *mut c_void) -> c_int;
|
||||
fn fdt_begin_node(fdt: *mut c_void, name: *const c_char) -> c_int;
|
||||
fn fdt_property(fdt: *mut c_void, name: *const c_char, val: *const c_void, len: c_int)
|
||||
-> c_int;
|
||||
fn fdt_end_node(fdt: *mut c_void) -> c_int;
|
||||
fn fdt_open_into(fdt: *const c_void, buf: *mut c_void, bufsize: c_int) -> c_int;
|
||||
fn fdt_finish(fdt: *const c_void) -> c_int;
|
||||
fn fdt_pack(fdt: *mut c_void) -> c_int;
|
||||
}
|
||||
|
||||
/// Trait for devices to be added to the Flattened Device Tree.
|
||||
pub trait DeviceInfoForFDT {
|
||||
/// Returns the address where this device will be loaded.
|
||||
fn addr(&self) -> u64;
|
||||
/// Returns the associated interrupt for this device.
|
||||
fn irq(&self) -> u32;
|
||||
/// Returns the amount of memory that needs to be reserved for this device.
|
||||
fn length(&self) -> u64;
|
||||
}
|
||||
|
||||
/// Errors thrown while configuring the Flattened Device Tree for aarch64.
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Failed to append node to the FDT.
|
||||
AppendFDTNode(io::Error),
|
||||
/// Failed to append a property to the FDT.
|
||||
AppendFDTProperty(io::Error),
|
||||
/// Syscall for creating FDT failed.
|
||||
CreateFDT(io::Error),
|
||||
/// Failed to obtain a C style string.
|
||||
CstringFDTTransform(NulError),
|
||||
/// Failure in calling syscall for terminating this FDT.
|
||||
FinishFDTReserveMap(io::Error),
|
||||
/// Failure in writing FDT in memory.
|
||||
WriteFDTToMemory(GuestMemoryError),
|
||||
}
|
||||
type Result<T> = result::Result<T, Error>;
|
||||
|
||||
/// Creates the flattened device tree for this aarch64 VM.
|
||||
pub fn create_fdt<T: DeviceInfoForFDT + Clone + Debug>(
|
||||
guest_mem: &GuestMemoryMmap,
|
||||
cmdline: &CStr,
|
||||
vcpu_mpidr: Vec<u64>,
|
||||
device_info: &HashMap<(DeviceType, String), T>,
|
||||
gic_device: &Box<dyn GICDevice>,
|
||||
initrd: &Option<InitramfsConfig>,
|
||||
pci_space_address: &Option<(u64, u64)>,
|
||||
) -> Result<Vec<u8>> {
|
||||
// Alocate stuff necessary for the holding the blob.
|
||||
let mut fdt = vec![0; FDT_MAX_SIZE];
|
||||
|
||||
allocate_fdt(&mut fdt)?;
|
||||
|
||||
// For an explanation why these nodes were introduced in the blob take a look at
|
||||
// https://github.com/torvalds/linux/blob/master/Documentation/devicetree/booting-without-of.txt#L845
|
||||
// Look for "Required nodes and properties".
|
||||
|
||||
// Header or the root node as per above mentioned documentation.
|
||||
append_begin_node(&mut fdt, "")?;
|
||||
append_property_string(&mut fdt, "compatible", "linux,dummy-virt")?;
|
||||
// For info on #address-cells and size-cells read "Note about cells and address representation"
|
||||
// from the above mentioned txt file.
|
||||
append_property_u32(&mut fdt, "#address-cells", ADDRESS_CELLS)?;
|
||||
append_property_u32(&mut fdt, "#size-cells", SIZE_CELLS)?;
|
||||
// This is not mandatory but we use it to point the root node to the node
|
||||
// containing description of the interrupt controller for this VM.
|
||||
append_property_u32(&mut fdt, "interrupt-parent", GIC_PHANDLE)?;
|
||||
create_cpu_nodes(&mut fdt, &vcpu_mpidr)?;
|
||||
create_memory_node(&mut fdt, guest_mem)?;
|
||||
create_chosen_node(&mut fdt, cmdline, initrd)?;
|
||||
create_gic_node(&mut fdt, gic_device)?;
|
||||
create_timer_node(&mut fdt)?;
|
||||
create_clock_node(&mut fdt)?;
|
||||
create_psci_node(&mut fdt)?;
|
||||
create_devices_node(&mut fdt, device_info)?;
|
||||
if let Some((pci_device_base, pci_device_size)) = pci_space_address {
|
||||
create_pci_nodes(&mut fdt, *pci_device_base, *pci_device_size)?;
|
||||
}
|
||||
|
||||
// End Header node.
|
||||
append_end_node(&mut fdt)?;
|
||||
|
||||
// Allocate another buffer so we can format and then write fdt to guest.
|
||||
let mut fdt_final = vec![0; FDT_MAX_SIZE];
|
||||
finish_fdt(&mut fdt, &mut fdt_final)?;
|
||||
|
||||
// Write FDT to memory.
|
||||
let fdt_address = GuestAddress(get_fdt_addr(&guest_mem));
|
||||
guest_mem
|
||||
.write_slice(fdt_final.as_slice(), fdt_address)
|
||||
.map_err(Error::WriteFDTToMemory)?;
|
||||
Ok(fdt_final)
|
||||
}
|
||||
|
||||
// Following are auxiliary functions for allocating and finishing the FDT.
|
||||
fn allocate_fdt(fdt: &mut Vec<u8>) -> Result<()> {
|
||||
// Safe since we allocated this array with FDT_MAX_SIZE.
|
||||
let mut fdt_ret = unsafe { fdt_create(fdt.as_mut_ptr() as *mut c_void, FDT_MAX_SIZE as c_int) };
|
||||
|
||||
if fdt_ret != 0 {
|
||||
return Err(Error::CreateFDT(io::Error::last_os_error()));
|
||||
}
|
||||
|
||||
// The flattened device trees created with fdt_create() contains a list of
|
||||
// reserved memory areas. We need to call `fdt_finish_reservemap` so as to make sure that there is a
|
||||
// terminator in the reservemap list and whatever happened to be at the
|
||||
// start of the FDT data section would end up being interpreted as
|
||||
// reservemap entries.
|
||||
// Safe since we previously allocated this array.
|
||||
fdt_ret = unsafe { fdt_finish_reservemap(fdt.as_mut_ptr() as *mut c_void) };
|
||||
if fdt_ret != 0 {
|
||||
return Err(Error::FinishFDTReserveMap(io::Error::last_os_error()));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn finish_fdt(from_fdt: &mut Vec<u8>, to_fdt: &mut Vec<u8>) -> Result<()> {
|
||||
// Safe since we allocated `fdt_final` and previously passed in its size.
|
||||
let mut fdt_ret = unsafe { fdt_finish(from_fdt.as_mut_ptr() as *mut c_void) };
|
||||
if fdt_ret != 0 {
|
||||
return Err(Error::FinishFDTReserveMap(io::Error::last_os_error()));
|
||||
}
|
||||
|
||||
// Safe because we allocated both arrays with the correct size.
|
||||
fdt_ret = unsafe {
|
||||
fdt_open_into(
|
||||
from_fdt.as_mut_ptr() as *mut c_void,
|
||||
to_fdt.as_mut_ptr() as *mut c_void,
|
||||
FDT_MAX_SIZE as i32,
|
||||
)
|
||||
};
|
||||
if fdt_ret != 0 {
|
||||
return Err(Error::FinishFDTReserveMap(io::Error::last_os_error()));
|
||||
}
|
||||
|
||||
// Safe since we allocated `to_fdt`.
|
||||
fdt_ret = unsafe { fdt_pack(to_fdt.as_mut_ptr() as *mut c_void) };
|
||||
if fdt_ret != 0 {
|
||||
return Err(Error::FinishFDTReserveMap(io::Error::last_os_error()));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Following are auxiliary functions for appending nodes to FDT.
|
||||
fn append_begin_node(fdt: &mut Vec<u8>, name: &str) -> Result<()> {
|
||||
let cstr_name = CString::new(name).map_err(CstringFDTTransform)?;
|
||||
|
||||
// Safe because we allocated fdt and converted name to a CString
|
||||
let fdt_ret = unsafe { fdt_begin_node(fdt.as_mut_ptr() as *mut c_void, cstr_name.as_ptr()) };
|
||||
if fdt_ret != 0 {
|
||||
return Err(Error::AppendFDTNode(io::Error::last_os_error()));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn append_end_node(fdt: &mut Vec<u8>) -> Result<()> {
|
||||
// Safe because we allocated fdt.
|
||||
let fdt_ret = unsafe { fdt_end_node(fdt.as_mut_ptr() as *mut c_void) };
|
||||
if fdt_ret != 0 {
|
||||
return Err(Error::AppendFDTNode(io::Error::last_os_error()));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Following are auxiliary functions for appending property nodes to the nodes of the FDT.
|
||||
fn append_property_u32(fdt: &mut Vec<u8>, name: &str, val: u32) -> Result<()> {
|
||||
append_property(fdt, name, &to_be32(val))
|
||||
}
|
||||
|
||||
fn append_property_u64(fdt: &mut Vec<u8>, name: &str, val: u64) -> Result<()> {
|
||||
append_property(fdt, name, &to_be64(val))
|
||||
}
|
||||
|
||||
fn append_property_string(fdt: &mut Vec<u8>, name: &str, value: &str) -> Result<()> {
|
||||
let cstr_value = CString::new(value).map_err(CstringFDTTransform)?;
|
||||
append_property_cstring(fdt, name, &cstr_value)
|
||||
}
|
||||
|
||||
fn append_property_cstring(fdt: &mut Vec<u8>, name: &str, cstr_value: &CStr) -> Result<()> {
|
||||
let value_bytes = cstr_value.to_bytes_with_nul();
|
||||
let cstr_name = CString::new(name).map_err(CstringFDTTransform)?;
|
||||
// Safe because we allocated fdt, converted name and value to CStrings
|
||||
let fdt_ret = unsafe {
|
||||
fdt_property(
|
||||
fdt.as_mut_ptr() as *mut c_void,
|
||||
cstr_name.as_ptr(),
|
||||
value_bytes.as_ptr() as *mut c_void,
|
||||
value_bytes.len() as i32,
|
||||
)
|
||||
};
|
||||
if fdt_ret != 0 {
|
||||
return Err(Error::AppendFDTProperty(io::Error::last_os_error()));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn append_property_null(fdt: &mut Vec<u8>, name: &str) -> Result<()> {
|
||||
let cstr_name = CString::new(name).map_err(CstringFDTTransform)?;
|
||||
|
||||
// Safe because we allocated fdt, converted name to a CString
|
||||
let fdt_ret = unsafe {
|
||||
fdt_property(
|
||||
fdt.as_mut_ptr() as *mut c_void,
|
||||
cstr_name.as_ptr(),
|
||||
null(),
|
||||
0,
|
||||
)
|
||||
};
|
||||
if fdt_ret != 0 {
|
||||
return Err(Error::AppendFDTProperty(io::Error::last_os_error()));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn append_property(fdt: &mut Vec<u8>, name: &str, val: &[u8]) -> Result<()> {
|
||||
let cstr_name = CString::new(name).map_err(CstringFDTTransform)?;
|
||||
let val_ptr = val.as_ptr() as *const c_void;
|
||||
|
||||
// Safe because we allocated fdt and converted name to a CString
|
||||
let fdt_ret = unsafe {
|
||||
fdt_property(
|
||||
fdt.as_mut_ptr() as *mut c_void,
|
||||
cstr_name.as_ptr(),
|
||||
val_ptr,
|
||||
val.len() as i32,
|
||||
)
|
||||
};
|
||||
if fdt_ret != 0 {
|
||||
return Err(Error::AppendFDTProperty(io::Error::last_os_error()));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Auxiliary functions for writing u32/u64 numbers in big endian order.
|
||||
fn to_be32(input: u32) -> [u8; 4] {
|
||||
u32::to_be_bytes(input)
|
||||
}
|
||||
|
||||
fn to_be64(input: u64) -> [u8; 8] {
|
||||
u64::to_be_bytes(input)
|
||||
}
|
||||
|
||||
// Helper functions for generating a properly formatted byte vector using 32-bit/64-bit cells.
|
||||
fn generate_prop32(cells: &[u32]) -> Vec<u8> {
|
||||
let mut ret: Vec<u8> = Vec::new();
|
||||
for &e in cells {
|
||||
ret.extend(to_be32(e).iter());
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
fn generate_prop64(cells: &[u64]) -> Vec<u8> {
|
||||
let mut ret: Vec<u8> = Vec::new();
|
||||
for &e in cells {
|
||||
ret.extend(to_be64(e).iter());
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
// Following are the auxiliary function for creating the different nodes that we append to our FDT.
|
||||
fn create_cpu_nodes(fdt: &mut Vec<u8>, vcpu_mpidr: &Vec<u64>) -> Result<()> {
|
||||
// See https://github.com/torvalds/linux/blob/master/Documentation/devicetree/bindings/arm/cpus.yaml.
|
||||
append_begin_node(fdt, "cpus")?;
|
||||
// As per documentation, on ARM v8 64-bit systems value should be set to 2.
|
||||
append_property_u32(fdt, "#address-cells", 0x02)?;
|
||||
append_property_u32(fdt, "#size-cells", 0x0)?;
|
||||
let num_cpus = vcpu_mpidr.len();
|
||||
|
||||
for cpu_index in 0..num_cpus {
|
||||
let cpu_name = format!("cpu@{:x}", cpu_index);
|
||||
append_begin_node(fdt, &cpu_name)?;
|
||||
append_property_string(fdt, "device_type", "cpu")?;
|
||||
append_property_string(fdt, "compatible", "arm,arm-v8")?;
|
||||
if num_cpus > 1 {
|
||||
// This is required on armv8 64-bit. See aforementioned documentation.
|
||||
append_property_string(fdt, "enable-method", "psci")?;
|
||||
}
|
||||
// Set the field to first 24 bits of the MPIDR - Multiprocessor Affinity Register.
|
||||
// See http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0488c/BABHBJCI.html.
|
||||
append_property_u64(fdt, "reg", vcpu_mpidr[cpu_index] & 0x7FFFFF)?;
|
||||
append_end_node(fdt)?;
|
||||
}
|
||||
append_end_node(fdt)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_memory_node(fdt: &mut Vec<u8>, guest_mem: &GuestMemoryMmap) -> Result<()> {
|
||||
let mem_size = guest_mem.last_addr().raw_value() - super::layout::RAM_64BIT_START + 1;
|
||||
// See https://github.com/torvalds/linux/blob/master/Documentation/devicetree/booting-without-of.txt#L960
|
||||
// for an explanation of this.
|
||||
let mem_reg_prop = generate_prop64(&[super::layout::RAM_64BIT_START as u64, mem_size as u64]);
|
||||
|
||||
append_begin_node(fdt, "memory")?;
|
||||
append_property_string(fdt, "device_type", "memory")?;
|
||||
append_property(fdt, "reg", &mem_reg_prop)?;
|
||||
append_end_node(fdt)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_chosen_node(
|
||||
fdt: &mut Vec<u8>,
|
||||
cmdline: &CStr,
|
||||
initrd: &Option<InitramfsConfig>,
|
||||
) -> Result<()> {
|
||||
append_begin_node(fdt, "chosen")?;
|
||||
append_property_cstring(fdt, "bootargs", cmdline)?;
|
||||
|
||||
if let Some(initrd_config) = initrd {
|
||||
append_property_u64(
|
||||
fdt,
|
||||
"linux,initrd-start",
|
||||
initrd_config.address.raw_value() as u64,
|
||||
)?;
|
||||
append_property_u64(
|
||||
fdt,
|
||||
"linux,initrd-end",
|
||||
initrd_config.address.raw_value() + initrd_config.size as u64,
|
||||
)?;
|
||||
}
|
||||
|
||||
append_end_node(fdt)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_gic_node(fdt: &mut Vec<u8>, gic_device: &Box<dyn GICDevice>) -> Result<()> {
|
||||
let gic_reg_prop = generate_prop64(gic_device.device_properties());
|
||||
|
||||
append_begin_node(fdt, "intc")?;
|
||||
append_property_string(fdt, "compatible", gic_device.fdt_compatibility())?;
|
||||
append_property_null(fdt, "interrupt-controller")?;
|
||||
// "interrupt-cells" field specifies the number of cells needed to encode an
|
||||
// interrupt source. The type shall be a <u32> and the value shall be 3 if no PPI affinity description
|
||||
// is required.
|
||||
append_property_u32(fdt, "#interrupt-cells", 3)?;
|
||||
append_property(fdt, "reg", &gic_reg_prop)?;
|
||||
append_property_u32(fdt, "phandle", GIC_PHANDLE)?;
|
||||
append_property_u32(fdt, "#address-cells", 2)?;
|
||||
append_property_u32(fdt, "#size-cells", 2)?;
|
||||
append_property_null(fdt, "ranges")?;
|
||||
let gic_intr = [
|
||||
GIC_FDT_IRQ_TYPE_PPI,
|
||||
gic_device.fdt_maint_irq(),
|
||||
IRQ_TYPE_LEVEL_HI,
|
||||
];
|
||||
let gic_intr_prop = generate_prop32(&gic_intr);
|
||||
|
||||
append_property(fdt, "interrupts", &gic_intr_prop)?;
|
||||
|
||||
if gic_device.msi_compatible() {
|
||||
append_begin_node(fdt, "msic")?;
|
||||
append_property_string(fdt, "compatible", gic_device.msi_compatiblility())?;
|
||||
append_property_null(fdt, "msi-controller")?;
|
||||
append_property_u32(fdt, "phandle", MSI_PHANDLE)?;
|
||||
let msi_reg_prop = generate_prop64(gic_device.msi_properties());
|
||||
append_property(fdt, "reg", &msi_reg_prop)?;
|
||||
append_end_node(fdt)?;
|
||||
}
|
||||
|
||||
append_end_node(fdt)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_clock_node(fdt: &mut Vec<u8>) -> Result<()> {
|
||||
// The Advanced Peripheral Bus (APB) is part of the Advanced Microcontroller Bus Architecture
|
||||
// (AMBA) protocol family. It defines a low-cost interface that is optimized for minimal power
|
||||
// consumption and reduced interface complexity.
|
||||
// PCLK is the clock source and this node defines exactly the clock for the APB.
|
||||
append_begin_node(fdt, "apb-pclk")?;
|
||||
append_property_string(fdt, "compatible", "fixed-clock")?;
|
||||
append_property_u32(fdt, "#clock-cells", 0x0)?;
|
||||
append_property_u32(fdt, "clock-frequency", 24000000)?;
|
||||
append_property_string(fdt, "clock-output-names", "clk24mhz")?;
|
||||
append_property_u32(fdt, "phandle", CLOCK_PHANDLE)?;
|
||||
append_end_node(fdt)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_timer_node(fdt: &mut Vec<u8>) -> Result<()> {
|
||||
// See
|
||||
// https://github.com/torvalds/linux/blob/master/Documentation/devicetree/bindings/interrupt-controller/arch_timer.txt
|
||||
// These are fixed interrupt numbers for the timer device.
|
||||
let irqs = [13, 14, 11, 10];
|
||||
let compatible = "arm,armv8-timer";
|
||||
|
||||
let mut timer_reg_cells: Vec<u32> = Vec::new();
|
||||
for &irq in irqs.iter() {
|
||||
timer_reg_cells.push(GIC_FDT_IRQ_TYPE_PPI);
|
||||
timer_reg_cells.push(irq);
|
||||
timer_reg_cells.push(IRQ_TYPE_LEVEL_HI);
|
||||
}
|
||||
let timer_reg_prop = generate_prop32(timer_reg_cells.as_slice());
|
||||
|
||||
append_begin_node(fdt, "timer")?;
|
||||
append_property_string(fdt, "compatible", compatible)?;
|
||||
append_property_null(fdt, "always-on")?;
|
||||
append_property(fdt, "interrupts", &timer_reg_prop)?;
|
||||
append_end_node(fdt)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_psci_node(fdt: &mut Vec<u8>) -> Result<()> {
|
||||
let compatible = "arm,psci-0.2";
|
||||
append_begin_node(fdt, "psci")?;
|
||||
append_property_string(fdt, "compatible", compatible)?;
|
||||
// Two methods available: hvc and smc.
|
||||
// As per documentation, PSCI calls between a guest and hypervisor may use the HVC conduit instead of SMC.
|
||||
// So, since we are using kvm, we need to use hvc.
|
||||
append_property_string(fdt, "method", "hvc")?;
|
||||
append_end_node(fdt)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_virtio_node<T: DeviceInfoForFDT + Clone + Debug>(
|
||||
fdt: &mut Vec<u8>,
|
||||
dev_info: &T,
|
||||
) -> Result<()> {
|
||||
let device_reg_prop = generate_prop64(&[dev_info.addr(), dev_info.length()]);
|
||||
let irq = generate_prop32(&[GIC_FDT_IRQ_TYPE_SPI, dev_info.irq(), IRQ_TYPE_EDGE_RISING]);
|
||||
|
||||
append_begin_node(fdt, &format!("virtio_mmio@{:x}", dev_info.addr()))?;
|
||||
append_property_string(fdt, "compatible", "virtio,mmio")?;
|
||||
append_property(fdt, "reg", &device_reg_prop)?;
|
||||
append_property(fdt, "interrupts", &irq)?;
|
||||
append_property_u32(fdt, "interrupt-parent", GIC_PHANDLE)?;
|
||||
append_end_node(fdt)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_serial_node<T: DeviceInfoForFDT + Clone + Debug>(
|
||||
fdt: &mut Vec<u8>,
|
||||
dev_info: &T,
|
||||
) -> Result<()> {
|
||||
let serial_reg_prop = generate_prop64(&[dev_info.addr(), dev_info.length()]);
|
||||
let irq = generate_prop32(&[GIC_FDT_IRQ_TYPE_SPI, dev_info.irq(), IRQ_TYPE_EDGE_RISING]);
|
||||
|
||||
append_begin_node(fdt, &format!("uart@{:x}", dev_info.addr()))?;
|
||||
append_property_string(fdt, "compatible", "ns16550a")?;
|
||||
append_property(fdt, "reg", &serial_reg_prop)?;
|
||||
append_property_u32(fdt, "clocks", CLOCK_PHANDLE)?;
|
||||
append_property_string(fdt, "clock-names", "apb_pclk")?;
|
||||
append_property(fdt, "interrupts", &irq)?;
|
||||
append_end_node(fdt)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_rtc_node<T: DeviceInfoForFDT + Clone + Debug>(
|
||||
fdt: &mut Vec<u8>,
|
||||
dev_info: &T,
|
||||
) -> Result<()> {
|
||||
let compatible = b"arm,pl031\0arm,primecell\0";
|
||||
let rtc_reg_prop = generate_prop64(&[dev_info.addr(), dev_info.length()]);
|
||||
let irq = generate_prop32(&[GIC_FDT_IRQ_TYPE_SPI, dev_info.irq(), IRQ_TYPE_LEVEL_HI]);
|
||||
append_begin_node(fdt, &format!("rtc@{:x}", dev_info.addr()))?;
|
||||
append_property(fdt, "compatible", compatible)?;
|
||||
append_property(fdt, "reg", &rtc_reg_prop)?;
|
||||
append_property(fdt, "interrupts", &irq)?;
|
||||
append_property_u32(fdt, "clocks", CLOCK_PHANDLE)?;
|
||||
append_property_string(fdt, "clock-names", "apb_pclk")?;
|
||||
append_end_node(fdt)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_devices_node<T: DeviceInfoForFDT + Clone + Debug>(
|
||||
fdt: &mut Vec<u8>,
|
||||
dev_info: &HashMap<(DeviceType, String), T>,
|
||||
) -> Result<()> {
|
||||
// Create one temp Vec to store all virtio devices
|
||||
let mut ordered_virtio_device: Vec<&T> = Vec::new();
|
||||
|
||||
for ((device_type, _device_id), info) in dev_info {
|
||||
match device_type {
|
||||
DeviceType::RTC => create_rtc_node(fdt, info)?,
|
||||
DeviceType::Serial => create_serial_node(fdt, info)?,
|
||||
DeviceType::Virtio(_) => {
|
||||
ordered_virtio_device.push(info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort out virtio devices by address from low to high and insert them into fdt table.
|
||||
ordered_virtio_device.sort_by(|a, b| a.addr().cmp(&b.addr()));
|
||||
// Current address allocation strategy in cloud-hypervisor is: the first created device
|
||||
// will be allocated to higher address. Here we reverse the vector to make sure that
|
||||
// the older created device will appear in front of the newer created device in FDT.
|
||||
ordered_virtio_device.reverse();
|
||||
for ordered_device_info in ordered_virtio_device.drain(..) {
|
||||
create_virtio_node(fdt, ordered_device_info)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_pci_nodes(fdt: &mut Vec<u8>, pci_device_base: u64, pci_device_size: u64) -> Result<()> {
|
||||
// Add node for PCIe controller.
|
||||
// See Documentation/devicetree/bindings/pci/host-generic-pci.txt in the kernel
|
||||
// and https://elinux.org/Device_Tree_Usage.
|
||||
let ranges = generate_prop32(&[
|
||||
// mmio addresses
|
||||
0x2000000, // (ss = 10: 32-bit memory space)
|
||||
(MEM_32BIT_DEVICES_START.0 >> 32) as u32, // PCI address
|
||||
MEM_32BIT_DEVICES_START.0 as u32,
|
||||
(MEM_32BIT_DEVICES_START.0 >> 32) as u32, // CPU address
|
||||
MEM_32BIT_DEVICES_START.0 as u32,
|
||||
(MEM_32BIT_DEVICES_SIZE >> 32) as u32, // size
|
||||
MEM_32BIT_DEVICES_SIZE as u32,
|
||||
// device addresses
|
||||
0x3000000, // (ss = 11: 64-bit memory space)
|
||||
(pci_device_base >> 32) as u32, // PCI address
|
||||
pci_device_base as u32,
|
||||
(pci_device_base >> 32) as u32, // CPU address
|
||||
pci_device_base as u32,
|
||||
(pci_device_size >> 32) as u32, // size
|
||||
pci_device_size as u32,
|
||||
]);
|
||||
let bus_range = generate_prop32(&[0, 0]); // Only bus 0
|
||||
let reg = generate_prop64(&[PCI_MMCONFIG_START.0, PCI_MMCONFIG_SIZE]);
|
||||
|
||||
append_begin_node(fdt, "pci")?;
|
||||
append_property_string(fdt, "compatible", "pci-host-ecam-generic")?;
|
||||
append_property_string(fdt, "device_type", "pci")?;
|
||||
append_property(fdt, "ranges", &ranges)?;
|
||||
append_property(fdt, "bus-range", &bus_range)?;
|
||||
append_property_u32(fdt, "#address-cells", 3)?;
|
||||
append_property_u32(fdt, "#size-cells", 2)?;
|
||||
append_property(fdt, "reg", ®)?;
|
||||
append_property_u32(fdt, "#interrupt-cells", 1)?;
|
||||
append_property_null(fdt, "interrupt-map")?;
|
||||
append_property_null(fdt, "interrupt-map-mask")?;
|
||||
append_property_null(fdt, "dma-coherent")?;
|
||||
append_property_u32(fdt, "msi-parent", MSI_PHANDLE)?;
|
||||
append_end_node(fdt)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,174 +0,0 @@
|
||||
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::result;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Errors thrown while setting up the GIC.
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Error while calling KVM ioctl for setting up the global interrupt controller.
|
||||
CreateGIC(hypervisor::HypervisorVmError),
|
||||
/// Error while setting device attributes for the GIC.
|
||||
SetDeviceAttribute(hypervisor::HypervisorDeviceError),
|
||||
}
|
||||
type Result<T> = result::Result<T, Error>;
|
||||
|
||||
pub trait GICDevice {
|
||||
/// Returns the hypervisor agnostic Device of the GIC device
|
||||
fn device(&self) -> &Arc<dyn hypervisor::Device>;
|
||||
|
||||
/// Returns the fdt compatibility property of the device
|
||||
fn fdt_compatibility(&self) -> &str;
|
||||
|
||||
/// Returns the maint_irq fdt property of the device
|
||||
fn fdt_maint_irq(&self) -> u32;
|
||||
|
||||
/// Returns an array with GIC device properties
|
||||
fn device_properties(&self) -> &[u64];
|
||||
|
||||
/// Returns the number of vCPUs this GIC handles
|
||||
fn vcpu_count(&self) -> u64;
|
||||
|
||||
/// Returns whether the GIC device is MSI compatible or not
|
||||
fn msi_compatible(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Returns the MSI compatibility property of the device
|
||||
fn msi_compatiblility(&self) -> &str {
|
||||
""
|
||||
}
|
||||
|
||||
/// Returns the MSI reg property of the device
|
||||
fn msi_properties(&self) -> &[u64] {
|
||||
&[]
|
||||
}
|
||||
}
|
||||
|
||||
pub mod kvm {
|
||||
use super::GICDevice;
|
||||
use super::Result;
|
||||
use crate::aarch64::gicv2::kvm::KvmGICv2;
|
||||
use crate::aarch64::gicv3::kvm::KvmGICv3;
|
||||
use crate::aarch64::gicv3_its::kvm::KvmGICv3ITS;
|
||||
use crate::layout;
|
||||
use hypervisor::kvm::kvm_bindings;
|
||||
use std::boxed::Box;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Trait for GIC devices.
|
||||
pub trait KvmGICDevice: Send + Sync + GICDevice {
|
||||
/// Returns the GIC version of the device
|
||||
fn version() -> u32;
|
||||
|
||||
/// Create the GIC device object
|
||||
fn create_device(
|
||||
device: Arc<dyn hypervisor::Device>,
|
||||
vcpu_count: u64,
|
||||
) -> Box<dyn GICDevice>;
|
||||
|
||||
/// Setup the device-specific attributes
|
||||
fn init_device_attributes(
|
||||
vm: &Arc<dyn hypervisor::Vm>,
|
||||
gic_device: &Box<dyn GICDevice>,
|
||||
) -> Result<()>;
|
||||
|
||||
/// Initialize a GIC device
|
||||
fn init_device(vm: &Arc<dyn hypervisor::Vm>) -> Result<Arc<dyn hypervisor::Device>> {
|
||||
let mut gic_device = kvm_bindings::kvm_create_device {
|
||||
type_: Self::version(),
|
||||
fd: 0,
|
||||
flags: 0,
|
||||
};
|
||||
|
||||
vm.create_device(&mut gic_device)
|
||||
.map_err(super::Error::CreateGIC)
|
||||
}
|
||||
|
||||
/// Set a GIC device attribute
|
||||
fn set_device_attribute(
|
||||
device: &Arc<dyn hypervisor::Device>,
|
||||
group: u32,
|
||||
attr: u64,
|
||||
addr: u64,
|
||||
flags: u32,
|
||||
) -> Result<()> {
|
||||
let attr = kvm_bindings::kvm_device_attr {
|
||||
group: group,
|
||||
attr: attr,
|
||||
addr: addr,
|
||||
flags: flags,
|
||||
};
|
||||
device
|
||||
.set_device_attr(&attr)
|
||||
.map_err(super::Error::SetDeviceAttribute)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Finalize the setup of a GIC device
|
||||
fn finalize_device(gic_device: &Box<dyn GICDevice>) -> Result<()> {
|
||||
/* We need to tell the kernel how many irqs to support with this vgic.
|
||||
* See the `layout` module for details.
|
||||
*/
|
||||
let nr_irqs: u32 = layout::IRQ_MAX - layout::IRQ_BASE + 1;
|
||||
let nr_irqs_ptr = &nr_irqs as *const u32;
|
||||
Self::set_device_attribute(
|
||||
gic_device.device(),
|
||||
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_NR_IRQS,
|
||||
0,
|
||||
nr_irqs_ptr as u64,
|
||||
0,
|
||||
)?;
|
||||
|
||||
/* Finalize the GIC.
|
||||
* See https://code.woboq.org/linux/linux/virt/kvm/arm/vgic/vgic-kvm-device.c.html#211.
|
||||
*/
|
||||
Self::set_device_attribute(
|
||||
gic_device.device(),
|
||||
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_CTRL,
|
||||
u64::from(kvm_bindings::KVM_DEV_ARM_VGIC_CTRL_INIT),
|
||||
0,
|
||||
0,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Method to initialize the GIC device
|
||||
fn new(vm: &Arc<dyn hypervisor::Vm>, vcpu_count: u64) -> Result<Box<dyn GICDevice>> {
|
||||
let vgic_fd = Self::init_device(vm)?;
|
||||
|
||||
let device = Self::create_device(vgic_fd, vcpu_count);
|
||||
|
||||
Self::init_device_attributes(vm, &device)?;
|
||||
|
||||
Self::finalize_device(&device)?;
|
||||
|
||||
Ok(device)
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a GIC device.
|
||||
///
|
||||
/// It will try to create by default a GICv3 device. If that fails it will try
|
||||
/// to fall-back to a GICv2 device.
|
||||
pub fn create_gic(
|
||||
vm: &Arc<dyn hypervisor::Vm>,
|
||||
vcpu_count: u64,
|
||||
its_required: bool,
|
||||
) -> Result<Box<dyn GICDevice>> {
|
||||
if its_required {
|
||||
KvmGICv3ITS::new(vm, vcpu_count)
|
||||
} else {
|
||||
KvmGICv3ITS::new(vm, vcpu_count).or_else(|_| {
|
||||
debug!("Failed to create GICv3-ITS, will try GICv3 instead.");
|
||||
KvmGICv3::new(vm, vcpu_count).or_else(|_| {
|
||||
debug!("Failed to create GICv3, will try GICv2 instead.");
|
||||
KvmGICv2::new(vm, vcpu_count)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod kvm {
|
||||
use crate::aarch64::gic::kvm::KvmGICDevice;
|
||||
use crate::aarch64::gic::{Error, GICDevice};
|
||||
use std::{boxed::Box, result};
|
||||
type Result<T> = result::Result<T, Error>;
|
||||
use crate::layout;
|
||||
use hypervisor::kvm::kvm_bindings;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Represent a GIC v2 device
|
||||
pub struct KvmGICv2 {
|
||||
/// The hypervisor agnostic device
|
||||
device: Arc<dyn hypervisor::Device>,
|
||||
|
||||
/// GIC device properties, to be used for setting up the fdt entry
|
||||
properties: [u64; 4],
|
||||
|
||||
/// Number of CPUs handled by the device
|
||||
vcpu_count: u64,
|
||||
}
|
||||
|
||||
impl KvmGICv2 {
|
||||
// Unfortunately bindgen omits defines that are based on other defines.
|
||||
// See arch/arm64/include/uapi/asm/kvm.h file from the linux kernel.
|
||||
const KVM_VGIC_V2_DIST_SIZE: u64 = 0x1000;
|
||||
const KVM_VGIC_V2_CPU_SIZE: u64 = 0x2000;
|
||||
|
||||
// Device trees specific constants
|
||||
const ARCH_GIC_V2_MAINT_IRQ: u32 = 8;
|
||||
|
||||
/// Get the address of the GICv2 distributor.
|
||||
const fn get_dist_addr() -> u64 {
|
||||
layout::MAPPED_IO_START - KvmGICv2::KVM_VGIC_V2_DIST_SIZE
|
||||
}
|
||||
|
||||
/// Get the size of the GIC_v2 distributor.
|
||||
const fn get_dist_size() -> u64 {
|
||||
KvmGICv2::KVM_VGIC_V2_DIST_SIZE
|
||||
}
|
||||
|
||||
/// Get the address of the GIC_v2 CPU.
|
||||
const fn get_cpu_addr() -> u64 {
|
||||
KvmGICv2::get_dist_addr() - KvmGICv2::KVM_VGIC_V2_CPU_SIZE
|
||||
}
|
||||
|
||||
/// Get the size of the GIC_v2 CPU.
|
||||
const fn get_cpu_size() -> u64 {
|
||||
KvmGICv2::KVM_VGIC_V2_CPU_SIZE
|
||||
}
|
||||
}
|
||||
|
||||
impl GICDevice for KvmGICv2 {
|
||||
fn device(&self) -> &Arc<dyn hypervisor::Device> {
|
||||
&self.device
|
||||
}
|
||||
|
||||
fn device_properties(&self) -> &[u64] {
|
||||
&self.properties
|
||||
}
|
||||
|
||||
fn fdt_compatibility(&self) -> &str {
|
||||
"arm,gic-400"
|
||||
}
|
||||
|
||||
fn fdt_maint_irq(&self) -> u32 {
|
||||
KvmGICv2::ARCH_GIC_V2_MAINT_IRQ
|
||||
}
|
||||
|
||||
fn vcpu_count(&self) -> u64 {
|
||||
self.vcpu_count
|
||||
}
|
||||
}
|
||||
|
||||
impl KvmGICDevice for KvmGICv2 {
|
||||
fn version() -> u32 {
|
||||
kvm_bindings::kvm_device_type_KVM_DEV_TYPE_ARM_VGIC_V2
|
||||
}
|
||||
|
||||
fn create_device(
|
||||
device: Arc<dyn hypervisor::Device>,
|
||||
vcpu_count: u64,
|
||||
) -> Box<dyn GICDevice> {
|
||||
Box::new(KvmGICv2 {
|
||||
device: device,
|
||||
properties: [
|
||||
KvmGICv2::get_dist_addr(),
|
||||
KvmGICv2::get_dist_size(),
|
||||
KvmGICv2::get_cpu_addr(),
|
||||
KvmGICv2::get_cpu_size(),
|
||||
],
|
||||
vcpu_count: vcpu_count,
|
||||
})
|
||||
}
|
||||
|
||||
fn init_device_attributes(
|
||||
_vm: &Arc<dyn hypervisor::Vm>,
|
||||
gic_device: &Box<dyn GICDevice>,
|
||||
) -> Result<()> {
|
||||
/* Setting up the distributor attribute.
|
||||
We are placing the GIC below 1GB so we need to substract the size of the distributor. */
|
||||
Self::set_device_attribute(
|
||||
gic_device.device(),
|
||||
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ADDR,
|
||||
u64::from(kvm_bindings::KVM_VGIC_V2_ADDR_TYPE_DIST),
|
||||
&KvmGICv2::get_dist_addr() as *const u64 as u64,
|
||||
0,
|
||||
)?;
|
||||
|
||||
/* Setting up the CPU attribute. */
|
||||
Self::set_device_attribute(
|
||||
gic_device.device(),
|
||||
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ADDR,
|
||||
u64::from(kvm_bindings::KVM_VGIC_V2_ADDR_TYPE_CPU),
|
||||
&KvmGICv2::get_cpu_addr() as *const u64 as u64,
|
||||
0,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod kvm {
|
||||
use crate::aarch64::gic::kvm::KvmGICDevice;
|
||||
use crate::aarch64::gic::{Error, GICDevice};
|
||||
use crate::layout;
|
||||
use hypervisor::kvm::kvm_bindings;
|
||||
use std::sync::Arc;
|
||||
use std::{boxed::Box, result};
|
||||
type Result<T> = result::Result<T, Error>;
|
||||
|
||||
pub struct KvmGICv3 {
|
||||
/// The hypervisor agnostic device
|
||||
device: Arc<dyn hypervisor::Device>,
|
||||
|
||||
/// GIC device properties, to be used for setting up the fdt entry
|
||||
properties: [u64; 4],
|
||||
|
||||
/// Number of CPUs handled by the device
|
||||
vcpu_count: u64,
|
||||
}
|
||||
|
||||
impl KvmGICv3 {
|
||||
// Unfortunately bindgen omits defines that are based on other defines.
|
||||
// See arch/arm64/include/uapi/asm/kvm.h file from the linux kernel.
|
||||
pub const SZ_64K: u64 = 0x0001_0000;
|
||||
const KVM_VGIC_V3_DIST_SIZE: u64 = KvmGICv3::SZ_64K;
|
||||
const KVM_VGIC_V3_REDIST_SIZE: u64 = (2 * KvmGICv3::SZ_64K);
|
||||
|
||||
// Device trees specific constants
|
||||
pub const ARCH_GIC_V3_MAINT_IRQ: u32 = 9;
|
||||
|
||||
/// Get the address of the GIC distributor.
|
||||
pub fn get_dist_addr() -> u64 {
|
||||
layout::MAPPED_IO_START - KvmGICv3::KVM_VGIC_V3_DIST_SIZE
|
||||
}
|
||||
|
||||
/// Get the size of the GIC distributor.
|
||||
pub fn get_dist_size() -> u64 {
|
||||
KvmGICv3::KVM_VGIC_V3_DIST_SIZE
|
||||
}
|
||||
|
||||
/// Get the address of the GIC redistributors.
|
||||
pub fn get_redists_addr(vcpu_count: u64) -> u64 {
|
||||
KvmGICv3::get_dist_addr() - KvmGICv3::get_redists_size(vcpu_count)
|
||||
}
|
||||
|
||||
/// Get the size of the GIC redistributors.
|
||||
pub fn get_redists_size(vcpu_count: u64) -> u64 {
|
||||
vcpu_count * KvmGICv3::KVM_VGIC_V3_REDIST_SIZE
|
||||
}
|
||||
}
|
||||
|
||||
impl GICDevice for KvmGICv3 {
|
||||
fn device(&self) -> &Arc<dyn hypervisor::Device> {
|
||||
&self.device
|
||||
}
|
||||
|
||||
fn fdt_compatibility(&self) -> &str {
|
||||
"arm,gic-v3"
|
||||
}
|
||||
|
||||
fn fdt_maint_irq(&self) -> u32 {
|
||||
KvmGICv3::ARCH_GIC_V3_MAINT_IRQ
|
||||
}
|
||||
|
||||
fn device_properties(&self) -> &[u64] {
|
||||
&self.properties
|
||||
}
|
||||
|
||||
fn vcpu_count(&self) -> u64 {
|
||||
self.vcpu_count
|
||||
}
|
||||
}
|
||||
|
||||
impl KvmGICDevice for KvmGICv3 {
|
||||
fn version() -> u32 {
|
||||
kvm_bindings::kvm_device_type_KVM_DEV_TYPE_ARM_VGIC_V3
|
||||
}
|
||||
|
||||
fn create_device(
|
||||
device: Arc<dyn hypervisor::Device>,
|
||||
vcpu_count: u64,
|
||||
) -> Box<dyn GICDevice> {
|
||||
Box::new(KvmGICv3 {
|
||||
device: device,
|
||||
properties: [
|
||||
KvmGICv3::get_dist_addr(),
|
||||
KvmGICv3::get_dist_size(),
|
||||
KvmGICv3::get_redists_addr(vcpu_count),
|
||||
KvmGICv3::get_redists_size(vcpu_count),
|
||||
],
|
||||
vcpu_count: vcpu_count,
|
||||
})
|
||||
}
|
||||
|
||||
fn init_device_attributes(
|
||||
_vm: &Arc<dyn hypervisor::Vm>,
|
||||
gic_device: &Box<dyn GICDevice>,
|
||||
) -> Result<()> {
|
||||
/* Setting up the distributor attribute.
|
||||
We are placing the GIC below 1GB so we need to substract the size of the distributor.
|
||||
*/
|
||||
Self::set_device_attribute(
|
||||
gic_device.device(),
|
||||
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ADDR,
|
||||
u64::from(kvm_bindings::KVM_VGIC_V3_ADDR_TYPE_DIST),
|
||||
&KvmGICv3::get_dist_addr() as *const u64 as u64,
|
||||
0,
|
||||
)?;
|
||||
|
||||
/* Setting up the redistributors' attribute.
|
||||
We are calculating here the start of the redistributors address. We have one per CPU.
|
||||
*/
|
||||
Self::set_device_attribute(
|
||||
gic_device.device(),
|
||||
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ADDR,
|
||||
u64::from(kvm_bindings::KVM_VGIC_V3_ADDR_TYPE_REDIST),
|
||||
&KvmGICv3::get_redists_addr(u64::from(gic_device.vcpu_count())) as *const u64
|
||||
as u64,
|
||||
0,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,133 +0,0 @@
|
||||
// Copyright 2020 ARM Limited
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod kvm {
|
||||
use std::sync::Arc;
|
||||
use std::{boxed::Box, result};
|
||||
type Result<T> = result::Result<T, Error>;
|
||||
use crate::aarch64::gic::kvm::KvmGICDevice;
|
||||
use crate::aarch64::gic::{Error, GICDevice};
|
||||
use crate::aarch64::gicv3::kvm::KvmGICv3;
|
||||
use hypervisor::kvm::kvm_bindings;
|
||||
|
||||
pub struct KvmGICv3ITS {
|
||||
/// The hypervisor agnostic device
|
||||
device: Arc<dyn hypervisor::Device>,
|
||||
|
||||
/// GIC device properties, to be used for setting up the fdt entry
|
||||
gic_properties: [u64; 4],
|
||||
|
||||
/// MSI device properties, to be used for setting up the fdt entry
|
||||
msi_properties: [u64; 2],
|
||||
|
||||
/// Number of CPUs handled by the device
|
||||
vcpu_count: u64,
|
||||
}
|
||||
|
||||
impl KvmGICv3ITS {
|
||||
const KVM_VGIC_V3_ITS_SIZE: u64 = (2 * KvmGICv3::SZ_64K);
|
||||
|
||||
fn get_msi_size() -> u64 {
|
||||
KvmGICv3ITS::KVM_VGIC_V3_ITS_SIZE
|
||||
}
|
||||
|
||||
fn get_msi_addr(vcpu_count: u64) -> u64 {
|
||||
KvmGICv3::get_redists_addr(vcpu_count) - KvmGICv3ITS::get_msi_size()
|
||||
}
|
||||
}
|
||||
|
||||
impl GICDevice for KvmGICv3ITS {
|
||||
fn device(&self) -> &Arc<dyn hypervisor::Device> {
|
||||
&self.device
|
||||
}
|
||||
|
||||
fn fdt_compatibility(&self) -> &str {
|
||||
"arm,gic-v3"
|
||||
}
|
||||
|
||||
fn msi_compatible(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn msi_compatiblility(&self) -> &str {
|
||||
"arm,gic-v3-its"
|
||||
}
|
||||
|
||||
fn fdt_maint_irq(&self) -> u32 {
|
||||
KvmGICv3::ARCH_GIC_V3_MAINT_IRQ
|
||||
}
|
||||
|
||||
fn msi_properties(&self) -> &[u64] {
|
||||
&self.msi_properties
|
||||
}
|
||||
|
||||
fn device_properties(&self) -> &[u64] {
|
||||
&self.gic_properties
|
||||
}
|
||||
|
||||
fn vcpu_count(&self) -> u64 {
|
||||
self.vcpu_count
|
||||
}
|
||||
}
|
||||
|
||||
impl KvmGICDevice for KvmGICv3ITS {
|
||||
fn version() -> u32 {
|
||||
KvmGICv3::version()
|
||||
}
|
||||
|
||||
fn create_device(
|
||||
device: Arc<dyn hypervisor::Device>,
|
||||
vcpu_count: u64,
|
||||
) -> Box<dyn GICDevice> {
|
||||
Box::new(KvmGICv3ITS {
|
||||
device: device,
|
||||
gic_properties: [
|
||||
KvmGICv3::get_dist_addr(),
|
||||
KvmGICv3::get_dist_size(),
|
||||
KvmGICv3::get_redists_addr(vcpu_count),
|
||||
KvmGICv3::get_redists_size(vcpu_count),
|
||||
],
|
||||
msi_properties: [
|
||||
KvmGICv3ITS::get_msi_addr(vcpu_count),
|
||||
KvmGICv3ITS::get_msi_size(),
|
||||
],
|
||||
vcpu_count: vcpu_count,
|
||||
})
|
||||
}
|
||||
|
||||
fn init_device_attributes(
|
||||
vm: &Arc<dyn hypervisor::Vm>,
|
||||
gic_device: &Box<dyn GICDevice>,
|
||||
) -> Result<()> {
|
||||
KvmGICv3::init_device_attributes(vm, gic_device)?;
|
||||
|
||||
let mut its_device = kvm_bindings::kvm_create_device {
|
||||
type_: kvm_bindings::kvm_device_type_KVM_DEV_TYPE_ARM_VGIC_ITS,
|
||||
fd: 0,
|
||||
flags: 0,
|
||||
};
|
||||
|
||||
let its_fd = vm
|
||||
.create_device(&mut its_device)
|
||||
.map_err(Error::CreateGIC)?;
|
||||
|
||||
Self::set_device_attribute(
|
||||
&its_fd,
|
||||
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ADDR,
|
||||
u64::from(kvm_bindings::KVM_VGIC_ITS_ADDR_TYPE),
|
||||
&KvmGICv3ITS::get_msi_addr(u64::from(gic_device.vcpu_count())) as *const u64 as u64,
|
||||
0,
|
||||
)?;
|
||||
|
||||
Self::set_device_attribute(
|
||||
&its_fd,
|
||||
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_CTRL,
|
||||
u64::from(kvm_bindings::KVM_DEV_ARM_VGIC_CTRL_INIT),
|
||||
0,
|
||||
0,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,86 +1,7 @@
|
||||
// Copyright 2020 Arm Limited (or its affiliates). All rights reserved.
|
||||
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//
|
||||
// Memory layout of Aarch64 guest:
|
||||
//
|
||||
// Physical +---------------------------------------------------------------+
|
||||
// address | |
|
||||
// end | |
|
||||
// ~ ~ ~ ~
|
||||
// | |
|
||||
// | Highmem PCI MMIO space |
|
||||
// | |
|
||||
// RAM end +---------------------------------------------------------------+
|
||||
// (dynamic, | |
|
||||
// including | |
|
||||
// hotplug ~ ~ ~ ~
|
||||
// memory) | |
|
||||
// | DRAM |
|
||||
// | |
|
||||
// 2GB +---------------------------------------------------------------+
|
||||
// | |
|
||||
// | Reserved |
|
||||
// | |
|
||||
// 1G+256M +---------------------------------------------------------------+
|
||||
// | |
|
||||
// | PCI MMCONFIG space |
|
||||
// | |
|
||||
// 1GB +---------------------------------------------------------------+
|
||||
// | |
|
||||
// | PCI MMIO space |
|
||||
// | |
|
||||
// 256 M +---------------------------------------------------------------|
|
||||
// | |
|
||||
// | Legacy devices space |
|
||||
// | |
|
||||
// 144 M +---------------------------------------------------------------|
|
||||
// | |
|
||||
// | Reserverd (now GIC is here) |
|
||||
// | |
|
||||
// 0GB +---------------------------------------------------------------+
|
||||
//
|
||||
//
|
||||
|
||||
use vm_memory::{GuestAddress, GuestUsize};
|
||||
|
||||
/// Below this address will reside the GIC, above this address will reside the MMIO devices.
|
||||
pub const MAPPED_IO_START: u64 = 0x0900_0000;
|
||||
|
||||
/// Space 0x0900_0000 ~ 0x1000_0000 is reserved for legacy devices.
|
||||
pub const LEGACY_SERIAL_MAPPED_IO_START: u64 = 0x0900_0000;
|
||||
pub const LEGACY_RTC_MAPPED_IO_START: u64 = 0x0901_0000;
|
||||
|
||||
/// Legacy space will be allocated at once whiling setting up legacy devices.
|
||||
pub const LEGACY_DEVICES_MAPPED_IO_SIZE: u64 = 0x0700_0000;
|
||||
|
||||
/// Starting from 0x1000_0000 (256MiB), the 768MiB (ends at 1 GiB) is used for PCIE MMIO
|
||||
pub const MEM_32BIT_DEVICES_START: GuestAddress = GuestAddress(0x1000_0000);
|
||||
pub const MEM_32BIT_DEVICES_SIZE: u64 = 0x3000_0000;
|
||||
|
||||
/// PCI MMCONFIG space (start: after the device space at 1 GiB, length: 256MiB)
|
||||
pub const PCI_MMCONFIG_START: GuestAddress = GuestAddress(0x4000_0000);
|
||||
pub const PCI_MMCONFIG_SIZE: GuestUsize = 256 << 20;
|
||||
|
||||
/// Start of RAM on 64 bit ARM.
|
||||
pub const RAM_64BIT_START: u64 = 0x8000_0000;
|
||||
|
||||
/// Kernel command line maximum size.
|
||||
/// As per `arch/arm64/include/uapi/asm/setup.h`.
|
||||
pub const CMDLINE_MAX_SIZE: usize = 2048;
|
||||
|
||||
/// Maximum size of the device tree blob as specified in https://www.kernel.org/doc/Documentation/arm64/booting.txt.
|
||||
pub const FDT_MAX_SIZE: usize = 0x20_0000;
|
||||
|
||||
// As per virt/kvm/arm/vgic/vgic-kvm-device.c we need
|
||||
// the number of interrupts our GIC will support to be:
|
||||
// * bigger than 32
|
||||
// * less than 1023 and
|
||||
// * a multiple of 32.
|
||||
// We are setting up our interrupt controller to support a maximum of 128 interrupts.
|
||||
/// First usable interrupt on aarch64.
|
||||
pub const IRQ_BASE: u32 = 32;
|
||||
|
||||
/// Last usable interrupt on aarch64.
|
||||
pub const IRQ_MAX: u32 = 159;
|
||||
/// Kernel command line start address.
|
||||
pub const CMDLINE_START: usize = 0x0;
|
||||
/// Kernel command line start address maximum size.
|
||||
pub const CMDLINE_MAX_SIZE: usize = 0x0;
|
||||
|
||||
@@ -1,269 +1,27 @@
|
||||
// Copyright 2020 Arm Limited (or its affiliates). All rights reserved.
|
||||
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// Module for the flattened device tree.
|
||||
pub mod fdt;
|
||||
/// Module for the global interrupt controller configuration.
|
||||
pub mod gic;
|
||||
mod gicv2;
|
||||
mod gicv3;
|
||||
mod gicv3_its;
|
||||
/// Layout for this aarch64 system.
|
||||
pub mod layout;
|
||||
/// Logic for configuring aarch64 registers.
|
||||
pub mod regs;
|
||||
|
||||
pub use self::fdt::DeviceInfoForFDT;
|
||||
use crate::DeviceType;
|
||||
use crate::RegionType;
|
||||
use hypervisor::kvm::kvm_bindings;
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::CStr;
|
||||
use std::fmt::Debug;
|
||||
use std::sync::Arc;
|
||||
use vm_memory::{
|
||||
Address, GuestAddress, GuestAddressSpace, GuestMemory, GuestMemoryAtomic, GuestMemoryMmap,
|
||||
GuestUsize,
|
||||
};
|
||||
use memory_model::{GuestAddress, GuestMemory};
|
||||
|
||||
/// Errors thrown while configuring aarch64 system.
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Failed to create a FDT.
|
||||
SetupFDT(fdt::Error),
|
||||
|
||||
/// Failed to create a GIC.
|
||||
SetupGIC(gic::Error),
|
||||
|
||||
/// Failed to compute the initramfs address.
|
||||
InitramfsAddress,
|
||||
|
||||
/// Error configuring the general purpose registers
|
||||
REGSConfiguration(regs::Error),
|
||||
|
||||
/// Error fetching prefered target
|
||||
VcpuArmPreferredTarget(hypervisor::HypervisorVmError),
|
||||
|
||||
/// Error doing Vcpu Init on Arm.
|
||||
VcpuArmInit(hypervisor::HypervisorCpuError),
|
||||
/// Stub function that needs to be implemented when aarch64 functionality is added.
|
||||
pub fn arch_memory_regions(size: usize) -> Vec<(GuestAddress, usize, RegionType)> {
|
||||
vec![(GuestAddress(0), size, RegionType::Ram)]
|
||||
}
|
||||
|
||||
impl From<Error> for super::Error {
|
||||
fn from(e: Error) -> super::Error {
|
||||
super::Error::AArch64Setup(e)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
/// Specifies the entry point address where the guest must start
|
||||
/// executing code.
|
||||
pub struct EntryPoint {
|
||||
/// Address in guest memory where the guest must start execution
|
||||
pub entry_addr: GuestAddress,
|
||||
}
|
||||
|
||||
/// Configure the specified VCPU, and return its MPIDR.
|
||||
pub fn configure_vcpu(
|
||||
fd: &Arc<dyn hypervisor::Vcpu>,
|
||||
id: u8,
|
||||
vm: &Arc<dyn hypervisor::Vm>,
|
||||
kernel_entry_point: Option<EntryPoint>,
|
||||
vm_memory: &GuestMemoryAtomic<GuestMemoryMmap>,
|
||||
) -> super::Result<u64> {
|
||||
let mut kvi: kvm_bindings::kvm_vcpu_init = kvm_bindings::kvm_vcpu_init::default();
|
||||
|
||||
// This reads back the kernel's preferred target type.
|
||||
vm.get_preferred_target(&mut kvi)
|
||||
.map_err(Error::VcpuArmPreferredTarget)?;
|
||||
// We already checked that the capability is supported.
|
||||
kvi.features[0] |= 1 << kvm_bindings::KVM_ARM_VCPU_PSCI_0_2;
|
||||
// Non-boot cpus are powered off initially.
|
||||
if id > 0 {
|
||||
kvi.features[0] |= 1 << kvm_bindings::KVM_ARM_VCPU_POWER_OFF;
|
||||
}
|
||||
|
||||
fd.vcpu_init(&kvi).map_err(Error::VcpuArmInit)?;
|
||||
if let Some(kernel_entry_point) = kernel_entry_point {
|
||||
regs::setup_regs(
|
||||
fd,
|
||||
id,
|
||||
kernel_entry_point.entry_addr.raw_value(),
|
||||
&vm_memory.memory(),
|
||||
)
|
||||
.map_err(Error::REGSConfiguration)?;
|
||||
}
|
||||
|
||||
let mpidr = regs::read_mpidr(fd).map_err(Error::REGSConfiguration)?;
|
||||
Ok(mpidr)
|
||||
}
|
||||
|
||||
pub fn arch_memory_regions(size: GuestUsize) -> Vec<(GuestAddress, usize, RegionType)> {
|
||||
let mut regions = Vec::new();
|
||||
// 0 ~ 256 MiB: Reserved
|
||||
regions.push((
|
||||
GuestAddress(0),
|
||||
layout::MEM_32BIT_DEVICES_START.0 as usize,
|
||||
RegionType::Reserved,
|
||||
));
|
||||
|
||||
// 256 MiB ~ 1 G: MMIO space
|
||||
regions.push((
|
||||
layout::MEM_32BIT_DEVICES_START,
|
||||
layout::MEM_32BIT_DEVICES_SIZE as usize,
|
||||
RegionType::SubRegion,
|
||||
));
|
||||
|
||||
// 1G ~ 2G: reserved. The leading 256M for PCIe MMCONFIG space
|
||||
regions.push((
|
||||
layout::PCI_MMCONFIG_START,
|
||||
(layout::RAM_64BIT_START - layout::PCI_MMCONFIG_START.0) as usize,
|
||||
RegionType::Reserved,
|
||||
));
|
||||
|
||||
regions.push((
|
||||
GuestAddress(layout::RAM_64BIT_START),
|
||||
size as usize,
|
||||
RegionType::Ram,
|
||||
));
|
||||
|
||||
regions
|
||||
}
|
||||
|
||||
/// Configures the system and should be called once per vm before starting vcpu threads.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `guest_mem` - The memory to be used by the guest.
|
||||
/// * `num_cpus` - Number of virtual CPUs the guest will have.
|
||||
pub fn configure_system<T: DeviceInfoForFDT + Clone + Debug>(
|
||||
vm: &Arc<dyn hypervisor::Vm>,
|
||||
guest_mem: &GuestMemoryMmap,
|
||||
cmdline_cstring: &CStr,
|
||||
vcpu_count: u64,
|
||||
vcpu_mpidr: Vec<u64>,
|
||||
device_info: &HashMap<(DeviceType, String), T>,
|
||||
initrd: &Option<super::InitramfsConfig>,
|
||||
pci_space_address: &Option<(u64, u64)>,
|
||||
/// Stub function that needs to be implemented when aarch64 functionality is added.
|
||||
pub fn configure_system(
|
||||
_guest_mem: &GuestMemory,
|
||||
_cmdline_addr: GuestAddress,
|
||||
_cmdline_size: usize,
|
||||
_num_cpus: u8,
|
||||
_rsdp_addr: Option<GuestAddress>,
|
||||
) -> super::Result<()> {
|
||||
// If pci_space_address is present, it means PCI devices are used ("pci" feature enabled).
|
||||
// Then GITv3-ITS is required for MSI messaging.
|
||||
// Otherwise ("mmio" feature enabled), any version of GIC is OK.
|
||||
let gic_device = gic::kvm::create_gic(vm, vcpu_count, pci_space_address.is_some())
|
||||
.map_err(Error::SetupGIC)?;
|
||||
|
||||
fdt::create_fdt(
|
||||
guest_mem,
|
||||
cmdline_cstring,
|
||||
vcpu_mpidr,
|
||||
device_info,
|
||||
&gic_device,
|
||||
initrd,
|
||||
pci_space_address,
|
||||
)
|
||||
.map_err(Error::SetupFDT)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns the memory address where the initramfs could be loaded.
|
||||
pub fn initramfs_load_addr(
|
||||
guest_mem: &GuestMemoryMmap,
|
||||
initramfs_size: usize,
|
||||
) -> super::Result<u64> {
|
||||
let round_to_pagesize = |size| (size + (super::PAGE_SIZE - 1)) & !(super::PAGE_SIZE - 1);
|
||||
match GuestAddress(get_fdt_addr(&guest_mem))
|
||||
.checked_sub(round_to_pagesize(initramfs_size) as u64)
|
||||
{
|
||||
Some(offset) => {
|
||||
if guest_mem.address_in_range(offset) {
|
||||
Ok(offset.raw_value())
|
||||
} else {
|
||||
Err(super::Error::AArch64Setup(Error::InitramfsAddress))
|
||||
}
|
||||
}
|
||||
None => Err(super::Error::AArch64Setup(Error::InitramfsAddress)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the memory address where the kernel could be loaded.
|
||||
pub fn get_kernel_start() -> u64 {
|
||||
layout::RAM_64BIT_START
|
||||
}
|
||||
|
||||
// Auxiliary function to get the address where the device tree blob is loaded.
|
||||
fn get_fdt_addr(mem: &GuestMemoryMmap) -> u64 {
|
||||
// If the memory allocated is smaller than the size allocated for the FDT,
|
||||
// we return the start of the DRAM so that
|
||||
// we allow the code to try and load the FDT.
|
||||
|
||||
if let Some(addr) = mem.last_addr().checked_sub(layout::FDT_MAX_SIZE as u64 - 1) {
|
||||
if mem.address_in_range(addr) {
|
||||
return addr.raw_value();
|
||||
}
|
||||
}
|
||||
|
||||
layout::RAM_64BIT_START
|
||||
}
|
||||
|
||||
pub fn get_host_cpu_phys_bits() -> u8 {
|
||||
// The value returned here is used to determine the physical address space size
|
||||
// for a VM (IPA size).
|
||||
// In recent kernel versions, the maxium IPA size supported by the host can be
|
||||
// known by querying cap KVM_CAP_ARM_VM_IPA_SIZE. And the IPA size for a
|
||||
// guest can be configured smaller.
|
||||
// But in Cloud-Hypervisor we simply use the maxium value for the VM.
|
||||
// Reference https://lwn.net/Articles/766767/.
|
||||
//
|
||||
// The correct way to query KVM_CAP_ARM_VM_IPA_SIZE is via rust-vmm/kvm-ioctls,
|
||||
// which wraps all IOCTL's and provides easy interface to user hypervisors.
|
||||
// For now the cap hasn't been supported. A separate patch will be submitted to
|
||||
// rust-vmm to add it.
|
||||
// So a hardcoded value is used here as a temporary solution.
|
||||
// It will be replace once rust-vmm/kvm-ioctls is ready.
|
||||
//
|
||||
40
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_arch_memory_regions_dram() {
|
||||
let regions = arch_memory_regions((1usize << 32) as u64); //4GB
|
||||
assert_eq!(4, regions.len());
|
||||
assert_eq!(GuestAddress(layout::RAM_64BIT_START), regions[3].0);
|
||||
assert_eq!(1usize << 32, regions[3].1);
|
||||
assert_eq!(RegionType::Ram, regions[3].2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_fdt_addr() {
|
||||
let mut regions = Vec::new();
|
||||
|
||||
regions.push((
|
||||
GuestAddress(layout::RAM_64BIT_START),
|
||||
(layout::FDT_MAX_SIZE - 0x1000) as usize,
|
||||
));
|
||||
let mem = GuestMemoryMmap::from_ranges(®ions).expect("Cannot initialize memory");
|
||||
assert_eq!(get_fdt_addr(&mem), layout::RAM_64BIT_START);
|
||||
regions.clear();
|
||||
|
||||
regions.push((
|
||||
GuestAddress(layout::RAM_64BIT_START),
|
||||
(layout::FDT_MAX_SIZE) as usize,
|
||||
));
|
||||
let mem = GuestMemoryMmap::from_ranges(®ions).expect("Cannot initialize memory");
|
||||
assert_eq!(get_fdt_addr(&mem), layout::RAM_64BIT_START);
|
||||
regions.clear();
|
||||
|
||||
regions.push((
|
||||
GuestAddress(layout::RAM_64BIT_START),
|
||||
(layout::FDT_MAX_SIZE + 0x1000) as usize,
|
||||
));
|
||||
let mem = GuestMemoryMmap::from_ranges(®ions).expect("Cannot initialize memory");
|
||||
assert_eq!(get_fdt_addr(&mem), 0x1000 + layout::RAM_64BIT_START);
|
||||
regions.clear();
|
||||
}
|
||||
/// Stub function that needs to be implemented when aarch64 functionality is added.
|
||||
pub fn get_reserved_mem_addr() -> usize {
|
||||
0
|
||||
}
|
||||
|
||||
@@ -1,156 +0,0 @@
|
||||
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the THIRD-PARTY file.
|
||||
|
||||
use super::get_fdt_addr;
|
||||
use hypervisor::kvm::kvm_bindings::{
|
||||
user_pt_regs, KVM_REG_ARM64, KVM_REG_ARM64_SYSREG, KVM_REG_ARM64_SYSREG_CRM_MASK,
|
||||
KVM_REG_ARM64_SYSREG_CRM_SHIFT, KVM_REG_ARM64_SYSREG_CRN_MASK, KVM_REG_ARM64_SYSREG_CRN_SHIFT,
|
||||
KVM_REG_ARM64_SYSREG_OP0_MASK, KVM_REG_ARM64_SYSREG_OP0_SHIFT, KVM_REG_ARM64_SYSREG_OP1_MASK,
|
||||
KVM_REG_ARM64_SYSREG_OP1_SHIFT, KVM_REG_ARM64_SYSREG_OP2_MASK, KVM_REG_ARM64_SYSREG_OP2_SHIFT,
|
||||
KVM_REG_ARM_CORE, KVM_REG_SIZE_U64,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use std::{mem, result};
|
||||
use vm_memory::GuestMemoryMmap;
|
||||
|
||||
/// Errors thrown while setting aarch64 registers.
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Failed to set core register (PC, PSTATE or general purpose ones).
|
||||
SetCoreRegister(hypervisor::HypervisorCpuError),
|
||||
/// Failed to get a system register.
|
||||
GetSysRegister(hypervisor::HypervisorCpuError),
|
||||
}
|
||||
type Result<T> = result::Result<T, Error>;
|
||||
|
||||
#[allow(non_upper_case_globals)]
|
||||
// PSR (Processor State Register) bits.
|
||||
// Taken from arch/arm64/include/uapi/asm/ptrace.h.
|
||||
const PSR_MODE_EL1h: u64 = 0x0000_0005;
|
||||
const PSR_F_BIT: u64 = 0x0000_0040;
|
||||
const PSR_I_BIT: u64 = 0x0000_0080;
|
||||
const PSR_A_BIT: u64 = 0x0000_0100;
|
||||
const PSR_D_BIT: u64 = 0x0000_0200;
|
||||
// Taken from arch/arm64/kvm/inject_fault.c.
|
||||
const PSTATE_FAULT_BITS_64: u64 = PSR_MODE_EL1h | PSR_A_BIT | PSR_F_BIT | PSR_I_BIT | PSR_D_BIT;
|
||||
|
||||
// Following are macros that help with getting the ID of a aarch64 core register.
|
||||
// The core register are represented by the user_pt_regs structure. Look for it in
|
||||
// arch/arm64/include/uapi/asm/ptrace.h.
|
||||
|
||||
// This macro gets the offset of a structure (i.e `str`) member (i.e `field`) without having
|
||||
// an instance of that structure.
|
||||
// It uses a null pointer to retrieve the offset to the field.
|
||||
// Inspired by C solution: `#define offsetof(str, f) ((size_t)(&((str *)0)->f))`.
|
||||
// Doing `offset__of!(user_pt_regs, pstate)` in our rust code will trigger the following:
|
||||
// unsafe { &(*(0 as *const user_pt_regs)).pstate as *const _ as usize }
|
||||
// The dereference expression produces an lvalue, but that lvalue is not actually read from,
|
||||
// we're just doing pointer math on it, so in theory, it should safe.
|
||||
macro_rules! offset__of {
|
||||
($str:ty, $field:ident) => {
|
||||
unsafe { &(*(0 as *const $str)).$field as *const _ as usize }
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! arm64_core_reg {
|
||||
($reg: tt) => {
|
||||
// As per `kvm_arm_copy_reg_indices`, the id of a core register can be obtained like this:
|
||||
// `const u64 core_reg = KVM_REG_ARM64 | KVM_REG_SIZE_U64 | KVM_REG_ARM_CORE | i`, where i is obtained with:
|
||||
// `for (i = 0; i < sizeof(struct kvm_regs) / sizeof(__u32); i++) {`
|
||||
// We are using here `user_pt_regs` since this structure contains the core register and it is at
|
||||
// the start of `kvm_regs`.
|
||||
// struct kvm_regs {
|
||||
// struct user_pt_regs regs; /* sp = sp_el0 */
|
||||
//
|
||||
// __u64 sp_el1;
|
||||
// __u64 elr_el1;
|
||||
//
|
||||
// __u64 spsr[KVM_NR_SPSR];
|
||||
//
|
||||
// struct user_fpsimd_state fp_regs;
|
||||
//};
|
||||
// struct user_pt_regs {
|
||||
// __u64 regs[31];
|
||||
// __u64 sp;
|
||||
// __u64 pc;
|
||||
// __u64 pstate;
|
||||
//};
|
||||
// In our implementation we need: pc, pstate and user_pt_regs->regs[0].
|
||||
KVM_REG_ARM64 as u64
|
||||
| KVM_REG_SIZE_U64 as u64
|
||||
| u64::from(KVM_REG_ARM_CORE)
|
||||
| ((offset__of!(user_pt_regs, $reg) / mem::size_of::<u32>()) as u64)
|
||||
};
|
||||
}
|
||||
|
||||
// This macro computes the ID of a specific ARM64 system register similar to how
|
||||
// the kernel C macro does.
|
||||
// https://elixir.bootlin.com/linux/v4.20.17/source/arch/arm64/include/uapi/asm/kvm.h#L203
|
||||
macro_rules! arm64_sys_reg {
|
||||
($name: tt, $op0: tt, $op1: tt, $crn: tt, $crm: tt, $op2: tt) => {
|
||||
const $name: u64 = KVM_REG_ARM64 as u64
|
||||
| KVM_REG_SIZE_U64 as u64
|
||||
| KVM_REG_ARM64_SYSREG as u64
|
||||
| ((($op0 as u64) << KVM_REG_ARM64_SYSREG_OP0_SHIFT)
|
||||
& KVM_REG_ARM64_SYSREG_OP0_MASK as u64)
|
||||
| ((($op1 as u64) << KVM_REG_ARM64_SYSREG_OP1_SHIFT)
|
||||
& KVM_REG_ARM64_SYSREG_OP1_MASK as u64)
|
||||
| ((($crn as u64) << KVM_REG_ARM64_SYSREG_CRN_SHIFT)
|
||||
& KVM_REG_ARM64_SYSREG_CRN_MASK as u64)
|
||||
| ((($crm as u64) << KVM_REG_ARM64_SYSREG_CRM_SHIFT)
|
||||
& KVM_REG_ARM64_SYSREG_CRM_MASK as u64)
|
||||
| ((($op2 as u64) << KVM_REG_ARM64_SYSREG_OP2_SHIFT)
|
||||
& KVM_REG_ARM64_SYSREG_OP2_MASK as u64);
|
||||
};
|
||||
}
|
||||
|
||||
// Constant imported from the Linux kernel:
|
||||
// https://elixir.bootlin.com/linux/v4.20.17/source/arch/arm64/include/asm/sysreg.h#L135
|
||||
arm64_sys_reg!(MPIDR_EL1, 3, 0, 0, 0, 5);
|
||||
|
||||
/// Configure core registers for a given CPU.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `vcpu` - Structure for the VCPU that holds the VCPU's fd.
|
||||
/// * `cpu_id` - Index of current vcpu.
|
||||
/// * `boot_ip` - Starting instruction pointer.
|
||||
/// * `mem` - Reserved DRAM for current VM.
|
||||
pub fn setup_regs(
|
||||
vcpu: &Arc<dyn hypervisor::Vcpu>,
|
||||
cpu_id: u8,
|
||||
boot_ip: u64,
|
||||
mem: &GuestMemoryMmap,
|
||||
) -> Result<()> {
|
||||
// Get the register index of the PSTATE (Processor State) register.
|
||||
vcpu.set_one_reg(arm64_core_reg!(pstate), PSTATE_FAULT_BITS_64)
|
||||
.map_err(Error::SetCoreRegister)?;
|
||||
|
||||
// Other vCPUs are powered off initially awaiting PSCI wakeup.
|
||||
if cpu_id == 0 {
|
||||
// Setting the PC (Processor Counter) to the current program address (kernel address).
|
||||
vcpu.set_one_reg(arm64_core_reg!(pc), boot_ip)
|
||||
.map_err(Error::SetCoreRegister)?;
|
||||
|
||||
// Last mandatory thing to set -> the address pointing to the FDT (also called DTB).
|
||||
// "The device tree blob (dtb) must be placed on an 8-byte boundary and must
|
||||
// not exceed 2 megabytes in size." -> https://www.kernel.org/doc/Documentation/arm64/booting.txt.
|
||||
// We are choosing to place it the end of DRAM. See `get_fdt_addr`.
|
||||
vcpu.set_one_reg(arm64_core_reg!(regs), get_fdt_addr(mem) as u64)
|
||||
.map_err(Error::SetCoreRegister)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Read the MPIDR - Multiprocessor Affinity Register.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `vcpu` - Structure for the VCPU that holds the VCPU's fd.
|
||||
pub fn read_mpidr(vcpu: &Arc<dyn hypervisor::Vcpu>) -> Result<u64> {
|
||||
vcpu.get_one_reg(MPIDR_EL1).map_err(Error::GetSysRegister)
|
||||
}
|
||||
104
arch/src/lib.rs
104
arch/src/lib.rs
@@ -1,11 +1,6 @@
|
||||
// Copyright 2020 Arm Limited (or its affiliates). All rights reserved.
|
||||
// Copyright © 2020, Oracle and/or its affiliates.
|
||||
//
|
||||
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//! Implements platform specific functionality.
|
||||
//! Supported platforms: x86_64, aarch64.
|
||||
#![allow(
|
||||
clippy::unreadable_literal,
|
||||
clippy::redundant_static_lifetimes,
|
||||
@@ -15,53 +10,31 @@
|
||||
)]
|
||||
|
||||
extern crate byteorder;
|
||||
extern crate hypervisor;
|
||||
extern crate kvm_bindings;
|
||||
extern crate libc;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
#[cfg(feature = "acpi")]
|
||||
extern crate acpi_tables;
|
||||
extern crate arch_gen;
|
||||
extern crate kvm_ioctls;
|
||||
extern crate linux_loader;
|
||||
extern crate vm_memory;
|
||||
|
||||
use std::fmt;
|
||||
use std::result;
|
||||
|
||||
/// Type for returning error code.
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
/// X86_64 specific error triggered during system configuration.
|
||||
X86_64Setup(x86_64::Error),
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
/// AArch64 specific error triggered during system configuration.
|
||||
AArch64Setup(aarch64::Error),
|
||||
/// The zero page extends past the end of guest_mem.
|
||||
ZeroPagePastRamEnd,
|
||||
/// Error writing the zero page of guest memory.
|
||||
ZeroPageSetup(vm_memory::GuestMemoryError),
|
||||
/// The memory map table extends past the end of guest memory.
|
||||
MemmapTablePastRamEnd,
|
||||
/// Error writing memory map table to guest memory.
|
||||
MemmapTableSetup,
|
||||
/// The hvm_start_info structure extends past the end of guest memory.
|
||||
StartInfoPastRamEnd,
|
||||
/// Error writing hvm_start_info to guest memory.
|
||||
StartInfoSetup,
|
||||
/// Failed to compute initramfs address.
|
||||
InitramfsAddress,
|
||||
/// Error writing module entry to guest memory.
|
||||
ModlistSetup(vm_memory::GuestMemoryError),
|
||||
/// RSDP Beyond Guest Memory
|
||||
RSDPPastRamEnd,
|
||||
}
|
||||
|
||||
/// Type for returning public functions outcome.
|
||||
pub type Result<T> = result::Result<T, Error>;
|
||||
|
||||
/// Type for memory region types.
|
||||
#[derive(PartialEq, Debug)]
|
||||
#[derive(PartialEq)]
|
||||
pub enum RegionType {
|
||||
/// RAM type
|
||||
Ram,
|
||||
@@ -79,15 +52,13 @@ pub enum RegionType {
|
||||
Reserved,
|
||||
}
|
||||
|
||||
/// Module for aarch64 related functionality.
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub mod aarch64;
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub use aarch64::{
|
||||
arch_memory_regions, configure_system, configure_vcpu, fdt::DeviceInfoForFDT,
|
||||
get_host_cpu_phys_bits, get_kernel_start, initramfs_load_addr, layout,
|
||||
layout::CMDLINE_MAX_SIZE, layout::IRQ_BASE, layout::IRQ_MAX, EntryPoint,
|
||||
arch_memory_regions, configure_system, get_reserved_mem_addr, layout::CMDLINE_MAX_SIZE,
|
||||
layout::CMDLINE_START,
|
||||
};
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
@@ -95,66 +66,5 @@ pub mod x86_64;
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub use x86_64::{
|
||||
arch_memory_regions, configure_system, configure_vcpu, get_host_cpu_phys_bits,
|
||||
initramfs_load_addr, layout, layout::CMDLINE_MAX_SIZE, layout::CMDLINE_START, regs,
|
||||
BootProtocol, CpuidPatch, CpuidReg, EntryPoint,
|
||||
arch_memory_regions, configure_system, layout, layout::CMDLINE_MAX_SIZE, layout::CMDLINE_START,
|
||||
};
|
||||
|
||||
/// Safe wrapper for `sysconf(_SC_PAGESIZE)`.
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
#[inline(always)]
|
||||
fn pagesize() -> usize {
|
||||
// Trivially safe
|
||||
unsafe { libc::sysconf(libc::_SC_PAGESIZE) as usize }
|
||||
}
|
||||
|
||||
/// Type for passing information about the initramfs in the guest memory.
|
||||
pub struct InitramfsConfig {
|
||||
/// Load address of initramfs in guest memory
|
||||
pub address: vm_memory::GuestAddress,
|
||||
/// Size of initramfs in guest memory
|
||||
pub size: usize,
|
||||
}
|
||||
|
||||
/// Types of devices that can get attached to this platform.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, Copy)]
|
||||
pub enum DeviceType {
|
||||
/// Device Type: Virtio.
|
||||
Virtio(u32),
|
||||
/// Device Type: Serial.
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
Serial,
|
||||
/// Device Type: RTC.
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
RTC,
|
||||
}
|
||||
|
||||
/// Default (smallest) memory page size for the supported architectures.
|
||||
pub const PAGE_SIZE: usize = 4096;
|
||||
|
||||
impl fmt::Display for DeviceType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
/// Structure to describe MMIO device information
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub struct MMIODeviceInfo {
|
||||
pub addr: u64,
|
||||
pub irq: u32,
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
impl DeviceInfoForFDT for MMIODeviceInfo {
|
||||
fn addr(&self) -> u64 {
|
||||
self.addr
|
||||
}
|
||||
fn irq(&self) -> u32 {
|
||||
self.irq
|
||||
}
|
||||
fn length(&self) -> u64 {
|
||||
4096 as u64
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// Copyright © 2020, Oracle and/or its affiliates.
|
||||
//
|
||||
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
@@ -8,51 +6,26 @@
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
|
||||
// For GDT details see arch/x86/include/asm/segment.h
|
||||
use hypervisor::x86_64::SegmentRegister;
|
||||
|
||||
use kvm_bindings::kvm_segment;
|
||||
|
||||
/// Constructor for a conventional segment GDT (or LDT) entry. Derived from the kernel's segment.h.
|
||||
pub fn gdt_entry(flags: u16, base: u32, limit: u32) -> u64 {
|
||||
(((base as u64) & 0xff000000u64) << (56 - 24))
|
||||
((((base as u64) & 0xff000000u64) << (56 - 24))
|
||||
| (((flags as u64) & 0x0000f0ffu64) << 40)
|
||||
| (((limit as u64) & 0x000f0000u64) << (48 - 16))
|
||||
| (((base as u64) & 0x00ffffffu64) << 16)
|
||||
| ((limit as u64) & 0x0000ffffu64)
|
||||
| ((limit as u64) & 0x0000ffffu64))
|
||||
}
|
||||
|
||||
fn get_base(entry: u64) -> u64 {
|
||||
(((entry) & 0xFF00000000000000) >> 32)
|
||||
((((entry) & 0xFF00000000000000) >> 32)
|
||||
| (((entry) & 0x000000FF00000000) >> 16)
|
||||
| (((entry) & 0x00000000FFFF0000) >> 16)
|
||||
| (((entry) & 0x00000000FFFF0000) >> 16))
|
||||
}
|
||||
|
||||
// Extract the segment limit from the GDT segment descriptor.
|
||||
//
|
||||
// In a segment descriptor, the limit field is 20 bits, so it can directly describe
|
||||
// a range from 0 to 0xFFFFF (1MByte). When G flag is set (4-KByte page granularity) it
|
||||
// scales the value in the limit field by a factor of 2^12 (4Kbytes), making the effective
|
||||
// limit range from 0xFFF (4 KBytes) to 0xFFFF_FFFF (4 GBytes).
|
||||
//
|
||||
// However, the limit field in the VMCS definition is a 32 bit field, and the limit value is not
|
||||
// automatically scaled using the G flag. This means that for a desired range of 4GB for a
|
||||
// given segment, its limit must be specified as 0xFFFF_FFFF. Therefore the method of obtaining
|
||||
// the limit from the GDT entry is not sufficient, since it only provides 20 bits when 32 bits
|
||||
// are necessary. Fortunately, we can check if the G flag is set when extracting the limit since
|
||||
// the full GDT entry is passed as an argument, and perform the scaling of the limit value to
|
||||
// return the full 32 bit value.
|
||||
//
|
||||
// The scaling mentioned above is required when using PVH boot, since the guest boots in protected
|
||||
// (32-bit) mode and must be able to access the entire 32-bit address space. It does not cause issues
|
||||
// for the case of direct boot to 64-bit (long) mode, since in 64-bit mode the processor does not
|
||||
// perform runtime limit checking on code or data segments.
|
||||
fn get_limit(entry: u64) -> u32 {
|
||||
let limit: u32 =
|
||||
((((entry) & 0x000F000000000000) >> 32) | ((entry) & 0x000000000000FFFF)) as u32;
|
||||
|
||||
// Perform manual limit scaling if G flag is set
|
||||
match get_g(entry) {
|
||||
0 => limit,
|
||||
_ => ((limit << 12) | 0xFFF), // G flag is either 0 or 1
|
||||
}
|
||||
((((entry) & 0x000F000000000000) >> 32) | ((entry) & 0x000000000000FFFF)) as u32
|
||||
}
|
||||
|
||||
fn get_g(entry: u64) -> u8 {
|
||||
@@ -87,14 +60,14 @@ fn get_type(entry: u64) -> u8 {
|
||||
((entry & 0x00000F0000000000) >> 40) as u8
|
||||
}
|
||||
|
||||
/// Automatically build the struct for SET_SREGS from the kernel bit fields.
|
||||
/// Automatically build the kvm struct for SET_SREGS from the kernel bit fields.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `entry` - The gdt entry.
|
||||
/// * `table_index` - Index of the entry in the gdt table.
|
||||
pub fn segment_from_gdt(entry: u64, table_index: u8) -> SegmentRegister {
|
||||
SegmentRegister {
|
||||
pub fn kvm_segment_from_gdt(entry: u64, table_index: u8) -> kvm_segment {
|
||||
kvm_segment {
|
||||
base: get_base(entry),
|
||||
limit: get_limit(entry),
|
||||
selector: (table_index * 8) as u16,
|
||||
@@ -121,7 +94,7 @@ mod tests {
|
||||
#[test]
|
||||
fn field_parse() {
|
||||
let gdt = gdt_entry(0xA09B, 0x100000, 0xfffff);
|
||||
let seg = segment_from_gdt(gdt, 0);
|
||||
let seg = kvm_segment_from_gdt(gdt, 0);
|
||||
// 0xA09B
|
||||
// 'A'
|
||||
assert_eq!(0x1, seg.g);
|
||||
@@ -136,7 +109,7 @@ mod tests {
|
||||
assert_eq!(0xB, seg.type_);
|
||||
// base and limit
|
||||
assert_eq!(0x100000, seg.base);
|
||||
assert_eq!(0xffffffff, seg.limit);
|
||||
assert_eq!(0xfffff, seg.limit);
|
||||
assert_eq!(0x0, seg.unusable);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,27 +8,27 @@
|
||||
use std::io::Cursor;
|
||||
use std::mem;
|
||||
use std::result;
|
||||
use std::sync::Arc;
|
||||
|
||||
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
|
||||
|
||||
use hypervisor::x86_64::LapicState;
|
||||
use kvm_bindings::kvm_lapic_state;
|
||||
use kvm_ioctls;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
GetLapic(anyhow::Error),
|
||||
SetLapic(anyhow::Error),
|
||||
GetLapic(kvm_ioctls::Error),
|
||||
SetLapic(kvm_ioctls::Error),
|
||||
}
|
||||
|
||||
pub type Result<T> = result::Result<T, hypervisor::HypervisorCpuError>;
|
||||
pub type Result<T> = result::Result<T, Error>;
|
||||
|
||||
// Defines poached from apicdef.h kernel header.
|
||||
pub const APIC_LVT0: usize = 0x350;
|
||||
pub const APIC_LVT1: usize = 0x360;
|
||||
pub const APIC_MODE_NMI: u32 = 0x4;
|
||||
pub const APIC_MODE_EXTINT: u32 = 0x7;
|
||||
const APIC_LVT0: usize = 0x350;
|
||||
const APIC_LVT1: usize = 0x360;
|
||||
const APIC_MODE_NMI: u32 = 0x4;
|
||||
const APIC_MODE_EXTINT: u32 = 0x7;
|
||||
|
||||
pub fn get_klapic_reg(klapic: &LapicState, reg_offset: usize) -> u32 {
|
||||
fn get_klapic_reg(klapic: &kvm_lapic_state, reg_offset: usize) -> u32 {
|
||||
let sliceu8 = unsafe {
|
||||
// This array is only accessed as parts of a u32 word, so interpret it as a u8 array.
|
||||
// Cursors are only readable on arrays of u8, not i8(c_char).
|
||||
@@ -41,7 +41,7 @@ pub fn get_klapic_reg(klapic: &LapicState, reg_offset: usize) -> u32 {
|
||||
.expect("Failed to read klapic register")
|
||||
}
|
||||
|
||||
pub fn set_klapic_reg(klapic: &mut LapicState, reg_offset: usize, value: u32) {
|
||||
fn set_klapic_reg(klapic: &mut kvm_lapic_state, reg_offset: usize, value: u32) {
|
||||
let sliceu8 = unsafe {
|
||||
// This array is only accessed as parts of a u32 word, so interpret it as a u8 array.
|
||||
// Cursors are only readable on arrays of u8, not i8(c_char).
|
||||
@@ -54,16 +54,16 @@ pub fn set_klapic_reg(klapic: &mut LapicState, reg_offset: usize, value: u32) {
|
||||
.expect("Failed to write klapic register")
|
||||
}
|
||||
|
||||
pub fn set_apic_delivery_mode(reg: u32, mode: u32) -> u32 {
|
||||
((reg) & !0x700) | ((mode) << 8)
|
||||
fn set_apic_delivery_mode(reg: u32, mode: u32) -> u32 {
|
||||
(((reg) & !0x700) | ((mode) << 8))
|
||||
}
|
||||
|
||||
/// Configures LAPICs. LAPIC0 is set for external interrupts, LAPIC1 is set for NMI.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `vcpu` - The VCPU object to configure.
|
||||
pub fn set_lint(vcpu: &Arc<dyn hypervisor::Vcpu>) -> Result<()> {
|
||||
let mut klapic = vcpu.get_lapic()?;
|
||||
pub fn set_lint(vcpu: &kvm_ioctls::VcpuFd) -> Result<()> {
|
||||
let mut klapic = vcpu.get_lapic().map_err(Error::GetLapic)?;
|
||||
|
||||
let lvt_lint0 = get_klapic_reg(&klapic, APIC_LVT0);
|
||||
set_klapic_reg(
|
||||
@@ -78,23 +78,24 @@ pub fn set_lint(vcpu: &Arc<dyn hypervisor::Vcpu>) -> Result<()> {
|
||||
set_apic_delivery_mode(lvt_lint1, APIC_MODE_NMI),
|
||||
);
|
||||
|
||||
vcpu.set_lapic(&klapic)
|
||||
vcpu.set_lapic(&klapic).map_err(Error::SetLapic)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
extern crate kvm_ioctls;
|
||||
extern crate rand;
|
||||
use self::rand::Rng;
|
||||
|
||||
use super::*;
|
||||
use kvm_ioctls::Kvm;
|
||||
|
||||
const KVM_APIC_REG_SIZE: usize = 0x400;
|
||||
|
||||
#[test]
|
||||
fn test_set_and_get_klapic_reg() {
|
||||
let reg_offset = 0x340;
|
||||
let mut klapic = LapicState::default();
|
||||
let mut klapic = kvm_lapic_state::default();
|
||||
set_klapic_reg(&mut klapic, reg_offset, 3);
|
||||
let value = get_klapic_reg(&klapic, reg_offset);
|
||||
assert_eq!(value, 3);
|
||||
@@ -104,7 +105,7 @@ mod tests {
|
||||
#[should_panic]
|
||||
fn test_set_and_get_klapic_out_of_bounds() {
|
||||
let reg_offset = KVM_APIC_REG_SIZE + 10;
|
||||
let mut klapic = LapicState::default();
|
||||
let mut klapic = kvm_lapic_state::default();
|
||||
set_klapic_reg(&mut klapic, reg_offset, 3);
|
||||
}
|
||||
|
||||
@@ -118,4 +119,40 @@ mod tests {
|
||||
let after: Vec<u32> = v.iter().map(|x| ((*x & !0x700) | ((2) << 8))).collect();
|
||||
assert_eq!(v, after);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_setlint() {
|
||||
let kvm = kvm_ioctls::Kvm::new().unwrap();
|
||||
assert!(kvm.check_extension(kvm_ioctls::Cap::Irqchip));
|
||||
let vm = kvm.create_vm().unwrap();
|
||||
//the get_lapic ioctl will fail if there is no irqchip created beforehand.
|
||||
assert!(vm.create_irq_chip().is_ok());
|
||||
let vcpu = vm.create_vcpu(0).unwrap();
|
||||
let klapic_before: kvm_lapic_state = vcpu.get_lapic().unwrap();
|
||||
|
||||
// Compute the value that is expected to represent LVT0 and LVT1.
|
||||
let lint0 = get_klapic_reg(&klapic_before, APIC_LVT0);
|
||||
let lint1 = get_klapic_reg(&klapic_before, APIC_LVT1);
|
||||
let lint0_mode_expected = set_apic_delivery_mode(lint0, APIC_MODE_EXTINT);
|
||||
let lint1_mode_expected = set_apic_delivery_mode(lint1, APIC_MODE_NMI);
|
||||
|
||||
set_lint(&vcpu).unwrap();
|
||||
|
||||
// Compute the value that represents LVT0 and LVT1 after set_lint.
|
||||
let klapic_actual: kvm_lapic_state = vcpu.get_lapic().unwrap();
|
||||
let lint0_mode_actual = get_klapic_reg(&klapic_actual, APIC_LVT0);
|
||||
let lint1_mode_actual = get_klapic_reg(&klapic_actual, APIC_LVT1);
|
||||
assert_eq!(lint0_mode_expected, lint0_mode_actual);
|
||||
assert_eq!(lint1_mode_expected, lint1_mode_actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_setlint_fails() {
|
||||
let kvm = Kvm::new().unwrap();
|
||||
let vm = kvm.create_vm().unwrap();
|
||||
let vcpu = vm.create_vcpu(0).unwrap();
|
||||
// 'get_lapic' ioctl triggered by the 'set_lint' function will fail if there is no
|
||||
// irqchip created beforehand.
|
||||
assert!(set_lint(&vcpu).is_err());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// Copyright © 2020, Oracle and/or its affiliates.
|
||||
//
|
||||
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
@@ -28,17 +26,6 @@ pub const LOW_RAM_START: GuestAddress = GuestAddress(0x0);
|
||||
pub const BOOT_GDT_START: GuestAddress = GuestAddress(0x500);
|
||||
pub const BOOT_IDT_START: GuestAddress = GuestAddress(0x520);
|
||||
|
||||
/// Address for the hvm_start_info struct used in PVH boot
|
||||
pub const PVH_INFO_START: GuestAddress = GuestAddress(0x6000);
|
||||
|
||||
/// Starting address of array of modules of hvm_modlist_entry type.
|
||||
/// Used to enable initrd support using the PVH boot ABI.
|
||||
pub const MODLIST_START: GuestAddress = GuestAddress(0x6040);
|
||||
|
||||
/// Address of memory map table used in PVH boot. Can overlap
|
||||
/// with the zero page address since they are mutually exclusive.
|
||||
pub const MEMMAP_START: GuestAddress = GuestAddress(0x7000);
|
||||
|
||||
/// The 'zero page', a.k.a linux kernel bootparams.
|
||||
pub const ZERO_PAGE_START: GuestAddress = GuestAddress(0x7000);
|
||||
|
||||
@@ -47,10 +34,9 @@ pub const BOOT_STACK_START: GuestAddress = GuestAddress(0x8000);
|
||||
pub const BOOT_STACK_POINTER: GuestAddress = GuestAddress(0x8ff0);
|
||||
|
||||
// Initial pagetables.
|
||||
pub const PML5_START: GuestAddress = GuestAddress(0x9000);
|
||||
pub const PML4_START: GuestAddress = GuestAddress(0xa000);
|
||||
pub const PDPTE_START: GuestAddress = GuestAddress(0xb000);
|
||||
pub const PDE_START: GuestAddress = GuestAddress(0xc000);
|
||||
pub const PML4_START: GuestAddress = GuestAddress(0x9000);
|
||||
pub const PDPTE_START: GuestAddress = GuestAddress(0xa000);
|
||||
pub const PDE_START: GuestAddress = GuestAddress(0xb000);
|
||||
|
||||
/// Kernel command line start address.
|
||||
pub const CMDLINE_START: GuestAddress = GuestAddress(0x20000);
|
||||
@@ -70,8 +56,6 @@ pub const EBDA_START: GuestAddress = GuestAddress(0xa0000);
|
||||
// ACPI RSDP table
|
||||
pub const RSDP_POINTER: GuestAddress = EBDA_START;
|
||||
|
||||
pub const SMBIOS_START: u64 = 0xf0000; // First possible location per the spec.
|
||||
|
||||
// == End of "EBDA" range ==
|
||||
|
||||
// ** High RAM (start: 1MiB, length: 3071MiB) **
|
||||
@@ -81,18 +65,18 @@ pub const HIGH_RAM_START: GuestAddress = GuestAddress(0x100000);
|
||||
|
||||
// ** 32-bit reserved area (start: 3GiB, length: 1GiB) **
|
||||
pub const MEM_32BIT_RESERVED_START: GuestAddress = GuestAddress(0xc000_0000);
|
||||
pub const MEM_32BIT_RESERVED_SIZE: GuestUsize = 1024 << 20;
|
||||
pub const MEM_32BIT_RESERVED_SIZE: GuestUsize = (1024 << 20);
|
||||
|
||||
// == Fixed constants within the "32-bit reserved" range ==
|
||||
|
||||
// Sub range: 32-bit PCI devices (start: 3GiB, length: 640Mib)
|
||||
pub const MEM_32BIT_DEVICES_START: GuestAddress = MEM_32BIT_RESERVED_START;
|
||||
pub const MEM_32BIT_DEVICES_SIZE: GuestUsize = 640 << 20;
|
||||
pub const MEM_32BIT_DEVICES_SIZE: GuestUsize = (640 << 20);
|
||||
|
||||
// PCI MMCONFIG space (start: after the device space, length: 256MiB)
|
||||
pub const PCI_MMCONFIG_START: GuestAddress =
|
||||
GuestAddress(MEM_32BIT_DEVICES_START.0 + MEM_32BIT_DEVICES_SIZE);
|
||||
pub const PCI_MMCONFIG_SIZE: GuestUsize = 256 << 20;
|
||||
pub const PCI_MMCONFIG_SIZE: GuestUsize = (256 << 20);
|
||||
|
||||
// IOAPIC
|
||||
pub const IOAPIC_START: GuestAddress = GuestAddress(0xfec0_0000);
|
||||
|
||||
@@ -1,131 +1,26 @@
|
||||
// Copyright © 2020, Oracle and/or its affiliates.
|
||||
//
|
||||
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
use std::sync::Arc;
|
||||
|
||||
mod gdt;
|
||||
pub mod interrupts;
|
||||
pub mod layout;
|
||||
#[cfg(not(feature = "acpi"))]
|
||||
mod mptable;
|
||||
pub mod regs;
|
||||
use crate::InitramfsConfig;
|
||||
|
||||
use crate::RegionType;
|
||||
use hypervisor::{CpuId, CpuIdEntry, CPUID_FLAG_VALID_INDEX};
|
||||
use linux_loader::loader::bootparam::{boot_params, setup_header};
|
||||
use linux_loader::loader::elf::start_info::{
|
||||
hvm_memmap_table_entry, hvm_modlist_entry, hvm_start_info,
|
||||
};
|
||||
use std::mem;
|
||||
use vm_memory::{
|
||||
Address, ByteValued, Bytes, GuestAddress, GuestAddressSpace, GuestMemory, GuestMemoryAtomic,
|
||||
GuestMemoryMmap, GuestMemoryRegion, GuestUsize,
|
||||
Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryMmap, GuestUsize,
|
||||
};
|
||||
mod smbios;
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum BootProtocol {
|
||||
LinuxBoot,
|
||||
PvhBoot,
|
||||
}
|
||||
|
||||
impl ::std::fmt::Display for BootProtocol {
|
||||
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
||||
match self {
|
||||
BootProtocol::LinuxBoot => write!(f, "Linux 64-bit boot protocol"),
|
||||
BootProtocol::PvhBoot => write!(f, "PVH boot protocol"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
/// Specifies the entry point address where the guest must start
|
||||
/// executing code, as well as which of the supported boot protocols
|
||||
/// is to be used to configure the guest initial state.
|
||||
pub struct EntryPoint {
|
||||
/// Address in guest memory where the guest must start execution
|
||||
pub entry_addr: GuestAddress,
|
||||
/// Specifies which boot protocol to use
|
||||
pub protocol: BootProtocol,
|
||||
/// This field is used for bzImage to fill zero page
|
||||
pub setup_header: Option<setup_header>,
|
||||
}
|
||||
|
||||
const E820_RAM: u32 = 1;
|
||||
const E820_RESERVED: u32 = 2;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SgxEpcSection {
|
||||
start: GuestAddress,
|
||||
size: GuestUsize,
|
||||
}
|
||||
|
||||
impl SgxEpcSection {
|
||||
pub fn new(start: GuestAddress, size: GuestUsize) -> Self {
|
||||
SgxEpcSection { start, size }
|
||||
}
|
||||
pub fn start(&self) -> GuestAddress {
|
||||
self.start
|
||||
}
|
||||
pub fn size(&self) -> GuestUsize {
|
||||
self.size
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SgxEpcRegion {
|
||||
start: GuestAddress,
|
||||
size: GuestUsize,
|
||||
epc_sections: Vec<SgxEpcSection>,
|
||||
}
|
||||
|
||||
impl SgxEpcRegion {
|
||||
pub fn new(start: GuestAddress, size: GuestUsize) -> Self {
|
||||
SgxEpcRegion {
|
||||
start,
|
||||
size,
|
||||
epc_sections: Vec::new(),
|
||||
}
|
||||
}
|
||||
pub fn start(&self) -> GuestAddress {
|
||||
self.start
|
||||
}
|
||||
pub fn size(&self) -> GuestUsize {
|
||||
self.size
|
||||
}
|
||||
pub fn epc_sections(&self) -> &Vec<SgxEpcSection> {
|
||||
&self.epc_sections
|
||||
}
|
||||
pub fn push(&mut self, epc_section: SgxEpcSection) {
|
||||
self.epc_sections.push(epc_section);
|
||||
}
|
||||
}
|
||||
|
||||
// This is a workaround to the Rust enforcement specifying that any implementation of a foreign
|
||||
// trait (in this case `DataInit`) where:
|
||||
// * the type that is implementing the trait is foreign or
|
||||
// * all of the parameters being passed to the trait (if there are any) are also foreign
|
||||
// is prohibited.
|
||||
#[derive(Copy, Clone, Default)]
|
||||
struct StartInfoWrapper(hvm_start_info);
|
||||
|
||||
// It is safe to initialize StartInfoWrapper which is a wrapper over `hvm_start_info` (a series of ints).
|
||||
unsafe impl ByteValued for StartInfoWrapper {}
|
||||
|
||||
#[derive(Copy, Clone, Default)]
|
||||
struct MemmapTableEntryWrapper(hvm_memmap_table_entry);
|
||||
|
||||
unsafe impl ByteValued for MemmapTableEntryWrapper {}
|
||||
|
||||
#[derive(Copy, Clone, Default)]
|
||||
struct ModlistEntryWrapper(hvm_modlist_entry);
|
||||
|
||||
unsafe impl ByteValued for ModlistEntryWrapper {}
|
||||
|
||||
// This is a workaround to the Rust enforcement specifying that any implementation of a foreign
|
||||
// trait (in this case `DataInit`) where:
|
||||
// * the type that is implementing the trait is foreign or
|
||||
@@ -141,39 +36,8 @@ unsafe impl ByteValued for BootParamsWrapper {}
|
||||
pub enum Error {
|
||||
/// Invalid e820 setup params.
|
||||
E820Configuration,
|
||||
#[cfg(not(feature = "acpi"))]
|
||||
/// Error writing MP table to memory.
|
||||
MpTableSetup(mptable::Error),
|
||||
|
||||
/// Error configuring the general purpose registers
|
||||
REGSConfiguration(regs::Error),
|
||||
|
||||
/// Error configuring the special registers
|
||||
SREGSConfiguration(regs::Error),
|
||||
|
||||
/// Error configuring the floating point related registers
|
||||
FPUConfiguration(regs::Error),
|
||||
|
||||
/// Error configuring the MSR registers
|
||||
MSRSConfiguration(regs::Error),
|
||||
|
||||
/// Failed to set supported CPUs.
|
||||
SetSupportedCpusFailed(anyhow::Error),
|
||||
|
||||
/// Cannot set the local interruption due to bad configuration.
|
||||
LocalIntConfiguration(anyhow::Error),
|
||||
|
||||
/// Error setting up SMBIOS table
|
||||
SmbiosSetup(smbios::Error),
|
||||
|
||||
/// Could not find any SGX EPC section
|
||||
NoSgxEpcSection,
|
||||
|
||||
/// Missing SGX CPU feature
|
||||
MissingSgxFeature,
|
||||
|
||||
/// Missing SGX_LC CPU feature
|
||||
MissingSgxLaunchControlFeature,
|
||||
}
|
||||
|
||||
impl From<Error> for super::Error {
|
||||
@@ -182,183 +46,6 @@ impl From<Error> for super::Error {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum CpuidReg {
|
||||
EAX,
|
||||
EBX,
|
||||
ECX,
|
||||
EDX,
|
||||
}
|
||||
|
||||
pub struct CpuidPatch {
|
||||
pub function: u32,
|
||||
pub index: u32,
|
||||
pub flags_bit: Option<u8>,
|
||||
pub eax_bit: Option<u8>,
|
||||
pub ebx_bit: Option<u8>,
|
||||
pub ecx_bit: Option<u8>,
|
||||
pub edx_bit: Option<u8>,
|
||||
}
|
||||
|
||||
impl CpuidPatch {
|
||||
pub fn set_cpuid_reg(
|
||||
cpuid: &mut CpuId,
|
||||
function: u32,
|
||||
index: Option<u32>,
|
||||
reg: CpuidReg,
|
||||
value: u32,
|
||||
) {
|
||||
let entries = cpuid.as_mut_slice();
|
||||
|
||||
let mut entry_found = false;
|
||||
for entry in entries.iter_mut() {
|
||||
if entry.function == function && (index == None || index.unwrap() == entry.index) {
|
||||
entry_found = true;
|
||||
match reg {
|
||||
CpuidReg::EAX => {
|
||||
entry.eax = value;
|
||||
}
|
||||
CpuidReg::EBX => {
|
||||
entry.ebx = value;
|
||||
}
|
||||
CpuidReg::ECX => {
|
||||
entry.ecx = value;
|
||||
}
|
||||
CpuidReg::EDX => {
|
||||
entry.edx = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if entry_found {
|
||||
return;
|
||||
}
|
||||
|
||||
// Entry not found, so let's add it.
|
||||
if let Some(index) = index {
|
||||
let mut entry = CpuIdEntry {
|
||||
function,
|
||||
index,
|
||||
flags: CPUID_FLAG_VALID_INDEX,
|
||||
..Default::default()
|
||||
};
|
||||
match reg {
|
||||
CpuidReg::EAX => {
|
||||
entry.eax = value;
|
||||
}
|
||||
CpuidReg::EBX => {
|
||||
entry.ebx = value;
|
||||
}
|
||||
CpuidReg::ECX => {
|
||||
entry.ecx = value;
|
||||
}
|
||||
CpuidReg::EDX => {
|
||||
entry.edx = value;
|
||||
}
|
||||
}
|
||||
|
||||
if let Err(e) = cpuid.push(entry) {
|
||||
error!("Failed adding new CPUID entry: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn patch_cpuid(cpuid: &mut CpuId, patches: Vec<CpuidPatch>) {
|
||||
let entries = cpuid.as_mut_slice();
|
||||
|
||||
for entry in entries.iter_mut() {
|
||||
for patch in patches.iter() {
|
||||
if entry.function == patch.function && entry.index == patch.index {
|
||||
if let Some(flags_bit) = patch.flags_bit {
|
||||
entry.flags |= 1 << flags_bit;
|
||||
}
|
||||
if let Some(eax_bit) = patch.eax_bit {
|
||||
entry.eax |= 1 << eax_bit;
|
||||
}
|
||||
if let Some(ebx_bit) = patch.ebx_bit {
|
||||
entry.ebx |= 1 << ebx_bit;
|
||||
}
|
||||
if let Some(ecx_bit) = patch.ecx_bit {
|
||||
entry.ecx |= 1 << ecx_bit;
|
||||
}
|
||||
if let Some(edx_bit) = patch.edx_bit {
|
||||
entry.edx |= 1 << edx_bit;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_feature_enabled(
|
||||
cpuid: &CpuId,
|
||||
function: u32,
|
||||
index: u32,
|
||||
reg: CpuidReg,
|
||||
feature_bit: usize,
|
||||
) -> bool {
|
||||
let entries = cpuid.as_slice();
|
||||
let mask = 1 << feature_bit;
|
||||
|
||||
for entry in entries.iter() {
|
||||
if entry.function == function && entry.index == index {
|
||||
let reg_val: u32;
|
||||
match reg {
|
||||
CpuidReg::EAX => {
|
||||
reg_val = entry.eax;
|
||||
}
|
||||
CpuidReg::EBX => {
|
||||
reg_val = entry.ebx;
|
||||
}
|
||||
CpuidReg::ECX => {
|
||||
reg_val = entry.ecx;
|
||||
}
|
||||
CpuidReg::EDX => {
|
||||
reg_val = entry.edx;
|
||||
}
|
||||
}
|
||||
|
||||
return (reg_val & mask) == mask;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn configure_vcpu(
|
||||
fd: &Arc<dyn hypervisor::Vcpu>,
|
||||
id: u8,
|
||||
kernel_entry_point: Option<EntryPoint>,
|
||||
vm_memory: &GuestMemoryAtomic<GuestMemoryMmap>,
|
||||
cpuid: CpuId,
|
||||
) -> super::Result<()> {
|
||||
let mut cpuid = cpuid;
|
||||
CpuidPatch::set_cpuid_reg(&mut cpuid, 0xb, None, CpuidReg::EDX, u32::from(id));
|
||||
CpuidPatch::set_cpuid_reg(&mut cpuid, 0x1f, None, CpuidReg::EDX, u32::from(id));
|
||||
fd.set_cpuid2(&cpuid)
|
||||
.map_err(|e| Error::SetSupportedCpusFailed(e.into()))?;
|
||||
|
||||
regs::setup_msrs(fd).map_err(Error::MSRSConfiguration)?;
|
||||
if let Some(kernel_entry_point) = kernel_entry_point {
|
||||
// Safe to unwrap because this method is called after the VM is configured
|
||||
regs::setup_regs(
|
||||
fd,
|
||||
kernel_entry_point.entry_addr.raw_value(),
|
||||
layout::BOOT_STACK_POINTER.raw_value(),
|
||||
layout::ZERO_PAGE_START.raw_value(),
|
||||
kernel_entry_point.protocol,
|
||||
)
|
||||
.map_err(Error::REGSConfiguration)?;
|
||||
regs::setup_fpu(fd).map_err(Error::FPUConfiguration)?;
|
||||
regs::setup_sregs(&vm_memory.memory(), fd, kernel_entry_point.protocol)
|
||||
.map_err(Error::SREGSConfiguration)?;
|
||||
}
|
||||
interrupts::set_lint(fd).map_err(|e| Error::LocalIntConfiguration(e.into()))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns a Vec of the valid memory addresses.
|
||||
/// These should be used to configure the GuestMemory structure for the platform.
|
||||
/// For x86_64 all addresses are valid from the start of the kernel except a
|
||||
@@ -419,236 +106,33 @@ pub fn configure_system(
|
||||
guest_mem: &GuestMemoryMmap,
|
||||
cmdline_addr: GuestAddress,
|
||||
cmdline_size: usize,
|
||||
initramfs: &Option<InitramfsConfig>,
|
||||
_num_cpus: u8,
|
||||
num_cpus: u8,
|
||||
setup_hdr: Option<setup_header>,
|
||||
rsdp_addr: Option<GuestAddress>,
|
||||
boot_prot: BootProtocol,
|
||||
sgx_epc_region: Option<SgxEpcRegion>,
|
||||
) -> super::Result<()> {
|
||||
smbios::setup_smbios(guest_mem).map_err(Error::SmbiosSetup)?;
|
||||
|
||||
// Note that this puts the mptable at the last 1k of Linux's 640k base RAM
|
||||
#[cfg(not(feature = "acpi"))]
|
||||
mptable::setup_mptable(guest_mem, _num_cpus).map_err(Error::MpTableSetup)?;
|
||||
|
||||
// Check that the RAM is not smaller than the RSDP start address
|
||||
if let Some(rsdp_addr) = rsdp_addr {
|
||||
if rsdp_addr.0 > guest_mem.last_addr().0 {
|
||||
return Err(super::Error::RSDPPastRamEnd);
|
||||
}
|
||||
}
|
||||
|
||||
match boot_prot {
|
||||
BootProtocol::PvhBoot => {
|
||||
configure_pvh(
|
||||
guest_mem,
|
||||
cmdline_addr,
|
||||
initramfs,
|
||||
rsdp_addr,
|
||||
sgx_epc_region,
|
||||
)?;
|
||||
}
|
||||
BootProtocol::LinuxBoot => {
|
||||
configure_64bit_boot(
|
||||
guest_mem,
|
||||
cmdline_addr,
|
||||
cmdline_size,
|
||||
initramfs,
|
||||
setup_hdr,
|
||||
rsdp_addr,
|
||||
sgx_epc_region,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn configure_pvh(
|
||||
guest_mem: &GuestMemoryMmap,
|
||||
cmdline_addr: GuestAddress,
|
||||
initramfs: &Option<InitramfsConfig>,
|
||||
rsdp_addr: Option<GuestAddress>,
|
||||
sgx_epc_region: Option<SgxEpcRegion>,
|
||||
) -> super::Result<()> {
|
||||
const XEN_HVM_START_MAGIC_VALUE: u32 = 0x336ec578;
|
||||
|
||||
let mut start_info: StartInfoWrapper = StartInfoWrapper(hvm_start_info::default());
|
||||
|
||||
start_info.0.magic = XEN_HVM_START_MAGIC_VALUE;
|
||||
start_info.0.version = 1; // pvh has version 1
|
||||
start_info.0.nr_modules = 0;
|
||||
start_info.0.cmdline_paddr = cmdline_addr.raw_value() as u64;
|
||||
start_info.0.memmap_paddr = layout::MEMMAP_START.raw_value();
|
||||
|
||||
if let Some(rsdp_addr) = rsdp_addr {
|
||||
start_info.0.rsdp_paddr = rsdp_addr.0;
|
||||
}
|
||||
|
||||
if let Some(initramfs_config) = initramfs {
|
||||
// The initramfs has been written to guest memory already, here we just need to
|
||||
// create the module structure that describes it.
|
||||
let ramdisk_mod: ModlistEntryWrapper = ModlistEntryWrapper(hvm_modlist_entry {
|
||||
paddr: initramfs_config.address.raw_value(),
|
||||
size: initramfs_config.size as u64,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
start_info.0.nr_modules += 1;
|
||||
start_info.0.modlist_paddr = layout::MODLIST_START.raw_value();
|
||||
|
||||
// Write the modlist struct to guest memory.
|
||||
guest_mem
|
||||
.write_obj(ramdisk_mod, layout::MODLIST_START)
|
||||
.map_err(super::Error::ModlistSetup)?;
|
||||
}
|
||||
|
||||
// Vector to hold the memory maps which needs to be written to guest memory
|
||||
// at MEMMAP_START after all of the mappings are recorded.
|
||||
let mut memmap: Vec<hvm_memmap_table_entry> = Vec::new();
|
||||
|
||||
// Create the memory map entries.
|
||||
add_memmap_entry(&mut memmap, 0, layout::EBDA_START.raw_value(), E820_RAM)?;
|
||||
|
||||
let mem_end = guest_mem.last_addr();
|
||||
|
||||
if mem_end < layout::MEM_32BIT_RESERVED_START {
|
||||
add_memmap_entry(
|
||||
&mut memmap,
|
||||
layout::HIGH_RAM_START.raw_value(),
|
||||
mem_end.unchecked_offset_from(layout::HIGH_RAM_START) + 1,
|
||||
E820_RAM,
|
||||
)?;
|
||||
} else {
|
||||
add_memmap_entry(
|
||||
&mut memmap,
|
||||
layout::HIGH_RAM_START.raw_value(),
|
||||
layout::MEM_32BIT_RESERVED_START.unchecked_offset_from(layout::HIGH_RAM_START),
|
||||
E820_RAM,
|
||||
)?;
|
||||
if mem_end > layout::RAM_64BIT_START {
|
||||
add_memmap_entry(
|
||||
&mut memmap,
|
||||
layout::RAM_64BIT_START.raw_value(),
|
||||
mem_end.unchecked_offset_from(layout::RAM_64BIT_START) + 1,
|
||||
E820_RAM,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
add_memmap_entry(
|
||||
&mut memmap,
|
||||
layout::PCI_MMCONFIG_START.0,
|
||||
layout::PCI_MMCONFIG_SIZE,
|
||||
E820_RESERVED,
|
||||
)?;
|
||||
|
||||
if let Some(sgx_epc_region) = sgx_epc_region {
|
||||
add_memmap_entry(
|
||||
&mut memmap,
|
||||
sgx_epc_region.start().raw_value(),
|
||||
sgx_epc_region.size() as u64,
|
||||
E820_RESERVED,
|
||||
)?;
|
||||
}
|
||||
|
||||
start_info.0.memmap_entries = memmap.len() as u32;
|
||||
|
||||
// Copy the vector with the memmap table to the MEMMAP_START address
|
||||
// which is already saved in the memmap_paddr field of hvm_start_info struct.
|
||||
let mut memmap_start_addr = layout::MEMMAP_START;
|
||||
|
||||
guest_mem
|
||||
.checked_offset(
|
||||
memmap_start_addr,
|
||||
mem::size_of::<hvm_memmap_table_entry>() * start_info.0.memmap_entries as usize,
|
||||
)
|
||||
.ok_or(super::Error::MemmapTablePastRamEnd)?;
|
||||
|
||||
// For every entry in the memmap vector, create a MemmapTableEntryWrapper
|
||||
// and write it to guest memory.
|
||||
for memmap_entry in memmap {
|
||||
let map_entry_wrapper: MemmapTableEntryWrapper = MemmapTableEntryWrapper(memmap_entry);
|
||||
|
||||
guest_mem
|
||||
.write_obj(map_entry_wrapper, memmap_start_addr)
|
||||
.map_err(|_| super::Error::MemmapTableSetup)?;
|
||||
memmap_start_addr =
|
||||
memmap_start_addr.unchecked_add(mem::size_of::<hvm_memmap_table_entry>() as u64);
|
||||
}
|
||||
|
||||
// The hvm_start_info struct itself must be stored at PVH_START_INFO
|
||||
// address, and %rbx will be initialized to contain PVH_INFO_START prior to
|
||||
// starting the guest, as required by the PVH ABI.
|
||||
let start_info_addr = layout::PVH_INFO_START;
|
||||
|
||||
guest_mem
|
||||
.checked_offset(start_info_addr, mem::size_of::<hvm_start_info>())
|
||||
.ok_or(super::Error::StartInfoPastRamEnd)?;
|
||||
|
||||
// Write the start_info struct to guest memory.
|
||||
guest_mem
|
||||
.write_obj(start_info, start_info_addr)
|
||||
.map_err(|_| super::Error::StartInfoSetup)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn add_memmap_entry(
|
||||
memmap: &mut Vec<hvm_memmap_table_entry>,
|
||||
addr: u64,
|
||||
size: u64,
|
||||
mem_type: u32,
|
||||
) -> Result<(), Error> {
|
||||
// Add the table entry to the vector
|
||||
memmap.push(hvm_memmap_table_entry {
|
||||
addr,
|
||||
size,
|
||||
type_: mem_type,
|
||||
reserved: 0,
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn configure_64bit_boot(
|
||||
guest_mem: &GuestMemoryMmap,
|
||||
cmdline_addr: GuestAddress,
|
||||
cmdline_size: usize,
|
||||
initramfs: &Option<InitramfsConfig>,
|
||||
setup_hdr: Option<setup_header>,
|
||||
rsdp_addr: Option<GuestAddress>,
|
||||
sgx_epc_region: Option<SgxEpcRegion>,
|
||||
) -> super::Result<()> {
|
||||
const KERNEL_BOOT_FLAG_MAGIC: u16 = 0xaa55;
|
||||
const KERNEL_HDR_MAGIC: u32 = 0x53726448;
|
||||
const KERNEL_LOADER_OTHER: u8 = 0xff;
|
||||
const KERNEL_MIN_ALIGNMENT_BYTES: u32 = 0x1000000; // Must be non-zero.
|
||||
|
||||
// Note that this puts the mptable at the last 1k of Linux's 640k base RAM
|
||||
mptable::setup_mptable(guest_mem, num_cpus).map_err(Error::MpTableSetup)?;
|
||||
|
||||
let mut params: BootParamsWrapper = BootParamsWrapper(boot_params::default());
|
||||
|
||||
if let Some(hdr) = setup_hdr {
|
||||
// We should use the header if the loader provides one (e.g. from a bzImage).
|
||||
params.0.hdr = hdr;
|
||||
params.0.hdr.cmd_line_ptr = cmdline_addr.raw_value() as u32;
|
||||
params.0.hdr.cmdline_size = cmdline_size as u32;
|
||||
} else {
|
||||
params.0.hdr.type_of_loader = KERNEL_LOADER_OTHER;
|
||||
params.0.hdr.boot_flag = KERNEL_BOOT_FLAG_MAGIC;
|
||||
params.0.hdr.header = KERNEL_HDR_MAGIC;
|
||||
params.0.hdr.cmd_line_ptr = cmdline_addr.raw_value() as u32;
|
||||
params.0.hdr.cmdline_size = cmdline_size as u32;
|
||||
params.0.hdr.kernel_alignment = KERNEL_MIN_ALIGNMENT_BYTES;
|
||||
};
|
||||
|
||||
// Common bootparams settings
|
||||
if params.0.hdr.type_of_loader == 0 {
|
||||
params.0.hdr.type_of_loader = KERNEL_LOADER_OTHER;
|
||||
}
|
||||
params.0.hdr.cmd_line_ptr = cmdline_addr.raw_value() as u32;
|
||||
params.0.hdr.cmdline_size = cmdline_size as u32;
|
||||
|
||||
if let Some(initramfs_config) = initramfs {
|
||||
params.0.hdr.ramdisk_image = initramfs_config.address.raw_value() as u32;
|
||||
params.0.hdr.ramdisk_size = initramfs_config.size as u32;
|
||||
}
|
||||
|
||||
add_e820_entry(&mut params.0, 0, layout::EBDA_START.raw_value(), E820_RAM)?;
|
||||
|
||||
let mem_end = guest_mem.last_addr();
|
||||
@@ -683,15 +167,6 @@ fn configure_64bit_boot(
|
||||
E820_RESERVED,
|
||||
)?;
|
||||
|
||||
if let Some(sgx_epc_region) = sgx_epc_region {
|
||||
add_e820_entry(
|
||||
&mut params.0,
|
||||
sgx_epc_region.start().raw_value(),
|
||||
sgx_epc_region.size() as u64,
|
||||
E820_RESERVED,
|
||||
)?;
|
||||
}
|
||||
|
||||
if let Some(rsdp_addr) = rsdp_addr {
|
||||
params.0.acpi_rsdp_addr = rsdp_addr.0;
|
||||
}
|
||||
@@ -727,162 +202,6 @@ fn add_e820_entry(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns the memory address where the initramfs could be loaded.
|
||||
pub fn initramfs_load_addr(
|
||||
guest_mem: &GuestMemoryMmap,
|
||||
initramfs_size: usize,
|
||||
) -> super::Result<u64> {
|
||||
let first_region = guest_mem
|
||||
.find_region(GuestAddress::new(0))
|
||||
.ok_or(super::Error::InitramfsAddress)?;
|
||||
// It's safe to cast to usize because the size of a region can't be greater than usize.
|
||||
let lowmem_size = first_region.len() as usize;
|
||||
|
||||
if lowmem_size < initramfs_size {
|
||||
return Err(super::Error::InitramfsAddress);
|
||||
}
|
||||
|
||||
let aligned_addr: u64 = ((lowmem_size - initramfs_size) & !(crate::pagesize() - 1)) as u64;
|
||||
Ok(aligned_addr)
|
||||
}
|
||||
|
||||
pub fn get_host_cpu_phys_bits() -> u8 {
|
||||
use std::arch::x86_64;
|
||||
unsafe {
|
||||
let leaf = x86_64::__cpuid(0x8000_0000);
|
||||
|
||||
// Detect and handle AMD SME (Secure Memory Encryption) properly.
|
||||
// Some physical address bits may become reserved when the feature is enabled.
|
||||
// See AMD64 Architecture Programmer's Manual Volume 2, Section 7.10.1
|
||||
let reduced = if leaf.eax >= 0x8000_001f
|
||||
&& leaf.ebx == 0x6874_7541 // Vendor ID: AuthenticAMD
|
||||
&& leaf.ecx == 0x444d_4163
|
||||
&& leaf.edx == 0x6974_6e65
|
||||
&& x86_64::__cpuid(0x8000_001f).eax & 0x1 != 0
|
||||
{
|
||||
(x86_64::__cpuid(0x8000_001f).ebx >> 6) & 0x3f
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
if leaf.eax >= 0x8000_0008 {
|
||||
let leaf = x86_64::__cpuid(0x8000_0008);
|
||||
((leaf.eax & 0xff) - reduced) as u8
|
||||
} else {
|
||||
36
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_cpuid_topology(
|
||||
cpuid: &mut CpuId,
|
||||
threads_per_core: u8,
|
||||
cores_per_die: u8,
|
||||
dies_per_package: u8,
|
||||
) {
|
||||
let thread_width = 8 - (threads_per_core - 1).leading_zeros();
|
||||
let core_width = (8 - (cores_per_die - 1).leading_zeros()) + thread_width;
|
||||
let die_width = (8 - (dies_per_package - 1).leading_zeros()) + core_width;
|
||||
|
||||
// CPU Topology leaf 0xb
|
||||
CpuidPatch::set_cpuid_reg(cpuid, 0xb, Some(0), CpuidReg::EAX, thread_width);
|
||||
CpuidPatch::set_cpuid_reg(
|
||||
cpuid,
|
||||
0xb,
|
||||
Some(0),
|
||||
CpuidReg::EBX,
|
||||
u32::from(threads_per_core),
|
||||
);
|
||||
CpuidPatch::set_cpuid_reg(cpuid, 0xb, Some(0), CpuidReg::ECX, 1 << 8);
|
||||
|
||||
CpuidPatch::set_cpuid_reg(cpuid, 0xb, Some(1), CpuidReg::EAX, die_width);
|
||||
CpuidPatch::set_cpuid_reg(
|
||||
cpuid,
|
||||
0xb,
|
||||
Some(1),
|
||||
CpuidReg::EBX,
|
||||
u32::from(dies_per_package * cores_per_die * threads_per_core),
|
||||
);
|
||||
CpuidPatch::set_cpuid_reg(cpuid, 0xb, Some(1), CpuidReg::ECX, 2 << 8);
|
||||
|
||||
// CPU Topology leaf 0x1f
|
||||
CpuidPatch::set_cpuid_reg(cpuid, 0x1f, Some(0), CpuidReg::EAX, thread_width);
|
||||
CpuidPatch::set_cpuid_reg(
|
||||
cpuid,
|
||||
0x1f,
|
||||
Some(0),
|
||||
CpuidReg::EBX,
|
||||
u32::from(threads_per_core),
|
||||
);
|
||||
CpuidPatch::set_cpuid_reg(cpuid, 0x1f, Some(0), CpuidReg::ECX, 1 << 8);
|
||||
|
||||
CpuidPatch::set_cpuid_reg(cpuid, 0x1f, Some(1), CpuidReg::EAX, core_width);
|
||||
CpuidPatch::set_cpuid_reg(
|
||||
cpuid,
|
||||
0x1f,
|
||||
Some(1),
|
||||
CpuidReg::EBX,
|
||||
u32::from(cores_per_die * threads_per_core),
|
||||
);
|
||||
CpuidPatch::set_cpuid_reg(cpuid, 0x1f, Some(1), CpuidReg::ECX, 2 << 8);
|
||||
|
||||
CpuidPatch::set_cpuid_reg(cpuid, 0x1f, Some(2), CpuidReg::EAX, die_width);
|
||||
CpuidPatch::set_cpuid_reg(
|
||||
cpuid,
|
||||
0x1f,
|
||||
Some(2),
|
||||
CpuidReg::EBX,
|
||||
u32::from(dies_per_package * cores_per_die * threads_per_core),
|
||||
);
|
||||
CpuidPatch::set_cpuid_reg(cpuid, 0x1f, Some(2), CpuidReg::ECX, 5 << 8);
|
||||
}
|
||||
|
||||
// The goal is to update the CPUID sub-leaves to reflect the number of EPC
|
||||
// sections exposed to the guest.
|
||||
pub fn update_cpuid_sgx(cpuid: &mut CpuId, epc_sections: Vec<SgxEpcSection>) -> Result<(), Error> {
|
||||
// Something's wrong if there's no EPC section.
|
||||
if epc_sections.is_empty() {
|
||||
return Err(Error::NoSgxEpcSection);
|
||||
}
|
||||
// We can't go further if the hypervisor does not support SGX feature.
|
||||
if !CpuidPatch::is_feature_enabled(cpuid, 0x7, 0, CpuidReg::EBX, 2) {
|
||||
return Err(Error::MissingSgxFeature);
|
||||
}
|
||||
// We can't go further if the hypervisor does not support SGX_LC feature.
|
||||
if !CpuidPatch::is_feature_enabled(cpuid, 0x7, 0, CpuidReg::ECX, 30) {
|
||||
return Err(Error::MissingSgxLaunchControlFeature);
|
||||
}
|
||||
|
||||
// Get host CPUID for leaf 0x12, subleaf 0x2. This is to retrieve EPC
|
||||
// properties such as confidentiality and integrity.
|
||||
let leaf = unsafe { std::arch::x86_64::__cpuid_count(0x12, 0x2) };
|
||||
|
||||
for (i, epc_section) in epc_sections.iter().enumerate() {
|
||||
let subleaf_idx = i + 2;
|
||||
let start = epc_section.start().raw_value();
|
||||
let size = epc_section.size() as u64;
|
||||
let eax = (start & 0xffff_f000) as u32 | 0x1;
|
||||
let ebx = (start >> 32) as u32;
|
||||
let ecx = (size & 0xffff_f000) as u32 | (leaf.ecx & 0xf);
|
||||
let edx = (size >> 32) as u32;
|
||||
// CPU Topology leaf 0x12
|
||||
CpuidPatch::set_cpuid_reg(cpuid, 0x12, Some(subleaf_idx as u32), CpuidReg::EAX, eax);
|
||||
CpuidPatch::set_cpuid_reg(cpuid, 0x12, Some(subleaf_idx as u32), CpuidReg::EBX, ebx);
|
||||
CpuidPatch::set_cpuid_reg(cpuid, 0x12, Some(subleaf_idx as u32), CpuidReg::ECX, ecx);
|
||||
CpuidPatch::set_cpuid_reg(cpuid, 0x12, Some(subleaf_idx as u32), CpuidReg::EDX, edx);
|
||||
}
|
||||
|
||||
// Add one NULL entry to terminate the dynamic list
|
||||
let subleaf_idx = epc_sections.len() + 2;
|
||||
// CPU Topology leaf 0x12
|
||||
CpuidPatch::set_cpuid_reg(cpuid, 0x12, Some(subleaf_idx as u32), CpuidReg::EAX, 0);
|
||||
CpuidPatch::set_cpuid_reg(cpuid, 0x12, Some(subleaf_idx as u32), CpuidReg::EBX, 0);
|
||||
CpuidPatch::set_cpuid_reg(cpuid, 0x12, Some(subleaf_idx as u32), CpuidReg::ECX, 0);
|
||||
CpuidPatch::set_cpuid_reg(cpuid, 0x12, Some(subleaf_idx as u32), CpuidReg::EDX, 0);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -908,17 +227,7 @@ mod tests {
|
||||
fn test_system_configuration() {
|
||||
let no_vcpus = 4;
|
||||
let gm = GuestMemoryMmap::from_ranges(&vec![(GuestAddress(0), 0x10000)]).unwrap();
|
||||
let config_err = configure_system(
|
||||
&gm,
|
||||
GuestAddress(0),
|
||||
0,
|
||||
&None,
|
||||
1,
|
||||
None,
|
||||
Some(layout::RSDP_POINTER),
|
||||
BootProtocol::LinuxBoot,
|
||||
None,
|
||||
);
|
||||
let config_err = configure_system(&gm, GuestAddress(0), 0, 1, None, None);
|
||||
assert!(config_err.is_err());
|
||||
|
||||
// Now assigning some memory that falls before the 32bit memory hole.
|
||||
@@ -930,31 +239,7 @@ mod tests {
|
||||
.map(|r| (r.0, r.1))
|
||||
.collect();
|
||||
let gm = GuestMemoryMmap::from_ranges(&ram_regions).unwrap();
|
||||
configure_system(
|
||||
&gm,
|
||||
GuestAddress(0),
|
||||
0,
|
||||
&None,
|
||||
no_vcpus,
|
||||
None,
|
||||
None,
|
||||
BootProtocol::LinuxBoot,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
configure_system(
|
||||
&gm,
|
||||
GuestAddress(0),
|
||||
0,
|
||||
&None,
|
||||
no_vcpus,
|
||||
None,
|
||||
None,
|
||||
BootProtocol::PvhBoot,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
configure_system(&gm, GuestAddress(0), 0, no_vcpus, None, None).unwrap();
|
||||
|
||||
// Now assigning some memory that is equal to the start of the 32bit memory hole.
|
||||
let mem_size = 3328 << 20;
|
||||
@@ -965,31 +250,7 @@ mod tests {
|
||||
.map(|r| (r.0, r.1))
|
||||
.collect();
|
||||
let gm = GuestMemoryMmap::from_ranges(&ram_regions).unwrap();
|
||||
configure_system(
|
||||
&gm,
|
||||
GuestAddress(0),
|
||||
0,
|
||||
&None,
|
||||
no_vcpus,
|
||||
None,
|
||||
None,
|
||||
BootProtocol::LinuxBoot,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
configure_system(
|
||||
&gm,
|
||||
GuestAddress(0),
|
||||
0,
|
||||
&None,
|
||||
no_vcpus,
|
||||
None,
|
||||
None,
|
||||
BootProtocol::PvhBoot,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
configure_system(&gm, GuestAddress(0), 0, no_vcpus, None, None).unwrap();
|
||||
|
||||
// Now assigning some memory that falls after the 32bit memory hole.
|
||||
let mem_size = 3330 << 20;
|
||||
@@ -1000,31 +261,7 @@ mod tests {
|
||||
.map(|r| (r.0, r.1))
|
||||
.collect();
|
||||
let gm = GuestMemoryMmap::from_ranges(&ram_regions).unwrap();
|
||||
configure_system(
|
||||
&gm,
|
||||
GuestAddress(0),
|
||||
0,
|
||||
&None,
|
||||
no_vcpus,
|
||||
None,
|
||||
None,
|
||||
BootProtocol::LinuxBoot,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
configure_system(
|
||||
&gm,
|
||||
GuestAddress(0),
|
||||
0,
|
||||
&None,
|
||||
no_vcpus,
|
||||
None,
|
||||
None,
|
||||
BootProtocol::PvhBoot,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
configure_system(&gm, GuestAddress(0), 0, no_vcpus, None, None).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1066,29 +303,4 @@ mod tests {
|
||||
)
|
||||
.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_memmap_entry() {
|
||||
let mut memmap: Vec<hvm_memmap_table_entry> = Vec::new();
|
||||
|
||||
let expected_memmap = vec![
|
||||
hvm_memmap_table_entry {
|
||||
addr: 0x0,
|
||||
size: 0x1000,
|
||||
type_: E820_RAM,
|
||||
..Default::default()
|
||||
},
|
||||
hvm_memmap_table_entry {
|
||||
addr: 0x10000,
|
||||
size: 0xa000,
|
||||
type_: E820_RESERVED,
|
||||
..Default::default()
|
||||
},
|
||||
];
|
||||
|
||||
add_memmap_entry(&mut memmap, 0, 0x1000, E820_RAM).unwrap();
|
||||
add_memmap_entry(&mut memmap, 0x10000, 0xa000, E820_RESERVED).unwrap();
|
||||
|
||||
assert_eq!(format!("{:?}", memmap), format!("{:?}", expected_memmap));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,34 +1,35 @@
|
||||
// Copyright © 2020, Oracle and/or its affiliates.
|
||||
//
|
||||
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
use std::sync::Arc;
|
||||
|
||||
use std::{mem, result};
|
||||
|
||||
use super::gdt::{gdt_entry, segment_from_gdt};
|
||||
use super::BootProtocol;
|
||||
use hypervisor::x86_64::{FpuState, SpecialRegisters, StandardRegisters};
|
||||
use layout::{
|
||||
BOOT_GDT_START, BOOT_IDT_START, PDE_START, PDPTE_START, PML4_START, PML5_START, PVH_INFO_START,
|
||||
};
|
||||
use super::gdt::{gdt_entry, kvm_segment_from_gdt};
|
||||
use arch_gen::x86::msr_index;
|
||||
use kvm_bindings::{kvm_fpu, kvm_msr_entry, kvm_regs, kvm_sregs, Msrs};
|
||||
use kvm_ioctls::VcpuFd;
|
||||
use layout::{BOOT_GDT_START, BOOT_IDT_START, PDE_START, PDPTE_START, PML4_START};
|
||||
use vm_memory::{Address, Bytes, GuestMemory, GuestMemoryError, GuestMemoryMmap};
|
||||
|
||||
// MTRR constants
|
||||
const MTRR_ENABLE: u64 = 0x800; // IA32_MTRR_DEF_TYPE MSR: E (MTRRs enabled) flag, bit 11
|
||||
const MTRR_MEM_TYPE_WB: u64 = 0x6;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Failed to get SREGs for this CPU.
|
||||
GetStatusRegisters(hypervisor::HypervisorCpuError),
|
||||
GetStatusRegisters(kvm_ioctls::Error),
|
||||
/// Failed to set base registers for this CPU.
|
||||
SetBaseRegisters(hypervisor::HypervisorCpuError),
|
||||
SetBaseRegisters(kvm_ioctls::Error),
|
||||
/// Failed to configure the FPU.
|
||||
SetFPURegisters(hypervisor::HypervisorCpuError),
|
||||
SetFPURegisters(kvm_ioctls::Error),
|
||||
/// Setting up MSRs failed.
|
||||
SetModelSpecificRegisters(hypervisor::HypervisorCpuError),
|
||||
SetModelSpecificRegisters(kvm_ioctls::Error),
|
||||
/// Failed to set SREGs for this CPU.
|
||||
SetStatusRegisters(hypervisor::HypervisorCpuError),
|
||||
SetStatusRegisters(kvm_ioctls::Error),
|
||||
/// Checking the GDT address failed.
|
||||
CheckGDTAddr,
|
||||
/// Writing the GDT to RAM failed.
|
||||
@@ -41,8 +42,6 @@ pub enum Error {
|
||||
WritePDEAddress(GuestMemoryError),
|
||||
/// Writing PML4 to RAM failed.
|
||||
WritePML4Address(GuestMemoryError),
|
||||
/// Writing PML5 to RAM failed.
|
||||
WritePML5Address(GuestMemoryError),
|
||||
}
|
||||
|
||||
pub type Result<T> = result::Result<T, Error>;
|
||||
@@ -52,8 +51,8 @@ pub type Result<T> = result::Result<T, Error>;
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `vcpu` - Structure for the VCPU that holds the VCPU's fd.
|
||||
pub fn setup_fpu(vcpu: &Arc<dyn hypervisor::Vcpu>) -> Result<()> {
|
||||
let fpu: FpuState = FpuState {
|
||||
pub fn setup_fpu(vcpu: &VcpuFd) -> Result<()> {
|
||||
let fpu: kvm_fpu = kvm_fpu {
|
||||
fcw: 0x37f,
|
||||
mxcsr: 0x1f80,
|
||||
..Default::default()
|
||||
@@ -67,8 +66,8 @@ pub fn setup_fpu(vcpu: &Arc<dyn hypervisor::Vcpu>) -> Result<()> {
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `vcpu` - Structure for the VCPU that holds the VCPU's fd.
|
||||
pub fn setup_msrs(vcpu: &Arc<dyn hypervisor::Vcpu>) -> Result<()> {
|
||||
vcpu.set_msrs(&hypervisor::x86_64::boot_msr_entries())
|
||||
pub fn setup_msrs(vcpu: &VcpuFd) -> Result<()> {
|
||||
vcpu.set_msrs(&create_msr_entries())
|
||||
.map_err(Error::SetModelSpecificRegisters)?;
|
||||
|
||||
Ok(())
|
||||
@@ -82,31 +81,16 @@ pub fn setup_msrs(vcpu: &Arc<dyn hypervisor::Vcpu>) -> Result<()> {
|
||||
/// * `boot_ip` - Starting instruction pointer.
|
||||
/// * `boot_sp` - Starting stack pointer.
|
||||
/// * `boot_si` - Must point to zero page address per Linux ABI.
|
||||
pub fn setup_regs(
|
||||
vcpu: &Arc<dyn hypervisor::Vcpu>,
|
||||
boot_ip: u64,
|
||||
boot_sp: u64,
|
||||
boot_si: u64,
|
||||
boot_prot: BootProtocol,
|
||||
) -> Result<()> {
|
||||
let regs: StandardRegisters = match boot_prot {
|
||||
// Configure regs as required by PVH boot protocol.
|
||||
BootProtocol::PvhBoot => StandardRegisters {
|
||||
rflags: 0x0000000000000002u64,
|
||||
rbx: PVH_INFO_START.raw_value(),
|
||||
rip: boot_ip,
|
||||
..Default::default()
|
||||
},
|
||||
// Configure regs as required by Linux 64-bit boot protocol.
|
||||
BootProtocol::LinuxBoot => StandardRegisters {
|
||||
rflags: 0x0000000000000002u64,
|
||||
rip: boot_ip,
|
||||
rsp: boot_sp,
|
||||
rbp: boot_sp,
|
||||
rsi: boot_si,
|
||||
..Default::default()
|
||||
},
|
||||
pub fn setup_regs(vcpu: &VcpuFd, boot_ip: u64, boot_sp: u64, boot_si: u64) -> Result<()> {
|
||||
let regs: kvm_regs = kvm_regs {
|
||||
rflags: 0x0000000000000002u64,
|
||||
rip: boot_ip,
|
||||
rsp: boot_sp,
|
||||
rbp: boot_sp,
|
||||
rsi: boot_si,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
vcpu.set_regs(®s).map_err(Error::SetBaseRegisters)
|
||||
}
|
||||
|
||||
@@ -116,18 +100,11 @@ pub fn setup_regs(
|
||||
///
|
||||
/// * `mem` - The memory that will be passed to the guest.
|
||||
/// * `vcpu` - Structure for the VCPU that holds the VCPU's fd.
|
||||
pub fn setup_sregs(
|
||||
mem: &GuestMemoryMmap,
|
||||
vcpu: &Arc<dyn hypervisor::Vcpu>,
|
||||
boot_prot: BootProtocol,
|
||||
) -> Result<()> {
|
||||
let mut sregs: SpecialRegisters = vcpu.get_sregs().map_err(Error::GetStatusRegisters)?;
|
||||
pub fn setup_sregs(mem: &GuestMemoryMmap, vcpu: &VcpuFd) -> Result<()> {
|
||||
let mut sregs: kvm_sregs = vcpu.get_sregs().map_err(Error::GetStatusRegisters)?;
|
||||
|
||||
configure_segments_and_sregs(mem, &mut sregs, boot_prot)?;
|
||||
|
||||
if let BootProtocol::LinuxBoot = boot_prot {
|
||||
setup_page_tables(mem, &mut sregs)?; // TODO(dgreid) - Can this be done once per system instead?
|
||||
}
|
||||
configure_segments_and_sregs(mem, &mut sregs)?;
|
||||
setup_page_tables(mem, &mut sregs)?; // TODO(dgreid) - Can this be done once per system instead?
|
||||
|
||||
vcpu.set_sregs(&sregs).map_err(Error::SetStatusRegisters)
|
||||
}
|
||||
@@ -140,7 +117,6 @@ const EFER_LME: u64 = 0x100;
|
||||
const X86_CR0_PE: u64 = 0x1;
|
||||
const X86_CR0_PG: u64 = 0x80000000;
|
||||
const X86_CR4_PAE: u64 = 0x20;
|
||||
const X86_CR4_LA57: u64 = 0x1000;
|
||||
|
||||
fn write_gdt_table(table: &[u64], guest_mem: &GuestMemoryMmap) -> Result<()> {
|
||||
let boot_gdt_addr = BOOT_GDT_START;
|
||||
@@ -160,35 +136,17 @@ fn write_idt_value(val: u64, guest_mem: &GuestMemoryMmap) -> Result<()> {
|
||||
.map_err(Error::WriteIDT)
|
||||
}
|
||||
|
||||
pub fn configure_segments_and_sregs(
|
||||
mem: &GuestMemoryMmap,
|
||||
sregs: &mut SpecialRegisters,
|
||||
boot_prot: BootProtocol,
|
||||
) -> Result<()> {
|
||||
let gdt_table: [u64; BOOT_GDT_MAX as usize] = match boot_prot {
|
||||
BootProtocol::PvhBoot => {
|
||||
// Configure GDT entries as specified by PVH boot protocol
|
||||
[
|
||||
gdt_entry(0, 0, 0), // NULL
|
||||
gdt_entry(0xc09b, 0, 0xffffffff), // CODE
|
||||
gdt_entry(0xc093, 0, 0xffffffff), // DATA
|
||||
gdt_entry(0x008b, 0, 0x67), // TSS
|
||||
]
|
||||
}
|
||||
BootProtocol::LinuxBoot => {
|
||||
// Configure GDT entries as specified by Linux 64bit boot protocol
|
||||
[
|
||||
gdt_entry(0, 0, 0), // NULL
|
||||
gdt_entry(0xa09b, 0, 0xfffff), // CODE
|
||||
gdt_entry(0xc093, 0, 0xfffff), // DATA
|
||||
gdt_entry(0x808b, 0, 0xfffff), // TSS
|
||||
]
|
||||
}
|
||||
};
|
||||
fn configure_segments_and_sregs(mem: &GuestMemoryMmap, sregs: &mut kvm_sregs) -> Result<()> {
|
||||
let gdt_table: [u64; BOOT_GDT_MAX as usize] = [
|
||||
gdt_entry(0, 0, 0), // NULL
|
||||
gdt_entry(0xa09b, 0, 0xfffff), // CODE
|
||||
gdt_entry(0xc093, 0, 0xfffff), // DATA
|
||||
gdt_entry(0x808b, 0, 0xfffff), // TSS
|
||||
];
|
||||
|
||||
let code_seg = segment_from_gdt(gdt_table[1], 1);
|
||||
let data_seg = segment_from_gdt(gdt_table[2], 2);
|
||||
let tss_seg = segment_from_gdt(gdt_table[3], 3);
|
||||
let code_seg = kvm_segment_from_gdt(gdt_table[1], 1);
|
||||
let data_seg = kvm_segment_from_gdt(gdt_table[2], 2);
|
||||
let tss_seg = kvm_segment_from_gdt(gdt_table[3], 3);
|
||||
|
||||
// Write segments
|
||||
write_gdt_table(&gdt_table[..], mem)?;
|
||||
@@ -207,33 +165,15 @@ pub fn configure_segments_and_sregs(
|
||||
sregs.ss = data_seg;
|
||||
sregs.tr = tss_seg;
|
||||
|
||||
match boot_prot {
|
||||
BootProtocol::PvhBoot => {
|
||||
sregs.cr0 = X86_CR0_PE;
|
||||
sregs.cr4 = 0;
|
||||
}
|
||||
BootProtocol::LinuxBoot => {
|
||||
/* 64-bit protected mode */
|
||||
sregs.cr0 |= X86_CR0_PE;
|
||||
sregs.efer |= EFER_LME | EFER_LMA;
|
||||
}
|
||||
}
|
||||
/* 64-bit protected mode */
|
||||
sregs.cr0 |= X86_CR0_PE;
|
||||
sregs.efer |= EFER_LME | EFER_LMA;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn setup_page_tables(mem: &GuestMemoryMmap, sregs: &mut SpecialRegisters) -> Result<()> {
|
||||
// Puts PML5 or PML4 right after zero page but aligned to 4k.
|
||||
if unsafe { std::arch::x86_64::__cpuid(7).ecx } & (1 << 16) != 0 {
|
||||
// Entry covering VA [0..256TB)
|
||||
mem.write_obj(PML4_START.raw_value() | 0x03, PML5_START)
|
||||
.map_err(Error::WritePML5Address)?;
|
||||
|
||||
sregs.cr3 = PML5_START.raw_value();
|
||||
sregs.cr4 |= X86_CR4_LA57;
|
||||
} else {
|
||||
sregs.cr3 = PML4_START.raw_value();
|
||||
}
|
||||
fn setup_page_tables(mem: &GuestMemoryMmap, sregs: &mut kvm_sregs) -> Result<()> {
|
||||
// Puts PML4 right after zero page but aligned to 4k.
|
||||
|
||||
// Entry covering VA [0..512GB)
|
||||
mem.write_obj(PDPTE_START.raw_value() | 0x03, PML4_START)
|
||||
@@ -242,7 +182,6 @@ pub fn setup_page_tables(mem: &GuestMemoryMmap, sregs: &mut SpecialRegisters) ->
|
||||
// Entry covering VA [0..1GB)
|
||||
mem.write_obj(PDE_START.raw_value() | 0x03, PDPTE_START)
|
||||
.map_err(Error::WritePDPTEAddress)?;
|
||||
|
||||
// 512 2MB entries together covering VA [0..1GB). Note we are assuming
|
||||
// CPU supports 2MB pages (/proc/cpuinfo has 'pse'). All modern CPUs do.
|
||||
for i in 0..512 {
|
||||
@@ -250,17 +189,83 @@ pub fn setup_page_tables(mem: &GuestMemoryMmap, sregs: &mut SpecialRegisters) ->
|
||||
.map_err(Error::WritePDEAddress)?;
|
||||
}
|
||||
|
||||
sregs.cr3 = PML4_START.raw_value();
|
||||
sregs.cr4 |= X86_CR4_PAE;
|
||||
sregs.cr0 |= X86_CR0_PG;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_msr_entries() -> Msrs {
|
||||
let mut entries = Vec::<kvm_msr_entry>::new();
|
||||
|
||||
entries.push(kvm_msr_entry {
|
||||
index: msr_index::MSR_IA32_SYSENTER_CS,
|
||||
data: 0x0,
|
||||
..Default::default()
|
||||
});
|
||||
entries.push(kvm_msr_entry {
|
||||
index: msr_index::MSR_IA32_SYSENTER_ESP,
|
||||
data: 0x0,
|
||||
..Default::default()
|
||||
});
|
||||
entries.push(kvm_msr_entry {
|
||||
index: msr_index::MSR_IA32_SYSENTER_EIP,
|
||||
data: 0x0,
|
||||
..Default::default()
|
||||
});
|
||||
// x86_64 specific msrs, we only run on x86_64 not x86.
|
||||
entries.push(kvm_msr_entry {
|
||||
index: msr_index::MSR_STAR,
|
||||
data: 0x0,
|
||||
..Default::default()
|
||||
});
|
||||
entries.push(kvm_msr_entry {
|
||||
index: msr_index::MSR_CSTAR,
|
||||
data: 0x0,
|
||||
..Default::default()
|
||||
});
|
||||
entries.push(kvm_msr_entry {
|
||||
index: msr_index::MSR_KERNEL_GS_BASE,
|
||||
data: 0x0,
|
||||
..Default::default()
|
||||
});
|
||||
entries.push(kvm_msr_entry {
|
||||
index: msr_index::MSR_SYSCALL_MASK,
|
||||
data: 0x0,
|
||||
..Default::default()
|
||||
});
|
||||
entries.push(kvm_msr_entry {
|
||||
index: msr_index::MSR_LSTAR,
|
||||
data: 0x0,
|
||||
..Default::default()
|
||||
});
|
||||
// end of x86_64 specific code
|
||||
entries.push(kvm_msr_entry {
|
||||
index: msr_index::MSR_IA32_TSC,
|
||||
data: 0x0,
|
||||
..Default::default()
|
||||
});
|
||||
entries.push(kvm_msr_entry {
|
||||
index: msr_index::MSR_IA32_MISC_ENABLE,
|
||||
data: msr_index::MSR_IA32_MISC_ENABLE_FAST_STRING as u64,
|
||||
..Default::default()
|
||||
});
|
||||
entries.push(kvm_msr_entry {
|
||||
index: msr_index::MSR_MTRRdefType,
|
||||
data: MTRR_ENABLE | MTRR_MEM_TYPE_WB,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
Msrs::from_entries(&entries)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
extern crate kvm_ioctls;
|
||||
extern crate vm_memory;
|
||||
|
||||
use super::*;
|
||||
use kvm_ioctls::Kvm;
|
||||
use vm_memory::{GuestAddress, GuestMemoryMmap};
|
||||
|
||||
fn create_guest_mem() -> GuestMemoryMmap {
|
||||
@@ -273,9 +278,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn segments_and_sregs() {
|
||||
let mut sregs: SpecialRegisters = Default::default();
|
||||
let mut sregs: kvm_sregs = Default::default();
|
||||
let gm = create_guest_mem();
|
||||
configure_segments_and_sregs(&gm, &mut sregs, BootProtocol::LinuxBoot).unwrap();
|
||||
configure_segments_and_sregs(&gm, &mut sregs).unwrap();
|
||||
|
||||
assert_eq!(0x0, read_u64(&gm, BOOT_GDT_START));
|
||||
assert_eq!(
|
||||
@@ -293,59 +298,26 @@ mod tests {
|
||||
assert_eq!(0x0, read_u64(&gm, BOOT_IDT_START));
|
||||
|
||||
assert_eq!(0, sregs.cs.base);
|
||||
assert_eq!(0xffffffff, sregs.ds.limit);
|
||||
assert_eq!(0xfffff, sregs.ds.limit);
|
||||
assert_eq!(0x10, sregs.es.selector);
|
||||
assert_eq!(1, sregs.fs.present);
|
||||
assert_eq!(1, sregs.gs.g);
|
||||
assert_eq!(0, sregs.ss.avl);
|
||||
assert_eq!(0, sregs.tr.base);
|
||||
assert_eq!(0xffffffff, sregs.tr.limit);
|
||||
assert_eq!(0xfffff, sregs.tr.limit);
|
||||
assert_eq!(0, sregs.tr.avl);
|
||||
assert_eq!(X86_CR0_PE, sregs.cr0);
|
||||
assert_eq!(EFER_LME | EFER_LMA, sregs.efer);
|
||||
|
||||
configure_segments_and_sregs(&gm, &mut sregs, BootProtocol::PvhBoot).unwrap();
|
||||
assert_eq!(0x0, read_u64(&gm, BOOT_GDT_START));
|
||||
assert_eq!(
|
||||
0xcf9b000000ffff,
|
||||
read_u64(&gm, BOOT_GDT_START.unchecked_add(8))
|
||||
);
|
||||
assert_eq!(
|
||||
0xcf93000000ffff,
|
||||
read_u64(&gm, BOOT_GDT_START.unchecked_add(16))
|
||||
);
|
||||
assert_eq!(
|
||||
0x8b0000000067,
|
||||
read_u64(&gm, BOOT_GDT_START.unchecked_add(24))
|
||||
);
|
||||
assert_eq!(0x0, read_u64(&gm, BOOT_IDT_START));
|
||||
|
||||
assert_eq!(0, sregs.cs.base);
|
||||
assert_eq!(0xffffffff, sregs.ds.limit);
|
||||
assert_eq!(0x10, sregs.es.selector);
|
||||
assert_eq!(1, sregs.fs.present);
|
||||
assert_eq!(1, sregs.gs.g);
|
||||
assert_eq!(0, sregs.ss.avl);
|
||||
assert_eq!(0, sregs.tr.base);
|
||||
assert_eq!(0, sregs.tr.g);
|
||||
assert_eq!(0x67, sregs.tr.limit);
|
||||
assert_eq!(0xb, sregs.tr.type_);
|
||||
assert_eq!(0, sregs.tr.avl);
|
||||
assert_eq!(X86_CR0_PE, sregs.cr0);
|
||||
assert_eq!(0, sregs.cr4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn page_tables() {
|
||||
let mut sregs: SpecialRegisters = Default::default();
|
||||
let mut sregs: kvm_sregs = Default::default();
|
||||
let gm = create_guest_mem();
|
||||
setup_page_tables(&gm, &mut sregs).unwrap();
|
||||
|
||||
if unsafe { std::arch::x86_64::__cpuid(7).ecx } & (1 << 16) != 0 {
|
||||
assert_eq!(0xa003, read_u64(&gm, PML5_START));
|
||||
}
|
||||
assert_eq!(0xb003, read_u64(&gm, PML4_START));
|
||||
assert_eq!(0xc003, read_u64(&gm, PDPTE_START));
|
||||
assert_eq!(0xa003, read_u64(&gm, PML4_START));
|
||||
assert_eq!(0xb003, read_u64(&gm, PDPTE_START));
|
||||
for i in 0..512 {
|
||||
assert_eq!(
|
||||
(i << 21) + 0x83u64,
|
||||
@@ -353,12 +325,99 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
if unsafe { std::arch::x86_64::__cpuid(7).ecx } & (1 << 16) != 0 {
|
||||
assert_eq!(PML5_START.raw_value(), sregs.cr3);
|
||||
} else {
|
||||
assert_eq!(PML4_START.raw_value(), sregs.cr3);
|
||||
}
|
||||
assert_eq!(PML4_START.raw_value(), sregs.cr3);
|
||||
assert_eq!(X86_CR4_PAE, sregs.cr4);
|
||||
assert_eq!(X86_CR0_PG, sregs.cr0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_setup_fpu() {
|
||||
let kvm = Kvm::new().unwrap();
|
||||
let vm = kvm.create_vm().unwrap();
|
||||
let vcpu = vm.create_vcpu(0).unwrap();
|
||||
setup_fpu(&vcpu).unwrap();
|
||||
|
||||
let expected_fpu: kvm_fpu = kvm_fpu {
|
||||
fcw: 0x37f,
|
||||
mxcsr: 0x1f80,
|
||||
..Default::default()
|
||||
};
|
||||
let actual_fpu: kvm_fpu = vcpu.get_fpu().unwrap();
|
||||
// TODO: auto-generate kvm related structures with PartialEq on.
|
||||
assert_eq!(expected_fpu.fcw, actual_fpu.fcw);
|
||||
// Setting the mxcsr register from kvm_fpu inside setup_fpu does not influence anything.
|
||||
// See 'kvm_arch_vcpu_ioctl_set_fpu' from arch/x86/kvm/x86.c.
|
||||
// The mxcsr will stay 0 and the assert below fails. Decide whether or not we should
|
||||
// remove it at all.
|
||||
// assert!(expected_fpu.mxcsr == actual_fpu.mxcsr);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_setup_msrs() {
|
||||
let kvm = Kvm::new().unwrap();
|
||||
let vm = kvm.create_vm().unwrap();
|
||||
let vcpu = vm.create_vcpu(0).unwrap();
|
||||
setup_msrs(&vcpu).unwrap();
|
||||
|
||||
// This test will check against the last MSR entry configured (the tenth one).
|
||||
// See create_msr_entries for details.
|
||||
let mut msrs = Msrs::from_entries(&[kvm_msr_entry {
|
||||
index: msr_index::MSR_IA32_MISC_ENABLE,
|
||||
..Default::default()
|
||||
}]);
|
||||
|
||||
// get_msrs returns the number of msrs that it succeed in reading. We only want to read 1
|
||||
// in this test case scenario.
|
||||
let read_msrs = vcpu.get_msrs(&mut msrs).unwrap();
|
||||
assert_eq!(read_msrs, 1);
|
||||
|
||||
// Official entries that were setup when we did setup_msrs. We need to assert that the
|
||||
// tenth one (i.e the one with index msr_index::MSR_IA32_MISC_ENABLE has the data we
|
||||
// expect.
|
||||
let entry_vec = create_msr_entries();
|
||||
assert_eq!(entry_vec.as_slice()[9], msrs.as_slice()[0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_setup_regs() {
|
||||
let kvm = Kvm::new().unwrap();
|
||||
let vm = kvm.create_vm().unwrap();
|
||||
let vcpu = vm.create_vcpu(0).unwrap();
|
||||
|
||||
let expected_regs: kvm_regs = kvm_regs {
|
||||
rflags: 0x0000000000000002u64,
|
||||
rip: 1,
|
||||
rsp: 2,
|
||||
rbp: 2,
|
||||
rsi: 3,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
setup_regs(
|
||||
&vcpu,
|
||||
expected_regs.rip,
|
||||
expected_regs.rsp,
|
||||
expected_regs.rsi,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let actual_regs: kvm_regs = vcpu.get_regs().unwrap();
|
||||
assert_eq!(actual_regs, expected_regs);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_setup_sregs() {
|
||||
let kvm = Kvm::new().unwrap();
|
||||
let vm = kvm.create_vm().unwrap();
|
||||
let vcpu = vm.create_vcpu(0).unwrap();
|
||||
|
||||
let mut expected_sregs: kvm_sregs = vcpu.get_sregs().unwrap();
|
||||
let gm = create_guest_mem();
|
||||
configure_segments_and_sregs(&gm, &mut expected_sregs).unwrap();
|
||||
setup_page_tables(&gm, &mut expected_sregs).unwrap();
|
||||
|
||||
setup_sregs(&gm, &vcpu).unwrap();
|
||||
let actual_sregs: kvm_sregs = vcpu.get_sregs().unwrap();
|
||||
assert_eq!(expected_sregs, actual_sregs);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,263 +0,0 @@
|
||||
// Copyright © 2020 Intel Corporation
|
||||
//
|
||||
// Copyright 2019 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||
|
||||
use layout::SMBIOS_START;
|
||||
use std::fmt::{self, Display};
|
||||
use std::mem;
|
||||
use std::result;
|
||||
use std::slice;
|
||||
use vm_memory::ByteValued;
|
||||
use vm_memory::{Address, Bytes, GuestAddress, GuestMemoryMmap};
|
||||
|
||||
#[allow(unused_variables)]
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// There was too little guest memory to store the entire SMBIOS table.
|
||||
NotEnoughMemory,
|
||||
/// The SMBIOS table has too little address space to be stored.
|
||||
AddressOverflow,
|
||||
/// Failure while zeroing out the memory for the SMBIOS table.
|
||||
Clear,
|
||||
/// Failure to write SMBIOS entrypoint structure
|
||||
WriteSmbiosEp,
|
||||
/// Failure to write additional data to memory
|
||||
WriteData,
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {}
|
||||
|
||||
impl Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
use self::Error::*;
|
||||
|
||||
let description = match self {
|
||||
NotEnoughMemory => "There was too little guest memory to store the SMBIOS table",
|
||||
AddressOverflow => "The SMBIOS table has too little address space to be stored",
|
||||
Clear => "Failure while zeroing out the memory for the SMBIOS table",
|
||||
WriteSmbiosEp => "Failure to write SMBIOS entrypoint structure",
|
||||
WriteData => "Failure to write additional data to memory",
|
||||
};
|
||||
|
||||
write!(f, "SMBIOS error: {}", description)
|
||||
}
|
||||
}
|
||||
|
||||
pub type Result<T> = result::Result<T, Error>;
|
||||
|
||||
// Constants sourced from SMBIOS Spec 3.2.0.
|
||||
const SM3_MAGIC_IDENT: &[u8; 5usize] = b"_SM3_";
|
||||
const BIOS_INFORMATION: u8 = 0;
|
||||
const SYSTEM_INFORMATION: u8 = 1;
|
||||
const END_OF_TABLE: u8 = 127;
|
||||
const PCI_SUPPORTED: u64 = 1 << 7;
|
||||
const IS_VIRTUAL_MACHINE: u8 = 1 << 4;
|
||||
|
||||
fn compute_checksum<T: Copy>(v: &T) -> u8 {
|
||||
// Safe because we are only reading the bytes within the size of the `T` reference `v`.
|
||||
let v_slice = unsafe { slice::from_raw_parts(v as *const T as *const u8, mem::size_of::<T>()) };
|
||||
let mut checksum: u8 = 0;
|
||||
for i in v_slice.iter() {
|
||||
checksum = checksum.wrapping_add(*i);
|
||||
}
|
||||
(!checksum).wrapping_add(1)
|
||||
}
|
||||
|
||||
#[repr(packed)]
|
||||
#[derive(Default, Copy)]
|
||||
pub struct Smbios30Entrypoint {
|
||||
pub signature: [u8; 5usize],
|
||||
pub checksum: u8,
|
||||
pub length: u8,
|
||||
pub majorver: u8,
|
||||
pub minorver: u8,
|
||||
pub docrev: u8,
|
||||
pub revision: u8,
|
||||
pub reserved: u8,
|
||||
pub max_size: u32,
|
||||
pub physptr: u64,
|
||||
}
|
||||
unsafe impl ByteValued for Smbios30Entrypoint {}
|
||||
|
||||
impl Clone for Smbios30Entrypoint {
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(packed)]
|
||||
#[derive(Default, Copy)]
|
||||
pub struct SmbiosBiosInfo {
|
||||
pub typ: u8,
|
||||
pub length: u8,
|
||||
pub handle: u16,
|
||||
pub vendor: u8,
|
||||
pub version: u8,
|
||||
pub start_addr: u16,
|
||||
pub release_date: u8,
|
||||
pub rom_size: u8,
|
||||
pub characteristics: u64,
|
||||
pub characteristics_ext1: u8,
|
||||
pub characteristics_ext2: u8,
|
||||
}
|
||||
|
||||
impl Clone for SmbiosBiosInfo {
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl ByteValued for SmbiosBiosInfo {}
|
||||
|
||||
#[repr(packed)]
|
||||
#[derive(Default, Copy)]
|
||||
pub struct SmbiosSysInfo {
|
||||
pub typ: u8,
|
||||
pub length: u8,
|
||||
pub handle: u16,
|
||||
pub manufacturer: u8,
|
||||
pub product_name: u8,
|
||||
pub version: u8,
|
||||
pub serial_number: u8,
|
||||
pub uuid: [u8; 16usize],
|
||||
pub wake_up_type: u8,
|
||||
pub sku: u8,
|
||||
pub family: u8,
|
||||
}
|
||||
|
||||
impl Clone for SmbiosSysInfo {
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl ByteValued for SmbiosSysInfo {}
|
||||
|
||||
fn write_and_incr<T: ByteValued>(
|
||||
mem: &GuestMemoryMmap,
|
||||
val: T,
|
||||
mut curptr: GuestAddress,
|
||||
) -> Result<GuestAddress> {
|
||||
mem.write_obj(val, curptr).map_err(|_| Error::WriteData)?;
|
||||
curptr = curptr
|
||||
.checked_add(mem::size_of::<T>() as u64)
|
||||
.ok_or(Error::NotEnoughMemory)?;
|
||||
Ok(curptr)
|
||||
}
|
||||
|
||||
fn write_string(
|
||||
mem: &GuestMemoryMmap,
|
||||
val: &str,
|
||||
mut curptr: GuestAddress,
|
||||
) -> Result<GuestAddress> {
|
||||
for c in val.as_bytes().iter() {
|
||||
curptr = write_and_incr(mem, *c, curptr)?;
|
||||
}
|
||||
curptr = write_and_incr(mem, 0 as u8, curptr)?;
|
||||
Ok(curptr)
|
||||
}
|
||||
|
||||
pub fn setup_smbios(mem: &GuestMemoryMmap) -> Result<()> {
|
||||
let physptr = GuestAddress(SMBIOS_START)
|
||||
.checked_add(mem::size_of::<Smbios30Entrypoint>() as u64)
|
||||
.ok_or(Error::NotEnoughMemory)?;
|
||||
let mut curptr = physptr;
|
||||
let mut handle = 0;
|
||||
|
||||
{
|
||||
handle += 1;
|
||||
let mut smbios_biosinfo = SmbiosBiosInfo::default();
|
||||
smbios_biosinfo.typ = BIOS_INFORMATION;
|
||||
smbios_biosinfo.length = mem::size_of::<SmbiosBiosInfo>() as u8;
|
||||
smbios_biosinfo.handle = handle;
|
||||
smbios_biosinfo.vendor = 1; // First string written in this section
|
||||
smbios_biosinfo.version = 2; // Second string written in this section
|
||||
smbios_biosinfo.characteristics = PCI_SUPPORTED;
|
||||
smbios_biosinfo.characteristics_ext2 = IS_VIRTUAL_MACHINE;
|
||||
curptr = write_and_incr(mem, smbios_biosinfo, curptr)?;
|
||||
curptr = write_string(mem, "cloud-hypervisor", curptr)?;
|
||||
curptr = write_string(mem, "0", curptr)?;
|
||||
curptr = write_and_incr(mem, 0 as u8, curptr)?;
|
||||
}
|
||||
|
||||
{
|
||||
handle += 1;
|
||||
let mut smbios_sysinfo = SmbiosSysInfo::default();
|
||||
smbios_sysinfo.typ = SYSTEM_INFORMATION;
|
||||
smbios_sysinfo.length = mem::size_of::<SmbiosSysInfo>() as u8;
|
||||
smbios_sysinfo.handle = handle;
|
||||
smbios_sysinfo.manufacturer = 1; // First string written in this section
|
||||
smbios_sysinfo.product_name = 2; // Second string written in this section
|
||||
curptr = write_and_incr(mem, smbios_sysinfo, curptr)?;
|
||||
curptr = write_string(mem, "Cloud Hypervisor", curptr)?;
|
||||
curptr = write_string(mem, "cloud-hypervisor", curptr)?;
|
||||
curptr = write_and_incr(mem, 0 as u8, curptr)?;
|
||||
}
|
||||
|
||||
{
|
||||
handle += 1;
|
||||
let mut smbios_sysinfo = SmbiosSysInfo::default();
|
||||
smbios_sysinfo.typ = END_OF_TABLE;
|
||||
smbios_sysinfo.length = mem::size_of::<SmbiosSysInfo>() as u8;
|
||||
smbios_sysinfo.handle = handle;
|
||||
curptr = write_and_incr(mem, smbios_sysinfo, curptr)?;
|
||||
curptr = write_and_incr(mem, 0 as u8, curptr)?;
|
||||
}
|
||||
|
||||
{
|
||||
let mut smbios_ep = Smbios30Entrypoint::default();
|
||||
smbios_ep.signature = *SM3_MAGIC_IDENT;
|
||||
smbios_ep.length = mem::size_of::<Smbios30Entrypoint>() as u8;
|
||||
// SMBIOS rev 3.2.0
|
||||
smbios_ep.majorver = 0x03;
|
||||
smbios_ep.minorver = 0x02;
|
||||
smbios_ep.docrev = 0x00;
|
||||
smbios_ep.revision = 0x01; // SMBIOS 3.0
|
||||
smbios_ep.max_size = curptr.unchecked_offset_from(physptr) as u32;
|
||||
smbios_ep.physptr = physptr.0;
|
||||
smbios_ep.checksum = compute_checksum(&smbios_ep);
|
||||
mem.write_obj(smbios_ep, GuestAddress(SMBIOS_START))
|
||||
.map_err(|_| Error::WriteSmbiosEp)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn struct_size() {
|
||||
assert_eq!(
|
||||
mem::size_of::<Smbios30Entrypoint>(),
|
||||
0x18usize,
|
||||
concat!("Size of: ", stringify!(Smbios30Entrypoint))
|
||||
);
|
||||
assert_eq!(
|
||||
mem::size_of::<SmbiosBiosInfo>(),
|
||||
0x14usize,
|
||||
concat!("Size of: ", stringify!(SmbiosBiosInfo))
|
||||
);
|
||||
assert_eq!(
|
||||
mem::size_of::<SmbiosSysInfo>(),
|
||||
0x1busize,
|
||||
concat!("Size of: ", stringify!(SmbiosSysInfo))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn entrypoint_checksum() {
|
||||
let mem = GuestMemoryMmap::from_ranges(&[(GuestAddress(SMBIOS_START), 4096)]).unwrap();
|
||||
|
||||
setup_smbios(&mem).unwrap();
|
||||
|
||||
let smbios_ep: Smbios30Entrypoint = mem.read_obj(GuestAddress(SMBIOS_START)).unwrap();
|
||||
|
||||
assert_eq!(compute_checksum(&smbios_ep), 0);
|
||||
}
|
||||
}
|
||||
@@ -20,3 +20,6 @@
|
||||
#[allow(non_upper_case_globals)]
|
||||
#[allow(clippy::unreadable_literal, clippy::redundant_static_lifetimes)]
|
||||
pub mod mpspec;
|
||||
#[allow(non_upper_case_globals)]
|
||||
#[allow(clippy::unreadable_literal, clippy::redundant_static_lifetimes)]
|
||||
pub mod msr_index;
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
[package]
|
||||
name = "block_util"
|
||||
version = "0.1.0"
|
||||
authors = ["The Cloud Hypervisor Authors"]
|
||||
edition = "2018"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
io_uring = []
|
||||
|
||||
[dependencies]
|
||||
io-uring = { git = "https://github.com/tokio-rs/io-uring.git", branch = "0.4" }
|
||||
libc = "0.2.74"
|
||||
log = "0.4.11"
|
||||
serde = ">=1.0.27"
|
||||
serde_derive = ">=1.0.27"
|
||||
serde_json = ">=1.0.9"
|
||||
virtio-bindings = { version = "0.1", features = ["virtio-v5_0_0"]}
|
||||
vm-memory = { version = "0.2.1", features = ["backend-mmap", "backend-atomic"] }
|
||||
vm-virtio = { path = "../vm-virtio" }
|
||||
vmm-sys-util = ">=0.3.1"
|
||||
@@ -1,545 +0,0 @@
|
||||
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
//
|
||||
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
//
|
||||
// Copyright © 2020 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
|
||||
#[cfg(feature = "io_uring")]
|
||||
use io_uring::{opcode, IoUring, Probe};
|
||||
use serde::ser::{Serialize, SerializeStruct, Serializer};
|
||||
use std::cmp;
|
||||
use std::io::{self, Read, Seek, SeekFrom, Write};
|
||||
use std::os::linux::fs::MetadataExt;
|
||||
#[cfg(feature = "io_uring")]
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
use std::path::PathBuf;
|
||||
use std::result;
|
||||
use virtio_bindings::bindings::virtio_blk::*;
|
||||
use vm_memory::{ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryError, GuestMemoryMmap};
|
||||
use vm_virtio::DescriptorChain;
|
||||
#[cfg(feature = "io_uring")]
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
const SECTOR_SHIFT: u8 = 9;
|
||||
pub const SECTOR_SIZE: u64 = (0x01 as u64) << SECTOR_SHIFT;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Guest gave us bad memory addresses.
|
||||
GuestMemory(GuestMemoryError),
|
||||
/// Guest gave us offsets that would have overflowed a usize.
|
||||
CheckedOffset(GuestAddress, usize),
|
||||
/// Guest gave us a write only descriptor that protocol says to read from.
|
||||
UnexpectedWriteOnlyDescriptor,
|
||||
/// Guest gave us a read only descriptor that protocol says to write to.
|
||||
UnexpectedReadOnlyDescriptor,
|
||||
/// Guest gave us too few descriptors in a descriptor chain.
|
||||
DescriptorChainTooShort,
|
||||
/// Guest gave us a descriptor that was too short to use.
|
||||
DescriptorLengthTooSmall,
|
||||
/// Getting a block's metadata fails for any reason.
|
||||
GetFileMetadata,
|
||||
/// The requested operation would cause a seek beyond disk end.
|
||||
InvalidOffset,
|
||||
}
|
||||
|
||||
fn build_device_id(disk_path: &PathBuf) -> result::Result<String, Error> {
|
||||
let blk_metadata = match disk_path.metadata() {
|
||||
Err(_) => return Err(Error::GetFileMetadata),
|
||||
Ok(m) => m,
|
||||
};
|
||||
// This is how kvmtool does it.
|
||||
let device_id = format!(
|
||||
"{}{}{}",
|
||||
blk_metadata.st_dev(),
|
||||
blk_metadata.st_rdev(),
|
||||
blk_metadata.st_ino()
|
||||
);
|
||||
Ok(device_id)
|
||||
}
|
||||
|
||||
pub fn build_disk_image_id(disk_path: &PathBuf) -> Vec<u8> {
|
||||
let mut default_disk_image_id = vec![0; VIRTIO_BLK_ID_BYTES as usize];
|
||||
match build_device_id(disk_path) {
|
||||
Err(_) => {
|
||||
warn!("Could not generate device id. We'll use a default.");
|
||||
}
|
||||
Ok(m) => {
|
||||
// The kernel only knows to read a maximum of VIRTIO_BLK_ID_BYTES.
|
||||
// This will also zero out any leftover bytes.
|
||||
let disk_id = m.as_bytes();
|
||||
let bytes_to_copy = cmp::min(disk_id.len(), VIRTIO_BLK_ID_BYTES as usize);
|
||||
default_disk_image_id[..bytes_to_copy].clone_from_slice(&disk_id[..bytes_to_copy])
|
||||
}
|
||||
}
|
||||
default_disk_image_id
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ExecuteError {
|
||||
BadRequest(Error),
|
||||
Flush(io::Error),
|
||||
Read(GuestMemoryError),
|
||||
Seek(io::Error),
|
||||
Write(GuestMemoryError),
|
||||
Unsupported(u32),
|
||||
SubmitIoUring(io::Error),
|
||||
GetHostAddress(GuestMemoryError),
|
||||
}
|
||||
|
||||
impl ExecuteError {
|
||||
pub fn status(&self) -> u32 {
|
||||
match *self {
|
||||
ExecuteError::BadRequest(_) => VIRTIO_BLK_S_IOERR,
|
||||
ExecuteError::Flush(_) => VIRTIO_BLK_S_IOERR,
|
||||
ExecuteError::Read(_) => VIRTIO_BLK_S_IOERR,
|
||||
ExecuteError::Seek(_) => VIRTIO_BLK_S_IOERR,
|
||||
ExecuteError::Write(_) => VIRTIO_BLK_S_IOERR,
|
||||
ExecuteError::Unsupported(_) => VIRTIO_BLK_S_UNSUPP,
|
||||
ExecuteError::SubmitIoUring(_) => VIRTIO_BLK_S_IOERR,
|
||||
ExecuteError::GetHostAddress(_) => VIRTIO_BLK_S_IOERR,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum RequestType {
|
||||
In,
|
||||
Out,
|
||||
Flush,
|
||||
GetDeviceID,
|
||||
Unsupported(u32),
|
||||
}
|
||||
|
||||
pub fn request_type(
|
||||
mem: &GuestMemoryMmap,
|
||||
desc_addr: GuestAddress,
|
||||
) -> result::Result<RequestType, Error> {
|
||||
let type_ = mem.read_obj(desc_addr).map_err(Error::GuestMemory)?;
|
||||
match type_ {
|
||||
VIRTIO_BLK_T_IN => Ok(RequestType::In),
|
||||
VIRTIO_BLK_T_OUT => Ok(RequestType::Out),
|
||||
VIRTIO_BLK_T_FLUSH => Ok(RequestType::Flush),
|
||||
VIRTIO_BLK_T_GET_ID => Ok(RequestType::GetDeviceID),
|
||||
t => Ok(RequestType::Unsupported(t)),
|
||||
}
|
||||
}
|
||||
|
||||
fn sector(mem: &GuestMemoryMmap, desc_addr: GuestAddress) -> result::Result<u64, Error> {
|
||||
const SECTOR_OFFSET: usize = 8;
|
||||
let addr = match mem.checked_offset(desc_addr, SECTOR_OFFSET) {
|
||||
Some(v) => v,
|
||||
None => return Err(Error::CheckedOffset(desc_addr, SECTOR_OFFSET)),
|
||||
};
|
||||
|
||||
mem.read_obj(addr).map_err(Error::GuestMemory)
|
||||
}
|
||||
|
||||
pub struct Request {
|
||||
pub request_type: RequestType,
|
||||
pub sector: u64,
|
||||
pub data_addr: GuestAddress,
|
||||
pub data_len: u32,
|
||||
pub status_addr: GuestAddress,
|
||||
pub writeback: bool,
|
||||
}
|
||||
|
||||
impl Request {
|
||||
pub fn parse(
|
||||
avail_desc: &DescriptorChain,
|
||||
mem: &GuestMemoryMmap,
|
||||
) -> result::Result<Request, Error> {
|
||||
// The head contains the request type which MUST be readable.
|
||||
if avail_desc.is_write_only() {
|
||||
return Err(Error::UnexpectedWriteOnlyDescriptor);
|
||||
}
|
||||
|
||||
let mut req = Request {
|
||||
request_type: request_type(&mem, avail_desc.addr)?,
|
||||
sector: sector(&mem, avail_desc.addr)?,
|
||||
data_addr: GuestAddress(0),
|
||||
data_len: 0,
|
||||
status_addr: GuestAddress(0),
|
||||
writeback: true,
|
||||
};
|
||||
|
||||
let data_desc;
|
||||
let status_desc;
|
||||
let desc = avail_desc
|
||||
.next_descriptor()
|
||||
.ok_or(Error::DescriptorChainTooShort)?;
|
||||
|
||||
if !desc.has_next() {
|
||||
status_desc = desc;
|
||||
// Only flush requests are allowed to skip the data descriptor.
|
||||
if req.request_type != RequestType::Flush {
|
||||
return Err(Error::DescriptorChainTooShort);
|
||||
}
|
||||
} else {
|
||||
data_desc = desc;
|
||||
status_desc = data_desc
|
||||
.next_descriptor()
|
||||
.ok_or(Error::DescriptorChainTooShort)?;
|
||||
|
||||
if data_desc.is_write_only() && req.request_type == RequestType::Out {
|
||||
return Err(Error::UnexpectedWriteOnlyDescriptor);
|
||||
}
|
||||
if !data_desc.is_write_only() && req.request_type == RequestType::In {
|
||||
return Err(Error::UnexpectedReadOnlyDescriptor);
|
||||
}
|
||||
if !data_desc.is_write_only() && req.request_type == RequestType::GetDeviceID {
|
||||
return Err(Error::UnexpectedReadOnlyDescriptor);
|
||||
}
|
||||
|
||||
req.data_addr = data_desc.addr;
|
||||
req.data_len = data_desc.len;
|
||||
}
|
||||
|
||||
// The status MUST always be writable.
|
||||
if !status_desc.is_write_only() {
|
||||
return Err(Error::UnexpectedReadOnlyDescriptor);
|
||||
}
|
||||
|
||||
if status_desc.len < 1 {
|
||||
return Err(Error::DescriptorLengthTooSmall);
|
||||
}
|
||||
|
||||
req.status_addr = status_desc.addr;
|
||||
|
||||
Ok(req)
|
||||
}
|
||||
|
||||
#[allow(clippy::ptr_arg)]
|
||||
pub fn execute<T: Seek + Read + Write>(
|
||||
&self,
|
||||
disk: &mut T,
|
||||
disk_nsectors: u64,
|
||||
mem: &GuestMemoryMmap,
|
||||
disk_id: &Vec<u8>,
|
||||
) -> result::Result<u32, ExecuteError> {
|
||||
let mut top: u64 = u64::from(self.data_len) / SECTOR_SIZE;
|
||||
if u64::from(self.data_len) % SECTOR_SIZE != 0 {
|
||||
top += 1;
|
||||
}
|
||||
top = top
|
||||
.checked_add(self.sector)
|
||||
.ok_or(ExecuteError::BadRequest(Error::InvalidOffset))?;
|
||||
if top > disk_nsectors {
|
||||
return Err(ExecuteError::BadRequest(Error::InvalidOffset));
|
||||
}
|
||||
|
||||
disk.seek(SeekFrom::Start(self.sector << SECTOR_SHIFT))
|
||||
.map_err(ExecuteError::Seek)?;
|
||||
|
||||
match self.request_type {
|
||||
RequestType::In => {
|
||||
mem.read_exact_from(self.data_addr, disk, self.data_len as usize)
|
||||
.map_err(ExecuteError::Read)?;
|
||||
return Ok(self.data_len);
|
||||
}
|
||||
RequestType::Out => {
|
||||
mem.write_all_to(self.data_addr, disk, self.data_len as usize)
|
||||
.map_err(ExecuteError::Write)?;
|
||||
if !self.writeback {
|
||||
disk.flush().map_err(ExecuteError::Flush)?;
|
||||
}
|
||||
}
|
||||
RequestType::Flush => disk.flush().map_err(ExecuteError::Flush)?,
|
||||
RequestType::GetDeviceID => {
|
||||
if (self.data_len as usize) < disk_id.len() {
|
||||
return Err(ExecuteError::BadRequest(Error::InvalidOffset));
|
||||
}
|
||||
mem.write_slice(&disk_id.as_slice(), self.data_addr)
|
||||
.map_err(ExecuteError::Write)?;
|
||||
}
|
||||
RequestType::Unsupported(t) => return Err(ExecuteError::Unsupported(t)),
|
||||
};
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
#[cfg(feature = "io_uring")]
|
||||
pub fn execute_io_uring(
|
||||
&self,
|
||||
mem: &GuestMemoryMmap,
|
||||
io_uring: &mut IoUring,
|
||||
disk_nsectors: u64,
|
||||
disk_image_fd: RawFd,
|
||||
disk_id: &[u8],
|
||||
user_data: u64,
|
||||
) -> result::Result<bool, ExecuteError> {
|
||||
let data_len = self.data_len;
|
||||
let sector = self.sector;
|
||||
let data_addr = self.data_addr;
|
||||
let request_type = self.request_type;
|
||||
|
||||
let mut top: u64 = u64::from(data_len) / SECTOR_SIZE;
|
||||
if u64::from(data_len) % SECTOR_SIZE != 0 {
|
||||
top += 1;
|
||||
}
|
||||
top = top
|
||||
.checked_add(sector)
|
||||
.ok_or(ExecuteError::BadRequest(Error::InvalidOffset))?;
|
||||
if top > disk_nsectors {
|
||||
return Err(ExecuteError::BadRequest(Error::InvalidOffset));
|
||||
}
|
||||
|
||||
let buf = mem
|
||||
.get_slice(data_addr, data_len as usize)
|
||||
.map_err(ExecuteError::GetHostAddress)?
|
||||
.as_ptr();
|
||||
let offset = (sector as i64) << SECTOR_SHIFT;
|
||||
|
||||
let (submitter, sq, _) = io_uring.split();
|
||||
let mut avail_sq = sq.available();
|
||||
|
||||
// Queue operations expected to be submitted.
|
||||
match request_type {
|
||||
RequestType::In => {
|
||||
// Safe because we know the file descriptor is valid and we
|
||||
// relied on vm-memory to provide the buffer address.
|
||||
let _ = unsafe {
|
||||
avail_sq.push(
|
||||
opcode::Read::new(opcode::types::Fd(disk_image_fd), buf, data_len)
|
||||
.offset(offset)
|
||||
.build()
|
||||
.user_data(user_data),
|
||||
)
|
||||
};
|
||||
}
|
||||
RequestType::Out => {
|
||||
// Safe because we know the file descriptor is valid and we
|
||||
// relied on vm-memory to provide the buffer address.
|
||||
let _ = unsafe {
|
||||
avail_sq.push(
|
||||
opcode::Write::new(opcode::types::Fd(disk_image_fd), buf, data_len)
|
||||
.offset(offset)
|
||||
.build()
|
||||
.user_data(user_data),
|
||||
)
|
||||
};
|
||||
}
|
||||
RequestType::Flush => {
|
||||
// Safe because we know the file descriptor is valid.
|
||||
let _ = unsafe {
|
||||
avail_sq.push(
|
||||
opcode::Fsync::new(opcode::types::Fd(disk_image_fd))
|
||||
.build()
|
||||
.user_data(user_data),
|
||||
)
|
||||
};
|
||||
}
|
||||
RequestType::GetDeviceID => {
|
||||
if (data_len as usize) < disk_id.len() {
|
||||
return Err(ExecuteError::BadRequest(Error::InvalidOffset));
|
||||
}
|
||||
mem.write_slice(disk_id, data_addr)
|
||||
.map_err(ExecuteError::Write)?;
|
||||
return Ok(false);
|
||||
}
|
||||
RequestType::Unsupported(t) => return Err(ExecuteError::Unsupported(t)),
|
||||
}
|
||||
|
||||
// Update the submission queue and submit new operations to the
|
||||
// io_uring instance.
|
||||
avail_sq.sync();
|
||||
submitter.submit().map_err(ExecuteError::SubmitIoUring)?;
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
pub fn set_writeback(&mut self, writeback: bool) {
|
||||
self.writeback = writeback
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default, Deserialize)]
|
||||
#[repr(C, packed)]
|
||||
pub struct VirtioBlockConfig {
|
||||
pub capacity: u64,
|
||||
pub size_max: u32,
|
||||
pub seg_max: u32,
|
||||
pub geometry: VirtioBlockGeometry,
|
||||
pub blk_size: u32,
|
||||
pub physical_block_exp: u8,
|
||||
pub alignment_offset: u8,
|
||||
pub min_io_size: u16,
|
||||
pub opt_io_size: u32,
|
||||
pub writeback: u8,
|
||||
pub unused: u8,
|
||||
pub num_queues: u16,
|
||||
pub max_discard_sectors: u32,
|
||||
pub max_discard_seg: u32,
|
||||
pub discard_sector_alignment: u32,
|
||||
pub max_write_zeroes_sectors: u32,
|
||||
pub max_write_zeroes_seg: u32,
|
||||
pub write_zeroes_may_unmap: u8,
|
||||
pub unused1: [u8; 3],
|
||||
}
|
||||
|
||||
// We must explicitly implement Serialize since the structure is packed and
|
||||
// it's unsafe to borrow from a packed structure. And by default, if we derive
|
||||
// Serialize from serde, it will borrow the values from the structure.
|
||||
// That's why this implementation copies each field separately before it
|
||||
// serializes the entire structure field by field.
|
||||
impl Serialize for VirtioBlockConfig {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let capacity = self.capacity;
|
||||
let size_max = self.size_max;
|
||||
let seg_max = self.seg_max;
|
||||
let geometry = self.geometry;
|
||||
let blk_size = self.blk_size;
|
||||
let physical_block_exp = self.physical_block_exp;
|
||||
let alignment_offset = self.alignment_offset;
|
||||
let min_io_size = self.min_io_size;
|
||||
let opt_io_size = self.opt_io_size;
|
||||
let writeback = self.writeback;
|
||||
let unused = self.unused;
|
||||
let num_queues = self.num_queues;
|
||||
let max_discard_sectors = self.max_discard_sectors;
|
||||
let max_discard_seg = self.max_discard_seg;
|
||||
let discard_sector_alignment = self.discard_sector_alignment;
|
||||
let max_write_zeroes_sectors = self.max_write_zeroes_sectors;
|
||||
let max_write_zeroes_seg = self.max_write_zeroes_seg;
|
||||
let write_zeroes_may_unmap = self.write_zeroes_may_unmap;
|
||||
let unused1 = self.unused1;
|
||||
|
||||
let mut virtio_block_config = serializer.serialize_struct("VirtioBlockConfig", 60)?;
|
||||
virtio_block_config.serialize_field("capacity", &capacity)?;
|
||||
virtio_block_config.serialize_field("size_max", &size_max)?;
|
||||
virtio_block_config.serialize_field("seg_max", &seg_max)?;
|
||||
virtio_block_config.serialize_field("geometry", &geometry)?;
|
||||
virtio_block_config.serialize_field("blk_size", &blk_size)?;
|
||||
virtio_block_config.serialize_field("physical_block_exp", &physical_block_exp)?;
|
||||
virtio_block_config.serialize_field("alignment_offset", &alignment_offset)?;
|
||||
virtio_block_config.serialize_field("min_io_size", &min_io_size)?;
|
||||
virtio_block_config.serialize_field("opt_io_size", &opt_io_size)?;
|
||||
virtio_block_config.serialize_field("writeback", &writeback)?;
|
||||
virtio_block_config.serialize_field("unused", &unused)?;
|
||||
virtio_block_config.serialize_field("num_queues", &num_queues)?;
|
||||
virtio_block_config.serialize_field("max_discard_sectors", &max_discard_sectors)?;
|
||||
virtio_block_config.serialize_field("max_discard_seg", &max_discard_seg)?;
|
||||
virtio_block_config
|
||||
.serialize_field("discard_sector_alignment", &discard_sector_alignment)?;
|
||||
virtio_block_config
|
||||
.serialize_field("max_write_zeroes_sectors", &max_write_zeroes_sectors)?;
|
||||
virtio_block_config.serialize_field("max_write_zeroes_seg", &max_write_zeroes_seg)?;
|
||||
virtio_block_config.serialize_field("write_zeroes_may_unmap", &write_zeroes_may_unmap)?;
|
||||
virtio_block_config.serialize_field("unused1", &unused1)?;
|
||||
virtio_block_config.end()
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl ByteValued for VirtioBlockConfig {}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default, Deserialize)]
|
||||
#[repr(C, packed)]
|
||||
pub struct VirtioBlockGeometry {
|
||||
pub cylinders: u16,
|
||||
pub heads: u8,
|
||||
pub sectors: u8,
|
||||
}
|
||||
|
||||
// We must explicitly implement Serialize since the structure is packed and
|
||||
// it's unsafe to borrow from a packed structure. And by default, if we derive
|
||||
// Serialize from serde, it will borrow the values from the structure.
|
||||
// That's why this implementation copies each field separately before it
|
||||
// serializes the entire structure field by field.
|
||||
impl Serialize for VirtioBlockGeometry {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let cylinders = self.cylinders;
|
||||
let heads = self.heads;
|
||||
let sectors = self.sectors;
|
||||
|
||||
let mut virtio_block_geometry = serializer.serialize_struct("VirtioBlockGeometry", 4)?;
|
||||
virtio_block_geometry.serialize_field("cylinders", &cylinders)?;
|
||||
virtio_block_geometry.serialize_field("heads", &heads)?;
|
||||
virtio_block_geometry.serialize_field("sectors", §ors)?;
|
||||
virtio_block_geometry.end()
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl ByteValued for VirtioBlockGeometry {}
|
||||
|
||||
/// Check if io_uring for block device can be used on the current system, as
|
||||
/// it correctly supports the expected io_uring features.
|
||||
#[cfg(feature = "io_uring")]
|
||||
pub fn block_io_uring_is_supported() -> bool {
|
||||
let error_msg = "io_uring not supported:";
|
||||
|
||||
// Check we can create an io_uring instance, which effectively verifies
|
||||
// that io_uring_setup() syscall is supported.
|
||||
let io_uring = match IoUring::new(1) {
|
||||
Ok(io_uring) => io_uring,
|
||||
Err(e) => {
|
||||
info!("{} failed to create io_uring instance: {}", error_msg, e);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
let submitter = io_uring.submitter();
|
||||
|
||||
let event_fd = match EventFd::new(libc::EFD_NONBLOCK) {
|
||||
Ok(fd) => fd,
|
||||
Err(e) => {
|
||||
info!("{} failed to create eventfd: {}", error_msg, e);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// Check we can register an eventfd as this is going to be needed while
|
||||
// using io_uring with the virtio block device. This also validates that
|
||||
// io_uring_register() syscall is supported.
|
||||
match submitter.register_eventfd(event_fd.as_raw_fd()) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
info!("{} failed to register eventfd: {}", error_msg, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
let mut probe = Probe::new();
|
||||
|
||||
// Check we can register a probe to validate supported operations.
|
||||
match submitter.register_probe(&mut probe) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
info!("{} failed to register a probe: {}", error_msg, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check IORING_OP_FSYNC is supported
|
||||
if !probe.is_supported(opcode::Fsync::CODE) {
|
||||
info!("{} IORING_OP_FSYNC operation not supported", error_msg);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check IORING_OP_READ is supported
|
||||
if !probe.is_supported(opcode::Read::CODE) {
|
||||
info!("{} IORING_OP_READ operation not supported", error_msg);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check IORING_OP_WRITE is supported
|
||||
if !probe.is_supported(opcode::Write::CODE) {
|
||||
info!("{} IORING_OP_WRITE operation not supported", error_msg);
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
22
build.rs
22
build.rs
@@ -1,22 +0,0 @@
|
||||
// Copyright © 2020 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
use std::process::Command;
|
||||
|
||||
fn main() {
|
||||
let git_out = Command::new("git")
|
||||
.args(&["describe", "--dirty"])
|
||||
.output()
|
||||
.expect("Expect to get git describe output");
|
||||
|
||||
// This println!() has a special behavior, as it will set the environment
|
||||
// variable BUILT_VERSION, so that it can be reused from the binary.
|
||||
// Particularly, this is used from src/main.rs to display the exact
|
||||
// version.
|
||||
println!(
|
||||
"cargo:rustc-env=BUILT_VERSION={}",
|
||||
String::from_utf8(git_out.stdout).unwrap()
|
||||
);
|
||||
}
|
||||
@@ -4,26 +4,19 @@ version = "0.1.0"
|
||||
authors = ["The Chromium OS Authors"]
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
bitflags = ">=1.2.1"
|
||||
bitflags = "1.2.1"
|
||||
byteorder = "1.3.4"
|
||||
epoll = ">=4.0.1"
|
||||
libc = "0.2.74"
|
||||
log = "0.4.11"
|
||||
serde = {version = ">=1.0.27", features = ["rc"] }
|
||||
serde_derive = ">=1.0.27"
|
||||
serde_json = ">=1.0.9"
|
||||
epoll = "4.1.0"
|
||||
libc = "0.2.66"
|
||||
log = "0.4.8"
|
||||
vm-device = { path = "../vm-device" }
|
||||
acpi_tables = { path = "../acpi_tables", optional = true }
|
||||
vm-memory = "0.2.1"
|
||||
vm-migration = { path = "../vm-migration" }
|
||||
vmm-sys-util = ">=0.3.1"
|
||||
vm-memory = { git = "https://github.com/rust-vmm/vm-memory" }
|
||||
vmm-sys-util = "0.4.0"
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.1.0"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
acpi = ["acpi_tables"]
|
||||
acpi = []
|
||||
cmos = []
|
||||
fwdebug = []
|
||||
|
||||
@@ -3,9 +3,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
use acpi_tables::{aml, aml::Aml};
|
||||
use std::sync::Arc;
|
||||
use std::time::Instant;
|
||||
use vm_device::interrupt::InterruptSourceGroup;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
use BusDevice;
|
||||
@@ -96,93 +94,3 @@ impl BusDevice for AcpiGEDDevice {
|
||||
|
||||
fn write(&mut self, _base: u64, _offset: u64, _data: &[u8]) {}
|
||||
}
|
||||
|
||||
#[cfg(feature = "acpi")]
|
||||
impl Aml for AcpiGEDDevice {
|
||||
fn to_aml_bytes(&self) -> Vec<u8> {
|
||||
aml::Device::new(
|
||||
"_SB_.GED_".into(),
|
||||
vec![
|
||||
&aml::Name::new("_HID".into(), &"ACPI0013"),
|
||||
&aml::Name::new("_UID".into(), &aml::ZERO),
|
||||
&aml::Name::new(
|
||||
"_CRS".into(),
|
||||
&aml::ResourceTemplate::new(vec![&aml::Interrupt::new(
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
self.ged_irq,
|
||||
)]),
|
||||
),
|
||||
&aml::OpRegion::new("GDST".into(), aml::OpRegionSpace::SystemIO, 0xb000, 0x1),
|
||||
&aml::Field::new(
|
||||
"GDST".into(),
|
||||
aml::FieldAccessType::Byte,
|
||||
aml::FieldUpdateRule::WriteAsZeroes,
|
||||
vec![aml::FieldEntry::Named(*b"GDAT", 8)],
|
||||
),
|
||||
&aml::Method::new(
|
||||
"_EVT".into(),
|
||||
1,
|
||||
true,
|
||||
vec![
|
||||
&aml::Store::new(&aml::Local(0), &aml::Path::new("GDAT")),
|
||||
&aml::And::new(&aml::Local(1), &aml::Local(0), &aml::ONE),
|
||||
&aml::If::new(
|
||||
&aml::Equal::new(&aml::Local(1), &aml::ONE),
|
||||
vec![&aml::MethodCall::new("\\_SB_.CPUS.CSCN".into(), vec![])],
|
||||
),
|
||||
&aml::And::new(&aml::Local(1), &aml::Local(0), &2usize),
|
||||
&aml::If::new(
|
||||
&aml::Equal::new(&aml::Local(1), &2usize),
|
||||
vec![&aml::MethodCall::new("\\_SB_.MHPC.MSCN".into(), vec![])],
|
||||
),
|
||||
&aml::And::new(&aml::Local(1), &aml::Local(0), &4usize),
|
||||
&aml::If::new(
|
||||
&aml::Equal::new(&aml::Local(1), &4usize),
|
||||
vec![&aml::MethodCall::new("\\_SB_.PCI0.PCNT".into(), vec![])],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
.to_aml_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AcpiPMTimerDevice {
|
||||
start: Instant,
|
||||
}
|
||||
|
||||
impl AcpiPMTimerDevice {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
start: Instant::now(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for AcpiPMTimerDevice {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl BusDevice for AcpiPMTimerDevice {
|
||||
fn read(&mut self, _base: u64, _offset: u64, data: &mut [u8]) {
|
||||
let now = Instant::now();
|
||||
let since = now.duration_since(self.start);
|
||||
let nanos = since.as_nanos();
|
||||
|
||||
const PM_TIMER_FREQUENCY_HZ: u128 = 3_579_545;
|
||||
const NANOS_PER_SECOND: u128 = 1_000_000_000;
|
||||
|
||||
let counter = (nanos * PM_TIMER_FREQUENCY_HZ) / NANOS_PER_SECOND;
|
||||
let counter: u32 = (counter & 0xffff_ffff) as u32;
|
||||
|
||||
data.copy_from_slice(&counter.to_le_bytes());
|
||||
}
|
||||
|
||||
fn write(&mut self, _base: u64, _offset: u64, _data: &[u8]) {}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
use std::cmp::{Ord, Ordering, PartialEq, PartialOrd};
|
||||
use std::collections::btree_map::BTreeMap;
|
||||
use std::sync::{Arc, Mutex, RwLock, Weak};
|
||||
use std::sync::{Arc, Mutex, RwLock};
|
||||
use std::{convert, error, fmt, io, result};
|
||||
|
||||
/// Trait for devices that respond to reads or writes in an arbitrary address space.
|
||||
@@ -95,7 +95,7 @@ impl PartialOrd for BusRange {
|
||||
/// only restriction is that no two devices can overlap in this address space.
|
||||
#[derive(Default)]
|
||||
pub struct Bus {
|
||||
devices: RwLock<BTreeMap<BusRange, Weak<Mutex<dyn BusDevice>>>>,
|
||||
devices: RwLock<BTreeMap<BusRange, Arc<Mutex<dyn BusDevice>>>>,
|
||||
}
|
||||
|
||||
impl Bus {
|
||||
@@ -112,7 +112,7 @@ impl Bus {
|
||||
.range(..=BusRange { base: addr, len: 1 })
|
||||
.rev()
|
||||
.next()?;
|
||||
Some((*range, dev.upgrade().unwrap().clone()))
|
||||
Some((*range, dev.clone()))
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
@@ -147,7 +147,7 @@ impl Bus {
|
||||
.devices
|
||||
.write()
|
||||
.unwrap()
|
||||
.insert(BusRange { base, len }, Arc::downgrade(&device))
|
||||
.insert(BusRange { base, len }, device)
|
||||
.is_some()
|
||||
{
|
||||
return Err(Error::Overlap);
|
||||
@@ -171,24 +171,6 @@ impl Bus {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Removes all entries referencing the given device.
|
||||
pub fn remove_by_device(&self, device: &Arc<Mutex<dyn BusDevice>>) -> Result<()> {
|
||||
let mut device_list = self.devices.write().unwrap();
|
||||
let mut remove_key_list = Vec::new();
|
||||
|
||||
for (key, value) in device_list.iter() {
|
||||
if Arc::ptr_eq(&value.upgrade().unwrap(), device) {
|
||||
remove_key_list.push(*key);
|
||||
}
|
||||
}
|
||||
|
||||
for key in remove_key_list.iter() {
|
||||
device_list.remove(key);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Updates the address range for an existing device.
|
||||
pub fn update_range(
|
||||
&self,
|
||||
@@ -330,8 +312,9 @@ mod tests {
|
||||
|
||||
let bus = Bus::new();
|
||||
let mut data = [1, 2, 3, 4];
|
||||
let device = Arc::new(Mutex::new(DummyDevice));
|
||||
assert!(bus.insert(device.clone(), 0x10, 0x10).is_ok());
|
||||
assert!(bus
|
||||
.insert(Arc::new(Mutex::new(DummyDevice)), 0x10, 0x10)
|
||||
.is_ok());
|
||||
assert!(bus.write(0x10, &mut data));
|
||||
assert!(bus.read(0x10, &mut data));
|
||||
assert_eq!(data, [1, 2, 3, 4]);
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
// Copyright 2020, ARM Limited.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||
|
||||
use super::interrupt_controller::{Error, InterruptController};
|
||||
use std::result;
|
||||
use std::sync::Arc;
|
||||
use vm_device::interrupt::{
|
||||
InterruptIndex, InterruptManager, InterruptSourceGroup, MsiIrqGroupConfig,
|
||||
};
|
||||
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
|
||||
|
||||
type Result<T> = result::Result<T, Error>;
|
||||
|
||||
// Reserve 32 IRQs (GSI 32 ~ 64) for legacy device.
|
||||
// GsiAllocator should allocate beyond this: from 64 on
|
||||
pub const IRQ_LEGACY_COUNT: usize = 32;
|
||||
pub const IRQ_SPI_OFFSET: usize = 32;
|
||||
|
||||
// This Gic struct implements InterruptController to provide interrupt delivery service.
|
||||
// The Gic source files in arch/ folder maintain the Aarch64 specific Gic device.
|
||||
// The 2 Gic instances could be merged together.
|
||||
// Leave this refactoring to future. Two options may be considered:
|
||||
// 1. Move Gic*.rs from arch/ folder here.
|
||||
// 2. Move this file and ioapic.rs to arch/, as they are architecture specific.
|
||||
pub struct Gic {
|
||||
interrupt_source_group: Arc<Box<dyn InterruptSourceGroup>>,
|
||||
}
|
||||
|
||||
impl Gic {
|
||||
pub fn new(
|
||||
_vcpu_count: u8,
|
||||
interrupt_manager: Arc<dyn InterruptManager<GroupConfig = MsiIrqGroupConfig>>,
|
||||
) -> Result<Gic> {
|
||||
let interrupt_source_group = interrupt_manager
|
||||
.create_group(MsiIrqGroupConfig {
|
||||
base: IRQ_SPI_OFFSET as InterruptIndex,
|
||||
count: IRQ_LEGACY_COUNT as InterruptIndex,
|
||||
})
|
||||
.map_err(Error::CreateInterruptSourceGroup)?;
|
||||
|
||||
Ok(Gic {
|
||||
interrupt_source_group,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl InterruptController for Gic {
|
||||
fn enable(&self) -> Result<()> {
|
||||
&self
|
||||
.interrupt_source_group
|
||||
.enable()
|
||||
.map_err(Error::EnableInterrupt)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// This should be called anytime an interrupt needs to be injected into the
|
||||
// running guest.
|
||||
fn service_irq(&mut self, irq: usize) -> Result<()> {
|
||||
self.interrupt_source_group
|
||||
.trigger(irq as InterruptIndex)
|
||||
.map_err(Error::TriggerInterrupt)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
const GIC_SNAPSHOT_ID: &str = "gic";
|
||||
impl Snapshottable for Gic {
|
||||
fn id(&self) -> String {
|
||||
GIC_SNAPSHOT_ID.to_string()
|
||||
}
|
||||
|
||||
fn snapshot(&self) -> std::result::Result<Snapshot, MigratableError> {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
fn restore(&mut self, _snapshot: Snapshot) -> std::result::Result<(), MigratableError> {
|
||||
unimplemented!();
|
||||
}
|
||||
}
|
||||
|
||||
impl Pausable for Gic {}
|
||||
impl Transportable for Gic {}
|
||||
impl Migratable for Gic {}
|
||||
@@ -1,61 +0,0 @@
|
||||
// Copyright 2020, ARM Limited.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||
|
||||
use std::io;
|
||||
use std::result;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Invalid destination mode.
|
||||
InvalidDestinationMode,
|
||||
/// Invalid trigger mode.
|
||||
InvalidTriggerMode,
|
||||
/// Invalid delivery mode.
|
||||
InvalidDeliveryMode,
|
||||
/// Failed creating the interrupt source group.
|
||||
CreateInterruptSourceGroup(io::Error),
|
||||
/// Failed triggering the interrupt.
|
||||
TriggerInterrupt(io::Error),
|
||||
/// Failed masking the interrupt.
|
||||
MaskInterrupt(io::Error),
|
||||
/// Failed unmasking the interrupt.
|
||||
UnmaskInterrupt(io::Error),
|
||||
/// Failed updating the interrupt.
|
||||
UpdateInterrupt(io::Error),
|
||||
/// Failed enabling the interrupt.
|
||||
EnableInterrupt(io::Error),
|
||||
}
|
||||
|
||||
type Result<T> = result::Result<T, Error>;
|
||||
|
||||
pub struct MsiMessage {
|
||||
// Message Address Register
|
||||
// 31-20: Base address. Fixed value (0x0FEE)
|
||||
// 19-12: Destination ID
|
||||
// 11-4: Reserved
|
||||
// 3: Redirection Hint indication
|
||||
// 2: Destination Mode
|
||||
// 1-0: Reserved
|
||||
pub addr: u32,
|
||||
// Message Data Register
|
||||
// 32-16: Reserved
|
||||
// 15: Trigger Mode. 0 = Edge, 1 = Level
|
||||
// 14: Level. 0 = Deassert, 1 = Assert
|
||||
// 13-11: Reserved
|
||||
// 10-8: Delivery Mode
|
||||
// 7-0: Vector
|
||||
pub data: u32,
|
||||
}
|
||||
|
||||
// Introduce trait InterruptController to uniform the interrupt
|
||||
// service provided for devices.
|
||||
// Device manager uses this trait without caring whether it is a
|
||||
// IOAPIC (X86) or GIC (Arm).
|
||||
pub trait InterruptController: Send {
|
||||
fn service_irq(&mut self, irq: usize) -> Result<()>;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
fn enable(&self) -> Result<()>;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
fn end_of_interrupt(&mut self, vec: u8);
|
||||
}
|
||||
@@ -9,10 +9,9 @@
|
||||
// Implementation of an intel 82093AA Input/Output Advanced Programmable Interrupt Controller
|
||||
// See https://pdos.csail.mit.edu/6.828/2016/readings/ia32/ioapic.pdf for a specification.
|
||||
|
||||
use super::interrupt_controller::{Error, InterruptController};
|
||||
use crate::BusDevice;
|
||||
use anyhow::anyhow;
|
||||
use byteorder::{ByteOrder, LittleEndian};
|
||||
use std::io;
|
||||
use std::result;
|
||||
use std::sync::Arc;
|
||||
use vm_device::interrupt::{
|
||||
@@ -20,14 +19,28 @@ use vm_device::interrupt::{
|
||||
MsiIrqGroupConfig, MsiIrqSourceConfig,
|
||||
};
|
||||
use vm_memory::GuestAddress;
|
||||
use vm_migration::{
|
||||
Migratable, MigratableError, Pausable, Snapshot, SnapshotDataSection, Snapshottable,
|
||||
Transportable,
|
||||
};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(remote = "GuestAddress")]
|
||||
pub struct GuestAddressDef(pub u64);
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Invalid destination mode.
|
||||
InvalidDestinationMode,
|
||||
/// Invalid trigger mode.
|
||||
InvalidTriggerMode,
|
||||
/// Invalid delivery mode.
|
||||
InvalidDeliveryMode,
|
||||
/// Failed creating the interrupt source group.
|
||||
CreateInterruptSourceGroup(io::Error),
|
||||
/// Failed triggering the interrupt.
|
||||
TriggerInterrupt(io::Error),
|
||||
/// Failed masking the interrupt.
|
||||
MaskInterrupt(io::Error),
|
||||
/// Failed unmasking the interrupt.
|
||||
UnmaskInterrupt(io::Error),
|
||||
/// Failed updating the interrupt.
|
||||
UpdateInterrupt(io::Error),
|
||||
/// Failed enabling the interrupt.
|
||||
EnableInterrupt(io::Error),
|
||||
}
|
||||
|
||||
type Result<T> = result::Result<T, Error>;
|
||||
|
||||
@@ -86,6 +99,25 @@ fn set_remote_irr(entry: &mut RedirectionTableEntry, val: u8) {
|
||||
*entry |= u64::from(val & 0x1) << 14;
|
||||
}
|
||||
|
||||
pub struct MsiMessage {
|
||||
// Message Address Register
|
||||
// 31-20: Base address. Fixed value (0x0FEE)
|
||||
// 19-12: Destination ID
|
||||
// 11-4: Reserved
|
||||
// 3: Redirection Hint indication
|
||||
// 2: Destination Mode
|
||||
// 1-0: Reserved
|
||||
pub addr: u32,
|
||||
// Message Data Register
|
||||
// 32-16: Reserved
|
||||
// 15: Trigger Mode. 0 = Edge, 1 = Level
|
||||
// 14: Level. 0 = Deassert, 1 = Assert
|
||||
// 13-11: Reserved
|
||||
// 10-8: Delivery Mode
|
||||
// 7-0: Vector
|
||||
pub data: u32,
|
||||
}
|
||||
|
||||
pub const NUM_IOAPIC_PINS: usize = 24;
|
||||
const IOAPIC_VERSION_ID: u32 = 0x0017_0011;
|
||||
|
||||
@@ -134,25 +166,13 @@ fn decode_irq_from_selector(selector: u8) -> (usize, bool) {
|
||||
}
|
||||
|
||||
pub struct Ioapic {
|
||||
id: String,
|
||||
id_reg: u32,
|
||||
id: u32,
|
||||
reg_sel: u32,
|
||||
reg_entries: [RedirectionTableEntry; NUM_IOAPIC_PINS],
|
||||
used_entries: [bool; NUM_IOAPIC_PINS],
|
||||
apic_address: GuestAddress,
|
||||
interrupt_source_group: Arc<Box<dyn InterruptSourceGroup>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct IoapicState {
|
||||
id_reg: u32,
|
||||
reg_sel: u32,
|
||||
reg_entries: [RedirectionTableEntry; NUM_IOAPIC_PINS],
|
||||
used_entries: [bool; NUM_IOAPIC_PINS],
|
||||
#[serde(with = "GuestAddressDef")]
|
||||
apic_address: GuestAddress,
|
||||
}
|
||||
|
||||
impl BusDevice for Ioapic {
|
||||
fn read(&mut self, _base: u64, offset: u64, data: &mut [u8]) {
|
||||
assert!(data.len() == 4);
|
||||
@@ -190,7 +210,6 @@ impl BusDevice for Ioapic {
|
||||
|
||||
impl Ioapic {
|
||||
pub fn new(
|
||||
id: String,
|
||||
apic_address: GuestAddress,
|
||||
interrupt_manager: Arc<dyn InterruptManager<GroupConfig = MsiIrqGroupConfig>>,
|
||||
) -> Result<Ioapic> {
|
||||
@@ -206,87 +225,43 @@ impl Ioapic {
|
||||
.map_err(Error::EnableInterrupt)?;
|
||||
|
||||
Ok(Ioapic {
|
||||
id,
|
||||
id_reg: 0,
|
||||
id: 0,
|
||||
reg_sel: 0,
|
||||
reg_entries: [0; NUM_IOAPIC_PINS],
|
||||
used_entries: [false; NUM_IOAPIC_PINS],
|
||||
apic_address,
|
||||
interrupt_source_group,
|
||||
})
|
||||
}
|
||||
|
||||
fn ioapic_write(&mut self, val: u32) {
|
||||
debug!("IOAPIC_W reg 0x{:x}, val 0x{:x}", self.reg_sel, val);
|
||||
|
||||
match self.reg_sel as u8 {
|
||||
IOAPIC_REG_ID => self.id_reg = (val >> 24) & 0xf,
|
||||
IOWIN_OFF..=REG_MAX_OFFSET => {
|
||||
let (index, is_high_bits) = decode_irq_from_selector(self.reg_sel as u8);
|
||||
if is_high_bits {
|
||||
self.reg_entries[index] &= 0xffff_ffff;
|
||||
self.reg_entries[index] |= u64::from(val) << 32;
|
||||
} else {
|
||||
// Ensure not to override read-only bits:
|
||||
// - Delivery Status (bit 12)
|
||||
// - Remote IRR (bit 14)
|
||||
self.reg_entries[index] &= 0xffff_ffff_0000_5000;
|
||||
self.reg_entries[index] |= u64::from(val) & 0xffff_afff;
|
||||
}
|
||||
// The entry must be updated through the interrupt source
|
||||
// group.
|
||||
if let Err(e) = self.update_entry(index) {
|
||||
error!("Failed updating IOAPIC entry: {:?}", e);
|
||||
}
|
||||
// Store the information this IRQ is now being used.
|
||||
self.used_entries[index] = true;
|
||||
}
|
||||
_ => error!("IOAPIC: invalid write to register offset"),
|
||||
}
|
||||
}
|
||||
|
||||
fn ioapic_read(&self) -> u32 {
|
||||
debug!("IOAPIC_R reg 0x{:x}", self.reg_sel);
|
||||
|
||||
match self.reg_sel as u8 {
|
||||
IOAPIC_REG_VERSION => IOAPIC_VERSION_ID,
|
||||
IOAPIC_REG_ID | IOAPIC_REG_ARBITRATION_ID => (self.id_reg & 0xf) << 24,
|
||||
IOWIN_OFF..=REG_MAX_OFFSET => {
|
||||
let (index, is_high_bits) = decode_irq_from_selector(self.reg_sel as u8);
|
||||
if is_high_bits {
|
||||
(self.reg_entries[index] >> 32) as u32
|
||||
} else {
|
||||
(self.reg_entries[index] & 0xffff_ffff) as u32
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
error!("IOAPIC: invalid read from register offset");
|
||||
0
|
||||
// The ioapic must be informed about EOIs in order to deassert interrupts
|
||||
// already sent.
|
||||
pub fn end_of_interrupt(&mut self, vec: u8) {
|
||||
for i in 0..NUM_IOAPIC_PINS {
|
||||
let entry = &mut self.reg_entries[i];
|
||||
// Clear Remote IRR bit
|
||||
if vector(*entry) == vec && trigger_mode(*entry) == 1 {
|
||||
set_remote_irr(entry, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn state(&self) -> IoapicState {
|
||||
IoapicState {
|
||||
id_reg: self.id_reg,
|
||||
reg_sel: self.reg_sel,
|
||||
reg_entries: self.reg_entries,
|
||||
used_entries: self.used_entries,
|
||||
apic_address: self.apic_address,
|
||||
}
|
||||
}
|
||||
// This should be called anytime an interrupt needs to be injected into the
|
||||
// running guest.
|
||||
pub fn service_irq(&mut self, irq: usize) -> Result<()> {
|
||||
let entry = &mut self.reg_entries[irq];
|
||||
|
||||
fn set_state(&mut self, state: &IoapicState) -> Result<()> {
|
||||
self.id_reg = state.id_reg;
|
||||
self.reg_sel = state.reg_sel;
|
||||
self.reg_entries = state.reg_entries;
|
||||
self.used_entries = state.used_entries;
|
||||
self.apic_address = state.apic_address;
|
||||
for (irq, entry) in self.used_entries.iter().enumerate() {
|
||||
if *entry {
|
||||
self.update_entry(irq)?;
|
||||
}
|
||||
self.interrupt_source_group
|
||||
.trigger(irq as InterruptIndex)
|
||||
.map_err(Error::TriggerInterrupt)?;
|
||||
debug!("Interrupt successfully delivered");
|
||||
|
||||
// If trigger mode is level sensitive, set the Remote IRR bit.
|
||||
// It will be cleared when the EOI is received.
|
||||
if trigger_mode(*entry) == 1 {
|
||||
set_remote_irr(entry, 1);
|
||||
}
|
||||
// Clear the Delivery Status bit
|
||||
set_delivery_status(entry, 0);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -344,7 +319,6 @@ impl Ioapic {
|
||||
high_addr: 0x0,
|
||||
low_addr,
|
||||
data,
|
||||
devid: 0,
|
||||
};
|
||||
|
||||
self.interrupt_source_group
|
||||
@@ -363,84 +337,52 @@ impl Ioapic {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl InterruptController for Ioapic {
|
||||
// The ioapic must be informed about EOIs in order to deassert interrupts
|
||||
// already sent.
|
||||
fn end_of_interrupt(&mut self, vec: u8) {
|
||||
for i in 0..NUM_IOAPIC_PINS {
|
||||
let entry = &mut self.reg_entries[i];
|
||||
// Clear Remote IRR bit
|
||||
if vector(*entry) == vec && trigger_mode(*entry) == 1 {
|
||||
set_remote_irr(entry, 0);
|
||||
fn ioapic_write(&mut self, val: u32) {
|
||||
debug!("IOAPIC_W reg 0x{:x}, val 0x{:x}", self.reg_sel, val);
|
||||
|
||||
match self.reg_sel as u8 {
|
||||
IOAPIC_REG_ID => self.id = (val >> 24) & 0xf,
|
||||
IOWIN_OFF..=REG_MAX_OFFSET => {
|
||||
let (index, is_high_bits) = decode_irq_from_selector(self.reg_sel as u8);
|
||||
if is_high_bits {
|
||||
self.reg_entries[index] &= 0xffff_ffff;
|
||||
self.reg_entries[index] |= u64::from(val) << 32;
|
||||
} else {
|
||||
// Ensure not to override read-only bits:
|
||||
// - Delivery Status (bit 12)
|
||||
// - Remote IRR (bit 14)
|
||||
self.reg_entries[index] &= 0xffff_ffff_0000_5000;
|
||||
self.reg_entries[index] |= u64::from(val) & 0xffff_afff;
|
||||
}
|
||||
// The entry must be updated through the interrupt source
|
||||
// group.
|
||||
if let Err(e) = self.update_entry(index) {
|
||||
error!("Failed updating IOAPIC entry: {:?}", e);
|
||||
}
|
||||
}
|
||||
_ => error!("IOAPIC: invalid write to register offset"),
|
||||
}
|
||||
}
|
||||
|
||||
fn ioapic_read(&self) -> u32 {
|
||||
debug!("IOAPIC_R reg 0x{:x}", self.reg_sel);
|
||||
|
||||
match self.reg_sel as u8 {
|
||||
IOAPIC_REG_VERSION => IOAPIC_VERSION_ID,
|
||||
IOAPIC_REG_ID | IOAPIC_REG_ARBITRATION_ID => (self.id & 0xf) << 24,
|
||||
IOWIN_OFF..=REG_MAX_OFFSET => {
|
||||
let (index, is_high_bits) = decode_irq_from_selector(self.reg_sel as u8);
|
||||
if is_high_bits {
|
||||
(self.reg_entries[index] >> 32) as u32
|
||||
} else {
|
||||
(self.reg_entries[index] & 0xffff_ffff) as u32
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
error!("IOAPIC: invalid read from register offset");
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This should be called anytime an interrupt needs to be injected into the
|
||||
// running guest.
|
||||
fn service_irq(&mut self, irq: usize) -> Result<()> {
|
||||
let entry = &mut self.reg_entries[irq];
|
||||
|
||||
self.interrupt_source_group
|
||||
.trigger(irq as InterruptIndex)
|
||||
.map_err(Error::TriggerInterrupt)?;
|
||||
debug!("Interrupt successfully delivered");
|
||||
|
||||
// If trigger mode is level sensitive, set the Remote IRR bit.
|
||||
// It will be cleared when the EOI is received.
|
||||
if trigger_mode(*entry) == 1 {
|
||||
set_remote_irr(entry, 1);
|
||||
}
|
||||
// Clear the Delivery Status bit
|
||||
set_delivery_status(entry, 0);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Snapshottable for Ioapic {
|
||||
fn id(&self) -> String {
|
||||
self.id.clone()
|
||||
}
|
||||
|
||||
fn snapshot(&self) -> std::result::Result<Snapshot, MigratableError> {
|
||||
let snapshot =
|
||||
serde_json::to_vec(&self.state()).map_err(|e| MigratableError::Snapshot(e.into()))?;
|
||||
|
||||
let mut ioapic_snapshot = Snapshot::new(self.id.as_str());
|
||||
ioapic_snapshot.add_data_section(SnapshotDataSection {
|
||||
id: format!("{}-section", self.id),
|
||||
snapshot,
|
||||
});
|
||||
|
||||
Ok(ioapic_snapshot)
|
||||
}
|
||||
|
||||
fn restore(&mut self, snapshot: Snapshot) -> std::result::Result<(), MigratableError> {
|
||||
if let Some(ioapic_section) = snapshot.snapshot_data.get(&format!("{}-section", self.id)) {
|
||||
let ioapic_state = match serde_json::from_slice(&ioapic_section.snapshot) {
|
||||
Ok(state) => state,
|
||||
Err(error) => {
|
||||
return Err(MigratableError::Restore(anyhow!(
|
||||
"Could not deserialize IOAPIC {}",
|
||||
error
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
return self.set_state(&ioapic_state).map_err(|e| {
|
||||
MigratableError::Restore(anyhow!("Could not restore IOAPIC state {:?}", e))
|
||||
});
|
||||
}
|
||||
|
||||
Err(MigratableError::Restore(anyhow!(
|
||||
"Could not find IOAPIC snapshot section"
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
impl Pausable for Ioapic {}
|
||||
impl Transportable for Ioapic {}
|
||||
impl Migratable for Ioapic {}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
use libc::{clock_gettime, gmtime_r, time_t, timespec, tm, CLOCK_REALTIME};
|
||||
use libc::{gmtime_r, time, time_t, tm};
|
||||
use std::cmp::min;
|
||||
use std::mem;
|
||||
|
||||
@@ -77,17 +77,14 @@ impl BusDevice for Cmos {
|
||||
let day;
|
||||
let month;
|
||||
let year;
|
||||
// The clock_gettime and gmtime_r calls are safe as long as the structs they are
|
||||
// given are large enough, and neither of them fail. It is safe to zero initialize
|
||||
// the tm and timespec struct because it contains only plain data.
|
||||
let update_in_progress = unsafe {
|
||||
let mut timespec: timespec = mem::zeroed();
|
||||
clock_gettime(CLOCK_REALTIME, &mut timespec as *mut _);
|
||||
|
||||
let now: time_t = timespec.tv_sec;
|
||||
// The time and gmtime_r calls are safe as long as the structs they are given are
|
||||
// large enough, and neither of them fail. It is safe to zero initialize the tm
|
||||
// struct because it contains only plain data.
|
||||
unsafe {
|
||||
let mut tm: tm = mem::zeroed();
|
||||
let mut now: time_t = 0;
|
||||
time(&mut now as *mut _);
|
||||
gmtime_r(&now, &mut tm as *mut _);
|
||||
|
||||
// The following lines of code are safe but depend on tm being in scope.
|
||||
seconds = tm.tm_sec;
|
||||
minutes = tm.tm_min;
|
||||
@@ -96,11 +93,6 @@ impl BusDevice for Cmos {
|
||||
day = tm.tm_mday;
|
||||
month = tm.tm_mon + 1;
|
||||
year = tm.tm_year;
|
||||
|
||||
// Update in Progress bit held for last 224us of each second
|
||||
const NANOSECONDS_PER_SECOND: i64 = 1_000_000_000;
|
||||
const UIP_HOLD_LENGTH: i64 = 8 * NANOSECONDS_PER_SECOND / 32768;
|
||||
timespec.tv_nsec >= (NANOSECONDS_PER_SECOND - UIP_HOLD_LENGTH)
|
||||
};
|
||||
match self.index {
|
||||
0x00 => to_bcd(seconds as u8),
|
||||
@@ -110,8 +102,6 @@ impl BusDevice for Cmos {
|
||||
0x07 => to_bcd(day as u8),
|
||||
0x08 => to_bcd(month as u8),
|
||||
0x09 => to_bcd((year % 100) as u8),
|
||||
// Bit 5 for 32kHz clock. Bit 7 for Update in Progress
|
||||
0x0a => 1 << 5 | (update_in_progress as u8) << 7,
|
||||
0x32 => to_bcd(((year + 1900) / 100) as u8),
|
||||
_ => {
|
||||
// self.index is always guaranteed to be in range via INDEX_MASK.
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
//
|
||||
// Copyright © 2020 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||
//
|
||||
|
||||
use BusDevice;
|
||||
|
||||
/// Provides firmware debug output via I/O port controls
|
||||
#[derive(Default)]
|
||||
pub struct FwDebugDevice {}
|
||||
|
||||
impl FwDebugDevice {
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
/// FwDebugDevice sits on the I/O bus as 0x402 and receives ASCII characters
|
||||
impl BusDevice for FwDebugDevice {
|
||||
/// Upon read return the magic value to indicate that there is a debug port
|
||||
fn read(&mut self, _base: u64, _offset: u64, data: &mut [u8]) {
|
||||
if data.len() == 1 {
|
||||
data[0] = 0xe9
|
||||
} else {
|
||||
error!("Invalid read size on debug port: {}", data.len())
|
||||
}
|
||||
}
|
||||
|
||||
fn write(&mut self, _base: u64, _offset: u64, data: &[u8]) {
|
||||
if data.len() == 1 {
|
||||
print!("{}", data[0] as char);
|
||||
} else {
|
||||
error!("Invalid write size on debug port: {}", data.len())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,19 +7,10 @@
|
||||
|
||||
#[cfg(feature = "cmos")]
|
||||
mod cmos;
|
||||
#[cfg(feature = "fwdebug")]
|
||||
mod fwdebug;
|
||||
mod i8042;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
mod rtc_pl031;
|
||||
mod serial;
|
||||
|
||||
#[cfg(feature = "cmos")]
|
||||
pub use self::cmos::Cmos;
|
||||
#[cfg(feature = "fwdebug")]
|
||||
pub use self::fwdebug::FwDebugDevice;
|
||||
pub use self::i8042::I8042Device;
|
||||
pub use self::serial::Serial;
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub use self::rtc_pl031::RTC;
|
||||
|
||||
@@ -1,626 +0,0 @@
|
||||
// Copyright 2020 Arm Limited (or its affiliates). All rights reserved.
|
||||
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//! ARM PL031 Real Time Clock
|
||||
//!
|
||||
//! This module implements a PL031 Real Time Clock (RTC) that provides to provides long time base counter.
|
||||
//! This is achieved by generating an interrupt signal after counting for a programmed number of cycles of
|
||||
//! a real-time clock input.
|
||||
//!
|
||||
use std::fmt;
|
||||
use std::sync::Arc;
|
||||
use std::time::Instant;
|
||||
use std::{io, result};
|
||||
|
||||
use crate::BusDevice;
|
||||
use vm_device::interrupt::InterruptSourceGroup;
|
||||
|
||||
// As you can see in https://static.docs.arm.com/ddi0224/c/real_time_clock_pl031_r1p3_technical_reference_manual_DDI0224C.pdf
|
||||
// at section 3.2 Summary of RTC registers, the total size occupied by this device is 0x000 -> 0xFFC + 4 = 0x1000.
|
||||
// From 0x0 to 0x1C we have following registers:
|
||||
const RTCDR: u64 = 0x0; // Data Register.
|
||||
const RTCMR: u64 = 0x4; // Match Register.
|
||||
const RTCLR: u64 = 0x8; // Load Regiser.
|
||||
const RTCCR: u64 = 0xc; // Control Register.
|
||||
const RTCIMSC: u64 = 0x10; // Interrupt Mask Set or Clear Register.
|
||||
const RTCRIS: u64 = 0x14; // Raw Interrupt Status.
|
||||
const RTCMIS: u64 = 0x18; // Masked Interrupt Status.
|
||||
const RTCICR: u64 = 0x1c; // Interrupt Clear Register.
|
||||
// From 0x020 to 0xFDC => reserved space.
|
||||
// From 0xFE0 to 0x1000 => Peripheral and PrimeCell Identification Registers which are Read Only registers.
|
||||
// AMBA standard devices have CIDs (Cell IDs) and PIDs (Peripheral IDs). The linux kernel will look for these in order to assert the identity
|
||||
// of these devices (i.e look at the `amba_device_try_add` function).
|
||||
// We are putting the expected values (look at 'Reset value' column from above mentioned document) in an array.
|
||||
const PL031_ID: [u8; 8] = [0x31, 0x10, 0x14, 0x00, 0x0d, 0xf0, 0x05, 0xb1];
|
||||
// We are only interested in the margins.
|
||||
const AMBA_ID_LOW: u64 = 0xFE0;
|
||||
const AMBA_ID_HIGH: u64 = 0x1000;
|
||||
/// Constant to convert seconds to nanoseconds.
|
||||
pub const NANOS_PER_SECOND: u64 = 1_000_000_000;
|
||||
|
||||
#[allow(unused_macros)]
|
||||
macro_rules! generate_read_fn {
|
||||
($fn_name: ident, $data_type: ty, $byte_type: ty, $type_size: expr, $endian_type: ident) => {
|
||||
#[allow(dead_code)]
|
||||
pub fn $fn_name(input: &[$byte_type]) -> $data_type {
|
||||
assert!($type_size == std::mem::size_of::<$data_type>());
|
||||
let mut array = [0u8; $type_size];
|
||||
for (byte, read) in array.iter_mut().zip(input.iter().cloned()) {
|
||||
*byte = read as u8;
|
||||
}
|
||||
<$data_type>::$endian_type(array)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[allow(unused_macros)]
|
||||
macro_rules! generate_write_fn {
|
||||
($fn_name: ident, $data_type: ty, $byte_type: ty, $endian_type: ident) => {
|
||||
#[allow(dead_code)]
|
||||
pub fn $fn_name(buf: &mut [$byte_type], n: $data_type) {
|
||||
for (byte, read) in buf
|
||||
.iter_mut()
|
||||
.zip(<$data_type>::$endian_type(n).iter().cloned())
|
||||
{
|
||||
*byte = read as $byte_type;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
generate_read_fn!(read_le_u16, u16, u8, 2, from_le_bytes);
|
||||
generate_read_fn!(read_le_u32, u32, u8, 4, from_le_bytes);
|
||||
generate_read_fn!(read_le_u64, u64, u8, 8, from_le_bytes);
|
||||
generate_read_fn!(read_le_i32, i32, i8, 4, from_le_bytes);
|
||||
|
||||
generate_read_fn!(read_be_u16, u16, u8, 2, from_be_bytes);
|
||||
generate_read_fn!(read_be_u32, u32, u8, 4, from_be_bytes);
|
||||
|
||||
generate_write_fn!(write_le_u16, u16, u8, to_le_bytes);
|
||||
generate_write_fn!(write_le_u32, u32, u8, to_le_bytes);
|
||||
generate_write_fn!(write_le_u64, u64, u8, to_le_bytes);
|
||||
generate_write_fn!(write_le_i32, i32, i8, to_le_bytes);
|
||||
|
||||
generate_write_fn!(write_be_u16, u16, u8, to_be_bytes);
|
||||
generate_write_fn!(write_be_u32, u32, u8, to_be_bytes);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
BadWriteOffset(u64),
|
||||
InterruptFailure(io::Error),
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Error::BadWriteOffset(offset) => write!(f, "Bad Write Offset: {}", offset),
|
||||
Error::InterruptFailure(e) => write!(f, "Failed to trigger interrupt: {}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type Result<T> = result::Result<T, Error>;
|
||||
|
||||
/// Wrapper over `libc::clockid_t` to specify Linux Kernel clock source.
|
||||
pub enum ClockType {
|
||||
/// Equivalent to `libc::CLOCK_MONOTONIC`.
|
||||
Monotonic,
|
||||
/// Equivalent to `libc::CLOCK_REALTIME`.
|
||||
#[allow(dead_code)]
|
||||
Real,
|
||||
/// Equivalent to `libc::CLOCK_PROCESS_CPUTIME_ID`.
|
||||
ProcessCpu,
|
||||
/// Equivalent to `libc::CLOCK_THREAD_CPUTIME_ID`.
|
||||
#[allow(dead_code)]
|
||||
ThreadCpu,
|
||||
}
|
||||
|
||||
impl Into<libc::clockid_t> for ClockType {
|
||||
fn into(self) -> libc::clockid_t {
|
||||
match self {
|
||||
ClockType::Monotonic => libc::CLOCK_MONOTONIC,
|
||||
ClockType::Real => libc::CLOCK_REALTIME,
|
||||
ClockType::ProcessCpu => libc::CLOCK_PROCESS_CPUTIME_ID,
|
||||
ClockType::ThreadCpu => libc::CLOCK_THREAD_CPUTIME_ID,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Structure representing the date in local time with nanosecond precision.
|
||||
pub struct LocalTime {
|
||||
/// Seconds in current minute.
|
||||
sec: i32,
|
||||
/// Minutes in current hour.
|
||||
min: i32,
|
||||
/// Hours in current day, 24H format.
|
||||
hour: i32,
|
||||
/// Days in current month.
|
||||
mday: i32,
|
||||
/// Months in current year.
|
||||
mon: i32,
|
||||
/// Years passed since 1900 BC.
|
||||
year: i32,
|
||||
/// Nanoseconds in current second.
|
||||
nsec: i64,
|
||||
}
|
||||
|
||||
impl LocalTime {
|
||||
/// Returns the [LocalTime](struct.LocalTime.html) structure for the calling moment.
|
||||
#[allow(dead_code)]
|
||||
pub fn now() -> LocalTime {
|
||||
let mut timespec = libc::timespec {
|
||||
tv_sec: 0,
|
||||
tv_nsec: 0,
|
||||
};
|
||||
let mut tm: libc::tm = libc::tm {
|
||||
tm_sec: 0,
|
||||
tm_min: 0,
|
||||
tm_hour: 0,
|
||||
tm_mday: 0,
|
||||
tm_mon: 0,
|
||||
tm_year: 0,
|
||||
tm_wday: 0,
|
||||
tm_yday: 0,
|
||||
tm_isdst: 0,
|
||||
tm_gmtoff: 0,
|
||||
tm_zone: std::ptr::null(),
|
||||
};
|
||||
|
||||
// Safe because the parameters are valid.
|
||||
unsafe {
|
||||
libc::clock_gettime(libc::CLOCK_REALTIME, &mut timespec);
|
||||
libc::localtime_r(×pec.tv_sec, &mut tm);
|
||||
}
|
||||
|
||||
LocalTime {
|
||||
sec: tm.tm_sec,
|
||||
min: tm.tm_min,
|
||||
hour: tm.tm_hour,
|
||||
mday: tm.tm_mday,
|
||||
mon: tm.tm_mon,
|
||||
year: tm.tm_year,
|
||||
nsec: timespec.tv_nsec,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for LocalTime {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}-{:02}-{:02}T{:02}:{:02}:{:02}.{:09}",
|
||||
self.year + 1900,
|
||||
self.mon + 1,
|
||||
self.mday,
|
||||
self.hour,
|
||||
self.min,
|
||||
self.sec,
|
||||
self.nsec
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Holds a micro-second resolution timestamp with both the real time and cpu time.
|
||||
#[derive(Clone)]
|
||||
pub struct TimestampUs {
|
||||
/// Real time in microseconds.
|
||||
pub time_us: u64,
|
||||
/// Cpu time in microseconds.
|
||||
pub cputime_us: u64,
|
||||
}
|
||||
|
||||
impl Default for TimestampUs {
|
||||
fn default() -> TimestampUs {
|
||||
TimestampUs {
|
||||
time_us: get_time(ClockType::Monotonic) / 1000,
|
||||
cputime_us: get_time(ClockType::ProcessCpu) / 1000,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a timestamp in nanoseconds from a monotonic clock.
|
||||
///
|
||||
/// Uses `_rdstc` on `x86_64` and [`get_time`](fn.get_time.html) on other architectures.
|
||||
#[allow(dead_code)]
|
||||
pub fn timestamp_cycles() -> u64 {
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
// Safe because there's nothing that can go wrong with this call.
|
||||
unsafe {
|
||||
std::arch::x86_64::_rdtsc() as u64
|
||||
}
|
||||
#[cfg(not(target_arch = "x86_64"))]
|
||||
{
|
||||
get_time(ClockType::Monotonic)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a timestamp in nanoseconds based on the provided clock type.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `clock_type` - Identifier of the Linux Kernel clock on which to act.
|
||||
pub fn get_time(clock_type: ClockType) -> u64 {
|
||||
let mut time_struct = libc::timespec {
|
||||
tv_sec: 0,
|
||||
tv_nsec: 0,
|
||||
};
|
||||
// Safe because the parameters are valid.
|
||||
unsafe { libc::clock_gettime(clock_type.into(), &mut time_struct) };
|
||||
seconds_to_nanoseconds(time_struct.tv_sec).unwrap() as u64 + (time_struct.tv_nsec as u64)
|
||||
}
|
||||
|
||||
/// Converts a timestamp in seconds to an equivalent one in nanoseconds.
|
||||
/// Returns `None` if the conversion overflows.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `value` - Timestamp in seconds.
|
||||
pub fn seconds_to_nanoseconds(value: i64) -> Option<i64> {
|
||||
value.checked_mul(NANOS_PER_SECOND as i64)
|
||||
}
|
||||
|
||||
/// A RTC device following the PL031 specification..
|
||||
pub struct RTC {
|
||||
previous_now: Instant,
|
||||
tick_offset: i64,
|
||||
// This is used for implementing the RTC alarm. However, in Firecracker we do not need it.
|
||||
match_value: u32,
|
||||
// Writes to this register load an update value into the RTC.
|
||||
load: u32,
|
||||
imsc: u32,
|
||||
ris: u32,
|
||||
interrupt: Arc<Box<dyn InterruptSourceGroup>>,
|
||||
}
|
||||
|
||||
impl RTC {
|
||||
/// Constructs an AMBA PL031 RTC device.
|
||||
pub fn new(interrupt: Arc<Box<dyn InterruptSourceGroup>>) -> RTC {
|
||||
RTC {
|
||||
// This is used only for duration measuring purposes.
|
||||
previous_now: Instant::now(),
|
||||
tick_offset: get_time(ClockType::Real) as i64,
|
||||
match_value: 0,
|
||||
load: 0,
|
||||
imsc: 0,
|
||||
ris: 0,
|
||||
interrupt,
|
||||
}
|
||||
}
|
||||
|
||||
fn trigger_interrupt(&mut self) -> Result<()> {
|
||||
self.interrupt.trigger(0).map_err(Error::InterruptFailure)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_time(&self) -> u32 {
|
||||
let ts = (self.tick_offset as i128)
|
||||
+ (Instant::now().duration_since(self.previous_now).as_nanos() as i128);
|
||||
(ts / NANOS_PER_SECOND as i128) as u32
|
||||
}
|
||||
|
||||
fn handle_write(&mut self, offset: u64, val: u32) -> Result<()> {
|
||||
match offset {
|
||||
RTCMR => {
|
||||
// The MR register is used for implementing the RTC alarm. A real time clock alarm is
|
||||
// a feature that can be used to allow a computer to 'wake up' after shut down to execute
|
||||
// tasks every day or on a certain day. It can sometimes be found in the 'Power Management'
|
||||
// section of a motherboard's BIOS setup. This is functionality that extends beyond
|
||||
// Firecracker intended use. However, we increment a metric just in case.
|
||||
self.match_value = val;
|
||||
}
|
||||
RTCLR => {
|
||||
self.load = val;
|
||||
self.previous_now = Instant::now();
|
||||
// If the unwrap fails, then the internal value of the clock has been corrupted and
|
||||
// we want to terminate the execution of the process.
|
||||
self.tick_offset = seconds_to_nanoseconds(i64::from(val)).unwrap();
|
||||
}
|
||||
RTCIMSC => {
|
||||
self.imsc = val & 1;
|
||||
self.trigger_interrupt()?;
|
||||
}
|
||||
RTCICR => {
|
||||
// As per above mentioned doc, the interrupt is cleared by writing any data value to
|
||||
// the Interrupt Clear Register.
|
||||
self.ris = 0;
|
||||
self.trigger_interrupt()?;
|
||||
}
|
||||
RTCCR => (), // ignore attempts to turn off the timer.
|
||||
o => {
|
||||
return Err(Error::BadWriteOffset(o));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl BusDevice for RTC {
|
||||
fn read(&mut self, _base: u64, offset: u64, data: &mut [u8]) {
|
||||
let v;
|
||||
let mut read_ok = true;
|
||||
|
||||
if offset < AMBA_ID_HIGH && offset >= AMBA_ID_LOW {
|
||||
let index = ((offset - AMBA_ID_LOW) >> 2) as usize;
|
||||
v = u32::from(PL031_ID[index]);
|
||||
} else {
|
||||
v = match offset {
|
||||
RTCDR => self.get_time(),
|
||||
RTCMR => {
|
||||
// Even though we are not implementing RTC alarm we return the last value
|
||||
self.match_value
|
||||
}
|
||||
RTCLR => self.load,
|
||||
RTCCR => 1, // RTC is always enabled.
|
||||
RTCIMSC => self.imsc,
|
||||
RTCRIS => self.ris,
|
||||
RTCMIS => self.ris & self.imsc,
|
||||
_ => {
|
||||
read_ok = false;
|
||||
0
|
||||
}
|
||||
};
|
||||
}
|
||||
if read_ok && data.len() <= 4 {
|
||||
write_le_u32(data, v);
|
||||
} else {
|
||||
warn!(
|
||||
"Invalid RTC PL031 read: offset {}, data length {}",
|
||||
offset,
|
||||
data.len()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn write(&mut self, _base: u64, offset: u64, data: &[u8]) {
|
||||
if data.len() <= 4 {
|
||||
let v = read_le_u32(&data[..]);
|
||||
if let Err(e) = self.handle_write(offset, v) {
|
||||
warn!("Failed to write to RTC PL031 device: {}", e);
|
||||
}
|
||||
} else {
|
||||
warn!(
|
||||
"Invalid RTC PL031 write: offset {}, data length {}",
|
||||
offset,
|
||||
data.len()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::sync::Arc;
|
||||
use vm_device::interrupt::{InterruptIndex, InterruptSourceConfig};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
const LEGACY_RTC_MAPPED_IO_START: u64 = 0x0901_0000;
|
||||
|
||||
#[test]
|
||||
fn test_get_time() {
|
||||
for _ in 0..1000 {
|
||||
assert!(get_time(ClockType::Monotonic) <= get_time(ClockType::Monotonic));
|
||||
}
|
||||
|
||||
for _ in 0..1000 {
|
||||
assert!(get_time(ClockType::ProcessCpu) <= get_time(ClockType::ProcessCpu));
|
||||
}
|
||||
|
||||
for _ in 0..1000 {
|
||||
assert!(get_time(ClockType::ThreadCpu) <= get_time(ClockType::ThreadCpu));
|
||||
}
|
||||
|
||||
assert_ne!(get_time(ClockType::Real), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_local_time_display() {
|
||||
let local_time = LocalTime {
|
||||
sec: 30,
|
||||
min: 15,
|
||||
hour: 10,
|
||||
mday: 4,
|
||||
mon: 6,
|
||||
year: 119,
|
||||
nsec: 123_456_789,
|
||||
};
|
||||
assert_eq!(
|
||||
String::from("2019-07-04T10:15:30.123456789"),
|
||||
local_time.to_string()
|
||||
);
|
||||
|
||||
let local_time = LocalTime {
|
||||
sec: 5,
|
||||
min: 5,
|
||||
hour: 5,
|
||||
mday: 23,
|
||||
mon: 7,
|
||||
year: 44,
|
||||
nsec: 123,
|
||||
};
|
||||
assert_eq!(
|
||||
String::from("1944-08-23T05:05:05.000000123"),
|
||||
local_time.to_string()
|
||||
);
|
||||
|
||||
let local_time = LocalTime::now();
|
||||
assert!(local_time.mon >= 0 && local_time.mon <= 11);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_seconds_to_nanoseconds() {
|
||||
assert_eq!(
|
||||
seconds_to_nanoseconds(100).unwrap() as u64,
|
||||
100 * NANOS_PER_SECOND
|
||||
);
|
||||
|
||||
assert!(seconds_to_nanoseconds(9_223_372_037).is_none());
|
||||
}
|
||||
|
||||
struct TestInterrupt {
|
||||
event_fd: EventFd,
|
||||
}
|
||||
|
||||
impl InterruptSourceGroup for TestInterrupt {
|
||||
fn trigger(&self, _index: InterruptIndex) -> result::Result<(), std::io::Error> {
|
||||
self.event_fd.write(1)
|
||||
}
|
||||
|
||||
fn update(
|
||||
&self,
|
||||
_index: InterruptIndex,
|
||||
_config: InterruptSourceConfig,
|
||||
) -> result::Result<(), std::io::Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn notifier(&self, _index: InterruptIndex) -> Option<&EventFd> {
|
||||
Some(&self.event_fd)
|
||||
}
|
||||
}
|
||||
|
||||
impl TestInterrupt {
|
||||
fn new(event_fd: EventFd) -> Self {
|
||||
TestInterrupt { event_fd }
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rtc_read_write_and_event() {
|
||||
let intr_evt = EventFd::new(libc::EFD_NONBLOCK).unwrap();
|
||||
|
||||
let mut rtc = RTC::new(Arc::new(Box::new(TestInterrupt::new(
|
||||
intr_evt.try_clone().unwrap(),
|
||||
))));
|
||||
let mut data = [0; 4];
|
||||
|
||||
// Read and write to the MR register.
|
||||
write_le_u32(&mut data, 123);
|
||||
rtc.write(LEGACY_RTC_MAPPED_IO_START, RTCMR, &mut data);
|
||||
rtc.read(LEGACY_RTC_MAPPED_IO_START, RTCMR, &mut data);
|
||||
let v = read_le_u32(&data[..]);
|
||||
assert_eq!(v, 123);
|
||||
|
||||
// Read and write to the LR register.
|
||||
let v = get_time(ClockType::Real);
|
||||
write_le_u32(&mut data, (v / NANOS_PER_SECOND) as u32);
|
||||
let previous_now_before = rtc.previous_now;
|
||||
rtc.write(LEGACY_RTC_MAPPED_IO_START, RTCLR, &mut data);
|
||||
|
||||
assert!(rtc.previous_now > previous_now_before);
|
||||
|
||||
rtc.read(LEGACY_RTC_MAPPED_IO_START, RTCLR, &mut data);
|
||||
let v_read = read_le_u32(&data[..]);
|
||||
assert_eq!((v / NANOS_PER_SECOND) as u32, v_read);
|
||||
|
||||
// Read and write to IMSC register.
|
||||
// Test with non zero value.
|
||||
let non_zero = 1;
|
||||
write_le_u32(&mut data, non_zero);
|
||||
rtc.write(LEGACY_RTC_MAPPED_IO_START, RTCIMSC, &mut data);
|
||||
// The interrupt line should be on.
|
||||
assert!(rtc.interrupt.notifier(0).unwrap().read().unwrap() == 1);
|
||||
rtc.read(LEGACY_RTC_MAPPED_IO_START, RTCIMSC, &mut data);
|
||||
let v = read_le_u32(&data[..]);
|
||||
assert_eq!(non_zero & 1, v);
|
||||
|
||||
// Now test with 0.
|
||||
write_le_u32(&mut data, 0);
|
||||
rtc.write(LEGACY_RTC_MAPPED_IO_START, RTCIMSC, &mut data);
|
||||
rtc.read(LEGACY_RTC_MAPPED_IO_START, RTCIMSC, &mut data);
|
||||
let v = read_le_u32(&data[..]);
|
||||
assert_eq!(0, v);
|
||||
|
||||
// Read and write to the ICR register.
|
||||
write_le_u32(&mut data, 1);
|
||||
rtc.write(LEGACY_RTC_MAPPED_IO_START, RTCICR, &mut data);
|
||||
// The interrupt line should be on.
|
||||
assert!(rtc.interrupt.notifier(0).unwrap().read().unwrap() > 1);
|
||||
let v_before = read_le_u32(&data[..]);
|
||||
|
||||
rtc.read(LEGACY_RTC_MAPPED_IO_START, RTCICR, &mut data);
|
||||
let v = read_le_u32(&data[..]);
|
||||
// ICR is a write only register. Data received should stay equal to data sent.
|
||||
assert_eq!(v, v_before);
|
||||
|
||||
// Attempts to turn off the RTC should not go through.
|
||||
write_le_u32(&mut data, 0);
|
||||
rtc.write(LEGACY_RTC_MAPPED_IO_START, RTCCR, &mut data);
|
||||
rtc.read(LEGACY_RTC_MAPPED_IO_START, RTCCR, &mut data);
|
||||
let v = read_le_u32(&data[..]);
|
||||
assert_eq!(v, 1);
|
||||
|
||||
// Attempts to write beyond the writable space. Using here the space used to read
|
||||
// the CID and PID from.
|
||||
write_le_u32(&mut data, 0);
|
||||
rtc.write(LEGACY_RTC_MAPPED_IO_START, AMBA_ID_LOW, &mut data);
|
||||
// However, reading from the AMBA_ID_LOW should succeed upon read.
|
||||
|
||||
let mut data = [0; 4];
|
||||
rtc.read(LEGACY_RTC_MAPPED_IO_START, AMBA_ID_LOW, &mut data);
|
||||
let index = AMBA_ID_LOW + 3;
|
||||
assert_eq!(data[0], PL031_ID[((index - AMBA_ID_LOW) >> 2) as usize]);
|
||||
}
|
||||
|
||||
macro_rules! byte_order_test_read_write {
|
||||
($test_name: ident, $write_fn_name: ident, $read_fn_name: ident, $is_be: expr, $data_type: ty) => {
|
||||
#[test]
|
||||
fn $test_name() {
|
||||
#[allow(overflowing_literals)]
|
||||
let test_cases = [
|
||||
(
|
||||
0x0123_4567_89AB_CDEF as u64,
|
||||
[0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef],
|
||||
),
|
||||
(
|
||||
0x0000_0000_0000_0000 as u64,
|
||||
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
|
||||
),
|
||||
(
|
||||
0x1923_2345_ABF3_CCD4 as u64,
|
||||
[0x19, 0x23, 0x23, 0x45, 0xAB, 0xF3, 0xCC, 0xD4],
|
||||
),
|
||||
(
|
||||
0x0FF0_0FF0_0FF0_0FF0 as u64,
|
||||
[0x0F, 0xF0, 0x0F, 0xF0, 0x0F, 0xF0, 0x0F, 0xF0],
|
||||
),
|
||||
(
|
||||
0xFFFF_FFFF_FFFF_FFFF as u64,
|
||||
[0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF],
|
||||
),
|
||||
(
|
||||
0x89AB_12D4_C2D2_09BB as u64,
|
||||
[0x89, 0xAB, 0x12, 0xD4, 0xC2, 0xD2, 0x09, 0xBB],
|
||||
),
|
||||
];
|
||||
|
||||
let type_size = std::mem::size_of::<$data_type>();
|
||||
for (test_val, v_arr) in &test_cases {
|
||||
let v = *test_val as $data_type;
|
||||
let cmp_iter: Box<dyn Iterator<Item = _>> = if $is_be {
|
||||
Box::new(v_arr[(8 - type_size)..].iter())
|
||||
} else {
|
||||
Box::new(v_arr.iter().rev())
|
||||
};
|
||||
// test write
|
||||
let mut write_arr = vec![Default::default(); type_size];
|
||||
$write_fn_name(&mut write_arr, v);
|
||||
for (cmp, cur) in cmp_iter.zip(write_arr.iter()) {
|
||||
assert_eq!(*cmp, *cur as u8)
|
||||
}
|
||||
// test read
|
||||
let read_val = $read_fn_name(&write_arr);
|
||||
assert_eq!(v, read_val);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
byte_order_test_read_write!(test_le_u16, write_le_u16, read_le_u16, false, u16);
|
||||
byte_order_test_read_write!(test_le_u32, write_le_u32, read_le_u32, false, u32);
|
||||
byte_order_test_read_write!(test_le_u64, write_le_u64, read_le_u64, false, u64);
|
||||
byte_order_test_read_write!(test_le_i32, write_le_i32, read_le_i32, false, i32);
|
||||
byte_order_test_read_write!(test_be_u16, write_be_u16, read_be_u16, true, u16);
|
||||
byte_order_test_read_write!(test_be_u32, write_be_u32, read_be_u32, true, u32);
|
||||
}
|
||||
@@ -6,15 +6,10 @@
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
|
||||
use crate::BusDevice;
|
||||
use anyhow::anyhow;
|
||||
use std::collections::VecDeque;
|
||||
use std::sync::Arc;
|
||||
use std::{io, result};
|
||||
use vm_device::interrupt::InterruptSourceGroup;
|
||||
use vm_migration::{
|
||||
Migratable, MigratableError, Pausable, Snapshot, SnapshotDataSection, Snapshottable,
|
||||
Transportable,
|
||||
};
|
||||
use vmm_sys_util::errno::Result;
|
||||
|
||||
const LOOP_SIZE: usize = 0x40;
|
||||
@@ -60,7 +55,6 @@ const DEFAULT_BAUD_DIVISOR: u16 = 12; // 9600 bps
|
||||
/// This can optionally write the guest's output to a Write trait object. To send input to the
|
||||
/// guest, use `queue_input_bytes`.
|
||||
pub struct Serial {
|
||||
id: String,
|
||||
interrupt_enable: u8,
|
||||
interrupt_identification: u8,
|
||||
interrupt: Arc<Box<dyn InterruptSourceGroup>>,
|
||||
@@ -74,27 +68,12 @@ pub struct Serial {
|
||||
out: Option<Box<dyn io::Write + Send>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct SerialState {
|
||||
interrupt_enable: u8,
|
||||
interrupt_identification: u8,
|
||||
line_control: u8,
|
||||
line_status: u8,
|
||||
modem_control: u8,
|
||||
modem_status: u8,
|
||||
scratch: u8,
|
||||
baud_divisor: u16,
|
||||
in_buffer: VecDeque<u8>,
|
||||
}
|
||||
|
||||
impl Serial {
|
||||
pub fn new(
|
||||
id: String,
|
||||
interrupt: Arc<Box<dyn InterruptSourceGroup>>,
|
||||
out: Option<Box<dyn io::Write + Send>>,
|
||||
) -> Serial {
|
||||
Serial {
|
||||
id,
|
||||
interrupt_enable: 0,
|
||||
interrupt_identification: DEFAULT_INTERRUPT_IDENTIFICATION,
|
||||
interrupt,
|
||||
@@ -111,16 +90,15 @@ impl Serial {
|
||||
|
||||
/// Constructs a Serial port ready for output.
|
||||
pub fn new_out(
|
||||
id: String,
|
||||
interrupt: Arc<Box<dyn InterruptSourceGroup>>,
|
||||
out: Box<dyn io::Write + Send>,
|
||||
) -> Serial {
|
||||
Self::new(id, interrupt, Some(out))
|
||||
Self::new(interrupt, Some(out))
|
||||
}
|
||||
|
||||
/// Constructs a Serial port with no connected output.
|
||||
pub fn new_sink(id: String, interrupt: Arc<Box<dyn InterruptSourceGroup>>) -> Serial {
|
||||
Self::new(id, interrupt, None)
|
||||
pub fn new_sink(interrupt: Arc<Box<dyn InterruptSourceGroup>>) -> Serial {
|
||||
Self::new(interrupt, None)
|
||||
}
|
||||
|
||||
/// Queues raw bytes for the guest to read and signals the interrupt if the line status would
|
||||
@@ -216,32 +194,6 @@ impl Serial {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn state(&self) -> SerialState {
|
||||
SerialState {
|
||||
interrupt_enable: self.interrupt_enable,
|
||||
interrupt_identification: self.interrupt_identification,
|
||||
line_control: self.line_control,
|
||||
line_status: self.line_status,
|
||||
modem_control: self.modem_control,
|
||||
modem_status: self.modem_status,
|
||||
scratch: self.scratch,
|
||||
baud_divisor: self.baud_divisor,
|
||||
in_buffer: self.in_buffer.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn set_state(&mut self, state: &SerialState) {
|
||||
self.interrupt_enable = state.interrupt_enable;
|
||||
self.interrupt_identification = state.interrupt_identification;
|
||||
self.line_control = state.line_control;
|
||||
self.line_status = state.line_status;
|
||||
self.modem_control = state.modem_control;
|
||||
self.modem_status = state.modem_status;
|
||||
self.scratch = state.scratch;
|
||||
self.baud_divisor = state.baud_divisor;
|
||||
self.in_buffer = state.in_buffer.clone();
|
||||
}
|
||||
}
|
||||
|
||||
impl BusDevice for Serial {
|
||||
@@ -284,51 +236,6 @@ impl BusDevice for Serial {
|
||||
}
|
||||
}
|
||||
|
||||
impl Snapshottable for Serial {
|
||||
fn id(&self) -> String {
|
||||
self.id.clone()
|
||||
}
|
||||
|
||||
fn snapshot(&self) -> std::result::Result<Snapshot, MigratableError> {
|
||||
let snapshot =
|
||||
serde_json::to_vec(&self.state()).map_err(|e| MigratableError::Snapshot(e.into()))?;
|
||||
|
||||
let mut serial_snapshot = Snapshot::new(self.id.as_str());
|
||||
serial_snapshot.add_data_section(SnapshotDataSection {
|
||||
id: format!("{}-section", self.id),
|
||||
snapshot,
|
||||
});
|
||||
|
||||
Ok(serial_snapshot)
|
||||
}
|
||||
|
||||
fn restore(&mut self, snapshot: Snapshot) -> std::result::Result<(), MigratableError> {
|
||||
if let Some(serial_section) = snapshot.snapshot_data.get(&format!("{}-section", self.id)) {
|
||||
let serial_state = match serde_json::from_slice(&serial_section.snapshot) {
|
||||
Ok(state) => state,
|
||||
Err(error) => {
|
||||
return Err(MigratableError::Restore(anyhow!(
|
||||
"Could not deserialize SERIAL {}",
|
||||
error
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
self.set_state(&serial_state);
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Err(MigratableError::Restore(anyhow!(
|
||||
"Could not find the serial snapshot section"
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
impl Pausable for Serial {}
|
||||
impl Transportable for Serial {}
|
||||
impl Migratable for Serial {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -337,8 +244,6 @@ mod tests {
|
||||
use vm_device::interrupt::{InterruptIndex, InterruptSourceConfig};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
const SERIAL_NAME: &str = "serial";
|
||||
|
||||
struct TestInterrupt {
|
||||
event_fd: EventFd,
|
||||
}
|
||||
@@ -389,7 +294,6 @@ mod tests {
|
||||
let intr_evt = EventFd::new(0).unwrap();
|
||||
let serial_out = SharedBuffer::new();
|
||||
let mut serial = Serial::new_out(
|
||||
String::from(SERIAL_NAME),
|
||||
Arc::new(Box::new(TestInterrupt::new(intr_evt.try_clone().unwrap()))),
|
||||
Box::new(serial_out.clone()),
|
||||
);
|
||||
@@ -409,7 +313,6 @@ mod tests {
|
||||
let intr_evt = EventFd::new(0).unwrap();
|
||||
let serial_out = SharedBuffer::new();
|
||||
let mut serial = Serial::new_out(
|
||||
String::from(SERIAL_NAME),
|
||||
Arc::new(Box::new(TestInterrupt::new(intr_evt.try_clone().unwrap()))),
|
||||
Box::new(serial_out.clone()),
|
||||
);
|
||||
@@ -447,10 +350,9 @@ mod tests {
|
||||
#[test]
|
||||
fn serial_thr() {
|
||||
let intr_evt = EventFd::new(0).unwrap();
|
||||
let mut serial = Serial::new_sink(
|
||||
String::from(SERIAL_NAME),
|
||||
Arc::new(Box::new(TestInterrupt::new(intr_evt.try_clone().unwrap()))),
|
||||
);
|
||||
let mut serial = Serial::new_sink(Arc::new(Box::new(TestInterrupt::new(
|
||||
intr_evt.try_clone().unwrap(),
|
||||
))));
|
||||
|
||||
// write 1 to the interrupt event fd, so that read doesn't block in case the event fd
|
||||
// counter doesn't change (for 0 it blocks)
|
||||
@@ -469,10 +371,9 @@ mod tests {
|
||||
#[test]
|
||||
fn serial_dlab() {
|
||||
let intr_evt = EventFd::new(0).unwrap();
|
||||
let mut serial = Serial::new_sink(
|
||||
String::from(SERIAL_NAME),
|
||||
Arc::new(Box::new(TestInterrupt::new(intr_evt.try_clone().unwrap()))),
|
||||
);
|
||||
let mut serial = Serial::new_sink(Arc::new(Box::new(TestInterrupt::new(
|
||||
intr_evt.try_clone().unwrap(),
|
||||
))));
|
||||
|
||||
serial.write(0, LCR as u64, &[LCR_DLAB_BIT as u8]);
|
||||
serial.write(0, DLAB_LOW as u64, &[0x12 as u8]);
|
||||
@@ -490,10 +391,9 @@ mod tests {
|
||||
#[test]
|
||||
fn serial_modem() {
|
||||
let intr_evt = EventFd::new(0).unwrap();
|
||||
let mut serial = Serial::new_sink(
|
||||
String::from(SERIAL_NAME),
|
||||
Arc::new(Box::new(TestInterrupt::new(intr_evt.try_clone().unwrap()))),
|
||||
);
|
||||
let mut serial = Serial::new_sink(Arc::new(Box::new(TestInterrupt::new(
|
||||
intr_evt.try_clone().unwrap(),
|
||||
))));
|
||||
|
||||
serial.write(0, MCR as u64, &[MCR_LOOP_BIT as u8]);
|
||||
serial.write(0, DATA as u64, &['a' as u8]);
|
||||
@@ -516,10 +416,9 @@ mod tests {
|
||||
#[test]
|
||||
fn serial_scratch() {
|
||||
let intr_evt = EventFd::new(0).unwrap();
|
||||
let mut serial = Serial::new_sink(
|
||||
String::from(SERIAL_NAME),
|
||||
Arc::new(Box::new(TestInterrupt::new(intr_evt.try_clone().unwrap()))),
|
||||
);
|
||||
let mut serial = Serial::new_sink(Arc::new(Box::new(TestInterrupt::new(
|
||||
intr_evt.try_clone().unwrap(),
|
||||
))));
|
||||
|
||||
serial.write(0, SCR as u64, &[0x12 as u8]);
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
|
||||
//! Emulates virtual and hardware devices.
|
||||
extern crate anyhow;
|
||||
#[macro_use]
|
||||
extern crate bitflags;
|
||||
extern crate byteorder;
|
||||
@@ -14,16 +13,9 @@ extern crate epoll;
|
||||
extern crate libc;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
#[cfg(feature = "acpi")]
|
||||
extern crate acpi_tables;
|
||||
extern crate serde;
|
||||
extern crate vm_device;
|
||||
extern crate vm_memory;
|
||||
extern crate vm_migration;
|
||||
extern crate vmm_sys_util;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
extern crate serde_json;
|
||||
|
||||
use std::fs::File;
|
||||
use std::io;
|
||||
@@ -31,15 +23,11 @@ use std::io;
|
||||
#[cfg(feature = "acpi")]
|
||||
mod acpi;
|
||||
mod bus;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub mod gic;
|
||||
pub mod interrupt_controller;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub mod ioapic;
|
||||
pub mod legacy;
|
||||
|
||||
#[cfg(feature = "acpi")]
|
||||
pub use self::acpi::{AcpiGEDDevice, AcpiPMTimerDevice, AcpiShutdownDevice};
|
||||
pub use self::acpi::{AcpiGEDDevice, AcpiShutdownDevice};
|
||||
pub use self::bus::{Bus, BusDevice, Error as BusError};
|
||||
|
||||
pub type DeviceEventT = u16;
|
||||
@@ -85,6 +73,5 @@ bitflags! {
|
||||
const NO_DEVICES_CHANGED = 0;
|
||||
const CPU_DEVICES_CHANGED = 0b1;
|
||||
const MEMORY_DEVICES_CHANGED = 0b10;
|
||||
const PCI_DEVICES_CHANGED = 0b100;
|
||||
}
|
||||
}
|
||||
|
||||
51
docs/api.md
51
docs/api.md
@@ -78,26 +78,18 @@ Shut the VMM down | `/vmm.shutdown` | N/A | N/A
|
||||
|
||||
#### Virtual Machine (VM) Actions
|
||||
|
||||
Action | Endpoint | Request Body | Response Body | Prerequisites
|
||||
-----------------------------------|---------------------|---------------------------|--------------------------|---------------------------
|
||||
Create the VM | `/vm.create` | `/schemas/VmConfig` | N/A | The VM is not created yet
|
||||
Delete the VM | `/vm.delete` | N/A | N/A | N/A
|
||||
Boot the VM | `/vm.boot` | N/A | N/A | The VM is created but not booted
|
||||
Shut the VM down | `/vm.shutdown` | N/A | N/A | The VM is booted
|
||||
Reboot the VM | `/vm.reboot` | N/A | N/A | The VM is booted
|
||||
Pause the VM | `/vm.pause` | N/A | N/A | The VM is booted
|
||||
Resume the VM | `/vm.resume` | N/A | N/A | The VM is paused
|
||||
Add/remove CPUs to/from the VM | `/vm.resize` | `/schemas/VmResize` | N/A | The VM is booted
|
||||
Remove memory from the VM | `/vm.resize` | `/schemas/VmResize` | N/A | The VM is booted
|
||||
Dump the VM information | `/vm.info` | N/A | `/schemas/VmInfo` | The VM is created
|
||||
Add VFIO PCI device to the VM | `/vm.add-device` | `/schemas/VmAddDevice` | `/schemas/PciDeviceInfo` | The VM is booted
|
||||
Add disk device to the VM | `/vm.add-disk` | `/schemas/DiskConfig` | `/schemas/PciDeviceInfo` | The VM is booted
|
||||
Add fs device to the VM | `/vm.add-fs` | `/schemas/FsConfig` | `/schemas/PciDeviceInfo` | The VM is booted
|
||||
Add pmem device to the VM | `/vm.add-pmem` | `/schemas/PmemConfig` | `/schemas/PciDeviceInfo` | The VM is booted
|
||||
Add network device to the VM | `/vm.add-net` | `/schemas/NetConfig` | `/schemas/PciDeviceInfo` | The VM is booted
|
||||
Add vsock device to the VM | `/vm.add-vsock` | `/schemas/VsockConfig` | `/schemas/PciDeviceInfo` | The VM is booted
|
||||
Remove device from the VM | `/vm.remove-device` | `/schemas/VmRemoveDevice` | N/A | The VM is booted
|
||||
Dump the VM counters | `/vm.counters` | N/A | `/schemas/VmCounters` | The VM is booted
|
||||
Action | Endpoint | Request Body | Response Body | Prerequisites
|
||||
---------------------------------|----------------|---------------------|-------------------|---------------------------
|
||||
Create the VM | `/vm.create` | `/schemas/VmConfig` | N/A | The VM is not created yet
|
||||
Delete the VM | `/vm.delete` | N/A | N/A | The VM is created but not booted
|
||||
Boot the VM | `/vm.boot` | N/A | N/A | The VM is created
|
||||
Shut the VM down | `/vm.shutdown` | N/A | N/A | The VM is booted
|
||||
Reboot the VM | `/vm.reboot` | N/A | N/A | The VM is booted
|
||||
Pause the VM | `/vm.pause` | N/A | N/A | The VM is booted
|
||||
Resume the VM | `/vm.resume` | N/A | N/A | The VM is paused
|
||||
Add/remove CPUs to/from the VM | `/vm.resize` | `/schemas/VmResize` | N/A | The VM is booted
|
||||
Remove memory from the VM | `/vm.resize` | `/schemas/VmResize` | N/A | The VM is booted
|
||||
Dump the VM information | `/vm.info` | N/A | `/schemas/VmInfo` | The VM is created
|
||||
|
||||
### REST API Examples
|
||||
|
||||
@@ -122,10 +114,10 @@ We want to create a virtual machine with the following characteristics:
|
||||
* 4 vCPUs
|
||||
* 1 GB of RAM
|
||||
* 1 virtio based networking interface
|
||||
* Direct kernel boot from a custom 5.6.0-rc4 Linux kernel located at
|
||||
* Direct kernel boot from a custom 5.5.0 Linux kernel located at
|
||||
`/opt/clh/kernel/vmlinux-virtio-fs-virtio-iommu`
|
||||
* Using a Ubuntu image as its root filesystem, located at
|
||||
`/opt/clh/images/focal-server-cloudimg-amd64.raw`
|
||||
* Using a Clear Linux image as its root filesystem, located at
|
||||
`/opt/clh/images/clear-30080-kvm.img`
|
||||
|
||||
```shell
|
||||
#!/bin/bash
|
||||
@@ -137,8 +129,8 @@ curl --unix-socket /tmp/cloud-hypervisor.sock -i \
|
||||
-d '{
|
||||
"cpus":{"boot_vcpus": 4, "max_vcpus": 4},
|
||||
"kernel":{"path":"/opt/clh/kernel/vmlinux-virtio-fs-virtio-iommu"},
|
||||
"cmdline":{"args":"console=ttyS0 console=hvc0 root=/dev/vda1 rw"},
|
||||
"disks":[{"path":"/opt/clh/images/focal-server-cloudimg-amd64.raw"}],
|
||||
"cmdline":{"args":"console=hvc0 reboot=k panic=1 nomodules i8042.noaux i8042.nomux i8042.nopnp i8042.dumbkbd root=/dev/vda3"},
|
||||
"disks":[{"path":"/opt/clh/images/clear-30080-kvm.img"}],
|
||||
"rng":{"src":"/dev/urandom"},
|
||||
"net":[{"ip":"192.168.10.10", "mask":"255.255.255.0", "mac":"12:34:56:78:90:01"}]
|
||||
}'
|
||||
@@ -275,7 +267,7 @@ are received and processed by the VMM control loop.
|
||||
In order for the VMM control loop to respond to any internal API command, it
|
||||
must be able to send a response back to the MPSC sender. For that purpose, all
|
||||
internal API command payload carry the [Sender](https://doc.rust-lang.org/std/sync/mpsc/struct.Sender.html)
|
||||
end of an [MPSC](https://doc.rust-lang.org/std/sync/mpsc/) channel.
|
||||
end of an [MPSC](https://doc.rust-lang.org/std/sync/mpsc/) channel.
|
||||
|
||||
The sender of any internal API command is therefore responsible for:
|
||||
|
||||
@@ -305,8 +297,8 @@ APIs work together, let's look at a complete VM creation flow, from the
|
||||
-d '{
|
||||
"cpus":{"boot_vcpus": 4, "max_vcpus": 4},
|
||||
"kernel":{"path":"/opt/clh/kernel/vmlinux-virtio-fs-virtio-iommu"},
|
||||
"cmdline":{"args":"console=ttyS0 console=hvc0 root=/dev/vda1 rw"},
|
||||
"disks":[{"path":"/opt/clh/images/focal-server-cloudimg-amd64.raw"}],
|
||||
"cmdline":{"args":"console=hvc0 reboot=k panic=1 nomodules i8042.noaux i8042.nomux i8042.nopnp i8042.dumbkbd root=/dev/vda3"},
|
||||
"disks":[{"path":"/opt/clh/images/clear-30080-kvm.img"}],
|
||||
"rng":{"src":"/dev/urandom"},
|
||||
"net":[{"ip":"192.168.10.10", "mask":"255.255.255.0", "mac":"12:34:56:78:90:01"}]
|
||||
}'
|
||||
@@ -355,7 +347,7 @@ APIs work together, let's look at a complete VM creation flow, from the
|
||||
} else {
|
||||
Err(ApiError::VmAlreadyCreated)
|
||||
};
|
||||
|
||||
|
||||
sender.send(response).map_err(Error::ApiResponseSend)?;
|
||||
}
|
||||
```
|
||||
@@ -376,3 +368,4 @@ APIs work together, let's look at a complete VM creation flow, from the
|
||||
user. This is abstracted by the
|
||||
[micro_http](https://github.com/firecracker-microvm/firecracker/tree/master/src/micro_http)
|
||||
crate.
|
||||
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
# How to build and run Cloud-hypervisor on AArch64
|
||||
|
||||
Cloud-hypervisor is partially enabled on AArch64 architecture.
|
||||
Although all features are not ready yet, you can begin to test Cloud-hypervisor on a AArch64 host by following this guide.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
On AArch64 machines, Cloud-hypervisor depends on an external library `libfdt-dev` for generating Flattened Device Tree (FDT).
|
||||
|
||||
The long-term plan is to replace `libfdt-dev` with some pure-Rust component to get rid of such dependency.
|
||||
|
||||
```bash
|
||||
sudo apt-get update
|
||||
sudo apt-get install libfdt-dev
|
||||
```
|
||||
|
||||
## Build
|
||||
|
||||
For Virtio devices, you can choose MMIO or PCI as transport option.
|
||||
|
||||
### MMIO
|
||||
|
||||
```bash
|
||||
cargo build --no-default-features --features mmio,kvm
|
||||
```
|
||||
|
||||
### PCI
|
||||
|
||||
Using PCI devices requires GICv3-ITS for MSI messaging. GICv3-ITS is very common in modern servers, but your machine happen to be old ones with GICv2(M) (like Raspberry Pi 4) or GICv3 without ITS, MMIO can still work.
|
||||
|
||||
```bash
|
||||
cargo build --no-default-features --features pci,kvm
|
||||
```
|
||||
|
||||
## Image
|
||||
|
||||
Download kernel binary and rootfs image from AWS.
|
||||
|
||||
```bash
|
||||
wget https://s3.amazonaws.com/spec.ccfc.min/img/aarch64/ubuntu_with_ssh/fsfiles/xenial.rootfs.ext4 -O rootfs.ext4
|
||||
wget https://s3.amazonaws.com/spec.ccfc.min/img/aarch64/ubuntu_with_ssh/kernel/vmlinux.bin -O kernel.bin
|
||||
```
|
||||
|
||||
## Containerized build
|
||||
|
||||
If you want to build and test Cloud Hypervisor without having to install all the required dependencies, you can also turn to the development script: dev_cli.sh.
|
||||
|
||||
To build the development container:
|
||||
|
||||
```bash
|
||||
./scripts/dev_cli.sh build-container
|
||||
```
|
||||
|
||||
To build Cloud-hypervisor in the container: (The default option for Virtio transport is MMIO.)
|
||||
|
||||
```bash
|
||||
./scripts/dev_cli.sh build
|
||||
```
|
||||
|
||||
## Run
|
||||
|
||||
Assuming you have built Cloud-hypervisor with the development container, a VM can be started with command:
|
||||
|
||||
```bash
|
||||
sudo build/cargo_target/aarch64-unknown-linux-gnu/debug/cloud-hypervisor --kernel kernel.bin --disk path=rootfs.ext4 --cmdline "keep_bootcon console=hvc0 reboot=k panic=1 pci=off root=/dev/vda rw" --cpus boot=4 --memory size=512M --serial file=serial.log --log-file log.log -vvv
|
||||
```
|
||||
|
||||
If the build was done out of the container, replace the binary path with `target/debug/cloud-hypervisor`.
|
||||
@@ -1,136 +1,96 @@
|
||||
# How to create a custom Ubuntu image
|
||||
# How to create a custom Clear Linux image
|
||||
|
||||
In the context of adding more utilities to the Ubuntu cloud image being used
|
||||
for integration testing, this quick guide details how to achieve the proper
|
||||
modification of an official Ubuntu cloud image.
|
||||
In the context of adding more utility to the cloudguest image being used
|
||||
for integration testing, this is a quick guide on how to achieve the creation
|
||||
of your own Clear Linux image using the official Clear Linux tooling.
|
||||
|
||||
## Prepare the environment
|
||||
|
||||
From the host, the goal is run a Clear Linux VM that will allow us to build
|
||||
the custom image we want.
|
||||
|
||||
```bash
|
||||
# Get latest CL version:
|
||||
IMG_VERSION=$(curl https://download.clearlinux.org/latest)
|
||||
# Get latest clear-kvm image:
|
||||
wget -P $HOME/workloads/ https://download.clearlinux.org/current/clear-${IMG_VERSION}-kvm.img.xz
|
||||
# Extract the image
|
||||
unxz $HOME/workloads/clear-${IMG_VERSION}-kvm.img.xz
|
||||
# Make sure cloud-hypervisor binary has CAP_NET_ADMIN capability set
|
||||
sudo setcap cap_net_admin+ep cloud-hypervisor
|
||||
# Boot cloud-hypervisor VM with the downloaded image
|
||||
./cloud-hypervisor -v --kernel $HOME/workloads/vmlinux --disk path=clear-${IMG_VERSION}-kvm.img --cmdline "console=ttyS0 console=hvc0 reboot=k panic=1 nomodules root=/dev/vda3 rw" --cpus 1 --memory size=4G --net tap=,mac=
|
||||
# Setup connectivity
|
||||
# First make sure to enable IP forwarding (disabled on Linux by default)
|
||||
sudo bash -c "echo 1 > /proc/sys/net/ipv4/ip_forward"
|
||||
# Retrieve the interface name and the gateway IP
|
||||
IFACE=$(ip route | grep default | awk -F 'dev' '{print $2}' | awk -F ' ' '{print $1}')
|
||||
GW=$(ip route | grep vmtap0 | awk -F ' ' '{print $1}')
|
||||
# Create a new masquerade rule to tag the packets going out
|
||||
sudo iptables -t nat -A POSTROUTING -s ${GW} -o ${IFACE} -j MASQUERADE
|
||||
```
|
||||
|
||||
## Create the image
|
||||
|
||||
Let's go through the steps on how to extend an official Ubuntu image. These
|
||||
steps can be applied to other distributions (with a few changes regarding
|
||||
package management).
|
||||
|
||||
### Get latest Ubuntu cloud image
|
||||
From the guest, we can now create the image.
|
||||
|
||||
```bash
|
||||
wget https://cloud-images.ubuntu.com/focal/current/focal-server-cloudimg-amd64.img
|
||||
# Setup connectivity
|
||||
sudo ip addr add 192.168.249.2/24 dev enp0s3
|
||||
sudo ip route add default via 192.168.249.1
|
||||
# Install necessary bundles
|
||||
sudo swupd bundle-add clr-installer
|
||||
sudo swupd bundle-add os-installer
|
||||
# Download and update cloudguest image configuration
|
||||
wget https://download.clearlinux.org/current/config/image/cloudguest.yaml
|
||||
sed -i '/size: \"864M\"/d' cloudguest.yaml
|
||||
sed -i 's/\"800M\"/\"2G\"/g' cloudguest.yaml
|
||||
sed -i 's/bootloader,/bootloader,\n iperf,/g' cloudguest.yaml
|
||||
sed -i 's/systemd-networkd-autostart/sysadmin-basic,\n systemd-networkd-autostart/g' cloudguest.yaml
|
||||
# Create the custom cloudguest image
|
||||
clr-installer -c cloudguest.yaml
|
||||
# Make the guest accessible through ssh
|
||||
sudo mkdir -p /etc/ssh
|
||||
sudo bash -c "echo 'PermitRootLogin yes' >> /etc/ssh/sshd_config"
|
||||
```
|
||||
|
||||
### Check the file format is QCOW2
|
||||
### Retrieve the image
|
||||
|
||||
Once the new image has been created and the guest is accessible through
|
||||
`ssh`, it is time to retrieve the image from the host.
|
||||
|
||||
```bash
|
||||
file focal-server-cloudimg-amd64.img
|
||||
focal-server-cloudimg-amd64.img: QEMU QCOW2 Image (v2), 2361393152 bytes
|
||||
```
|
||||
|
||||
### Convert QCOW2 into RAW
|
||||
|
||||
```bash
|
||||
qemu-img convert -p -f qcow2 -O raw focal-server-cloudimg-amd64.img focal-server-cloudimg-amd64.raw
|
||||
```
|
||||
|
||||
### Identify the Linux partition
|
||||
|
||||
The goal is to mount the image rootfs so that it can be modified as needed.
|
||||
That's why we need to identify where the Linux filesystem partition is located
|
||||
in the image.
|
||||
|
||||
```bash
|
||||
sudo fdisk -l focal-server-cloudimg-amd64.raw
|
||||
Disk focal-server-cloudimg-amd64.raw: 2.2 GiB, 2361393152 bytes, 4612096 sectors
|
||||
Units: sectors of 1 * 512 = 512 bytes
|
||||
Sector size (logical/physical): 512 bytes / 512 bytes
|
||||
I/O size (minimum/optimal): 512 bytes / 512 bytes
|
||||
Disklabel type: gpt
|
||||
Disk identifier: A1171ABA-2BEA-4218-A467-1B2B607E5953
|
||||
|
||||
Device Start End Sectors Size Type
|
||||
focal-server-cloudimg-amd64.raw1 227328 4612062 4384735 2.1G Linux filesystem
|
||||
focal-server-cloudimg-amd64.raw14 2048 10239 8192 4M BIOS boot
|
||||
focal-server-cloudimg-amd64.raw15 10240 227327 217088 106M EFI System
|
||||
|
||||
Partition table entries are not in disk order.
|
||||
```
|
||||
|
||||
### Mount the Linux partition
|
||||
|
||||
```bash
|
||||
mkdir -p /mnt
|
||||
sudo mount -o loop,offset=$((227328 * 512)) focal-server-cloudimg-amd64.raw /mnt
|
||||
```
|
||||
|
||||
### Change root directory
|
||||
|
||||
Changing the root directory will allow us to install new packages to the rootfs
|
||||
contained by the cloud image.
|
||||
|
||||
```bash
|
||||
sudo chroot /mnt
|
||||
mount -t proc proc /proc
|
||||
mount -t devpts devpts /dev/pts
|
||||
```
|
||||
|
||||
### Install needed packages
|
||||
|
||||
In the context Cloud-Hypervisor's integration tests, we need several utilities.
|
||||
Here is the way to install them for a Ubuntu image. This step is specific to
|
||||
Ubuntu distributions.
|
||||
|
||||
```bash
|
||||
apt update
|
||||
apt install fio iperf iperf3 socat
|
||||
```
|
||||
|
||||
### Remove snapd
|
||||
|
||||
This prevents snapd from trying to mount squashfs filesystem when the kernel
|
||||
might not support it. This might be the case when the image is used with direct
|
||||
kernel boot. This step is specific to Ubuntu distributions.
|
||||
|
||||
```bash
|
||||
apt remove --purge snapd
|
||||
```
|
||||
|
||||
### Cleanup the image
|
||||
|
||||
Leave no trace in the image before unmounting its content.
|
||||
|
||||
```bash
|
||||
umount /dev/pts
|
||||
umount /proc
|
||||
history -c
|
||||
exit
|
||||
umount /mnt
|
||||
```
|
||||
|
||||
### Rename the image
|
||||
|
||||
Renaming is important to identify this is a modified image.
|
||||
|
||||
```bash
|
||||
mv focal-server-cloudimg-amd64.raw focal-server-cloudimg-amd64-custom.raw
|
||||
```
|
||||
|
||||
### Create QCOW2 from RAW
|
||||
|
||||
Last step is to create the QCOW2 image back from the modified image.
|
||||
|
||||
```bash
|
||||
qemu-img convert -p -f raw -O qcow2 focal-server-cloudimg-amd64-custom.raw focal-server-cloudimg-amd64-custom.qcow2
|
||||
# Retrieve new image (this is a raw image)
|
||||
scp root@192.168.249.2:cloudguest.img .
|
||||
mv cloudguest.img clear-cloudguest-raw.img
|
||||
# Create the QCOW image from the RAW image
|
||||
qemu-img convert -p -f raw -O qcow2 clear-cloudguest-raw.img clear-cloudguest.img
|
||||
# Compress the QCOW image
|
||||
xz -k -T $(nproc) clear-cloudguest.img
|
||||
```
|
||||
|
||||
## Switch CI to use the new image
|
||||
|
||||
### Upload to Azure storage
|
||||
|
||||
The next step is to update both images (QCOW2 and RAW) stored as part of the
|
||||
Azure storage account, replacing them with the newly created ones. This will
|
||||
make these new images available from the integration tests. This is usually
|
||||
achieved through the web interface.
|
||||
The next step is to update the image stored as part of the Azure storage
|
||||
account, replacing it with the newly created image. This will make this
|
||||
new image available from the integration tests.
|
||||
This is usually achieved through the web interface.
|
||||
|
||||
### Update integration tests
|
||||
|
||||
Last step is about updating the integration tests to work with this new image.
|
||||
The key point is to identify where the Linux filesystem partition is located,
|
||||
as we might need to update the direct kernel boot command line, replacing
|
||||
`/dev/vda1` with the appropriate partition number.
|
||||
The key point is to identify the UUID of this new image so that it can be used
|
||||
directly from the tests.
|
||||
|
||||
Proceed as follow to determine this UUID:
|
||||
|
||||
```bash
|
||||
# Mount the image
|
||||
sudo mount -o loop,offset=$((2048 * 512)) clear-cloudguest-raw.img /mnt/
|
||||
# Identify UUID
|
||||
sudo cat /mnt/loader/entries/Clear-linux-kvm-*.conf | grep "root=PARTUUID="
|
||||
# Unmount the image
|
||||
sudo umount /mnt
|
||||
```
|
||||
|
||||
@@ -43,7 +43,7 @@ to easily grep for the tracing logs (e.g.
|
||||
```
|
||||
./target/debug/cloud-hypervisor \
|
||||
--kernel ~/rust-hypervisor-firmware/target/target/release/hypervisor-fw \
|
||||
--disk path=~/hypervisor/images/focal-server-cloudimg-amd64.raw \
|
||||
--disk path=~/hypervisor/images/clear-30080-kvm.img \
|
||||
--cpus 4 \
|
||||
--memory size=1024M \
|
||||
--rng \
|
||||
|
||||
12
docs/fs.md
12
docs/fs.md
@@ -52,28 +52,28 @@ Direct kernel boot option is preferred since we need to provide the custom kerne
|
||||
|
||||
Because _vhost-user_ expects a dedicated process (__virtiofsd__ in this case) to be able to access the guest RAM to communicate through the _virtqueues_ with the driver running in the guest, `--memory` option needs to be slightly modified. It needs to specify a backing file for the memory so that an external process can access it.
|
||||
|
||||
Assuming you have `focal-server-cloudimg-amd64.raw` and `custom-vmlinux.bin` on your system, here is the __cloud-hypervisor__ command you need to run:
|
||||
Assuming you have `clear-kvm.img` and `custom-vmlinux.bin` on your system, here is the __cloud-hypervisor__ command you need to run:
|
||||
```bash
|
||||
./cloud-hypervisor \
|
||||
--cpus 4 \
|
||||
--memory "size=512,file=/dev/shm" \
|
||||
--disk path=focal-server-cloudimg-amd64.raw \
|
||||
--disk path=clear-kvm.img \
|
||||
--kernel custom-vmlinux.bin \
|
||||
--cmdline "console=ttyS0 console=hvc0 root=/dev/vda1 rw" \
|
||||
--fs tag=myfs,socket=/tmp/virtiofs,num_queues=1,queue_size=512
|
||||
--cmdline "console=ttyS0 reboot=k panic=1 nomodules root=/dev/vda3" \
|
||||
--fs tag=myfs,sock=/tmp/virtiofs,num_queues=1,queue_size=512
|
||||
```
|
||||
|
||||
By default, DAX is enabled with a cache window of 8GiB. You can specify a custom size (let's say 4GiB for this example) for the cache by explicitly setting DAX and the cache size:
|
||||
|
||||
```bash
|
||||
--fs tag=virtiofs,socket=/tmp/virtiofs,num_queues=1,queue_size=512,dax=on,cache_size=4G
|
||||
--fs tag=virtiofs,sock=/tmp/virtiofs,num_queues=1,queue_size=512,dax=on,cache_size=4G
|
||||
|
||||
```
|
||||
|
||||
In case you don't want to use a shared window of cache to pass the shared files content, this means you will have to explicitly disable DAX with `dax=off`. Note that in this case, the `cache_size` parameter will be ignored.
|
||||
|
||||
```bash
|
||||
--fs tag=virtiofs,socket=/tmp/virtiofs,num_queues=1,queue_size=512,dax=off
|
||||
--fs tag=virtiofs,sock=/tmp/virtiofs,num_queues=1,queue_size=512,dax=off
|
||||
|
||||
```
|
||||
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
# Fuzzing in Cloud Hypervisor
|
||||
|
||||
Cloud Hypervisor uses [cargo-fuzz](https://github.com/rust-fuzz/cargo-fuzz) for fuzzing individual components.
|
||||
|
||||
The fuzzers are are in the `fuzz/fuzz_targets` directory
|
||||
|
||||
## Preparation
|
||||
|
||||
Switch to nightly:
|
||||
|
||||
````
|
||||
rustup override set nightly
|
||||
````
|
||||
|
||||
Install `cargo fuzz`:
|
||||
|
||||
```
|
||||
cargo install cargo-fuzz
|
||||
```
|
||||
|
||||
## Running the fuzzers
|
||||
|
||||
e.g. To run the `qcow` fuzzer using all available CPUs:
|
||||
|
||||
```
|
||||
cargo fuzz run qcow -j `nproc`
|
||||
```
|
||||
|
||||
## Adding a new fuzzer
|
||||
|
||||
```
|
||||
cargo fuzz add <new_fuzzer>
|
||||
```
|
||||
|
||||
Inspiration for fuzzers can be found in [crosvm](https://chromium.googlesource.com/chromiumos/platform/crosvm/+/refs/heads/master/fuzz/)
|
||||
@@ -5,7 +5,9 @@ Currently Cloud Hypervisor only support hot plugging of CPU devices.
|
||||
## Kernel support
|
||||
|
||||
For hotplug on Cloud Hypervisor ACPI GED support is needed. This can either be achieved by turning on `CONFIG_ACPI_REDUCED_HARDWARE_ONLY`
|
||||
or by using this kernel patch (available in 5.5-rc1 and later): https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/patch/drivers/acpi/Makefile?id=ac36d37e943635fc072e9d4f47e40a48fbcdb3f0
|
||||
or by using this kernel patch (available in 5.5rc1 and later): https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/patch/drivers/acpi/Makefile?id=ac36d37e943635fc072e9d4f47e40a48fbcdb3f0
|
||||
|
||||
This patch is integrated into the Clear Linux KVM and cloudguest images.
|
||||
|
||||
## CPU Hot Plug
|
||||
|
||||
@@ -20,9 +22,8 @@ To use CPU hotplug start the VM with the number of max vCPUs greater than the nu
|
||||
$ pushd $CLOUDH
|
||||
$ sudo setcap cap_net_admin+ep ./cloud-hypervisor/target/release/cloud-hypervisor
|
||||
$ ./cloud-hypervisor/target/release/cloud-hypervisor \
|
||||
--kernel custom-vmlinux.bin \
|
||||
--cmdline "console=ttyS0 console=hvc0 root=/dev/vda1 rw" \
|
||||
--disk path=focal-server-cloudimg-amd64.raw \
|
||||
--kernel ./hypervisor-fw \
|
||||
--disk path=clear-31890-kvm.img \
|
||||
--cpus boot=4,max=8 \
|
||||
--memory size=1024M \
|
||||
--net "tap=,mac=,ip=,mask=" \
|
||||
@@ -74,9 +75,8 @@ To use memory hotplug start the VM specifying some size RAM in the "hotplug_size
|
||||
$ pushd $CLOUDH
|
||||
$ sudo setcap cap_net_admin+ep ./cloud-hypervisor/target/release/cloud-hypervisor
|
||||
$ ./cloud-hypervisor/target/release/cloud-hypervisor \
|
||||
--kernel custom-vmlinux.bin \
|
||||
--cmdline "console=ttyS0 console=hvc0 root=/dev/vda1 rw" \
|
||||
--disk path=focal-server-cloudimg-amd64.raw \
|
||||
--kernel ./hypervisor-fw \
|
||||
--disk path=clear-31890-kvm.img \
|
||||
--cpus boot=4,max=8 \
|
||||
--memory size=1024M,hotplug_size=8192M \
|
||||
--net "tap=,mac=,ip=,mask=" \
|
||||
@@ -91,7 +91,7 @@ Before issuing the API request it is necessary to run the following command insi
|
||||
root@ch-guest ~ # echo online | sudo tee /sys/devices/system/memory/auto_online_blocks
|
||||
```
|
||||
|
||||
To ask the VMM to add expand the RAM for the VM (request is in bytes):
|
||||
To ask the VMM to add expand the RAM for the VM:
|
||||
|
||||
```shell
|
||||
curl -H "Accept: application/json" -H "Content-Type: application/json" -i -XPUT --unix-socket /tmp/ch-socket -d "{ \"desired_vcpus\": 4, \"desired_ram\" : 3221225472}" http://localhost/api/v1/vm.resize
|
||||
@@ -110,4 +110,4 @@ Due to guest OS limitations is is necessary to ensure that amount of memory adde
|
||||
|
||||
The same API can also be used to reduce the desired RAM for a VM but the change will not be applied until the VM is rebooted.
|
||||
|
||||
Memory and CPU resizing can be combined together into the same HTTP API request.
|
||||
Memory and CPU resizing can be combined together into the same HTTP API request.
|
||||
@@ -1,55 +0,0 @@
|
||||
# Intel SGX
|
||||
|
||||
Intel® Software Guard Extensions (Intel® SGX) is an Intel technology designed
|
||||
to increase the security of application code and data. Cloud-Hypervisor supports
|
||||
SGX virtualization through KVM. Because SGX is built on hardware features that
|
||||
cannot be emulated in software, virtualizing SGX requires support in KVM and in
|
||||
the host kernel. The required Linux and KVM changes can be found in the
|
||||
[KVM SGX Tree](https://github.com/intel/kvm-sgx).
|
||||
|
||||
Utilizing SGX in the guest requires a kernel/OS with SGX support, e.g. a kernel
|
||||
buit using the [SGX Linux Development Tree](https://github.com/jsakkine-intel/linux-sgx.git)
|
||||
or the [KVM SGX Tree](https://github.com/intel/kvm-sgx). Running KVM SGX as the
|
||||
guest kernel allows nested virtualization of SGX.
|
||||
|
||||
For more information about SGX, please refer to the [SGX Homepage](https://software.intel.com/sgx).
|
||||
|
||||
For more information about SGX SDK and how to test SGX, please refer to the
|
||||
following [instructions](https://github.com/intel/linux-sgx).
|
||||
|
||||
## Cloud-Hypervisor support
|
||||
|
||||
Assuming the host exposes `/dev/sgx/virt_epc`, we can pass SGX enclaves through
|
||||
the guest.
|
||||
|
||||
In order to use SGX enclaves within a Cloud-Hypervisor VM, we must define one
|
||||
or several Enclave Page Cache (EPC) sections. Here is an example of a VM being
|
||||
created with 2 EPC sections, the first one being 64MiB with pre-allocated
|
||||
memory, the second one being 32MiB with no pre-allocated memory.
|
||||
|
||||
```bash
|
||||
./cloud-hypervisor \
|
||||
--cpus boot=1 \
|
||||
--memory size=1G \
|
||||
--disk path=focal-server-cloudimg-amd64.raw \
|
||||
--kernel bzImage \
|
||||
--cmdline "console=ttyS0 console=hvc0 root=/dev/vda1 rw" \
|
||||
--sgx-epc size=64M,prefault=on size=32M,prefault=off
|
||||
```
|
||||
|
||||
Once booted, and assuming your guest kernel contains the patches from the
|
||||
[KVM SGX Tree](https://github.com/intel/kvm-sgx), you can validate SGX devices
|
||||
have been correctly created under `/dev/sgx`:
|
||||
|
||||
```bash
|
||||
ls /dev/sgx
|
||||
enclave provision virt_epc
|
||||
```
|
||||
|
||||
From this point, it is possible to run any SGX application from the guest, as
|
||||
it will access `/dev/sgx/enclave` device to create dedicated SGX enclaves.
|
||||
|
||||
Note: There is only one contiguous SGX EPC region, which contains all SGX EPC
|
||||
sections. This region is exposed through ACPI and marked as reserved through
|
||||
the e820 table. It is treated yet as another device, which means it should
|
||||
appear at the end of the guest address space.
|
||||
@@ -88,9 +88,9 @@ virtual IOMMU:
|
||||
./cloud-hypervisor \
|
||||
--cpus 1 \
|
||||
--memory size=512M \
|
||||
--disk path=focal-server-cloudimg-amd64.raw,iommu=on \
|
||||
--disk path=clear-kvm.img,iommu=on \
|
||||
--kernel custom-bzImage \
|
||||
--cmdline "console=ttyS0 console=hvc0 root=/dev/vda1 rw" \
|
||||
--cmdline "console=ttyS0 root=/dev/vda3" \
|
||||
```
|
||||
|
||||
From a guest perspective, it is easy to verify if the device is protected by
|
||||
@@ -165,9 +165,9 @@ be consumed.
|
||||
./cloud-hypervisor \
|
||||
--cpus 1 \
|
||||
--memory size=8G,file=/dev/hugepages \
|
||||
--disk path=focal-server-cloudimg-amd64.raw \
|
||||
--disk path=clear-kvm.img \
|
||||
--kernel custom-bzImage \
|
||||
--cmdline "console=ttyS0 console=hvc0 root=/dev/vda1 rw hugepagesz=2M hugepages=2048" \
|
||||
--cmdline "console=ttyS0 root=/dev/vda3 hugepagesz=2M hugepages=2048" \
|
||||
--net tap=,mac=,iommu=on
|
||||
```
|
||||
|
||||
@@ -182,9 +182,9 @@ passing through is `0000:00:01.0`.
|
||||
./cloud-hypervisor \
|
||||
--cpus 1 \
|
||||
--memory size=8G,file=/dev/hugepages \
|
||||
--disk path=focal-server-cloudimg-amd64.raw \
|
||||
--disk path=clear-kvm.img \
|
||||
--kernel custom-bzImage \
|
||||
--cmdline "console=ttyS0 console=hvc0 root=/dev/vda1 rw kvm-intel.nested=1 vfio_iommu_type1.allow_unsafe_interrupts rw hugepagesz=2M hugepages=2048" \
|
||||
--cmdline "console=ttyS0 root=/dev/vda3 kvm-intel.nested=1 vfio_iommu_type1.allow_unsafe_interrupts rw hugepagesz=2M hugepages=2048" \
|
||||
--device path=/sys/bus/pci/devices/0000:00:01.0,iommu=on
|
||||
```
|
||||
|
||||
@@ -202,8 +202,8 @@ Last thing is to start the L2 guest with the huge pages memory backend.
|
||||
./cloud-hypervisor \
|
||||
--cpus 1 \
|
||||
--memory size=4G,file=/dev/hugepages \
|
||||
--disk path=focal-server-cloudimg-amd64.raw \
|
||||
--disk path=clear-kvm.img \
|
||||
--kernel custom-bzImage \
|
||||
--cmdline "console=ttyS0 console=hvc0 root=/dev/vda1 rw" \
|
||||
--cmdline "console=ttyS0 root=/dev/vda3" \
|
||||
--device path=/sys/bus/pci/devices/0000:00:04.0
|
||||
```
|
||||
|
||||
@@ -25,9 +25,9 @@ Use one `--net` command-line argument from cloud-hypervisor to specify the emula
|
||||
./cloud-hypervisor \
|
||||
--cpus 4 \
|
||||
--memory "size=512M" \
|
||||
--disk path=focal-server-cloudimg-amd64.raw \
|
||||
--disk path=my-root-disk.img \
|
||||
--kernel my-vmlinux.bin \
|
||||
--cmdline "console=ttyS0 console=hvc0 root=/dev/vda1 rw" \
|
||||
--cmdline "console=ttyS0 reboot=k panic=1 nomodules root=/dev/vda3" \
|
||||
--net tap=ich0,mac=a4:a1:c2:00:00:01,ip=192.168.4.2,mask=255.255.255.0,num_queues=2,queue_size=256 \
|
||||
tap=ich1,mac=a4:a1:c2:00:00:02,ip=10.0.1.2,mask=255.255.255.0,num_queues=2,queue_size=256
|
||||
```
|
||||
@@ -51,11 +51,6 @@ If the tap device is pre-created on host before guest boot up. To use multiple q
|
||||
[root@localhost ~]# ip tuntap add name ich0 mode tap multi_queue
|
||||
```
|
||||
|
||||
And the `--net` device should specify support for multiple queues. `num_queues` must be a multiple of 2 starting at least from 4 since multiple queues really means multiple queue pairs. We need at least 2 pairs for this configuration to be correct:
|
||||
|
||||
```bash
|
||||
--net tap=ich0,mac=a4:a1:c2:00:00:01,ip=192.168.4.2,mask=255.255.255.0,num_queues=4,queue_size=256
|
||||
```
|
||||
|
||||
## Configure the tap devices
|
||||
|
||||
|
||||
@@ -1,112 +0,0 @@
|
||||
# Snapshot and Restore
|
||||
|
||||
The goal for the snapshot/restore feature is to provide the user with the
|
||||
ability to take a snapshot of a previously paused virtual machine. This
|
||||
snapshot can be used as the base for creating new identical virtual machines,
|
||||
without the need to boot them from scratch. The restore codepath takes the
|
||||
snapshot and creates the exact same virtual machine, restoring the previously
|
||||
saved states. The new virtual machine is restored in a paused state, as it was
|
||||
before the snapshot was performed.
|
||||
|
||||
This feature is important for the project as it establishes the first step
|
||||
towards the support for live migration.
|
||||
|
||||
## Snapshot a Cloud-Hypervisor VM
|
||||
|
||||
First thing, we must run a Cloud-Hypervisor VM:
|
||||
|
||||
```bash
|
||||
./cloud-hypervisor \
|
||||
--api-socket /tmp/cloud-hypervisor.sock \
|
||||
--cpus boot=4 \
|
||||
--memory size=4G \
|
||||
--kernel bzImage \
|
||||
--cmdline "root=/dev/vda1 console=hvc0 rw" \
|
||||
--disk path=focal-server-cloudimg-amd64.raw
|
||||
```
|
||||
|
||||
At any point in time when the VM is running, one might choose to pause it:
|
||||
|
||||
```bash
|
||||
./ch-remote --api-socket=/tmp/cloud-hypervisor.sock pause
|
||||
```
|
||||
|
||||
Once paused, the VM can be safely snapshot into the specified directory and
|
||||
using the following command:
|
||||
|
||||
```bash
|
||||
./ch-remote --api-socket=/tmp/cloud-hypervisor.sock snapshot file:///home/foo/snapshot
|
||||
```
|
||||
|
||||
Given the directory was present on the system, the snapshot will succeed and
|
||||
it should contain the following files:
|
||||
|
||||
```bash
|
||||
ll /home/foo/snapshot/
|
||||
total 4194536
|
||||
drwxrwxr-x 2 foo bar 4096 Jul 22 11:50 ./
|
||||
drwxr-xr-x 47 foo bar 4096 Jul 22 11:47 ../
|
||||
-rw------- 1 foo bar 3221225472 Jul 22 11:19 memory-region-0
|
||||
-rw------- 1 foo bar 1073741824 Jul 22 11:19 memory-region-1
|
||||
-rw------- 1 foo bar 217853 Jul 22 11:19 vm.json
|
||||
```
|
||||
|
||||
In this particular example, we can observe that 2 memory region files were
|
||||
created. That is explained by the size of the guest RAM, which is 4GiB in this
|
||||
case. Because it exceeds 3GiB (which is where we can find a ~1GiB memory hole),
|
||||
Cloud-Hypervisor needs 2 distincts memory regions to be created. Each memory
|
||||
region's content is stored through a dedicated file, which explains why we end
|
||||
up with 2 different files, the first one containing the guest RAM range 0-3GiB
|
||||
and the second one containing the guest RAM range 3-4GiB.
|
||||
|
||||
`vm.json` gathers all information related to the virtual machine configuration
|
||||
and state. The configuration bits are used to create a similar virtual machine
|
||||
with the correct amount of CPUs, RAM, and other expected devices. The state
|
||||
bits are used to restore each component in the state it was left before the
|
||||
snapshot occured.
|
||||
|
||||
## Restore a Cloud-Hypervisor VM
|
||||
|
||||
Given that one has access to an existing snapshot in `/home/foo/snapshot`,
|
||||
it is possible to create a new VM based on this snapshot with the following
|
||||
command:
|
||||
|
||||
```bash
|
||||
./cloud-hypervisor \
|
||||
--api-socket /tmp/cloud-hypervisor.sock \
|
||||
--restore source_url=file:///home/foo/snapshot
|
||||
```
|
||||
|
||||
Or using two different commands from two terminals:
|
||||
|
||||
```bash
|
||||
# First terminal
|
||||
./cloud-hypervisor --api-socket /tmp/cloud-hypervisor.sock
|
||||
|
||||
# Second terminal
|
||||
./ch-remote --api-socket=/tmp/cloud-hypervisor.sock restore source_url=file:///home/foo/snapshot
|
||||
```
|
||||
|
||||
Remember the VM is restored in a `paused` state, which was the VM's state when
|
||||
it was snapshot. For this reason, one must explicitly `resume` the VM before to
|
||||
start using it.
|
||||
|
||||
```bash
|
||||
./ch-remote --api-socket=/tmp/cloud-hypervisor.sock resume
|
||||
```
|
||||
|
||||
At this point, the VM is fully restored and is identical to the VM which was
|
||||
snapshot earlier.
|
||||
|
||||
## Limitations
|
||||
|
||||
The support of snapshot/restore feature is still experimental, meaning one
|
||||
might still find some bugs associated with it.
|
||||
|
||||
Additionally, some devices and features don't support to be snapshot and
|
||||
restored yet:
|
||||
- `vhost-user` devices
|
||||
- `virtio-mem`
|
||||
- Intel SGX
|
||||
|
||||
VFIO devices are out of scope.
|
||||
@@ -66,10 +66,10 @@ takes the device's sysfs path as an argument. In our example it is
|
||||
```
|
||||
./target/debug/cloud-hypervisor \
|
||||
--kernel ~/vmlinux \
|
||||
--disk path=~/focal-server-cloudimg-amd64.raw \
|
||||
--disk path=~/clear-29160-kvm.img \
|
||||
--console off \
|
||||
--serial tty \
|
||||
--cmdline "console=ttyS0 root=/dev/vda1 rw" \
|
||||
--cmdline "console=ttyS0 reboot=k panic=1 nomodules i8042.noaux i8042.nomux i8042.nopnp i8042.dumbkbd root=/dev/vda3" \
|
||||
--cpus 4 \
|
||||
--memory size=512M \
|
||||
--device path=/sys/bus/pci/devices/0000:01:00.0/
|
||||
|
||||
@@ -1,104 +0,0 @@
|
||||
# How to test vhost-user-blk with SPDK
|
||||
|
||||
The purpose of this document is to illustrate how to test vhost-user-blk in Cloud Hypervisor with SPDK as the backend.
|
||||
|
||||
## Framework
|
||||
|
||||
It's a simple test to validate the block read/write between VM and block backend.
|
||||
```
|
||||
+----+----------+ +-------------+-----------+
|
||||
| | | | | |
|
||||
| |vhost-user|----------| vhost-user | dpdk |
|
||||
| |blk device| | port 1 | |
|
||||
| | | | | |
|
||||
| +----------+ +-------------+-----------+
|
||||
| | | |
|
||||
| vm | | spdk |
|
||||
| | | |
|
||||
+--+----------------------------------------------------+--+
|
||||
| | hugepages | |
|
||||
| +----------------------------------------------------+ |
|
||||
| |
|
||||
| host |
|
||||
| |
|
||||
+----------------------------------------------------------+
|
||||
```
|
||||
## Prerequisites
|
||||
|
||||
Prior to running the test, the following steps need to be performed.
|
||||
- Enable hugepages
|
||||
- Install SPDK
|
||||
|
||||
Here are some good references for detailing them.
|
||||
- spdk
|
||||
* https://spdk.io/doc/
|
||||
|
||||
## Test environment
|
||||
|
||||
The below test environment is based on ubuntu release(16.04.1 LTS), as for other system, please check related document.
|
||||
The test runs with multiple queue (MQ) support enabled, using 4 queues defined for both SPDK and the virtual machine.
|
||||
Here are the details on how the test can be run.
|
||||
|
||||
### The hugepages settings in host linux
|
||||
Add "default_hugepagesz=1G hugepagesz=1G hugepages=2" into host linux cmdline.
|
||||
As for how to change Ubuntu linux cmdline in grub file, please ref below link:
|
||||
https://www.ostechnix.com/configure-grub-2-boot-loader-settings-ubuntu-16-04/
|
||||
reboot Ubuntu
|
||||
sudo mount -t hugetlbfs -o pagesize=1G none /dev/hugepages
|
||||
|
||||
### Download the SPDK code
|
||||
git clone https://github.com/spdk/spdk
|
||||
cd spdk
|
||||
git submodule update --init
|
||||
|
||||
### Create the build dep
|
||||
./scripts/pkgdep.sh
|
||||
|
||||
### Build spdk
|
||||
./configure
|
||||
make
|
||||
|
||||
### Set the SPDk environment
|
||||
sudo HUGEMEM=2048 scripts/setup.sh
|
||||
sudo ./app/vhost/vhost -S /var/tmp -s 1024 -m 0x3 &
|
||||
|
||||
### Create 512M block device
|
||||
sudo scripts/rpc.py bdev_malloc_create 512 512 -b Malloc0
|
||||
sudo scripts/rpc.py vhost_create_blk_controller --cpumask 0x1 vhost.1 Malloc0
|
||||
|
||||
_Launch the VM_
|
||||
|
||||
VMs run in client mode. They connect to the socket created by the `dpdkvhostuser` in the SPDK backend.
|
||||
```bash
|
||||
# From the test terminal. We need to create one vhost-user-blk device for the --disk.
|
||||
./cloud-hypervisor \
|
||||
--cpus boot=4 \
|
||||
--memory size=1024M,hugepages=on \
|
||||
--kernel linux/arch/x86/boot/compressed/vmlinux.bin \
|
||||
--cmdline "console=ttyS0 root=/dev/vda1 rw iommu=off" \
|
||||
--disk path=images/focal-server-cloudimg-amd64.raw vhost_user=true,socket=/var/tmp/vhost.1,num_queues=4,queue_size=128 \
|
||||
--console off \
|
||||
--serial tty \
|
||||
--rng
|
||||
```
|
||||
|
||||
```bash
|
||||
# How to test the vhost-user-blk device with SPDK backend
|
||||
login in guest
|
||||
|
||||
# Use lsblk command to find out vhost-user-blk device
|
||||
lsblk
|
||||
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
|
||||
vda 252:0 0 2.2G 0 disk
|
||||
├─vda1 252:1 0 2.1G 0 part /
|
||||
├─vda14 252:14 0 4M 0 part
|
||||
└─vda15 252:15 0 106M 0 part /boot/efi
|
||||
vdb 253:16 0 512M 0 disk
|
||||
|
||||
The vhost-user-blk device is /dev/vdb
|
||||
|
||||
# How to do simple read/write test
|
||||
dd if=/dev/vdb of=/dev/null bs=2M iflag=direct
|
||||
dd of=/dev/vdb if=/dev/zero bs=2M oflag=direct count=256
|
||||
|
||||
If you want to do fio test, please install fio binary into guest. The detailed info is not listed here.
|
||||
@@ -1,132 +0,0 @@
|
||||
# How to test Vhost-user net with OpenVSwitch/DPDK
|
||||
|
||||
The purpose of this document is to illustrate how to test vhost-user-net in cloud-hypervisor with OVS/DPDK as the backend.
|
||||
|
||||
## Framework
|
||||
|
||||
It's a simple test to validate the communication between two virtual machine, connecting them to vhost-user ports respectively provided by `OVS/DPDK`.
|
||||
```
|
||||
+----+----------+ +-------------+-----------+-------------+ +----------+----+
|
||||
| | | | | | | | | |
|
||||
| |vhost-user|----------| vhost-user | ovs | vhost-user |----------|vhost-user| |
|
||||
| |net device| | port 1 | | port 2 | |net device| |
|
||||
| | | | | | | | | |
|
||||
| +----------+ +-------------+-----------+-------------+ +----------+ |
|
||||
| | | | | |
|
||||
|vm1 | | dpdk | | vm2 |
|
||||
| | | | | |
|
||||
+--+---------------------------------------------------------------------------------------------+--+
|
||||
| | hugepages | |
|
||||
| +---------------------------------------------------------------------------------------------+ |
|
||||
| |
|
||||
| host |
|
||||
| |
|
||||
+---------------------------------------------------------------------------------------------------+
|
||||
```
|
||||
## Prerequisites
|
||||
|
||||
Prior to running the test, the following steps need to be performed.
|
||||
- Enable hugepages
|
||||
- Install DPDK
|
||||
- Install OVS
|
||||
|
||||
Here are some good references for detailing them.
|
||||
- Red Hat
|
||||
* https://wiki.qemu.org/Documentation/vhost-user-ovs-dpdk
|
||||
- Ubuntu server
|
||||
* https://help.ubuntu.com/lts/serverguide/DPDK.html
|
||||
* https://software.intel.com/en-us/articles/set-up-open-vswitch-with-dpdk-on-ubuntu-server
|
||||
|
||||
## Test
|
||||
The test runs with multiple queue (MQ) support enabled, using 2 pairs of TX/RX queues defined for both OVS and the virtual machine. Here are the details on how the test can be run.
|
||||
|
||||
_Setup OVS_
|
||||
|
||||
`ovs_test.sh` is created to setup and start OVS. OVS will provide the `dpdkvhostuser` backend running in server mode.
|
||||
```bash
|
||||
mkdir -p /var/run/openvswitch
|
||||
modprobe openvswitch
|
||||
killall ovsdb-server ovs-vswitchd
|
||||
rm -f /var/run/openvswitch/vhost-user*
|
||||
rm -f /etc/openvswitch/conf.db
|
||||
export DB_SOCK=/var/run/openvswitch/db.sock
|
||||
ovsdb-tool create /etc/openvswitch/conf.db /usr/share/openvswitch/vswitch.ovsschema
|
||||
ovsdb-server --remote=punix:$DB_SOCK --remote=db:Open_vSwitch,Open_vSwitch,manager_options --pidfile --detach
|
||||
ovs-vsctl --no-wait init
|
||||
ovs-vsctl --no-wait set Open_vSwitch . other_config:dpdk-lcore-mask=0xf
|
||||
ovs-vsctl --no-wait set Open_vSwitch . other_config:dpdk-socket-mem=1024
|
||||
ovs-vsctl --no-wait set Open_vSwitch . other_config:dpdk-init=true
|
||||
ovs-vsctl --no-wait set Open_vSwitch . other_config:pmd-cpu-mask=0xf
|
||||
ovs-vswitchd unix:$DB_SOCK --pidfile --detach --log-file=/var/log/openvswitch/ovs-vswitchd.log
|
||||
ovs-vsctl add-br ovsbr0 -- set bridge ovsbr0 datapath_type=netdev
|
||||
ovs-vsctl add-port ovsbr0 vhost-user1 -- set Interface vhost-user1 type=dpdkvhostuser
|
||||
ovs-vsctl add-port ovsbr0 vhost-user2 -- set Interface vhost-user2 type=dpdkvhostuser
|
||||
ovs-vsctl set Interface vhost-user1 options:n_rxq=2
|
||||
ovs-vsctl set Interface vhost-user2 options:n_rxq=2
|
||||
```
|
||||
_Run ovs_test.sh_
|
||||
```bash
|
||||
./ovs_test.sh
|
||||
```
|
||||
|
||||
_Launch the VMs_
|
||||
|
||||
VMs run in client mode. They connect to the socket created by the `dpdkvhostuser` backend.
|
||||
```bash
|
||||
# From one terminal. We need to give the cloud-hypervisor binary the NET_ADMIN capabilities for it to set TAP interfaces up on the host.
|
||||
./cloud-hypervisor \
|
||||
--cpus boot=2 \
|
||||
--memory size=512M,hugepages=on \
|
||||
--kernel vmlinux \
|
||||
--cmdline "console=ttyS0 console=hvc0 root=/dev/vda1 rw" \
|
||||
--disk path=focal-server-cloudimg-amd64.raw \
|
||||
--net mac=52:54:00:02:d9:01,vhost_user=true,socket=/var/run/openvswitch/vhost-user1,num_queues=4
|
||||
|
||||
# From another terminal. We need to give the cloud-hypervisor binary the NET_ADMIN capabilities for it to set TAP interfaces up on the host.
|
||||
./cloud-hypervisor \
|
||||
--cpus boot=2 \
|
||||
--memory size=512M,hugepages=on \
|
||||
--kernel vmlinux \
|
||||
--cmdline "console=ttyS0 console=hvc0 root=/dev/vda1 rw" \
|
||||
--disk path=focal-server-cloudimg-amd64.raw \
|
||||
--net "mac=52:54:20:11:C5:02,vhost_user=true,socket=/var/run/openvswitch/vhost-user2,num_queues=4"
|
||||
```
|
||||
|
||||
_Setup VM1_
|
||||
```bash
|
||||
# From inside the guest
|
||||
sudo ip addr add 172.100.0.1/24 dev enp0s3
|
||||
```
|
||||
|
||||
_Setup VM2_
|
||||
```bash
|
||||
# From inside the guest
|
||||
sudo ip addr add 172.100.0.2/24 dev enp0s3
|
||||
```
|
||||
|
||||
_Ping VM1 from VM2_
|
||||
```bash
|
||||
# From inside the guest
|
||||
sudo ping 172.100.0.1
|
||||
```
|
||||
|
||||
_Ping VM2 from VM1_
|
||||
```bash
|
||||
# From inside the guest
|
||||
sudo ping 172.100.0.2
|
||||
```
|
||||
|
||||
__Result:__ At this point, VM1 and VM2 can ping each other successfully. We can now run `iperf3` test.
|
||||
|
||||
_Run VM1 as server_
|
||||
```bash
|
||||
# From inside the guest
|
||||
iperf3 -s -p 4444
|
||||
```
|
||||
|
||||
_Run VM2 as client_
|
||||
```bash
|
||||
# From inside the guest
|
||||
iperf3 -c 172.100.0.1 -t 30 -p 4444 &
|
||||
```
|
||||
|
||||
4
fuzz/.gitignore
vendored
4
fuzz/.gitignore
vendored
@@ -1,4 +0,0 @@
|
||||
|
||||
target
|
||||
corpus
|
||||
artifacts
|
||||
@@ -1,41 +0,0 @@
|
||||
|
||||
[package]
|
||||
name = "cloud-hypervisor-fuzz"
|
||||
version = "0.0.0"
|
||||
authors = ["Automatically generated"]
|
||||
publish = false
|
||||
edition = "2018"
|
||||
|
||||
[package.metadata]
|
||||
cargo-fuzz = true
|
||||
|
||||
[dependencies]
|
||||
libc = "0.2.72"
|
||||
libfuzzer-sys = "0.3"
|
||||
qcow = { path = "../qcow" }
|
||||
virtio-devices = { path = "../virtio-devices" }
|
||||
vmm-sys-util = ">=0.3.1"
|
||||
vm-virtio = { path = "../vm-virtio" }
|
||||
vm-memory = "0.2.1"
|
||||
|
||||
[dependencies.cloud-hypervisor]
|
||||
path = ".."
|
||||
|
||||
[patch.crates-io]
|
||||
vm-memory = { git = "https://github.com/cloud-hypervisor/vm-memory", branch = "ch" }
|
||||
|
||||
# Prevent this from interfering with workspaces
|
||||
[workspace]
|
||||
members = ["."]
|
||||
|
||||
[[bin]]
|
||||
name = "qcow"
|
||||
path = "fuzz_targets/qcow.rs"
|
||||
test = false
|
||||
doc = false
|
||||
|
||||
[[bin]]
|
||||
name = "block"
|
||||
path = "fuzz_targets/block.rs"
|
||||
test = false
|
||||
doc = false
|
||||
@@ -1,136 +0,0 @@
|
||||
// Copyright 2018 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#![no_main]
|
||||
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use std::ffi;
|
||||
use std::fs::File;
|
||||
use std::io::{self, Cursor, Read, Seek, SeekFrom};
|
||||
use std::mem::size_of;
|
||||
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use virtio_devices::{Block, VirtioDevice, VirtioInterrupt, VirtioInterruptType};
|
||||
use vm_memory::{Bytes, GuestAddress, GuestMemoryAtomic, GuestMemoryMmap};
|
||||
use vm_virtio::Queue;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
const MEM_SIZE: u64 = 256 * 1024 * 1024;
|
||||
const DESC_SIZE: u64 = 16; // Bytes in one virtio descriptor.
|
||||
const QUEUE_SIZE: u16 = 16; // Max entries in the queue.
|
||||
const CMD_SIZE: usize = 16; // Bytes in the command.
|
||||
|
||||
fuzz_target!(|bytes| {
|
||||
let size_u64 = size_of::<u64>();
|
||||
let mem = GuestMemoryMmap::from_ranges(&[(GuestAddress(0), MEM_SIZE as usize)]).unwrap();
|
||||
|
||||
// The fuzz data is interpreted as:
|
||||
// starting index 8 bytes
|
||||
// command location 8 bytes
|
||||
// command 16 bytes
|
||||
// descriptors circular buffer 16 bytes * 3
|
||||
if bytes.len() < 4 * size_u64 {
|
||||
// Need an index to start.
|
||||
return;
|
||||
}
|
||||
|
||||
let mut data_image = Cursor::new(bytes);
|
||||
|
||||
let first_index = read_u64(&mut data_image);
|
||||
if first_index > MEM_SIZE / DESC_SIZE {
|
||||
return;
|
||||
}
|
||||
let first_offset = first_index * DESC_SIZE;
|
||||
if first_offset as usize + size_u64 > bytes.len() {
|
||||
return;
|
||||
}
|
||||
|
||||
let command_addr = read_u64(&mut data_image);
|
||||
if command_addr > MEM_SIZE - CMD_SIZE as u64 {
|
||||
return;
|
||||
}
|
||||
if mem
|
||||
.write_slice(
|
||||
&bytes[2 * size_u64..(2 * size_u64) + CMD_SIZE],
|
||||
GuestAddress(command_addr as u64),
|
||||
)
|
||||
.is_err()
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
data_image.seek(SeekFrom::Start(first_offset)).unwrap();
|
||||
let desc_table = read_u64(&mut data_image);
|
||||
|
||||
if mem
|
||||
.write_slice(&bytes[32..], GuestAddress(desc_table as u64))
|
||||
.is_err()
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let mut q = Queue::new(QUEUE_SIZE);
|
||||
q.ready = true;
|
||||
q.size = QUEUE_SIZE / 2;
|
||||
q.max_size = QUEUE_SIZE;
|
||||
|
||||
let queue_evts: Vec<EventFd> = vec![EventFd::new(0).unwrap()];
|
||||
let queue_fd = queue_evts[0].as_raw_fd();
|
||||
let queue_evt = unsafe { EventFd::from_raw_fd(libc::dup(queue_fd)) };
|
||||
|
||||
let shm = memfd_create(&ffi::CString::new("fuzz").unwrap(), 0).unwrap();
|
||||
let disk_file: File = unsafe { File::from_raw_fd(shm) };
|
||||
let raw_img = qcow::RawFile::new(disk_file, false);
|
||||
|
||||
let mut block = Block::new(
|
||||
"tmp".to_owned(),
|
||||
raw_img,
|
||||
PathBuf::from(""),
|
||||
false,
|
||||
false,
|
||||
2,
|
||||
256,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
block
|
||||
.activate(
|
||||
GuestMemoryAtomic::new(mem),
|
||||
Arc::new(NoopVirtioInterrupt {}),
|
||||
vec![q],
|
||||
queue_evts,
|
||||
)
|
||||
.ok();
|
||||
|
||||
queue_evt.write(77).unwrap(); // Rings the doorbell, any byte will do.
|
||||
});
|
||||
|
||||
fn read_u64<T: Read>(readable: &mut T) -> u64 {
|
||||
let mut buf = [0u8; size_of::<u64>()];
|
||||
readable.read_exact(&mut buf[..]).unwrap();
|
||||
u64::from_le_bytes(buf)
|
||||
}
|
||||
|
||||
fn memfd_create(name: &ffi::CStr, flags: u32) -> Result<RawFd, io::Error> {
|
||||
let res = unsafe { libc::syscall(libc::SYS_memfd_create, name.as_ptr(), flags) };
|
||||
|
||||
if res < 0 {
|
||||
Err(io::Error::last_os_error())
|
||||
} else {
|
||||
Ok(res as RawFd)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NoopVirtioInterrupt {}
|
||||
|
||||
impl VirtioInterrupt for NoopVirtioInterrupt {
|
||||
fn trigger(
|
||||
&self,
|
||||
_int_type: &VirtioInterruptType,
|
||||
_queue: Option<&Queue>,
|
||||
) -> std::result::Result<(), std::io::Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
// Copyright 2018 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
|
||||
#![no_main]
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use qcow::{QcowFile, RawFile};
|
||||
use std::ffi;
|
||||
use std::fs::File;
|
||||
use std::io::{self, Cursor, Read, Seek, SeekFrom, Write};
|
||||
use std::mem::size_of;
|
||||
use std::os::unix::io::{FromRawFd, RawFd};
|
||||
|
||||
// Take the first 64 bits of data as an address and the next 64 bits as data to
|
||||
// store there. The rest of the data is used as a qcow image.
|
||||
fuzz_target!(|bytes| {
|
||||
if bytes.len() < 16 {
|
||||
// Need an address and data, each are 8 bytes.
|
||||
return;
|
||||
}
|
||||
let mut disk_image = Cursor::new(bytes);
|
||||
let addr = read_u64(&mut disk_image);
|
||||
let value = read_u64(&mut disk_image);
|
||||
let shm = memfd_create(&ffi::CString::new("fuzz").unwrap(), 0).unwrap();
|
||||
let mut disk_file: File = unsafe { File::from_raw_fd(shm) };
|
||||
disk_file.write_all(&bytes[16..]).unwrap();
|
||||
disk_file.seek(SeekFrom::Start(0)).unwrap();
|
||||
if let Ok(mut qcow) = QcowFile::from(RawFile::new(disk_file, false)) {
|
||||
if qcow.seek(SeekFrom::Start(addr)).is_ok() {
|
||||
let _ = qcow.write_all(&value.to_le_bytes());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
fn read_u64<T: Read>(readable: &mut T) -> u64 {
|
||||
let mut buf = [0u8; size_of::<u64>()];
|
||||
readable.read_exact(&mut buf[..]).unwrap();
|
||||
u64::from_le_bytes(buf)
|
||||
}
|
||||
|
||||
fn memfd_create(name: &ffi::CStr, flags: u32) -> Result<RawFd, io::Error> {
|
||||
let res = unsafe { libc::syscall(libc::SYS_memfd_create, name.as_ptr(), flags) };
|
||||
|
||||
if res < 0 {
|
||||
Err(io::Error::last_os_error())
|
||||
} else {
|
||||
Ok(res as RawFd)
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
[package]
|
||||
name = "hypervisor"
|
||||
version = "0.1.0"
|
||||
authors = ["Muminul Islam <muislam@microsoft.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[features]
|
||||
kvm = []
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
thiserror = "1.0"
|
||||
libc = "0.2.74"
|
||||
log = "0.4.11"
|
||||
kvm-ioctls = { git = "https://github.com/cloud-hypervisor/kvm-ioctls", branch = "ch" }
|
||||
kvm-bindings = { git = "https://github.com/cloud-hypervisor/kvm-bindings", branch = "ch", features = ["with-serde", "fam-wrappers"] }
|
||||
serde = {version = ">=1.0.27", features = ["rc"] }
|
||||
serde_derive = ">=1.0.27"
|
||||
serde_json = ">=1.0.9"
|
||||
vm-memory = { version = "0.2.0", features = ["backend-mmap", "backend-atomic"] }
|
||||
vmm-sys-util = { version = ">=0.5.0", features = ["with-serde"] }
|
||||
|
||||
[dependencies.linux-loader]
|
||||
git = "https://github.com/rust-vmm/linux-loader"
|
||||
features = ["elf", "bzimage"]
|
||||
@@ -1,15 +0,0 @@
|
||||
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
//
|
||||
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
//
|
||||
// Copyright © 2019 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
|
||||
//
|
||||
// Copyright © 2020, Microsoft Corporation
|
||||
//
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub mod x86;
|
||||
@@ -1,36 +0,0 @@
|
||||
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
//
|
||||
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
//
|
||||
// Copyright © 2019 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
|
||||
//
|
||||
// Copyright © 2020, Microsoft Corporation
|
||||
//
|
||||
|
||||
#[allow(non_upper_case_globals)]
|
||||
#[allow(non_camel_case_types)]
|
||||
#[allow(non_snake_case)]
|
||||
#[allow(non_upper_case_globals)]
|
||||
#[allow(unused)]
|
||||
#[allow(
|
||||
clippy::unreadable_literal,
|
||||
clippy::redundant_static_lifetimes,
|
||||
clippy::trivially_copy_pass_by_ref,
|
||||
clippy::useless_transmute,
|
||||
clippy::should_implement_trait,
|
||||
clippy::transmute_ptr_to_ptr,
|
||||
clippy::unreadable_literal,
|
||||
clippy::redundant_static_lifetimes
|
||||
)]
|
||||
pub mod msr_index;
|
||||
|
||||
// MTRR constants
|
||||
pub const MTRR_ENABLE: u64 = 0x800; // IA32_MTRR_DEF_TYPE MSR: E (MTRRs enabled) flag, bit 11
|
||||
pub const MTRR_MEM_TYPE_WB: u64 = 0x6;
|
||||
|
||||
// IOAPIC pins
|
||||
pub const NUM_IOAPIC_PINS: usize = 24;
|
||||
@@ -1,312 +0,0 @@
|
||||
// Copyright © 2019 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
|
||||
//
|
||||
// Copyright © 2020, Microsoft Corporation
|
||||
//
|
||||
// Copyright 2018-2019 CrowdStrike, Inc.
|
||||
//
|
||||
//
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
use crate::aarch64::VcpuInit;
|
||||
use crate::{CpuState, MpState};
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use crate::x86_64::{
|
||||
CpuId, ExtendedControlRegisters, FpuState, LapicState, MsrEntries, SpecialRegisters,
|
||||
StandardRegisters, VcpuEvents, Xsave,
|
||||
};
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
///
|
||||
/// Enum for CPU error
|
||||
pub enum HypervisorCpuError {
|
||||
///
|
||||
/// Setting standard registers error
|
||||
///
|
||||
#[error("Failed to set standard register: {0}")]
|
||||
SetStandardRegs(#[source] anyhow::Error),
|
||||
///
|
||||
/// Setting standard registers error
|
||||
///
|
||||
#[error("Failed to get standard registers: {0}")]
|
||||
GetStandardRegs(#[source] anyhow::Error),
|
||||
///
|
||||
/// Setting special register error
|
||||
///
|
||||
#[error("Failed to set special registers: {0}")]
|
||||
SetSpecialRegs(#[source] anyhow::Error),
|
||||
///
|
||||
/// Getting standard register error
|
||||
///
|
||||
#[error("Failed to get special registers: {0}")]
|
||||
GetSpecialRegs(#[source] anyhow::Error),
|
||||
///
|
||||
/// Setting floating point registers error
|
||||
///
|
||||
#[error("Failed to set special register: {0}")]
|
||||
SetFloatingPointRegs(#[source] anyhow::Error),
|
||||
///
|
||||
/// Getting floating point register error
|
||||
///
|
||||
#[error("Failed to get special register: {0}")]
|
||||
GetFloatingPointRegs(#[source] anyhow::Error),
|
||||
///
|
||||
/// Setting Cpuid error
|
||||
///
|
||||
#[error("Failed to set Cpuid: {0}")]
|
||||
SetCpuid(#[source] anyhow::Error),
|
||||
///
|
||||
/// Getting Cpuid error
|
||||
///
|
||||
#[error("Failed to get Cpuid: {0}")]
|
||||
GetCpuid(#[source] anyhow::Error),
|
||||
///
|
||||
/// Setting lapic state error
|
||||
///
|
||||
#[error("Failed to set Lapic state: {0}")]
|
||||
SetLapicState(#[source] anyhow::Error),
|
||||
///
|
||||
/// Getting Lapic state error
|
||||
///
|
||||
#[error("Failed to get Lapic state: {0}")]
|
||||
GetlapicState(#[source] anyhow::Error),
|
||||
///
|
||||
/// Setting MSR entries error
|
||||
///
|
||||
#[error("Failed to set Msr entries: {0}")]
|
||||
SetMsrEntries(#[source] anyhow::Error),
|
||||
///
|
||||
/// Getting Msr entries error
|
||||
///
|
||||
#[error("Failed to get Msr entries: {0}")]
|
||||
GetMsrEntries(#[source] anyhow::Error),
|
||||
///
|
||||
/// Setting MSR entries error
|
||||
///
|
||||
#[error("Failed to set MP state: {0}")]
|
||||
SetMpState(#[source] anyhow::Error),
|
||||
///
|
||||
/// Getting Msr entries error
|
||||
///
|
||||
#[error("Failed to get MP state: {0}")]
|
||||
GetMpState(#[source] anyhow::Error),
|
||||
///
|
||||
/// Setting Saved Processor Extended States error
|
||||
///
|
||||
#[error("Failed to set Saved Processor Extended States: {0}")]
|
||||
SetXsaveState(#[source] anyhow::Error),
|
||||
///
|
||||
/// Getting Saved Processor Extended States error
|
||||
///
|
||||
#[error("Failed to get Saved Processor Extended States: {0}")]
|
||||
GetXsaveState(#[source] anyhow::Error),
|
||||
///
|
||||
/// Setting Extended Control Registers error
|
||||
///
|
||||
#[error("Failed to set Extended Control Registers: {0}")]
|
||||
SetXcsr(#[source] anyhow::Error),
|
||||
///
|
||||
/// Getting Extended Control Registers error
|
||||
///
|
||||
#[error("Failed to get Extended Control Registers: {0}")]
|
||||
GetXcsr(#[source] anyhow::Error),
|
||||
///
|
||||
/// Running Vcpu error
|
||||
///
|
||||
#[error("Failed to run vcpu: {0}")]
|
||||
RunVcpu(#[source] anyhow::Error),
|
||||
///
|
||||
/// Getting Vcpu events error
|
||||
///
|
||||
#[error("Failed to get Vcpu events: {0}")]
|
||||
GetVcpuEvents(#[source] anyhow::Error),
|
||||
///
|
||||
/// Setting Vcpu events error
|
||||
///
|
||||
#[error("Failed to set Vcpu events: {0}")]
|
||||
SetVcpuEvents(#[source] anyhow::Error),
|
||||
///
|
||||
/// Vcpu Init error
|
||||
///
|
||||
#[error("Failed to init vcpu: {0}")]
|
||||
VcpuInit(#[source] anyhow::Error),
|
||||
///
|
||||
/// Setting one reg error
|
||||
///
|
||||
#[error("Failed to init vcpu: {0}")]
|
||||
SetOneReg(#[source] anyhow::Error),
|
||||
///
|
||||
/// Getting one reg error
|
||||
///
|
||||
#[error("Failed to init vcpu: {0}")]
|
||||
GetOneReg(#[source] anyhow::Error),
|
||||
///
|
||||
/// Getting guest clock paused error
|
||||
///
|
||||
#[error("Failed to notify guest its clock was paused: {0}")]
|
||||
NotifyGuestClockPaused(#[source] anyhow::Error),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum VmExit<'a> {
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
IoOut(u16 /* port */, &'a [u8] /* data */),
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
IoIn(u16 /* port */, &'a mut [u8] /* data */),
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
IoapicEoi(u8 /* vector */),
|
||||
MmioRead(u64 /* address */, &'a mut [u8]),
|
||||
MmioWrite(u64 /* address */, &'a [u8]),
|
||||
Ignore,
|
||||
Reset,
|
||||
}
|
||||
|
||||
///
|
||||
/// Result type for returning from a function
|
||||
///
|
||||
pub type Result<T> = anyhow::Result<T, HypervisorCpuError>;
|
||||
///
|
||||
/// Trait to represent a generic Vcpu
|
||||
///
|
||||
pub trait Vcpu: Send + Sync {
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// Returns the vCPU general purpose registers.
|
||||
///
|
||||
fn get_regs(&self) -> Result<StandardRegisters>;
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// Sets the vCPU general purpose registers.
|
||||
///
|
||||
fn set_regs(&self, regs: &StandardRegisters) -> Result<()>;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// Returns the vCPU special registers.
|
||||
///
|
||||
fn get_sregs(&self) -> Result<SpecialRegisters>;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// Sets the vCPU special registers
|
||||
///
|
||||
fn set_sregs(&self, sregs: &SpecialRegisters) -> Result<()>;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// Returns the floating point state (FPU) from the vCPU.
|
||||
///
|
||||
fn get_fpu(&self) -> Result<FpuState>;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// Set the floating point state (FPU) of a vCPU
|
||||
///
|
||||
fn set_fpu(&self, fpu: &FpuState) -> Result<()>;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// X86 specific call to setup the CPUID registers.
|
||||
///
|
||||
fn set_cpuid2(&self, cpuid: &CpuId) -> Result<()>;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// X86 specific call to retrieve the CPUID registers.
|
||||
///
|
||||
fn get_cpuid2(&self, num_entries: usize) -> Result<CpuId>;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// Returns the state of the LAPIC (Local Advanced Programmable Interrupt Controller).
|
||||
///
|
||||
fn get_lapic(&self) -> Result<LapicState>;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// Sets the state of the LAPIC (Local Advanced Programmable Interrupt Controller).
|
||||
///
|
||||
fn set_lapic(&self, lapic: &LapicState) -> Result<()>;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// Returns the model-specific registers (MSR) for this vCPU.
|
||||
///
|
||||
fn get_msrs(&self, msrs: &mut MsrEntries) -> Result<usize>;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// Setup the model-specific registers (MSR) for this vCPU.
|
||||
///
|
||||
fn set_msrs(&self, msrs: &MsrEntries) -> Result<usize>;
|
||||
///
|
||||
/// Returns the vcpu's current "multiprocessing state".
|
||||
///
|
||||
fn get_mp_state(&self) -> Result<MpState>;
|
||||
///
|
||||
/// Sets the vcpu's current "multiprocessing state".
|
||||
///
|
||||
fn set_mp_state(&self, mp_state: MpState) -> Result<()>;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// X86 specific call that returns the vcpu's current "xsave struct".
|
||||
///
|
||||
fn get_xsave(&self) -> Result<Xsave>;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// X86 specific call that sets the vcpu's current "xsave struct".
|
||||
///
|
||||
fn set_xsave(&self, xsave: &Xsave) -> Result<()>;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// X86 specific call that returns the vcpu's current "xcrs".
|
||||
///
|
||||
fn get_xcrs(&self) -> Result<ExtendedControlRegisters>;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// X86 specific call that sets the vcpu's current "xcrs".
|
||||
///
|
||||
fn set_xcrs(&self, xcrs: &ExtendedControlRegisters) -> Result<()>;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// Returns currently pending exceptions, interrupts, and NMIs as well as related
|
||||
/// states of the vcpu.
|
||||
///
|
||||
fn get_vcpu_events(&self) -> Result<VcpuEvents>;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// Sets pending exceptions, interrupts, and NMIs as well as related states
|
||||
/// of the vcpu.
|
||||
///
|
||||
fn set_vcpu_events(&self, events: &VcpuEvents) -> Result<()>;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// Let the guest know that it has been paused, which prevents from
|
||||
/// potential soft lockups when being resumed.
|
||||
///
|
||||
fn notify_guest_clock_paused(&self) -> Result<()>;
|
||||
///
|
||||
/// Sets the type of CPU to be exposed to the guest and optional features.
|
||||
///
|
||||
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
|
||||
fn vcpu_init(&self, kvi: &VcpuInit) -> Result<()>;
|
||||
///
|
||||
/// Sets the value of one register for this vCPU.
|
||||
///
|
||||
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
|
||||
fn set_one_reg(&self, reg_id: u64, data: u64) -> Result<()>;
|
||||
///
|
||||
/// Sets the value of one register for this vCPU.
|
||||
///
|
||||
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
|
||||
fn get_one_reg(&self, reg_id: u64) -> Result<u64>;
|
||||
///
|
||||
/// Retrieve the vCPU state.
|
||||
/// This function is necessary to snapshot the VM
|
||||
///
|
||||
fn state(&self) -> Result<CpuState>;
|
||||
///
|
||||
/// Set the vCPU state.
|
||||
/// This function is required when restoring the VM
|
||||
///
|
||||
fn set_state(&self, state: &CpuState) -> Result<()>;
|
||||
|
||||
///
|
||||
/// Triggers the running of the current virtual CPU returning an exit reason.
|
||||
///
|
||||
fn run(&self) -> std::result::Result<VmExit, HypervisorCpuError>;
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
// Copyright © 2019 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
|
||||
//
|
||||
// Copyright © 2020, Microsoft Corporation
|
||||
//
|
||||
// Copyright 2018-2019 CrowdStrike, Inc.
|
||||
//
|
||||
// Copyright 2020, ARM Limited
|
||||
//
|
||||
|
||||
use crate::DeviceAttr;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
///
|
||||
/// Enum for device error
|
||||
pub enum HypervisorDeviceError {
|
||||
///
|
||||
/// Set device attribute error
|
||||
///
|
||||
#[error("Failed to set device attribute: {0}")]
|
||||
SetDeviceAttribute(#[source] anyhow::Error),
|
||||
}
|
||||
|
||||
///
|
||||
/// Result type for returning from a function
|
||||
///
|
||||
pub type Result<T> = std::result::Result<T, HypervisorDeviceError>;
|
||||
|
||||
///
|
||||
/// Trait to represent a device
|
||||
///
|
||||
/// This crate provides a hypervisor-agnostic interfaces for device
|
||||
///
|
||||
pub trait Device: Send + Sync + AsRawFd {
|
||||
/// Set device attribute.
|
||||
fn set_device_attr(&self, attr: &DeviceAttr) -> Result<()>;
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
// Copyright © 2019 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
|
||||
//
|
||||
// Copyright © 2020, Microsoft Corporation
|
||||
//
|
||||
// Copyright 2018-2019 CrowdStrike, Inc.
|
||||
//
|
||||
//
|
||||
use crate::vm::Vm;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use crate::x86_64::{CpuId, MsrList};
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use kvm_ioctls::Cap;
|
||||
use std::sync::Arc;
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
///
|
||||
///
|
||||
pub enum HypervisorError {
|
||||
///
|
||||
/// Vm creation failure
|
||||
///
|
||||
#[error("Failed to create Vm: {0}")]
|
||||
VmCreate(#[source] anyhow::Error),
|
||||
///
|
||||
/// Vm setup failure
|
||||
///
|
||||
#[error("Failed to setup Vm: {0}")]
|
||||
VmSetup(#[source] anyhow::Error),
|
||||
///
|
||||
/// API version error
|
||||
///
|
||||
#[error("Failed to get API Version: {0}")]
|
||||
GetApiVersion(#[source] anyhow::Error),
|
||||
///
|
||||
/// Vcpu mmap error
|
||||
///
|
||||
#[error("Failed to get Vcpu Mmap: {0}")]
|
||||
GetVcpuMmap(#[source] anyhow::Error),
|
||||
///
|
||||
/// Max Vcpu error
|
||||
///
|
||||
#[error("Failed to get number of max vcpus: {0}")]
|
||||
GetMaxVcpu(#[source] anyhow::Error),
|
||||
///
|
||||
/// Recommended Vcpu error
|
||||
///
|
||||
#[error("Failed to get number of max vcpus: {0}")]
|
||||
GetNrVcpus(#[source] anyhow::Error),
|
||||
///
|
||||
/// CpuId error
|
||||
///
|
||||
#[error("Failed to get number of max vcpus: {0}")]
|
||||
GetCpuId(#[source] anyhow::Error),
|
||||
///
|
||||
/// Failed to retrieve list of MSRs.
|
||||
///
|
||||
#[error("Failed to get the list of supported MSRs: {0}")]
|
||||
GetMsrList(#[source] anyhow::Error),
|
||||
}
|
||||
|
||||
///
|
||||
/// Result type for returning from a function
|
||||
///
|
||||
pub type Result<T> = std::result::Result<T, HypervisorError>;
|
||||
|
||||
///
|
||||
/// Trait to represent a Hypervisor
|
||||
///
|
||||
/// This crate provides a hypervisor-agnostic interfaces
|
||||
///
|
||||
pub trait Hypervisor: Send + Sync {
|
||||
///
|
||||
/// Create a Vm using the underlying hypervisor
|
||||
/// Return a hypervisor-agnostic Vm trait object
|
||||
///
|
||||
fn create_vm(&self) -> Result<Arc<dyn Vm>>;
|
||||
///
|
||||
/// Get the API version of the hypervisor
|
||||
///
|
||||
fn get_api_version(&self) -> i32;
|
||||
///
|
||||
/// Returns the size of the memory mapping required to use the vcpu's structures
|
||||
///
|
||||
fn get_vcpu_mmap_size(&self) -> Result<usize>;
|
||||
///
|
||||
/// Gets the recommended maximum number of VCPUs per VM.
|
||||
///
|
||||
fn get_max_vcpus(&self) -> Result<usize>;
|
||||
///
|
||||
/// Gets the recommended number of VCPUs per VM.
|
||||
///
|
||||
fn get_nr_vcpus(&self) -> Result<usize>;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// Checks if a particular `Cap` is available.
|
||||
///
|
||||
fn check_capability(&self, c: Cap) -> bool;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// Get the supported CpuID
|
||||
///
|
||||
fn get_cpuid(&self) -> Result<CpuId>;
|
||||
///
|
||||
/// Check particular extensions if any
|
||||
///
|
||||
fn check_required_extensions(&self) -> Result<()>;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// Retrieve the list of MSRs supported by the hypervisor.
|
||||
///
|
||||
fn get_msr_list(&self) -> Result<MsrList>;
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
// Copyright © 2019 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
|
||||
//
|
||||
// Copyright © 2020, Microsoft Corporation
|
||||
//
|
||||
// Copyright 2018-2019 CrowdStrike, Inc.
|
||||
//
|
||||
//
|
||||
|
||||
///
|
||||
/// Export generically-named wrappers of kvm-bindings for Unix-based platforms
|
||||
///
|
||||
use crate::kvm::{KvmError, KvmResult};
|
||||
pub use kvm_bindings::kvm_vcpu_init as VcpuInit;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
pub use {kvm_ioctls::Cap, kvm_ioctls::Kvm};
|
||||
|
||||
pub fn check_required_kvm_extensions(kvm: &Kvm) -> KvmResult<()> {
|
||||
if !kvm.check_extension(Cap::SignalMsi) {
|
||||
return Err(KvmError::CapabilityMissing(Cap::SignalMsi));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct VcpuKvmState {}
|
||||
@@ -1,953 +0,0 @@
|
||||
// Copyright © 2019 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
|
||||
//
|
||||
// Copyright © 2020, Microsoft Corporation
|
||||
//
|
||||
// Copyright 2018-2019 CrowdStrike, Inc.
|
||||
//
|
||||
//
|
||||
|
||||
use kvm_ioctls::{NoDatamatch, VcpuFd, VmFd};
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
use std::result;
|
||||
use std::sync::Arc;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use vm_memory::Address;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub use crate::aarch64::{check_required_kvm_extensions, VcpuInit, VcpuKvmState as CpuState};
|
||||
use crate::cpu;
|
||||
use crate::device;
|
||||
use crate::hypervisor;
|
||||
use crate::vm;
|
||||
// x86_64 dependencies
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub mod x86_64;
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use x86_64::{
|
||||
check_required_kvm_extensions, FpuState, SpecialRegisters, StandardRegisters, KVM_TSS_ADDRESS,
|
||||
};
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub use x86_64::{
|
||||
CpuId, CpuIdEntry, ExtendedControlRegisters, LapicState, MsrEntries, VcpuKvmState as CpuState,
|
||||
Xsave, CPUID_FLAG_VALID_INDEX,
|
||||
};
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use kvm_bindings::{kvm_enable_cap, MsrList, KVM_CAP_SPLIT_IRQCHIP};
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use crate::arch::x86::NUM_IOAPIC_PINS;
|
||||
|
||||
// aarch64 dependencies
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub mod aarch64;
|
||||
|
||||
pub use kvm_bindings;
|
||||
pub use kvm_bindings::{
|
||||
kvm_create_device, kvm_device_type_KVM_DEV_TYPE_VFIO, kvm_irq_routing, kvm_irq_routing_entry,
|
||||
kvm_userspace_memory_region, KVM_IRQ_ROUTING_MSI, KVM_MEM_READONLY, KVM_MSI_VALID_DEVID,
|
||||
};
|
||||
pub use kvm_ioctls;
|
||||
pub use kvm_ioctls::{Cap, Kvm};
|
||||
|
||||
///
|
||||
/// Export generically-named wrappers of kvm-bindings for Unix-based platforms
|
||||
///
|
||||
pub use {
|
||||
kvm_bindings::kvm_clock_data as ClockData, kvm_bindings::kvm_create_device as CreateDevice,
|
||||
kvm_bindings::kvm_device_attr as DeviceAttr,
|
||||
kvm_bindings::kvm_irq_routing_entry as IrqRoutingEntry, kvm_bindings::kvm_mp_state as MpState,
|
||||
kvm_bindings::kvm_userspace_memory_region as MemoryRegion,
|
||||
kvm_bindings::kvm_vcpu_events as VcpuEvents, kvm_ioctls::DeviceFd, kvm_ioctls::IoEventAddress,
|
||||
kvm_ioctls::VcpuExit,
|
||||
};
|
||||
|
||||
/// Wrapper over KVM VM ioctls.
|
||||
pub struct KvmVm {
|
||||
fd: Arc<VmFd>,
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
msrs: MsrEntries,
|
||||
}
|
||||
|
||||
// Returns a `Vec<T>` with a size in bytes at least as large as `size_in_bytes`.
|
||||
fn vec_with_size_in_bytes<T: Default>(size_in_bytes: usize) -> Vec<T> {
|
||||
let rounded_size = (size_in_bytes + size_of::<T>() - 1) / size_of::<T>();
|
||||
let mut v = Vec::with_capacity(rounded_size);
|
||||
v.resize_with(rounded_size, T::default);
|
||||
v
|
||||
}
|
||||
|
||||
// The kvm API has many structs that resemble the following `Foo` structure:
|
||||
//
|
||||
// ```
|
||||
// #[repr(C)]
|
||||
// struct Foo {
|
||||
// some_data: u32
|
||||
// entries: __IncompleteArrayField<__u32>,
|
||||
// }
|
||||
// ```
|
||||
//
|
||||
// In order to allocate such a structure, `size_of::<Foo>()` would be too small because it would not
|
||||
// include any space for `entries`. To make the allocation large enough while still being aligned
|
||||
// for `Foo`, a `Vec<Foo>` is created. Only the first element of `Vec<Foo>` would actually be used
|
||||
// as a `Foo`. The remaining memory in the `Vec<Foo>` is for `entries`, which must be contiguous
|
||||
// with `Foo`. This function is used to make the `Vec<Foo>` with enough space for `count` entries.
|
||||
use std::mem::size_of;
|
||||
fn vec_with_array_field<T: Default, F>(count: usize) -> Vec<T> {
|
||||
let element_space = count * size_of::<F>();
|
||||
let vec_size_bytes = size_of::<T>() + element_space;
|
||||
vec_with_size_in_bytes(vec_size_bytes)
|
||||
}
|
||||
|
||||
///
|
||||
/// Implementation of Vm trait for KVM
|
||||
/// Example:
|
||||
/// #[cfg(feature = "kvm")]
|
||||
/// extern crate hypervisor
|
||||
/// let kvm = hypervisor::kvm::KvmHypervisor::new().unwrap();
|
||||
/// let hypervisor: Arc<dyn hypervisor::Hypervisor> = Arc::new(kvm);
|
||||
/// let vm = hypervisor.create_vm().expect("new VM fd creation failed");
|
||||
/// vm.set/get().unwrap()
|
||||
///
|
||||
impl vm::Vm for KvmVm {
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// Sets the address of the three-page region in the VM's address space.
|
||||
///
|
||||
fn set_tss_address(&self, offset: usize) -> vm::Result<()> {
|
||||
self.fd
|
||||
.set_tss_address(offset)
|
||||
.map_err(|e| vm::HypervisorVmError::SetTssAddress(e.into()))
|
||||
}
|
||||
///
|
||||
/// Creates an in-kernel interrupt controller.
|
||||
///
|
||||
fn create_irq_chip(&self) -> vm::Result<()> {
|
||||
self.fd
|
||||
.create_irq_chip()
|
||||
.map_err(|e| vm::HypervisorVmError::CreateIrq(e.into()))
|
||||
}
|
||||
///
|
||||
/// Registers an event that will, when signaled, trigger the `gsi` IRQ.
|
||||
///
|
||||
fn register_irqfd(&self, fd: &EventFd, gsi: u32) -> vm::Result<()> {
|
||||
self.fd
|
||||
.register_irqfd(fd, gsi)
|
||||
.map_err(|e| vm::HypervisorVmError::RegisterIrqFd(e.into()))
|
||||
}
|
||||
///
|
||||
/// Unregisters an event that will, when signaled, trigger the `gsi` IRQ.
|
||||
///
|
||||
fn unregister_irqfd(&self, fd: &EventFd, gsi: u32) -> vm::Result<()> {
|
||||
self.fd
|
||||
.unregister_irqfd(fd, gsi)
|
||||
.map_err(|e| vm::HypervisorVmError::UnregisterIrqFd(e.into()))
|
||||
}
|
||||
///
|
||||
/// Creates a VcpuFd object from a vcpu RawFd.
|
||||
///
|
||||
fn create_vcpu(&self, id: u8) -> vm::Result<Arc<dyn cpu::Vcpu>> {
|
||||
let vc = self
|
||||
.fd
|
||||
.create_vcpu(id)
|
||||
.map_err(|e| vm::HypervisorVmError::CreateVcpu(e.into()))?;
|
||||
let vcpu = KvmVcpu {
|
||||
fd: vc,
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
msrs: self.msrs.clone(),
|
||||
};
|
||||
Ok(Arc::new(vcpu))
|
||||
}
|
||||
///
|
||||
/// Registers an event to be signaled whenever a certain address is written to.
|
||||
///
|
||||
fn register_ioevent(
|
||||
&self,
|
||||
fd: &EventFd,
|
||||
addr: &IoEventAddress,
|
||||
datamatch: Option<vm::DataMatch>,
|
||||
) -> vm::Result<()> {
|
||||
if let Some(dm) = datamatch {
|
||||
match dm {
|
||||
vm::DataMatch::DataMatch32(kvm_dm32) => self
|
||||
.fd
|
||||
.register_ioevent(fd, addr, kvm_dm32)
|
||||
.map_err(|e| vm::HypervisorVmError::RegisterIoEvent(e.into())),
|
||||
vm::DataMatch::DataMatch64(kvm_dm64) => self
|
||||
.fd
|
||||
.register_ioevent(fd, addr, kvm_dm64)
|
||||
.map_err(|e| vm::HypervisorVmError::RegisterIoEvent(e.into())),
|
||||
}
|
||||
} else {
|
||||
self.fd
|
||||
.register_ioevent(fd, addr, NoDatamatch)
|
||||
.map_err(|e| vm::HypervisorVmError::RegisterIoEvent(e.into()))
|
||||
}
|
||||
}
|
||||
///
|
||||
/// Unregisters an event from a certain address it has been previously registered to.
|
||||
///
|
||||
fn unregister_ioevent(&self, fd: &EventFd, addr: &IoEventAddress) -> vm::Result<()> {
|
||||
self.fd
|
||||
.unregister_ioevent(fd, addr, NoDatamatch)
|
||||
.map_err(|e| vm::HypervisorVmError::UnregisterIoEvent(e.into()))
|
||||
}
|
||||
///
|
||||
/// Sets the GSI routing table entries, overwriting any previously set
|
||||
/// entries, as per the `KVM_SET_GSI_ROUTING` ioctl.
|
||||
///
|
||||
fn set_gsi_routing(&self, entries: &[IrqRoutingEntry]) -> vm::Result<()> {
|
||||
let mut irq_routing =
|
||||
vec_with_array_field::<kvm_irq_routing, kvm_irq_routing_entry>(entries.len());
|
||||
irq_routing[0].nr = entries.len() as u32;
|
||||
irq_routing[0].flags = 0;
|
||||
|
||||
unsafe {
|
||||
let entries_slice: &mut [kvm_irq_routing_entry] =
|
||||
irq_routing[0].entries.as_mut_slice(entries.len());
|
||||
entries_slice.copy_from_slice(&entries);
|
||||
}
|
||||
|
||||
self.fd
|
||||
.set_gsi_routing(&irq_routing[0])
|
||||
.map_err(|e| vm::HypervisorVmError::SetGsiRouting(e.into()))
|
||||
}
|
||||
///
|
||||
/// Creates a memory region structure that can be used with set_user_memory_region
|
||||
///
|
||||
fn make_user_memory_region(
|
||||
&self,
|
||||
slot: u32,
|
||||
guest_phys_addr: u64,
|
||||
memory_size: u64,
|
||||
userspace_addr: u64,
|
||||
readonly: bool,
|
||||
) -> MemoryRegion {
|
||||
MemoryRegion {
|
||||
slot,
|
||||
guest_phys_addr,
|
||||
memory_size,
|
||||
userspace_addr,
|
||||
flags: if readonly { KVM_MEM_READONLY } else { 0 },
|
||||
}
|
||||
}
|
||||
///
|
||||
/// Creates/modifies a guest physical memory slot.
|
||||
///
|
||||
fn set_user_memory_region(&self, user_memory_region: MemoryRegion) -> vm::Result<()> {
|
||||
// Safe because guest regions are guaranteed not to overlap.
|
||||
unsafe {
|
||||
self.fd
|
||||
.set_user_memory_region(user_memory_region)
|
||||
.map_err(|e| vm::HypervisorVmError::SetUserMemory(e.into()))
|
||||
}
|
||||
}
|
||||
///
|
||||
/// Creates an emulated device in the kernel.
|
||||
///
|
||||
/// See the documentation for `KVM_CREATE_DEVICE`.
|
||||
fn create_device(&self, device: &mut CreateDevice) -> vm::Result<Arc<dyn device::Device>> {
|
||||
let fd = self
|
||||
.fd
|
||||
.create_device(device)
|
||||
.map_err(|e| vm::HypervisorVmError::CreateDevice(e.into()))?;
|
||||
let device = KvmDevice { fd };
|
||||
Ok(Arc::new(device))
|
||||
}
|
||||
///
|
||||
/// Returns the preferred CPU target type which can be emulated by KVM on underlying host.
|
||||
///
|
||||
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
|
||||
fn get_preferred_target(&self, kvi: &mut VcpuInit) -> vm::Result<()> {
|
||||
self.fd
|
||||
.get_preferred_target(kvi)
|
||||
.map_err(|e| vm::HypervisorVmError::GetPreferredTarget(e.into()))
|
||||
}
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
fn enable_split_irq(&self) -> vm::Result<()> {
|
||||
// Set TSS
|
||||
self.fd
|
||||
.set_tss_address(KVM_TSS_ADDRESS.raw_value() as usize)
|
||||
.map_err(|e| vm::HypervisorVmError::EnableSplitIrq(e.into()))?;
|
||||
// Create split irqchip
|
||||
// Only the local APIC is emulated in kernel, both PICs and IOAPIC
|
||||
// are not.
|
||||
let mut cap: kvm_enable_cap = Default::default();
|
||||
cap.cap = KVM_CAP_SPLIT_IRQCHIP;
|
||||
cap.args[0] = NUM_IOAPIC_PINS as u64;
|
||||
self.fd
|
||||
.enable_cap(&cap)
|
||||
.map_err(|e| vm::HypervisorVmError::EnableSplitIrq(e.into()))?;
|
||||
Ok(())
|
||||
}
|
||||
/// Retrieve guest clock.
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
fn get_clock(&self) -> vm::Result<ClockData> {
|
||||
self.fd
|
||||
.get_clock()
|
||||
.map_err(|e| vm::HypervisorVmError::GetClock(e.into()))
|
||||
}
|
||||
/// Set guest clock.
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
fn set_clock(&self, data: &ClockData) -> vm::Result<()> {
|
||||
self.fd
|
||||
.set_clock(data)
|
||||
.map_err(|e| vm::HypervisorVmError::SetClock(e.into()))
|
||||
}
|
||||
/// Checks if a particular `Cap` is available.
|
||||
fn check_extension(&self, c: Cap) -> bool {
|
||||
self.fd.check_extension(c)
|
||||
}
|
||||
/// Create a device that is used for passthrough
|
||||
fn create_passthrough_device(&self) -> vm::Result<Arc<dyn device::Device>> {
|
||||
let mut vfio_dev = kvm_create_device {
|
||||
type_: kvm_device_type_KVM_DEV_TYPE_VFIO,
|
||||
fd: 0,
|
||||
flags: 0,
|
||||
};
|
||||
|
||||
self.create_device(&mut vfio_dev)
|
||||
.map_err(|e| vm::HypervisorVmError::CreatePassthroughDevice(e.into()))
|
||||
}
|
||||
}
|
||||
/// Wrapper over KVM system ioctls.
|
||||
pub struct KvmHypervisor {
|
||||
kvm: Kvm,
|
||||
}
|
||||
/// Enum for KVM related error
|
||||
#[derive(Debug)]
|
||||
pub enum KvmError {
|
||||
CapabilityMissing(Cap),
|
||||
}
|
||||
pub type KvmResult<T> = result::Result<T, KvmError>;
|
||||
impl KvmHypervisor {
|
||||
/// Create a hypervisor based on Kvm
|
||||
pub fn new() -> hypervisor::Result<KvmHypervisor> {
|
||||
let kvm_obj = Kvm::new().map_err(|e| hypervisor::HypervisorError::VmCreate(e.into()))?;
|
||||
Ok(KvmHypervisor { kvm: kvm_obj })
|
||||
}
|
||||
}
|
||||
/// Implementation of Hypervisor trait for KVM
|
||||
/// Example:
|
||||
/// #[cfg(feature = "kvm")]
|
||||
/// extern crate hypervisor
|
||||
/// let kvm = hypervisor::kvm::KvmHypervisor::new().unwrap();
|
||||
/// let hypervisor: Arc<dyn hypervisor::Hypervisor> = Arc::new(kvm);
|
||||
/// let vm = hypervisor.create_vm().expect("new VM fd creation failed");
|
||||
///
|
||||
impl hypervisor::Hypervisor for KvmHypervisor {
|
||||
/// Create a KVM vm object and return the object as Vm trait object
|
||||
/// Example
|
||||
/// # extern crate hypervisor;
|
||||
/// # use hypervisor::KvmHypervisor;
|
||||
/// use hypervisor::KvmVm;
|
||||
/// let hypervisor = KvmHypervisor::new().unwrap();
|
||||
/// let vm = hypervisor.create_vm().unwrap()
|
||||
///
|
||||
fn create_vm(&self) -> hypervisor::Result<Arc<dyn vm::Vm>> {
|
||||
let fd: VmFd;
|
||||
loop {
|
||||
match self.kvm.create_vm() {
|
||||
Ok(res) => fd = res,
|
||||
Err(e) => {
|
||||
if e.errno() == libc::EINTR {
|
||||
// If the error returned is EINTR, which means the
|
||||
// ioctl has been interrupted, we have to retry as
|
||||
// this can't be considered as a regular error.
|
||||
continue;
|
||||
} else {
|
||||
return Err(hypervisor::HypervisorError::VmCreate(e.into()));
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
let vm_fd = Arc::new(fd);
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
{
|
||||
let msr_list = self.get_msr_list()?;
|
||||
let num_msrs = msr_list.as_fam_struct_ref().nmsrs as usize;
|
||||
let mut msrs = MsrEntries::new(num_msrs);
|
||||
let indices = msr_list.as_slice();
|
||||
let msr_entries = msrs.as_mut_slice();
|
||||
for (pos, index) in indices.iter().enumerate() {
|
||||
msr_entries[pos].index = *index;
|
||||
}
|
||||
|
||||
Ok(Arc::new(KvmVm { fd: vm_fd, msrs }))
|
||||
}
|
||||
|
||||
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
|
||||
{
|
||||
Ok(Arc::new(KvmVm { fd: vm_fd }))
|
||||
}
|
||||
}
|
||||
|
||||
fn check_required_extensions(&self) -> hypervisor::Result<()> {
|
||||
check_required_kvm_extensions(&self.kvm).expect("Missing KVM capabilities");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
///
|
||||
/// Returns the KVM API version.
|
||||
///
|
||||
fn get_api_version(&self) -> i32 {
|
||||
self.kvm.get_api_version()
|
||||
}
|
||||
///
|
||||
/// Returns the size of the memory mapping required to use the vcpu's `kvm_run` structure.
|
||||
///
|
||||
fn get_vcpu_mmap_size(&self) -> hypervisor::Result<usize> {
|
||||
self.kvm
|
||||
.get_vcpu_mmap_size()
|
||||
.map_err(|e| hypervisor::HypervisorError::GetVcpuMmap(e.into()))
|
||||
}
|
||||
///
|
||||
/// Gets the recommended maximum number of VCPUs per VM.
|
||||
///
|
||||
fn get_max_vcpus(&self) -> hypervisor::Result<usize> {
|
||||
Ok(self.kvm.get_max_vcpus())
|
||||
}
|
||||
///
|
||||
/// Gets the recommended number of VCPUs per VM.
|
||||
///
|
||||
fn get_nr_vcpus(&self) -> hypervisor::Result<usize> {
|
||||
Ok(self.kvm.get_nr_vcpus())
|
||||
}
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// Checks if a particular `Cap` is available.
|
||||
///
|
||||
fn check_capability(&self, c: Cap) -> bool {
|
||||
self.kvm.check_extension(c)
|
||||
}
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// X86 specific call to get the system supported CPUID values.
|
||||
///
|
||||
fn get_cpuid(&self) -> hypervisor::Result<CpuId> {
|
||||
self.kvm
|
||||
.get_supported_cpuid(kvm_bindings::KVM_MAX_CPUID_ENTRIES)
|
||||
.map_err(|e| hypervisor::HypervisorError::GetCpuId(e.into()))
|
||||
}
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// Retrieve the list of MSRs supported by KVM.
|
||||
///
|
||||
fn get_msr_list(&self) -> hypervisor::Result<MsrList> {
|
||||
self.kvm
|
||||
.get_msr_index_list()
|
||||
.map_err(|e| hypervisor::HypervisorError::GetMsrList(e.into()))
|
||||
}
|
||||
}
|
||||
/// Vcpu struct for KVM
|
||||
pub struct KvmVcpu {
|
||||
fd: VcpuFd,
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
msrs: MsrEntries,
|
||||
}
|
||||
/// Implementation of Vcpu trait for KVM
|
||||
/// Example:
|
||||
/// #[cfg(feature = "kvm")]
|
||||
/// extern crate hypervisor
|
||||
/// let kvm = hypervisor::kvm::KvmHypervisor::new().unwrap();
|
||||
/// let hypervisor: Arc<dyn hypervisor::Hypervisor> = Arc::new(kvm);
|
||||
/// let vm = hypervisor.create_vm().expect("new VM fd creation failed");
|
||||
/// let vcpu = vm.create_vcpu(0).unwrap();
|
||||
/// vcpu.get/set().unwrap()
|
||||
///
|
||||
impl cpu::Vcpu for KvmVcpu {
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// Returns the vCPU general purpose registers.
|
||||
///
|
||||
fn get_regs(&self) -> cpu::Result<StandardRegisters> {
|
||||
self.fd
|
||||
.get_regs()
|
||||
.map_err(|e| cpu::HypervisorCpuError::GetStandardRegs(e.into()))
|
||||
}
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// Sets the vCPU general purpose registers using the `KVM_SET_REGS` ioctl.
|
||||
///
|
||||
fn set_regs(&self, regs: &StandardRegisters) -> cpu::Result<()> {
|
||||
self.fd
|
||||
.set_regs(regs)
|
||||
.map_err(|e| cpu::HypervisorCpuError::SetStandardRegs(e.into()))
|
||||
}
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// Returns the vCPU special registers.
|
||||
///
|
||||
fn get_sregs(&self) -> cpu::Result<SpecialRegisters> {
|
||||
self.fd
|
||||
.get_sregs()
|
||||
.map_err(|e| cpu::HypervisorCpuError::GetSpecialRegs(e.into()))
|
||||
}
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// Sets the vCPU special registers using the `KVM_SET_SREGS` ioctl.
|
||||
///
|
||||
fn set_sregs(&self, sregs: &SpecialRegisters) -> cpu::Result<()> {
|
||||
self.fd
|
||||
.set_sregs(sregs)
|
||||
.map_err(|e| cpu::HypervisorCpuError::SetSpecialRegs(e.into()))
|
||||
}
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// Returns the floating point state (FPU) from the vCPU.
|
||||
///
|
||||
fn get_fpu(&self) -> cpu::Result<FpuState> {
|
||||
self.fd
|
||||
.get_fpu()
|
||||
.map_err(|e| cpu::HypervisorCpuError::GetFloatingPointRegs(e.into()))
|
||||
}
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// Set the floating point state (FPU) of a vCPU using the `KVM_SET_FPU` ioct.
|
||||
///
|
||||
fn set_fpu(&self, fpu: &FpuState) -> cpu::Result<()> {
|
||||
self.fd
|
||||
.set_fpu(fpu)
|
||||
.map_err(|e| cpu::HypervisorCpuError::SetFloatingPointRegs(e.into()))
|
||||
}
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// X86 specific call to setup the CPUID registers.
|
||||
///
|
||||
fn set_cpuid2(&self, cpuid: &CpuId) -> cpu::Result<()> {
|
||||
self.fd
|
||||
.set_cpuid2(cpuid)
|
||||
.map_err(|e| cpu::HypervisorCpuError::SetCpuid(e.into()))
|
||||
}
|
||||
///
|
||||
/// X86 specific call to retrieve the CPUID registers.
|
||||
///
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
fn get_cpuid2(&self, num_entries: usize) -> cpu::Result<CpuId> {
|
||||
self.fd
|
||||
.get_cpuid2(num_entries)
|
||||
.map_err(|e| cpu::HypervisorCpuError::GetCpuid(e.into()))
|
||||
}
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// Returns the state of the LAPIC (Local Advanced Programmable Interrupt Controller).
|
||||
///
|
||||
fn get_lapic(&self) -> cpu::Result<LapicState> {
|
||||
self.fd
|
||||
.get_lapic()
|
||||
.map_err(|e| cpu::HypervisorCpuError::GetlapicState(e.into()))
|
||||
}
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// Sets the state of the LAPIC (Local Advanced Programmable Interrupt Controller).
|
||||
///
|
||||
fn set_lapic(&self, klapic: &LapicState) -> cpu::Result<()> {
|
||||
self.fd
|
||||
.set_lapic(klapic)
|
||||
.map_err(|e| cpu::HypervisorCpuError::SetLapicState(e.into()))
|
||||
}
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// Returns the model-specific registers (MSR) for this vCPU.
|
||||
///
|
||||
fn get_msrs(&self, msrs: &mut MsrEntries) -> cpu::Result<usize> {
|
||||
self.fd
|
||||
.get_msrs(msrs)
|
||||
.map_err(|e| cpu::HypervisorCpuError::GetMsrEntries(e.into()))
|
||||
}
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// Setup the model-specific registers (MSR) for this vCPU.
|
||||
/// Returns the number of MSR entries actually written.
|
||||
///
|
||||
fn set_msrs(&self, msrs: &MsrEntries) -> cpu::Result<usize> {
|
||||
self.fd
|
||||
.set_msrs(msrs)
|
||||
.map_err(|e| cpu::HypervisorCpuError::SetMsrEntries(e.into()))
|
||||
}
|
||||
///
|
||||
/// Returns the vcpu's current "multiprocessing state".
|
||||
///
|
||||
fn get_mp_state(&self) -> cpu::Result<MpState> {
|
||||
self.fd
|
||||
.get_mp_state()
|
||||
.map_err(|e| cpu::HypervisorCpuError::GetMpState(e.into()))
|
||||
}
|
||||
///
|
||||
/// Sets the vcpu's current "multiprocessing state".
|
||||
///
|
||||
fn set_mp_state(&self, mp_state: MpState) -> cpu::Result<()> {
|
||||
self.fd
|
||||
.set_mp_state(mp_state)
|
||||
.map_err(|e| cpu::HypervisorCpuError::SetMpState(e.into()))
|
||||
}
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// X86 specific call that returns the vcpu's current "xsave struct".
|
||||
///
|
||||
fn get_xsave(&self) -> cpu::Result<Xsave> {
|
||||
self.fd
|
||||
.get_xsave()
|
||||
.map_err(|e| cpu::HypervisorCpuError::GetXsaveState(e.into()))
|
||||
}
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// X86 specific call that sets the vcpu's current "xsave struct".
|
||||
///
|
||||
fn set_xsave(&self, xsave: &Xsave) -> cpu::Result<()> {
|
||||
self.fd
|
||||
.set_xsave(xsave)
|
||||
.map_err(|e| cpu::HypervisorCpuError::SetXsaveState(e.into()))
|
||||
}
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// X86 specific call that returns the vcpu's current "xcrs".
|
||||
///
|
||||
fn get_xcrs(&self) -> cpu::Result<ExtendedControlRegisters> {
|
||||
self.fd
|
||||
.get_xcrs()
|
||||
.map_err(|e| cpu::HypervisorCpuError::GetXcsr(e.into()))
|
||||
}
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// X86 specific call that sets the vcpu's current "xcrs".
|
||||
///
|
||||
fn set_xcrs(&self, xcrs: &ExtendedControlRegisters) -> cpu::Result<()> {
|
||||
self.fd
|
||||
.set_xcrs(&xcrs)
|
||||
.map_err(|e| cpu::HypervisorCpuError::SetXcsr(e.into()))
|
||||
}
|
||||
///
|
||||
/// Triggers the running of the current virtual CPU returning an exit reason.
|
||||
///
|
||||
fn run(&self) -> std::result::Result<cpu::VmExit, cpu::HypervisorCpuError> {
|
||||
match self.fd.run() {
|
||||
Ok(run) => match run {
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
VcpuExit::IoIn(addr, data) => Ok(cpu::VmExit::IoIn(addr, data)),
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
VcpuExit::IoOut(addr, data) => Ok(cpu::VmExit::IoOut(addr, data)),
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
VcpuExit::IoapicEoi(vector) => Ok(cpu::VmExit::IoapicEoi(vector)),
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
VcpuExit::Shutdown | VcpuExit::Hlt => Ok(cpu::VmExit::Reset),
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
VcpuExit::SystemEvent(event_type, flags) => {
|
||||
use kvm_bindings::KVM_SYSTEM_EVENT_SHUTDOWN;
|
||||
// On Aarch64, when the VM is shutdown, run() returns
|
||||
// VcpuExit::SystemEvent with reason KVM_SYSTEM_EVENT_SHUTDOWN
|
||||
if event_type == KVM_SYSTEM_EVENT_SHUTDOWN {
|
||||
Ok(cpu::VmExit::Reset)
|
||||
} else {
|
||||
Err(cpu::HypervisorCpuError::RunVcpu(anyhow!(
|
||||
"Unexpected system event with type 0x{:x}, flags 0x{:x}",
|
||||
event_type,
|
||||
flags
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
VcpuExit::MmioRead(addr, data) => Ok(cpu::VmExit::MmioRead(addr, data)),
|
||||
VcpuExit::MmioWrite(addr, data) => Ok(cpu::VmExit::MmioWrite(addr, data)),
|
||||
|
||||
r => Err(cpu::HypervisorCpuError::RunVcpu(anyhow!(
|
||||
"Unexpected exit reason on vcpu run: {:?}",
|
||||
r
|
||||
))),
|
||||
},
|
||||
|
||||
Err(ref e) => match e.errno() {
|
||||
libc::EAGAIN | libc::EINTR => Ok(cpu::VmExit::Ignore),
|
||||
_ => Err(cpu::HypervisorCpuError::RunVcpu(anyhow!(
|
||||
"VCPU error {:?}",
|
||||
e
|
||||
))),
|
||||
},
|
||||
}
|
||||
}
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// Returns currently pending exceptions, interrupts, and NMIs as well as related
|
||||
/// states of the vcpu.
|
||||
///
|
||||
fn get_vcpu_events(&self) -> cpu::Result<VcpuEvents> {
|
||||
self.fd
|
||||
.get_vcpu_events()
|
||||
.map_err(|e| cpu::HypervisorCpuError::GetVcpuEvents(e.into()))
|
||||
}
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// Sets pending exceptions, interrupts, and NMIs as well as related states
|
||||
/// of the vcpu.
|
||||
///
|
||||
fn set_vcpu_events(&self, events: &VcpuEvents) -> cpu::Result<()> {
|
||||
self.fd
|
||||
.set_vcpu_events(events)
|
||||
.map_err(|e| cpu::HypervisorCpuError::SetVcpuEvents(e.into()))
|
||||
}
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// Let the guest know that it has been paused, which prevents from
|
||||
/// potential soft lockups when being resumed.
|
||||
///
|
||||
fn notify_guest_clock_paused(&self) -> cpu::Result<()> {
|
||||
self.fd
|
||||
.kvmclock_ctrl()
|
||||
.map_err(|e| cpu::HypervisorCpuError::NotifyGuestClockPaused(e.into()))
|
||||
}
|
||||
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
|
||||
fn vcpu_init(&self, kvi: &VcpuInit) -> cpu::Result<()> {
|
||||
self.fd
|
||||
.vcpu_init(kvi)
|
||||
.map_err(|e| cpu::HypervisorCpuError::VcpuInit(e.into()))
|
||||
}
|
||||
///
|
||||
/// Sets the value of one register for this vCPU.
|
||||
///
|
||||
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
|
||||
fn set_one_reg(&self, reg_id: u64, data: u64) -> cpu::Result<()> {
|
||||
self.fd
|
||||
.set_one_reg(reg_id, data)
|
||||
.map_err(|e| cpu::HypervisorCpuError::SetOneReg(e.into()))
|
||||
}
|
||||
///
|
||||
/// Gets the value of one register for this vCPU.
|
||||
///
|
||||
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
|
||||
fn get_one_reg(&self, reg_id: u64) -> cpu::Result<u64> {
|
||||
self.fd
|
||||
.get_one_reg(reg_id)
|
||||
.map_err(|e| cpu::HypervisorCpuError::GetOneReg(e.into()))
|
||||
}
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// Get the current CPU state
|
||||
///
|
||||
/// Ordering requirements:
|
||||
///
|
||||
/// KVM_GET_MP_STATE calls kvm_apic_accept_events(), which might modify
|
||||
/// vCPU/LAPIC state. As such, it must be done before most everything
|
||||
/// else, otherwise we cannot restore everything and expect it to work.
|
||||
///
|
||||
/// KVM_GET_VCPU_EVENTS/KVM_SET_VCPU_EVENTS is unsafe if other vCPUs are
|
||||
/// still running.
|
||||
///
|
||||
/// KVM_GET_LAPIC may change state of LAPIC before returning it.
|
||||
///
|
||||
/// GET_VCPU_EVENTS should probably be last to save. The code looks as
|
||||
/// it might as well be affected by internal state modifications of the
|
||||
/// GET ioctls.
|
||||
///
|
||||
/// SREGS saves/restores a pending interrupt, similar to what
|
||||
/// VCPU_EVENTS also does.
|
||||
///
|
||||
/// GET_MSRS requires a pre-populated data structure to do something
|
||||
/// meaningful. For SET_MSRS it will then contain good data.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate hypervisor;
|
||||
/// # use hypervisor::KvmHypervisor;
|
||||
/// # use std::sync::Arc;
|
||||
/// let kvm = hypervisor::kvm::KvmHypervisor::new().unwrap();
|
||||
/// let hv: Arc<dyn hypervisor::Hypervisor> = Arc::new(kvm);
|
||||
/// let vm = hv.create_vm().expect("new VM fd creation failed");
|
||||
/// vm.enable_split_irq().unwrap();
|
||||
/// let vcpu = vm.create_vcpu(0).unwrap();
|
||||
/// let state = vcpu.state().unwrap();
|
||||
/// ```
|
||||
fn state(&self) -> cpu::Result<CpuState> {
|
||||
let mp_state = self.get_mp_state()?;
|
||||
let regs = self.get_regs()?;
|
||||
let sregs = self.get_sregs()?;
|
||||
let xsave = self.get_xsave()?;
|
||||
let xcrs = self.get_xcrs()?;
|
||||
let lapic_state = self.get_lapic()?;
|
||||
let fpu = self.get_fpu()?;
|
||||
|
||||
// Try to get all MSRs based on the list previously retrieved from KVM.
|
||||
// If the number of MSRs obtained from GET_MSRS is different from the
|
||||
// expected amount, we fallback onto a slower method by getting MSRs
|
||||
// by chunks. This is the only way to make sure we try to get as many
|
||||
// MSRs as possible, even if some MSRs are not supported.
|
||||
let mut msr_entries = self.msrs.clone();
|
||||
let expected_num_msrs = msr_entries.as_fam_struct_ref().nmsrs as usize;
|
||||
let num_msrs = self.get_msrs(&mut msr_entries)?;
|
||||
let msrs = if num_msrs != expected_num_msrs {
|
||||
let mut faulty_msr_index = num_msrs;
|
||||
let mut msr_entries_tmp =
|
||||
MsrEntries::from_entries(&msr_entries.as_slice()[..faulty_msr_index]);
|
||||
|
||||
loop {
|
||||
warn!(
|
||||
"Detected faulty MSR 0x{:x} while getting MSRs",
|
||||
msr_entries.as_slice()[faulty_msr_index].index
|
||||
);
|
||||
|
||||
let start_pos = faulty_msr_index + 1;
|
||||
let mut sub_msr_entries =
|
||||
MsrEntries::from_entries(&msr_entries.as_slice()[start_pos..]);
|
||||
let expected_num_msrs = sub_msr_entries.as_fam_struct_ref().nmsrs as usize;
|
||||
let num_msrs = self.get_msrs(&mut sub_msr_entries)?;
|
||||
|
||||
for i in 0..num_msrs {
|
||||
msr_entries_tmp
|
||||
.push(sub_msr_entries.as_slice()[i])
|
||||
.map_err(|e| {
|
||||
cpu::HypervisorCpuError::GetMsrEntries(anyhow!(
|
||||
"Failed adding MSR entries: {:?}",
|
||||
e
|
||||
))
|
||||
})?;
|
||||
}
|
||||
|
||||
if num_msrs == expected_num_msrs {
|
||||
break;
|
||||
}
|
||||
|
||||
faulty_msr_index = start_pos + num_msrs;
|
||||
}
|
||||
|
||||
msr_entries_tmp
|
||||
} else {
|
||||
msr_entries
|
||||
};
|
||||
|
||||
let vcpu_events = self.get_vcpu_events()?;
|
||||
|
||||
Ok(CpuState {
|
||||
msrs,
|
||||
vcpu_events,
|
||||
regs,
|
||||
sregs,
|
||||
fpu,
|
||||
lapic_state,
|
||||
xsave,
|
||||
xcrs,
|
||||
mp_state,
|
||||
})
|
||||
}
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
fn state(&self) -> cpu::Result<CpuState> {
|
||||
unimplemented!();
|
||||
}
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// Restore the previously saved CPU state
|
||||
///
|
||||
/// Ordering requirements:
|
||||
///
|
||||
/// KVM_GET_VCPU_EVENTS/KVM_SET_VCPU_EVENTS is unsafe if other vCPUs are
|
||||
/// still running.
|
||||
///
|
||||
/// Some SET ioctls (like set_mp_state) depend on kvm_vcpu_is_bsp(), so
|
||||
/// if we ever change the BSP, we have to do that before restoring anything.
|
||||
/// The same seems to be true for CPUID stuff.
|
||||
///
|
||||
/// SREGS saves/restores a pending interrupt, similar to what
|
||||
/// VCPU_EVENTS also does.
|
||||
///
|
||||
/// SET_REGS clears pending exceptions unconditionally, thus, it must be
|
||||
/// done before SET_VCPU_EVENTS, which restores it.
|
||||
///
|
||||
/// SET_LAPIC must come after SET_SREGS, because the latter restores
|
||||
/// the apic base msr.
|
||||
///
|
||||
/// SET_LAPIC must come before SET_MSRS, because the TSC deadline MSR
|
||||
/// only restores successfully, when the LAPIC is correctly configured.
|
||||
///
|
||||
/// Arguments: CpuState
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate hypervisor;
|
||||
/// # use hypervisor::KvmHypervisor;
|
||||
/// # use std::sync::Arc;
|
||||
/// let kvm = hypervisor::kvm::KvmHypervisor::new().unwrap();
|
||||
/// let hv: Arc<dyn hypervisor::Hypervisor> = Arc::new(kvm);
|
||||
/// let vm = hv.create_vm().expect("new VM fd creation failed");
|
||||
/// vm.enable_split_irq().unwrap();
|
||||
/// let vcpu = vm.create_vcpu(0).unwrap();
|
||||
/// let state = vcpu.state().unwrap();
|
||||
/// vcpu.set_state(&state).unwrap();
|
||||
/// ```
|
||||
fn set_state(&self, state: &CpuState) -> cpu::Result<()> {
|
||||
self.set_mp_state(state.mp_state)?;
|
||||
self.set_regs(&state.regs)?;
|
||||
self.set_sregs(&state.sregs)?;
|
||||
self.set_xsave(&state.xsave)?;
|
||||
self.set_xcrs(&state.xcrs)?;
|
||||
self.set_lapic(&state.lapic_state)?;
|
||||
self.set_fpu(&state.fpu)?;
|
||||
|
||||
// Try to set all MSRs previously stored.
|
||||
// If the number of MSRs set from SET_MSRS is different from the
|
||||
// expected amount, we fallback onto a slower method by setting MSRs
|
||||
// by chunks. This is the only way to make sure we try to set as many
|
||||
// MSRs as possible, even if some MSRs are not supported.
|
||||
let expected_num_msrs = state.msrs.as_fam_struct_ref().nmsrs as usize;
|
||||
let num_msrs = self.set_msrs(&state.msrs)?;
|
||||
if num_msrs != expected_num_msrs {
|
||||
let mut faulty_msr_index = num_msrs;
|
||||
|
||||
loop {
|
||||
warn!(
|
||||
"Detected faulty MSR 0x{:x} while setting MSRs",
|
||||
state.msrs.as_slice()[faulty_msr_index].index
|
||||
);
|
||||
|
||||
let start_pos = faulty_msr_index + 1;
|
||||
let sub_msr_entries = MsrEntries::from_entries(&state.msrs.as_slice()[start_pos..]);
|
||||
let expected_num_msrs = sub_msr_entries.as_fam_struct_ref().nmsrs as usize;
|
||||
let num_msrs = self.set_msrs(&sub_msr_entries)?;
|
||||
|
||||
if num_msrs == expected_num_msrs {
|
||||
break;
|
||||
}
|
||||
|
||||
faulty_msr_index = start_pos + num_msrs;
|
||||
}
|
||||
}
|
||||
|
||||
self.set_vcpu_events(&state.vcpu_events)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
#[allow(unused_variables)]
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
fn set_state(&self, state: &CpuState) -> cpu::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Device struct for KVM
|
||||
pub struct KvmDevice {
|
||||
fd: DeviceFd,
|
||||
}
|
||||
|
||||
impl device::Device for KvmDevice {
|
||||
///
|
||||
/// Set device attribute
|
||||
///
|
||||
fn set_device_attr(&self, attr: &DeviceAttr) -> device::Result<()> {
|
||||
self.fd
|
||||
.set_device_attr(attr)
|
||||
.map_err(|e| device::HypervisorDeviceError::SetDeviceAttribute(e.into()))
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRawFd for KvmDevice {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.fd.as_raw_fd()
|
||||
}
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
// Copyright © 2019 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
|
||||
//
|
||||
// Copyright © 2020, Microsoft Corporation
|
||||
//
|
||||
// Copyright 2018-2019 CrowdStrike, Inc.
|
||||
//
|
||||
//
|
||||
|
||||
use vm_memory::GuestAddress;
|
||||
|
||||
use crate::arch::x86::{msr_index, MTRR_ENABLE, MTRR_MEM_TYPE_WB};
|
||||
use crate::kvm::{Cap, Kvm, KvmError, KvmResult};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
|
||||
///
|
||||
/// Export generically-named wrappers of kvm-bindings for Unix-based platforms
|
||||
///
|
||||
pub use {
|
||||
kvm_bindings::kvm_cpuid_entry2 as CpuIdEntry, kvm_bindings::kvm_dtable as DescriptorTable,
|
||||
kvm_bindings::kvm_fpu as FpuState, kvm_bindings::kvm_lapic_state as LapicState,
|
||||
kvm_bindings::kvm_mp_state as MpState, kvm_bindings::kvm_msr_entry as MsrEntry,
|
||||
kvm_bindings::kvm_regs as StandardRegisters, kvm_bindings::kvm_segment as SegmentRegister,
|
||||
kvm_bindings::kvm_sregs as SpecialRegisters, kvm_bindings::kvm_vcpu_events as VcpuEvents,
|
||||
kvm_bindings::kvm_xcrs as ExtendedControlRegisters, kvm_bindings::kvm_xsave as Xsave,
|
||||
kvm_bindings::CpuId, kvm_bindings::MsrList, kvm_bindings::Msrs as MsrEntries,
|
||||
kvm_bindings::KVM_CPUID_FLAG_SIGNIFCANT_INDEX as CPUID_FLAG_VALID_INDEX,
|
||||
};
|
||||
|
||||
pub const KVM_TSS_ADDRESS: GuestAddress = GuestAddress(0xfffb_d000);
|
||||
|
||||
macro_rules! msr {
|
||||
($msr:expr) => {
|
||||
MsrEntry {
|
||||
index: $msr,
|
||||
data: 0x0,
|
||||
..Default::default()
|
||||
}
|
||||
};
|
||||
}
|
||||
macro_rules! msr_data {
|
||||
($msr:expr, $data:expr) => {
|
||||
MsrEntry {
|
||||
index: $msr,
|
||||
data: $data,
|
||||
..Default::default()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn boot_msr_entries() -> MsrEntries {
|
||||
MsrEntries::from_entries(&[
|
||||
msr!(msr_index::MSR_IA32_SYSENTER_CS),
|
||||
msr!(msr_index::MSR_IA32_SYSENTER_ESP),
|
||||
msr!(msr_index::MSR_IA32_SYSENTER_EIP),
|
||||
msr!(msr_index::MSR_STAR),
|
||||
msr!(msr_index::MSR_CSTAR),
|
||||
msr!(msr_index::MSR_LSTAR),
|
||||
msr!(msr_index::MSR_KERNEL_GS_BASE),
|
||||
msr!(msr_index::MSR_SYSCALL_MASK),
|
||||
msr!(msr_index::MSR_IA32_TSC),
|
||||
msr_data!(
|
||||
msr_index::MSR_IA32_MISC_ENABLE,
|
||||
msr_index::MSR_IA32_MISC_ENABLE_FAST_STRING as u64
|
||||
),
|
||||
msr_data!(msr_index::MSR_MTRRdefType, MTRR_ENABLE | MTRR_MEM_TYPE_WB),
|
||||
])
|
||||
}
|
||||
|
||||
///
|
||||
/// Check KVM extension for Linux
|
||||
///
|
||||
pub fn check_required_kvm_extensions(kvm: &Kvm) -> KvmResult<()> {
|
||||
if !kvm.check_extension(Cap::SignalMsi) {
|
||||
return Err(KvmError::CapabilityMissing(Cap::SignalMsi));
|
||||
}
|
||||
if !kvm.check_extension(Cap::TscDeadlineTimer) {
|
||||
return Err(KvmError::CapabilityMissing(Cap::TscDeadlineTimer));
|
||||
}
|
||||
if !kvm.check_extension(Cap::SplitIrqchip) {
|
||||
return Err(KvmError::CapabilityMissing(Cap::SplitIrqchip));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct VcpuKvmState {
|
||||
pub msrs: MsrEntries,
|
||||
pub vcpu_events: VcpuEvents,
|
||||
pub regs: StandardRegisters,
|
||||
pub sregs: SpecialRegisters,
|
||||
pub fpu: FpuState,
|
||||
pub lapic_state: LapicState,
|
||||
pub xsave: Xsave,
|
||||
pub xcrs: ExtendedControlRegisters,
|
||||
pub mp_state: MpState,
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
// Copyright © 2019 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
|
||||
//
|
||||
// Copyright © 2020, Microsoft Corporation
|
||||
//
|
||||
// Copyright 2018-2019 CrowdStrike, Inc.
|
||||
//
|
||||
//
|
||||
|
||||
//! A generic abstraction around hypervisor functionality
|
||||
//!
|
||||
//! This crate offers a trait abstraction for underlying hypervisors
|
||||
//!
|
||||
//! # Platform support
|
||||
//!
|
||||
//! - x86_64
|
||||
//! - arm64
|
||||
//!
|
||||
|
||||
#[macro_use]
|
||||
extern crate anyhow;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
extern crate serde;
|
||||
extern crate serde_derive;
|
||||
extern crate serde_json;
|
||||
extern crate thiserror;
|
||||
|
||||
/// KVM implementation module
|
||||
pub mod kvm;
|
||||
|
||||
/// Hypevisor related module
|
||||
pub mod hypervisor;
|
||||
|
||||
/// Vm related module
|
||||
pub mod vm;
|
||||
|
||||
/// Architecture specific definitions
|
||||
pub mod arch;
|
||||
|
||||
/// CPU related module
|
||||
mod cpu;
|
||||
|
||||
/// Device related module
|
||||
mod device;
|
||||
|
||||
pub use crate::hypervisor::{Hypervisor, HypervisorError};
|
||||
pub use cpu::{HypervisorCpuError, Vcpu, VmExit};
|
||||
pub use device::{Device, HypervisorDeviceError};
|
||||
pub use kvm::*;
|
||||
pub use vm::{DataMatch, HypervisorVmError, Vm};
|
||||
|
||||
use std::sync::Arc;
|
||||
pub fn new() -> std::result::Result<Arc<dyn Hypervisor>, HypervisorError> {
|
||||
#[cfg(feature = "kvm")]
|
||||
let hv = kvm::KvmHypervisor::new()?;
|
||||
|
||||
Ok(Arc::new(hv))
|
||||
}
|
||||
@@ -1,182 +0,0 @@
|
||||
// Copyright © 2019 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
|
||||
//
|
||||
// Copyright © 2020, Microsoft Corporation
|
||||
//
|
||||
// Copyright 2018-2019 CrowdStrike, Inc.
|
||||
//
|
||||
//
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
use crate::aarch64::VcpuInit;
|
||||
use crate::cpu::Vcpu;
|
||||
use crate::device::Device;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use crate::ClockData;
|
||||
use crate::{CreateDevice, IoEventAddress, IrqRoutingEntry, MemoryRegion};
|
||||
use kvm_ioctls::Cap;
|
||||
use std::sync::Arc;
|
||||
use thiserror::Error;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
///
|
||||
/// I/O events data matches (32 or 64 bits).
|
||||
///
|
||||
pub enum DataMatch {
|
||||
DataMatch32(u32),
|
||||
DataMatch64(u64),
|
||||
}
|
||||
|
||||
impl Into<u64> for DataMatch {
|
||||
fn into(self) -> u64 {
|
||||
match self {
|
||||
DataMatch::DataMatch32(dm) => dm.into(),
|
||||
DataMatch::DataMatch64(dm) => dm,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
///
|
||||
/// Enum for VM error
|
||||
pub enum HypervisorVmError {
|
||||
///
|
||||
/// Create Vcpu error
|
||||
///
|
||||
#[error("Failed to create Vcpu: {0}")]
|
||||
CreateVcpu(#[source] anyhow::Error),
|
||||
///
|
||||
/// TSS address error
|
||||
///
|
||||
#[error("Failed to set TSS address: {0}")]
|
||||
SetTssAddress(#[source] anyhow::Error),
|
||||
///
|
||||
/// Create interrupt controller error
|
||||
///
|
||||
#[error("Failed to create interrupt controller: {0}")]
|
||||
CreateIrq(#[source] anyhow::Error),
|
||||
///
|
||||
/// Register interrupt event error
|
||||
///
|
||||
#[error("Failed to register interrupt event: {0}")]
|
||||
RegisterIrqFd(#[source] anyhow::Error),
|
||||
///
|
||||
/// Un register interrupt event error
|
||||
///
|
||||
#[error("Failed to unregister interrupt event: {0}")]
|
||||
UnregisterIrqFd(#[source] anyhow::Error),
|
||||
///
|
||||
/// Register IO event error
|
||||
///
|
||||
#[error("Failed to register IO event: {0}")]
|
||||
RegisterIoEvent(#[source] anyhow::Error),
|
||||
///
|
||||
/// Unregister IO event error
|
||||
///
|
||||
#[error("Failed to unregister IO event: {0}")]
|
||||
UnregisterIoEvent(#[source] anyhow::Error),
|
||||
///
|
||||
/// Set GSI routing error
|
||||
///
|
||||
#[error("Failed to set GSI routing: {0}")]
|
||||
SetGsiRouting(#[source] anyhow::Error),
|
||||
///
|
||||
/// Set user memory error
|
||||
///
|
||||
#[error("Failed to set user memory: {0}")]
|
||||
SetUserMemory(#[source] anyhow::Error),
|
||||
///
|
||||
/// Create device error
|
||||
///
|
||||
#[error("Failed to set GSI routing: {0}")]
|
||||
CreateDevice(#[source] anyhow::Error),
|
||||
///
|
||||
/// Get preferred target error
|
||||
///
|
||||
#[error("Failed to get preferred target: {0}")]
|
||||
GetPreferredTarget(#[source] anyhow::Error),
|
||||
///
|
||||
/// Enable split Irq error
|
||||
///
|
||||
#[error("Failed to enable split Irq: {0}")]
|
||||
EnableSplitIrq(#[source] anyhow::Error),
|
||||
///
|
||||
/// Get clock error
|
||||
///
|
||||
#[error("Failed to get clock: {0}")]
|
||||
GetClock(#[source] anyhow::Error),
|
||||
///
|
||||
/// Set clock error
|
||||
///
|
||||
#[error("Failed to set clock: {0}")]
|
||||
SetClock(#[source] anyhow::Error),
|
||||
///
|
||||
/// Create passthrough device
|
||||
///
|
||||
#[error("Failed to create passthrough device: {0}")]
|
||||
CreatePassthroughDevice(#[source] anyhow::Error),
|
||||
}
|
||||
///
|
||||
/// Result type for returning from a function
|
||||
///
|
||||
pub type Result<T> = std::result::Result<T, HypervisorVmError>;
|
||||
|
||||
///
|
||||
/// Trait to represent a Vm
|
||||
///
|
||||
/// This crate provides a hypervisor-agnostic interfaces for Vm
|
||||
///
|
||||
pub trait Vm: Send + Sync {
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
/// Sets the address of the three-page region in the VM's address space.
|
||||
fn set_tss_address(&self, offset: usize) -> Result<()>;
|
||||
/// Creates an in-kernel interrupt controller.
|
||||
fn create_irq_chip(&self) -> Result<()>;
|
||||
/// Registers an event that will, when signaled, trigger the `gsi` IRQ.
|
||||
fn register_irqfd(&self, fd: &EventFd, gsi: u32) -> Result<()>;
|
||||
/// Unregister an event that will, when signaled, trigger the `gsi` IRQ.
|
||||
fn unregister_irqfd(&self, fd: &EventFd, gsi: u32) -> Result<()>;
|
||||
/// Creates a new KVM vCPU file descriptor and maps the memory corresponding
|
||||
fn create_vcpu(&self, id: u8) -> Result<Arc<dyn Vcpu>>;
|
||||
/// Registers an event to be signaled whenever a certain address is written to.
|
||||
fn register_ioevent(
|
||||
&self,
|
||||
fd: &EventFd,
|
||||
addr: &IoEventAddress,
|
||||
datamatch: Option<DataMatch>,
|
||||
) -> Result<()>;
|
||||
/// Unregister an event from a certain address it has been previously registered to.
|
||||
fn unregister_ioevent(&self, fd: &EventFd, addr: &IoEventAddress) -> Result<()>;
|
||||
/// Sets the GSI routing table entries, overwriting any previously set
|
||||
fn set_gsi_routing(&self, entries: &[IrqRoutingEntry]) -> Result<()>;
|
||||
/// Creates a memory region structure that can be used with set_user_memory_region
|
||||
fn make_user_memory_region(
|
||||
&self,
|
||||
slot: u32,
|
||||
guest_phys_addr: u64,
|
||||
memory_size: u64,
|
||||
userspace_addr: u64,
|
||||
readonly: bool,
|
||||
) -> MemoryRegion;
|
||||
/// Creates/modifies a guest physical memory slot.
|
||||
fn set_user_memory_region(&self, user_memory_region: MemoryRegion) -> Result<()>;
|
||||
/// Creates an emulated device in the kernel.
|
||||
fn create_device(&self, device: &mut CreateDevice) -> Result<Arc<dyn Device>>;
|
||||
/// Returns the preferred CPU target type which can be emulated by KVM on underlying host.
|
||||
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
|
||||
fn get_preferred_target(&self, kvi: &mut VcpuInit) -> Result<()>;
|
||||
/// Enable split Irq capability
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
fn enable_split_irq(&self) -> Result<()>;
|
||||
/// Retrieve guest clock.
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
fn get_clock(&self) -> Result<ClockData>;
|
||||
/// Set guest clock.
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
fn set_clock(&self, data: &ClockData) -> Result<()>;
|
||||
/// Checks if a particular `Cap` is available.
|
||||
fn check_extension(&self, c: Cap) -> bool;
|
||||
/// Create a device that is used for passthrough
|
||||
fn create_passthrough_device(&self) -> Result<Arc<dyn Device>>;
|
||||
}
|
||||
@@ -4,4 +4,4 @@ version = "0.1.0"
|
||||
authors = ["The Chromium OS Authors"]
|
||||
|
||||
[dependencies]
|
||||
vmm-sys-util = ">=0.3.1"
|
||||
vmm-sys-util = "0.4.0"
|
||||
|
||||
@@ -4,18 +4,14 @@ version = "0.1.0"
|
||||
authors = ["The Chromium OS Authors"]
|
||||
|
||||
[dependencies]
|
||||
epoll = ">=4.0.1"
|
||||
libc = "0.2.74"
|
||||
log = "0.4.11"
|
||||
net_gen = { path = "../net_gen" }
|
||||
libc = "0.2.66"
|
||||
rand = "0.7.3"
|
||||
serde = "1.0.114"
|
||||
virtio-bindings = "0.1.0"
|
||||
vm-memory = { version = "0.2.1", features = ["backend-mmap", "backend-atomic"] }
|
||||
vm-virtio = { path = "../vm-virtio" }
|
||||
vmm-sys-util = ">=0.3.1"
|
||||
serde = "1.0.104"
|
||||
vmm-sys-util = "0.4.0"
|
||||
|
||||
net_gen = { path = "../net_gen" }
|
||||
|
||||
[dev-dependencies]
|
||||
lazy_static = "1.3.0"
|
||||
pnet = "0.26.0"
|
||||
serde_json = "1.0.57"
|
||||
lazy_static = "1.4.0"
|
||||
pnet = "0.25.0"
|
||||
serde_json = "1.0.48"
|
||||
|
||||
@@ -11,28 +11,21 @@
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
extern crate libc;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
extern crate net_gen;
|
||||
extern crate rand;
|
||||
extern crate serde;
|
||||
extern crate virtio_bindings;
|
||||
extern crate vm_memory;
|
||||
extern crate vm_virtio;
|
||||
|
||||
extern crate net_gen;
|
||||
extern crate vmm_sys_util;
|
||||
|
||||
mod mac;
|
||||
mod open_tap;
|
||||
mod queue_pair;
|
||||
mod tap;
|
||||
|
||||
use std::io::Error as IoError;
|
||||
use std::os::unix::io::{FromRawFd, RawFd};
|
||||
use std::{io, mem, net};
|
||||
use std::mem;
|
||||
use std::net;
|
||||
use std::os::unix::io::FromRawFd;
|
||||
|
||||
pub use mac::{MacAddr, MAC_ADDR_LEN};
|
||||
pub use open_tap::{open_tap, Error as OpenTapError};
|
||||
pub use queue_pair::{NetCounters, NetQueuePair, NetQueuePairError, RxVirtio, TxVirtio};
|
||||
pub use tap::{Error as TapError, Tap};
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -69,39 +62,6 @@ fn create_socket() -> Result<net::UdpSocket> {
|
||||
Ok(unsafe { net::UdpSocket::from_raw_fd(sock) })
|
||||
}
|
||||
|
||||
fn vnet_hdr_len() -> usize {
|
||||
use virtio_bindings::bindings::virtio_net::virtio_net_hdr_v1;
|
||||
std::mem::size_of::<virtio_net_hdr_v1>()
|
||||
}
|
||||
|
||||
pub fn register_listener(
|
||||
epoll_fd: RawFd,
|
||||
fd: RawFd,
|
||||
ev_type: epoll::Events,
|
||||
data: u64,
|
||||
) -> std::result::Result<(), io::Error> {
|
||||
epoll::ctl(
|
||||
epoll_fd,
|
||||
epoll::ControlOptions::EPOLL_CTL_ADD,
|
||||
fd,
|
||||
epoll::Event::new(ev_type, data),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn unregister_listener(
|
||||
epoll_fd: RawFd,
|
||||
fd: RawFd,
|
||||
ev_type: epoll::Events,
|
||||
data: u64,
|
||||
) -> std::result::Result<(), io::Error> {
|
||||
epoll::ctl(
|
||||
epoll_fd,
|
||||
epoll::ControlOptions::EPOLL_CTL_DEL,
|
||||
fd,
|
||||
epoll::Event::new(ev_type, data),
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -9,7 +9,6 @@ use rand::Rng;
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
use std::result::Result;
|
||||
use std::str::FromStr;
|
||||
|
||||
use serde::de::{Deserialize, Deserializer, Error};
|
||||
use serde::ser::{Serialize, Serializer};
|
||||
@@ -121,18 +120,6 @@ impl<'de> Deserialize<'de> for MacAddr {
|
||||
}
|
||||
}
|
||||
|
||||
pub enum MacAddrParseError {
|
||||
InvalidValue(String),
|
||||
}
|
||||
|
||||
impl FromStr for MacAddr {
|
||||
type Err = MacAddrParseError;
|
||||
|
||||
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
|
||||
MacAddr::parse_str(s).map_err(|_| MacAddrParseError::InvalidValue(s.to_owned()))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
extern crate serde_json;
|
||||
|
||||
@@ -1,116 +0,0 @@
|
||||
// Copyright (c) 2020 Intel Corporation. All rights reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||
|
||||
use super::{vnet_hdr_len, MacAddr, Tap, TapError};
|
||||
use std::net::Ipv4Addr;
|
||||
use std::path::Path;
|
||||
use std::{fs, io};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Failed to convert an hexadecimal string into an integer.
|
||||
ConvertHexStringToInt(std::num::ParseIntError),
|
||||
/// Error related to the multiqueue support (no support TAP side).
|
||||
MultiQueueNoTapSupport,
|
||||
/// Error related to the multiqueue support (no support device side).
|
||||
MultiQueueNoDeviceSupport,
|
||||
/// Failed to read the TAP flags from sysfs.
|
||||
ReadSysfsTunFlags(io::Error),
|
||||
/// Open tap device failed.
|
||||
TapOpen(TapError),
|
||||
/// Setting tap IP failed.
|
||||
TapSetIp(TapError),
|
||||
/// Setting tap netmask failed.
|
||||
TapSetNetmask(TapError),
|
||||
/// Setting MAC address failed
|
||||
TapSetMac(TapError),
|
||||
/// Getting MAC address failed
|
||||
TapGetMac(TapError),
|
||||
/// Setting tap interface offload flags failed.
|
||||
TapSetOffload(TapError),
|
||||
/// Setting vnet header size failed.
|
||||
TapSetVnetHdrSize(TapError),
|
||||
/// Enabling tap interface failed.
|
||||
TapEnable(TapError),
|
||||
}
|
||||
|
||||
type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
fn check_mq_support(if_name: &Option<&str>, queue_pairs: usize) -> Result<()> {
|
||||
if let Some(tap_name) = if_name {
|
||||
let mq = queue_pairs > 1;
|
||||
let path = format!("/sys/class/net/{}/tun_flags", tap_name);
|
||||
// interface does not exist, check is not required
|
||||
if !Path::new(&path).exists() {
|
||||
return Ok(());
|
||||
}
|
||||
let tun_flags_str = fs::read_to_string(path).map_err(Error::ReadSysfsTunFlags)?;
|
||||
let tun_flags = u32::from_str_radix(tun_flags_str.trim().trim_start_matches("0x"), 16)
|
||||
.map_err(Error::ConvertHexStringToInt)?;
|
||||
if (tun_flags & net_gen::IFF_MULTI_QUEUE != 0) && !mq {
|
||||
return Err(Error::MultiQueueNoDeviceSupport);
|
||||
} else if (tun_flags & net_gen::IFF_MULTI_QUEUE == 0) && mq {
|
||||
return Err(Error::MultiQueueNoTapSupport);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Create a new virtio network device with the given IP address and
|
||||
/// netmask.
|
||||
pub fn open_tap(
|
||||
if_name: Option<&str>,
|
||||
ip_addr: Option<Ipv4Addr>,
|
||||
netmask: Option<Ipv4Addr>,
|
||||
host_mac: &mut Option<MacAddr>,
|
||||
num_rx_q: usize,
|
||||
) -> Result<Vec<Tap>> {
|
||||
let mut taps: Vec<Tap> = Vec::new();
|
||||
let mut ifname: String = String::new();
|
||||
let vnet_hdr_size = vnet_hdr_len() as i32;
|
||||
let flag = net_gen::TUN_F_CSUM | net_gen::TUN_F_UFO | net_gen::TUN_F_TSO4 | net_gen::TUN_F_TSO6;
|
||||
|
||||
// In case the tap interface already exists, check if the number of
|
||||
// queues is appropriate. The tap might not support multiqueue while
|
||||
// the number of queues indicates the user expects multiple queues, or
|
||||
// on the contrary, the tap might support multiqueue while the number
|
||||
// of queues indicates the user doesn't expect multiple queues.
|
||||
check_mq_support(&if_name, num_rx_q)?;
|
||||
|
||||
for i in 0..num_rx_q {
|
||||
let tap: Tap;
|
||||
if i == 0 {
|
||||
tap = match if_name {
|
||||
Some(name) => Tap::open_named(name, num_rx_q).map_err(Error::TapOpen)?,
|
||||
None => Tap::new(num_rx_q).map_err(Error::TapOpen)?,
|
||||
};
|
||||
if let Some(ip) = ip_addr {
|
||||
tap.set_ip_addr(ip).map_err(Error::TapSetIp)?;
|
||||
}
|
||||
if let Some(mask) = netmask {
|
||||
tap.set_netmask(mask).map_err(Error::TapSetNetmask)?;
|
||||
}
|
||||
if let Some(mac) = host_mac {
|
||||
tap.set_mac_addr(*mac).map_err(Error::TapSetMac)?
|
||||
} else {
|
||||
*host_mac = Some(tap.get_mac_addr().map_err(Error::TapGetMac)?)
|
||||
}
|
||||
tap.enable().map_err(Error::TapEnable)?;
|
||||
tap.set_offload(flag).map_err(Error::TapSetOffload)?;
|
||||
|
||||
tap.set_vnet_hdr_size(vnet_hdr_size)
|
||||
.map_err(Error::TapSetVnetHdrSize)?;
|
||||
|
||||
ifname = String::from_utf8(tap.get_if_name()).unwrap();
|
||||
} else {
|
||||
tap = Tap::open_named(ifname.as_str(), num_rx_q).map_err(Error::TapOpen)?;
|
||||
tap.set_offload(flag).map_err(Error::TapSetOffload)?;
|
||||
|
||||
tap.set_vnet_hdr_size(vnet_hdr_size)
|
||||
.map_err(Error::TapSetVnetHdrSize)?;
|
||||
}
|
||||
taps.push(tap);
|
||||
}
|
||||
Ok(taps)
|
||||
}
|
||||
@@ -1,380 +0,0 @@
|
||||
// Copyright (c) 2020 Intel Corporation. All rights reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||
|
||||
use super::{register_listener, unregister_listener, vnet_hdr_len, Tap};
|
||||
use libc::EAGAIN;
|
||||
use std::cmp;
|
||||
use std::io;
|
||||
use std::io::{Read, Write};
|
||||
use std::num::Wrapping;
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use std::sync::Arc;
|
||||
use vm_memory::{Bytes, GuestAddress, GuestAddressSpace, GuestMemoryAtomic, GuestMemoryMmap};
|
||||
use vm_virtio::{DescriptorChain, Queue};
|
||||
|
||||
/// The maximum buffer size when segmentation offload is enabled. This
|
||||
/// includes the 12-byte virtio net header.
|
||||
/// http://docs.oasis-open.org/virtio/virtio/v1.0/virtio-v1.0.html#x1-1740003
|
||||
const MAX_BUFFER_SIZE: usize = 65562;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TxVirtio {
|
||||
pub iovec: Vec<(GuestAddress, usize)>,
|
||||
pub frame_buf: [u8; MAX_BUFFER_SIZE],
|
||||
pub counter_bytes: Wrapping<u64>,
|
||||
pub counter_frames: Wrapping<u64>,
|
||||
}
|
||||
|
||||
impl Default for TxVirtio {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl TxVirtio {
|
||||
pub fn new() -> Self {
|
||||
TxVirtio {
|
||||
iovec: Vec::new(),
|
||||
frame_buf: [0u8; MAX_BUFFER_SIZE],
|
||||
counter_bytes: Wrapping(0),
|
||||
counter_frames: Wrapping(0),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process_desc_chain(&mut self, mem: &GuestMemoryMmap, tap: &mut Tap, queue: &mut Queue) {
|
||||
while let Some(avail_desc) = queue.iter(&mem).next() {
|
||||
let head_index = avail_desc.index;
|
||||
let mut read_count = 0;
|
||||
let mut next_desc = Some(avail_desc);
|
||||
|
||||
self.iovec.clear();
|
||||
while let Some(desc) = next_desc {
|
||||
if desc.is_write_only() {
|
||||
break;
|
||||
}
|
||||
self.iovec.push((desc.addr, desc.len as usize));
|
||||
read_count += desc.len as usize;
|
||||
next_desc = desc.next_descriptor();
|
||||
}
|
||||
|
||||
read_count = 0;
|
||||
// Copy buffer from across multiple descriptors.
|
||||
// TODO(performance - Issue #420): change this to use `writev()` instead of `write()`
|
||||
// and get rid of the intermediate buffer.
|
||||
for (desc_addr, desc_len) in self.iovec.drain(..) {
|
||||
let limit = cmp::min((read_count + desc_len) as usize, self.frame_buf.len());
|
||||
|
||||
let read_result =
|
||||
mem.read_slice(&mut self.frame_buf[read_count..limit as usize], desc_addr);
|
||||
match read_result {
|
||||
Ok(_) => {
|
||||
// Increment by number of bytes actually read
|
||||
read_count += limit - read_count;
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Failed to read slice: {:?}", e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let write_result = tap.write(&self.frame_buf[..read_count]);
|
||||
match write_result {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
println!("net: tx: error failed to write to tap: {}", e);
|
||||
}
|
||||
};
|
||||
|
||||
self.counter_bytes += Wrapping((read_count - vnet_hdr_len()) as u64);
|
||||
self.counter_frames += Wrapping(1);
|
||||
|
||||
queue.add_used(&mem, head_index, 0);
|
||||
queue.update_avail_event(&mem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct RxVirtio {
|
||||
pub deferred_frame: bool,
|
||||
pub deferred_irqs: bool,
|
||||
pub bytes_read: usize,
|
||||
pub frame_buf: [u8; MAX_BUFFER_SIZE],
|
||||
pub counter_bytes: Wrapping<u64>,
|
||||
pub counter_frames: Wrapping<u64>,
|
||||
}
|
||||
|
||||
impl Default for RxVirtio {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl RxVirtio {
|
||||
pub fn new() -> Self {
|
||||
RxVirtio {
|
||||
deferred_frame: false,
|
||||
deferred_irqs: false,
|
||||
bytes_read: 0,
|
||||
frame_buf: [0u8; MAX_BUFFER_SIZE],
|
||||
counter_bytes: Wrapping(0),
|
||||
counter_frames: Wrapping(0),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process_desc_chain(
|
||||
&mut self,
|
||||
mem: &GuestMemoryMmap,
|
||||
mut next_desc: Option<DescriptorChain>,
|
||||
queue: &mut Queue,
|
||||
) -> bool {
|
||||
let head_index = next_desc.as_ref().unwrap().index;
|
||||
let mut write_count = 0;
|
||||
|
||||
// Copy from frame into buffer, which may span multiple descriptors.
|
||||
loop {
|
||||
match next_desc {
|
||||
Some(desc) => {
|
||||
if !desc.is_write_only() {
|
||||
break;
|
||||
}
|
||||
let limit = cmp::min(write_count + desc.len as usize, self.bytes_read);
|
||||
let source_slice = &self.frame_buf[write_count..limit];
|
||||
let write_result = mem.write_slice(source_slice, desc.addr);
|
||||
|
||||
match write_result {
|
||||
Ok(_) => {
|
||||
write_count = limit;
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to write slice: {:?}", e);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
if write_count >= self.bytes_read {
|
||||
break;
|
||||
}
|
||||
next_desc = desc.next_descriptor();
|
||||
}
|
||||
None => {
|
||||
warn!("Receiving buffer is too small to hold frame of current size");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.counter_bytes += Wrapping((write_count - vnet_hdr_len()) as u64);
|
||||
self.counter_frames += Wrapping(1);
|
||||
|
||||
queue.add_used(&mem, head_index, write_count as u32);
|
||||
queue.update_avail_event(&mem);
|
||||
|
||||
// Mark that we have at least one pending packet and we need to interrupt the guest.
|
||||
self.deferred_irqs = true;
|
||||
|
||||
// Update the frame_buf buffer.
|
||||
if write_count < self.bytes_read {
|
||||
self.frame_buf.copy_within(write_count..self.bytes_read, 0);
|
||||
self.bytes_read -= write_count;
|
||||
false
|
||||
} else {
|
||||
self.bytes_read = 0;
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub struct NetCounters {
|
||||
pub tx_bytes: Arc<AtomicU64>,
|
||||
pub tx_frames: Arc<AtomicU64>,
|
||||
pub rx_bytes: Arc<AtomicU64>,
|
||||
pub rx_frames: Arc<AtomicU64>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum NetQueuePairError {
|
||||
/// No memory configured
|
||||
NoMemoryConfigured,
|
||||
/// Error registering listener
|
||||
RegisterListener(io::Error),
|
||||
/// Error unregistering listener
|
||||
UnregisterListener(io::Error),
|
||||
/// Error reading from the TAP device
|
||||
FailedReadTap,
|
||||
}
|
||||
|
||||
pub struct NetQueuePair {
|
||||
pub mem: Option<GuestMemoryAtomic<GuestMemoryMmap>>,
|
||||
pub tap: Tap,
|
||||
pub rx: RxVirtio,
|
||||
pub tx: TxVirtio,
|
||||
pub epoll_fd: Option<RawFd>,
|
||||
pub rx_tap_listening: bool,
|
||||
pub counters: NetCounters,
|
||||
pub tap_event_id: u16,
|
||||
}
|
||||
|
||||
impl NetQueuePair {
|
||||
// Copies a single frame from `self.rx.frame_buf` into the guest. Returns true
|
||||
// if a buffer was used, and false if the frame must be deferred until a buffer
|
||||
// is made available by the driver.
|
||||
fn rx_single_frame(&mut self, mut queue: &mut Queue) -> Result<bool, NetQueuePairError> {
|
||||
let mem = self
|
||||
.mem
|
||||
.as_ref()
|
||||
.ok_or(NetQueuePairError::NoMemoryConfigured)
|
||||
.map(|m| m.memory())?;
|
||||
let next_desc = queue.iter(&mem).next();
|
||||
|
||||
if next_desc.is_none() {
|
||||
// Queue has no available descriptors
|
||||
if self.rx_tap_listening {
|
||||
unregister_listener(
|
||||
self.epoll_fd.unwrap(),
|
||||
self.tap.as_raw_fd(),
|
||||
epoll::Events::EPOLLIN,
|
||||
u64::from(self.tap_event_id),
|
||||
)
|
||||
.map_err(NetQueuePairError::UnregisterListener)?;
|
||||
self.rx_tap_listening = false;
|
||||
info!("Listener unregistered");
|
||||
}
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
Ok(self.rx.process_desc_chain(&mem, next_desc, &mut queue))
|
||||
}
|
||||
|
||||
fn process_rx(&mut self, queue: &mut Queue) -> Result<bool, NetQueuePairError> {
|
||||
// Read as many frames as possible.
|
||||
loop {
|
||||
match self.read_tap() {
|
||||
Ok(count) => {
|
||||
self.rx.bytes_read = count;
|
||||
if !self.rx_single_frame(queue)? {
|
||||
self.rx.deferred_frame = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
// The tap device is non-blocking, so any error aside from EAGAIN is
|
||||
// unexpected.
|
||||
match e.raw_os_error() {
|
||||
Some(err) if err == EAGAIN => (),
|
||||
_ => {
|
||||
error!("Failed to read tap: {:?}", e);
|
||||
return Err(NetQueuePairError::FailedReadTap);
|
||||
}
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Consume the counters from the Rx/Tx queues and accumulate into
|
||||
// the counters for the device as whole. This consumption is needed
|
||||
// to handle MQ.
|
||||
self.counters
|
||||
.rx_bytes
|
||||
.fetch_add(self.rx.counter_bytes.0, Ordering::AcqRel);
|
||||
self.counters
|
||||
.rx_frames
|
||||
.fetch_add(self.rx.counter_frames.0, Ordering::AcqRel);
|
||||
self.rx.counter_bytes = Wrapping(0);
|
||||
self.rx.counter_frames = Wrapping(0);
|
||||
|
||||
if self.rx.deferred_irqs {
|
||||
self.rx.deferred_irqs = false;
|
||||
let mem = self
|
||||
.mem
|
||||
.as_ref()
|
||||
.ok_or(NetQueuePairError::NoMemoryConfigured)
|
||||
.map(|m| m.memory())?;
|
||||
Ok(queue.needs_notification(&mem, queue.next_used))
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resume_rx(&mut self, queue: &mut Queue) -> Result<bool, NetQueuePairError> {
|
||||
if !self.rx_tap_listening {
|
||||
register_listener(
|
||||
self.epoll_fd.unwrap(),
|
||||
self.tap.as_raw_fd(),
|
||||
epoll::Events::EPOLLIN,
|
||||
u64::from(self.tap_event_id),
|
||||
)
|
||||
.map_err(NetQueuePairError::RegisterListener)?;
|
||||
self.rx_tap_listening = true;
|
||||
info!("Listener registered");
|
||||
}
|
||||
if self.rx.deferred_frame {
|
||||
if self.rx_single_frame(queue)? {
|
||||
self.rx.deferred_frame = false;
|
||||
// process_rx() was interrupted possibly before consuming all
|
||||
// packets in the tap; try continuing now.
|
||||
self.process_rx(queue)
|
||||
} else if self.rx.deferred_irqs {
|
||||
self.rx.deferred_irqs = false;
|
||||
let mem = self
|
||||
.mem
|
||||
.as_ref()
|
||||
.ok_or(NetQueuePairError::NoMemoryConfigured)
|
||||
.map(|m| m.memory())?;
|
||||
Ok(queue.needs_notification(&mem, queue.next_used))
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process_tx(&mut self, mut queue: &mut Queue) -> Result<bool, NetQueuePairError> {
|
||||
let mem = self
|
||||
.mem
|
||||
.as_ref()
|
||||
.ok_or(NetQueuePairError::NoMemoryConfigured)
|
||||
.map(|m| m.memory())?;
|
||||
self.tx.process_desc_chain(&mem, &mut self.tap, &mut queue);
|
||||
|
||||
self.counters
|
||||
.tx_bytes
|
||||
.fetch_add(self.tx.counter_bytes.0, Ordering::AcqRel);
|
||||
self.counters
|
||||
.tx_frames
|
||||
.fetch_add(self.tx.counter_frames.0, Ordering::AcqRel);
|
||||
self.tx.counter_bytes = Wrapping(0);
|
||||
self.tx.counter_frames = Wrapping(0);
|
||||
|
||||
Ok(queue.needs_notification(&mem, queue.next_used))
|
||||
}
|
||||
|
||||
pub fn process_rx_tap(&mut self, mut queue: &mut Queue) -> Result<bool, NetQueuePairError> {
|
||||
if self.rx.deferred_frame
|
||||
// Process a deferred frame first if available. Don't read from tap again
|
||||
// until we manage to receive this deferred frame.
|
||||
{
|
||||
if self.rx_single_frame(&mut queue)? {
|
||||
self.rx.deferred_frame = false;
|
||||
self.process_rx(&mut queue)
|
||||
} else if self.rx.deferred_irqs {
|
||||
self.rx.deferred_irqs = false;
|
||||
Ok(true)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
} else {
|
||||
self.process_rx(&mut queue)
|
||||
}
|
||||
}
|
||||
|
||||
fn read_tap(&mut self) -> io::Result<usize> {
|
||||
self.tap.read(&mut self.rx.frame_buf)
|
||||
}
|
||||
}
|
||||
@@ -5,33 +5,28 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the THIRD-PARTY file.
|
||||
|
||||
use super::{create_sockaddr, create_socket, Error as NetUtilError, MacAddr};
|
||||
use mac::MAC_ADDR_LEN;
|
||||
use net_gen;
|
||||
use std::fs::File;
|
||||
use std::io::{Error as IoError, Read, Result as IoResult, Write};
|
||||
use std::net;
|
||||
use std::os::raw::*;
|
||||
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
|
||||
|
||||
use super::{create_sockaddr, create_socket, Error as NetUtilError};
|
||||
use libc;
|
||||
use net_gen;
|
||||
use vmm_sys_util::ioctl::{ioctl_with_mut_ref, ioctl_with_ref, ioctl_with_val};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Couldn't open /dev/net/tun.
|
||||
OpenTun(IoError),
|
||||
/// Unable to configure tap interface.
|
||||
ConfigureTap(IoError),
|
||||
/// Unable to retrieve features.
|
||||
GetFeatures(IoError),
|
||||
/// Missing multiqueue support in the kernel.
|
||||
MultiQueueKernelSupport,
|
||||
/// Unable to create tap interface.
|
||||
CreateTap(IoError),
|
||||
/// ioctl failed.
|
||||
IoctlError(IoError),
|
||||
/// Failed to create a socket.
|
||||
NetUtil(NetUtilError),
|
||||
InvalidIfname,
|
||||
/// Error parsing MAC data
|
||||
MacParsing(()),
|
||||
}
|
||||
|
||||
pub type Result<T> = ::std::result::Result<T, Error>;
|
||||
@@ -101,20 +96,6 @@ impl Tap {
|
||||
// We just checked that the fd is valid.
|
||||
let tuntap = unsafe { File::from_raw_fd(fd) };
|
||||
|
||||
// Let's validate some features before going any further.
|
||||
// ioctl is safe since we call it with a valid tap fd and check the return
|
||||
// value.
|
||||
let mut features = 0;
|
||||
let ret = unsafe { ioctl_with_mut_ref(&tuntap, net_gen::TUNGETFEATURES(), &mut features) };
|
||||
if ret < 0 {
|
||||
return Err(Error::GetFeatures(IoError::last_os_error()));
|
||||
}
|
||||
|
||||
// Check if the user parameters match the kernel support for MQ
|
||||
if (features & net_gen::IFF_MULTI_QUEUE == 0) && num_queue_pairs > 1 {
|
||||
return Err(Error::MultiQueueKernelSupport);
|
||||
}
|
||||
|
||||
// This is pretty messy because of the unions used by ifreq. Since we
|
||||
// don't call as_mut on the same union field more than once, this block
|
||||
// is safe.
|
||||
@@ -134,8 +115,9 @@ impl Tap {
|
||||
// ioctl is safe since we call it with a valid tap fd and check the return
|
||||
// value.
|
||||
let ret = unsafe { ioctl_with_mut_ref(&tuntap, net_gen::TUNSETIFF(), &mut ifreq) };
|
||||
|
||||
if ret < 0 {
|
||||
return Err(Error::ConfigureTap(IoError::last_os_error()));
|
||||
return Err(Error::CreateTap(IoError::last_os_error()));
|
||||
}
|
||||
|
||||
let if_name_temp = unsafe { *ifreq.ifr_ifrn.ifrn_name.as_ref() };
|
||||
@@ -177,72 +159,6 @@ impl Tap {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set mac addr for tap interface.
|
||||
pub fn set_mac_addr(&self, addr: MacAddr) -> Result<()> {
|
||||
// Checking if the mac address already matches the desired one
|
||||
// is useful to avoid making the "set ioctl" in the case where
|
||||
// the VMM is running without the privilege to do that.
|
||||
// In practice this comes from a reboot after the configuration
|
||||
// has been update with the kernel generated address.
|
||||
if self.get_mac_addr()? == addr {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let sock = create_socket().map_err(Error::NetUtil)?;
|
||||
|
||||
let mut ifreq = self.get_ifreq();
|
||||
|
||||
// ioctl is safe. Called with a valid sock fd, and we check the return.
|
||||
#[allow(clippy::cast_lossless)]
|
||||
let ret =
|
||||
unsafe { ioctl_with_ref(&sock, net_gen::sockios::SIOCGIFHWADDR as c_ulong, &ifreq) };
|
||||
if ret < 0 {
|
||||
return Err(Error::IoctlError(IoError::last_os_error()));
|
||||
}
|
||||
// We only access one field of the ifru union, hence this is safe.
|
||||
unsafe {
|
||||
let ifru_hwaddr = ifreq.ifr_ifru.ifru_hwaddr.as_mut();
|
||||
for (i, v) in addr.get_bytes().iter().enumerate() {
|
||||
ifru_hwaddr.sa_data[i] = *v as c_char;
|
||||
}
|
||||
}
|
||||
|
||||
// ioctl is safe. Called with a valid sock fd, and we check the return.
|
||||
#[allow(clippy::cast_lossless)]
|
||||
let ret =
|
||||
unsafe { ioctl_with_ref(&sock, net_gen::sockios::SIOCSIFHWADDR as c_ulong, &ifreq) };
|
||||
if ret < 0 {
|
||||
return Err(Error::IoctlError(IoError::last_os_error()));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get mac addr for tap interface.
|
||||
pub fn get_mac_addr(&self) -> Result<MacAddr> {
|
||||
let sock = create_socket().map_err(Error::NetUtil)?;
|
||||
|
||||
let ifreq = self.get_ifreq();
|
||||
|
||||
// ioctl is safe. Called with a valid sock fd, and we check the return.
|
||||
#[allow(clippy::cast_lossless)]
|
||||
let ret =
|
||||
unsafe { ioctl_with_ref(&sock, net_gen::sockios::SIOCGIFHWADDR as c_ulong, &ifreq) };
|
||||
if ret < 0 {
|
||||
return Err(Error::IoctlError(IoError::last_os_error()));
|
||||
}
|
||||
|
||||
// We only access one field of the ifru union, hence this is safe.
|
||||
let addr = unsafe {
|
||||
let ifru_hwaddr = ifreq.ifr_ifru.ifru_hwaddr.as_ref();
|
||||
MacAddr::from_bytes(
|
||||
&*(&ifru_hwaddr.sa_data[0..MAC_ADDR_LEN] as *const _ as *const [u8]),
|
||||
)
|
||||
.map_err(Error::MacParsing)?
|
||||
};
|
||||
Ok(addr)
|
||||
}
|
||||
|
||||
/// Set the netmask for the subnet that the tap interface will exist on.
|
||||
pub fn set_netmask(&self, netmask: net::Ipv4Addr) -> Result<()> {
|
||||
let sock = create_socket().map_err(Error::NetUtil)?;
|
||||
@@ -286,26 +202,9 @@ impl Tap {
|
||||
|
||||
let mut ifreq = self.get_ifreq();
|
||||
|
||||
#[allow(clippy::cast_lossless)]
|
||||
let ret =
|
||||
unsafe { ioctl_with_ref(&sock, net_gen::sockios::SIOCGIFFLAGS as c_ulong, &ifreq) };
|
||||
if ret < 0 {
|
||||
return Err(Error::IoctlError(IoError::last_os_error()));
|
||||
}
|
||||
|
||||
// If TAP device is already up don't try and enable it
|
||||
let ifru_flags = unsafe { ifreq.ifr_ifru.ifru_flags.as_ref() };
|
||||
if ifru_flags
|
||||
& (net_gen::net_device_flags_IFF_UP | net_gen::net_device_flags_IFF_RUNNING) as i16
|
||||
== (net_gen::net_device_flags_IFF_UP | net_gen::net_device_flags_IFF_RUNNING) as i16
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// We only access one field of the ifru union, hence this is safe.
|
||||
unsafe {
|
||||
let ifru_flags = ifreq.ifr_ifru.ifru_flags.as_mut();
|
||||
|
||||
*ifru_flags =
|
||||
(net_gen::net_device_flags_IFF_UP | net_gen::net_device_flags_IFF_RUNNING) as i16;
|
||||
}
|
||||
@@ -527,7 +426,7 @@ mod tests {
|
||||
let interface = interfaces.into_iter().find(interface_name_matches).unwrap();
|
||||
|
||||
if let Ok(Ethernet(tx, rx)) = datalink::channel(&interface, Default::default()) {
|
||||
(interface.mac.unwrap(), tx, rx)
|
||||
(interface.mac_address(), tx, rx)
|
||||
} else {
|
||||
panic!("datalink channel error or unhandled channel type");
|
||||
}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
[package]
|
||||
name = "option_parser"
|
||||
version = "0.1.0"
|
||||
authors = ["The Cloud Hypervisor Authors"]
|
||||
edition = "2018"
|
||||
@@ -1,172 +0,0 @@
|
||||
// Copyright © 2020 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct OptionParser {
|
||||
options: HashMap<String, OptionParserValue>,
|
||||
}
|
||||
|
||||
struct OptionParserValue {
|
||||
value: Option<String>,
|
||||
requires_value: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum OptionParserError {
|
||||
UnknownOption(String),
|
||||
InvalidSyntax(String),
|
||||
Conversion(String, String),
|
||||
}
|
||||
|
||||
impl fmt::Display for OptionParserError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
OptionParserError::UnknownOption(s) => write!(f, "unknown option: {}", s),
|
||||
OptionParserError::InvalidSyntax(s) => write!(f, "invalid syntax:{}", s),
|
||||
OptionParserError::Conversion(field, value) => {
|
||||
write!(f, "unable to parse {} for {}", value, field)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
type OptionParserResult<T> = std::result::Result<T, OptionParserError>;
|
||||
|
||||
impl OptionParser {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
options: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse(&mut self, input: &str) -> OptionParserResult<()> {
|
||||
if input.trim().is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let options_list: Vec<&str> = input.trim().split(',').collect();
|
||||
|
||||
for option in options_list.iter() {
|
||||
let parts: Vec<&str> = option.split('=').collect();
|
||||
|
||||
match self.options.get_mut(parts[0]) {
|
||||
None => return Err(OptionParserError::UnknownOption(parts[0].to_owned())),
|
||||
Some(value) => {
|
||||
if value.requires_value {
|
||||
if parts.len() != 2 {
|
||||
return Err(OptionParserError::InvalidSyntax((*option).to_owned()));
|
||||
}
|
||||
value.value = Some(parts[1].trim().to_owned());
|
||||
} else {
|
||||
value.value = Some(String::new());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn add(&mut self, option: &str) -> &mut Self {
|
||||
self.options.insert(
|
||||
option.to_owned(),
|
||||
OptionParserValue {
|
||||
value: None,
|
||||
requires_value: true,
|
||||
},
|
||||
);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub fn add_valueless(&mut self, option: &str) -> &mut Self {
|
||||
self.options.insert(
|
||||
option.to_owned(),
|
||||
OptionParserValue {
|
||||
value: None,
|
||||
requires_value: false,
|
||||
},
|
||||
);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub fn get(&self, option: &str) -> Option<String> {
|
||||
self.options
|
||||
.get(option)
|
||||
.and_then(|v| v.value.clone())
|
||||
.and_then(|s| if s.is_empty() { None } else { Some(s) })
|
||||
}
|
||||
|
||||
pub fn is_set(&self, option: &str) -> bool {
|
||||
self.options
|
||||
.get(option)
|
||||
.and_then(|v| v.value.as_ref())
|
||||
.is_some()
|
||||
}
|
||||
|
||||
pub fn convert<T: FromStr>(&self, option: &str) -> OptionParserResult<Option<T>> {
|
||||
match self.get(option) {
|
||||
None => Ok(None),
|
||||
Some(v) => Ok(Some(v.parse().map_err(|_| {
|
||||
OptionParserError::Conversion(option.to_owned(), v.to_owned())
|
||||
})?)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Toggle(pub bool);
|
||||
|
||||
pub enum ToggleParseError {
|
||||
InvalidValue(String),
|
||||
}
|
||||
|
||||
impl FromStr for Toggle {
|
||||
type Err = ToggleParseError;
|
||||
|
||||
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
|
||||
match s.to_lowercase().as_str() {
|
||||
"" => Ok(Toggle(false)),
|
||||
"on" => Ok(Toggle(true)),
|
||||
"off" => Ok(Toggle(false)),
|
||||
"true" => Ok(Toggle(true)),
|
||||
"false" => Ok(Toggle(false)),
|
||||
_ => Err(ToggleParseError::InvalidValue(s.to_owned())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ByteSized(pub u64);
|
||||
|
||||
pub enum ByteSizedParseError {
|
||||
InvalidValue(String),
|
||||
}
|
||||
|
||||
impl FromStr for ByteSized {
|
||||
type Err = ByteSizedParseError;
|
||||
|
||||
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
|
||||
Ok(ByteSized({
|
||||
let s = s.trim();
|
||||
let shift = if s.ends_with('K') {
|
||||
10
|
||||
} else if s.ends_with('M') {
|
||||
20
|
||||
} else if s.ends_with('G') {
|
||||
30
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
let s = s.trim_end_matches(|c| c == 'K' || c == 'M' || c == 'G');
|
||||
s.parse::<u64>()
|
||||
.map_err(|_| ByteSizedParseError::InvalidValue(s.to_owned()))?
|
||||
<< shift
|
||||
}))
|
||||
}
|
||||
}
|
||||
@@ -5,22 +5,10 @@ authors = ["Samuel Ortiz <sameo@linux.intel.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
vm-allocator = { path = "../vm-allocator" }
|
||||
byteorder = "1.3.4"
|
||||
devices = { path = "../devices" }
|
||||
hypervisor = { path = "../hypervisor" }
|
||||
vfio-ioctls = { git = "https://github.com/cloud-hypervisor/vfio-ioctls", branch = "ch" }
|
||||
vmm-sys-util = ">=0.3.1"
|
||||
libc = "0.2.74"
|
||||
log = "0.4.11"
|
||||
serde = {version = ">=1.0.27", features = ["rc"] }
|
||||
serde_derive = ">=1.0.27"
|
||||
serde_json = ">=1.0.9"
|
||||
vm-allocator = { path = "../vm-allocator" }
|
||||
libc = "0.2.66"
|
||||
log = "0.4.8"
|
||||
vm-device = { path = "../vm-device" }
|
||||
vm-memory = "0.2.1"
|
||||
vm-migration = { path = "../vm-migration" }
|
||||
|
||||
[dependencies.vfio-bindings]
|
||||
version = "0.2.0"
|
||||
features = ["fam-wrappers"]
|
||||
vm-memory = { git = "https://github.com/rust-vmm/vm-memory" }
|
||||
|
||||
148
pci/src/bus.rs
148
pci/src/bus.rs
@@ -8,15 +8,14 @@ use crate::configuration::{
|
||||
use crate::device::{DeviceRelocation, Error as PciDeviceError, PciDevice};
|
||||
use byteorder::{ByteOrder, LittleEndian};
|
||||
use devices::BusDevice;
|
||||
use std;
|
||||
use std::any::Any;
|
||||
use std::collections::HashMap;
|
||||
use std::ops::DerefMut;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::sync::{Arc, Mutex, Weak};
|
||||
use vm_memory::{Address, GuestAddress, GuestUsize};
|
||||
|
||||
const VENDOR_ID_INTEL: u16 = 0x8086;
|
||||
const DEVICE_ID_INTEL_VIRT_PCIE_HOST: u16 = 0x0d57;
|
||||
const NUM_DEVICE_IDS: usize = 32;
|
||||
|
||||
/// Errors for device manager.
|
||||
#[derive(Debug)]
|
||||
@@ -29,12 +28,6 @@ pub enum PciRootError {
|
||||
PioInsert(devices::BusError),
|
||||
/// Could not add a device to the mmio bus.
|
||||
MmioInsert(devices::BusError),
|
||||
/// Could not find an available device slot on the PCI bus.
|
||||
NoPciDeviceSlotAvailable,
|
||||
/// Invalid PCI device identifier provided.
|
||||
InvalidPciDeviceSlot(usize),
|
||||
/// Valid PCI device identifier but already used.
|
||||
AlreadyInUsePciDeviceSlot(usize),
|
||||
}
|
||||
pub type Result<T> = std::result::Result<T, PciRootError>;
|
||||
|
||||
@@ -54,7 +47,6 @@ impl PciRoot {
|
||||
config: PciConfiguration::new(
|
||||
VENDOR_ID_INTEL,
|
||||
DEVICE_ID_INTEL_VIRT_PCIE_HOST,
|
||||
0,
|
||||
PciClassCode::BridgeDevice,
|
||||
&PciBridgeSubclass::HostBridge,
|
||||
None,
|
||||
@@ -87,42 +79,35 @@ impl PciDevice for PciRoot {
|
||||
pub struct PciBus {
|
||||
/// Devices attached to this bus.
|
||||
/// Device 0 is host bridge.
|
||||
devices: HashMap<u32, Arc<Mutex<dyn PciDevice>>>,
|
||||
device_reloc: Arc<dyn DeviceRelocation>,
|
||||
device_ids: Vec<bool>,
|
||||
devices: Vec<Arc<Mutex<dyn PciDevice>>>,
|
||||
device_reloc: Weak<dyn DeviceRelocation>,
|
||||
}
|
||||
|
||||
impl PciBus {
|
||||
pub fn new(pci_root: PciRoot, device_reloc: Arc<dyn DeviceRelocation>) -> Self {
|
||||
let mut devices: HashMap<u32, Arc<Mutex<dyn PciDevice>>> = HashMap::new();
|
||||
let mut device_ids: Vec<bool> = vec![false; NUM_DEVICE_IDS];
|
||||
pub fn new(pci_root: PciRoot, device_reloc: Weak<dyn DeviceRelocation>) -> Self {
|
||||
let mut devices: Vec<Arc<Mutex<dyn PciDevice>>> = Vec::new();
|
||||
|
||||
devices.insert(0, Arc::new(Mutex::new(pci_root)));
|
||||
device_ids[0] = true;
|
||||
devices.push(Arc::new(Mutex::new(pci_root)));
|
||||
|
||||
PciBus {
|
||||
devices,
|
||||
device_reloc,
|
||||
device_ids,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register_mapping(
|
||||
&self,
|
||||
dev: Arc<Mutex<dyn BusDevice>>,
|
||||
#[cfg(target_arch = "x86_64")] io_bus: &devices::Bus,
|
||||
io_bus: &devices::Bus,
|
||||
mmio_bus: &devices::Bus,
|
||||
bars: Vec<(GuestAddress, GuestUsize, PciBarRegionType)>,
|
||||
) -> Result<()> {
|
||||
for (address, size, type_) in bars {
|
||||
match type_ {
|
||||
PciBarRegionType::IORegion => {
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
io_bus
|
||||
.insert(dev.clone(), address.raw_value(), size)
|
||||
.map_err(PciRootError::PioInsert)?;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
error!("I/O region is not supported");
|
||||
}
|
||||
PciBarRegionType::Memory32BitRegion | PciBarRegionType::Memory64BitRegion => {
|
||||
mmio_bus
|
||||
@@ -134,51 +119,13 @@ impl PciBus {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn add_device(
|
||||
&mut self,
|
||||
pci_device_bdf: u32,
|
||||
device: Arc<Mutex<dyn PciDevice>>,
|
||||
) -> Result<()> {
|
||||
self.devices.insert(pci_device_bdf >> 3, device);
|
||||
pub fn add_device(&mut self, device: Arc<Mutex<dyn PciDevice>>) -> Result<()> {
|
||||
self.devices.push(device);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn remove_by_device(&mut self, device: &Arc<Mutex<dyn PciDevice>>) -> Result<()> {
|
||||
self.devices.retain(|_, dev| !Arc::ptr_eq(dev, device));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn next_device_id(&mut self) -> Result<u32> {
|
||||
for (idx, device_id) in self.device_ids.iter_mut().enumerate() {
|
||||
if !(*device_id) {
|
||||
*device_id = true;
|
||||
return Ok(idx as u32);
|
||||
}
|
||||
}
|
||||
|
||||
Err(PciRootError::NoPciDeviceSlotAvailable)
|
||||
}
|
||||
|
||||
pub fn get_device_id(&mut self, id: usize) -> Result<()> {
|
||||
if id < NUM_DEVICE_IDS {
|
||||
if !self.device_ids[id] {
|
||||
self.device_ids[id] = true;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(PciRootError::AlreadyInUsePciDeviceSlot(id))
|
||||
}
|
||||
} else {
|
||||
Err(PciRootError::InvalidPciDeviceSlot(id))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn put_device_id(&mut self, id: usize) -> Result<()> {
|
||||
if id < NUM_DEVICE_IDS {
|
||||
self.device_ids[id] = false;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(PciRootError::InvalidPciDeviceSlot(id))
|
||||
}
|
||||
pub fn next_device_id(&self) -> u32 {
|
||||
self.devices.len() as u32
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,7 +150,7 @@ impl PciConfigIo {
|
||||
}
|
||||
|
||||
let (bus, device, function, register) =
|
||||
parse_io_config_address(self.config_address & !0x8000_0000);
|
||||
parse_config_address(self.config_address & !0x8000_0000);
|
||||
|
||||
// Only support one bus.
|
||||
if bus != 0 {
|
||||
@@ -219,7 +166,7 @@ impl PciConfigIo {
|
||||
.lock()
|
||||
.unwrap()
|
||||
.devices
|
||||
.get(&(device as u32))
|
||||
.get(device)
|
||||
.map_or(0xffff_ffff, |d| {
|
||||
d.lock().unwrap().read_config_register(register)
|
||||
})
|
||||
@@ -236,7 +183,7 @@ impl PciConfigIo {
|
||||
}
|
||||
|
||||
let (bus, device, _function, register) =
|
||||
parse_io_config_address(self.config_address & !0x8000_0000);
|
||||
parse_config_address(self.config_address & !0x8000_0000);
|
||||
|
||||
// Only support one bus.
|
||||
if bus != 0 {
|
||||
@@ -244,23 +191,20 @@ impl PciConfigIo {
|
||||
}
|
||||
|
||||
let pci_bus = self.pci_bus.lock().unwrap();
|
||||
if let Some(d) = pci_bus.devices.get(&(device as u32)) {
|
||||
if let Some(d) = pci_bus.devices.get(device) {
|
||||
let mut device = d.lock().unwrap();
|
||||
|
||||
// Find out if one of the device's BAR is being reprogrammed, and
|
||||
// reprogram it if needed.
|
||||
if let Some(params) = device.detect_bar_reprogramming(register, data) {
|
||||
if let Err(e) = pci_bus.device_reloc.move_bar(
|
||||
if let Err(e) = pci_bus.device_reloc.upgrade().unwrap().move_bar(
|
||||
params.old_base,
|
||||
params.new_base,
|
||||
params.len,
|
||||
device.deref_mut(),
|
||||
params.region_type,
|
||||
) {
|
||||
error!(
|
||||
"Failed moving device BAR: {}: 0x{:x}->0x{:x}(0x{:x})",
|
||||
e, params.old_base, params.new_base, params.len
|
||||
);
|
||||
error!("Failed moving device BAR: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -333,7 +277,7 @@ impl PciConfigMmio {
|
||||
}
|
||||
|
||||
fn config_space_read(&self, config_address: u32) -> u32 {
|
||||
let (bus, device, _function, register) = parse_mmio_config_address(config_address);
|
||||
let (bus, device, _function, register) = parse_config_address(config_address);
|
||||
|
||||
// Only support one bus.
|
||||
if bus != 0 {
|
||||
@@ -344,7 +288,7 @@ impl PciConfigMmio {
|
||||
.lock()
|
||||
.unwrap()
|
||||
.devices
|
||||
.get(&(device as u32))
|
||||
.get(device)
|
||||
.map_or(0xffff_ffff, |d| {
|
||||
d.lock().unwrap().read_config_register(register)
|
||||
})
|
||||
@@ -355,7 +299,7 @@ impl PciConfigMmio {
|
||||
return;
|
||||
}
|
||||
|
||||
let (bus, device, _function, register) = parse_mmio_config_address(config_address);
|
||||
let (bus, device, _function, register) = parse_config_address(config_address);
|
||||
|
||||
// Only support one bus.
|
||||
if bus != 0 {
|
||||
@@ -363,23 +307,20 @@ impl PciConfigMmio {
|
||||
}
|
||||
|
||||
let pci_bus = self.pci_bus.lock().unwrap();
|
||||
if let Some(d) = pci_bus.devices.get(&(device as u32)) {
|
||||
if let Some(d) = pci_bus.devices.get(device) {
|
||||
let mut device = d.lock().unwrap();
|
||||
|
||||
// Find out if one of the device's BAR is being reprogrammed, and
|
||||
// reprogram it if needed.
|
||||
if let Some(params) = device.detect_bar_reprogramming(register, data) {
|
||||
if let Err(e) = pci_bus.device_reloc.move_bar(
|
||||
if let Err(e) = pci_bus.device_reloc.upgrade().unwrap().move_bar(
|
||||
params.old_base,
|
||||
params.new_base,
|
||||
params.len,
|
||||
device.deref_mut(),
|
||||
params.region_type,
|
||||
) {
|
||||
error!(
|
||||
"Failed moving device BAR: {}: 0x{:x}->0x{:x}(0x{:x})",
|
||||
e, params.old_base, params.new_base, params.len
|
||||
);
|
||||
error!("Failed moving device BAR: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -415,33 +356,8 @@ impl BusDevice for PciConfigMmio {
|
||||
}
|
||||
}
|
||||
|
||||
fn shift_and_mask(value: u32, offset: usize, mask: u32) -> usize {
|
||||
((value >> offset) & mask) as usize
|
||||
}
|
||||
|
||||
// Parse the MMIO address offset to a (bus, device, function, register) tuple.
|
||||
// See section 7.2.2 PCI Express Enhanced Configuration Access Mechanism (ECAM)
|
||||
// from the Pci Express Base Specification Revision 5.0 Version 1.0.
|
||||
fn parse_mmio_config_address(config_address: u32) -> (usize, usize, usize, usize) {
|
||||
const BUS_NUMBER_OFFSET: usize = 20;
|
||||
const BUS_NUMBER_MASK: u32 = 0x00ff;
|
||||
const DEVICE_NUMBER_OFFSET: usize = 15;
|
||||
const DEVICE_NUMBER_MASK: u32 = 0x1f;
|
||||
const FUNCTION_NUMBER_OFFSET: usize = 12;
|
||||
const FUNCTION_NUMBER_MASK: u32 = 0x07;
|
||||
const REGISTER_NUMBER_OFFSET: usize = 2;
|
||||
const REGISTER_NUMBER_MASK: u32 = 0x3ff;
|
||||
|
||||
(
|
||||
shift_and_mask(config_address, BUS_NUMBER_OFFSET, BUS_NUMBER_MASK),
|
||||
shift_and_mask(config_address, DEVICE_NUMBER_OFFSET, DEVICE_NUMBER_MASK),
|
||||
shift_and_mask(config_address, FUNCTION_NUMBER_OFFSET, FUNCTION_NUMBER_MASK),
|
||||
shift_and_mask(config_address, REGISTER_NUMBER_OFFSET, REGISTER_NUMBER_MASK),
|
||||
)
|
||||
}
|
||||
|
||||
// Parse the CONFIG_ADDRESS register to a (bus, device, function, register) tuple.
|
||||
fn parse_io_config_address(config_address: u32) -> (usize, usize, usize, usize) {
|
||||
fn parse_config_address(config_address: u32) -> (usize, usize, usize, usize) {
|
||||
const BUS_NUMBER_OFFSET: usize = 16;
|
||||
const BUS_NUMBER_MASK: u32 = 0x00ff;
|
||||
const DEVICE_NUMBER_OFFSET: usize = 11;
|
||||
@@ -451,10 +367,12 @@ fn parse_io_config_address(config_address: u32) -> (usize, usize, usize, usize)
|
||||
const REGISTER_NUMBER_OFFSET: usize = 2;
|
||||
const REGISTER_NUMBER_MASK: u32 = 0x3f;
|
||||
|
||||
(
|
||||
shift_and_mask(config_address, BUS_NUMBER_OFFSET, BUS_NUMBER_MASK),
|
||||
shift_and_mask(config_address, DEVICE_NUMBER_OFFSET, DEVICE_NUMBER_MASK),
|
||||
shift_and_mask(config_address, FUNCTION_NUMBER_OFFSET, FUNCTION_NUMBER_MASK),
|
||||
shift_and_mask(config_address, REGISTER_NUMBER_OFFSET, REGISTER_NUMBER_MASK),
|
||||
)
|
||||
let bus_number = ((config_address >> BUS_NUMBER_OFFSET) & BUS_NUMBER_MASK) as usize;
|
||||
let device_number = ((config_address >> DEVICE_NUMBER_OFFSET) & DEVICE_NUMBER_MASK) as usize;
|
||||
let function_number =
|
||||
((config_address >> FUNCTION_NUMBER_OFFSET) & FUNCTION_NUMBER_MASK) as usize;
|
||||
let register_number =
|
||||
((config_address >> REGISTER_NUMBER_OFFSET) & REGISTER_NUMBER_MASK) as usize;
|
||||
|
||||
(bus_number, device_number, function_number, register_number)
|
||||
}
|
||||
|
||||
245
pci/src/configuration.rs
Normal file → Executable file
245
pci/src/configuration.rs
Normal file → Executable file
@@ -2,16 +2,15 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use crate::device::BarReprogrammingParams;
|
||||
use crate::{MsixConfig, PciInterruptPin};
|
||||
use anyhow::anyhow;
|
||||
use byteorder::{ByteOrder, LittleEndian};
|
||||
use std::fmt::{self, Display};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use vm_migration::{MigratableError, Pausable, Snapshot, SnapshotDataSection, Snapshottable};
|
||||
|
||||
// The number of 32bit registers in the config space, 4096 bytes.
|
||||
const NUM_CONFIGURATION_REGISTERS: usize = 1024;
|
||||
// The number of 32bit registers in the config space, 256 bytes.
|
||||
const NUM_CONFIGURATION_REGISTERS: usize = 64;
|
||||
|
||||
const STATUS_REG: usize = 1;
|
||||
const STATUS_REG_CAPABILITIES_USED_MASK: u32 = 0x0010_0000;
|
||||
@@ -245,65 +244,16 @@ pub trait PciCapability {
|
||||
fn id(&self) -> PciCapabilityID;
|
||||
}
|
||||
|
||||
fn encode_32_bits_bar_size(bar_size: u32) -> Option<u32> {
|
||||
if bar_size > 0 {
|
||||
return Some(!(bar_size - 1));
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn decode_32_bits_bar_size(bar_size: u32) -> Option<u32> {
|
||||
if bar_size > 0 {
|
||||
return Some(!bar_size + 1);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn encode_64_bits_bar_size(bar_size: u64) -> Option<(u32, u32)> {
|
||||
if bar_size > 0 {
|
||||
let result = !(bar_size - 1);
|
||||
let result_hi = (result >> 32) as u32;
|
||||
let result_lo = (result & 0xffff_ffff) as u32;
|
||||
return Some((result_hi, result_lo));
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn decode_64_bits_bar_size(bar_size_hi: u32, bar_size_lo: u32) -> Option<u64> {
|
||||
let bar_size: u64 = ((bar_size_hi as u64) << 32) | (bar_size_lo as u64);
|
||||
if bar_size > 0 {
|
||||
return Some(!bar_size + 1);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Default, Clone, Copy)]
|
||||
struct PciBar {
|
||||
addr: u32,
|
||||
size: u32,
|
||||
used: bool,
|
||||
r#type: Option<PciBarRegionType>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct PciConfigurationState {
|
||||
registers: Vec<u32>,
|
||||
writable_bits: Vec<u32>,
|
||||
bars: Vec<PciBar>,
|
||||
rom_bar_addr: u32,
|
||||
rom_bar_size: u32,
|
||||
rom_bar_used: bool,
|
||||
last_capability: Option<(usize, usize)>,
|
||||
msix_cap_reg_idx: Option<usize>,
|
||||
}
|
||||
|
||||
/// Contains the configuration space of a PCI node.
|
||||
/// See the [specification](https://en.wikipedia.org/wiki/PCI_configuration_space).
|
||||
/// The configuration space is accessed with DWORD reads and writes from the guest.
|
||||
pub struct PciConfiguration {
|
||||
registers: [u32; NUM_CONFIGURATION_REGISTERS],
|
||||
writable_bits: [u32; NUM_CONFIGURATION_REGISTERS], // writable bits for each register.
|
||||
bars: [PciBar; NUM_BAR_REGS],
|
||||
bar_addr: [u32; NUM_BAR_REGS],
|
||||
bar_size: [u32; NUM_BAR_REGS],
|
||||
bar_used: [bool; NUM_BAR_REGS],
|
||||
bar_type: [Option<PciBarRegionType>; NUM_BAR_REGS],
|
||||
rom_bar_addr: u32,
|
||||
rom_bar_size: u32,
|
||||
rom_bar_used: bool,
|
||||
@@ -314,7 +264,7 @@ pub struct PciConfiguration {
|
||||
}
|
||||
|
||||
/// See pci_regs.h in kernel
|
||||
#[derive(Copy, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
pub enum PciBarRegionType {
|
||||
Memory32BitRegion = 0,
|
||||
IORegion = 0x01,
|
||||
@@ -347,10 +297,6 @@ pub enum Error {
|
||||
CapabilityEmpty,
|
||||
CapabilityLengthInvalid(usize),
|
||||
CapabilitySpaceFull(usize),
|
||||
Decode32BarSize,
|
||||
Decode64BarSize,
|
||||
Encode32BarSize,
|
||||
Encode64BarSize,
|
||||
RomBarAddressInvalid(u64, u64),
|
||||
RomBarInUse(usize),
|
||||
RomBarInvalid(usize),
|
||||
@@ -378,10 +324,6 @@ impl Display for Error {
|
||||
CapabilityEmpty => write!(f, "empty capabilities are invalid"),
|
||||
CapabilityLengthInvalid(l) => write!(f, "Invalid capability length {}", l),
|
||||
CapabilitySpaceFull(s) => write!(f, "capability of size {} doesn't fit", s),
|
||||
Decode32BarSize => write!(f, "failed to decode 32 bits BAR size"),
|
||||
Decode64BarSize => write!(f, "failed to decode 64 bits BAR size"),
|
||||
Encode32BarSize => write!(f, "failed to encode 32 bits BAR size"),
|
||||
Encode64BarSize => write!(f, "failed to encode 64 bits BAR size"),
|
||||
RomBarAddressInvalid(a, s) => write!(f, "address {} size {} too big", a, s),
|
||||
RomBarInUse(b) => write!(f, "rom bar {} already used", b),
|
||||
RomBarInvalid(b) => write!(f, "rom bar {} invalid, max {}", b, NUM_BAR_REGS - 1),
|
||||
@@ -395,7 +337,6 @@ impl PciConfiguration {
|
||||
pub fn new(
|
||||
vendor_id: u16,
|
||||
device_id: u16,
|
||||
revision_id: u8,
|
||||
class_code: PciClassCode,
|
||||
subclass: &dyn PciSubclass,
|
||||
programming_interface: Option<&dyn PciProgrammingInterface>,
|
||||
@@ -406,6 +347,8 @@ impl PciConfiguration {
|
||||
) -> Self {
|
||||
let mut registers = [0u32; NUM_CONFIGURATION_REGISTERS];
|
||||
let mut writable_bits = [0u32; NUM_CONFIGURATION_REGISTERS];
|
||||
let bar_addr = [0u32; NUM_BAR_REGS];
|
||||
let bar_size = [0u32; NUM_BAR_REGS];
|
||||
registers[0] = u32::from(device_id) << 16 | u32::from(vendor_id);
|
||||
// TODO(dverkamp): Status should be write-1-to-clear
|
||||
writable_bits[1] = 0x0000_ffff; // Status (r/o), command (r/w)
|
||||
@@ -416,8 +359,7 @@ impl PciConfiguration {
|
||||
};
|
||||
registers[2] = u32::from(class_code.get_register_value()) << 24
|
||||
| u32::from(subclass.get_register_value()) << 16
|
||||
| u32::from(pi) << 8
|
||||
| u32::from(revision_id);
|
||||
| u32::from(pi) << 8;
|
||||
writable_bits[3] = 0x0000_00ff; // Cacheline size (r/w)
|
||||
match header_type {
|
||||
PciHeaderType::Device => {
|
||||
@@ -432,12 +374,13 @@ impl PciConfiguration {
|
||||
};
|
||||
registers[11] = u32::from(subsystem_id) << 16 | u32::from(subsystem_vendor_id);
|
||||
|
||||
let bars = [PciBar::default(); NUM_BAR_REGS];
|
||||
|
||||
PciConfiguration {
|
||||
registers,
|
||||
writable_bits,
|
||||
bars,
|
||||
bar_addr,
|
||||
bar_size,
|
||||
bar_used: [false; NUM_BAR_REGS],
|
||||
bar_type: [None; NUM_BAR_REGS],
|
||||
rom_bar_addr: 0,
|
||||
rom_bar_size: 0,
|
||||
rom_bar_used: false,
|
||||
@@ -447,31 +390,6 @@ impl PciConfiguration {
|
||||
}
|
||||
}
|
||||
|
||||
fn state(&self) -> PciConfigurationState {
|
||||
PciConfigurationState {
|
||||
registers: self.registers.to_vec(),
|
||||
writable_bits: self.writable_bits.to_vec(),
|
||||
bars: self.bars.to_vec(),
|
||||
rom_bar_addr: self.rom_bar_addr,
|
||||
rom_bar_size: self.rom_bar_size,
|
||||
rom_bar_used: self.rom_bar_used,
|
||||
last_capability: self.last_capability,
|
||||
msix_cap_reg_idx: self.msix_cap_reg_idx,
|
||||
}
|
||||
}
|
||||
|
||||
fn set_state(&mut self, state: &PciConfigurationState) {
|
||||
self.registers.clone_from_slice(state.registers.as_slice());
|
||||
self.writable_bits
|
||||
.clone_from_slice(state.writable_bits.as_slice());
|
||||
self.bars.clone_from_slice(state.bars.as_slice());
|
||||
self.rom_bar_addr = state.rom_bar_addr;
|
||||
self.rom_bar_size = state.rom_bar_size;
|
||||
self.rom_bar_used = state.rom_bar_used;
|
||||
self.last_capability = state.last_capability;
|
||||
self.msix_cap_reg_idx = state.msix_cap_reg_idx;
|
||||
}
|
||||
|
||||
/// Reads a 32bit register from `reg_idx` in the register map.
|
||||
pub fn read_reg(&self, reg_idx: usize) -> u32 {
|
||||
*(self.registers.get(reg_idx).unwrap_or(&0xffff_ffff))
|
||||
@@ -485,14 +403,14 @@ impl PciConfiguration {
|
||||
// Handle very specific case where the BAR is being written with
|
||||
// all 1's to retrieve the BAR size during next BAR reading.
|
||||
if value == 0xffff_ffff {
|
||||
mask &= self.bars[reg_idx - 4].size;
|
||||
mask = self.bar_size[reg_idx - 4];
|
||||
}
|
||||
} else if reg_idx == ROM_BAR_REG {
|
||||
// Handle very specific case where the BAR is being written with
|
||||
// all 1's on bits 31-11 to retrieve the BAR size during next BAR
|
||||
// reading.
|
||||
if value & ROM_BAR_ADDR_MASK == ROM_BAR_ADDR_MASK {
|
||||
mask &= self.rom_bar_size;
|
||||
mask = self.rom_bar_size;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -554,7 +472,7 @@ impl PciConfiguration {
|
||||
/// (i.e, region size must be power of two, register not already used). Returns 'None' on
|
||||
/// failure all, `Some(BarIndex)` on success.
|
||||
pub fn add_pci_bar(&mut self, config: &PciBarConfiguration) -> Result<usize> {
|
||||
if self.bars[config.reg_idx].used {
|
||||
if self.bar_used[config.reg_idx] {
|
||||
return Err(Error::BarInUse(config.reg_idx));
|
||||
}
|
||||
|
||||
@@ -576,11 +494,6 @@ impl PciConfiguration {
|
||||
if end_addr > u64::from(u32::max_value()) {
|
||||
return Err(Error::BarAddressInvalid(config.addr, config.size));
|
||||
}
|
||||
|
||||
// Encode the BAR size as expected by the software running in
|
||||
// the guest.
|
||||
self.bars[config.reg_idx].size =
|
||||
encode_32_bits_bar_size(config.size as u32).ok_or(Error::Encode32BarSize)?;
|
||||
}
|
||||
PciBarRegionType::Memory64BitRegion => {
|
||||
if config.reg_idx + 1 >= NUM_BAR_REGS {
|
||||
@@ -591,21 +504,15 @@ impl PciConfiguration {
|
||||
return Err(Error::BarAddressInvalid(config.addr, config.size));
|
||||
}
|
||||
|
||||
if self.bars[config.reg_idx + 1].used {
|
||||
if self.bar_used[config.reg_idx + 1] {
|
||||
return Err(Error::BarInUse64(config.reg_idx));
|
||||
}
|
||||
|
||||
// Encode the BAR size as expected by the software running in
|
||||
// the guest.
|
||||
let (bar_size_hi, bar_size_lo) =
|
||||
encode_64_bits_bar_size(config.size).ok_or(Error::Encode64BarSize)?;
|
||||
|
||||
self.registers[bar_idx + 1] = (config.addr >> 32) as u32;
|
||||
self.writable_bits[bar_idx + 1] = 0xffff_ffff;
|
||||
self.bars[config.reg_idx + 1].addr = self.registers[bar_idx + 1];
|
||||
self.bars[config.reg_idx].size = bar_size_lo;
|
||||
self.bars[config.reg_idx + 1].size = bar_size_hi;
|
||||
self.bars[config.reg_idx + 1].used = true;
|
||||
self.bar_addr[config.reg_idx + 1] = self.registers[bar_idx + 1];
|
||||
self.bar_size[config.reg_idx + 1] = (config.size >> 32) as u32;
|
||||
self.bar_used[config.reg_idx + 1] = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -619,9 +526,10 @@ impl PciConfiguration {
|
||||
|
||||
self.registers[bar_idx] = ((config.addr as u32) & mask) | lower_bits;
|
||||
self.writable_bits[bar_idx] = mask;
|
||||
self.bars[config.reg_idx].addr = self.registers[bar_idx];
|
||||
self.bars[config.reg_idx].used = true;
|
||||
self.bars[config.reg_idx].r#type = Some(config.region_type);
|
||||
self.bar_addr[config.reg_idx] = self.registers[bar_idx];
|
||||
self.bar_size[config.reg_idx] = config.size as u32;
|
||||
self.bar_used[config.reg_idx] = true;
|
||||
self.bar_type[config.reg_idx] = Some(config.region_type);
|
||||
Ok(config.reg_idx)
|
||||
}
|
||||
|
||||
@@ -651,8 +559,7 @@ impl PciConfiguration {
|
||||
self.registers[config.reg_idx] = (config.addr as u32) | active;
|
||||
self.writable_bits[config.reg_idx] = ROM_BAR_ADDR_MASK;
|
||||
self.rom_bar_addr = self.registers[config.reg_idx];
|
||||
self.rom_bar_size =
|
||||
encode_32_bits_bar_size(config.size as u32).ok_or(Error::Encode32BarSize)?;
|
||||
self.rom_bar_size = config.size as u32;
|
||||
self.rom_bar_used = true;
|
||||
Ok(config.reg_idx)
|
||||
}
|
||||
@@ -661,11 +568,11 @@ impl PciConfiguration {
|
||||
pub fn get_bar_addr(&self, bar_num: usize) -> u64 {
|
||||
let bar_idx = BAR0_REG + bar_num;
|
||||
|
||||
let mut addr = u64::from(self.bars[bar_num].addr & self.writable_bits[bar_idx]);
|
||||
let mut addr = u64::from(self.bar_addr[bar_num] & self.writable_bits[bar_idx]);
|
||||
|
||||
if let Some(bar_type) = self.bars[bar_num].r#type {
|
||||
if let Some(bar_type) = self.bar_type[bar_num] {
|
||||
if bar_type == PciBarRegionType::Memory64BitRegion {
|
||||
addr |= u64::from(self.bars[bar_num + 1].addr) << 32;
|
||||
addr |= u64::from(self.bar_addr[bar_num + 1]) << 32;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -770,11 +677,11 @@ impl PciConfiguration {
|
||||
let mask = self.writable_bits[reg_idx];
|
||||
if reg_idx >= BAR0_REG && reg_idx < BAR0_REG + NUM_BAR_REGS {
|
||||
let bar_idx = reg_idx - 4;
|
||||
if (value & mask) != (self.bars[bar_idx].addr & mask) {
|
||||
if (value & mask) != (self.bar_addr[bar_idx] & mask) {
|
||||
// Handle special case where the address being written is
|
||||
// different from the address initially provided. This is a
|
||||
// BAR reprogramming case which needs to be properly caught.
|
||||
if let Some(bar_type) = self.bars[bar_idx].r#type {
|
||||
if let Some(bar_type) = self.bar_type[bar_idx] {
|
||||
match bar_type {
|
||||
PciBarRegionType::Memory64BitRegion => {}
|
||||
_ => {
|
||||
@@ -788,16 +695,12 @@ impl PciConfiguration {
|
||||
"DETECT BAR REPROG: current 0x{:x}, new 0x{:x}",
|
||||
self.registers[reg_idx], value
|
||||
);
|
||||
let old_base = u64::from(self.bars[bar_idx].addr & mask);
|
||||
let old_base = u64::from(self.bar_addr[bar_idx] & mask);
|
||||
let new_base = u64::from(value & mask);
|
||||
let len = u64::from(
|
||||
decode_32_bits_bar_size(self.bars[bar_idx].size)
|
||||
.ok_or(Error::Decode32BarSize)
|
||||
.unwrap(),
|
||||
);
|
||||
let len = u64::from(self.bar_size[bar_idx]);
|
||||
let region_type = bar_type;
|
||||
|
||||
self.bars[bar_idx].addr = value;
|
||||
self.bar_addr[bar_idx] = value;
|
||||
|
||||
return Some(BarReprogrammingParams {
|
||||
old_base,
|
||||
@@ -809,7 +712,7 @@ impl PciConfiguration {
|
||||
}
|
||||
} else if (reg_idx > BAR0_REG)
|
||||
&& (self.registers[reg_idx - 1] & self.writable_bits[reg_idx - 1])
|
||||
!= (self.bars[bar_idx - 1].addr & self.writable_bits[reg_idx - 1])
|
||||
!= (self.bar_addr[bar_idx - 1] & self.writable_bits[reg_idx - 1])
|
||||
{
|
||||
// Ignore the case where the BAR size is being asked for.
|
||||
// Because we are in the 64bits case here, we have to check
|
||||
@@ -817,7 +720,7 @@ impl PciConfiguration {
|
||||
// asked for the BAR size too.
|
||||
if value == 0xffff_ffff
|
||||
&& self.registers[reg_idx - 1] & self.writable_bits[reg_idx - 1]
|
||||
== self.bars[bar_idx - 1].size & self.writable_bits[reg_idx - 1]
|
||||
== self.bar_size[bar_idx - 1] & self.writable_bits[reg_idx - 1]
|
||||
{
|
||||
return None;
|
||||
}
|
||||
@@ -826,20 +729,16 @@ impl PciConfiguration {
|
||||
"DETECT BAR REPROG: current 0x{:x}, new 0x{:x}",
|
||||
self.registers[reg_idx], value
|
||||
);
|
||||
let old_base = u64::from(self.bars[bar_idx].addr & mask) << 32
|
||||
| u64::from(self.bars[bar_idx - 1].addr & self.writable_bits[reg_idx - 1]);
|
||||
let old_base = u64::from(self.bar_addr[bar_idx] & mask) << 32
|
||||
| u64::from(self.bar_addr[bar_idx - 1] & self.writable_bits[reg_idx - 1]);
|
||||
let new_base = u64::from(value & mask) << 32
|
||||
| u64::from(self.registers[reg_idx - 1] & self.writable_bits[reg_idx - 1]);
|
||||
let len = decode_64_bits_bar_size(
|
||||
self.bars[bar_idx].size,
|
||||
self.bars[bar_idx - 1].size,
|
||||
)
|
||||
.ok_or(Error::Decode64BarSize)
|
||||
.unwrap();
|
||||
let len = u64::from(self.bar_size[bar_idx]) << 32
|
||||
| u64::from(self.bar_size[bar_idx - 1]);
|
||||
let region_type = PciBarRegionType::Memory64BitRegion;
|
||||
|
||||
self.bars[bar_idx].addr = value;
|
||||
self.bars[bar_idx - 1].addr = self.registers[reg_idx - 1];
|
||||
self.bar_addr[bar_idx] = value;
|
||||
self.bar_addr[bar_idx - 1] = self.registers[reg_idx - 1];
|
||||
|
||||
return Some(BarReprogrammingParams {
|
||||
old_base,
|
||||
@@ -861,11 +760,7 @@ impl PciConfiguration {
|
||||
);
|
||||
let old_base = u64::from(self.rom_bar_addr & mask);
|
||||
let new_base = u64::from(value & mask);
|
||||
let len = u64::from(
|
||||
decode_32_bits_bar_size(self.rom_bar_size)
|
||||
.ok_or(Error::Decode32BarSize)
|
||||
.unwrap(),
|
||||
);
|
||||
let len = u64::from(self.rom_bar_size);
|
||||
let region_type = PciBarRegionType::Memory32BitRegion;
|
||||
|
||||
self.rom_bar_addr = value;
|
||||
@@ -882,54 +777,6 @@ impl PciConfiguration {
|
||||
}
|
||||
}
|
||||
|
||||
impl Pausable for PciConfiguration {}
|
||||
|
||||
impl Snapshottable for PciConfiguration {
|
||||
fn id(&self) -> String {
|
||||
String::from("pci_configuration")
|
||||
}
|
||||
|
||||
fn snapshot(&self) -> std::result::Result<Snapshot, MigratableError> {
|
||||
let snapshot =
|
||||
serde_json::to_vec(&self.state()).map_err(|e| MigratableError::Snapshot(e.into()))?;
|
||||
|
||||
let mut config_snapshot = Snapshot::new(self.id().as_str());
|
||||
config_snapshot.add_data_section(SnapshotDataSection {
|
||||
id: format!("{}-section", self.id()),
|
||||
snapshot,
|
||||
});
|
||||
|
||||
Ok(config_snapshot)
|
||||
}
|
||||
|
||||
fn restore(&mut self, snapshot: Snapshot) -> std::result::Result<(), MigratableError> {
|
||||
if let Some(config_section) = snapshot
|
||||
.snapshot_data
|
||||
.get(&format!("{}-section", self.id()))
|
||||
{
|
||||
let config_state = match serde_json::from_slice(&config_section.snapshot) {
|
||||
Ok(state) => state,
|
||||
Err(error) => {
|
||||
return Err(MigratableError::Restore(anyhow!(
|
||||
"Could not deserialize {}: {}",
|
||||
self.id(),
|
||||
error
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
self.set_state(&config_state);
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Err(MigratableError::Restore(anyhow!(
|
||||
"Could not find {} snapshot section",
|
||||
self.id()
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for PciBarConfiguration {
|
||||
fn default() -> Self {
|
||||
PciBarConfiguration {
|
||||
@@ -1015,7 +862,6 @@ mod tests {
|
||||
let mut cfg = PciConfiguration::new(
|
||||
0x1234,
|
||||
0x5678,
|
||||
0x1,
|
||||
PciClassCode::MultimediaController,
|
||||
&PciMultimediaSubclass::AudioController,
|
||||
None,
|
||||
@@ -1071,7 +917,6 @@ mod tests {
|
||||
let cfg = PciConfiguration::new(
|
||||
0x1234,
|
||||
0x5678,
|
||||
0x1,
|
||||
PciClassCode::MultimediaController,
|
||||
&PciMultimediaSubclass::AudioController,
|
||||
Some(&TestPI::Test),
|
||||
|
||||
5
pci/src/device.rs
Normal file → Executable file
5
pci/src/device.rs
Normal file → Executable file
@@ -55,11 +55,6 @@ pub trait PciDevice: BusDevice {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
|
||||
/// Frees the PCI BARs previously allocated with a call to allocate_bars().
|
||||
fn free_bars(&mut self, _allocator: &mut SystemAllocator) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sets a register in the configuration space.
|
||||
/// * `reg_idx` - The index of the config register to modify.
|
||||
/// * `offset` - Offset in to the register.
|
||||
|
||||
@@ -6,11 +6,6 @@
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
extern crate devices;
|
||||
extern crate hypervisor;
|
||||
extern crate serde;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
extern crate serde_json;
|
||||
extern crate vm_memory;
|
||||
|
||||
mod bus;
|
||||
@@ -18,7 +13,6 @@ mod configuration;
|
||||
mod device;
|
||||
mod msi;
|
||||
mod msix;
|
||||
mod vfio;
|
||||
|
||||
pub use self::bus::{PciBus, PciConfigIo, PciConfigMmio, PciRoot, PciRootError};
|
||||
pub use self::configuration::{
|
||||
@@ -31,7 +25,6 @@ pub use self::device::{
|
||||
};
|
||||
pub use self::msi::{msi_num_enabled_vectors, MsiCap, MsiConfig};
|
||||
pub use self::msix::{MsixCap, MsixConfig, MsixTableEntry, MSIX_TABLE_ENTRY_SIZE};
|
||||
pub use self::vfio::{VfioPciDevice, VfioPciError};
|
||||
|
||||
/// PCI has four interrupt pins A->D.
|
||||
#[derive(Copy, Clone)]
|
||||
|
||||
@@ -201,7 +201,6 @@ impl MsiConfig {
|
||||
high_addr: self.cap.msg_addr_hi,
|
||||
low_addr: self.cap.msg_addr_lo,
|
||||
data: self.cap.msg_data as u32,
|
||||
devid: 0,
|
||||
};
|
||||
|
||||
if let Err(e) = self
|
||||
@@ -215,8 +214,6 @@ impl MsiConfig {
|
||||
if let Err(e) = self.interrupt_source_group.mask(idx as InterruptIndex) {
|
||||
error!("Failed masking vector: {:?}", e);
|
||||
}
|
||||
} else if let Err(e) = self.interrupt_source_group.unmask(idx as InterruptIndex) {
|
||||
error!("Failed unmasking vector: {:?}", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
122
pci/src/msix.rs
122
pci/src/msix.rs
@@ -6,17 +6,14 @@
|
||||
extern crate byteorder;
|
||||
extern crate vm_memory;
|
||||
|
||||
use crate::{PciCapability, PciCapabilityID};
|
||||
use anyhow::anyhow;
|
||||
use byteorder::{ByteOrder, LittleEndian};
|
||||
use std::io;
|
||||
use std::result;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::{PciCapability, PciCapabilityID};
|
||||
use byteorder::{ByteOrder, LittleEndian};
|
||||
use vm_device::interrupt::{
|
||||
InterruptIndex, InterruptSourceConfig, InterruptSourceGroup, MsiIrqSourceConfig,
|
||||
};
|
||||
use vm_memory::ByteValued;
|
||||
use vm_migration::{MigratableError, Pausable, Snapshot, SnapshotDataSection, Snapshottable};
|
||||
|
||||
const MAX_MSIX_VECTORS_PER_DEVICE: u16 = 2048;
|
||||
const MSIX_TABLE_ENTRIES_MODULO: u64 = 16;
|
||||
@@ -28,15 +25,7 @@ const FUNCTION_MASK_MASK: u16 = (1 << FUNCTION_MASK_BIT) as u16;
|
||||
const MSIX_ENABLE_MASK: u16 = (1 << MSIX_ENABLE_BIT) as u16;
|
||||
pub const MSIX_TABLE_ENTRY_SIZE: usize = 16;
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Error {
|
||||
/// Failed enabling the interrupt route.
|
||||
EnableInterruptRoute(io::Error),
|
||||
/// Failed updating the interrupt route.
|
||||
UpdateInterruptRoute(io::Error),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MsixTableEntry {
|
||||
pub msg_addr_lo: u32,
|
||||
pub msg_addr_hi: u32,
|
||||
@@ -61,18 +50,9 @@ impl Default for MsixTableEntry {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct MsixConfigState {
|
||||
table_entries: Vec<MsixTableEntry>,
|
||||
pba_entries: Vec<u64>,
|
||||
masked: bool,
|
||||
enabled: bool,
|
||||
}
|
||||
|
||||
pub struct MsixConfig {
|
||||
pub table_entries: Vec<MsixTableEntry>,
|
||||
pub pba_entries: Vec<u64>,
|
||||
pub devid: u32,
|
||||
interrupt_source_group: Arc<Box<dyn InterruptSourceGroup>>,
|
||||
masked: bool,
|
||||
enabled: bool,
|
||||
@@ -82,7 +62,6 @@ impl MsixConfig {
|
||||
pub fn new(
|
||||
msix_vectors: u16,
|
||||
interrupt_source_group: Arc<Box<dyn InterruptSourceGroup>>,
|
||||
devid: u32,
|
||||
) -> Self {
|
||||
assert!(msix_vectors <= MAX_MSIX_VECTORS_PER_DEVICE);
|
||||
|
||||
@@ -95,54 +74,12 @@ impl MsixConfig {
|
||||
MsixConfig {
|
||||
table_entries,
|
||||
pba_entries,
|
||||
devid,
|
||||
interrupt_source_group,
|
||||
masked: false,
|
||||
enabled: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn state(&self) -> MsixConfigState {
|
||||
MsixConfigState {
|
||||
table_entries: self.table_entries.clone(),
|
||||
pba_entries: self.pba_entries.clone(),
|
||||
masked: self.masked,
|
||||
enabled: self.enabled,
|
||||
}
|
||||
}
|
||||
|
||||
fn set_state(&mut self, state: &MsixConfigState) -> result::Result<(), Error> {
|
||||
self.table_entries = state.table_entries.clone();
|
||||
self.pba_entries = state.pba_entries.clone();
|
||||
self.masked = state.masked;
|
||||
self.enabled = state.enabled;
|
||||
|
||||
if self.enabled && !self.masked {
|
||||
for (idx, table_entry) in self.table_entries.iter().enumerate() {
|
||||
if table_entry.masked() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let config = MsiIrqSourceConfig {
|
||||
high_addr: table_entry.msg_addr_hi,
|
||||
low_addr: table_entry.msg_addr_lo,
|
||||
data: table_entry.msg_data,
|
||||
devid: self.devid,
|
||||
};
|
||||
|
||||
self.interrupt_source_group
|
||||
.update(idx as InterruptIndex, InterruptSourceConfig::MsiIrq(config))
|
||||
.map_err(Error::UpdateInterruptRoute)?;
|
||||
|
||||
self.interrupt_source_group
|
||||
.enable()
|
||||
.map_err(Error::EnableInterruptRoute)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn masked(&self) -> bool {
|
||||
self.masked
|
||||
}
|
||||
@@ -166,7 +103,6 @@ impl MsixConfig {
|
||||
high_addr: table_entry.msg_addr_hi,
|
||||
low_addr: table_entry.msg_addr_lo,
|
||||
data: table_entry.msg_data,
|
||||
devid: self.devid,
|
||||
};
|
||||
|
||||
if let Err(e) = self
|
||||
@@ -180,9 +116,6 @@ impl MsixConfig {
|
||||
if let Err(e) = self.interrupt_source_group.mask(idx as InterruptIndex) {
|
||||
error!("Failed masking vector: {:?}", e);
|
||||
}
|
||||
} else if let Err(e) = self.interrupt_source_group.unmask(idx as InterruptIndex)
|
||||
{
|
||||
error!("Failed unmasking vector: {:?}", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -311,7 +244,6 @@ impl MsixConfig {
|
||||
high_addr: table_entry.msg_addr_hi,
|
||||
low_addr: table_entry.msg_addr_lo,
|
||||
data: table_entry.msg_data,
|
||||
devid: self.devid,
|
||||
};
|
||||
|
||||
if let Err(e) = self.interrupt_source_group.update(
|
||||
@@ -430,52 +362,6 @@ impl MsixConfig {
|
||||
}
|
||||
}
|
||||
|
||||
impl Pausable for MsixConfig {}
|
||||
|
||||
impl Snapshottable for MsixConfig {
|
||||
fn id(&self) -> String {
|
||||
String::from("msix_config")
|
||||
}
|
||||
|
||||
fn snapshot(&self) -> std::result::Result<Snapshot, MigratableError> {
|
||||
let snapshot =
|
||||
serde_json::to_vec(&self.state()).map_err(|e| MigratableError::Snapshot(e.into()))?;
|
||||
|
||||
let mut msix_snapshot = Snapshot::new(self.id().as_str());
|
||||
msix_snapshot.add_data_section(SnapshotDataSection {
|
||||
id: format!("{}-section", self.id()),
|
||||
snapshot,
|
||||
});
|
||||
|
||||
Ok(msix_snapshot)
|
||||
}
|
||||
|
||||
fn restore(&mut self, snapshot: Snapshot) -> std::result::Result<(), MigratableError> {
|
||||
if let Some(msix_section) = snapshot
|
||||
.snapshot_data
|
||||
.get(&format!("{}-section", self.id()))
|
||||
{
|
||||
let msix_state = match serde_json::from_slice(&msix_section.snapshot) {
|
||||
Ok(state) => state,
|
||||
Err(error) => {
|
||||
return Err(MigratableError::Restore(anyhow!(
|
||||
"Could not deserialize MSI-X {}",
|
||||
error
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
return self.set_state(&msix_state).map_err(|e| {
|
||||
MigratableError::Restore(anyhow!("Could not restore MSI-X state {:?}", e))
|
||||
});
|
||||
}
|
||||
|
||||
Err(MigratableError::Restore(anyhow!(
|
||||
"Could not find MSI-X snapshot section"
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[repr(packed)]
|
||||
#[derive(Clone, Copy, Default)]
|
||||
|
||||
@@ -10,10 +10,11 @@ path = "src/qcow.rs"
|
||||
|
||||
[dependencies]
|
||||
byteorder = "1.3.4"
|
||||
libc = "0.2.74"
|
||||
log = "0.4.11"
|
||||
remain = "0.2.2"
|
||||
vmm-sys-util = ">=0.3.1"
|
||||
libc = "0.2.66"
|
||||
log = "0.4.8"
|
||||
remain = "0.2.1"
|
||||
vmm-sys-util = "0.4.0"
|
||||
vm-virtio = { path = "../vm-virtio" }
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.1.0"
|
||||
|
||||
22
qcow/src/qcow.rs
Normal file → Executable file
22
qcow/src/qcow.rs
Normal file → Executable file
@@ -6,26 +6,26 @@
|
||||
extern crate log;
|
||||
|
||||
mod qcow_raw_file;
|
||||
mod raw_file;
|
||||
mod refcount;
|
||||
mod vec_cache;
|
||||
|
||||
use crate::qcow_raw_file::QcowRawFile;
|
||||
use crate::refcount::RefCount;
|
||||
use crate::vec_cache::{CacheMap, Cacheable, VecCache};
|
||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use libc::{EINVAL, ENOSPC, ENOTSUP};
|
||||
use remain::sorted;
|
||||
use std::cmp::{max, min};
|
||||
use std::fmt::{self, Display};
|
||||
use std::io::{self, Read, Seek, SeekFrom, Write};
|
||||
use std::mem::size_of;
|
||||
use vm_virtio::RawFile;
|
||||
use vmm_sys_util::{
|
||||
file_traits::FileSetLen, file_traits::FileSync, seek_hole::SeekHole, write_zeroes::PunchHole,
|
||||
write_zeroes::WriteZeroes,
|
||||
};
|
||||
|
||||
pub use crate::raw_file::RawFile;
|
||||
use std::cmp::{max, min};
|
||||
use std::fmt::{self, Display};
|
||||
use std::io::{self, Read, Seek, SeekFrom, Write};
|
||||
use std::mem::size_of;
|
||||
|
||||
use crate::qcow_raw_file::QcowRawFile;
|
||||
use crate::refcount::RefCount;
|
||||
use crate::vec_cache::{CacheMap, Cacheable, VecCache};
|
||||
|
||||
#[sorted]
|
||||
#[derive(Debug)]
|
||||
@@ -367,7 +367,8 @@ fn max_refcount_clusters(refcount_order: u32, cluster_size: u32, num_clusters: u
|
||||
///
|
||||
/// ```
|
||||
/// # use std::io::{Read, Seek, SeekFrom};
|
||||
/// # use qcow::{self, QcowFile, RawFile};
|
||||
/// # use vm_virtio::RawFile;
|
||||
/// # use qcow::{self, QcowFile};
|
||||
/// # fn test(file: std::fs::File) -> std::io::Result<()> {
|
||||
/// let mut raw_img = RawFile::new(file, false);
|
||||
/// let mut q = QcowFile::from(raw_img).expect("Can't open qcow file");
|
||||
@@ -1702,6 +1703,7 @@ mod tests {
|
||||
use super::*;
|
||||
use std::io::{Read, Seek, SeekFrom, Write};
|
||||
use tempfile::tempfile;
|
||||
use vm_virtio::RawFile;
|
||||
|
||||
fn valid_header_v3() -> Vec<u8> {
|
||||
vec![
|
||||
|
||||
@@ -2,10 +2,11 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
|
||||
use super::RawFile;
|
||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use std::io::{self, BufWriter, Seek, SeekFrom};
|
||||
use std::mem::size_of;
|
||||
|
||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use vm_virtio::RawFile;
|
||||
use vmm_sys_util::write_zeroes::WriteZeroes;
|
||||
|
||||
/// A qcow file. Allows reading/writing clusters and appending clusters.
|
||||
|
||||
@@ -1,324 +0,0 @@
|
||||
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
//
|
||||
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
//
|
||||
// Copyright © 2020 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||
|
||||
use libc::c_void;
|
||||
use std::alloc::{alloc_zeroed, dealloc, Layout};
|
||||
use std::convert::TryInto;
|
||||
use std::fs::{File, Metadata};
|
||||
use std::io::{self, Read, Seek, SeekFrom, Write};
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
use std::slice;
|
||||
use vmm_sys_util::{seek_hole::SeekHole, write_zeroes::PunchHole};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RawFile {
|
||||
file: File,
|
||||
alignment: usize,
|
||||
position: u64,
|
||||
}
|
||||
|
||||
const BLK_ALIGNMENTS: [usize; 2] = [512, 4096];
|
||||
|
||||
fn is_valid_alignment(fd: RawFd, alignment: usize) -> bool {
|
||||
let layout = Layout::from_size_align(alignment, alignment).unwrap();
|
||||
let ptr = unsafe { alloc_zeroed(layout) };
|
||||
|
||||
let ret = unsafe {
|
||||
::libc::pread(
|
||||
fd,
|
||||
ptr as *mut c_void,
|
||||
alignment,
|
||||
alignment.try_into().unwrap(),
|
||||
)
|
||||
};
|
||||
|
||||
unsafe { dealloc(ptr, layout) };
|
||||
|
||||
ret >= 0
|
||||
}
|
||||
|
||||
impl RawFile {
|
||||
pub fn new(file: File, direct_io: bool) -> Self {
|
||||
// Assume no alignment restrictions if we aren't using O_DIRECT.
|
||||
let mut alignment = 0;
|
||||
if direct_io {
|
||||
for align in &BLK_ALIGNMENTS {
|
||||
if is_valid_alignment(file.as_raw_fd(), *align) {
|
||||
alignment = *align;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
RawFile {
|
||||
file,
|
||||
alignment: alignment.try_into().unwrap(),
|
||||
position: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn round_up(&self, offset: u64) -> u64 {
|
||||
let align: u64 = self.alignment.try_into().unwrap();
|
||||
((offset / (align + 1)) + 1) * align
|
||||
}
|
||||
|
||||
fn round_down(&self, offset: u64) -> u64 {
|
||||
let align: u64 = self.alignment.try_into().unwrap();
|
||||
(offset / align) * align
|
||||
}
|
||||
|
||||
fn is_aligned(&self, buf: &[u8]) -> bool {
|
||||
if self.alignment == 0 {
|
||||
return true;
|
||||
}
|
||||
|
||||
let align64: u64 = self.alignment.try_into().unwrap();
|
||||
|
||||
(self.position % align64 == 0)
|
||||
&& ((buf.as_ptr() as usize) % self.alignment == 0)
|
||||
&& (buf.len() % self.alignment == 0)
|
||||
}
|
||||
|
||||
pub fn set_len(&self, size: u64) -> std::io::Result<()> {
|
||||
self.file.set_len(size)
|
||||
}
|
||||
|
||||
pub fn metadata(&self) -> std::io::Result<Metadata> {
|
||||
self.file.metadata()
|
||||
}
|
||||
|
||||
pub fn try_clone(&self) -> std::io::Result<RawFile> {
|
||||
Ok(RawFile {
|
||||
file: self.file.try_clone().expect("RawFile cloning failed"),
|
||||
alignment: self.alignment,
|
||||
position: self.position,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn sync_all(&self) -> std::io::Result<()> {
|
||||
self.file.sync_all()
|
||||
}
|
||||
|
||||
pub fn sync_data(&self) -> std::io::Result<()> {
|
||||
self.file.sync_data()
|
||||
}
|
||||
}
|
||||
|
||||
impl Read for RawFile {
|
||||
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||
if self.is_aligned(buf) {
|
||||
match self.file.read(buf) {
|
||||
Ok(r) => {
|
||||
self.position = self.position.checked_add(r.try_into().unwrap()).unwrap();
|
||||
Ok(r)
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
} else {
|
||||
let rounded_pos: u64 = self.round_down(self.position);
|
||||
let file_offset: usize = self
|
||||
.position
|
||||
.checked_sub(rounded_pos)
|
||||
.unwrap()
|
||||
.try_into()
|
||||
.unwrap();
|
||||
let buf_len: usize = buf.len();
|
||||
let rounded_len: usize = self
|
||||
.round_up(
|
||||
file_offset
|
||||
.checked_add(buf_len)
|
||||
.unwrap()
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
)
|
||||
.try_into()
|
||||
.unwrap();
|
||||
|
||||
let layout = Layout::from_size_align(rounded_len, self.alignment).unwrap();
|
||||
let tmp_ptr = unsafe { alloc_zeroed(layout) };
|
||||
let tmp_buf = unsafe { slice::from_raw_parts_mut(tmp_ptr, rounded_len) };
|
||||
|
||||
// This can eventually replaced with read_at once its interface
|
||||
// has been stabilized.
|
||||
let ret = unsafe {
|
||||
::libc::pread64(
|
||||
self.file.as_raw_fd(),
|
||||
tmp_buf.as_mut_ptr() as *mut c_void,
|
||||
tmp_buf.len(),
|
||||
rounded_pos.try_into().unwrap(),
|
||||
)
|
||||
};
|
||||
if ret < 0 {
|
||||
unsafe { dealloc(tmp_ptr, layout) };
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
|
||||
let read: usize = ret.try_into().unwrap();
|
||||
if read < file_offset {
|
||||
unsafe { dealloc(tmp_ptr, layout) };
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
let mut to_copy = read - file_offset;
|
||||
if to_copy > buf_len {
|
||||
to_copy = buf_len;
|
||||
}
|
||||
|
||||
buf.copy_from_slice(&tmp_buf[file_offset..(file_offset + buf_len)]);
|
||||
unsafe { dealloc(tmp_ptr, layout) };
|
||||
|
||||
self.seek(SeekFrom::Current(to_copy.try_into().unwrap()))
|
||||
.unwrap();
|
||||
Ok(to_copy.try_into().unwrap())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Write for RawFile {
|
||||
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||
if self.is_aligned(buf) {
|
||||
match self.file.write(buf) {
|
||||
Ok(r) => {
|
||||
self.position = self.position.checked_add(r.try_into().unwrap()).unwrap();
|
||||
Ok(r)
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
} else {
|
||||
let rounded_pos: u64 = self.round_down(self.position);
|
||||
let file_offset: usize = self
|
||||
.position
|
||||
.checked_sub(rounded_pos)
|
||||
.unwrap()
|
||||
.try_into()
|
||||
.unwrap();
|
||||
let buf_len: usize = buf.len();
|
||||
let rounded_len: usize = self
|
||||
.round_up(
|
||||
file_offset
|
||||
.checked_add(buf_len)
|
||||
.unwrap()
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
)
|
||||
.try_into()
|
||||
.unwrap();
|
||||
|
||||
let layout = Layout::from_size_align(rounded_len, self.alignment).unwrap();
|
||||
let tmp_ptr = unsafe { alloc_zeroed(layout) };
|
||||
let tmp_buf = unsafe { slice::from_raw_parts_mut(tmp_ptr, rounded_len) };
|
||||
|
||||
// This can eventually replaced with read_at once its interface
|
||||
// has been stabilized.
|
||||
let ret = unsafe {
|
||||
::libc::pread64(
|
||||
self.file.as_raw_fd(),
|
||||
tmp_buf.as_mut_ptr() as *mut c_void,
|
||||
tmp_buf.len(),
|
||||
rounded_pos.try_into().unwrap(),
|
||||
)
|
||||
};
|
||||
if ret < 0 {
|
||||
unsafe { dealloc(tmp_ptr, layout) };
|
||||
return Err(io::Error::last_os_error());
|
||||
};
|
||||
|
||||
tmp_buf[file_offset..(file_offset + buf_len)].copy_from_slice(buf);
|
||||
|
||||
// This can eventually replaced with write_at once its interface
|
||||
// has been stabilized.
|
||||
let ret = unsafe {
|
||||
::libc::pwrite64(
|
||||
self.file.as_raw_fd(),
|
||||
tmp_buf.as_ptr() as *const c_void,
|
||||
tmp_buf.len(),
|
||||
rounded_pos.try_into().unwrap(),
|
||||
)
|
||||
};
|
||||
|
||||
unsafe { dealloc(tmp_ptr, layout) };
|
||||
|
||||
if ret < 0 {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
|
||||
let written: usize = ret.try_into().unwrap();
|
||||
if written < file_offset {
|
||||
Ok(0)
|
||||
} else {
|
||||
let mut to_seek = written - file_offset;
|
||||
if to_seek > buf_len {
|
||||
to_seek = buf_len;
|
||||
}
|
||||
|
||||
self.seek(SeekFrom::Current(to_seek.try_into().unwrap()))
|
||||
.unwrap();
|
||||
Ok(to_seek.try_into().unwrap())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> std::io::Result<()> {
|
||||
self.file.sync_all()
|
||||
}
|
||||
}
|
||||
|
||||
impl Seek for RawFile {
|
||||
fn seek(&mut self, newpos: SeekFrom) -> std::io::Result<u64> {
|
||||
match self.file.seek(newpos) {
|
||||
Ok(pos) => {
|
||||
self.position = pos;
|
||||
Ok(pos)
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PunchHole for RawFile {
|
||||
fn punch_hole(&mut self, offset: u64, length: u64) -> std::io::Result<()> {
|
||||
self.file.punch_hole(offset, length)
|
||||
}
|
||||
}
|
||||
|
||||
impl SeekHole for RawFile {
|
||||
fn seek_hole(&mut self, offset: u64) -> std::io::Result<Option<u64>> {
|
||||
match self.file.seek_hole(offset) {
|
||||
Ok(pos) => {
|
||||
if let Some(p) = pos {
|
||||
self.position = p;
|
||||
}
|
||||
Ok(pos)
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
fn seek_data(&mut self, offset: u64) -> std::io::Result<Option<u64>> {
|
||||
match self.file.seek_data(offset) {
|
||||
Ok(pos) => {
|
||||
if let Some(p) = pos {
|
||||
self.position = p;
|
||||
}
|
||||
Ok(pos)
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for RawFile {
|
||||
fn clone(&self) -> Self {
|
||||
RawFile {
|
||||
file: self.file.try_clone().expect("RawFile cloning failed"),
|
||||
alignment: self.alignment,
|
||||
position: self.position,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
|
||||
use std;
|
||||
use std::fmt::{self, Display};
|
||||
use std::io;
|
||||
|
||||
|
||||
@@ -120,7 +120,7 @@ impl<T: Cacheable> CacheMap<T> {
|
||||
{
|
||||
if self.map.len() == self.capacity {
|
||||
// TODO(dgreid) - smarter eviction strategy.
|
||||
let to_evict = *self.map.iter().next().unwrap().0;
|
||||
let to_evict = *self.map.iter().nth(0).unwrap().0;
|
||||
if let Some(evicted) = self.map.remove(&to_evict) {
|
||||
if evicted.dirty() {
|
||||
write_callback(to_evict, evicted)?;
|
||||
|
||||
393
release-notes.md
393
release-notes.md
@@ -1,52 +1,3 @@
|
||||
- [v0.8.0](#v080)
|
||||
- [Experimental Snapshot and Restore Support](#experimental-snapshot-and-restore-support)
|
||||
- [Experimental ARM64 Support](#experimental-arm64-support)
|
||||
- [Support for Using 5-level Paging in Guests](#support-for-using-5-level-paging-in-guests)
|
||||
- [Virtio Device Interrupt Suppression for Network Devices](#virtio-device-interrupt-suppression-for-network-devices)
|
||||
- [`vhost_user_fs` Improvements](#vhost_user_fs-improvements)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes)
|
||||
- [Command Line and API Changes](#command-line-and-api-changes)
|
||||
- [Contributors](#contributors)
|
||||
- [v0.9.0](#v090)
|
||||
- [`io_uring` Based Block Device Support](#io_uring-based-block-device-support)
|
||||
- [Block and Network Device Statistics](#block-and-network-device-statistics)
|
||||
- [HTTP API Responses](#http-api-responses)
|
||||
- [CPU Topology](#cpu-topology)
|
||||
- [Release Build Optimization](#release-build-optimization)
|
||||
- [Hypervisor Abstraction](#hypervisor-abstraction)
|
||||
- [Snapshot/Restore Improvements](#snapshotrestore-improvements)
|
||||
- [Virtio Memory Ballooning Support](#virtio-memory-ballooning-support)
|
||||
- [Enhancements to ARM64 Support](#enhancements-to-arm64-support)
|
||||
- [Intel SGX Support](#intel-sgx-support)
|
||||
- [`Seccomp` Sandbox Improvements](#seccomp-sandbox-improvements)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes)
|
||||
- [Contributors](#contributors)
|
||||
- [v0.8.0](#v080)
|
||||
- [Experimental Snapshot and Restore Support](#experimental-snapshot-and-restore-support)
|
||||
- [Experimental ARM64 Support](#experimental-arm64-support)
|
||||
- [Support for Using 5-level Paging in Guests](#support-for-using-5-level-paging-in-guests)
|
||||
- [Virtio Device Interrupt Suppression for Network Devices](#virtio-device-interrupt-suppression-for-network-devices)
|
||||
- [`vhost_user_fs` Improvements](#vhost_user_fs-improvements)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes-1)
|
||||
- [Command Line and API Changes](#command-line-and-api-changes)
|
||||
- [Contributors](#contributors-1)
|
||||
- [v0.7.0](#v070)
|
||||
- [Block, Network, Persistent Memory (PMEM), VirtioFS and Vsock hotplug](#block-network-persistent-memory-pmem-virtiofs-and-vsock-hotplug)
|
||||
- [Alternative `libc` Support](#alternative-libc-support)
|
||||
- [Multithreaded Multi Queued `vhost-user` Backends](#multithreaded-multi-queued-vhost-user-backends)
|
||||
- [Initial RamFS Support](#initial-ramfs-support)
|
||||
- [Alternative Memory Hotplug: `virtio-mem`](#alternative-memory-hotplug-virtio-mem)
|
||||
- [`Seccomp` Sandboxing](#seccomp-sandboxing)
|
||||
- [Updated Distribution Support](#updated-distribution-support)
|
||||
- [Command Line and API Changes](#command-line-and-api-changes-1)
|
||||
- [Contributors](#contributors-2)
|
||||
- [v0.6.0](#v060)
|
||||
- [Directly Assigned Devices Hotplug](#directly-assigned-devices-hotplug)
|
||||
- [Shared Filesystem Improvements](#shared-filesystem-improvements)
|
||||
- [Block and Networking IO Self Offloading](#block-and-networking-io-self-offloading)
|
||||
- [Command Line Interface](#command-line-interface)
|
||||
- [PVH Boot](#pvh-boot)
|
||||
- [Contributors](#contributors-3)
|
||||
- [v0.5.1](#v051)
|
||||
- [v0.5.0](#v050)
|
||||
- [Virtual Machine Dynamic Resizing](#virtual-machine-dynamic-resizing)
|
||||
@@ -54,7 +5,7 @@
|
||||
- [New Interrupt Management Framework](#new-interrupt-management-framework)
|
||||
- [Development Tools](#development-tools)
|
||||
- [Kata Containers Integration](#kata-containers-integration)
|
||||
- [Contributors](#contributors-4)
|
||||
- [Contributors](#contributors)
|
||||
- [v0.4.0](#v040)
|
||||
- [Dynamic virtual CPUs addition](#dynamic-virtual-cpus-addition)
|
||||
- [Programmatic firmware tables generation](#programmatic-firmware-tables-generation)
|
||||
@@ -63,7 +14,7 @@
|
||||
- [Userspace IOAPIC by default](#userspace-ioapic-by-default)
|
||||
- [PCI BAR reprogramming](#pci-bar-reprogramming)
|
||||
- [New `cloud-hypervisor` organization](#new-cloud-hypervisor-organization)
|
||||
- [Contributors](#contributors-5)
|
||||
- [Contributors](#contributors-1)
|
||||
- [v0.3.0](#v030)
|
||||
- [Block device offloading](#block-device-offloading)
|
||||
- [Network device backend](#network-device-backend)
|
||||
@@ -90,346 +41,6 @@
|
||||
- [Unit testing](#unit-testing)
|
||||
- [Integration tests parallelization](#integration-tests-parallelization)
|
||||
|
||||
# v0.9.0
|
||||
|
||||
This release has been tracked through the [0.9.0 project](https://github.com/cloud-hypervisor/cloud-hypervisor/projects/12).
|
||||
|
||||
Highlights for `cloud-hypervisor` version 0.9.0 include:
|
||||
|
||||
### `io_uring` Based Block Device Support
|
||||
|
||||
If the `io_uring` feature is enabled and the host kernel supports it then `io_uring` will be used for block devices. This results a very significant performance improvement.
|
||||
|
||||
### Block and Network Device Statistics
|
||||
|
||||
Statistics for activity of the `virtio` network and block devices is now exposed through a new `vm.counters` HTTP API entry point. These take the form of simple counters which can be used to observe the activity of the VM.
|
||||
|
||||
### HTTP API Responses
|
||||
|
||||
The HTTP API for adding devices now responds with the name that was assigned to the device as well the PCI BDF.
|
||||
|
||||
### CPU Topology
|
||||
|
||||
A `topology` parameter has been added to `--cpus` which allows the configuration of the guest CPU topology allowing the user to specify the numbers of sockets, packages per socket, cores per package and threads per core.
|
||||
|
||||
### Release Build Optimization
|
||||
|
||||
Our release build is now built with LTO (*Link Time Optimization*) which results in a ~20% reduction in the binary size.
|
||||
|
||||
### Hypervisor Abstraction
|
||||
|
||||
A new abstraction has been introduced, in the form of a `hypervisor` crate so as to enable the support of additional hypervisors beyond `KVM`.
|
||||
|
||||
### Snapshot/Restore Improvements
|
||||
|
||||
Multiple improvements have been made to the VM snapshot/restore support that was added in the last release. This includes persisting more vCPU state and in particular preserving the guest paravirtualized clock in order to avoid vCPU hangs inside the guest when running with multiple vCPUs.
|
||||
|
||||
### Virtio Memory Ballooning Support
|
||||
|
||||
A `virtio-balloon` device has been added, controlled through the `resize` control, which allows the reclamation of host memory by resizing a memory balloon inside the guest.
|
||||
|
||||
### Enhancements to ARM64 Support
|
||||
|
||||
The ARM64 support introduced in the last release has been further enhanced with support for using PCI for exposing devices into the guest as well as multiple bug fixes. It also now supports using an initramfs when booting.
|
||||
|
||||
### Intel SGX Support
|
||||
|
||||
The guest can now use Intel SGX if the host supports it. Details can be found in the dedicated [SGX documentation](docs/intel_sgx.md).
|
||||
|
||||
### `Seccomp` Sandbox Improvements
|
||||
|
||||
The most frequently used virtio devices are now isolated with their own `seccomp` filters. It is also now possible to pass `--seccomp=log` which result in the logging of requests that would have otherwise been denied to further aid development.
|
||||
|
||||
### Notable Bug Fixes
|
||||
|
||||
* Our `virtio-vsock` implementation has been resynced with the implementation from Firecracker and includes multiple bug fixes.
|
||||
* CPU hotplug has been fixed so that it is now possible to add, remove, and re-add vCPUs (#1338)
|
||||
* A workaround is now in place for when KVM reports MSRs available MSRs that are in fact unreadable preventing snapshot/restore from working correctly (#1543).
|
||||
* `virtio-mmio` based devices are now more widely tested (#275).
|
||||
* Multiple issues have been fixed with virtio device configuration (#1217)
|
||||
* Console input was wrongly consumed by both `virtio-console` and the serial. (#1521)
|
||||
|
||||
### Contributors
|
||||
|
||||
Many thanks to everyone who has contributed to our 0.9.0 release including some new faces.
|
||||
|
||||
* Anatol Belski <ab@php.net>
|
||||
* Bo Chen <chen.bo@intel.com>
|
||||
* Dr. David Alan Gilbert <dgilbert@redhat.com>
|
||||
* Henry Wang <Henry.Wang@arm.com>
|
||||
* Howard Zhang <howard.zhang@arm.com>
|
||||
* Hui Zhu <teawater@antfin.com>
|
||||
* Jianyong Wu <jianyong.wu@arm.com>
|
||||
* Jose Carlos Venegas Munoz <jose.carlos.venegas.munoz@intel.com>
|
||||
* LiYa'nan <oliverliyn@gmail.com>
|
||||
* Michael Zhao <michael.zhao@arm.com>
|
||||
* Muminul Islam <muislam@microsoft.com>
|
||||
* Praveen Paladugu <prapal@microsoft.com>
|
||||
* Ricardo Koller <ricarkol@gmail.com>
|
||||
* Rob Bradford <robert.bradford@intel.com>
|
||||
* Samuel Ortiz <sameo@linux.intel.com>
|
||||
* Sebastien Boeuf <sebastien.boeuf@intel.com>
|
||||
* Stefano Garzarella <sgarzare@redhat.com>
|
||||
* Wei Liu <liuwe@microsoft.com>
|
||||
|
||||
|
||||
# v0.8.0
|
||||
|
||||
This release has been tracked through the [0.8.0 project](https://github.com/cloud-hypervisor/cloud-hypervisor/projects/10).
|
||||
|
||||
Highlights for `cloud-hypervisor` version 0.8.0 include:
|
||||
|
||||
### Experimental Snapshot and Restore Support
|
||||
|
||||
This release includes the first version of the snapshot and restore feature.
|
||||
This allows a VM to be paused and then subsequently snapshotted. At a later
|
||||
point that snapshot may be restored into a new running VM identical to the
|
||||
original VM at the point it was paused.
|
||||
|
||||
This feature can be used for offline migration from one VM host to another, to
|
||||
allow the upgrading or rebooting of the host machine transparently to the guest
|
||||
or for templating the VM. This is an experimental feature and cannot be used on
|
||||
a VM using passthrough (VFIO) devices. Issues with SMP have also been observed
|
||||
(#1176).
|
||||
|
||||
### Experimental ARM64 Support
|
||||
|
||||
Included in this release is experimental support for running on ARM64.
|
||||
Currently only `virtio-mmio` devices and a serial port are supported. Full
|
||||
details can be found in the [ARM64 documentation](docs/arm64.md).
|
||||
|
||||
### Support for Using 5-level Paging in Guests
|
||||
|
||||
If the host supports it the guest is now enabled for 5-level paging (aka LA57).
|
||||
This works when booting the Linux kernel with a vmlinux, bzImage or firmware
|
||||
based boot. However booting an ELF kernel built with `CONFIG_PVH=y` does not
|
||||
work due to current limitations in the PVH boot process.
|
||||
|
||||
### Virtio Device Interrupt Suppression for Network Devices
|
||||
|
||||
With `virtio-net` and `vhost-user-net` devices the guest can suppress
|
||||
interrupts from the VMM by using the `VIRTIO_RING_F_EVENT_IDX` feature. This
|
||||
can lead to an improvement in performance by reducing the number of interrupts
|
||||
the guest must service.
|
||||
|
||||
### `vhost_user_fs` Improvements
|
||||
|
||||
The implementation in Cloud Hypervisor of the VirtioFS server now supports sandboxing itself with `seccomp`.
|
||||
|
||||
|
||||
### Notable Bug Fixes
|
||||
|
||||
* VMs that have not yet been booted can now be deleted (#1110).
|
||||
* By creating the `tap` device ahead of creating the VM it is not required to
|
||||
run the `cloud-hypervisor` binary with `CAP_NET_ADMIN` (#1273).
|
||||
* Block I/O via `virtio-block` or `vhost-user-block` now correctly adheres to
|
||||
the specification and synchronizes to the underlying filesystem as required
|
||||
based on guest feature negotiation. This avoids potential data loss (#399,
|
||||
#1216).
|
||||
* When booting with a large number of vCPUs then the ACPI table would be
|
||||
overwritten by the SMP `MPTABLE`. When compiled with the `acpi` feature the
|
||||
`MPTABLE` will no longer be generated (#1132).
|
||||
* Shutting down VMs that have been paused is now supported (#816).
|
||||
* Created socket files are deleted on shutdown (#1083).
|
||||
* Trying to use passthrough devices (VFIO) will be rejected on `mmio` builds
|
||||
(#751).
|
||||
|
||||
### Command Line and API Changes
|
||||
|
||||
This is non exhaustive list of HTTP API and command line changes:
|
||||
|
||||
* All user visible socket parameters are now consistently called `socket`
|
||||
rather than `sock` in some cases.
|
||||
* The `ch-remote` tool now shows any error message generated by the VMM
|
||||
* The `wce` parameter has been removed from `--disk` as the feature is always
|
||||
offered for negotiation.
|
||||
* `--net` has gained a `host_mac` option that allows the setting of the MAC
|
||||
address for the `tap` device on the host.
|
||||
|
||||
### Contributors
|
||||
|
||||
Many thanks to everyone who has contributed to our 0.8.0 release including some new faces.
|
||||
|
||||
* Anatol Belski <ab@php.net>
|
||||
* Arron Wang <arron.wang@intel.com>
|
||||
* Bo Chen <chen.bo@intel.com>
|
||||
* Dr. David Alan Gilbert <dgilbert@redhat.com>
|
||||
* Henry Wang <Henry.Wang@arm.com>
|
||||
* Hui Zhu <teawater@antfin.com>
|
||||
* LiYa'nan <oliverliyn@gmail.com>
|
||||
* Michael Zhao <michael.zhao@arm.com>
|
||||
* Rob Bradford <robert.bradford@intel.com>
|
||||
* Samuel Ortiz <sameo@linux.intel.com>
|
||||
* Sebastien Boeuf <sebastien.boeuf@intel.com>
|
||||
* Sergio Lopez <slp@redhat.com>
|
||||
|
||||
# v0.7.0
|
||||
|
||||
This release has been tracked through the [0.7.0 project](https://github.com/cloud-hypervisor/cloud-hypervisor/projects/7).
|
||||
|
||||
Highlights for `cloud-hypervisor` version 0.7.0 include:
|
||||
|
||||
### Block, Network, Persistent Memory (PMEM), VirtioFS and Vsock hotplug
|
||||
|
||||
Further to our effort to support modifying a running guest we now support
|
||||
hotplug and unplug of the following virtio backed devices: block, network,
|
||||
pmem, virtio-fs and vsock. This functionality is available on the (default) PCI
|
||||
based tranport and is exposed through the HTTP API. The `ch-remote` utility
|
||||
provides a CLI for adding or removing these device types after the VM has
|
||||
booted. User can use the `id` parameter on the devices to choose names for
|
||||
devices to ease their removal.
|
||||
|
||||
### Alternative `libc` Support
|
||||
|
||||
Cloud Hypervisor can now be compiled with the `musl` C library and this release
|
||||
contains a static binary compiled using that toolchain.
|
||||
|
||||
### Multithreaded Multi Queued `vhost-user` Backends
|
||||
|
||||
The `vhost-user` backends for network and block support that are shipped by
|
||||
Cloud Hypervisor have been enhanced to support multiple threads and queues to
|
||||
improve throughput. These backends are used automatically if `vhost_user=true`
|
||||
is passed when the devices are created.
|
||||
|
||||
### Initial RamFS Support
|
||||
|
||||
By passing the `--initramfs` command line option the user can specify a file to
|
||||
be loaded into the guest memory to be used as the kernel initial filesystem.
|
||||
This is usually used to allow the loading of drivers needed to be able to
|
||||
access the real root filesystem but it can also be used standalone for a very
|
||||
minimal image.
|
||||
|
||||
### Alternative Memory Hotplug: `virtio-mem`
|
||||
|
||||
As well as supporting ACPI based hotplug Cloud Hypervisor now supports using
|
||||
the `virtio-mem` hotplug alternative. This can be controlled by the
|
||||
`hotplug_method` parameter on the `--memory` command line option. It currently
|
||||
requires kernel patches to be able to support it.
|
||||
|
||||
### `Seccomp` Sandboxing
|
||||
|
||||
Cloud Hypervisor now has support for restricting the system calls that the
|
||||
process can use via the `seccomp` security API. This on by default and is
|
||||
controlled by the `--seccomp` command line option.
|
||||
|
||||
### Updated Distribution Support
|
||||
|
||||
With the release of Ubuntu 20.04 we have added that to the list of supported
|
||||
distributions and is part of our regular testing programme.
|
||||
|
||||
### Command Line and API Changes
|
||||
|
||||
This is non exhaustive list of HTTP API and command line changes
|
||||
|
||||
* New `id` fields added for devices to allow them to be named to ease removal.
|
||||
If no name is specified the VMM chooses one.
|
||||
* Use `--memory`'s `shared` and `hugepages` controls for determining backing
|
||||
memory instead of providing a path.
|
||||
* The `--vsock` parameter only takes one device as the Linux kernel only
|
||||
supports a single Vsock device. The REST API has removed the vector for this
|
||||
option and replaced it with a single optional field.
|
||||
* There is enhanced validation of the command line and API provided
|
||||
configurations to ensure that the provided options are compatible e.g. that
|
||||
shared memory is in use if any attempt is made to used a `vhost-user` backed
|
||||
device.
|
||||
* `ch-remote` has added `add-disk`, `add-fs`, `add-net`, `add-pmem` and
|
||||
`add-vsock` subcommands. For removal `remove-device` is used. The REST API
|
||||
has appropriate new HTTP endpoints too.
|
||||
* Specifying a `size` with `--pmem` is no longer required and instead the size
|
||||
will be obtained from the file. A `discard_writes` option has also been added
|
||||
to provide the equivalent of a read-only file.
|
||||
* The parameters to `--block-backend` have been changed to more closely align
|
||||
with those used by `--disk`.
|
||||
|
||||
### Contributors
|
||||
|
||||
Many thanks to everyone who has contributed to our 0.7.0 release including some new faces.
|
||||
|
||||
* Alejandro Jimenez <alejandro.j.jimenez@oracle.com>
|
||||
* Bo Chen <chen.bo@intel.com>
|
||||
* Cathy Zhang <cathy.zhang@intel.com>
|
||||
* Damjan Georgievski <gdamjan@gmail.com>
|
||||
* Dean Sheather <dean@coder.com>
|
||||
* Eryu Guan <eguan@linux.alibaba.com>
|
||||
* Hui Zhu <teawater@antfin.com>
|
||||
* Jose Carlos Venegas Munoz <jose.carlos.venegas.munoz@intel.com>
|
||||
* Martin Xu <martin.xu@intel.com>
|
||||
* Muminul Islam <muislam@microsoft.com>
|
||||
* Rob Bradford <robert.bradford@intel.com>
|
||||
* Samuel Ortiz <sameo@linux.intel.com>
|
||||
* Sebastien Boeuf <sebastien.boeuf@intel.com>
|
||||
* Sergio Lopez <slp@redhat.com>
|
||||
* Yang Zhong <yang.zhong@intel.com>
|
||||
* Yi Sun <yi.y.sun@linux.intel.com>
|
||||
|
||||
# v0.6.0
|
||||
|
||||
This release has been tracked through the [0.6.0 project](https://github.com/cloud-hypervisor/cloud-hypervisor/projects/7).
|
||||
|
||||
Highlights for `cloud-hypervisor` version 0.6.0 include:
|
||||
|
||||
### Directly Assigned Devices Hotplug
|
||||
|
||||
We continued our efforts around supporting dynamically changing the guest
|
||||
resources. After adding support for CPU and memory hotplug, Cloud Hypervisor
|
||||
now supports hot plugging and hot unplugging directly assigned (a.k.a. `VFIO`)
|
||||
devices into an already running guest. This closes the features gap for
|
||||
providing a complete Kata Containers workloads support with Cloud Hypervisor.
|
||||
|
||||
### Shared Filesystem Improvements
|
||||
|
||||
We enhanced our shared filesystem support through many `virtio-fs` improvements.
|
||||
By adding support for DAX, parallel processing of multiple requests, `FS_IO`,
|
||||
`LSEEK` and the `MMIO` virtio transport layer to our `vhost_user_fs` daemon, we
|
||||
improved our filesystem sharing performance, but also made it more stable and
|
||||
compatible with other `virtio-fs` implementations.
|
||||
|
||||
### Block and Networking IO Self Offloading
|
||||
|
||||
When choosing to offload the paravirtualized block and networking I/O to an
|
||||
external process (through the `vhost-user` protocol), Cloud Hypervisor now
|
||||
automatically spawns its default `vhost-user-blk` and `vhost-user-net` backends
|
||||
into their own, separate processes.
|
||||
This provides a seamless parvirtualized I/O user experience for those who want
|
||||
to run their guest I/O into separate executions contexts.
|
||||
|
||||
### Command Line Interface
|
||||
|
||||
More and more Cloud Hypervisor services are exposed through the
|
||||
[Rest API](https://github.com/cloud-hypervisor/cloud-hypervisor/blob/master/vmm/src/api/openapi/cloud-hypervisor.yaml)
|
||||
and thus only accessible via relatively cumbersome HTTP calls. In order
|
||||
to abstract those calls into a more user friendly tool, we created a Cloud
|
||||
Hypervisor Command Line Interface (CLI) called `ch-remote`.
|
||||
The `ch-remote` binary is created with each build and available e.g. at
|
||||
`cloud-hypervisor/target/debug/ch-remote` when doing a debug build.
|
||||
|
||||
Please check `ch-remote --help` for a complete description of all available
|
||||
commands.
|
||||
|
||||
### PVH Boot
|
||||
|
||||
In addition to the traditional Linux boot protocol, Cloud Hypervisor now
|
||||
supports direct kernel booting through the [PVH ABI](https://xenbits.xen.org/docs/unstable/misc/pvh.html).
|
||||
|
||||
### Contributors
|
||||
|
||||
With the 0.6.0 release, we are welcoming a few new contributors. Many thanks
|
||||
to them and to everyone that contributed to this release:
|
||||
|
||||
* Alejandro Jimenez <alejandro.j.jimenez@oracle.com>
|
||||
* Arron Wang <arron.wang@intel.com>
|
||||
* Bin Liu <liubin0329@gmail.com>
|
||||
* Bo Chen <chen.bo@intel.com>
|
||||
* Cathy Zhang <cathy.zhang@intel.com>
|
||||
* Eryu Guan <eguan@linux.alibaba.com>
|
||||
* Jose Carlos Venegas Munoz <jose.carlos.venegas.munoz@intel.com>
|
||||
* Liu Bo <bo.liu@linux.alibaba.com>
|
||||
* Qiu Wenbo <qiuwenbo@phytium.com.cn>
|
||||
* Rob Bradford <robert.bradford@intel.com>
|
||||
* Samuel Ortiz <sameo@linux.intel.com>
|
||||
* Sebastien Boeuf <sebastien.boeuf@intel.com>
|
||||
* Sergio Lopez <slp@redhat.com>
|
||||
|
||||
# v0.5.1
|
||||
|
||||
This is a bugfix release branched off v0.5.0. It contains the following fixes:
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
FROM ubuntu:18.04 as dev
|
||||
|
||||
ARG TARGETARCH="x86_64"
|
||||
ARG RUST_TOOLCHAIN="1.43.0"
|
||||
ARG RUST_TOOLCHAIN="1.40.0"
|
||||
ARG CLH_SRC_DIR="/cloud-hypervisor"
|
||||
ARG CLH_BUILD_DIR="$CLH_SRC_DIR/build"
|
||||
ARG CARGO_REGISTRY_DIR="$CLH_BUILD_DIR/cargo_registry"
|
||||
@@ -12,9 +11,9 @@ ENV RUSTUP_HOME=$CARGO_HOME
|
||||
ENV PATH="$PATH:$CARGO_HOME/bin"
|
||||
|
||||
# Install all CI dependencies
|
||||
RUN apt-get update \
|
||||
&& apt-get -yq upgrade \
|
||||
&& DEBIAN_FRONTEND=noninteractive apt-get install -yq \
|
||||
RUN apt-get update
|
||||
RUN apt-get -yq upgrade
|
||||
RUN DEBIAN_FRONTEND=noninteractive apt-get install -yq \
|
||||
build-essential \
|
||||
bc \
|
||||
docker.io \
|
||||
@@ -22,7 +21,6 @@ RUN apt-get update \
|
||||
wget \
|
||||
sudo \
|
||||
mtools \
|
||||
musl-tools \
|
||||
libssl-dev \
|
||||
pkg-config \
|
||||
flex \
|
||||
@@ -36,47 +34,13 @@ RUN apt-get update \
|
||||
libcap-ng-dev \
|
||||
socat \
|
||||
dosfstools \
|
||||
cpio \
|
||||
bsdtar \
|
||||
libfdt-dev \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN if [ "$TARGETARCH" = "x86_64" ]; then \
|
||||
apt-get update \
|
||||
&& apt-get -yq upgrade \
|
||||
&& DEBIAN_FRONTEND=noninteractive apt-get install -yq gcc-multilib \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*; fi
|
||||
|
||||
RUN if [ "$TARGETARCH" = "aarch64" ]; then \
|
||||
# On AArch64, `setcap` binary should be installed via `libcap2-bin`.
|
||||
# The `setcap` binary is used in integration tests.
|
||||
apt-get update \
|
||||
&& apt-get -yq upgrade \
|
||||
&& DEBIAN_FRONTEND=noninteractive apt-get install -yq libcap2-bin \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
# Build and install the musl-gcc version of libfdt. This is to ensure
|
||||
# the cloud-hypervisor binary can be built and linked using musl toolchain.
|
||||
&& git clone https://github.com/dgibson/dtc.git && cd dtc \
|
||||
&& sed -i 's/$(LIBFDT_archive) $(LIBFDT_lib)/$(LIBFDT_archive)/g' Makefile \
|
||||
&& export CC=musl-gcc && make libfdt \
|
||||
&& cp libfdt/libfdt.a /usr/lib/aarch64-linux-musl \
|
||||
&& rm -rf /dtc; fi
|
||||
|
||||
# Fix the libssl-dev install
|
||||
RUN cp /usr/include/"$TARGETARCH"-linux-gnu/openssl/opensslconf.h /usr/include/openssl/
|
||||
ENV OPENSSL_DIR=/usr/lib/"$TARGETARCH"-linux-gnu/
|
||||
ENV OPENSSL_LIB_DIR=/usr/lib/"$TARGETARCH"-linux-gnu/
|
||||
ENV OPENSSL_INCLUDE_DIR=/usr/include/
|
||||
|
||||
# Install the rust toolchain
|
||||
RUN nohup curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain "$RUST_TOOLCHAIN" \
|
||||
&& rustup target add $TARGETARCH-unknown-linux-musl --toolchain "$RUST_TOOLCHAIN" \
|
||||
&& if [ "$TARGETARCH" = "x86_64" ]; then rustup toolchain add $RUST_TOOLCHAIN-x86_64-unknown-linux-musl; fi \
|
||||
&& if [ "$TARGETARCH" = "x86_64" ]; then rustup component add rustfmt; fi \
|
||||
&& if [ "$TARGETARCH" = "x86_64" ]; then rustup component add clippy; fi \
|
||||
&& rustup component add rustfmt \
|
||||
&& rustup component add clippy \
|
||||
&& cargo install cargo-audit \
|
||||
&& rm -rf "$CARGO_HOME/registry" \
|
||||
&& ln -s "$CARGO_REGISTRY_DIR" "$CARGO_HOME/registry" \
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,18 +1,20 @@
|
||||
#
|
||||
# Automatically generated file; DO NOT EDIT.
|
||||
# Linux/x86 5.8.0-rc4 Kernel Configuration
|
||||
# Linux/x86 5.5.0-rc1 Kernel Configuration
|
||||
#
|
||||
|
||||
#
|
||||
# Compiler: gcc (Ubuntu 8.3.0-6ubuntu1) 8.3.0
|
||||
#
|
||||
CONFIG_CC_VERSION_TEXT="gcc (Ubuntu 9.3.0-10ubuntu2) 9.3.0"
|
||||
CONFIG_CC_IS_GCC=y
|
||||
CONFIG_GCC_VERSION=90300
|
||||
CONFIG_LD_VERSION=234000000
|
||||
CONFIG_GCC_VERSION=80300
|
||||
CONFIG_CLANG_VERSION=0
|
||||
CONFIG_CC_CAN_LINK=y
|
||||
CONFIG_CC_CAN_LINK_STATIC=y
|
||||
CONFIG_CC_HAS_ASM_GOTO=y
|
||||
CONFIG_CC_HAS_ASM_INLINE=y
|
||||
CONFIG_CC_HAS_WARN_MAYBE_UNINITIALIZED=y
|
||||
CONFIG_IRQ_WORK=y
|
||||
CONFIG_BUILDTIME_TABLE_SORT=y
|
||||
CONFIG_BUILDTIME_EXTABLE_SORT=y
|
||||
CONFIG_THREAD_INFO_IN_TASK=y
|
||||
|
||||
#
|
||||
@@ -35,14 +37,12 @@ CONFIG_KERNEL_GZIP=y
|
||||
# CONFIG_KERNEL_XZ is not set
|
||||
# CONFIG_KERNEL_LZO is not set
|
||||
# CONFIG_KERNEL_LZ4 is not set
|
||||
CONFIG_DEFAULT_INIT=""
|
||||
CONFIG_DEFAULT_HOSTNAME="cloud-hypervisor"
|
||||
CONFIG_SWAP=y
|
||||
CONFIG_SYSVIPC=y
|
||||
CONFIG_SYSVIPC_SYSCTL=y
|
||||
CONFIG_POSIX_MQUEUE=y
|
||||
CONFIG_POSIX_MQUEUE_SYSCTL=y
|
||||
# CONFIG_WATCH_QUEUE is not set
|
||||
CONFIG_CROSS_MEMORY_ATTACH=y
|
||||
# CONFIG_USELIB is not set
|
||||
CONFIG_AUDIT=y
|
||||
@@ -57,7 +57,6 @@ CONFIG_GENERIC_IRQ_SHOW=y
|
||||
CONFIG_GENERIC_IRQ_EFFECTIVE_AFF_MASK=y
|
||||
CONFIG_GENERIC_PENDING_IRQ=y
|
||||
CONFIG_GENERIC_IRQ_MIGRATION=y
|
||||
CONFIG_HARDIRQS_SW_RESEND=y
|
||||
CONFIG_IRQ_DOMAIN=y
|
||||
CONFIG_IRQ_DOMAIN_HIERARCHY=y
|
||||
CONFIG_GENERIC_MSI_IRQ=y
|
||||
@@ -71,6 +70,7 @@ CONFIG_SPARSE_IRQ=y
|
||||
# end of IRQ subsystem
|
||||
|
||||
CONFIG_CLOCKSOURCE_WATCHDOG=y
|
||||
CONFIG_ARCH_CLOCKSOURCE_DATA=y
|
||||
CONFIG_ARCH_CLOCKSOURCE_INIT=y
|
||||
CONFIG_CLOCKSOURCE_VALIDATE_LAST_CYCLE=y
|
||||
CONFIG_GENERIC_TIME_VSYSCALL=y
|
||||
@@ -102,7 +102,6 @@ CONFIG_TICK_CPU_ACCOUNTING=y
|
||||
# CONFIG_VIRT_CPU_ACCOUNTING_GEN is not set
|
||||
# CONFIG_IRQ_TIME_ACCOUNTING is not set
|
||||
CONFIG_HAVE_SCHED_AVG_IRQ=y
|
||||
# CONFIG_SCHED_THERMAL_PRESSURE is not set
|
||||
CONFIG_BSD_PROCESS_ACCT=y
|
||||
CONFIG_BSD_PROCESS_ACCT_V3=y
|
||||
CONFIG_TASKSTATS=y
|
||||
@@ -136,7 +135,6 @@ CONFIG_HAVE_UNSTABLE_SCHED_CLOCK=y
|
||||
#
|
||||
# Scheduler features
|
||||
#
|
||||
# CONFIG_UCLAMP_TASK is not set
|
||||
# end of Scheduler features
|
||||
|
||||
CONFIG_ARCH_SUPPORTS_NUMA_BALANCING=y
|
||||
@@ -149,6 +147,7 @@ CONFIG_CGROUPS=y
|
||||
CONFIG_PAGE_COUNTER=y
|
||||
CONFIG_MEMCG=y
|
||||
CONFIG_MEMCG_SWAP=y
|
||||
CONFIG_MEMCG_SWAP_ENABLED=y
|
||||
CONFIG_MEMCG_KMEM=y
|
||||
CONFIG_BLK_CGROUP=y
|
||||
CONFIG_CGROUP_WRITEBACK=y
|
||||
@@ -170,7 +169,6 @@ CONFIG_CGROUP_BPF=y
|
||||
CONFIG_SOCK_CGROUP_DATA=y
|
||||
CONFIG_NAMESPACES=y
|
||||
CONFIG_UTS_NS=y
|
||||
# CONFIG_TIME_NS is not set
|
||||
CONFIG_IPC_NS=y
|
||||
CONFIG_USER_NS=y
|
||||
CONFIG_PID_NS=y
|
||||
@@ -179,15 +177,7 @@ CONFIG_NET_NS=y
|
||||
CONFIG_SCHED_AUTOGROUP=y
|
||||
# CONFIG_SYSFS_DEPRECATED is not set
|
||||
CONFIG_RELAY=y
|
||||
CONFIG_BLK_DEV_INITRD=y
|
||||
CONFIG_INITRAMFS_SOURCE=""
|
||||
CONFIG_RD_GZIP=y
|
||||
CONFIG_RD_BZIP2=y
|
||||
CONFIG_RD_LZMA=y
|
||||
CONFIG_RD_XZ=y
|
||||
CONFIG_RD_LZO=y
|
||||
CONFIG_RD_LZ4=y
|
||||
# CONFIG_BOOT_CONFIG is not set
|
||||
# CONFIG_BLK_DEV_INITRD is not set
|
||||
CONFIG_CC_OPTIMIZE_FOR_PERFORMANCE=y
|
||||
# CONFIG_CC_OPTIMIZE_FOR_SIZE is not set
|
||||
CONFIG_SYSCTL=y
|
||||
@@ -216,14 +206,12 @@ CONFIG_SHMEM=y
|
||||
CONFIG_AIO=y
|
||||
CONFIG_IO_URING=y
|
||||
CONFIG_ADVISE_SYSCALLS=y
|
||||
CONFIG_HAVE_ARCH_USERFAULTFD_WP=y
|
||||
CONFIG_MEMBARRIER=y
|
||||
CONFIG_KALLSYMS=y
|
||||
# CONFIG_KALLSYMS_ALL is not set
|
||||
CONFIG_KALLSYMS_ABSOLUTE_PERCPU=y
|
||||
CONFIG_KALLSYMS_BASE_RELATIVE=y
|
||||
CONFIG_BPF_SYSCALL=y
|
||||
CONFIG_ARCH_WANT_DEFAULT_BPF_JIT=y
|
||||
CONFIG_USERFAULTFD=y
|
||||
CONFIG_ARCH_HAS_MEMBARRIER_SYNC_CORE=y
|
||||
CONFIG_RSEQ=y
|
||||
@@ -259,6 +247,7 @@ CONFIG_X86_64=y
|
||||
CONFIG_X86=y
|
||||
CONFIG_INSTRUCTION_DECODER=y
|
||||
CONFIG_OUTPUT_FORMAT="elf64-x86-64"
|
||||
CONFIG_ARCH_DEFCONFIG="arch/x86/configs/x86_64_defconfig"
|
||||
CONFIG_LOCKDEP_SUPPORT=y
|
||||
CONFIG_STACKTRACE_SUPPORT=y
|
||||
CONFIG_MMU=y
|
||||
@@ -287,7 +276,7 @@ CONFIG_X86_64_SMP=y
|
||||
CONFIG_ARCH_SUPPORTS_UPROBES=y
|
||||
CONFIG_FIX_EARLYCON_MEM=y
|
||||
CONFIG_DYNAMIC_PHYSICAL_MASK=y
|
||||
CONFIG_PGTABLE_LEVELS=5
|
||||
CONFIG_PGTABLE_LEVELS=4
|
||||
CONFIG_CC_HAS_SANE_STACKPROTECTOR=y
|
||||
|
||||
#
|
||||
@@ -314,6 +303,7 @@ CONFIG_PARAVIRT_SPINLOCKS=y
|
||||
CONFIG_KVM_GUEST=y
|
||||
CONFIG_ARCH_CPUIDLE_HALTPOLL=y
|
||||
CONFIG_PVH=y
|
||||
CONFIG_KVM_DEBUG_FS=y
|
||||
CONFIG_PARAVIRT_TIME_ACCOUNTING=y
|
||||
CONFIG_PARAVIRT_CLOCK=y
|
||||
# CONFIG_JAILHOUSE_GUEST is not set
|
||||
@@ -330,8 +320,6 @@ CONFIG_X86_CMPXCHG64=y
|
||||
CONFIG_X86_CMOV=y
|
||||
CONFIG_X86_MINIMUM_CPU_FAMILY=64
|
||||
CONFIG_X86_DEBUGCTLMSR=y
|
||||
CONFIG_IA32_FEAT_CTL=y
|
||||
CONFIG_X86_VMX_FEATURE_NAMES=y
|
||||
# CONFIG_PROCESSOR_SELECT is not set
|
||||
CONFIG_CPU_SUP_INTEL=y
|
||||
CONFIG_CPU_SUP_AMD=y
|
||||
@@ -371,7 +359,7 @@ CONFIG_X86_VSYSCALL_EMULATION=y
|
||||
# CONFIG_MICROCODE is not set
|
||||
CONFIG_X86_MSR=y
|
||||
CONFIG_X86_CPUID=y
|
||||
CONFIG_X86_5LEVEL=y
|
||||
# CONFIG_X86_5LEVEL is not set
|
||||
CONFIG_X86_DIRECT_GBPAGES=y
|
||||
# CONFIG_X86_CPA_STATISTICS is not set
|
||||
CONFIG_AMD_MEM_ENCRYPT=y
|
||||
@@ -379,6 +367,7 @@ CONFIG_AMD_MEM_ENCRYPT=y
|
||||
CONFIG_NUMA=y
|
||||
# CONFIG_AMD_NUMA is not set
|
||||
CONFIG_X86_64_ACPI_NUMA=y
|
||||
CONFIG_NODES_SPAN_OTHER_NODES=y
|
||||
# CONFIG_NUMA_EMU is not set
|
||||
CONFIG_NODES_SHIFT=10
|
||||
CONFIG_ARCH_SPARSEMEM_ENABLE=y
|
||||
@@ -400,6 +389,7 @@ CONFIG_ARCH_USES_PG_UNCACHED=y
|
||||
CONFIG_ARCH_RANDOM=y
|
||||
CONFIG_X86_SMAP=y
|
||||
# CONFIG_X86_UMIP is not set
|
||||
# CONFIG_X86_INTEL_MPX is not set
|
||||
# CONFIG_X86_INTEL_MEMORY_PROTECTION_KEYS is not set
|
||||
CONFIG_X86_INTEL_TSX_MODE_OFF=y
|
||||
# CONFIG_X86_INTEL_TSX_MODE_ON is not set
|
||||
@@ -421,12 +411,8 @@ CONFIG_ARCH_HAS_KEXEC_PURGATORY=y
|
||||
# CONFIG_CRASH_DUMP is not set
|
||||
CONFIG_PHYSICAL_START=0x1000000
|
||||
CONFIG_RELOCATABLE=y
|
||||
CONFIG_RANDOMIZE_BASE=y
|
||||
CONFIG_X86_NEED_RELOCS=y
|
||||
# CONFIG_RANDOMIZE_BASE is not set
|
||||
CONFIG_PHYSICAL_ALIGN=0x1000000
|
||||
CONFIG_DYNAMIC_MEMORY_LAYOUT=y
|
||||
CONFIG_RANDOMIZE_MEMORY=y
|
||||
CONFIG_RANDOMIZE_MEMORY_PHYSICAL_PADDING=0xa
|
||||
CONFIG_HOTPLUG_CPU=y
|
||||
# CONFIG_BOOTPARAM_HOTPLUG_CPU0 is not set
|
||||
# CONFIG_DEBUG_HOTPLUG_CPU0 is not set
|
||||
@@ -453,7 +439,6 @@ CONFIG_ARCH_HIBERNATION_HEADER=y
|
||||
# CONFIG_SUSPEND is not set
|
||||
CONFIG_HIBERNATE_CALLBACKS=y
|
||||
CONFIG_HIBERNATION=y
|
||||
# CONFIG_HIBERNATION_SNAPSHOT_DEV is not set
|
||||
CONFIG_PM_STD_PARTITION=""
|
||||
CONFIG_PM_SLEEP=y
|
||||
CONFIG_PM_SLEEP_SMP=y
|
||||
@@ -479,7 +464,6 @@ CONFIG_ACPI_REV_OVERRIDE_POSSIBLE=y
|
||||
# CONFIG_ACPI_AC is not set
|
||||
# CONFIG_ACPI_BATTERY is not set
|
||||
# CONFIG_ACPI_BUTTON is not set
|
||||
# CONFIG_ACPI_TINY_POWER_BUTTON is not set
|
||||
# CONFIG_ACPI_VIDEO is not set
|
||||
# CONFIG_ACPI_FAN is not set
|
||||
# CONFIG_ACPI_TAD is not set
|
||||
@@ -493,7 +477,6 @@ CONFIG_ACPI_HOTPLUG_CPU=y
|
||||
# CONFIG_ACPI_PROCESSOR_AGGREGATOR is not set
|
||||
CONFIG_ACPI_THERMAL=y
|
||||
CONFIG_ARCH_HAS_ACPI_TABLE_UPGRADE=y
|
||||
CONFIG_ACPI_TABLE_UPGRADE=y
|
||||
# CONFIG_ACPI_DEBUG is not set
|
||||
# CONFIG_ACPI_PCI_SLOT is not set
|
||||
CONFIG_ACPI_CONTAINER=y
|
||||
@@ -520,7 +503,6 @@ CONFIG_X86_PM_TIMER=y
|
||||
# CPU Frequency scaling
|
||||
#
|
||||
CONFIG_CPU_FREQ=y
|
||||
CONFIG_CPU_FREQ_GOV_ATTR_SET=y
|
||||
# CONFIG_CPU_FREQ_STAT is not set
|
||||
CONFIG_CPU_FREQ_DEFAULT_GOV_PERFORMANCE=y
|
||||
# CONFIG_CPU_FREQ_DEFAULT_GOV_POWERSAVE is not set
|
||||
@@ -533,7 +515,7 @@ CONFIG_CPU_FREQ_GOV_PERFORMANCE=y
|
||||
# CONFIG_CPU_FREQ_GOV_USERSPACE is not set
|
||||
# CONFIG_CPU_FREQ_GOV_ONDEMAND is not set
|
||||
# CONFIG_CPU_FREQ_GOV_CONSERVATIVE is not set
|
||||
CONFIG_CPU_FREQ_GOV_SCHEDUTIL=y
|
||||
# CONFIG_CPU_FREQ_GOV_SCHEDUTIL is not set
|
||||
|
||||
#
|
||||
# CPU frequency scaling drivers
|
||||
@@ -602,13 +584,11 @@ CONFIG_EFI_ESRT=y
|
||||
CONFIG_EFI_RUNTIME_MAP=y
|
||||
# CONFIG_EFI_FAKE_MEMMAP is not set
|
||||
CONFIG_EFI_RUNTIME_WRAPPERS=y
|
||||
CONFIG_EFI_GENERIC_STUB_INITRD_CMDLINE_LOADER=y
|
||||
# CONFIG_EFI_CAPSULE_LOADER is not set
|
||||
# CONFIG_EFI_TEST is not set
|
||||
# CONFIG_APPLE_PROPERTIES is not set
|
||||
# CONFIG_RESET_ATTACK_MITIGATION is not set
|
||||
# CONFIG_EFI_RCI2_TABLE is not set
|
||||
# CONFIG_EFI_DISABLE_PCI_DMA is not set
|
||||
# end of EFI (Extensible Firmware Interface) Support
|
||||
|
||||
CONFIG_EFI_EARLYCON=y
|
||||
@@ -634,13 +614,11 @@ CONFIG_HAVE_KVM_IRQ_BYPASS=y
|
||||
CONFIG_HAVE_KVM_NO_POLL=y
|
||||
CONFIG_VIRTUALIZATION=y
|
||||
CONFIG_KVM=y
|
||||
# CONFIG_KVM_WERROR is not set
|
||||
CONFIG_KVM_INTEL=y
|
||||
# CONFIG_KVM_AMD is not set
|
||||
CONFIG_AS_AVX512=y
|
||||
CONFIG_AS_SHA1_NI=y
|
||||
CONFIG_AS_SHA256_NI=y
|
||||
CONFIG_AS_TPAUSE=y
|
||||
# CONFIG_VHOST_NET is not set
|
||||
# CONFIG_VHOST_VSOCK is not set
|
||||
# CONFIG_VHOST_CROSS_ENDIAN_LEGACY is not set
|
||||
|
||||
#
|
||||
# General architecture-dependent options
|
||||
@@ -674,6 +652,7 @@ CONFIG_HAVE_ASM_MODVERSIONS=y
|
||||
CONFIG_HAVE_REGS_AND_STACK_ACCESS_API=y
|
||||
CONFIG_HAVE_RSEQ=y
|
||||
CONFIG_HAVE_FUNCTION_ARG_ACCESS_API=y
|
||||
CONFIG_HAVE_CLK=y
|
||||
CONFIG_HAVE_HW_BREAKPOINT=y
|
||||
CONFIG_HAVE_MIXED_BREAKPOINTS_REGS=y
|
||||
CONFIG_HAVE_USER_RETURN_NOTIFIER=y
|
||||
@@ -683,8 +662,7 @@ CONFIG_HAVE_PERF_REGS=y
|
||||
CONFIG_HAVE_PERF_USER_STACK_DUMP=y
|
||||
CONFIG_HAVE_ARCH_JUMP_LABEL=y
|
||||
CONFIG_HAVE_ARCH_JUMP_LABEL_RELATIVE=y
|
||||
CONFIG_MMU_GATHER_TABLE_FREE=y
|
||||
CONFIG_MMU_GATHER_RCU_TABLE_FREE=y
|
||||
CONFIG_HAVE_RCU_TABLE_FREE=y
|
||||
CONFIG_ARCH_HAVE_NMI_SAFE_CMPXCHG=y
|
||||
CONFIG_HAVE_ALIGNED_STRUCT_PAGE=y
|
||||
CONFIG_HAVE_CMPXCHG_LOCAL=y
|
||||
@@ -708,6 +686,7 @@ CONFIG_ARCH_WANT_HUGE_PMD_SHARE=y
|
||||
CONFIG_HAVE_ARCH_SOFT_DIRTY=y
|
||||
CONFIG_HAVE_MOD_ARCH_SPECIFIC=y
|
||||
CONFIG_MODULES_USE_ELF_RELA=y
|
||||
CONFIG_HAVE_IRQ_EXIT_ON_IRQ_STACK=y
|
||||
CONFIG_ARCH_HAS_ELF_RANDOMIZE=y
|
||||
CONFIG_HAVE_ARCH_MMAP_RND_BITS=y
|
||||
CONFIG_HAVE_EXIT_THREAD=y
|
||||
@@ -733,6 +712,7 @@ CONFIG_ARCH_HAS_MEM_ENCRYPT=y
|
||||
CONFIG_ARCH_HAS_GCOV_PROFILE_ALL=y
|
||||
# end of GCOV-based kernel profiling
|
||||
|
||||
CONFIG_PLUGIN_HOSTCC=""
|
||||
CONFIG_HAVE_GCC_PLUGINS=y
|
||||
# end of General architecture-dependent options
|
||||
|
||||
@@ -756,7 +736,6 @@ CONFIG_BLK_WBT=y
|
||||
CONFIG_BLK_WBT_MQ=y
|
||||
CONFIG_BLK_DEBUG_FS=y
|
||||
# CONFIG_BLK_SED_OPAL is not set
|
||||
# CONFIG_BLK_INLINE_ENCRYPTION is not set
|
||||
|
||||
#
|
||||
# Partition Types
|
||||
@@ -805,7 +784,6 @@ CONFIG_ARCH_USE_QUEUED_SPINLOCKS=y
|
||||
CONFIG_QUEUED_SPINLOCKS=y
|
||||
CONFIG_ARCH_USE_QUEUED_RWLOCKS=y
|
||||
CONFIG_QUEUED_RWLOCKS=y
|
||||
CONFIG_ARCH_HAS_NON_OVERLAPPING_ADDRESS_SPACE=y
|
||||
CONFIG_ARCH_HAS_SYNC_CORE_BEFORE_USERMODE=y
|
||||
CONFIG_ARCH_HAS_SYSCALL_WRAPPER=y
|
||||
CONFIG_FREEZER=y
|
||||
@@ -832,8 +810,8 @@ CONFIG_HAVE_MEMORY_PRESENT=y
|
||||
CONFIG_SPARSEMEM_EXTREME=y
|
||||
CONFIG_SPARSEMEM_VMEMMAP_ENABLE=y
|
||||
CONFIG_SPARSEMEM_VMEMMAP=y
|
||||
CONFIG_HAVE_MEMBLOCK_NODE_MAP=y
|
||||
CONFIG_HAVE_FAST_GUP=y
|
||||
CONFIG_NUMA_KEEP_MEMINFO=y
|
||||
CONFIG_MEMORY_ISOLATION=y
|
||||
CONFIG_HAVE_BOOTMEM_INFO_NODE=y
|
||||
CONFIG_MEMORY_HOTPLUG=y
|
||||
@@ -844,7 +822,6 @@ CONFIG_SPLIT_PTLOCK_CPUS=4
|
||||
CONFIG_MEMORY_BALLOON=y
|
||||
CONFIG_BALLOON_COMPACTION=y
|
||||
CONFIG_COMPACTION=y
|
||||
CONFIG_PAGE_REPORTING=y
|
||||
CONFIG_MIGRATION=y
|
||||
CONFIG_CONTIG_ALLOC=y
|
||||
CONFIG_PHYS_ADDR_T_64BIT=y
|
||||
@@ -858,27 +835,16 @@ CONFIG_TRANSPARENT_HUGEPAGE=y
|
||||
CONFIG_TRANSPARENT_HUGEPAGE_MADVISE=y
|
||||
CONFIG_ARCH_WANTS_THP_SWAP=y
|
||||
CONFIG_THP_SWAP=y
|
||||
CONFIG_TRANSPARENT_HUGE_PAGECACHE=y
|
||||
CONFIG_CLEANCACHE=y
|
||||
CONFIG_FRONTSWAP=y
|
||||
# CONFIG_CMA is not set
|
||||
CONFIG_ZSWAP=y
|
||||
# CONFIG_ZSWAP_COMPRESSOR_DEFAULT_DEFLATE is not set
|
||||
CONFIG_ZSWAP_COMPRESSOR_DEFAULT_LZO=y
|
||||
# CONFIG_ZSWAP_COMPRESSOR_DEFAULT_842 is not set
|
||||
# CONFIG_ZSWAP_COMPRESSOR_DEFAULT_LZ4 is not set
|
||||
# CONFIG_ZSWAP_COMPRESSOR_DEFAULT_LZ4HC is not set
|
||||
# CONFIG_ZSWAP_COMPRESSOR_DEFAULT_ZSTD is not set
|
||||
CONFIG_ZSWAP_COMPRESSOR_DEFAULT="lzo"
|
||||
CONFIG_ZSWAP_ZPOOL_DEFAULT_ZBUD=y
|
||||
# CONFIG_ZSWAP_ZPOOL_DEFAULT_Z3FOLD is not set
|
||||
# CONFIG_ZSWAP_ZPOOL_DEFAULT_ZSMALLOC is not set
|
||||
CONFIG_ZSWAP_ZPOOL_DEFAULT="zbud"
|
||||
# CONFIG_ZSWAP_DEFAULT_ON is not set
|
||||
CONFIG_ZPOOL=y
|
||||
CONFIG_ZBUD=y
|
||||
# CONFIG_Z3FOLD is not set
|
||||
CONFIG_ZSMALLOC=y
|
||||
# CONFIG_ZSMALLOC_PGTABLE_MAPPING is not set
|
||||
# CONFIG_PGTABLE_MAPPING is not set
|
||||
CONFIG_ZSMALLOC_STAT=y
|
||||
CONFIG_GENERIC_EARLY_IOREMAP=y
|
||||
# CONFIG_DEFERRED_STRUCT_PAGE_INIT is not set
|
||||
@@ -941,7 +907,6 @@ CONFIG_TCP_CONG_CUBIC=y
|
||||
CONFIG_DEFAULT_TCP_CONG="cubic"
|
||||
# CONFIG_TCP_MD5SIG is not set
|
||||
# CONFIG_IPV6 is not set
|
||||
# CONFIG_MPTCP is not set
|
||||
# CONFIG_NETWORK_SECMARK is not set
|
||||
CONFIG_NET_PTP_CLASSIFY=y
|
||||
# CONFIG_NETWORK_PHY_TIMESTAMPING is not set
|
||||
@@ -971,7 +936,6 @@ CONFIG_HAVE_NET_DSA=y
|
||||
# CONFIG_OPENVSWITCH is not set
|
||||
CONFIG_VSOCKETS=y
|
||||
CONFIG_VSOCKETS_DIAG=y
|
||||
# CONFIG_VSOCKETS_LOOPBACK is not set
|
||||
CONFIG_VIRTIO_VSOCKETS=y
|
||||
CONFIG_VIRTIO_VSOCKETS_COMMON=y
|
||||
# CONFIG_NETLINK_DIAG is not set
|
||||
@@ -980,7 +944,6 @@ CONFIG_VIRTIO_VSOCKETS_COMMON=y
|
||||
# CONFIG_HSR is not set
|
||||
# CONFIG_NET_SWITCHDEV is not set
|
||||
# CONFIG_NET_L3_MASTER_DEV is not set
|
||||
# CONFIG_QRTR is not set
|
||||
# CONFIG_NET_NCSI is not set
|
||||
CONFIG_RPS=y
|
||||
CONFIG_RFS_ACCEL=y
|
||||
@@ -1018,7 +981,6 @@ CONFIG_STREAM_PARSER=y
|
||||
CONFIG_GRO_CELLS=y
|
||||
CONFIG_NET_SOCK_MSG=y
|
||||
CONFIG_FAILOVER=y
|
||||
# CONFIG_ETHTOOL_NETLINK is not set
|
||||
CONFIG_HAVE_EBPF_JIT=y
|
||||
|
||||
#
|
||||
@@ -1030,7 +992,6 @@ CONFIG_HAVE_PCI=y
|
||||
CONFIG_PCI=y
|
||||
CONFIG_PCI_DOMAINS=y
|
||||
CONFIG_PCIEPORTBUS=y
|
||||
# CONFIG_HOTPLUG_PCI_PCIE is not set
|
||||
CONFIG_PCIEAER=y
|
||||
# CONFIG_PCIEAER_INJECT is not set
|
||||
# CONFIG_PCIE_ECRC is not set
|
||||
@@ -1054,11 +1015,7 @@ CONFIG_PCI_LOCKLESS_CONFIG=y
|
||||
# CONFIG_PCI_PASID is not set
|
||||
# CONFIG_PCI_P2PDMA is not set
|
||||
CONFIG_PCI_LABEL=y
|
||||
CONFIG_HOTPLUG_PCI=y
|
||||
CONFIG_HOTPLUG_PCI_ACPI=y
|
||||
# CONFIG_HOTPLUG_PCI_ACPI_IBM is not set
|
||||
# CONFIG_HOTPLUG_PCI_CPCI is not set
|
||||
# CONFIG_HOTPLUG_PCI_SHPC is not set
|
||||
# CONFIG_HOTPLUG_PCI is not set
|
||||
|
||||
#
|
||||
# PCI controller drivers
|
||||
@@ -1072,11 +1029,6 @@ CONFIG_HOTPLUG_PCI_ACPI=y
|
||||
# CONFIG_PCI_MESON is not set
|
||||
# end of DesignWare PCI Core Support
|
||||
|
||||
#
|
||||
# Mobiveil PCIe Core Support
|
||||
#
|
||||
# end of Mobiveil PCIe Core Support
|
||||
|
||||
#
|
||||
# Cadence PCIe controllers support
|
||||
#
|
||||
@@ -1131,7 +1083,6 @@ CONFIG_GENERIC_CPU_VULNERABILITIES=y
|
||||
#
|
||||
# Bus devices
|
||||
#
|
||||
# CONFIG_MHI_BUS is not set
|
||||
# end of Bus devices
|
||||
|
||||
CONFIG_CONNECTOR=y
|
||||
@@ -1169,6 +1120,7 @@ CONFIG_BLK_DEV_RAM_SIZE=16384
|
||||
# CONFIG_CDROM_PKTCDVD is not set
|
||||
# CONFIG_ATA_OVER_ETH is not set
|
||||
CONFIG_VIRTIO_BLK=y
|
||||
# CONFIG_VIRTIO_BLK_SCSI is not set
|
||||
# CONFIG_BLK_DEV_RBD is not set
|
||||
# CONFIG_BLK_DEV_RSXX is not set
|
||||
|
||||
@@ -1229,7 +1181,6 @@ CONFIG_VIRTIO_BLK=y
|
||||
# CONFIG_MISC_ALCOR_PCI is not set
|
||||
# CONFIG_MISC_RTSX_PCI is not set
|
||||
# CONFIG_HABANA_AI is not set
|
||||
# CONFIG_UACCE is not set
|
||||
# end of Misc devices
|
||||
|
||||
CONFIG_HAVE_IDE=y
|
||||
@@ -1260,14 +1211,12 @@ CONFIG_NETDEVICES=y
|
||||
CONFIG_NET_CORE=y
|
||||
# CONFIG_BONDING is not set
|
||||
# CONFIG_DUMMY is not set
|
||||
# CONFIG_WIREGUARD is not set
|
||||
# CONFIG_EQUALIZER is not set
|
||||
# CONFIG_NET_TEAM is not set
|
||||
# CONFIG_MACVLAN is not set
|
||||
# CONFIG_IPVLAN is not set
|
||||
# CONFIG_VXLAN is not set
|
||||
# CONFIG_GENEVE is not set
|
||||
# CONFIG_BAREUDP is not set
|
||||
# CONFIG_GTP is not set
|
||||
# CONFIG_MACSEC is not set
|
||||
# CONFIG_NETCONSOLE is not set
|
||||
@@ -1347,6 +1296,7 @@ CONFIG_INPUT_KEYBOARD=y
|
||||
CONFIG_INPUT_MISC=y
|
||||
# CONFIG_INPUT_AD714X is not set
|
||||
# CONFIG_INPUT_E3X0_BUTTON is not set
|
||||
# CONFIG_INPUT_MSM_VIBRATOR is not set
|
||||
# CONFIG_INPUT_PCSPKR is not set
|
||||
# CONFIG_INPUT_ATLAS_BTNS is not set
|
||||
CONFIG_INPUT_UINPUT=y
|
||||
@@ -1375,7 +1325,14 @@ CONFIG_HW_CONSOLE=y
|
||||
CONFIG_VT_HW_CONSOLE_BINDING=y
|
||||
CONFIG_UNIX98_PTYS=y
|
||||
# CONFIG_LEGACY_PTYS is not set
|
||||
# CONFIG_SERIAL_NONSTANDARD is not set
|
||||
# CONFIG_NOZOMI is not set
|
||||
# CONFIG_N_GSM is not set
|
||||
# CONFIG_TRACE_SINK is not set
|
||||
# CONFIG_NULL_TTY is not set
|
||||
# CONFIG_LDISC_AUTOLOAD is not set
|
||||
CONFIG_DEVMEM=y
|
||||
# CONFIG_DEVKMEM is not set
|
||||
|
||||
#
|
||||
# Serial drivers
|
||||
@@ -1384,7 +1341,6 @@ CONFIG_SERIAL_EARLYCON=y
|
||||
CONFIG_SERIAL_8250=y
|
||||
# CONFIG_SERIAL_8250_DEPRECATED_OPTIONS is not set
|
||||
CONFIG_SERIAL_8250_PNP=y
|
||||
# CONFIG_SERIAL_8250_16550A_VARIANTS is not set
|
||||
# CONFIG_SERIAL_8250_FINTEK is not set
|
||||
CONFIG_SERIAL_8250_CONSOLE=y
|
||||
CONFIG_SERIAL_8250_DMA=y
|
||||
@@ -1406,7 +1362,6 @@ CONFIG_SERIAL_8250_MID=y
|
||||
CONFIG_SERIAL_CORE=y
|
||||
CONFIG_SERIAL_CORE_CONSOLE=y
|
||||
# CONFIG_SERIAL_JSM is not set
|
||||
# CONFIG_SERIAL_LANTIQ is not set
|
||||
# CONFIG_SERIAL_SCCNXP is not set
|
||||
# CONFIG_SERIAL_ALTERA_JTAGUART is not set
|
||||
# CONFIG_SERIAL_ALTERA_UART is not set
|
||||
@@ -1416,18 +1371,12 @@ CONFIG_SERIAL_ARC_NR_PORTS=1
|
||||
# CONFIG_SERIAL_RP2 is not set
|
||||
# CONFIG_SERIAL_FSL_LPUART is not set
|
||||
# CONFIG_SERIAL_FSL_LINFLEXUART is not set
|
||||
# CONFIG_SERIAL_SPRD is not set
|
||||
# end of Serial drivers
|
||||
|
||||
# CONFIG_SERIAL_NONSTANDARD is not set
|
||||
# CONFIG_N_GSM is not set
|
||||
# CONFIG_NOZOMI is not set
|
||||
# CONFIG_NULL_TTY is not set
|
||||
# CONFIG_TRACE_SINK is not set
|
||||
CONFIG_HVC_DRIVER=y
|
||||
CONFIG_SERIAL_DEV_BUS=y
|
||||
CONFIG_SERIAL_DEV_CTRL_TTYPORT=y
|
||||
# CONFIG_TTY_PRINTK is not set
|
||||
CONFIG_HVC_DRIVER=y
|
||||
CONFIG_VIRTIO_CONSOLE=y
|
||||
# CONFIG_IPMI_HANDLER is not set
|
||||
CONFIG_HW_RANDOM=y
|
||||
@@ -1436,18 +1385,16 @@ CONFIG_HW_RANDOM_INTEL=y
|
||||
CONFIG_HW_RANDOM_AMD=y
|
||||
CONFIG_HW_RANDOM_VIA=y
|
||||
CONFIG_HW_RANDOM_VIRTIO=y
|
||||
CONFIG_NVRAM=y
|
||||
# CONFIG_APPLICOM is not set
|
||||
# CONFIG_MWAVE is not set
|
||||
CONFIG_DEVMEM=y
|
||||
# CONFIG_DEVKMEM is not set
|
||||
CONFIG_NVRAM=y
|
||||
CONFIG_RAW_DRIVER=y
|
||||
CONFIG_MAX_RAW_DEVS=256
|
||||
CONFIG_DEVPORT=y
|
||||
# CONFIG_HPET is not set
|
||||
CONFIG_HANGCHECK_TIMER=y
|
||||
# CONFIG_TCG_TPM is not set
|
||||
CONFIG_TELCLOCK=y
|
||||
CONFIG_DEVPORT=y
|
||||
# CONFIG_XILLYBUS is not set
|
||||
# end of Character devices
|
||||
|
||||
@@ -1487,7 +1434,7 @@ CONFIG_PTP_1588_CLOCK=y
|
||||
# Enable PHYLIB and NETWORK_PHY_TIMESTAMPING to see the additional clocks.
|
||||
#
|
||||
CONFIG_PTP_1588_CLOCK_KVM=y
|
||||
# CONFIG_PTP_1588_CLOCK_VMW is not set
|
||||
# CONFIG_PTP_1588_CLOCK_IDTCM is not set
|
||||
# end of PTP clock support
|
||||
|
||||
# CONFIG_PINCTRL is not set
|
||||
@@ -1504,6 +1451,7 @@ CONFIG_THERMAL_WRITABLE_TRIPS=y
|
||||
CONFIG_THERMAL_DEFAULT_GOV_STEP_WISE=y
|
||||
# CONFIG_THERMAL_DEFAULT_GOV_FAIR_SHARE is not set
|
||||
# CONFIG_THERMAL_DEFAULT_GOV_USER_SPACE is not set
|
||||
# CONFIG_THERMAL_DEFAULT_GOV_POWER_ALLOCATOR is not set
|
||||
CONFIG_THERMAL_GOV_FAIR_SHARE=y
|
||||
CONFIG_THERMAL_GOV_STEP_WISE=y
|
||||
# CONFIG_THERMAL_GOV_BANG_BANG is not set
|
||||
@@ -1556,7 +1504,6 @@ CONFIG_BCMA_POSSIBLE=y
|
||||
|
||||
# CONFIG_REGULATOR is not set
|
||||
# CONFIG_RC_CORE is not set
|
||||
# CONFIG_MEDIA_CEC_SUPPORT is not set
|
||||
# CONFIG_MEDIA_SUPPORT is not set
|
||||
|
||||
#
|
||||
@@ -1567,12 +1514,18 @@ CONFIG_VGA_ARB=y
|
||||
CONFIG_VGA_ARB_MAX_GPUS=16
|
||||
# CONFIG_VGA_SWITCHEROO is not set
|
||||
# CONFIG_DRM is not set
|
||||
# CONFIG_DRM_DP_CEC is not set
|
||||
|
||||
#
|
||||
# ARM devices
|
||||
#
|
||||
# end of ARM devices
|
||||
|
||||
#
|
||||
# ACP (Audio CoProcessor) Configuration
|
||||
#
|
||||
# end of ACP (Audio CoProcessor) Configuration
|
||||
|
||||
#
|
||||
# Frame buffer Devices
|
||||
#
|
||||
@@ -1689,7 +1642,6 @@ CONFIG_HID_GENERIC=y
|
||||
# CONFIG_HID_EZKEY is not set
|
||||
# CONFIG_HID_GEMBIRD is not set
|
||||
# CONFIG_HID_GFRM is not set
|
||||
# CONFIG_HID_GLORIOUS is not set
|
||||
# CONFIG_HID_KEYTOUCH is not set
|
||||
# CONFIG_HID_KYE is not set
|
||||
# CONFIG_HID_WALTOP is not set
|
||||
@@ -1767,9 +1719,7 @@ CONFIG_DMA_VIRTUAL_CHANNELS=y
|
||||
CONFIG_DMA_ACPI=y
|
||||
# CONFIG_ALTERA_MSGDMA is not set
|
||||
# CONFIG_INTEL_IDMA64 is not set
|
||||
# CONFIG_INTEL_IDXD is not set
|
||||
# CONFIG_INTEL_IOATDMA is not set
|
||||
# CONFIG_PLX_DMA is not set
|
||||
# CONFIG_QCOM_HIDMA_MGMT is not set
|
||||
# CONFIG_QCOM_HIDMA is not set
|
||||
CONFIG_DW_DMAC_CORE=y
|
||||
@@ -1790,8 +1740,6 @@ CONFIG_HSU_DMA=y
|
||||
# DMABUF options
|
||||
#
|
||||
# CONFIG_SYNC_FILE is not set
|
||||
# CONFIG_DMABUF_MOVE_NOTIFY is not set
|
||||
# CONFIG_DMABUF_HEAPS is not set
|
||||
# end of DMABUF options
|
||||
|
||||
# CONFIG_AUXDISPLAY is not set
|
||||
@@ -1823,12 +1771,9 @@ CONFIG_VIRTIO_PCI=y
|
||||
CONFIG_VIRTIO_PCI_LEGACY=y
|
||||
CONFIG_VIRTIO_PMEM=y
|
||||
CONFIG_VIRTIO_BALLOON=y
|
||||
CONFIG_VIRTIO_MEM=y
|
||||
CONFIG_VIRTIO_INPUT=y
|
||||
CONFIG_VIRTIO_MMIO=y
|
||||
CONFIG_VIRTIO_MMIO_CMDLINE_DEVICES=y
|
||||
# CONFIG_VDPA is not set
|
||||
# CONFIG_VHOST_MENU is not set
|
||||
|
||||
#
|
||||
# Microsoft Hyper-V guest support
|
||||
@@ -1843,10 +1788,15 @@ CONFIG_PMC_ATOM=y
|
||||
# CONFIG_MFD_CROS_EC is not set
|
||||
# CONFIG_CHROME_PLATFORMS is not set
|
||||
# CONFIG_MELLANOX_PLATFORM is not set
|
||||
CONFIG_HAVE_CLK=y
|
||||
CONFIG_CLKDEV_LOOKUP=y
|
||||
CONFIG_HAVE_CLK_PREPARE=y
|
||||
CONFIG_COMMON_CLK=y
|
||||
|
||||
#
|
||||
# Common Clock Framework
|
||||
#
|
||||
# end of Common Clock Framework
|
||||
|
||||
# CONFIG_HWSPINLOCK is not set
|
||||
|
||||
#
|
||||
@@ -1876,9 +1826,7 @@ CONFIG_IOMMU_DMA=y
|
||||
# CONFIG_INTEL_IOMMU is not set
|
||||
# CONFIG_IRQ_REMAP is not set
|
||||
CONFIG_VIRTIO_IOMMU=y
|
||||
CONFIG_VIRTIO_IOMMU_TOPOLOGY_HELPERS=y
|
||||
CONFIG_VIRTIO_IOMMU_TOPOLOGY=y
|
||||
CONFIG_ACPI_VIOT=y
|
||||
|
||||
#
|
||||
# Remoteproc drivers
|
||||
@@ -1961,7 +1909,6 @@ CONFIG_ACPI_VIOT=y
|
||||
# CONFIG_BCM_KONA_USB2_PHY is not set
|
||||
# CONFIG_PHY_PXA_28NM_HSIC is not set
|
||||
# CONFIG_PHY_PXA_28NM_USB2 is not set
|
||||
# CONFIG_PHY_INTEL_EMMC is not set
|
||||
# end of PHY Subsystem
|
||||
|
||||
# CONFIG_POWERCAP is not set
|
||||
@@ -1973,7 +1920,7 @@ CONFIG_ACPI_VIOT=y
|
||||
# end of Performance monitor support
|
||||
|
||||
CONFIG_RAS=y
|
||||
# CONFIG_USB4 is not set
|
||||
# CONFIG_THUNDERBOLT is not set
|
||||
|
||||
#
|
||||
# Android
|
||||
@@ -2003,13 +1950,11 @@ CONFIG_DAX=y
|
||||
# end of HW tracing support
|
||||
|
||||
# CONFIG_FPGA is not set
|
||||
# CONFIG_TEE is not set
|
||||
# CONFIG_UNISYS_VISORBUS is not set
|
||||
# CONFIG_SIOX is not set
|
||||
# CONFIG_SLIMBUS is not set
|
||||
# CONFIG_INTERCONNECT is not set
|
||||
# CONFIG_COUNTER is not set
|
||||
# CONFIG_MOST is not set
|
||||
# end of Device Drivers
|
||||
|
||||
#
|
||||
@@ -2044,7 +1989,6 @@ CONFIG_EXPORTFS=y
|
||||
CONFIG_FILE_LOCKING=y
|
||||
CONFIG_MANDATORY_FILE_LOCKING=y
|
||||
CONFIG_FS_ENCRYPTION=y
|
||||
CONFIG_FS_ENCRYPTION_ALGS=y
|
||||
# CONFIG_FS_VERITY is not set
|
||||
CONFIG_FSNOTIFY=y
|
||||
CONFIG_DNOTIFY=y
|
||||
@@ -2084,7 +2028,7 @@ CONFIG_CACHEFILES=y
|
||||
# end of CD-ROM/DVD Filesystems
|
||||
|
||||
#
|
||||
# DOS/FAT/EXFAT/NT Filesystems
|
||||
# DOS/FAT/NT Filesystems
|
||||
#
|
||||
CONFIG_FAT_FS=y
|
||||
CONFIG_MSDOS_FS=y
|
||||
@@ -2092,9 +2036,8 @@ CONFIG_VFAT_FS=y
|
||||
CONFIG_FAT_DEFAULT_CODEPAGE=437
|
||||
CONFIG_FAT_DEFAULT_IOCHARSET="ascii"
|
||||
# CONFIG_FAT_DEFAULT_UTF8 is not set
|
||||
# CONFIG_EXFAT_FS is not set
|
||||
# CONFIG_NTFS_FS is not set
|
||||
# end of DOS/FAT/EXFAT/NT Filesystems
|
||||
# end of DOS/FAT/NT Filesystems
|
||||
|
||||
#
|
||||
# Pseudo filesystems
|
||||
@@ -2182,6 +2125,7 @@ CONFIG_IO_WQ=y
|
||||
CONFIG_KEYS=y
|
||||
# CONFIG_KEYS_REQUEST_CACHE is not set
|
||||
CONFIG_PERSISTENT_KEYRINGS=y
|
||||
# CONFIG_BIG_KEYS is not set
|
||||
# CONFIG_ENCRYPTED_KEYS is not set
|
||||
# CONFIG_KEY_DH_OPERATIONS is not set
|
||||
# CONFIG_SECURITY_DMESG_RESTRICT is not set
|
||||
@@ -2192,7 +2136,6 @@ CONFIG_HAVE_HARDENED_USERCOPY_ALLOCATOR=y
|
||||
# CONFIG_HARDENED_USERCOPY is not set
|
||||
CONFIG_FORTIFY_SOURCE=y
|
||||
# CONFIG_STATIC_USERMODEHELPER is not set
|
||||
# CONFIG_IMA_SECURE_AND_OR_TRUSTED_BOOT is not set
|
||||
CONFIG_DEFAULT_SECURITY_DAC=y
|
||||
CONFIG_LSM="yama,loadpin,safesetid,integrity"
|
||||
|
||||
@@ -2400,7 +2343,7 @@ CONFIG_CRYPTO_LIB_ARC4=y
|
||||
# CONFIG_CRYPTO_LIB_CHACHA is not set
|
||||
# CONFIG_CRYPTO_LIB_CURVE25519 is not set
|
||||
CONFIG_CRYPTO_LIB_DES=y
|
||||
CONFIG_CRYPTO_LIB_POLY1305_RSIZE=11
|
||||
CONFIG_CRYPTO_LIB_POLY1305_RSIZE=4
|
||||
CONFIG_CRYPTO_LIB_POLY1305_GENERIC=y
|
||||
# CONFIG_CRYPTO_LIB_POLY1305 is not set
|
||||
# CONFIG_CRYPTO_LIB_CHACHA20POLY1305 is not set
|
||||
@@ -2425,13 +2368,11 @@ CONFIG_GENERIC_STRNLEN_USER=y
|
||||
CONFIG_GENERIC_NET_UTILS=y
|
||||
CONFIG_GENERIC_FIND_FIRST_BIT=y
|
||||
# CONFIG_CORDIC is not set
|
||||
# CONFIG_PRIME_NUMBERS is not set
|
||||
CONFIG_RATIONAL=y
|
||||
CONFIG_GENERIC_PCI_IOMAP=y
|
||||
CONFIG_GENERIC_IOMAP=y
|
||||
CONFIG_ARCH_USE_CMPXCHG_LOCKREF=y
|
||||
CONFIG_ARCH_HAS_FAST_MULTIPLIER=y
|
||||
CONFIG_ARCH_USE_SYM_ANNOTATIONS=y
|
||||
CONFIG_CRC_CCITT=y
|
||||
CONFIG_CRC16=y
|
||||
CONFIG_CRC_T10DIF=y
|
||||
@@ -2453,23 +2394,7 @@ CONFIG_ZLIB_INFLATE=y
|
||||
CONFIG_ZLIB_DEFLATE=y
|
||||
CONFIG_LZO_COMPRESS=y
|
||||
CONFIG_LZO_DECOMPRESS=y
|
||||
CONFIG_LZ4_DECOMPRESS=y
|
||||
CONFIG_XZ_DEC=y
|
||||
CONFIG_XZ_DEC_X86=y
|
||||
CONFIG_XZ_DEC_POWERPC=y
|
||||
CONFIG_XZ_DEC_IA64=y
|
||||
CONFIG_XZ_DEC_ARM=y
|
||||
CONFIG_XZ_DEC_ARMTHUMB=y
|
||||
CONFIG_XZ_DEC_SPARC=y
|
||||
CONFIG_XZ_DEC_BCJ=y
|
||||
# CONFIG_XZ_DEC_TEST is not set
|
||||
CONFIG_DECOMPRESS_GZIP=y
|
||||
CONFIG_DECOMPRESS_BZIP2=y
|
||||
CONFIG_DECOMPRESS_LZMA=y
|
||||
CONFIG_DECOMPRESS_XZ=y
|
||||
CONFIG_DECOMPRESS_LZO=y
|
||||
CONFIG_DECOMPRESS_LZ4=y
|
||||
CONFIG_GENERIC_ALLOCATOR=y
|
||||
# CONFIG_XZ_DEC is not set
|
||||
CONFIG_INTERVAL_TREE=y
|
||||
CONFIG_XARRAY_MULTI=y
|
||||
CONFIG_ASSOCIATIVE_ARRAY=y
|
||||
@@ -2481,7 +2406,6 @@ CONFIG_NEED_DMA_MAP_STATE=y
|
||||
CONFIG_ARCH_DMA_ADDR_T_64BIT=y
|
||||
CONFIG_ARCH_HAS_FORCE_DMA_UNENCRYPTED=y
|
||||
CONFIG_SWIOTLB=y
|
||||
CONFIG_DMA_COHERENT_POOL=y
|
||||
# CONFIG_DMA_API_DEBUG is not set
|
||||
CONFIG_SGL_ALLOC=y
|
||||
CONFIG_CPU_RMAP=y
|
||||
@@ -2491,7 +2415,6 @@ CONFIG_IRQ_POLL=y
|
||||
CONFIG_UCS2_STRING=y
|
||||
CONFIG_HAVE_GENERIC_VDSO=y
|
||||
CONFIG_GENERIC_GETTIMEOFDAY=y
|
||||
CONFIG_GENERIC_VDSO_TIME_NS=y
|
||||
CONFIG_FONT_SUPPORT=y
|
||||
# CONFIG_FONTS is not set
|
||||
CONFIG_FONT_8x8=y
|
||||
@@ -2518,7 +2441,6 @@ CONFIG_CONSOLE_LOGLEVEL_QUIET=4
|
||||
CONFIG_MESSAGE_LOGLEVEL_DEFAULT=4
|
||||
# CONFIG_BOOT_PRINTK_DELAY is not set
|
||||
CONFIG_DYNAMIC_DEBUG=y
|
||||
CONFIG_DYNAMIC_DEBUG_CORE=y
|
||||
CONFIG_SYMBOLIC_ERRNAME=y
|
||||
# CONFIG_DEBUG_BUGVERBOSE is not set
|
||||
# end of printk and dmesg options
|
||||
@@ -2532,6 +2454,7 @@ CONFIG_FRAME_WARN=2048
|
||||
CONFIG_STRIP_ASM_SYMS=y
|
||||
# CONFIG_READABLE_ASM is not set
|
||||
# CONFIG_HEADERS_INSTALL is not set
|
||||
CONFIG_OPTIMIZE_INLINING=y
|
||||
CONFIG_DEBUG_SECTION_MISMATCH=y
|
||||
CONFIG_SECTION_MISMATCH_WARN_ONLY=y
|
||||
CONFIG_FRAME_POINTER=y
|
||||
@@ -2545,12 +2468,12 @@ CONFIG_STACK_VALIDATION=y
|
||||
CONFIG_MAGIC_SYSRQ=y
|
||||
CONFIG_MAGIC_SYSRQ_DEFAULT_ENABLE=0x1
|
||||
CONFIG_MAGIC_SYSRQ_SERIAL=y
|
||||
CONFIG_MAGIC_SYSRQ_SERIAL_SEQUENCE=""
|
||||
CONFIG_DEBUG_FS=y
|
||||
CONFIG_HAVE_ARCH_KGDB=y
|
||||
# CONFIG_KGDB is not set
|
||||
CONFIG_ARCH_HAS_UBSAN_SANITIZE_ALL=y
|
||||
# CONFIG_UBSAN is not set
|
||||
CONFIG_UBSAN_ALIGNMENT=y
|
||||
# end of Generic Kernel Debugging Instruments
|
||||
|
||||
CONFIG_DEBUG_KERNEL=y
|
||||
@@ -2564,10 +2487,6 @@ CONFIG_DEBUG_MISC=y
|
||||
# CONFIG_PAGE_OWNER is not set
|
||||
# CONFIG_PAGE_POISONING is not set
|
||||
# CONFIG_DEBUG_RODATA_TEST is not set
|
||||
CONFIG_ARCH_HAS_DEBUG_WX=y
|
||||
# CONFIG_DEBUG_WX is not set
|
||||
CONFIG_GENERIC_PTDUMP=y
|
||||
# CONFIG_PTDUMP_DEBUGFS is not set
|
||||
# CONFIG_DEBUG_OBJECTS is not set
|
||||
# CONFIG_SLUB_DEBUG_ON is not set
|
||||
# CONFIG_SLUB_STATS is not set
|
||||
@@ -2575,9 +2494,7 @@ CONFIG_HAVE_DEBUG_KMEMLEAK=y
|
||||
# CONFIG_DEBUG_KMEMLEAK is not set
|
||||
# CONFIG_DEBUG_STACK_USAGE is not set
|
||||
# CONFIG_SCHED_STACK_END_CHECK is not set
|
||||
CONFIG_ARCH_HAS_DEBUG_VM_PGTABLE=y
|
||||
# CONFIG_DEBUG_VM is not set
|
||||
# CONFIG_DEBUG_VM_PGTABLE is not set
|
||||
CONFIG_ARCH_HAS_DEBUG_VIRTUAL=y
|
||||
# CONFIG_DEBUG_VIRTUAL is not set
|
||||
CONFIG_DEBUG_MEMORY_INIT=y
|
||||
@@ -2585,7 +2502,6 @@ CONFIG_DEBUG_MEMORY_INIT=y
|
||||
CONFIG_HAVE_ARCH_KASAN=y
|
||||
CONFIG_HAVE_ARCH_KASAN_VMALLOC=y
|
||||
CONFIG_CC_HAS_KASAN_GENERIC=y
|
||||
CONFIG_CC_HAS_WORKING_NOSANITIZE_ADDRESS=y
|
||||
# CONFIG_KASAN is not set
|
||||
CONFIG_KASAN_STACK=1
|
||||
# end of Memory Debugging
|
||||
@@ -2603,7 +2519,6 @@ CONFIG_HARDLOCKUP_CHECK_TIMESTAMP=y
|
||||
# CONFIG_HARDLOCKUP_DETECTOR is not set
|
||||
# CONFIG_DETECT_HUNG_TASK is not set
|
||||
# CONFIG_WQ_WATCHDOG is not set
|
||||
# CONFIG_TEST_LOCKUP is not set
|
||||
# end of Debug Oops, Lockups and Hangs
|
||||
|
||||
#
|
||||
@@ -2677,52 +2592,20 @@ CONFIG_HAVE_C_RECORDMCOUNT=y
|
||||
CONFIG_TRACING_SUPPORT=y
|
||||
# CONFIG_FTRACE is not set
|
||||
# CONFIG_PROVIDE_OHCI1394_DMA_INIT is not set
|
||||
# CONFIG_SAMPLES is not set
|
||||
CONFIG_HAVE_ARCH_KCSAN=y
|
||||
CONFIG_ARCH_HAS_DEVMEM_IS_ALLOWED=y
|
||||
CONFIG_STRICT_DEVMEM=y
|
||||
# CONFIG_IO_STRICT_DEVMEM is not set
|
||||
|
||||
#
|
||||
# x86 Debugging
|
||||
#
|
||||
CONFIG_TRACE_IRQFLAGS_SUPPORT=y
|
||||
CONFIG_X86_VERBOSE_BOOTUP=y
|
||||
CONFIG_EARLY_PRINTK=y
|
||||
# CONFIG_EARLY_PRINTK_DBGP is not set
|
||||
# CONFIG_EARLY_PRINTK_USB_XDBC is not set
|
||||
# CONFIG_EFI_PGT_DUMP is not set
|
||||
# CONFIG_DEBUG_TLBFLUSH is not set
|
||||
CONFIG_HAVE_MMIOTRACE_SUPPORT=y
|
||||
# CONFIG_X86_DECODER_SELFTEST is not set
|
||||
CONFIG_IO_DELAY_0X80=y
|
||||
# CONFIG_IO_DELAY_0XED is not set
|
||||
# CONFIG_IO_DELAY_UDELAY is not set
|
||||
# CONFIG_IO_DELAY_NONE is not set
|
||||
# CONFIG_DEBUG_BOOT_PARAMS is not set
|
||||
# CONFIG_CPA_DEBUG is not set
|
||||
# CONFIG_DEBUG_ENTRY is not set
|
||||
# CONFIG_DEBUG_NMI_SELFTEST is not set
|
||||
# CONFIG_X86_DEBUG_FPU is not set
|
||||
# CONFIG_PUNIT_ATOM_DEBUG is not set
|
||||
# CONFIG_UNWINDER_ORC is not set
|
||||
CONFIG_UNWINDER_FRAME_POINTER=y
|
||||
# CONFIG_UNWINDER_GUESS is not set
|
||||
# end of x86 Debugging
|
||||
# CONFIG_KUNIT is not set
|
||||
# CONFIG_NOTIFIER_ERROR_INJECTION is not set
|
||||
# CONFIG_FAULT_INJECTION is not set
|
||||
# end of Kernel hacking
|
||||
|
||||
#
|
||||
# Kernel Testing and Coverage
|
||||
#
|
||||
# CONFIG_KUNIT is not set
|
||||
# CONFIG_NOTIFIER_ERROR_INJECTION is not set
|
||||
# CONFIG_FAULT_INJECTION is not set
|
||||
CONFIG_ARCH_HAS_KCOV=y
|
||||
CONFIG_CC_HAS_SANCOV_TRACE_PC=y
|
||||
# CONFIG_KCOV is not set
|
||||
CONFIG_RUNTIME_TESTING_MENU=y
|
||||
# CONFIG_LKDTM is not set
|
||||
# CONFIG_TEST_LIST_SORT is not set
|
||||
# CONFIG_TEST_MIN_HEAP is not set
|
||||
# CONFIG_TEST_SORT is not set
|
||||
# CONFIG_BACKTRACE_SELF_TEST is not set
|
||||
# CONFIG_RBTREE_TEST is not set
|
||||
@@ -2750,5 +2633,38 @@ CONFIG_RUNTIME_TESTING_MENU=y
|
||||
# CONFIG_TEST_STACKINIT is not set
|
||||
# CONFIG_TEST_MEMINIT is not set
|
||||
# CONFIG_MEMTEST is not set
|
||||
# CONFIG_SAMPLES is not set
|
||||
CONFIG_ARCH_HAS_DEVMEM_IS_ALLOWED=y
|
||||
CONFIG_STRICT_DEVMEM=y
|
||||
# CONFIG_IO_STRICT_DEVMEM is not set
|
||||
|
||||
#
|
||||
# x86 Debugging
|
||||
#
|
||||
CONFIG_TRACE_IRQFLAGS_SUPPORT=y
|
||||
CONFIG_X86_VERBOSE_BOOTUP=y
|
||||
CONFIG_EARLY_PRINTK=y
|
||||
# CONFIG_EARLY_PRINTK_DBGP is not set
|
||||
# CONFIG_EARLY_PRINTK_USB_XDBC is not set
|
||||
# CONFIG_X86_PTDUMP is not set
|
||||
# CONFIG_EFI_PGT_DUMP is not set
|
||||
# CONFIG_DEBUG_WX is not set
|
||||
CONFIG_DOUBLEFAULT=y
|
||||
# CONFIG_DEBUG_TLBFLUSH is not set
|
||||
CONFIG_HAVE_MMIOTRACE_SUPPORT=y
|
||||
# CONFIG_X86_DECODER_SELFTEST is not set
|
||||
CONFIG_IO_DELAY_0X80=y
|
||||
# CONFIG_IO_DELAY_0XED is not set
|
||||
# CONFIG_IO_DELAY_UDELAY is not set
|
||||
# CONFIG_IO_DELAY_NONE is not set
|
||||
# CONFIG_DEBUG_BOOT_PARAMS is not set
|
||||
# CONFIG_CPA_DEBUG is not set
|
||||
# CONFIG_DEBUG_ENTRY is not set
|
||||
# CONFIG_DEBUG_NMI_SELFTEST is not set
|
||||
# CONFIG_X86_DEBUG_FPU is not set
|
||||
# CONFIG_PUNIT_ATOM_DEBUG is not set
|
||||
# CONFIG_UNWINDER_ORC is not set
|
||||
CONFIG_UNWINDER_FRAME_POINTER=y
|
||||
# CONFIG_UNWINDER_GUESS is not set
|
||||
# end of x86 Debugging
|
||||
# end of Kernel Testing and Coverage
|
||||
# end of Kernel hacking
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user