mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
Compare commits
13 Commits
v0.10.0
...
stable/v0.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
226d226fef | ||
|
|
293b8fad41 | ||
|
|
54ade15f9e | ||
|
|
03f64d12c3 | ||
|
|
9c3166237d | ||
|
|
3534540e6b | ||
|
|
f444451a04 | ||
|
|
89c25ea00c | ||
|
|
d89ab632e9 | ||
|
|
e32be99d6c | ||
|
|
4a62821e07 | ||
|
|
548ef43ca2 | ||
|
|
27c1b40c83 |
15
.github/workflows/audit.yaml
vendored
15
.github/workflows/audit.yaml
vendored
@@ -1,15 +0,0 @@
|
||||
name: Cloud Hypervisor Dependency Audit
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- '**/Cargo.toml'
|
||||
- '**/Cargo.lock'
|
||||
jobs:
|
||||
security_audit:
|
||||
name: Audit
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions-rs/audit-check@v1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
41
.github/workflows/build.yaml
vendored
41
.github/workflows/build.yaml
vendored
@@ -1,41 +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: Debug Build (default features)
|
||||
run: cargo build --all --target=${{ matrix.target }}
|
||||
|
||||
- name: Build (pci,acpi,kvm)
|
||||
run: cargo rustc --bin cloud-hypervisor --no-default-features --features "pci,acpi,kvm" -- -D warnings
|
||||
|
||||
- name: Build (pci,kvm)
|
||||
run: cargo rustc --bin cloud-hypervisor --no-default-features --features "pci,kvm" -- -D warnings
|
||||
|
||||
- name: Build (mmio,kvm)
|
||||
run: cargo rustc --bin cloud-hypervisor --no-default-features --features "mmio,kvm" -- -D warnings
|
||||
|
||||
- name: Release Build (default features)
|
||||
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"
|
||||
40
.github/workflows/quality-aarch64.yaml
vendored
40
.github/workflows/quality-aarch64.yaml
vendored
@@ -1,40 +0,0 @@
|
||||
name: Cloud Hypervisor Quality Checks
|
||||
on: [pull_request, create]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: github.event_name == 'pull_request'
|
||||
name: Quality (clippy, rustfmt)
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
rust:
|
||||
- stable
|
||||
target:
|
||||
- aarch64-unknown-linux-gnu
|
||||
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
|
||||
components: rustfmt, clippy
|
||||
- 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: Formatting (rustfmt)
|
||||
run: cargo fmt -- --check
|
||||
- name: Clippy (mmio,kvm)
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
use-cross: true
|
||||
command: clippy
|
||||
args: --target=${{ matrix.target }} --no-default-features --features "mmio,kvm" -- -D warnings
|
||||
- name: Clippy (pci,kvm)
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
use-cross: true
|
||||
command: clippy
|
||||
args: --target=${{ matrix.target }} --no-default-features --features "pci,kvm" -- -D warnings
|
||||
39
.github/workflows/quality.yaml
vendored
39
.github/workflows/quality.yaml
vendored
@@ -1,39 +0,0 @@
|
||||
name: Cloud Hypervisor Quality Checks
|
||||
on: [pull_request, create]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: github.event_name == 'pull_request'
|
||||
name: Quality (clippy, rustfmt)
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
rust:
|
||||
- stable
|
||||
target:
|
||||
- x86_64-unknown-linux-gnu
|
||||
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
|
||||
components: rustfmt, clippy
|
||||
|
||||
- name: Formatting (rustfmt)
|
||||
run: cargo fmt -- --check
|
||||
|
||||
- name: Clippy (all features)
|
||||
run: cargo clippy --all-targets --all-features -- -D warnings
|
||||
|
||||
- name: Clippy (pci,acpi,kvm)
|
||||
run: cargo clippy --all-targets --no-default-features --features "pci,acpi,kvm" -- -D warnings
|
||||
|
||||
- name: Clippy (pci,kvm)
|
||||
run: cargo clippy --all-targets --no-default-features --features "pci,kvm" -- -D warnings
|
||||
|
||||
- name: Clippy (mmio,kvm)
|
||||
run: cargo clippy --all-targets --no-default-features --features "mmio,kvm" -- -D warnings
|
||||
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
|
||||
1540
Cargo.lock
generated
1540
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
78
Cargo.toml
78
Cargo.toml
@@ -1,80 +1,68 @@
|
||||
[package]
|
||||
name = "cloud-hypervisor"
|
||||
version = "0.10.0"
|
||||
version = "0.5.1"
|
||||
authors = ["The Cloud Hypervisor Authors"]
|
||||
edition = "2018"
|
||||
default-run = "cloud-hypervisor"
|
||||
build = "build.rs"
|
||||
license = "LICENSE-APACHE & LICENSE-BSD-3-Clause"
|
||||
description = "Open source Virtual Machine Monitor (VMM) that runs on top of KVM"
|
||||
homepage = "https://github.com/cloud-hypervisor/cloud-hypervisor"
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "2.33.3", features = ["wrap_help"] }
|
||||
hypervisor = { path = "hypervisor" }
|
||||
libc = "0.2.77"
|
||||
log = { version = "0.4.11", features = ["std"] }
|
||||
option_parser = { path = "option_parser" }
|
||||
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" }
|
||||
|
||||
[build-dependencies]
|
||||
clap = { version = "2.33.3", features = ["wrap_help"] }
|
||||
|
||||
[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"
|
||||
]
|
||||
|
||||
170
Jenkinsfile
vendored
170
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,141 +14,55 @@ pipeline{
|
||||
}
|
||||
}
|
||||
stage ('Cancel older builds') {
|
||||
when { not { branch 'master' } }
|
||||
steps {
|
||||
cancelPreviousBuilds()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
stage ('Build') {
|
||||
failFast true
|
||||
parallel {
|
||||
stage ('Worker build') {
|
||||
agent { node { label 'bionic' } }
|
||||
options {
|
||||
timeout(time: 1, unit: 'HOURS')
|
||||
}
|
||||
stages {
|
||||
stage ('Checkout') {
|
||||
steps {
|
||||
checkout scm
|
||||
}
|
||||
}
|
||||
stage ('Run OpenAPI tests') {
|
||||
steps {
|
||||
sh "scripts/run_openapi_tests.sh"
|
||||
}
|
||||
}
|
||||
stage ('Run unit tests') {
|
||||
steps {
|
||||
sh "scripts/dev_cli.sh tests --unit"
|
||||
}
|
||||
}
|
||||
stage ('Run integration tests') {
|
||||
steps {
|
||||
sh "scripts/dev_cli.sh tests --integration"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
stage ('AArch64 worker build') {
|
||||
agent { node { label 'bionic-arm64' } }
|
||||
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') {
|
||||
steps {
|
||||
sh "scripts/dev_cli.sh tests --integration"
|
||||
}
|
||||
}
|
||||
}
|
||||
post {
|
||||
always {
|
||||
sh "sudo chown -R jenkins.jenkins ${WORKSPACE}"
|
||||
deleteDir()
|
||||
}
|
||||
}
|
||||
}
|
||||
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') {
|
||||
steps {
|
||||
sh "scripts/dev_cli.sh tests --integration --libc musl"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
stage ('Worker build SGX') {
|
||||
agent { node { label 'bionic-sgx' } }
|
||||
options {
|
||||
timeout(time: 1, unit: 'HOURS')
|
||||
}
|
||||
when { branch 'master' }
|
||||
stages {
|
||||
stage ('Checkout') {
|
||||
steps {
|
||||
checkout scm
|
||||
}
|
||||
}
|
||||
stage ('Run SGX integration tests') {
|
||||
steps {
|
||||
sh "scripts/dev_cli.sh tests --integration-sgx"
|
||||
}
|
||||
}
|
||||
stage ('Run SGX integration tests for musl') {
|
||||
steps {
|
||||
sh "scripts/dev_cli.sh tests --integration-sgx --libc musl"
|
||||
}
|
||||
}
|
||||
}
|
||||
post {
|
||||
always {
|
||||
sh "sudo chown -R jenkins.jenkins ${WORKSPACE}"
|
||||
deleteDir()
|
||||
}
|
||||
}
|
||||
}
|
||||
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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
154
README.md
154
README.md
@@ -1,34 +1,35 @@
|
||||
[](https://travis-ci.com/cloud-hypervisor/cloud-hypervisor)
|
||||
|
||||
- [1. What is Cloud Hypervisor?](#1-what-is-cloud-hypervisor)
|
||||
- [Objectives](#objectives)
|
||||
- [High Level](#high-level)
|
||||
- [Architectures](#architectures)
|
||||
- [Guest OS](#guest-os)
|
||||
- [2. Getting Started](#2-getting-started)
|
||||
- [Clone and build](#clone-and-build)
|
||||
- [Containerized builds and tests](#containerized-builds-and-tests)
|
||||
- [Run](#run)
|
||||
- [Cloud image](#cloud-image)
|
||||
- [Custom kernel and disk image](#custom-kernel-and-disk-image)
|
||||
- [Building your kernel](#building-your-kernel)
|
||||
- [Disk image](#disk-image)
|
||||
- [Booting the guest VM](#booting-the-guest-vm)
|
||||
- [3. Status](#3-status)
|
||||
- [Hot Plug](#hot-plug)
|
||||
- [Device Model](#device-model)
|
||||
- [TODO](#todo)
|
||||
- [4. `rust-vmm` project dependency](#4-rust-vmm-project-dependency)
|
||||
- [Firecracker and crosvm](#firecracker-and-crosvm)
|
||||
- [5. Community](#5-community)
|
||||
- [Contribute](#contribute)
|
||||
- [Join us](#join-us)
|
||||
1. [What is Cloud Hypervisor?](#1-what-is-cloud-hypervisor)
|
||||
* [Requirements](#requirements)
|
||||
+ [High Level](#high-level)
|
||||
+ [Architectures](#architectures)
|
||||
+ [Guest OS](#guest-os)
|
||||
2. [Getting Started](#2-getting-started)
|
||||
* [Clone and build](#clone-and-build)
|
||||
* [Run](#run)
|
||||
+ [Cloud image](#cloud-image)
|
||||
+ [Custom kernel and disk image](#custom-kernel-and-disk-image)
|
||||
- [Building your kernel](#building-your-kernel)
|
||||
- [Disk image](#disk-image)
|
||||
- [Booting the guest VM](#booting-the-guest-vm)
|
||||
3. [Status](#2-status)
|
||||
* [Device Model](#device-model)
|
||||
* [TODO](#todo)
|
||||
4. [rust-vmm dependency](#4-rust-vmm-dependency)
|
||||
* [Firecracker and crosvm](#firecracker-and-crosvm)
|
||||
5. [Community](#5-community)
|
||||
* [Join us](#join-us)
|
||||
6. [Security](#6-security)
|
||||
|
||||
# 1. What is Cloud Hypervisor?
|
||||
|
||||
**This project is an experiment and should not be used with production workloads.**
|
||||
|
||||
Cloud Hypervisor is an open source Virtual Machine Monitor (VMM) that runs on top of [KVM](https://www.kernel.org/doc/Documentation/virtual/kvm/api.txt).
|
||||
The project focuses on exclusively running modern, cloud workloads, on top of a limited set of hardware architectures and platforms.
|
||||
Cloud workloads refers to those that are usually run by customers inside a cloud provider. For our purposes this means modern operating systems with most I/O handled by paravirtualised devices (i.e. virtio), no requirement for legacy devices, and 64-bit CPUs.
|
||||
Cloud workloads refers to those that are usually run by customers inside a cloud provider. For our purposes this means modern
|
||||
Linux* distributions with most I/O handled by paravirtualised devices (i.e. virtio), no requirement for legacy devices and recent CPUs and KVM.
|
||||
|
||||
Cloud Hypervisor is implemented in [Rust](https://www.rust-lang.org/) and is based on the [rust-vmm](https://github.com/rust-vmm) crates.
|
||||
|
||||
@@ -36,7 +37,7 @@ Cloud Hypervisor is implemented in [Rust](https://www.rust-lang.org/) and is bas
|
||||
|
||||
### High Level
|
||||
|
||||
* KVM based
|
||||
* KVM and KVM only based
|
||||
* Minimal emulation
|
||||
* Low latency
|
||||
* Low memory footprint
|
||||
@@ -44,16 +45,19 @@ Cloud Hypervisor is implemented in [Rust](https://www.rust-lang.org/) and is bas
|
||||
* High performance
|
||||
* Small attack surface
|
||||
* 64-bit support only
|
||||
* CPU, memory, PCI hotplug
|
||||
* Build time configurable CPU, memory, PCI and NVDIMM hotplug
|
||||
* Machine to machine migration
|
||||
|
||||
### Architectures
|
||||
|
||||
Cloud Hypervisor supports the `x86-64` and `AArch64` architecture. There are some small differences in functionality between the two architecture (see #1125).
|
||||
`cloud-hypervisor` only supports the `x86-64` CPU architecture for now.
|
||||
|
||||
We're planning to add support for the `AArch64` architecture in the future.
|
||||
|
||||
### Guest OS
|
||||
* `64-bit Linux`
|
||||
|
||||
Cloud Hypervisor supports `64-bit Linux` with support for *modern* 64-bit Windows guests currently under development.
|
||||
Support for *modern* 64-bit Windows guest is being evaluated.
|
||||
|
||||
# 2. Getting Started
|
||||
|
||||
@@ -116,20 +120,20 @@ You can run a guest VM by either using an existing cloud image or booting into y
|
||||
|
||||
### Cloud image
|
||||
|
||||
Cloud Hypervisor supports booting disk images containing all needed
|
||||
`cloud-hypervisor` supports booting disk images containing all needed
|
||||
components to run cloud workloads, a.k.a. cloud images. To do that we rely on
|
||||
the [Rust Hypervisor
|
||||
Firmware](https://github.com/cloud-hypervisor/rust-hypervisor-firmware) project to provide
|
||||
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
|
||||
```
|
||||
|
||||
@@ -138,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=" \
|
||||
@@ -152,7 +156,8 @@ Multiple arguments can be given to the `--disk` parameter.
|
||||
|
||||
#### Building your kernel
|
||||
|
||||
Cloud Hypervisor also supports direct kernel boot into a `vmlinux` ELF kernel or `bzImage`. In order to support virtio-fs and virtio-iommu we have our own development branch. You are of course able to use your own kernel but these instructions will continue with the version that we develop and test against.
|
||||
`cloud-hypervisor` also supports direct kernel boot into a `vmlinux` ELF kernel
|
||||
image. In order to support virtio-fs and virtio-iommu we have our own development branch. You are of course able to use your own kernel but these instructions will continue with the version that we develop and test against.
|
||||
|
||||
To build the kernel:
|
||||
|
||||
@@ -160,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
|
||||
```
|
||||
@@ -173,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
|
||||
@@ -192,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=" \
|
||||
@@ -212,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=" \
|
||||
@@ -222,51 +227,52 @@ $ ./cloud-hypervisor/target/release/cloud-hypervisor \
|
||||
|
||||
# 3. Status
|
||||
|
||||
Cloud Hypervisor is under active development. No API or feature stability is guaranteed.
|
||||
`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 a rootfs from most distributions.
|
||||
Direct kernel boot to userspace should work with most rootfs.
|
||||
|
||||
## Hot Plug
|
||||
|
||||
Cloud Hypervisor supports hotplug of CPUs, passthrough devices (VFIO), `virtio-{net,block,pmem,fs,vsock}` and memory resizing. This [document](https://github.com/cloud-hypervisor/cloud-hypervisor/blob/master/docs/hotplug.md) details how to add devices to
|
||||
a running VM.
|
||||
This [document](https://github.com/cloud-hypervisor/cloud-hypervisor/blob/master/docs/hotplug.md) details how to add devices to
|
||||
a running VM. Currently only CPU hot plug is supported.
|
||||
|
||||
## Device Model
|
||||
|
||||
Details of the device model can be found in this [documentation](https://github.com/cloud-hypervisor/cloud-hypervisor/blob/master/docs/device_model.md).
|
||||
Follow this [documentation](https://github.com/cloud-hypervisor/cloud-hypervisor/blob/master/docs/device_model.md).
|
||||
|
||||
## TODO
|
||||
|
||||
We are not tracking the Cloud Hypervisor TODO list from a specific git tracked file but through
|
||||
We are not tracking the `cloud-hypervisor` TODO list from a specific git tracked file but through
|
||||
[github issues](https://github.com/cloud-hypervisor/cloud-hypervisor/issues/new) instead.
|
||||
|
||||
# 4. `rust-vmm` project dependency
|
||||
|
||||
In order to satisfy the design goal of having a high-performance, security-focused hypervisor the decision
|
||||
was made to use the [Rust](https://www.rust-lang.org/) programming language.
|
||||
The language's strong focus on memory and thread safety makes it an ideal candidate for implementing VMMs.
|
||||
The language's strong focus on memory and thread safety makes it an ideal candidate for implementing VMMs
|
||||
|
||||
Instead of implementing the VMM components from scratch, Cloud Hypervisor is importing the [rust-vmm](https://github.com/rust-vmm)
|
||||
Instead of implementing the VMM components from scratch, `cloud-hypervisor` is importing the [rust-vmm](https://github.com/rust-vmm)
|
||||
crates, and sharing code and architecture together with other VMMs like e.g. Amazon's [Firecracker](https://firecracker-microvm.github.io/)
|
||||
and Google's [crosvm](https://chromium.googlesource.com/chromiumos/platform/crosvm/).
|
||||
|
||||
Cloud Hypervisor embraces the rust-vmm project goals, which is to be able to share and re-use
|
||||
as many virtualization crates as possible. As such, the Cloud Hypervisor relationship with the rust-vmm
|
||||
`cloud-hypervisor` embraces the rust-vmm project goals, which is to be able to share and re-use
|
||||
as many virtualization crates as possible. As such, the `cloud-hypervisor` relationship with the rust-vmm
|
||||
project is twofold:
|
||||
|
||||
1. It will use as much of the rust-vmm code as possible. Any new rust-vmm crate that's relevant to the project
|
||||
goals will be integrated as soon as possible.
|
||||
2. As it is likely that the rust-vmm project will lack some of the features that Cloud Hypervisor needs (e.g. ACPI,
|
||||
VFIO, vhost-user, etc), we will be using the Cloud Hypervisor VMM to implement and test them, and contribute them
|
||||
2. As it is likely that the rust-vmm project will lack some of the features that `cloud-hypervisor` needs (e.g. ACPI,
|
||||
VFIO, vhost-user, etc), we will be using the `cloud-hypervisor` VMM to implement and test them, and contribute them
|
||||
back to the rust-vmm project.
|
||||
|
||||
## Firecracker and crosvm
|
||||
|
||||
A large part of the Cloud Hypervisor code is based on either the Firecracker or the crosvm projects implementations.
|
||||
A large part of the `cloud-hypervisor` code is based on either the Firecracker or the crosvm projects implementations.
|
||||
Both of these are VMMs written in Rust with a focus on safety and security, like Cloud Hypervisor.
|
||||
|
||||
However we want to emphasize that the Cloud Hypervisor project is neither a fork nor a reimplementation of any of those
|
||||
@@ -281,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.
|
||||
|
||||
@@ -298,3 +299,26 @@ etc, are all equal and welcome means of contribution. See the [CONTRIBUTING](CON
|
||||
Get an [invite to our Slack channel](https://join.slack.com/t/cloud-hypervisor/shared_invite/enQtNjY3MTE3MDkwNDQ4LWQ1MTA1ZDVmODkwMWQ1MTRhYzk4ZGNlN2UwNTI3ZmFlODU0OTcwOWZjMTkwZDExYWE3YjFmNzgzY2FmNDAyMjI)
|
||||
and [join us on Slack](https://cloud-hypervisor.slack.com/).
|
||||
|
||||
# 6. Security
|
||||
|
||||
**Reporting a Potential Security Vulnerability**: If you have discovered
|
||||
potential security vulnerability in this project, please send an e-mail to
|
||||
secure@intel.com. For issues related to Intel Products, please visit
|
||||
https://security-center.intel.com.
|
||||
|
||||
It is important to include the following details:
|
||||
- The projects and versions affected
|
||||
- Detailed description of the vulnerability
|
||||
- Information on known exploits
|
||||
|
||||
Vulnerability information is extremely sensitive. Please encrypt all security
|
||||
vulnerability reports using our *PGP key*
|
||||
|
||||
A member of the Intel Product Security Team will review your e-mail and
|
||||
contact you to to collaborate on resolving the issue. For more information on
|
||||
how Intel works to resolve security issues, see: *Vulnerability Handling
|
||||
Guidelines*
|
||||
|
||||
PGP Key: https://www.intel.com/content/www/us/en/security-center/pgp-public-key.html
|
||||
|
||||
Vulnerability Handling Guidelines: https://www.intel.com/content/www/us/en/security-center/vulnerability-handling-guidelines.html
|
||||
|
||||
@@ -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.77"
|
||||
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, S: ::std::hash::BuildHasher>(
|
||||
guest_mem: &GuestMemoryMmap,
|
||||
cmdline: &CStr,
|
||||
vcpu_mpidr: Vec<u64>,
|
||||
device_info: &HashMap<(DeviceType, String), T, S>,
|
||||
gic_device: &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: &[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, mpidr) in vcpu_mpidr.iter().enumerate().take(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", mpidr & 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: &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, S: ::std::hash::BuildHasher>(
|
||||
fdt: &mut Vec<u8>,
|
||||
dev_info: &HashMap<(DeviceType, String), T, S>,
|
||||
) -> 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_key(|&a| a.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: &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,
|
||||
attr,
|
||||
addr,
|
||||
flags,
|
||||
};
|
||||
device
|
||||
.set_device_attr(&attr)
|
||||
.map_err(super::Error::SetDeviceAttribute)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Finalize the setup of a GIC device
|
||||
fn finalize_device(gic_device: &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,
|
||||
properties: [
|
||||
KvmGICv2::get_dist_addr(),
|
||||
KvmGICv2::get_dist_size(),
|
||||
KvmGICv2::get_cpu_addr(),
|
||||
KvmGICv2::get_cpu_size(),
|
||||
],
|
||||
vcpu_count,
|
||||
})
|
||||
}
|
||||
|
||||
fn init_device_attributes(
|
||||
_vm: &Arc<dyn hypervisor::Vm>,
|
||||
gic_device: &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,127 +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,
|
||||
properties: [
|
||||
KvmGICv3::get_dist_addr(),
|
||||
KvmGICv3::get_dist_size(),
|
||||
KvmGICv3::get_redists_addr(vcpu_count),
|
||||
KvmGICv3::get_redists_size(vcpu_count),
|
||||
],
|
||||
vcpu_count,
|
||||
})
|
||||
}
|
||||
|
||||
fn init_device_attributes(
|
||||
_vm: &Arc<dyn hypervisor::Vm>,
|
||||
gic_device: &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(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,
|
||||
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,
|
||||
})
|
||||
}
|
||||
|
||||
fn init_device_attributes(
|
||||
vm: &Arc<dyn hypervisor::Vm>,
|
||||
gic_device: &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(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,270 +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.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn configure_system<T: DeviceInfoForFDT + Clone + Debug, S: ::std::hash::BuildHasher>(
|
||||
vm: &Arc<dyn hypervisor::Vm>,
|
||||
guest_mem: &GuestMemoryMmap,
|
||||
cmdline_cstring: &CStr,
|
||||
vcpu_count: u64,
|
||||
vcpu_mpidr: Vec<u64>,
|
||||
device_info: &HashMap<(DeviceType, String), T, S>,
|
||||
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 { &(*std::ptr::null::<user_pt_regs>()).$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,42 +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,
|
||||
|
||||
// Error populating Cpuid
|
||||
PopulatingCpuid,
|
||||
}
|
||||
|
||||
impl From<Error> for super::Error {
|
||||
@@ -185,241 +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,
|
||||
kvm_hyperv: bool,
|
||||
) -> 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));
|
||||
|
||||
if kvm_hyperv {
|
||||
// Remove conflicting entries
|
||||
cpuid.retain(|c| c.function != 0x4000_0000);
|
||||
cpuid.retain(|c| c.function != 0x4000_0001);
|
||||
|
||||
// See "Hypervisor Top Level Functional Specification" for details
|
||||
// Compliance with "Hv#1" requires leaves up to 0x4000_000a
|
||||
cpuid
|
||||
.push(CpuIdEntry {
|
||||
function: 0x40000000,
|
||||
eax: 0x4000000a, // Maximum cpuid leaf
|
||||
ebx: 0x756e694c, // "Linu"
|
||||
ecx: 0x564b2078, // "x KV"
|
||||
edx: 0x7648204d, // "M Hv"
|
||||
..Default::default()
|
||||
})
|
||||
.map_err(|_| Error::PopulatingCpuid)?;
|
||||
cpuid
|
||||
.push(CpuIdEntry {
|
||||
function: 0x40000001,
|
||||
eax: 0x31237648, // "Hv#1"
|
||||
..Default::default()
|
||||
})
|
||||
.map_err(|_| Error::PopulatingCpuid)?;
|
||||
cpuid
|
||||
.push(CpuIdEntry {
|
||||
function: 0x40000002,
|
||||
eax: 0x3839, // "Build number"
|
||||
ebx: 0xa0000, // "Version"
|
||||
..Default::default()
|
||||
})
|
||||
.map_err(|_| Error::PopulatingCpuid)?;
|
||||
cpuid
|
||||
.push(CpuIdEntry {
|
||||
function: 0x4000_0003,
|
||||
eax: 1 << 1 // AccessPartitionReferenceCounter
|
||||
| 1 << 2 // AccessSynicRegs
|
||||
| 1 << 3 // AccessSyntheticTimerRegs
|
||||
| 1 << 9, // AccessPartitionReferenceTsc
|
||||
..Default::default()
|
||||
})
|
||||
.map_err(|_| Error::PopulatingCpuid)?;
|
||||
for i in 0x4000_0004..=0x4000_000a {
|
||||
cpuid
|
||||
.push(CpuIdEntry {
|
||||
function: i,
|
||||
..Default::default()
|
||||
})
|
||||
.map_err(|_| Error::PopulatingCpuid)?;
|
||||
}
|
||||
}
|
||||
|
||||
fd.set_cpuid2(&cpuid)
|
||||
.map_err(|e| Error::SetSupportedCpusFailed(e.into()))?;
|
||||
|
||||
if kvm_hyperv {
|
||||
fd.enable_hyperv_synic().unwrap();
|
||||
}
|
||||
|
||||
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
|
||||
@@ -480,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();
|
||||
@@ -744,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;
|
||||
}
|
||||
@@ -788,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::*;
|
||||
@@ -969,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.
|
||||
@@ -991,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;
|
||||
@@ -1026,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;
|
||||
@@ -1061,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]
|
||||
@@ -1127,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 = ">=0.4.0"
|
||||
libc = "0.2.77"
|
||||
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,567 +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, squeue, 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,
|
||||
/// The requested operation does not support multiple descriptors.
|
||||
TooManyDescriptors,
|
||||
}
|
||||
|
||||
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_descriptors: Vec<(GuestAddress, 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_descriptors: Vec::new(),
|
||||
status_addr: GuestAddress(0),
|
||||
writeback: true,
|
||||
};
|
||||
|
||||
let status_desc;
|
||||
let mut 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 {
|
||||
while desc.has_next() {
|
||||
if desc.is_write_only() && req.request_type == RequestType::Out {
|
||||
return Err(Error::UnexpectedWriteOnlyDescriptor);
|
||||
}
|
||||
if !desc.is_write_only() && req.request_type == RequestType::In {
|
||||
return Err(Error::UnexpectedReadOnlyDescriptor);
|
||||
}
|
||||
if !desc.is_write_only() && req.request_type == RequestType::GetDeviceID {
|
||||
return Err(Error::UnexpectedReadOnlyDescriptor);
|
||||
}
|
||||
req.data_descriptors.push((desc.addr, desc.len));
|
||||
desc = desc
|
||||
.next_descriptor()
|
||||
.ok_or(Error::DescriptorChainTooShort)?;
|
||||
}
|
||||
status_desc = desc;
|
||||
}
|
||||
|
||||
// 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> {
|
||||
disk.seek(SeekFrom::Start(self.sector << SECTOR_SHIFT))
|
||||
.map_err(ExecuteError::Seek)?;
|
||||
let mut len = 0;
|
||||
for (data_addr, data_len) in &self.data_descriptors {
|
||||
let mut top: u64 = u64::from(*data_len) / SECTOR_SIZE;
|
||||
if u64::from(*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));
|
||||
}
|
||||
|
||||
match self.request_type {
|
||||
RequestType::In => {
|
||||
mem.read_exact_from(*data_addr, disk, *data_len as usize)
|
||||
.map_err(ExecuteError::Read)?;
|
||||
len += data_len;
|
||||
}
|
||||
RequestType::Out => {
|
||||
mem.write_all_to(*data_addr, disk, *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 (*data_len as usize) < disk_id.len() {
|
||||
return Err(ExecuteError::BadRequest(Error::InvalidOffset));
|
||||
}
|
||||
mem.write_slice(&disk_id.as_slice(), *data_addr)
|
||||
.map_err(ExecuteError::Write)?;
|
||||
}
|
||||
RequestType::Unsupported(t) => return Err(ExecuteError::Unsupported(t)),
|
||||
};
|
||||
}
|
||||
Ok(len)
|
||||
}
|
||||
|
||||
#[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 sector = self.sector;
|
||||
let request_type = self.request_type;
|
||||
let offset = (sector << SECTOR_SHIFT) as libc::off_t;
|
||||
|
||||
let (submitter, sq, _) = io_uring.split();
|
||||
let mut avail_sq = sq.available();
|
||||
|
||||
let mut iovecs = Vec::new();
|
||||
for (data_addr, data_len) in &self.data_descriptors {
|
||||
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 iovec = libc::iovec {
|
||||
iov_base: buf as *mut libc::c_void,
|
||||
iov_len: *data_len as libc::size_t,
|
||||
};
|
||||
iovecs.push(iovec);
|
||||
}
|
||||
|
||||
// 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::Readv::new(
|
||||
opcode::types::Fd(disk_image_fd),
|
||||
iovecs.as_ptr(),
|
||||
iovecs.len() as u32,
|
||||
)
|
||||
.offset(offset)
|
||||
.build()
|
||||
.flags(squeue::Flags::ASYNC)
|
||||
.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::Writev::new(
|
||||
opcode::types::Fd(disk_image_fd),
|
||||
iovecs.as_ptr(),
|
||||
iovecs.len() as u32,
|
||||
)
|
||||
.offset(offset)
|
||||
.build()
|
||||
.flags(squeue::Flags::ASYNC)
|
||||
.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()
|
||||
.flags(squeue::Flags::ASYNC)
|
||||
.user_data(user_data),
|
||||
)
|
||||
};
|
||||
}
|
||||
RequestType::GetDeviceID => {
|
||||
let (data_addr, data_len) = if self.data_descriptors.len() == 1 {
|
||||
(self.data_descriptors[0].0, self.data_descriptors[0].1)
|
||||
} else {
|
||||
return Err(ExecuteError::BadRequest(Error::TooManyDescriptors));
|
||||
};
|
||||
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
|
||||
}
|
||||
27
build.rs
27
build.rs
@@ -1,27 +0,0 @@
|
||||
// Copyright © 2020 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
#[macro_use(crate_version)]
|
||||
extern crate clap;
|
||||
|
||||
use std::process::Command;
|
||||
|
||||
fn main() {
|
||||
let mut version = crate_version!().to_string();
|
||||
|
||||
if let Ok(git_out) = Command::new("git").args(&["describe", "--dirty"]).output() {
|
||||
if git_out.status.success() {
|
||||
if let Ok(git_out_str) = String::from_utf8(git_out.stdout) {
|
||||
version = git_out_str;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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={}", version);
|
||||
}
|
||||
@@ -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.77"
|
||||
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,12 +3,10 @@
|
||||
// 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 vm_device::BusDevice;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
use BusDevice;
|
||||
use HotPlugNotificationFlags;
|
||||
|
||||
/// A device for handling ACPI shutdown and reboot
|
||||
@@ -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,
|
||||
@@ -214,30 +196,30 @@ impl Bus {
|
||||
/// Reads data from the device that owns the range containing `addr` and puts it into `data`.
|
||||
///
|
||||
/// Returns true on success, otherwise `data` is untouched.
|
||||
pub fn read(&self, addr: u64, data: &mut [u8]) -> Result<()> {
|
||||
pub fn read(&self, addr: u64, data: &mut [u8]) -> bool {
|
||||
if let Some((base, offset, dev)) = self.resolve(addr) {
|
||||
// OK to unwrap as lock() failing is a serious error condition and should panic.
|
||||
dev.lock()
|
||||
.expect("Failed to acquire device lock")
|
||||
.read(base, offset, data);
|
||||
Ok(())
|
||||
true
|
||||
} else {
|
||||
Err(Error::MissingAddressRange)
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes `data` to the device that owns the range containing `addr`.
|
||||
///
|
||||
/// Returns true on success, otherwise `data` is untouched.
|
||||
pub fn write(&self, addr: u64, data: &[u8]) -> Result<()> {
|
||||
pub fn write(&self, addr: u64, data: &[u8]) -> bool {
|
||||
if let Some((base, offset, dev)) = self.resolve(addr) {
|
||||
// OK to unwrap as lock() failing is a serious error condition and should panic.
|
||||
dev.lock()
|
||||
.expect("Failed to acquire device lock")
|
||||
.write(base, offset, data);
|
||||
Ok(())
|
||||
true
|
||||
} else {
|
||||
Err(Error::MissingAddressRange)
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -290,16 +272,16 @@ mod tests {
|
||||
let bus = Bus::new();
|
||||
let dummy = Arc::new(Mutex::new(DummyDevice));
|
||||
assert!(bus.insert(dummy.clone(), 0x10, 0x10).is_ok());
|
||||
assert!(bus.read(0x10, &mut [0, 0, 0, 0]).is_ok());
|
||||
assert!(bus.write(0x10, &[0, 0, 0, 0]).is_ok());
|
||||
assert!(bus.read(0x11, &mut [0, 0, 0, 0]).is_ok());
|
||||
assert!(bus.write(0x11, &[0, 0, 0, 0]).is_ok());
|
||||
assert!(bus.read(0x16, &mut [0, 0, 0, 0]).is_ok());
|
||||
assert!(bus.write(0x16, &[0, 0, 0, 0]).is_ok());
|
||||
assert!(bus.read(0x20, &mut [0, 0, 0, 0]).is_err());
|
||||
assert!(bus.write(0x20, &mut [0, 0, 0, 0]).is_err());
|
||||
assert!(bus.read(0x06, &mut [0, 0, 0, 0]).is_err());
|
||||
assert!(bus.write(0x06, &mut [0, 0, 0, 0]).is_err());
|
||||
assert!(bus.read(0x10, &mut [0, 0, 0, 0]));
|
||||
assert!(bus.write(0x10, &[0, 0, 0, 0]));
|
||||
assert!(bus.read(0x11, &mut [0, 0, 0, 0]));
|
||||
assert!(bus.write(0x11, &[0, 0, 0, 0]));
|
||||
assert!(bus.read(0x16, &mut [0, 0, 0, 0]));
|
||||
assert!(bus.write(0x16, &[0, 0, 0, 0]));
|
||||
assert!(!bus.read(0x20, &mut [0, 0, 0, 0]));
|
||||
assert!(!bus.write(0x20, &mut [0, 0, 0, 0]));
|
||||
assert!(!bus.read(0x06, &mut [0, 0, 0, 0]));
|
||||
assert!(!bus.write(0x06, &mut [0, 0, 0, 0]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -309,12 +291,12 @@ mod tests {
|
||||
assert!(bus.insert(dummy.clone(), 0x10, 0x10).is_ok());
|
||||
|
||||
let mut values = [0, 1, 2, 3];
|
||||
assert!(bus.read(0x10, &mut values).is_ok());
|
||||
assert!(bus.read(0x10, &mut values));
|
||||
assert_eq!(values, [0, 1, 2, 3]);
|
||||
assert!(bus.write(0x10, &values).is_ok());
|
||||
assert!(bus.read(0x15, &mut values).is_ok());
|
||||
assert!(bus.write(0x10, &values));
|
||||
assert!(bus.read(0x15, &mut values));
|
||||
assert_eq!(values, [5, 6, 7, 8]);
|
||||
assert!(bus.write(0x15, &values).is_ok());
|
||||
assert!(bus.write(0x15, &values));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -330,10 +312,11 @@ 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.write(0x10, &mut data).is_ok());
|
||||
assert!(bus.read(0x10, &mut data).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,84 +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(&mut 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,25 +9,38 @@
|
||||
// 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 anyhow::anyhow;
|
||||
use crate::BusDevice;
|
||||
use byteorder::{ByteOrder, LittleEndian};
|
||||
use std::io;
|
||||
use std::result;
|
||||
use std::sync::Arc;
|
||||
use vm_device::interrupt::{
|
||||
InterruptIndex, InterruptManager, InterruptSourceConfig, InterruptSourceGroup,
|
||||
MsiIrqGroupConfig, MsiIrqSourceConfig,
|
||||
};
|
||||
use vm_device::BusDevice;
|
||||
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(&mut 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,10 +2,11 @@
|
||||
// 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;
|
||||
use vm_device::BusDevice;
|
||||
|
||||
use crate::BusDevice;
|
||||
|
||||
const INDEX_MASK: u8 = 0x7f;
|
||||
const INDEX_OFFSET: u64 = 0x0;
|
||||
@@ -76,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;
|
||||
@@ -95,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),
|
||||
@@ -109,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 vm_device::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())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
use vm_device::BusDevice;
|
||||
use BusDevice;
|
||||
|
||||
/// A i8042 PS/2 controller that emulates just enough to shutdown the machine.
|
||||
pub struct I8042Device {
|
||||
|
||||
@@ -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,625 +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 vm_device::interrupt::InterruptSourceGroup;
|
||||
use vm_device::BusDevice;
|
||||
|
||||
// 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);
|
||||
}
|
||||
@@ -5,16 +5,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 anyhow::anyhow;
|
||||
use crate::BusDevice;
|
||||
use std::collections::VecDeque;
|
||||
use std::sync::Arc;
|
||||
use std::{io, result};
|
||||
use vm_device::interrupt::InterruptSourceGroup;
|
||||
use vm_device::BusDevice;
|
||||
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(&mut 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,34 +13,65 @@ 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;
|
||||
|
||||
#[cfg(feature = "acpi")]
|
||||
mod acpi;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub mod gic;
|
||||
pub mod interrupt_controller;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
mod bus;
|
||||
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;
|
||||
|
||||
/// The payload is used to handle events where the internal state of the VirtIO device
|
||||
/// needs to be changed.
|
||||
pub enum EpollHandlerPayload {
|
||||
/// DrivePayload(disk_image)
|
||||
DrivePayload(File),
|
||||
/// Events that do not need a payload.
|
||||
Empty,
|
||||
}
|
||||
|
||||
type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
pub trait EpollHandler: Send {
|
||||
fn handle_event(
|
||||
&mut self,
|
||||
device_event: DeviceEventT,
|
||||
event_flags: u32,
|
||||
payload: EpollHandlerPayload,
|
||||
) -> Result<()>;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
FailedReadingQueue {
|
||||
event_type: &'static str,
|
||||
underlying: io::Error,
|
||||
},
|
||||
FailedReadTap,
|
||||
FailedSignalingUsedQueue(io::Error),
|
||||
PayloadExpected,
|
||||
UnknownEvent {
|
||||
device: &'static str,
|
||||
event: DeviceEventT,
|
||||
},
|
||||
IoError(io::Error),
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
pub struct HotPlugNotificationFlags: u8 {
|
||||
const NO_DEVICES_CHANGED = 0;
|
||||
const CPU_DEVICES_CHANGED = 0b1;
|
||||
const MEMORY_DEVICES_CHANGED = 0b10;
|
||||
const PCI_DEVICES_CHANGED = 0b100;
|
||||
}
|
||||
}
|
||||
|
||||
52
docs/api.md
52
docs/api.md
@@ -78,27 +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
|
||||
Add/remove memory from the VM | `/vm.resize` | `/schemas/VmResize` | N/A | The VM is booted
|
||||
Add/remove memory from a zone | `/vm.resize-zone` | `/schemas/VmResizeZone` | 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
|
||||
|
||||
@@ -123,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
|
||||
@@ -138,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"}]
|
||||
}'
|
||||
@@ -276,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:
|
||||
|
||||
@@ -306,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"}]
|
||||
}'
|
||||
@@ -356,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)?;
|
||||
}
|
||||
```
|
||||
@@ -377,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 \
|
||||
|
||||
14
docs/fs.md
14
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=512M,shared=on" \
|
||||
--disk path=focal-server-cloudimg-amd64.raw \
|
||||
--memory "size=512,file=/dev/shm" \
|
||||
--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.
|
||||
@@ -86,11 +86,11 @@ virtual IOMMU:
|
||||
|
||||
```bash
|
||||
./cloud-hypervisor \
|
||||
--cpus boot=1 \
|
||||
--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
|
||||
@@ -163,11 +163,11 @@ be consumed.
|
||||
|
||||
```bash
|
||||
./cloud-hypervisor \
|
||||
--cpus boot=1 \
|
||||
--memory size=8G,hugepages=on \
|
||||
--disk path=focal-server-cloudimg-amd64.raw \
|
||||
--cpus 1 \
|
||||
--memory size=8G,file=/dev/hugepages \
|
||||
--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
|
||||
```
|
||||
|
||||
@@ -180,11 +180,11 @@ passing through is `0000:00:01.0`.
|
||||
|
||||
```bash
|
||||
./cloud-hypervisor \
|
||||
--cpus boot=1 \
|
||||
--memory size=8G,hugepages=on \
|
||||
--disk path=focal-server-cloudimg-amd64.raw \
|
||||
--cpus 1 \
|
||||
--memory size=8G,file=/dev/hugepages \
|
||||
--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
|
||||
```
|
||||
|
||||
@@ -200,10 +200,10 @@ Last thing is to start the L2 guest with the huge pages memory backend.
|
||||
|
||||
```bash
|
||||
./cloud-hypervisor \
|
||||
--cpus boot=1 \
|
||||
--memory size=4G,hugepages=on \
|
||||
--disk path=focal-server-cloudimg-amd64.raw \
|
||||
--cpus 1 \
|
||||
--memory size=4G,file=/dev/hugepages \
|
||||
--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
|
||||
```
|
||||
|
||||
467
docs/memory.md
467
docs/memory.md
@@ -1,467 +0,0 @@
|
||||
# Memory
|
||||
|
||||
Cloud-Hypervisor has many ways to expose memory to the guest VM. This document
|
||||
aims to explain what Cloud-Hypervisor is capable of and how it can be used to
|
||||
meet the needs of very different use cases.
|
||||
|
||||
## Basic Parameters
|
||||
|
||||
`MemoryConfig` or what is known as `--memory` from the CLI perspective is the
|
||||
easiest way to get started with Cloud-Hypervisor.
|
||||
|
||||
```rust
|
||||
struct MemoryConfig {
|
||||
size: u64,
|
||||
mergeable: bool,
|
||||
shared: bool,
|
||||
hugepages: bool,
|
||||
hotplug_method: HotplugMethod,
|
||||
hotplug_size: Option<u64>,
|
||||
hotplugged_size: Option<u64>,
|
||||
balloon: bool,
|
||||
balloon_size: u64,
|
||||
zones: Option<Vec<MemoryZoneConfig>>,
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
--memory <memory> Memory parameters "size=<guest_memory_size>,mergeable=on|off,shared=on|off,hugepages=on|off,hotplug_method=acpi|virtio-mem,hotplug_size=<hotpluggable_memory_size>,hotplugged_size=<hotplugged_memory_size>,balloon=on|off"
|
||||
```
|
||||
|
||||
### `size`
|
||||
|
||||
Size of the RAM in the guest VM.
|
||||
|
||||
This option is mandatory when using the `--memory` parameter.
|
||||
|
||||
Value is an unsigned integer of 64 bits.
|
||||
|
||||
_Example_
|
||||
|
||||
```
|
||||
--memory size=1G
|
||||
```
|
||||
|
||||
### `mergeable`
|
||||
|
||||
Specifies if the pages from the guest RAM must be marked as _mergeable_. In
|
||||
case this option is `true` or `on`, the pages will be marked with `madvise(2)`
|
||||
to let the host kernel know which pages are eligible for being merged by the
|
||||
KSM daemon.
|
||||
|
||||
This option can be used when trying to reach a higher density of VMs running
|
||||
on a single host, as it will reduce the amount of memory consumed by each VM.
|
||||
|
||||
By default this option is turned off.
|
||||
|
||||
_Example_
|
||||
|
||||
```
|
||||
--memory size=1G,mergeable=on
|
||||
```
|
||||
|
||||
### `shared`
|
||||
|
||||
Specifies if the memory must be `mmap(2)` with `MAP_SHARED` flag.
|
||||
|
||||
By sharing a memory mapping, one can share the guest RAM with other processes
|
||||
running on the host. One can use this option when running vhost-user devices
|
||||
as part of the VM device model, as they will be driven by standalone daemons
|
||||
needing access to the guest RAM content.
|
||||
|
||||
By default this option is turned off, which results in performing `mmap(2)`
|
||||
with `MAP_PRIVATE` flag.
|
||||
|
||||
_Example_
|
||||
|
||||
```
|
||||
--memory size=1G,shared=on
|
||||
```
|
||||
|
||||
### `hugepages`
|
||||
|
||||
Specifies if the memory must be `mmap(2)` with `MAP_HUGETLB` and `MAP_HUGE_2MB`
|
||||
flags. This performs a memory mapping relying on 2MiB pages instead of the
|
||||
default 4kiB pages.
|
||||
|
||||
By using hugepages, one can improve the overall performance of the VM, assuming
|
||||
the guest will allocate hugepages as well. Another interesting use case is VFIO
|
||||
as it speeds up the VM's boot time since the amount of IOMMU mappings are
|
||||
reduced.
|
||||
|
||||
By default this option is turned off.
|
||||
|
||||
_Example_
|
||||
|
||||
```
|
||||
--memory size=1G,hugepages=on
|
||||
```
|
||||
|
||||
### `hotplug_method`
|
||||
|
||||
Selects the way of adding and/or removing memory to/from a booted VM.
|
||||
|
||||
Possible values are `acpi` and `virtio-mem`. Default value is `acpi`.
|
||||
|
||||
_Example_
|
||||
|
||||
```
|
||||
--memory size=1G,hotplug_method=acpi
|
||||
```
|
||||
|
||||
### `hotplug_size`
|
||||
|
||||
Amount of memory that can be dynamically added to the VM.
|
||||
|
||||
Value is an unsigned integer of 64 bits. A value of 0 is invalid.
|
||||
|
||||
_Example_
|
||||
|
||||
```
|
||||
--memory size=1G,hotplug_size=1G
|
||||
```
|
||||
|
||||
### `hotplugged_size`
|
||||
|
||||
Amount of memory that will be dynamically added to the VM at boot. This option
|
||||
allows for starting a VM with a certain amount of memory that can be reduced
|
||||
during runtime.
|
||||
|
||||
This is only valid when the `hotplug_method` is `virtio-mem` as it does not
|
||||
make sense for the `acpi` use case. When using ACPI, the memory can't be
|
||||
resized after it has been extended.
|
||||
|
||||
This option is only valid when `hotplug_size` is specified, and its value can't
|
||||
exceed the value of `hotplug_size`.
|
||||
|
||||
Value is an unsigned integer of 64 bits. A value of 0 is invalid.
|
||||
|
||||
_Example_
|
||||
|
||||
```
|
||||
--memory size=1G,hotplug_method=virtio-mem,hotplug_size=1G,hotplugged_size=512M
|
||||
```
|
||||
|
||||
### `balloon`
|
||||
|
||||
Specifies if the `virtio-balloon` device must be activated. This creates a
|
||||
dedicated virtio device for managing the balloon in the guest, which allows
|
||||
guest to access more or less memory depending on the balloon size.
|
||||
|
||||
By default this option is turned off.
|
||||
|
||||
_Example_
|
||||
|
||||
```
|
||||
--memory size=1G,balloon=on
|
||||
```
|
||||
|
||||
## Advanced Parameters
|
||||
|
||||
`MemoryZoneConfig` or what is known as `--memory-zone` from the CLI perspective
|
||||
is a power user parameter. It allows for a full description of the guest RAM,
|
||||
describing how every memory region is backed and exposed to the guest.
|
||||
|
||||
```rust
|
||||
struct MemoryZoneConfig {
|
||||
id: String,
|
||||
size: u64,
|
||||
file: Option<PathBuf>,
|
||||
shared: bool,
|
||||
hugepages: bool,
|
||||
host_numa_node: Option<u32>,
|
||||
hotplug_size: Option<u64>,
|
||||
hotplugged_size: Option<u64>,
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
--memory-zone <memory-zone> User defined memory zone parameters "size=<guest_memory_region_size>,file=<backing_file>,shared=on|off,hugepages=on|off,host_numa_node=<node_id>,id=<zone_identifier>,hotplug_size=<hotpluggable_memory_size>,hotplugged_size=<hotplugged_memory_size>"
|
||||
```
|
||||
|
||||
This parameter expects one or more occurences, allowing for a list of memory
|
||||
zones to be defined. It must be used with `--memory size=0`, clearly indicating
|
||||
that the memory will be described through advanced parameters.
|
||||
|
||||
Each zone is given a list of options which we detail through the following
|
||||
sections.
|
||||
|
||||
### `id`
|
||||
|
||||
Memory zone identifier. This identifier must be unique, otherwise an error will
|
||||
be returned.
|
||||
|
||||
This option is useful when referring to a memory zone previously created. In
|
||||
particular, the `--numa` parameter can associate a memory zone to a specific
|
||||
NUMA node based on the memory zone identifier.
|
||||
|
||||
This option is mandatory when using the `--memory-zone` parameter.
|
||||
|
||||
Value is a string.
|
||||
|
||||
_Example_
|
||||
|
||||
```
|
||||
--memory size=0
|
||||
--memory-zone id=mem0,size=1G
|
||||
```
|
||||
|
||||
### `size`
|
||||
|
||||
Size of the memory zone.
|
||||
|
||||
This option is mandatory when using the `--memory-zone` parameter.
|
||||
|
||||
Value is an unsigned integer of 64 bits.
|
||||
|
||||
_Example_
|
||||
|
||||
```
|
||||
--memory size=0
|
||||
--memory-zone id=mem0,size=1G
|
||||
```
|
||||
|
||||
### `file`
|
||||
|
||||
Path to the file backing the memory zone. This can be either a file or a
|
||||
directory. In case of a file, it will be opened and used as the backing file
|
||||
for the `mmap(2)` operation. In case of a directory, a temporary file with no
|
||||
hard link on the filesystem will be created. This file will be used as the
|
||||
backing file for the `mmap(2)` operation.
|
||||
|
||||
This option can be particularly useful when trying to back a part of the guest
|
||||
RAM with a well known file. In the context of the snapshot/restore feature, and
|
||||
if the provided path is a file, the snapshot operation will not perform any
|
||||
copy of the guest RAM content for this specific memory zone since the user has
|
||||
access to it and it would duplicate data already stored on the current
|
||||
filesystem.
|
||||
|
||||
Value is a string.
|
||||
|
||||
_Example_
|
||||
|
||||
```
|
||||
--memory size=0
|
||||
--memory-zone id=mem0,size=1G,file=/foo/bar
|
||||
```
|
||||
|
||||
### `shared`
|
||||
|
||||
Specifies if the memory zone must be `mmap(2)` with `MAP_SHARED` flag.
|
||||
|
||||
By sharing a memory zone mapping, one can share part of the guest RAM with
|
||||
other processes running on the host. One can use this option when running
|
||||
vhost-user devices as part of the VM device model, as they will be driven
|
||||
by standalone daemons needing access to the guest RAM content.
|
||||
|
||||
By default this option is turned off, which result in performing `mmap(2)`
|
||||
with `MAP_PRIVATE` flag.
|
||||
|
||||
_Example_
|
||||
|
||||
```
|
||||
--memory size=0
|
||||
--memory-zone id=mem0,size=1G,shared=on
|
||||
```
|
||||
|
||||
### `hugepages`
|
||||
|
||||
Specifies if the memory zone must be `mmap(2)` with `MAP_HUGETLB` and
|
||||
`MAP_HUGE_2MB` flags. This performs a memory zone mapping relying on 2MiB
|
||||
pages instead of the default 4kiB pages.
|
||||
|
||||
By using hugepages, one can improve the overall performance of the VM, assuming
|
||||
the guest will allocate hugepages as well. Another interesting use case is VFIO
|
||||
as it speeds up the VM's boot time since the amount of IOMMU mappings are
|
||||
reduced.
|
||||
|
||||
By default this option is turned off.
|
||||
|
||||
_Example_
|
||||
|
||||
```
|
||||
--memory size=0
|
||||
--memory-zone id=mem0,size=1G,hugepages=on
|
||||
```
|
||||
|
||||
### `host_numa_node`
|
||||
|
||||
Node identifier of a node present on the host. This option will let the user
|
||||
pick a specific NUMA node from which the memory must be allocated. After the
|
||||
memory zone is `mmap(2)`, the NUMA policy for this memory mapping will be
|
||||
applied through `mbind(2)`, relying on the provided node identifier. If the
|
||||
node does not exist on the host, the call to `mbind(2)` will fail.
|
||||
|
||||
This option is useful when trying to back a VM memory with a specific type of
|
||||
memory from the host. Assuming a host has two types of memory, with one slower
|
||||
than the other, each related to a distinct NUMA node, one could create a VM
|
||||
with slower memory accesses by backing the entire guest RAM from the furthest
|
||||
NUMA node on the host.
|
||||
|
||||
This option also gives the opportunity to create a VM with non uniform memory
|
||||
accesses as one could define a first memory zone backed by fast memory, and a
|
||||
second memory zone backed by slow memory.
|
||||
|
||||
Value is an unsigned integer of 32 bits.
|
||||
|
||||
_Example_
|
||||
|
||||
```
|
||||
--memory size=0
|
||||
--memory-zone id=mem0,size=1G,host_numa_node=0
|
||||
```
|
||||
|
||||
### `hotplug_size`
|
||||
|
||||
Amount of memory that can be dynamically added to the memory zone. Since
|
||||
`virtio-mem` is the only way of resizing a memory zone, one must specify
|
||||
the `hotplug_method=virtio-mem` to the `--memory` parameter.
|
||||
|
||||
Value is an unsigned integer of 64 bits. A value of 0 is invalid.
|
||||
|
||||
_Example_
|
||||
|
||||
```
|
||||
--memory size=0,hotplug_method=virtio-mem
|
||||
--memory-zone id=mem0,size=1G,hotplug_size=1G
|
||||
```
|
||||
|
||||
### `hotplugged_size`
|
||||
|
||||
Amount of memory that will be dynamically added to a memory zone at VM's boot.
|
||||
This option allows for starting a VM with a certain amount of memory that can
|
||||
be reduced during runtime.
|
||||
|
||||
This is only valid when the `hotplug_method` is `virtio-mem` as it does not
|
||||
make sense for the `acpi` use case. When using ACPI, the memory can't be
|
||||
resized after it has been extended.
|
||||
|
||||
This option is only valid when `hotplug_size` is specified, and its value can't
|
||||
exceed the value of `hotplug_size`.
|
||||
|
||||
Value is an unsigned integer of 64 bits. A value of 0 is invalid.
|
||||
|
||||
_Example_
|
||||
|
||||
```
|
||||
--memory size=0,hotplug_method=virtio-mem
|
||||
--memory-zone id=mem0,size=1G,hotplug_size=1G,hotplugged_size=512M
|
||||
```
|
||||
|
||||
## NUMA settings
|
||||
|
||||
`NumaConfig` or what is known as `--numa` from the CLI perspective has been
|
||||
introduced to define a guest NUMA topology. It allows for a fine description
|
||||
about the CPUs and memory ranges associated with each NUMA node. Additionally
|
||||
it allows for specifying the distance between each NUMA node.
|
||||
|
||||
```rust
|
||||
struct NumaConfig {
|
||||
id: u32,
|
||||
cpus: Option<Vec<u8>>,
|
||||
distances: Option<Vec<NumaDistance>>,
|
||||
memory_zones: Option<Vec<String>>,
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
--numa <numa> Settings related to a given NUMA node "id=<node_id>,cpus=<cpus_id>,distances=<list_of_distances_to_destination_nodes>,memory_zones=<list_of_memory_zones>"
|
||||
```
|
||||
|
||||
### `guest_numa_id`
|
||||
|
||||
Node identifier of a guest NUMA node. This identifier must be unique, otherwise
|
||||
an error will be returned.
|
||||
|
||||
This option is mandatory when using the `--numa` parameter.
|
||||
|
||||
Value is an unsigned integer of 32 bits.
|
||||
|
||||
_Example_
|
||||
|
||||
```
|
||||
--numa guest_numa_id=0
|
||||
```
|
||||
|
||||
### `cpus`
|
||||
|
||||
List of virtual CPUs attached to the guest NUMA node identified by the
|
||||
`guest_numa_id` option. This allows for describing a list of CPUs which
|
||||
must be seen by the guest as belonging to the NUMA node `guest_numa_id`.
|
||||
|
||||
One can use this option for a fine grained description of the NUMA topology
|
||||
regarding the CPUs associated with it, which might help the guest run more
|
||||
efficiently.
|
||||
|
||||
Multiple values can be provided to define the list. Each value is an unsigned
|
||||
integer of 8 bits.
|
||||
|
||||
For instance, if one needs to attach all CPUs from 0 to 4 to a specific node,
|
||||
the syntax using `-` will help define a contiguous range with `cpus=0-4`. The
|
||||
same example could also be described with `cpus=0:1:2:3:4`.
|
||||
|
||||
A combination of both `-` and `:` separators is useful when one might need to
|
||||
describe a list containing all CPUs from 0 to 99 and the CPU 255, as it could
|
||||
simply be described with `cpus=0-99:255`.
|
||||
|
||||
_Example_
|
||||
|
||||
```
|
||||
--cpus boot=8
|
||||
--numa guest_numa_id=0,cpus=1-3:7
|
||||
--numa guest_numa_id=1,cpus=0:4-6
|
||||
```
|
||||
|
||||
### `distances`
|
||||
|
||||
List of distances between the current NUMA node referred by `guest_numa_id`
|
||||
and the destination NUMA nodes listed along with distances. This option let
|
||||
the user choose the distances between guest NUMA nodes. This is important to
|
||||
provide an accurate description of the way non uniform memory accesses will
|
||||
perform in the guest.
|
||||
|
||||
One or more tuple of two values must be provided through this option. The first
|
||||
value is an unsigned integer of 32 bits as it represents the destination NUMA
|
||||
node. The second value is an unsigned integer of 8 bits as it represents the
|
||||
distance between the current NUMA node and the destination NUMA node. The two
|
||||
values are separated by `@` (`value1@value2`), meaning the destination NUMA
|
||||
node `value1` is located at a distance of `value2`. Each tuple is separated
|
||||
from the others with `:` separator.
|
||||
|
||||
For instance, if one wants to define 3 NUMA nodes, with each node located at
|
||||
different distances, it can be described with the following example.
|
||||
|
||||
_Example_
|
||||
|
||||
```
|
||||
--numa guest_numa_id=0,distances=1@15:2@25
|
||||
--numa guest_numa_id=1,distances=0@15:2@20
|
||||
--numa guest_numa_id=2,distances=0@25:1@20
|
||||
```
|
||||
|
||||
### `memory_zones`
|
||||
|
||||
List of memory zones attached to the guest NUMA node identified by the
|
||||
`guest_numa_id` option. This allows for describing a list of memory ranges
|
||||
which must be seen by the guest as belonging to the NUMA node `guest_numa_id`.
|
||||
|
||||
This option can be very useful and powerful when combined with `host_numa_node`
|
||||
option from `--memory-zone` parameter as it allows for creating a VM with non
|
||||
uniform memory accesses, and let the guest know about it. It allows for
|
||||
exposing memory zones through different NUMA nodes, which can help the guest
|
||||
workload run more efficiently.
|
||||
|
||||
Multiple values can be provided to define the list. Each value is a string
|
||||
referring to an existing memory zone identifier. Values are separated from
|
||||
each other with the `:` separator.
|
||||
|
||||
_Example_
|
||||
|
||||
```
|
||||
--memory size=0
|
||||
--memory-zone id=mem0,size=1G
|
||||
--memory-zone id=mem1,size=1G
|
||||
--memory-zone id=mem2,size=1G
|
||||
--numa guest_numa_id=0,memory_zones=mem0:mem2
|
||||
--numa guest_numa_id=1,memory_zones=mem1
|
||||
```
|
||||
@@ -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,shared=true \
|
||||
--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,shared=true \
|
||||
--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,shared=true \
|
||||
--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.77"
|
||||
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,323 +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),
|
||||
///
|
||||
/// Enabling HyperV SynIC error
|
||||
///
|
||||
#[error("Failed to enable HyperV SynIC")]
|
||||
EnableHyperVSynIC(#[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,
|
||||
Hyperv,
|
||||
}
|
||||
|
||||
///
|
||||
/// 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 enable HyperV SynIC
|
||||
///
|
||||
fn enable_hyperv_synic(&self) -> 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,112 +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),
|
||||
///
|
||||
/// CpuId error
|
||||
///
|
||||
#[error("Failed to get cpuid: {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),
|
||||
///
|
||||
/// API version is not compatible
|
||||
///
|
||||
#[error("Incompatible API version")]
|
||||
IncompatibleApiVersion,
|
||||
}
|
||||
|
||||
///
|
||||
/// 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>>;
|
||||
///
|
||||
/// 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,989 +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")]
|
||||
pub use crate::aarch64::{check_required_kvm_extensions, VcpuInit, VcpuKvmState as CpuState};
|
||||
use crate::cpu;
|
||||
use crate::device;
|
||||
use crate::hypervisor;
|
||||
use crate::vm;
|
||||
use kvm_ioctls::{NoDatamatch, VcpuFd, VmFd};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
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;
|
||||
// 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_HYPERV_SYNIC, 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,
|
||||
};
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)]
|
||||
pub struct KvmVmState {}
|
||||
|
||||
pub use KvmVmState as VmState;
|
||||
/// Wrapper over KVM VM ioctls.
|
||||
pub struct KvmVm {
|
||||
fd: Arc<VmFd>,
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
msrs: MsrEntries,
|
||||
state: KvmVmState,
|
||||
}
|
||||
|
||||
// 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()))
|
||||
}
|
||||
///
|
||||
/// Get the Vm state. Return VM specific data
|
||||
///
|
||||
fn state(&self) -> vm::Result<VmState> {
|
||||
Ok(self.state)
|
||||
}
|
||||
///
|
||||
/// Set the VM state
|
||||
///
|
||||
fn set_state(&self, _state: &VmState) -> vm::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
/// 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()))?;
|
||||
let api_version = kvm_obj.get_api_version();
|
||||
|
||||
if api_version != kvm_bindings::KVM_API_VERSION as i32 {
|
||||
return Err(hypervisor::HypervisorError::IncompatibleApiVersion);
|
||||
}
|
||||
|
||||
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,
|
||||
state: VmState {},
|
||||
}))
|
||||
}
|
||||
|
||||
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
|
||||
{
|
||||
Ok(Arc::new(KvmVm {
|
||||
fd: vm_fd,
|
||||
state: VmState {},
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
fn check_required_extensions(&self) -> hypervisor::Result<()> {
|
||||
check_required_kvm_extensions(&self.kvm).expect("Missing KVM capabilities");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
///
|
||||
/// 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()))
|
||||
}
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// X86 specific call to enable HyperV SynIC
|
||||
///
|
||||
fn enable_hyperv_synic(&self) -> cpu::Result<()> {
|
||||
let mut cap: kvm_enable_cap = Default::default();
|
||||
cap.cap = KVM_CAP_HYPERV_SYNIC;
|
||||
self.fd
|
||||
.enable_cap(&cap)
|
||||
.map_err(|e| cpu::HypervisorCpuError::EnableHyperVSynIC(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)),
|
||||
VcpuExit::Hyperv => Ok(cpu::VmExit::Hyperv),
|
||||
|
||||
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<()> {
|
||||
warn!("CPU state was not restored");
|
||||
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,61 +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,187 +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::KvmVmState as VmState;
|
||||
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>>;
|
||||
/// Get the Vm state. Return VM specific data
|
||||
fn state(&self) -> Result<VmState>;
|
||||
/// Set the VM state
|
||||
fn set_state(&self, state: &VmState) -> Result<()>;
|
||||
}
|
||||
@@ -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.77"
|
||||
log = "0.4.11"
|
||||
net_gen = { path = "../net_gen" }
|
||||
libc = "0.2.66"
|
||||
rand = "0.7.3"
|
||||
serde = "1.0.116"
|
||||
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,267 +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);
|
||||
|
||||
#[derive(Debug)]
|
||||
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
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct IntegerList(pub Vec<u64>);
|
||||
|
||||
pub enum IntegerListParseError {
|
||||
InvalidValue(String),
|
||||
}
|
||||
|
||||
impl FromStr for IntegerList {
|
||||
type Err = IntegerListParseError;
|
||||
|
||||
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
|
||||
let mut integer_list = Vec::new();
|
||||
let ranges_list: Vec<&str> = s.trim().split(':').collect();
|
||||
|
||||
for range in ranges_list.iter() {
|
||||
let items: Vec<&str> = range.split('-').collect();
|
||||
|
||||
if items.len() > 2 {
|
||||
return Err(IntegerListParseError::InvalidValue(range.to_string()));
|
||||
}
|
||||
|
||||
let start_range = items[0]
|
||||
.parse::<u64>()
|
||||
.map_err(|_| IntegerListParseError::InvalidValue(items[0].to_owned()))?;
|
||||
|
||||
integer_list.push(start_range);
|
||||
|
||||
if items.len() == 2 {
|
||||
let end_range = items[1]
|
||||
.parse::<u64>()
|
||||
.map_err(|_| IntegerListParseError::InvalidValue(items[1].to_owned()))?;
|
||||
if start_range >= end_range {
|
||||
return Err(IntegerListParseError::InvalidValue(range.to_string()));
|
||||
}
|
||||
|
||||
for i in start_range..end_range {
|
||||
integer_list.push(i + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(IntegerList(integer_list))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TupleTwoIntegers(pub Vec<(u64, u64)>);
|
||||
|
||||
pub enum TupleTwoIntegersParseError {
|
||||
InvalidValue(String),
|
||||
}
|
||||
|
||||
impl FromStr for TupleTwoIntegers {
|
||||
type Err = TupleTwoIntegersParseError;
|
||||
|
||||
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
|
||||
let mut list = Vec::new();
|
||||
let tuples_list: Vec<&str> = s.trim().split(':').collect();
|
||||
|
||||
for tuple in tuples_list.iter() {
|
||||
let items: Vec<&str> = tuple.split('@').collect();
|
||||
|
||||
if items.len() != 2 {
|
||||
return Err(TupleTwoIntegersParseError::InvalidValue(tuple.to_string()));
|
||||
}
|
||||
|
||||
let item1 = items[0]
|
||||
.parse::<u64>()
|
||||
.map_err(|_| TupleTwoIntegersParseError::InvalidValue(items[0].to_owned()))?;
|
||||
let item2 = items[1]
|
||||
.parse::<u64>()
|
||||
.map_err(|_| TupleTwoIntegersParseError::InvalidValue(items[1].to_owned()))?;
|
||||
|
||||
list.push((item1, item2));
|
||||
}
|
||||
|
||||
Ok(TupleTwoIntegers(list))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct StringList(pub Vec<String>);
|
||||
|
||||
pub enum StringListParseError {
|
||||
InvalidValue(String),
|
||||
}
|
||||
|
||||
impl FromStr for StringList {
|
||||
type Err = StringListParseError;
|
||||
|
||||
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
|
||||
let string_list: Vec<String> = s.trim().split(':').map(|e| e.to_owned()).collect();
|
||||
|
||||
Ok(StringList(string_list))
|
||||
}
|
||||
}
|
||||
@@ -5,21 +5,10 @@ authors = ["Samuel Ortiz <sameo@linux.intel.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
byteorder = "1.3.4"
|
||||
hypervisor = { path = "../hypervisor" }
|
||||
vfio-ioctls = { git = "https://github.com/cloud-hypervisor/vfio-ioctls", branch = "ch" }
|
||||
vmm-sys-util = ">=0.3.1"
|
||||
libc = "0.2.77"
|
||||
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" }
|
||||
byteorder = "1.3.4"
|
||||
devices = { path = "../devices" }
|
||||
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" }
|
||||
|
||||
156
pci/src/bus.rs
156
pci/src/bus.rs
@@ -7,16 +7,15 @@ 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 vm_device::{Bus, BusDevice};
|
||||
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)]
|
||||
@@ -26,15 +25,9 @@ pub enum PciRootError {
|
||||
/// Could not allocate an IRQ number.
|
||||
AllocateIrq,
|
||||
/// Could not add a device to the port io bus.
|
||||
PioInsert(vm_device::BusError),
|
||||
PioInsert(devices::BusError),
|
||||
/// Could not add a device to the mmio bus.
|
||||
MmioInsert(vm_device::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),
|
||||
MmioInsert(devices::BusError),
|
||||
}
|
||||
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: &Bus,
|
||||
mmio_bus: &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(&mut 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),
|
||||
|
||||
7
pci/src/device.rs
Normal file → Executable file
7
pci/src/device.rs
Normal file → Executable file
@@ -3,11 +3,11 @@
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
|
||||
use crate::configuration::{self, PciBarRegionType};
|
||||
use devices::BusDevice;
|
||||
use std::any::Any;
|
||||
use std::fmt::{self, Display};
|
||||
use std::{self, io, result};
|
||||
use vm_allocator::SystemAllocator;
|
||||
use vm_device::BusDevice;
|
||||
use vm_memory::{GuestAddress, GuestUsize};
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -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.
|
||||
|
||||
@@ -5,11 +5,7 @@
|
||||
//! Implements pci devices and busses.
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
extern crate hypervisor;
|
||||
extern crate serde;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
extern crate serde_json;
|
||||
extern crate devices;
|
||||
extern crate vm_memory;
|
||||
|
||||
mod bus;
|
||||
@@ -17,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::{
|
||||
@@ -30,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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
124
pci/src/msix.rs
124
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
|
||||
}
|
||||
@@ -158,7 +95,7 @@ impl MsixConfig {
|
||||
self.masked = ((reg >> FUNCTION_MASK_BIT) & 1u16) == 1u16;
|
||||
self.enabled = ((reg >> MSIX_ENABLE_BIT) & 1u16) == 1u16;
|
||||
|
||||
// Update interrupt routing
|
||||
// Update KVM routes
|
||||
if old_masked != self.masked || old_enabled != self.enabled {
|
||||
if self.enabled && !self.masked {
|
||||
for (idx, table_entry) in self.table_entries.iter().enumerate() {
|
||||
@@ -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(&mut 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.77"
|
||||
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,
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user