Compare commits

..

8 Commits
v35.1 ... v23.1

Author SHA1 Message Date
Rob Bradford
479fef1b8e build: Release v23.1 (bug fix release)
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2022-05-09 14:54:18 +01:00
Bo Chen
fc8f867879 vmm: Add 'shutdown()' to vCPU seccomp filter
This is required when hot-removing a vfio-user device. Details code path
below:

Thread 6 "vcpu0" received signal SIGSYS, Bad system call.
[Switching to Thread 0x7f8196889700 (LWP 2358305)]
0x00007f8196dae7ab in shutdown () at ../sysdeps/unix/syscall-template.S:78
78	T_PSEUDO (SYSCALL_SYMBOL, SYSCALL_NAME, SYSCALL_NARGS)
(gdb) bt
  0x00007f8196dae7ab in shutdown () at ../sysdeps/unix/syscall-template.S:78
  0x000056189240737d in std::sys::unix::net::Socket::shutdown ()
    at library/std/src/sys/unix/net.rs:383
  std::os::unix::net::stream::UnixStream::shutdown () at library/std/src/os/unix/net/stream.rs:479
  0x000056189210e23d in vfio_user::Client::shutdown (self=0x7f8190014300)
    at vfio_user/src/lib.rs:787
  0x00005618920b9d02 in <pci::vfio_user::VfioUserPciDevice as core::ops::drop::Drop>::drop (
    self=0x7f819002d7c0) at pci/src/vfio_user.rs:551
  0x00005618920b8787 in core::ptr::drop_in_place<pci::vfio_user::VfioUserPciDevice> ()
    at /rustc/7737e0b5c4103216d6fd8cf941b7ab9bdbaace7c/library/core/src/ptr/mod.rs:188
  0x00005618920b92e3 in core::ptr::drop_in_place<core::cell::UnsafeCell<dyn pci::device::PciDevice>>
    () at /rustc/7737e0b5c4103216d6fd8cf941b7ab9bdbaace7c/library/core/src/ptr/mod.rs:188
  0x00005618920b9362 in core::ptr::drop_in_place<std::sync::mutex::Mutex<dyn pci::device::PciDevice>> () at /rustc/7737e0b5c4103216d6fd8cf941b7ab9bdbaace7c/library/core/src/ptr/mod.rs:188
  0x00005618920d8a3e in alloc::sync::Arc<T>::drop_slow (self=0x7f81968852b8)
    at /rustc/7737e0b5c4103216d6fd8cf941b7ab9bdbaace7c/library/alloc/src/sync.rs:1092
  0x00005618920ba273 in <alloc::sync::Arc<T> as core::ops::drop::Drop>::drop (self=0x7f81968852b8)
    at /rustc/7737e0b5c4103216d6fd8cf941b7ab9bdbaace7c/library/alloc/src/sync.rs:1688
 0x00005618920b76fb in core::ptr::drop_in_place<alloc::sync::Arc<std::sync::mutex::Mutex<dyn pci::device::PciDevice>>> ()
    at /rustc/7737e0b5c4103216d6fd8cf941b7ab9bdbaace7c/library/core/src/ptr/mod.rs:188
 0x0000561891b5e47d in vmm::device_manager::DeviceManager::eject_device (self=0x7f8190009600,
    pci_segment_id=0, device_id=3) at vmm/src/device_manager.rs:4000
 0x0000561891b674bc in <vmm::device_manager::DeviceManager as vm_device::bus::BusDevice>::write (
    self=0x7f8190009600, base=70368744108032, offset=8, data=&[u8](size=4) = {...})
    at vmm/src/device_manager.rs:4625
 0x00005618921927d5 in vm_device::bus::Bus::write (self=0x7f8190006e00, addr=70368744108040,
    data=&[u8](size=4) = {...}) at vm-device/src/bus.rs:235
 0x0000561891b72e10 in <vmm::vm::VmOps as hypervisor::vm::VmmOps>::mmio_write (
    self=0x7f81900097b0, gpa=70368744108040, data=&[u8](size=4) = {...}) at vmm/src/vm.rs:378
 0x0000561892133ae2 in <hypervisor::kvm::KvmVcpu as hypervisor::cpu::Vcpu>::run (
    self=0x7f8190013c90) at hypervisor/src/kvm/mod.rs:1114
 0x0000561891914e85 in vmm::cpu::Vcpu::run (self=0x7f819001b230) at vmm/src/cpu.rs:348
 0x000056189189f2cb in vmm::cpu::CpuManager::start_vcpu::{{closure}}::{{closure}} ()
    at vmm/src/cpu.rs:953

Signed-off-by: Bo Chen <chen.bo@intel.com>
(cherry picked from commit 42c19e14c5)
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2022-05-09 14:54:18 +01:00
Sebastien Boeuf
e2eb59bcc8 vmm: Remove FsConfig from VmConfig when unplugging fs device
All hotpluggable devices were properly removed from the VmConfig when a
remove-device command was issued, except for the "fs" type. Fix this
lack of support as it is causing the integration tests to fail with the
recent addition of verifying that identifiers are unique.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
(cherry picked from commit a5a2e591c9)
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2022-05-09 14:54:18 +01:00
LiHui
1e557fdb3c vmm: api: Do not delete the API socket on API server creation
The socket will safely deleted on shutdown and so it is not necessary to
delete the API socket when starting the HTTP server.

Fixes: #4026

Signed-off-by: LiHui <andrewli@kubesphere.io>
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
(cherry picked from commit ec0c1b01c4)
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2022-05-09 14:54:18 +01:00
Rob Bradford
10ce348aa6 tests: Use different API sockets when restoring
This prevents a conflict since the old API socket will not have been
cleaned up (due to the use of SIGKILL.)

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
(cherry picked from commit 4fed2d4ed7)
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2022-05-09 14:54:18 +01:00
Rob Bradford
63130d0f92 vmm: seccomp: Allow SYS_rseq as required by newer glibc
glibc 2.35 as shipped by Fedora 36 now uses the rseq syscall.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
(cherry picked from commit 4a04d1f8f2)
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2022-05-09 14:54:18 +01:00
Rob Bradford
e6cc364703 virtio-devices: mem: Reject resize if device not activated by guest
If the guest has not activated the virtio-mem device then reject an
attempt to resize using it.

Fixes: #4001

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
(cherry picked from commit c274ce4d49)
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2022-05-09 14:54:18 +01:00
Fabiano Fidêncio
a861918f06 docs: Fix the name of the I/O operations knobs
The I/O operations knobs are prefixed `ops_` rather than `bw_`, as `bw_`
refers to the "bandwidth" knobs.

Signed-off-by: Fabiano Fidêncio <fabiano.fidencio@intel.com>
(cherry picked from commit a87d1bbaa1)
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2022-05-09 14:54:18 +01:00
309 changed files with 22439 additions and 37456 deletions

View File

@@ -13,7 +13,7 @@ jobs:
- stable
- beta
- nightly
- "1.64"
- 1.56
target:
- x86_64-unknown-linux-gnu
- x86_64-unknown-linux-musl
@@ -29,36 +29,35 @@ jobs:
- name: Install Rust toolchain (${{ matrix.rust }})
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
override: true
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
override: true
- name: Debug Check (default features)
run: |
git rev-list origin/main..$GITHUB_SHA | xargs -t -I % sh -c 'git checkout %; cargo check --tests --all --target=${{ matrix.target }}'
git checkout $GITHUB_SHA
- name: Build (default features)
run: cargo rustc --locked --bin cloud-hypervisor -- -D warnings -D clippy::undocumented_unsafe_blocks
run: cargo rustc --bin cloud-hypervisor -- -D warnings
- name: Build (kvm)
run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks
- name: Build (common + kvm)
run: cargo rustc --bin cloud-hypervisor --no-default-features --features "common,kvm" -- -D warnings
- name: Build (default features + tdx)
run: cargo rustc --locked --bin cloud-hypervisor --features "tdx" -- -D warnings -D clippy::undocumented_unsafe_blocks
run: cargo rustc --bin cloud-hypervisor --features "tdx" -- -D warnings
- name: Build (default features + dbus_api)
run: cargo rustc --locked --bin cloud-hypervisor --features "dbus_api" -- -D warnings -D clippy::undocumented_unsafe_blocks
- name: Build (default features + amx)
run: cargo rustc --bin cloud-hypervisor --features "amx" -- -D warnings
- name: Build (default features + guest_debug)
run: cargo rustc --locked --bin cloud-hypervisor --features "guest_debug" -- -D warnings -D clippy::undocumented_unsafe_blocks
- name: Build (default features + gdb)
run: cargo rustc --bin cloud-hypervisor --features "gdb" -- -D warnings
- name: Build (mshv)
run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "mshv" -- -D warnings -D clippy::undocumented_unsafe_blocks
- name: Build (sev_snp)
run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "sev_snp" -- -D warnings -D clippy::undocumented_unsafe_blocks
- name: Build (mshv + kvm)
run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "mshv,kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks
- name: Build (common + mshv)
run: cargo rustc --bin cloud-hypervisor --no-default-features --features "common,mshv" -- -D warnings
- name: Release Build (default features)
run: cargo build --locked --all --release --target=${{ matrix.target }}
run: cargo build --all --release --target=${{ matrix.target }}
- name: Check build did not modify any files
run: test -z "$(git status --porcelain)"

View File

@@ -7,10 +7,6 @@ on:
pull_request:
paths: resources/Dockerfile
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
main:
runs-on: ubuntu-latest
@@ -24,19 +20,19 @@ jobs:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to ghcr
uses: docker/login-action@v2
- name: Login to DockerHub
if: ${{ github.event_name == 'push' }}
uses: docker/login-action@v1
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Docker meta
id: meta
uses: docker/metadata-action@v4
uses: docker/metadata-action@v3
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
# list of Docker images to use as base name for tags
images: cloudhypervisor/dev
# generate Docker tags based on the following events/attributes
tags: |
type=raw,value={{date 'YYYYMMDD'}}-0

View File

@@ -1,26 +0,0 @@
name: Lint Dockerfile
on:
push:
paths:
- resources/Dockerfile
pull_request:
paths:
- resources/Dockerfile
jobs:
hadolint:
name: Run Hadolint Dockerfile Linter
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Lint Dockerfile
uses: hadolint/hadolint-action@master
with:
dockerfile: ./resources/Dockerfile
format: tty
no-fail: false
verbose: true
failure-threshold: info

47
.github/workflows/quality-aarch64.yaml vendored Normal file
View File

@@ -0,0 +1,47 @@
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
continue-on-error: ${{ matrix.experimental }}
strategy:
fail-fast: false
matrix:
rust:
- stable
target:
- aarch64-unknown-linux-gnu
experimental: [false]
include:
- rust: beta
target: aarch64-unknown-linux-gnu
experimental: true
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 (common + kvm)
uses: actions-rs/cargo@v1
with:
use-cross: true
command: clippy
args: --target=${{ matrix.target }} --tests --all --no-default-features --features "common,kvm" -- -D warnings
- name: Clippy (default features)
uses: actions-rs/cargo@v1
with:
use-cross: true
command: clippy
args: --target=${{ matrix.target }} --tests --all -- -D warnings

View File

@@ -13,31 +13,15 @@ jobs:
rust:
- stable
target:
- aarch64-unknown-linux-gnu
- aarch64-unknown-linux-musl
- x86_64-unknown-linux-gnu
- x86_64-unknown-linux-musl
experimental: [false]
include:
- rust: beta
target: aarch64-unknown-linux-gnu
experimental: true
- rust: beta
target: aarch64-unknown-linux-musl
experimental: true
- rust: beta
target: x86_64-unknown-linux-gnu
experimental: true
- rust: beta
target: x86_64-unknown-linux-musl
experimental: true
steps:
- name: Code checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
uses: actions/checkout@v2
- name: Install Rust toolchain (${{ matrix.rust }})
uses: actions-rs/toolchain@v1
with:
@@ -46,85 +30,23 @@ jobs:
override: true
components: rustfmt, clippy
- name: Debug Check (default features)
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
run: |
set -e
commits=$(git rev-list origin/${{ github.base_ref }}..${{ github.sha }})
for commit in $commits; do git checkout $commit; cargo check --tests --examples --all --target=${{ matrix.target }}; done
git checkout ${{ github.sha }}
- name: Formatting (rustfmt)
run: cargo fmt -- --check
- name: Clippy (kvm)
uses: actions-rs/cargo@v1
with:
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
command: clippy
args: --target=${{ matrix.target }} --locked --all --all-targets --no-default-features --tests --examples --features "kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks
- name: Clippy (common + kvm)
run: cargo clippy --all --all-targets --no-default-features --tests --features "common,kvm" -- -D warnings
- name: Clippy (default features)
uses: actions-rs/cargo@v1
with:
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
command: clippy
args: --target=${{ matrix.target }} --locked --all --all-targets --tests --examples -- -D warnings -D clippy::undocumented_unsafe_blocks
run: cargo clippy --all --all-targets --tests -- -D warnings
- name: Clippy (default features + guest_debug)
uses: actions-rs/cargo@v1
with:
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
command: clippy
args: --target=${{ matrix.target }} --locked --all --all-targets --tests --examples --features "guest_debug" -- -D warnings -D clippy::undocumented_unsafe_blocks
- name: Clippy (default features + amx)
run: cargo clippy --all --all-targets --tests --features "amx" -- -D warnings
- name: Clippy (default features + tracing)
uses: actions-rs/cargo@v1
with:
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
command: clippy
args: --target=${{ matrix.target }} --locked --all --all-targets --tests --examples --features "tracing" -- -D warnings -D clippy::undocumented_unsafe_blocks
- name: Clippy (default features + gdb)
run: cargo clippy --all --all-targets --tests --features "gdb" -- -D warnings
- name: Clippy (mshv)
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
uses: actions-rs/cargo@v1
with:
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
command: clippy
args: --target=${{ matrix.target }} --locked --all --all-targets --no-default-features --tests --examples --features "mshv" -- -D warnings -D clippy::undocumented_unsafe_blocks
- name: Clippy (mshv + kvm)
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
uses: actions-rs/cargo@v1
with:
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
command: clippy
args: --target=${{ matrix.target }} --locked --all --all-targets --no-default-features --tests --examples --features "mshv,kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks
- name: Clippy (sev_snp)
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
uses: actions-rs/cargo@v1
with:
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
command: clippy
args: --target=${{ matrix.target }} --locked --all --all-targets --no-default-features --tests --examples --features "sev_snp" -- -D warnings -D clippy::undocumented_unsafe_blocks
- name: Clippy (kvm + tdx)
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
uses: actions-rs/cargo@v1
with:
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
command: clippy
args: --target=${{ matrix.target }} --locked --all --all-targets --no-default-features --tests --examples --features "tdx,kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks
- name: Clippy (common + mshv)
run: cargo clippy --all --all-targets --no-default-features --tests --features "common,mshv" -- -D warnings
- name: Check build did not modify any files
run: test -z "$(git status --porcelain)"
typos:
if: github.event_name == 'pull_request'
name: Typos / Spellcheck
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
# Executes "typos ."
- uses: crate-ci/typos@v1.16.11

View File

@@ -1,48 +1,62 @@
name: Cloud Hypervisor Release
on: [pull_request, create]
on: [create]
jobs:
release:
if: (github.event_name == 'create' && github.event.ref_type == 'tag') || github.event_name == 'pull_request'
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 musl-gcc
run: sudo apt install -y musl-tools
run: sudo apt install -y musl-tools
- name: Create release directory
run: rsync -rv --exclude=.git . ../cloud-hypervisor-${{ github.event.ref }}
- name: Install Rust toolchain (x86_64-unknown-linux-gnu)
uses: actions-rs/toolchain@v1
with:
toolchain: "1.67.1"
target: x86_64-unknown-linux-gnu
toolchain: 1.58
target: x86_64-unknown-linux-gnu
- name: Install Rust toolchain (x86_64-unknown-linux-musl)
uses: actions-rs/toolchain@v1
with:
toolchain: "1.67.1"
target: x86_64-unknown-linux-musl
toolchain: 1.58
target: x86_64-unknown-linux-musl
- name: Build
uses: actions-rs/cargo@v1
with:
toolchain: "1.67.1"
command: build
args: --all --release --features mshv --target=x86_64-unknown-linux-gnu
toolchain: 1.58
command: build
args: --all --release --target=x86_64-unknown-linux-gnu
- name: Static Build
uses: actions-rs/cargo@v1
with:
toolchain: "1.67.1"
command: build
args: --all --release --features mshv --target=x86_64-unknown-linux-musl
toolchain: 1.58
command: build
args: --all --release --target=x86_64-unknown-linux-musl
- name: Strip cloud-hypervisor binaries
run: strip target/*/release/cloud-hypervisor
- name: Install Rust toolchain (aarch64-unknown-linux-musl)
uses: actions-rs/toolchain@v1
with:
toolchain: "1.67.1"
target: aarch64-unknown-linux-musl
override: true
toolchain: 1.58
target: aarch64-unknown-linux-musl
override: true
- name: Static Build (AArch64)
uses: actions-rs/cargo@v1
with:
use-cross: true
command: build
args: --all --release --target=aarch64-unknown-linux-musl
- name: Vendor
working-directory: ../cloud-hypervisor-${{ github.event.ref }}
run: |
mkdir ../vendor-cargo-home
export CARGO_HOME=$(realpath ../vendor-cargo-home)
mkdir .cargo
cargo vendor > .cargo/config.toml
- name: Create Release
if: github.event_name == 'create' && github.event.ref_type == 'tag'
id: create_release
uses: actions/create-release@v1
env:
@@ -52,8 +66,20 @@ jobs:
release_name: ${{ github.ref }}
draft: true
prerelease: true
- name: Create vendored source archive
working-directory: ../
run: tar cJf cloud-hypervisor-${{ github.event.ref }}.tar.xz cloud-hypervisor-${{ github.event.ref }}
- name: Upload cloud-hypervisor vendored source archive
id: upload-release-cloud-hypervisor-vendored-sources
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ../cloud-hypervisor-${{ github.event.ref }}.tar.xz
asset_name: cloud-hypervisor-${{ github.event.ref }}.tar.xz
asset_content_type: application/x-xz
- name: Upload cloud-hypervisor
if: github.event_name == 'create' && github.event.ref_type == 'tag'
id: upload-release-cloud-hypervisor
uses: actions/upload-release-asset@v1
env:
@@ -64,7 +90,6 @@ jobs:
asset_name: cloud-hypervisor
asset_content_type: application/octet-stream
- name: Upload static cloud-hypervisor
if: github.event_name == 'create' && github.event.ref_type == 'tag'
id: upload-release-static-cloud-hypervisor
uses: actions/upload-release-asset@v1
env:
@@ -75,7 +100,6 @@ jobs:
asset_name: cloud-hypervisor-static
asset_content_type: application/octet-stream
- name: Upload ch-remote
if: github.event_name == 'create' && github.event.ref_type == 'tag'
id: upload-release-ch-remote
uses: actions/upload-release-asset@v1
env:
@@ -86,7 +110,6 @@ jobs:
asset_name: ch-remote
asset_content_type: application/octet-stream
- name: Upload static-ch-remote
if: github.event_name == 'create' && github.event.ref_type == 'tag'
id: upload-release-static-ch-remote
uses: actions/upload-release-asset@v1
env:
@@ -96,18 +119,7 @@ jobs:
asset_path: target/x86_64-unknown-linux-musl/release/ch-remote
asset_name: ch-remote-static
asset_content_type: application/octet-stream
- name: Clean build tree ahead of cross build
uses: actions-rs/cargo@v1
with:
command: clean
- name: Static Build (AArch64)
uses: actions-rs/cargo@v1
with:
use-cross: true
command: build
args: --all --release --target=aarch64-unknown-linux-musl
- name: Upload static AArch64 cloud-hypervisor
if: github.event_name == 'create' && github.event.ref_type == 'tag'
id: upload-release-static-aarch64-cloud-hypervisor
uses: actions/upload-release-asset@v1
env:
@@ -118,7 +130,6 @@ jobs:
asset_name: cloud-hypervisor-static-aarch64
asset_content_type: application/octet-stream
- name: Upload static AArch64 ch-remote
if: github.event_name == 'create' && github.event.ref_type == 'tag'
id: upload-release-static-aarch64-ch-remote
uses: actions/upload-release-asset@v1
env:
@@ -128,24 +139,3 @@ jobs:
asset_path: target/aarch64-unknown-linux-musl/release/ch-remote
asset_name: ch-remote-static-aarch64
asset_content_type: application/octet-stream
- name: Vendor
working-directory: ../cloud-hypervisor-${{ github.event.ref }}
run: |
mkdir ../vendor-cargo-home
export CARGO_HOME=$(realpath ../vendor-cargo-home)
mkdir .cargo
cargo vendor > .cargo/config.toml
- name: Create vendored source archive
working-directory: ../
run: tar cJf cloud-hypervisor-${{ github.event.ref }}.tar.xz cloud-hypervisor-${{ github.event.ref }}
- name: Upload cloud-hypervisor vendored source archive
if: github.event_name == 'create' && github.event.ref_type == 'tag'
id: upload-release-cloud-hypervisor-vendored-sources
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ../cloud-hypervisor-${{ github.event.ref }}.tar.xz
asset_name: cloud-hypervisor-${{ github.event.ref }}.tar.xz
asset_content_type: application/x-xz

2
.gitignore vendored
View File

@@ -4,5 +4,3 @@
**/*.rs.bk
**/Cargo.lock
**/rusty-tags.vi
/rpm/SOURCES
/.vscode

View File

@@ -1,21 +0,0 @@
# Configuration for https://github.com/crate-ci/typos
[files]
extend-exclude = [
"hypervisor/src/kvm/x86_64/mod.rs",
"resources/linux-config-*",
]
[default.extend-words]
ba = "ba"
CLASSE = "CLASSE"
conectix = "conectix"
Dake = "Dake"
EXTINT = "EXTINT"
INOUT = "INOUT"
liness = "liness"
outout = "outout"
[default.extend-identifiers]
fo = "fo"
fpr = "fpr"

View File

@@ -1,2 +0,0 @@
# Add the list of code owners here (using their GitHub username)
* @cloud-hypervisor/cloud-hypervisor-reviewers

View File

@@ -2,14 +2,12 @@
Cloud Hypervisor is an open source project licensed under the [Apache v2
License](https://opensource.org/licenses/Apache-2.0) and the [BSD 3
Clause](https://opensource.org/licenses/BSD-3-Clause) license. Individual files
contain details of their licensing and changes to that file are under the same
license unless the contribution changes the license of the file. When importing
code from a third party project (e.g. Firecracker or crosvm) please respect the
license of those projects.
New code should be under the [Apache v2
License](https://opensource.org/licenses/Apache-2.0).
Clause](https://opensource.org/licenses/BSD-3-Clause) license. Contributions
can be made under either license or both. Individual files contain details of
their licensing and changes to that file are under the same license unless the
contribution changes the license of the file. When importing code from a third
party project (e.g. Firecracker or CrosVM) please respect the license of those
projects.
## Coding Style
@@ -17,23 +15,6 @@ We follow the [Rust Style](https://github.com/rust-dev-tools/fmt-rfcs/blob/maste
convention and enforce it through the Continuous Integration (CI) process calling into `rustfmt`
for each submitted Pull Request (PR).
## Basic Checks
Please consider creating the following hook as `.git/hooks/pre-commit` in order
to ensure basic correctness of your code. You can extend this further if you
have specific features that you regularly develop against.
```sh
#!/bin/sh
cargo fmt -- --check || exit 1
cargo check --locked --all --all-targets --tests || exit 1
cargo clippy --locked --all --all-targets --tests -- -D warnings || exit 1
```
You will need to `chmod +x .git/hooks/pre-commit` to have it run on every
commit you make.
## Certificate of Origin
In order to get a clear contribution chain of trust we use the [signed-off-by language](https://01.org/community/signed-process)
@@ -82,9 +63,11 @@ you want to merge your changes to `cloud-hypervisor`:
2. Within your fork, create a branch for your contribution.
3. [Create a pull request](https://help.github.com/articles/creating-a-pull-request-from-a-fork/)
against the main branch of the Cloud Hypervisor repository.
4. To update your pull request amend existing commits whenever applicable and
4. Add reviewers to your pull request and then work with your reviewers to address
any comments and obtain minimum of 2 [maintainers](MAINTAINERS.md) approvals.
To update your pull request amend existing commits whenever applicable and
then push the new changes to your pull request branch.
5. Once the pull request is approved it can be integrated.
5. Once the pull request is approved, one of the maintainers will merge it.
## Issue tracking

1959
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[package]
name = "cloud-hypervisor"
version = "35.1.0"
version = "23.1.0"
authors = ["The Cloud Hypervisor Authors"]
edition = "2021"
default-run = "cloud-hypervisor"
@@ -10,77 +10,64 @@ description = "Open source Virtual Machine Monitor (VMM) that runs on top of KVM
homepage = "https://github.com/cloud-hypervisor/cloud-hypervisor"
# Minimum buildable version:
# Keep in sync with version in .github/workflows/build.yaml
# Policy on MSRV (see #4318):
# Can only be bumped by:
# a.) A dependency requires it,
# b.) If we want to use a new feature and that MSRV is at least 6 months old,
# c.) There is a security issue that is addressed by the toolchain update.
rust-version = "1.64"
rust-version = "1.56"
[profile.release]
lto = true
codegen-units = 1
opt-level = "s"
strip = true
[profile.profiling]
inherits = "release"
strip = false
debug = true
[dependencies]
anyhow = "1.0.75"
anyhow = "1.0.56"
api_client = { path = "api_client" }
argh = "0.1.9"
dhat = { version = "0.3.2", optional = true }
epoll = "4.3.3"
clap = { version = "3.1.8", features = ["wrap_help","cargo"] }
epoll = "4.3.1"
event_monitor = { path = "event_monitor" }
hypervisor = { path = "hypervisor" }
libc = "0.2.147"
log = { version = "0.4.17", features = ["std"] }
libc = "0.2.123"
log = { version = "0.4.16", features = ["std"] }
option_parser = { path = "option_parser" }
seccompiler = "0.3.0"
serde_json = "1.0.107"
signal-hook = "0.3.17"
thiserror = "1.0.40"
tpm = { path = "tpm"}
tracer = { path = "tracer" }
seccompiler = "0.2.0"
serde_json = "1.0.79"
signal-hook = "0.3.13"
thiserror = "1.0.30"
vmm = { path = "vmm" }
vmm-sys-util = "0.11.0"
vm-memory = "0.12.2"
zbus = { version = "3.11.1", optional = true }
vmm-sys-util = "0.9.0"
vm-memory = "0.7.0"
[build-dependencies]
clap = { version = "3.1.8", features = ["cargo"] }
# List of patched crates
[patch.crates-io]
kvm-bindings = { git = "https://github.com/cloud-hypervisor/kvm-bindings", branch = "ch-v0.6.0-tdx" }
kvm-bindings = { git = "https://github.com/cloud-hypervisor/kvm-bindings", branch = "ch-v0.5.0-tdx" }
kvm-ioctls = { git = "https://github.com/rust-vmm/kvm-ioctls", branch = "main" }
versionize_derive = { git = "https://github.com/cloud-hypervisor/versionize_derive", branch = "ch" }
[dev-dependencies]
dirs = "5.0.0"
dirs = "4.0.0"
lazy_static= "1.4.0"
net_util = { path = "net_util" }
once_cell = "1.18.0"
serde_json = "1.0.107"
serde_json = "1.0.79"
test_infra = { path = "test_infra" }
wait-timeout = "0.2.0"
[features]
default = ["kvm", "io_uring"]
dbus_api = ["zbus", "vmm/dbus_api"]
dhat-heap = ["dhat"] # For heap profiling
guest_debug = ["vmm/guest_debug"]
io_uring = ["vmm/io_uring"]
default = ["common", "kvm"]
# Common features for all hypervisors
common = ["fwdebug"]
amx = ["vmm/amx"]
cmos = ["vmm/cmos"]
fwdebug = ["vmm/fwdebug"]
gdb = ["vmm/gdb"]
kvm = ["vmm/kvm"]
mshv = ["vmm/mshv"]
sev_snp = ["vmm/sev_snp", "mshv"]
tdx = ["vmm/tdx"]
tracing = ["vmm/tracing", "tracer/tracing"]
[workspace]
members = [
"acpi_tables",
"api_client",
"arch",
"block",
"block_util",
"devices",
"event_monitor",
"hypervisor",
@@ -89,10 +76,11 @@ members = [
"option_parser",
"pci",
"performance-metrics",
"qcow",
"rate_limiter",
"serial_buffer",
"test_infra",
"tracer",
"vfio_user",
"vhdx",
"vhost_user_block",
"vhost_user_net",
"virtio-devices",

883
Jenkinsfile vendored
View File

@@ -1,509 +1,404 @@
def runWorkers = true
pipeline {
agent none
options {
timeout(time: 4, unit: 'HOURS')
}
stages {
stage('Early checks') {
agent { node { label 'built-in' } }
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Check if worker build can be skipped') {
when {
expression {
return skipWorkerBuild()
}
}
steps {
script {
runWorkers = false
echo 'No changes requiring a build'
}
}
}
stage('Check for RFC/WIP builds') {
when {
changeRequest comparator: 'REGEXP', title: '.*(rfc|RFC|wip|WIP).*'
beforeAgent true
}
steps {
error('Failing as this is marked as a WIP or RFC PR.')
}
}
stage('Cancel older builds') {
when { not { branch 'main' } }
steps {
cancelPreviousBuilds()
}
}
}
}
stage('Build') {
parallel {
stage('Worker build') {
agent { node { label 'jammy' } }
when {
beforeAgent true
expression {
return runWorkers
}
}
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Prepare environment') {
steps {
sh 'scripts/prepare_vdpa.sh'
}
}
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') {
options {
timeout(time: 1, unit: 'HOURS')
}
steps {
sh 'sudo modprobe openvswitch'
sh 'scripts/dev_cli.sh tests --integration'
}
}
stage('Run live-migration integration tests') {
options {
timeout(time: 1, unit: 'HOURS')
}
steps {
sh 'sudo modprobe openvswitch'
sh 'scripts/dev_cli.sh tests --integration-live-migration'
}
}
stage('Run unit tests for musl') {
steps {
sh 'scripts/dev_cli.sh tests --unit --libc musl'
}
}
stage('Run integration tests for musl') {
options {
timeout(time: 1, unit: 'HOURS')
}
steps {
sh 'sudo modprobe openvswitch'
sh 'scripts/dev_cli.sh tests --integration --libc musl'
}
}
stage('Run live-migration integration tests for musl') {
options {
timeout(time: 1, unit: 'HOURS')
}
steps {
sh 'sudo modprobe openvswitch'
sh 'scripts/dev_cli.sh tests --integration-live-migration --libc musl'
}
}
}
}
stage('Worker build - AMD') {
agent { node { label 'jammy-amd' } }
when {
beforeAgent true
expression {
return runWorkers
}
}
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Prepare environment') {
steps {
sh 'scripts/prepare_vdpa.sh'
}
}
stage('Run integration tests') {
options {
timeout(time: 1, unit: 'HOURS')
}
steps {
sh 'sudo modprobe openvswitch'
sh 'scripts/dev_cli.sh tests --integration -- -- --skip common_parallel::test_vfio'
}
}
stage('Run live-migration integration tests') {
options {
timeout(time: 1, unit: 'HOURS')
}
steps {
sh 'sudo modprobe openvswitch'
sh 'scripts/dev_cli.sh tests --integration-live-migration'
}
}
stage('Run integration tests for musl') {
options {
timeout(time: 1, unit: 'HOURS')
}
steps {
sh 'sudo modprobe openvswitch'
sh 'scripts/dev_cli.sh tests --integration --libc musl -- -- --skip common_parallel::test_vfio'
}
}
stage('Run live-migration integration tests for musl') {
options {
timeout(time: 1, unit: 'HOURS')
}
steps {
sh 'sudo modprobe openvswitch'
sh 'scripts/dev_cli.sh tests --integration-live-migration --libc musl'
}
}
}
}
stage('AArch64 worker build') {
agent { node { label 'bionic-arm64' } }
when {
beforeAgent true
expression {
return runWorkers
}
}
environment {
AZURE_CONNECTION_STRING = credentials('46b4e7d6-315f-4cc1-8333-b58780863b9b')
}
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Run unit tests') {
steps {
sh 'scripts/dev_cli.sh tests --unit --libc musl'
}
}
stage('Run integration tests') {
options {
timeout(time: 1, unit: 'HOURS')
}
steps {
sh 'sudo modprobe openvswitch'
sh 'scripts/dev_cli.sh tests --integration --libc musl'
}
}
stage('Install azure-cli') {
steps {
installAzureCli('focal', 'arm64')
}
}
stage('Download Windows image') {
steps {
sh '''#!/bin/bash -x
IMG_BASENAME=windows-11-iot-enterprise-aarch64.raw
IMG_PATH=$HOME/workloads/$IMG_BASENAME
IMG_GZ_PATH=$HOME/workloads/$IMG_BASENAME.gz
IMG_GZ_BLOB_NAME=windows-11-iot-enterprise-aarch64-9-min.raw.gz
cp "scripts/$IMG_BASENAME.sha1" "$HOME/workloads/"
pushd "$HOME/workloads"
if sha1sum "$IMG_BASENAME.sha1" --check; then
exit
fi
popd
mkdir -p "$HOME/workloads"
az storage blob download \
--container-name private-images \
--file "$IMG_GZ_PATH" \
--name "$IMG_GZ_BLOB_NAME" \
--connection-string "$AZURE_CONNECTION_STRING"
gzip -d $IMG_GZ_PATH
'''
}
}
stage('Run Windows guest integration tests') {
options {
timeout(time: 1, unit: 'HOURS')
}
steps {
sh 'scripts/dev_cli.sh tests --integration-windows --libc musl'
}
}
}
post {
always {
sh "sudo chown -R jenkins.jenkins ${WORKSPACE}"
deleteDir()
}
}
}
stage('Worker build - Windows guest') {
agent { node { label 'jammy' } }
when {
beforeAgent true
expression {
return runWorkers
}
}
environment {
AZURE_CONNECTION_STRING = credentials('46b4e7d6-315f-4cc1-8333-b58780863b9b')
}
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Install azure-cli') {
steps {
installAzureCli('jammy', 'amd64')
}
}
stage('Download assets') {
steps {
sh "mkdir ${env.HOME}/workloads"
sh 'az storage blob download --container-name private-images --file "$HOME/workloads/windows-server-2022-amd64-2.raw" --name windows-server-2022-amd64-2.raw --connection-string "$AZURE_CONNECTION_STRING"'
}
}
stage('Run Windows guest integration tests') {
options {
timeout(time: 1, unit: 'HOURS')
}
steps {
sh 'scripts/dev_cli.sh tests --integration-windows'
}
}
stage('Run Windows guest integration tests for musl') {
options {
timeout(time: 1, unit: 'HOURS')
}
steps {
sh 'scripts/dev_cli.sh tests --integration-windows --libc musl'
}
}
}
}
stage('Worker build - Metrics') {
agent { node { label 'jammy-metrics' } }
when {
branch 'main'
beforeAgent true
expression {
return runWorkers
}
}
environment {
METRICS_PUBLISH_KEY = credentials('52e0945f-ce7a-43d1-87af-67d1d87cc40f')
}
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Run metrics tests') {
options {
timeout(time: 1, unit: 'HOURS')
}
steps {
sh 'scripts/dev_cli.sh tests --metrics -- -- --report-file /root/workloads/metrics.json'
}
}
stage('Upload metrics report') {
steps {
sh 'curl -X PUT https://cloud-hypervisor-metrics.azurewebsites.net/api/publishmetrics -H "x-functions-key: $METRICS_PUBLISH_KEY" -T ~/workloads/metrics.json'
}
}
}
}
stage('Worker build - Rate Limiter') {
agent { node { label 'focal-metrics' } }
when {
branch 'main'
beforeAgent true
expression {
return runWorkers
}
}
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Run rate-limiter integration tests') {
options {
timeout(time: 10, unit: 'MINUTES')
}
steps {
sh 'scripts/dev_cli.sh tests --integration-rate-limiter'
}
}
}
}
stage('Worker build - SGX') {
agent { node { label 'jammy-sgx' } }
when {
beforeAgent true
allOf {
branch 'main'
expression {
return runWorkers
}
}
}
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Run SGX integration tests') {
options {
timeout(time: 1, unit: 'HOURS')
}
steps {
sh 'scripts/dev_cli.sh tests --integration-sgx'
}
}
stage('Run SGX integration tests for musl') {
options {
timeout(time: 1, unit: 'HOURS')
}
steps {
sh 'scripts/dev_cli.sh tests --integration-sgx --libc musl'
}
}
}
post {
always {
sh "sudo chown -R jenkins.jenkins ${WORKSPACE}"
deleteDir()
}
}
}
stage('Worker build - VFIO') {
agent { node { label 'jammy-vfio' } }
when {
beforeAgent true
allOf {
branch 'main'
expression {
return runWorkers
}
}
}
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Run VFIO integration tests') {
options {
timeout(time: 1, unit: 'HOURS')
}
steps {
sh 'scripts/dev_cli.sh tests --integration-vfio'
}
}
stage('Run VFIO integration tests for musl') {
options {
timeout(time: 1, unit: 'HOURS')
}
steps {
sh 'scripts/dev_cli.sh tests --integration-vfio --libc musl'
}
}
}
post {
always {
sh "sudo chown -R jenkins.jenkins ${WORKSPACE}"
deleteDir()
}
}
}
}
}
}
post {
regression {
script {
if (env.BRANCH_NAME == 'main') {
slackSend(color: '#ff0000', message: '"main" branch build is now failing', channel: '#jenkins-ci')
}
}
}
fixed {
script {
if (env.BRANCH_NAME == 'main') {
slackSend(color: '#00ff00', message: '"main" branch build is now fixed', channel: '#jenkins-ci')
}
}
}
}
pipeline{
agent none
stages {
stage ('Early checks') {
agent { node { label 'built-in' } }
stages {
stage ('Checkout') {
steps {
checkout scm
}
}
stage ('Check for documentation only changes') {
when {
expression {
return docsFileOnly()
}
}
steps {
script {
runWorkers = false
echo "Documentation only changes, no need to run the CI"
}
}
}
stage ('Check for RFC/WIP builds') {
when {
changeRequest comparator: 'REGEXP', title: '.*(rfc|RFC|wip|WIP).*'
beforeAgent true
}
steps {
error("Failing as this is marked as a WIP or RFC PR.")
}
}
stage ('Cancel older builds') {
when { not { branch 'main' } }
steps {
cancelPreviousBuilds()
}
}
}
}
stage ('Build') {
parallel {
stage ('Worker build') {
agent { node { label 'focal' } }
when {
beforeAgent true
expression {
return runWorkers
}
}
stages {
stage ('Checkout') {
steps {
checkout scm
}
}
stage ('Prepare environment') {
steps {
sh "scripts/prepare_vdpa.sh"
}
}
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') {
options {
timeout(time: 1, unit: 'HOURS')
}
steps {
sh "sudo modprobe openvswitch"
sh "scripts/dev_cli.sh tests --integration"
}
}
}
}
stage ('AArch64 worker build') {
agent { node { label 'bionic-arm64' } }
when {
beforeAgent true
expression {
return runWorkers
}
}
stages {
stage ('Checkout') {
steps {
checkout scm
}
}
stage ('Run unit tests') {
steps {
sh "scripts/dev_cli.sh tests --unit --libc musl"
}
}
stage ('Run integration tests') {
options {
timeout(time: 1, unit: 'HOURS')
}
steps {
sh "sudo modprobe openvswitch"
sh "scripts/dev_cli.sh tests --integration --libc musl"
}
}
}
post {
always {
sh "sudo chown -R jenkins.jenkins ${WORKSPACE}"
deleteDir()
}
}
}
stage ('Worker build (musl)') {
agent { node { label 'focal' } }
when {
beforeAgent true
expression {
return runWorkers
}
}
stages {
stage ('Checkout') {
steps {
checkout scm
}
}
stage ('Prepare environment') {
steps {
sh "scripts/prepare_vdpa.sh"
}
}
stage ('Run unit tests for musl') {
steps {
sh "scripts/dev_cli.sh tests --unit --libc musl"
}
}
stage ('Run integration tests for musl') {
options {
timeout(time: 1, unit: 'HOURS')
}
steps {
sh "sudo modprobe openvswitch"
sh "scripts/dev_cli.sh tests --integration --libc musl"
}
}
}
}
stage ('Worker build SGX') {
agent { node { label 'bionic-sgx' } }
when {
beforeAgent true
allOf {
branch 'main'
expression {
return runWorkers
}
}
}
stages {
stage ('Checkout') {
steps {
checkout scm
}
}
stage ('Run SGX integration tests') {
options {
timeout(time: 1, unit: 'HOURS')
}
steps {
sh "scripts/dev_cli.sh tests --integration-sgx"
}
}
stage ('Run SGX integration tests for musl') {
options {
timeout(time: 1, unit: 'HOURS')
}
steps {
sh "scripts/dev_cli.sh tests --integration-sgx --libc musl"
}
}
}
post {
always {
sh "sudo chown -R jenkins.jenkins ${WORKSPACE}"
deleteDir()
}
}
}
stage ('Worker build VFIO') {
agent { node { label 'bionic-vfio' } }
when {
beforeAgent true
allOf {
branch 'main'
expression {
return runWorkers
}
}
}
stages {
stage ('Checkout') {
steps {
checkout scm
}
}
stage ('Run VFIO integration tests') {
options {
timeout(time: 1, unit: 'HOURS')
}
steps {
sh "scripts/dev_cli.sh tests --integration-vfio"
}
}
stage ('Run VFIO integration tests for musl') {
options {
timeout(time: 1, unit: 'HOURS')
}
steps {
sh "scripts/dev_cli.sh tests --integration-vfio --libc musl"
}
}
}
post {
always {
sh "sudo chown -R jenkins.jenkins ${WORKSPACE}"
deleteDir()
}
}
}
stage ('Worker build - Windows guest') {
agent { node { label 'focal' } }
when {
beforeAgent true
expression {
return runWorkers
}
}
environment {
AZURE_CONNECTION_STRING = credentials('46b4e7d6-315f-4cc1-8333-b58780863b9b')
}
stages {
stage ('Checkout') {
steps {
checkout scm
}
}
stage ('Install azure-cli') {
steps {
installAzureCli()
}
}
stage ('Download assets') {
steps {
sh "mkdir ${env.HOME}/workloads"
sh 'az storage blob download --container-name private-images --file "$HOME/workloads/windows-server-2019.raw" --name windows-server-2019.raw --connection-string "$AZURE_CONNECTION_STRING"'
}
}
stage ('Run Windows guest integration tests') {
options {
timeout(time: 1, unit: 'HOURS')
}
steps {
sh "scripts/dev_cli.sh tests --integration-windows"
}
}
stage ('Run Windows guest integration tests for musl') {
options {
timeout(time: 1, unit: 'HOURS')
}
steps {
sh "scripts/dev_cli.sh tests --integration-windows --libc musl"
}
}
}
}
stage ('Worker build - Live Migration') {
agent { node { label 'focal-small' } }
when {
beforeAgent true
expression {
return runWorkers
}
}
stages {
stage ('Checkout') {
steps {
checkout scm
}
}
stage ('Run live-migration integration tests') {
options {
timeout(time: 1, unit: 'HOURS')
}
steps {
sh "sudo modprobe openvswitch"
sh "scripts/dev_cli.sh tests --integration-live-migration"
}
}
stage ('Run live-migration integration tests for musl') {
options {
timeout(time: 1, unit: 'HOURS')
}
steps {
sh "sudo modprobe openvswitch"
sh "scripts/dev_cli.sh tests --integration-live-migration --libc musl"
}
}
}
}
stage ('Worker build - Metrics') {
agent { node { label 'focal-metrics' } }
when {
branch 'main'
beforeAgent true
expression {
return runWorkers
}
}
environment {
METRICS_PUBLISH_KEY = credentials('52e0945f-ce7a-43d1-87af-67d1d87cc40f')
}
stages {
stage ('Checkout') {
steps {
checkout scm
}
}
stage ('Run metrics tests') {
options {
timeout(time: 1, unit: 'HOURS')
}
steps {
sh 'scripts/dev_cli.sh tests --metrics -- -- --report-file /root/workloads/metrics.json'
}
}
stage ('Upload metrics report') {
steps {
sh 'curl -X PUT https://cloud-hypervisor-metrics.azurewebsites.net/api/publishmetrics -H "x-functions-key: $METRICS_PUBLISH_KEY" -T ~/workloads/metrics.json'
}
}
}
}
}
}
}
post {
regression {
script {
if (env.BRANCH_NAME == 'main') {
slackSend (color: '#ff0000', message: '"main" branch build is now failing')
}
}
}
fixed {
script {
if (env.BRANCH_NAME == 'main') {
slackSend (color: '#00ff00', message: '"main" branch build is now fixed')
}
}
}
}
}
def cancelPreviousBuilds() {
// Check for other instances of this particular build, cancel any that are older than the current one
def jobName = env.JOB_NAME
def currentBuildNumber = env.BUILD_NUMBER.toInteger()
def currentJob = Jenkins.instance.getItemByFullName(jobName)
// Check for other instances of this particular build, cancel any that are older than the current one
def jobName = env.JOB_NAME
def currentBuildNumber = env.BUILD_NUMBER.toInteger()
def currentJob = Jenkins.instance.getItemByFullName(jobName)
// Loop through all instances of this particular job/branch
for (def build : currentJob.builds) {
if (build.isBuilding() && (build.number.toInteger() < currentBuildNumber)) {
echo "Older build still queued. Sending kill signal to build number: ${build.number}"
build.doStop()
}
}
// Loop through all instances of this particular job/branch
for (def build : currentJob.builds) {
if (build.isBuilding() && (build.number.toInteger() < currentBuildNumber)) {
echo "Older build still queued. Sending kill signal to build number: ${build.number}"
build.doStop()
}
}
}
def installAzureCli(distro, arch) {
sh 'sudo apt install -y ca-certificates curl apt-transport-https lsb-release gnupg'
sh 'curl -sL https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/microsoft.gpg > /dev/null'
sh "echo \"deb [arch=${arch}] https://packages.microsoft.com/repos/azure-cli/ ${distro} main\" | sudo tee /etc/apt/sources.list.d/azure-cli.list"
sh 'sudo apt update'
sh 'sudo apt install -y azure-cli'
def installAzureCli() {
sh "sudo apt install -y ca-certificates curl apt-transport-https lsb-release gnupg"
sh "curl -sL https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/microsoft.gpg > /dev/null"
sh "echo \"deb [arch=amd64] https://packages.microsoft.com/repos/azure-cli/ focal main\" | sudo tee /etc/apt/sources.list.d/azure-cli.list"
sh "sudo apt update"
sh "sudo apt install -y azure-cli"
}
def boolean skipWorkerBuild() {
def boolean docsFileOnly() {
if (env.CHANGE_TARGET == null) {
return false
return false;
}
if (sh(
return sh(
returnStatus: true,
script: "git diff --name-only origin/${env.CHANGE_TARGET}... | grep -v '\\.md'"
) != 0) {
return true
}
if (sh(
returnStatus: true,
script: "git diff --name-only origin/${env.CHANGE_TARGET}... | grep -v -E 'fuzz/'"
) != 0) {
return true
}
if (sh(
returnStatus: true,
script: "git diff --name-only origin/${env.CHANGE_TARGET}... | grep -v -E '.github/'"
) != 0) {
return true
}
return false
) != 0
}

View File

@@ -2,7 +2,6 @@
- Sebastien Boeuf - @sboeuf
- Robert Bradford - @rbradford
- Bo Chen - @likebreath
- Samuel Ortiz - @sameo
- Wei Liu - @liuw
- Michael Zhao - @michael2012z

410
README.md
View File

@@ -4,43 +4,42 @@
- [Architectures](#architectures)
- [Guest OS](#guest-os)
- [2. Getting Started](#2-getting-started)
- [Host OS](#host-os)
- [Use Pre-built Binaries](#use-pre-built-binaries)
- [Packages](#packages)
- [Building from Source](#building-from-source)
- [Booting Linux](#booting-linux)
- [Firmware Booting](#firmware-booting)
- [Custom Kernel and Disk Image](#custom-kernel-and-disk-image)
- [Building your Kernel](#building-your-kernel)
- [Preparation](#preparation)
- [Install prerequisites](#install-prerequisites)
- [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)
- [Roadmap](#roadmap)
- [4. Relationship with _Rust VMM_ Project](#4-relationship-with-rust-vmm-project)
- [Differences with Firecracker and crosvm](#differences-with-firecracker-and-crosvm)
- [TODO](#todo)
- [4. `rust-vmm` project dependency](#4-rust-vmm-project-dependency)
- [Firecracker and crosvm](#firecracker-and-crosvm)
- [5. Community](#5-community)
- [Contribute](#contribute)
- [Slack](#slack)
- [Mailing list](#mailing-list)
- [Join us](#join-us)
- [Security issues](#security-issues)
# 1. What is Cloud Hypervisor?
Cloud Hypervisor is an open source Virtual Machine Monitor (VMM) that runs on
top of the [KVM](https://www.kernel.org/doc/Documentation/virtual/kvm/api.txt)
hypervisor and the Microsoft Hypervisor (MSHV).
top of [KVM](https://www.kernel.org/doc/Documentation/virtual/kvm/api.txt)
hypervisor and Microsoft Hypervisor (MSHV).
The project focuses on running modern, _Cloud Workloads_, on specific, common,
hardware architectures. In this case _Cloud Workloads_ refers to those that are
run by customers inside a Cloud Service Provider. This means modern operating
systems with most I/O handled by
paravirtualised devices (e.g. _virtio_), no requirement for legacy devices, and
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 Hypervisor is implemented in [Rust](https://www.rust-lang.org/) and is
based on the [Rust VMM](https://github.com/rust-vmm) crates.
based on the [rust-vmm](https://github.com/rust-vmm) crates.
## Objectives
@@ -60,7 +59,7 @@ based on the [Rust VMM](https://github.com/rust-vmm) crates.
### Architectures
Cloud Hypervisor supports the `x86-64` and `AArch64` architectures. There are
minor differences in functionality between the two architectures
some small differences in functionality between the two architectures
(see [#1125](https://github.com/cloud-hypervisor/cloud-hypervisor/issues/1125)).
### Guest OS
@@ -69,189 +68,189 @@ Cloud Hypervisor supports `64-bit Linux` and Windows 10/Windows Server 2019.
# 2. Getting Started
The following sections describe how to build and run Cloud Hypervisor.
Below sections describe how to build and run Cloud Hypervisor on the `x86_64`
platform. For getting started on the `AArch64` platform, please refer to the
[Arm64 documentation](docs/arm64.md).
## Prerequisites for AArch64
## Preparation
- AArch64 servers (recommended) or development boards equipped with the GICv3
interrupt controller.
## Host OS
For required KVM functionality and adequate performance the recommended host
kernel version is 5.13. The majority of the CI currently tests with kernel
version 5.15.
## Use Pre-built Binaries
The recommended approach to getting started with Cloud Hypervisor is by using a
pre-built binary. Binaries are available for the [latest
release](https://github.com/cloud-hypervisor/cloud-hypervisor/releases/latest).
Use `cloud-hypervisor-static` for `x86-64` or `cloud-hypervisor-static-aarch64`
for `AArch64` platform.
## Packages
For convenience, packages are also available targeting some popular Linux
distributions. This is thanks to the [Open Build
Service](https://build.opensuse.org). The [OBS
README](https://github.com/cloud-hypervisor/obs-packaging) explains how to
enable the repository in a supported Linux distribution and install Cloud Hypervisor
and accompanying packages. Please report any packaging issues in the
[obs-packaging](https://github.com/cloud-hypervisor/obs-packaging) repository.
## Building from Source
Please see the [instructions for building from source](docs/building.md) if you
do not wish to use the pre-built binaries.
## Booting Linux
Cloud Hypervisor supports direct kernel boot (the x86-64 kernel requires the kernel
built with PVH support) or booting via a firmware (either [Rust Hypervisor
Firmware](https://github.com/cloud-hypervisor/rust-hypervisor-firmware) or an
edk2 UEFI firmware called `CLOUDHV` / `CLOUDHV_EFI`.)
Binary builds of the firmware files are available for the latest release of
[Rust Hypervisor
Firmware](https://github.com/cloud-hypervisor/rust-hypervisor-firmware/releases/latest)
and [our edk2
repository](https://github.com/cloud-hypervisor/edk2/releases/latest)
The choice of firmware depends on your guest OS choice; some experimentation
may be required.
### Firmware Booting
Cloud Hypervisor supports booting disk images containing all needed components
to run cloud workloads, a.k.a. cloud images.
The following sample commands will download an Ubuntu Cloud image, converting
it into a format that Cloud Hypervisor can use and a firmware to boot the image
with.
We create a folder to build and run `cloud-hypervisor` at `$HOME/cloud-hypervisor`
```shell
$ 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.4.2/hypervisor-fw
$ export CLOUDH=$HOME/cloud-hypervisor
$ mkdir $CLOUDH
```
The Ubuntu cloud images do not ship with a default password so it necessary to
use a `cloud-init` disk image to customise the image on the first boot. A basic
`cloud-init` image is generated by this [script](scripts/create-cloud-init.sh).
This seeds the image with a default username/password of `cloud/cloud123`. It
is only necessary to add this disk image on the first boot. Script also assigns
default IP address using `test_data/cloud-init/ubuntu/local/network-config` details
with `--net "mac=12:34:56:78:90:ab,tap="` option. Then the matching mac address
interface will be enabled as per `network-config` details.
## Install prerequisites
You need to install some prerequisite packages in order to build and test Cloud
Hypervisor. Here, all the steps are based on Ubuntu, for other Linux
distributions please replace the package manager and package name.
```shell
$ sudo setcap cap_net_admin+ep ./cloud-hypervisor
$ ./create-cloud-init.sh
$ ./cloud-hypervisor \
# Install git
$ sudo apt install git
# Install rust tool chain
$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Install build-essential
$ sudo apt install build-essential
# If you want to build statically linked binary please add musl target
$ rustup target add x86_64-unknown-linux-musl
```
## Clone and build
First you need to clone and build the cloud-hypervisor repo:
```shell
$ pushd $CLOUDH
$ git clone https://github.com/cloud-hypervisor/cloud-hypervisor.git
$ cd cloud-hypervisor
$ cargo build --release
# We need to give the cloud-hypervisor binary the NET_ADMIN capabilities for it to set TAP interfaces up on the host.
$ sudo setcap cap_net_admin+ep ./target/release/cloud-hypervisor
# If you want to build statically linked binary
$ cargo build --release --target=x86_64-unknown-linux-musl --all
$ popd
```
This will build a `cloud-hypervisor` binary under
`$CLOUDH/cloud-hypervisor/target/release/cloud-hypervisor`.
### Containerized builds and tests
If you want to build and test Cloud Hypervisor without having to install all the
required dependencies (The rust toolchain, cargo tools, etc), you can also use
Cloud Hypervisor's development script: `dev_cli.sh`. Please note that upon its
first invocation, this script will pull a fairly large container image.
For example, to build the Cloud Hypervisor release binary:
```shell
$ pushd $CLOUDH
$ cd cloud-hypervisor
$ ./scripts/dev_cli.sh build --release
```
With `dev_cli.sh`, one can also run the Cloud Hypervisor CI locally. This can be
very convenient for debugging CI errors without having to fully rely on the
Cloud Hypervisor CI infrastructure.
For example, to run the Cloud Hypervisor unit tests:
```shell
$ ./scripts/dev_cli.sh tests --unit
```
Run the `./scripts/dev_cli.sh --help` command to view all the supported
development script commands and their related options.
## Run
You can run a guest VM by either using an existing cloud image or booting into
your own kernel and disk image.
### Cloud image
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:
```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.3.2/hypervisor-fw
$ popd
```
```shell
$ 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=/tmp/ubuntu-cloudinit.img \
--disk path=focal-server-cloudimg-amd64.raw \
--cpus boot=4 \
--memory size=1024M \
--net "tap=,mac=,ip=,mask="
$ popd
```
If access to the firmware messages or interaction with the boot loader (e.g.
GRUB) is required then it necessary to switch to the serial console instead of
`virtio-console`.
Multiple arguments can be given to the `--disk` parameter.
```shell
$ ./cloud-hypervisor \
--kernel ./hypervisor-fw \
--disk path=focal-server-cloudimg-amd64.raw --disk path=/tmp/ubuntu-cloudinit.img \
--cpus boot=4 \
--memory size=1024M \
--net "tap=,mac=,ip=,mask=" \
--serial tty \
--console off
```
### Custom kernel and disk image
### Custom Kernel and Disk Image
#### Building your kernel
#### Building your Kernel
Cloud Hypervisor also supports direct kernel boot. For x86-64, a `vmlinux` ELF kernel (compiled with PVH support) is needed. In order to support development there is a custom branch; however provided the required options are enabled any recent kernel will suffice.
Cloud Hypervisor also supports direct kernel boot into a `vmlinux` ELF kernel.
In order to support virtio-watchdog 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:
```shell
# Clone the Cloud Hypervisor Linux branch
$ git clone --depth 1 https://github.com/cloud-hypervisor/linux.git -b ch-6.2 linux-cloud-hypervisor
$ pushd $CLOUDH
$ git clone --depth 1 https://github.com/cloud-hypervisor/linux.git -b ch-5.15.12 linux-cloud-hypervisor
$ pushd linux-cloud-hypervisor
# Use the x86-64 cloud-hypervisor kernel config to build your kernel for x86-64
$ wget https://raw.githubusercontent.com/cloud-hypervisor/cloud-hypervisor/main/resources/linux-config-x86_64
# Use the AArch64 cloud-hypervisor kernel config to build your kernel for AArch64
$ wget https://raw.githubusercontent.com/cloud-hypervisor/cloud-hypervisor/main/resources/linux-config-aarch64
$ cp linux-config-x86_64 .config # x86-64
$ cp linux-config-aarch64 .config # AArch64
# Do native build of the x86-64 kernel
# Use the cloud-hypervisor kernel config to build your kernel
$ cp $CLOUDH/cloud-hypervisor/resources/linux-config-x86_64 .config
$ KCFLAGS="-Wa,-mx86-used-note=no" make bzImage -j `nproc`
# Do native build of the AArch64 kernel
$ make -j `nproc`
$ popd
```
For x86-64, the `vmlinux` kernel image will then be located at
The `vmlinux` kernel image will then be located at
`linux-cloud-hypervisor/arch/x86/boot/compressed/vmlinux.bin`.
For AArch64, the `Image` kernel image will then be located at
`linux-cloud-hypervisor/arch/arm64/boot/Image`.
#### Disk image
For the disk image the same Ubuntu image as before can be used. This contains
an `ext4` root filesystem.
For the disk image, we will use a Ubuntu cloud image that contains a root
partition:
```shell
$ wget https://cloud-images.ubuntu.com/focal/current/focal-server-cloudimg-amd64.img # x86-64
$ wget https://cloud-images.ubuntu.com/focal/current/focal-server-cloudimg-arm64.img # AArch64
$ qemu-img convert -p -f qcow2 -O raw focal-server-cloudimg-amd64.img focal-server-cloudimg-amd64.raw # x86-64
$ qemu-img convert -p -f qcow2 -O raw focal-server-cloudimg-arm64.img focal-server-cloudimg-arm64.raw # AArch64
$ 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
$ popd
```
#### Booting the guest VM
These sample commands boot the disk image using the custom kernel whilst also
supplying the desired kernel command line.
- x86-64
Now we can directly boot into our custom kernel and make it use the Ubuntu root
partition. If we want to have 4 vCPUs and 1024 MBytes of memory:
```shell
$ sudo setcap cap_net_admin+ep ./cloud-hypervisor
$ ./create-cloud-init.sh
$ ./cloud-hypervisor \
$ 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 --disk path=/tmp/ubuntu-cloudinit.img \
--disk path=focal-server-cloudimg-amd64.raw \
--cmdline "console=hvc0 root=/dev/vda1 rw" \
--cpus boot=4 \
--memory size=1024M \
--net "tap=,mac=,ip=,mask="
```
- AArch64
The above example use the `virtio-console` device as the guest console, and this
device may not be enabled soon enough by the guest kernel to get early kernel
debug messages.
When in need for earlier debug messages, using the legacy serial device based
console is preferred:
```shell
$ sudo setcap cap_net_admin+ep ./cloud-hypervisor
$ ./create-cloud-init.sh
$ ./cloud-hypervisor \
--kernel ./linux-cloud-hypervisor/arch/arm64/boot/Image \
--disk path=focal-server-cloudimg-arm64.raw --disk path=/tmp/ubuntu-cloudinit.img \
--cmdline "console=hvc0 root=/dev/vda1 rw" \
--cpus boot=4 \
--memory size=1024M \
--net "tap=,mac=,ip=,mask="
```
If earlier kernel messages are required the serial console should be used instead of `virtio-console`.
- x86-64
```shell
$ ./cloud-hypervisor \
$ ./cloud-hypervisor/target/release/cloud-hypervisor \
--kernel ./linux-cloud-hypervisor/arch/x86/boot/compressed/vmlinux.bin \
--console off \
--serial tty \
@@ -262,24 +261,10 @@ $ ./cloud-hypervisor \
--net "tap=,mac=,ip=,mask="
```
- AArch64
```shell
$ ./cloud-hypervisor \
--kernel ./linux-cloud-hypervisor/arch/arm64/boot/Image \
--console off \
--serial tty \
--disk path=focal-server-cloudimg-arm64.raw \
--cmdline "console=ttyAMA0 root=/dev/vda1 rw" \
--cpus boot=4 \
--memory size=1024M \
--net "tap=,mac=,ip=,mask="
```
# 3. Status
Cloud Hypervisor is under active development. The following stability
guarantees are currently made:
Cloud Hypervisor is under active development. The following stability guarantees
are currently made:
* The API (including command line options) will not be removed or changed in a
breaking way without a minimum of 2 major releases notice. Where possible
@@ -297,17 +282,14 @@ Currently the following items are **not** guaranteed across updates:
* The following features are considered experimental and may change
substantially between releases: TDX, vfio-user, vDPA.
Further details can be found in the [release documentation](docs/releases.md).
As of 2022-04-05, the following cloud images are supported:
As of 2023-01-03, the following cloud images are supported:
- [Ubuntu Focal](https://cloud-images.ubuntu.com/focal/current/) (focal-server-cloudimg-{amd64,arm64}.img)
- [Ubuntu Jammy](https://cloud-images.ubuntu.com/jammy/current/) (jammy-server-cloudimg-{amd64,arm64}.img )
- [Fedora 36](https://fedora.mirrorservice.org/fedora/linux/releases/36/Cloud/) ([Fedora-Cloud-Base-36-1.5.x86_64.raw.xz](https://fedora.mirrorservice.org/fedora/linux/releases/36/Cloud/x86_64/images/) / [Fedora-Cloud-Base-36-1.5.aarch64.raw.xz](https://fedora.mirrorservice.org/fedora/linux/releases/36/Cloud/aarch64/images/))
- [Ubuntu Bionic](https://cloud-images.ubuntu.com/bionic/current/) (cloudimg)
- [Ubuntu Focal](https://cloud-images.ubuntu.com/focal/current/) (cloudimg)
- [Ubuntu Jammy](https://cloud-images.ubuntu.com/jammy/current/) (cloudimg)
Direct kernel boot to userspace should work with a rootfs from most
distributions although you may need to enable exotic filesystem types in the
reference kernel configuration (e.g. XFS or btrfs.)
distributions.
## Hot Plug
@@ -320,12 +302,14 @@ Cloud Hypervisor supports hotplug of CPUs, passthrough devices (VFIO),
Details of the device model can be found in this
[documentation](docs/device_model.md).
## Roadmap
## TODO
The project roadmap is tracked through a [GitHub
project](https://github.com/orgs/cloud-hypervisor/projects/6).
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. Relationship with _Rust VMM_ Project
# 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
@@ -334,26 +318,39 @@ 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) crates, and sharing code
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's goals, which is to be able
to share and re-use as many virtualization crates as possible.
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:
## Differences with Firecracker and crosvm
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 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 project's implementations. Both of these are VMMs written in Rust
with a focus on safety and security, like Cloud Hypervisor.
the crosvm projects implementations. Both of these are VMMs written in Rust with
a focus on safety and security, like Cloud Hypervisor.
The goal of the Cloud Hypervisor project differs from the aforementioned
projects in that it aims to be a general purpose VMM for _Cloud Workloads_ and
not limited to container/serverless or client workloads.
However we want to emphasize that the Cloud Hypervisor project is neither a fork
nor a reimplementation of any of those projects. The goals and use cases we're
trying to meet are different. We're aiming at supporting cloud workloads, i.e.
those modern, full Linux distribution images currently being run by Cloud
Service Provider (CSP) tenants.
The Cloud Hypervisor community thanks the communities of both the Firecracker
and crosvm projects for their excellent work.
Our primary target is not to support client or serverless use cases, and as such
our code base already diverges from the crosvm and Firecracker ones. As we add
more features to support our use cases, we believe that the divergence will
increase while at the same time sharing as much of the fundamental
virtualization code through the rust-vmm project crates as possible.
# 5. Community
@@ -363,27 +360,20 @@ repository.
## Contribute
The project strongly believes in building a global, diverse and collaborative
community around the Cloud Hypervisor project. Anyone who is interested in
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.
Contributing to a open source project like Cloud Hypervisor covers a lot more
than just sending code. Testing, documentation, pull request
We believe that contributing to a open source project like Cloud Hypervisor
covers a lot more than just sending code. Testing, documentation, pull request
reviews, bug reports, feature requests, project improvement suggestions, etc,
are all equal and welcome means of contribution. See the
[CONTRIBUTING](CONTRIBUTING.md) document for more details.
## Slack
## Join us
Get an [invite to our Slack channel](https://join.slack.com/t/cloud-hypervisor/shared_invite/enQtNjY3MTE3MDkwNDQ4LWQ1MTA1ZDVmODkwMWQ1MTRhYzk4ZGNlN2UwNTI3ZmFlODU0OTcwOWZjMTkwZDExYWE3YjFmNzgzY2FmNDAyMjI),
[join us on Slack](https://cloud-hypervisor.slack.com/), and [participate in our community activities](https://cloud-hypervisor.slack.com/archives/C04R5DUQVBN).
## Mailing list
Please report bugs using the [GitHub issue
tracker](https://github.com/cloud-hypervisor/cloud-hypervisor/issues) but for
broader community discussions you may use our [mailing
list](https://lists.cloudhypervisor.org/g/dev/).
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/).
## Security issues

8
acpi_tables/Cargo.toml Normal file
View File

@@ -0,0 +1,8 @@
[package]
name = "acpi_tables"
version = "0.1.0"
authors = ["The Cloud Hypervisor Authors"]
edition = "2021"
[dependencies]
vm-memory = "0.7.0"

1894
acpi_tables/src/aml.rs Normal file

File diff suppressed because it is too large Load Diff

12
acpi_tables/src/lib.rs Normal file
View File

@@ -0,0 +1,12 @@
// Copyright © 2019 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
pub mod aml;
pub mod rsdp;
pub mod sdt;
fn generate_checksum(data: &[u8]) -> u8 {
(255 - data.iter().fold(0u8, |acc, x| acc.wrapping_add(*x))).wrapping_add(1)
}

68
acpi_tables/src/rsdp.rs Normal file
View File

@@ -0,0 +1,68 @@
// Copyright © 2019 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
use vm_memory::ByteValued;
#[repr(packed)]
#[derive(Clone, Copy, Default)]
pub struct Rsdp {
pub signature: [u8; 8],
pub checksum: u8,
pub oem_id: [u8; 6],
pub revision: u8,
_rsdt_addr: u32,
pub length: u32,
pub xsdt_addr: u64,
pub extended_checksum: u8,
_reserved: [u8; 3],
}
// SAFETY: Rsdp only contains a series of integers
unsafe impl ByteValued for Rsdp {}
impl Rsdp {
pub fn new(oem_id: [u8; 6], xsdt_addr: u64) -> Self {
let mut rsdp = Rsdp {
signature: *b"RSD PTR ",
checksum: 0,
oem_id,
revision: 2,
_rsdt_addr: 0,
length: std::mem::size_of::<Rsdp>() as u32,
xsdt_addr,
extended_checksum: 0,
_reserved: [0; 3],
};
rsdp.checksum = super::generate_checksum(&rsdp.as_slice()[0..19]);
rsdp.extended_checksum = super::generate_checksum(rsdp.as_slice());
rsdp
}
pub fn len() -> usize {
std::mem::size_of::<Rsdp>()
}
}
#[cfg(test)]
mod tests {
use super::Rsdp;
use vm_memory::bytes::ByteValued;
#[test]
fn test_rsdp() {
let rsdp = Rsdp::new(*b"CHYPER", 0xdead_beef);
let sum = rsdp
.as_slice()
.iter()
.fold(0u8, |acc, x| acc.wrapping_add(*x));
assert_eq!(sum, 0);
let sum: u8 = rsdp
.as_slice()
.iter()
.fold(0u8, |acc, x| acc.wrapping_add(*x));
assert_eq!(sum, 0);
}
}

146
acpi_tables/src/sdt.rs Normal file
View File

@@ -0,0 +1,146 @@
// Copyright © 2019 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
#[repr(packed)]
pub struct GenericAddress {
pub address_space_id: u8,
pub register_bit_width: u8,
pub register_bit_offset: u8,
pub access_size: u8,
pub address: u64,
}
impl GenericAddress {
pub fn io_port_address<T>(address: u16) -> Self {
GenericAddress {
address_space_id: 1,
register_bit_width: 8 * std::mem::size_of::<T>() as u8,
register_bit_offset: 0,
access_size: std::mem::size_of::<T>() as u8,
address: u64::from(address),
}
}
pub fn mmio_address<T>(address: u64) -> Self {
GenericAddress {
address_space_id: 0,
register_bit_width: 8 * std::mem::size_of::<T>() as u8,
register_bit_offset: 0,
access_size: std::mem::size_of::<T>() as u8,
address,
}
}
}
pub struct Sdt {
data: Vec<u8>,
}
#[allow(clippy::len_without_is_empty)]
impl Sdt {
pub fn new(
signature: [u8; 4],
length: u32,
revision: u8,
oem_id: [u8; 6],
oem_table: [u8; 8],
oem_revision: u32,
) -> Self {
assert!(length >= 36);
let mut data = Vec::with_capacity(length as usize);
data.extend_from_slice(&signature);
data.extend_from_slice(&length.to_le_bytes());
data.push(revision);
data.push(0); // checksum
data.extend_from_slice(&oem_id);
data.extend_from_slice(&oem_table);
data.extend_from_slice(&oem_revision.to_le_bytes());
data.extend_from_slice(b"CLDH");
data.extend_from_slice(&0u32.to_le_bytes());
assert_eq!(data.len(), 36);
data.resize(length as usize, 0);
let mut sdt = Sdt { data };
sdt.update_checksum();
sdt
}
pub fn update_checksum(&mut self) {
self.data[9] = 0;
let checksum = super::generate_checksum(self.data.as_slice());
self.data[9] = checksum
}
pub fn as_slice(&self) -> &[u8] {
self.data.as_slice()
}
pub fn append<T>(&mut self, value: T) {
let orig_length = self.data.len();
let new_length = orig_length + std::mem::size_of::<T>();
self.data.resize(new_length, 0);
self.write_u32(4, new_length as u32);
self.write(orig_length, value);
}
pub fn append_slice(&mut self, data: &[u8]) {
let orig_length = self.data.len();
let new_length = orig_length + data.len();
self.write_u32(4, new_length as u32);
self.data.extend_from_slice(data);
self.update_checksum();
}
/// Write a value at the given offset
pub fn write<T>(&mut self, offset: usize, value: T) {
assert!((offset + (std::mem::size_of::<T>() - 1)) < self.data.len());
unsafe {
*(((self.data.as_mut_ptr() as usize) + offset) as *mut T) = value;
}
self.update_checksum();
}
pub fn write_u8(&mut self, offset: usize, val: u8) {
self.write(offset, val);
}
pub fn write_u16(&mut self, offset: usize, val: u16) {
self.write(offset, val);
}
pub fn write_u32(&mut self, offset: usize, val: u32) {
self.write(offset, val);
}
pub fn write_u64(&mut self, offset: usize, val: u64) {
self.write(offset, val);
}
pub fn len(&self) -> usize {
self.data.len()
}
}
#[cfg(test)]
mod tests {
use super::Sdt;
#[test]
fn test_sdt() {
let mut sdt = Sdt::new(*b"TEST", 40, 1, *b"CLOUDH", *b"TESTTEST", 1);
let sum: u8 = sdt
.as_slice()
.iter()
.fold(0u8, |acc, x| acc.wrapping_add(*x));
assert_eq!(sum, 0);
sdt.write_u32(36, 0x12345678);
let sum: u8 = sdt
.as_slice()
.iter()
.fold(0u8, |acc, x| acc.wrapping_add(*x));
assert_eq!(sum, 0);
}
}

View File

@@ -5,4 +5,4 @@ authors = ["The Cloud Hypervisor Authors"]
edition = "2021"
[dependencies]
vmm-sys-util = "0.11.0"
vmm-sys-util = "0.9.0"

View File

@@ -22,16 +22,16 @@ impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use Error::*;
match self {
Socket(e) => write!(f, "Error writing to or reading from HTTP socket: {e}"),
SocketSendFds(e) => write!(f, "Error writing to or reading from HTTP socket: {e}"),
StatusCodeParsing(e) => write!(f, "Error parsing HTTP status code: {e}"),
Socket(e) => write!(f, "Error writing to or reading from HTTP socket: {}", e),
SocketSendFds(e) => write!(f, "Error writing to or reading from HTTP socket: {}", e),
StatusCodeParsing(e) => write!(f, "Error parsing HTTP status code: {}", e),
MissingProtocol => write!(f, "HTTP output is missing protocol statement"),
ContentLengthParsing(e) => write!(f, "Error parsing HTTP Content-Length field: {e}"),
ContentLengthParsing(e) => write!(f, "Error parsing HTTP Content-Length field: {}", e),
ServerResponse(s, o) => {
if let Some(o) = o {
write!(f, "Server responded with an error: {s:?}: {o}")
write!(f, "Server responded with an error: {:?}: {}", s, o)
} else {
write!(f, "Server responded with an error: {s:?}")
write!(f, "Server responded with an error: {:?}", s)
}
}
}
@@ -79,7 +79,7 @@ impl StatusCode {
}
fn get_header<'a>(res: &'a str, header: &'a str) -> Option<&'a str> {
let header_str = format!("{header}: ");
let header_str = format!("{}: ", header);
res.find(&header_str)
.map(|o| &res[o + header_str.len()..o + res[o..].find('\r').unwrap()])
}
@@ -101,10 +101,6 @@ fn parse_http_response(socket: &mut dyn Read) -> Result<Option<String>, Error> {
loop {
let mut bytes = vec![0; 256];
let count = socket.read(&mut bytes).map_err(Error::Socket)?;
// If the return value is 0, the peer has performed an orderly shutdown.
if count == 0 {
break;
}
res.push_str(std::str::from_utf8(&bytes[0..count]).unwrap());
// End of headers
@@ -131,7 +127,7 @@ fn parse_http_response(socket: &mut dyn Read) -> Result<Option<String>, Error> {
}
}
}
let body_string = content_length.and(body_offset.map(|o| String::from(&res[o..])));
let body_string = content_length.and(Some(String::from(&res[body_offset.unwrap()..])));
let status_code = get_status_code(&res)?;
if status_code.is_server_error() {
@@ -141,19 +137,18 @@ fn parse_http_response(socket: &mut dyn Read) -> Result<Option<String>, Error> {
}
}
/// Make an API request using the fully qualified command name.
/// For example, full_command could be "vm.create" or "vmm.ping".
pub fn simple_api_full_command_with_fds_and_response<T: Read + Write + ScmSocket>(
pub fn simple_api_command_with_fds<T: Read + Write + ScmSocket>(
socket: &mut T,
method: &str,
full_command: &str,
c: &str,
request_body: Option<&str>,
request_fds: Vec<RawFd>,
) -> Result<Option<String>, Error> {
) -> Result<(), Error> {
socket
.send_with_fds(
&[format!(
"{method} /api/v1/{full_command} HTTP/1.1\r\nHost: localhost\r\nAccept: */*\r\n"
"{} /api/v1/vm.{} HTTP/1.1\r\nHost: localhost\r\nAccept: */*\r\n",
method, c
)
.as_bytes()],
&request_fds,
@@ -176,69 +171,12 @@ pub fn simple_api_full_command_with_fds_and_response<T: Read + Write + ScmSocket
socket.flush().map_err(Error::Socket)?;
parse_http_response(socket)
}
pub fn simple_api_full_command_with_fds<T: Read + Write + ScmSocket>(
socket: &mut T,
method: &str,
full_command: &str,
request_body: Option<&str>,
request_fds: Vec<RawFd>,
) -> Result<(), Error> {
let response = simple_api_full_command_with_fds_and_response(
socket,
method,
full_command,
request_body,
request_fds,
)?;
if response.is_some() {
println!("{}", response.unwrap());
if let Some(body) = parse_http_response(socket)? {
println!("{}", body);
}
Ok(())
}
pub fn simple_api_full_command<T: Read + Write + ScmSocket>(
socket: &mut T,
method: &str,
full_command: &str,
request_body: Option<&str>,
) -> Result<(), Error> {
simple_api_full_command_with_fds(socket, method, full_command, request_body, Vec::new())
}
pub fn simple_api_full_command_and_response<T: Read + Write + ScmSocket>(
socket: &mut T,
method: &str,
full_command: &str,
request_body: Option<&str>,
) -> Result<Option<String>, Error> {
simple_api_full_command_with_fds_and_response(
socket,
method,
full_command,
request_body,
Vec::new(),
)
}
pub fn simple_api_command_with_fds<T: Read + Write + ScmSocket>(
socket: &mut T,
method: &str,
c: &str,
request_body: Option<&str>,
request_fds: Vec<RawFd>,
) -> Result<(), Error> {
// Create the full VM command. For VMM commands, use
// simple_api_full_command().
let full_command = format!("vm.{c}");
simple_api_full_command_with_fds(socket, method, &full_command, request_body, request_fds)
}
pub fn simple_api_command<T: Read + Write + ScmSocket>(
socket: &mut T,
method: &str,

View File

@@ -6,25 +6,25 @@ edition = "2021"
[features]
default = []
sev_snp = []
tdx = []
[dependencies]
anyhow = "1.0.75"
acpi_tables = { path = "../acpi_tables" }
anyhow = "1.0.56"
byteorder = "1.4.3"
hypervisor = { path = "../hypervisor" }
libc = "0.2.147"
linux-loader = { version = "0.9.1", features = ["elf", "bzimage", "pe"] }
log = "0.4.17"
serde = { version = "1.0.168", features = ["rc", "derive"] }
thiserror = "1.0.40"
uuid = "1.3.4"
versionize = "0.1.10"
libc = "0.2.123"
linux-loader = { version = "0.4.0", features = ["elf", "bzimage", "pe"] }
log = "0.4.16"
serde = { version = "1.0.136", features = ["rc"] }
serde_derive = "1.0.136"
thiserror = "1.0.30"
versionize = "0.1.6"
versionize_derive = "0.1.4"
vm-memory = { version = "0.12.2", features = ["backend-mmap", "backend-bitmap"] }
vm-memory = { version = "0.7.0", features = ["backend-mmap", "backend-bitmap"] }
vm-migration = { path = "../vm-migration" }
vmm-sys-util = { version = "0.11.0", features = ["with-serde"] }
vmm-sys-util = { version = "0.9.0", features = ["with-serde"] }
[target.'cfg(target_arch = "aarch64")'.dependencies]
fdt_parser = { version = "0.1.4", package = "fdt" }
fdt_parser = { version = "0.1.3", package = 'fdt'}
vm-fdt = { git = "https://github.com/rust-vmm/vm-fdt", branch = "main" }

View File

@@ -8,24 +8,21 @@
use crate::{NumaNodes, PciSpaceInfo};
use byteorder::{BigEndian, ByteOrder};
use hypervisor::arch::aarch64::gic::Vgic;
use std::cmp;
use std::collections::HashMap;
use std::ffi::CStr;
use std::fmt::Debug;
use std::result;
use std::str;
use std::sync::{Arc, Mutex};
use super::super::DeviceType;
use super::super::GuestMemoryMmap;
use super::super::InitramfsConfig;
use super::gic::GicDevice;
use super::layout::{
IRQ_BASE, MEM_32BIT_DEVICES_SIZE, MEM_32BIT_DEVICES_START, MEM_PCI_IO_SIZE, MEM_PCI_IO_START,
PCI_HIGH_BASE, PCI_MMIO_CONFIG_SIZE_PER_SEGMENT,
};
use std::fs;
use std::path::Path;
use vm_fdt::{FdtWriter, FdtWriterResult};
use vm_memory::{Address, Bytes, GuestMemory, GuestMemoryError, GuestMemoryRegion};
@@ -42,12 +39,8 @@ const VIRTIO_IOMMU_PHANDLE: u32 = 5;
// NOTE: Keep FIRST_VCPU_PHANDLE the last PHANDLE defined.
// This is a value for uniquely identifying the FDT node containing the first vCPU.
// The last number of vCPU phandle depends on the number of vCPUs.
const FIRST_VCPU_PHANDLE: u32 = 8;
const FIRST_VCPU_PHANDLE: u32 = 6;
// This is a value for uniquely identifying the FDT node containing the L2 cache info
const L2_CACHE_PHANDLE: u32 = 6;
// This is a value for uniquely identifying the FDT node containing the L3 cache info
const L3_CACHE_PHANDLE: u32 = 7;
// Read the documentation specified when appending the root node to the FDT.
const ADDRESS_CELLS: u32 = 0x2;
const SIZE_CELLS: u32 = 0x2;
@@ -57,6 +50,8 @@ const SIZE_CELLS: u32 = 0x2;
// Look for "The 1st cell..."
const GIC_FDT_IRQ_TYPE_SPI: u32 = 0;
const GIC_FDT_IRQ_TYPE_PPI: u32 = 1;
const GIC_FDT_IRQ_PPI_CPU_SHIFT: u32 = 8;
const GIC_FDT_IRQ_PPI_CPU_MASK: u32 = 0xff << GIC_FDT_IRQ_PPI_CPU_SHIFT;
// 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;
@@ -87,132 +82,6 @@ pub enum Error {
}
type Result<T> = result::Result<T, Error>;
pub enum CacheLevel {
/// L1 data cache
L1D = 0,
/// L1 instruction cache
L1I = 1,
/// L2 cache
L2 = 2,
/// L3 cache
L3 = 3,
}
/// NOTE: cache size file directory example,
/// "/sys/devices/system/cpu/cpu0/cache/index0/size".
pub fn get_cache_size(cache_level: CacheLevel) -> u32 {
let mut file_directory: String = "/sys/devices/system/cpu/cpu0/cache".to_string();
match cache_level {
CacheLevel::L1D => file_directory += "/index0/size",
CacheLevel::L1I => file_directory += "/index1/size",
CacheLevel::L2 => file_directory += "/index2/size",
CacheLevel::L3 => file_directory += "/index3/size",
}
let file_path = Path::new(&file_directory);
if !file_path.exists() {
warn!("File: {} does not exist.", file_directory);
0
} else {
info!("File: {} exist.", file_directory);
let src = fs::read_to_string(file_directory).expect("File not exists or file corrupted.");
// The content of the file is as simple as a size, like: "32K"
let src = src.trim();
let src_digits: u32 = src[0..src.len() - 1].parse().unwrap();
let src_unit = &src[src.len() - 1..];
src_digits
* match src_unit {
"K" => 1024,
"M" => 1024u32.pow(2),
"G" => 1024u32.pow(3),
_ => 1,
}
}
}
/// NOTE: coherency_line_size file directory example,
/// "/sys/devices/system/cpu/cpu0/cache/index0/coherency_line_size".
pub fn get_cache_coherency_line_size(cache_level: CacheLevel) -> u32 {
let mut file_directory: String = "/sys/devices/system/cpu/cpu0/cache".to_string();
match cache_level {
CacheLevel::L1D => file_directory += "/index0/coherency_line_size",
CacheLevel::L1I => file_directory += "/index1/coherency_line_size",
CacheLevel::L2 => file_directory += "/index2/coherency_line_size",
CacheLevel::L3 => file_directory += "/index3/coherency_line_size",
}
let file_path = Path::new(&file_directory);
if !file_path.exists() {
warn!("File: {} does not exist.", file_directory);
0
} else {
info!("File: {} exist.", file_directory);
let src = fs::read_to_string(file_directory).expect("File not exists or file corrupted.");
src.trim().parse::<u32>().unwrap()
}
}
/// NOTE: number_of_sets file directory example,
/// "/sys/devices/system/cpu/cpu0/cache/index0/number_of_sets".
pub fn get_cache_number_of_sets(cache_level: CacheLevel) -> u32 {
let mut file_directory: String = "/sys/devices/system/cpu/cpu0/cache".to_string();
match cache_level {
CacheLevel::L1D => file_directory += "/index0/number_of_sets",
CacheLevel::L1I => file_directory += "/index1/number_of_sets",
CacheLevel::L2 => file_directory += "/index2/number_of_sets",
CacheLevel::L3 => file_directory += "/index3/number_of_sets",
}
let file_path = Path::new(&file_directory);
if !file_path.exists() {
warn!("File: {} does not exist.", file_directory);
0
} else {
info!("File: {} exist.", file_directory);
let src = fs::read_to_string(file_directory).expect("File not exists or file corrupted.");
src.trim().parse::<u32>().unwrap()
}
}
/// NOTE: shared_cpu_list file directory example,
/// "/sys/devices/system/cpu/cpu0/cache/index0/shared_cpu_list".
pub fn get_cache_shared(cache_level: CacheLevel) -> bool {
let mut file_directory: String = "/sys/devices/system/cpu/cpu0/cache".to_string();
let mut result = true;
match cache_level {
CacheLevel::L1D | CacheLevel::L1I => result = false,
CacheLevel::L2 => file_directory += "/index2/shared_cpu_list",
CacheLevel::L3 => file_directory += "/index3/shared_cpu_list",
}
if !result {
return false;
}
let file_path = Path::new(&file_directory);
if !file_path.exists() {
warn!("File: {} does not exist.", file_directory);
result = false;
} else {
info!("File: {} exist.", file_directory);
let src = fs::read_to_string(file_directory).expect("File not exists or file corrupted.");
let src = src.trim();
if src.is_empty() {
result = false;
} else {
result = src.contains('-') || src.contains(',');
}
}
result
}
/// Creates the flattened device tree for this aarch64 VM.
#[allow(clippy::too_many_arguments)]
pub fn create_fdt<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHasher>(
@@ -221,7 +90,7 @@ pub fn create_fdt<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHash
vcpu_mpidr: Vec<u64>,
vcpu_topology: Option<(u8, u8, u8)>,
device_info: &HashMap<(DeviceType, String), T, S>,
gic_device: &Arc<Mutex<dyn Vgic>>,
gic_device: &dyn GicDevice,
initrd: &Option<InitramfsConfig>,
pci_space_info: &[PciSpaceInfo],
numa_nodes: &NumaNodes,
@@ -251,7 +120,7 @@ pub fn create_fdt<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHash
create_gic_node(&mut fdt, gic_device)?;
create_timer_node(&mut fdt)?;
if pmu_supported {
create_pmu_node(&mut fdt)?;
create_pmu_node(&mut fdt, vcpu_mpidr.len())?;
}
create_clock_node(&mut fdt)?;
create_psci_node(&mut fdt)?;
@@ -290,71 +159,9 @@ fn create_cpu_nodes(
fdt.property_u32("#size-cells", 0x0)?;
let num_cpus = vcpu_mpidr.len();
let (threads_per_core, cores_per_package, packages) = vcpu_topology.unwrap_or((1, 1, 1));
let max_cpus: u32 = (threads_per_core * cores_per_package * packages).into();
// Add cache info.
// L1 Data Cache Info.
let mut l1_d_cache_size: u32 = 0;
let mut l1_d_cache_line_size: u32 = 0;
let mut l1_d_cache_sets: u32 = 0;
// L1 Instruction Cache Info.
let mut l1_i_cache_size: u32 = 0;
let mut l1_i_cache_line_size: u32 = 0;
let mut l1_i_cache_sets: u32 = 0;
// L2 Cache Info.
let mut l2_cache_size: u32 = 0;
let mut l2_cache_line_size: u32 = 0;
let mut l2_cache_sets: u32 = 0;
// L3 Cache Info.
let mut l3_cache_size: u32 = 0;
let mut l3_cache_line_size: u32 = 0;
let mut l3_cache_sets: u32 = 0;
// Cache Shared Info.
let mut l2_cache_shared: bool = false;
let mut l3_cache_shared: bool = false;
let cache_path = Path::new("/sys/devices/system/cpu/cpu0/cache");
let cache_exist: bool = cache_path.exists();
if !cache_exist {
warn!("cache sysfs system does not exist.");
} else {
info!("cache sysfs system exists.");
// L1 Data Cache Info.
l1_d_cache_size = get_cache_size(CacheLevel::L1D);
l1_d_cache_line_size = get_cache_coherency_line_size(CacheLevel::L1D);
l1_d_cache_sets = get_cache_number_of_sets(CacheLevel::L1D);
// L1 Instruction Cache Info.
l1_i_cache_size = get_cache_size(CacheLevel::L1I);
l1_i_cache_line_size = get_cache_coherency_line_size(CacheLevel::L1I);
l1_i_cache_sets = get_cache_number_of_sets(CacheLevel::L1I);
// L2 Cache Info.
l2_cache_size = get_cache_size(CacheLevel::L2);
l2_cache_line_size = get_cache_coherency_line_size(CacheLevel::L2);
l2_cache_sets = get_cache_number_of_sets(CacheLevel::L2);
// L3 Cache Info.
l3_cache_size = get_cache_size(CacheLevel::L3);
l3_cache_line_size = get_cache_coherency_line_size(CacheLevel::L3);
l3_cache_sets = get_cache_number_of_sets(CacheLevel::L3);
// Cache Shared Info.
if l2_cache_size != 0 {
l2_cache_shared = get_cache_shared(CacheLevel::L2);
}
if l3_cache_size != 0 {
l3_cache_shared = get_cache_shared(CacheLevel::L3);
}
}
for (cpu_id, mpidr) in vcpu_mpidr.iter().enumerate().take(num_cpus) {
let cpu_name = format!("cpu@{cpu_id:x}");
let cpu_name = format!("cpu@{:x}", cpu_id);
let cpu_node = fdt.begin_node(&cpu_name)?;
fdt.property_string("device_type", "cpu")?;
fdt.property_string("compatible", "arm,arm-v8")?;
@@ -377,112 +184,26 @@ fn create_cpu_nodes(
}
}
if cache_exist && l1_d_cache_size != 0 && l1_i_cache_size != 0 {
// Add cache info.
fdt.property_u32("d-cache-size", l1_d_cache_size)?;
fdt.property_u32("d-cache-line-size", l1_d_cache_line_size)?;
fdt.property_u32("d-cache-sets", l1_d_cache_sets)?;
fdt.property_u32("i-cache-size", l1_i_cache_size)?;
fdt.property_u32("i-cache-line-size", l1_i_cache_line_size)?;
fdt.property_u32("i-cache-sets", l1_i_cache_sets)?;
if l2_cache_size != 0 && !l2_cache_shared {
fdt.property_u32(
"next-level-cache",
cpu_id as u32 + max_cpus + FIRST_VCPU_PHANDLE + L2_CACHE_PHANDLE,
)?;
let l2_cache_name = "l2-cache0";
let l2_cache_node = fdt.begin_node(l2_cache_name)?;
// PHANDLE is used to mark device node, and PHANDLE is unique. To avoid phandle
// conflicts with other device nodes, consider the previous CPU PHANDLE, so the
// CPU L2 cache PHANDLE must start from the largest CPU PHANDLE plus 1.
fdt.property_u32(
"phandle",
cpu_id as u32 + max_cpus + FIRST_VCPU_PHANDLE + L2_CACHE_PHANDLE,
)?;
fdt.property_string("compatible", "cache")?;
fdt.property_u32("cache-size", l2_cache_size)?;
fdt.property_u32("cache-line-size", l2_cache_line_size)?;
fdt.property_u32("cache-sets", l2_cache_sets)?;
fdt.property_u32("cache-level", 2)?;
if l3_cache_size != 0 && l3_cache_shared {
let package_id: u32 = cpu_id as u32 / cores_per_package as u32;
fdt.property_u32(
"next-level-cache",
package_id
+ num_cpus as u32
+ max_cpus
+ FIRST_VCPU_PHANDLE
+ L2_CACHE_PHANDLE
+ L3_CACHE_PHANDLE,
)?;
}
fdt.end_node(l2_cache_node)?;
}
if l2_cache_size != 0 && l2_cache_shared {
warn!("L2 cache shared with other cpus");
}
}
fdt.end_node(cpu_node)?;
}
if cache_exist && l3_cache_size != 0 && !l2_cache_shared && l3_cache_shared {
let mut i: u32 = 0;
while i < packages.into() {
let l3_cache_name = "l3-cache0";
let l3_cache_node = fdt.begin_node(l3_cache_name)?;
// ARM L3 cache is generally shared within the package (socket), so the
// L3 cache node pointed to by the CPU in the package has the same L3
// cache PHANDLE. The L3 cache phandle must start from the largest L2
// cache PHANDLE plus 1 to avoid duplication.
fdt.property_u32(
"phandle",
i + num_cpus as u32
+ max_cpus
+ FIRST_VCPU_PHANDLE
+ L2_CACHE_PHANDLE
+ L3_CACHE_PHANDLE,
)?;
fdt.property_string("compatible", "cache")?;
fdt.property_null("cache-unified")?;
fdt.property_u32("cache-size", l3_cache_size)?;
fdt.property_u32("cache-line-size", l3_cache_line_size)?;
fdt.property_u32("cache-sets", l3_cache_sets)?;
fdt.property_u32("cache-level", 3)?;
fdt.end_node(l3_cache_node)?;
i += 1;
}
}
if let Some(topology) = vcpu_topology {
let (threads_per_core, cores_per_package, packages) = topology;
let cpu_map_node = fdt.begin_node("cpu-map")?;
// Create device tree nodes with regard of above mapping.
for package_idx in 0..packages {
let package_name = format!("socket{package_idx:x}");
let package_node = fdt.begin_node(&package_name)?;
// Cluster is the container of cores, and it is mandatory in the CPU topology.
// Add a default "cluster0" in each socket/package.
let cluster_node = fdt.begin_node("cluster0")?;
for cluster_idx in 0..packages {
let cluster_name = format!("cluster{:x}", cluster_idx);
let cluster_node = fdt.begin_node(&cluster_name)?;
for core_idx in 0..cores_per_package {
let core_name = format!("core{core_idx:x}");
let core_name = format!("core{:x}", core_idx);
let core_node = fdt.begin_node(&core_name)?;
for thread_idx in 0..threads_per_core {
let thread_name = format!("thread{thread_idx:x}");
let thread_name = format!("thread{:x}", thread_idx);
let thread_node = fdt.begin_node(&thread_name)?;
let cpu_idx = threads_per_core * cores_per_package * package_idx
let cpu_idx = threads_per_core * cores_per_package * cluster_idx
+ threads_per_core * core_idx
+ thread_idx;
fdt.property_u32("cpu", cpu_idx as u32 + FIRST_VCPU_PHANDLE)?;
@@ -492,7 +213,6 @@ fn create_cpu_nodes(
fdt.end_node(core_node)?;
}
fdt.end_node(cluster_node)?;
fdt.end_node(package_node)?;
}
fdt.end_node(cpu_map_node)?;
} else {
@@ -528,7 +248,7 @@ fn create_memory_node(
node_memory_addr = memory_region_start_addr;
}
}
let memory_node_name = format!("memory@{node_memory_addr:x}");
let memory_node_name = format!("memory@{:x}", node_memory_addr);
let memory_node = fdt.begin_node(&memory_node_name)?;
fdt.property_string("device_type", "memory")?;
fdt.property_array_u64("reg", &mem_reg_prop)?;
@@ -536,86 +256,35 @@ fn create_memory_node(
fdt.end_node(memory_node)?;
}
} else {
// Note: memory regions from "GuestMemory" are sorted and non-zero sized.
let ram_regions = {
let mut ram_regions = Vec::new();
let mut current_start = guest_mem
.iter()
.next()
.map(GuestMemoryRegion::start_addr)
.expect("GuestMemory must have one memory region at least")
.raw_value();
let mut current_end = current_start;
for (start, size) in guest_mem
.iter()
.map(|m| (m.start_addr().raw_value(), m.len()))
{
if current_end == start {
// This zone is continuous with the previous one.
current_end += size;
} else {
ram_regions.push((current_start, current_end));
current_start = start;
current_end = start + size;
}
}
ram_regions.push((current_start, current_end));
ram_regions
};
if ram_regions.len() > 2 {
panic!(
"There should be up to two non-continuous regions, devidided by the
gap at the end of 32bit address space."
);
}
// Create the memory node for memory region before the gap
{
let (first_region_start, first_region_end) = ram_regions
.first()
.expect("There should be at last one memory region");
let ram_start = super::layout::RAM_START.raw_value();
let mem_32bit_reserved_start = super::layout::MEM_32BIT_RESERVED_START.raw_value();
if !((first_region_start <= &ram_start)
&& (first_region_end > &ram_start)
&& (first_region_end <= &mem_32bit_reserved_start))
{
panic!(
"Unexpected first memory region layout: (start: 0x{:08x}, end: 0x{:08x}).
ram_start: 0x{:08x}, mem_32bit_reserved_start: 0x{:08x}",
first_region_start, first_region_end, ram_start, mem_32bit_reserved_start
);
}
let mem_size = first_region_end - ram_start;
let mem_reg_prop = [ram_start, mem_size];
let memory_node_name = format!("memory@{:x}", ram_start);
let last_addr = guest_mem.last_addr().raw_value();
if last_addr < super::layout::MEM_32BIT_RESERVED_START.raw_value() {
// Case 1: all RAM is under the hole
let mem_size = last_addr - super::layout::RAM_START.raw_value() + 1;
let mem_reg_prop = [super::layout::RAM_START.raw_value() as u64, mem_size as u64];
let memory_node = fdt.begin_node("memory")?;
fdt.property_string("device_type", "memory")?;
fdt.property_array_u64("reg", &mem_reg_prop)?;
fdt.end_node(memory_node)?;
} else {
// Case 2: RAM is split by the hole
// Region 1: RAM before the hole
let mem_size = super::layout::MEM_32BIT_RESERVED_START.raw_value()
- super::layout::RAM_START.raw_value();
let mem_reg_prop = [super::layout::RAM_START.raw_value() as u64, mem_size as u64];
let memory_node_name = format!("memory@{:x}", super::layout::RAM_START.raw_value());
let memory_node = fdt.begin_node(&memory_node_name)?;
fdt.property_string("device_type", "memory")?;
fdt.property_array_u64("reg", &mem_reg_prop)?;
fdt.end_node(memory_node)?;
}
// Create the memory map entry for memory region after the gap if any
if let Some((second_region_start, second_region_end)) = ram_regions.get(1) {
let ram_64bit_start = super::layout::RAM_64BIT_START.raw_value();
if second_region_start != &ram_64bit_start {
panic!(
"Unexpected second memory region layout: start: 0x{:08x}, ram_64bit_start: 0x{:08x}",
second_region_start, ram_64bit_start
);
}
let mem_size = second_region_end - ram_64bit_start;
let mem_reg_prop = [ram_64bit_start, mem_size];
let memory_node_name = format!("memory@{:x}", ram_64bit_start);
// Region 2: RAM after the hole
let mem_size = last_addr - super::layout::RAM_64BIT_START.raw_value() + 1;
let mem_reg_prop = [
super::layout::RAM_64BIT_START.raw_value() as u64,
mem_size as u64,
];
let memory_node_name =
format!("memory@{:x}", super::layout::RAM_64BIT_START.raw_value());
let memory_node = fdt.begin_node(&memory_node_name)?;
fdt.property_string("device_type", "memory")?;
fdt.property_array_u64("reg", &mem_reg_prop)?;
@@ -635,7 +304,7 @@ fn create_chosen_node(
fdt.property_string("bootargs", cmdline)?;
if let Some(initrd_config) = initrd {
let initrd_start = initrd_config.address.raw_value();
let initrd_start = initrd_config.address.raw_value() as u64;
let initrd_end = initrd_config.address.raw_value() + initrd_config.size as u64;
fdt.property_u64("linux,initrd-start", initrd_start)?;
fdt.property_u64("linux,initrd-end", initrd_end)?;
@@ -646,18 +315,18 @@ fn create_chosen_node(
Ok(())
}
fn create_gic_node(fdt: &mut FdtWriter, gic_device: &Arc<Mutex<dyn Vgic>>) -> FdtWriterResult<()> {
let gic_reg_prop = gic_device.lock().unwrap().device_properties();
fn create_gic_node(fdt: &mut FdtWriter, gic_device: &dyn GicDevice) -> FdtWriterResult<()> {
let gic_reg_prop = gic_device.device_properties();
let intc_node = fdt.begin_node("intc")?;
fdt.property_string("compatible", gic_device.lock().unwrap().fdt_compatibility())?;
fdt.property_string("compatible", gic_device.fdt_compatibility())?;
fdt.property_null("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.
fdt.property_u32("#interrupt-cells", 3)?;
fdt.property_array_u64("reg", &gic_reg_prop)?;
fdt.property_array_u64("reg", gic_reg_prop)?;
fdt.property_u32("phandle", GIC_PHANDLE)?;
fdt.property_u32("#address-cells", 2)?;
fdt.property_u32("#size-cells", 2)?;
@@ -665,18 +334,18 @@ fn create_gic_node(fdt: &mut FdtWriter, gic_device: &Arc<Mutex<dyn Vgic>>) -> Fd
let gic_intr_prop = [
GIC_FDT_IRQ_TYPE_PPI,
gic_device.lock().unwrap().fdt_maint_irq(),
gic_device.fdt_maint_irq(),
IRQ_TYPE_LEVEL_HI,
];
fdt.property_array_u32("interrupts", &gic_intr_prop)?;
if gic_device.lock().unwrap().msi_compatible() {
if gic_device.msi_compatible() {
let msic_node = fdt.begin_node("msic")?;
fdt.property_string("compatible", gic_device.lock().unwrap().msi_compatibility())?;
fdt.property_string("compatible", gic_device.msi_compatibility())?;
fdt.property_null("msi-controller")?;
fdt.property_u32("phandle", MSI_PHANDLE)?;
let msi_reg_prop = gic_device.lock().unwrap().msi_properties();
fdt.property_array_u64("reg", &msi_reg_prop)?;
let msi_reg_prop = gic_device.msi_properties();
fdt.property_array_u64("reg", msi_reg_prop)?;
fdt.end_node(msic_node)?;
}
@@ -871,9 +540,16 @@ fn create_devices_node<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::Buil
Ok(())
}
fn create_pmu_node(fdt: &mut FdtWriter) -> FdtWriterResult<()> {
fn create_pmu_node(fdt: &mut FdtWriter, cpu_nums: usize) -> FdtWriterResult<()> {
let num_cpus = cpu_nums as u64 as u32;
let compatible = "arm,armv8-pmuv3";
let irq = [GIC_FDT_IRQ_TYPE_PPI, AARCH64_PMU_IRQ, IRQ_TYPE_LEVEL_HI];
let cpu_mask: u32 =
(((1 << num_cpus) - 1) << GIC_FDT_IRQ_PPI_CPU_SHIFT) & GIC_FDT_IRQ_PPI_CPU_MASK;
let irq = [
GIC_FDT_IRQ_TYPE_PPI,
AARCH64_PMU_IRQ,
cpu_mask | IRQ_TYPE_LEVEL_HI,
];
let pmu_node = fdt.begin_node("pmu")?;
fdt.property_string("compatible", compatible)?;
@@ -1002,7 +678,7 @@ fn create_pci_nodes(
// See kernel document Documentation/devicetree/bindings/virtio/iommu.txt
// for virtio-iommu node settings.
let virtio_iommu_node_name = format!("virtio_iommu@{virtio_iommu_bdf:x}");
let virtio_iommu_node_name = format!("virtio_iommu@{:x}", virtio_iommu_bdf);
let virtio_iommu_node = fdt.begin_node(&virtio_iommu_node_name)?;
fdt.property_u32("#iommu-cells", 1)?;
fdt.property_string("compatible", "virtio,pci-iommu")?;

View File

@@ -1,12 +1,12 @@
// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
use crate::arch::aarch64::gic::{Error, Result};
use crate::device::HypervisorDeviceError;
use crate::kvm::kvm_bindings::{
use super::{Error, Result};
use crate::layout::IRQ_BASE;
use hypervisor::kvm::kvm_bindings::{
kvm_device_attr, KVM_DEV_ARM_VGIC_GRP_DIST_REGS, KVM_DEV_ARM_VGIC_GRP_NR_IRQS,
};
use kvm_ioctls::DeviceFd;
use std::sync::Arc;
/*
Distributor registers as detailed at page 456 from
@@ -77,7 +77,12 @@ static VGIC_DIST_REGS: &[DistReg] = &[
VGIC_DIST_REG!(GICD_IPRIORITYR, 8, 0),
];
fn dist_attr_access(gic: &DeviceFd, offset: u32, val: &u32, set: bool) -> Result<()> {
fn dist_attr_access(
gic: &Arc<dyn hypervisor::Device>,
offset: u32,
val: &u32,
set: bool,
) -> Result<()> {
let mut gic_dist_attr = kvm_device_attr {
group: KVM_DEV_ARM_VGIC_GRP_DIST_REGS,
attr: offset as u64,
@@ -85,30 +90,28 @@ fn dist_attr_access(gic: &DeviceFd, offset: u32, val: &u32, set: bool) -> Result
flags: 0,
};
if set {
gic.set_device_attr(&gic_dist_attr).map_err(|e| {
Error::SetDeviceAttribute(HypervisorDeviceError::SetDeviceAttribute(e.into()))
})?;
gic.set_device_attr(&gic_dist_attr)
.map_err(Error::SetDeviceAttribute)?;
} else {
gic.get_device_attr(&mut gic_dist_attr).map_err(|e| {
Error::GetDeviceAttribute(HypervisorDeviceError::GetDeviceAttribute(e.into()))
})?;
gic.get_device_attr(&mut gic_dist_attr)
.map_err(Error::GetDeviceAttribute)?;
}
Ok(())
}
/// Get the distributor control register.
pub fn read_ctlr(gic: &DeviceFd) -> Result<u32> {
pub fn read_ctlr(gic: &Arc<dyn hypervisor::Device>) -> Result<u32> {
let val: u32 = 0;
dist_attr_access(gic, GICD_CTLR, &val, false)?;
Ok(val)
}
/// Set the distributor control register.
pub fn write_ctlr(gic: &DeviceFd, val: u32) -> Result<()> {
pub fn write_ctlr(gic: &Arc<dyn hypervisor::Device>, val: u32) -> Result<()> {
dist_attr_access(gic, GICD_CTLR, &val, true)
}
fn get_interrupts_num(gic: &DeviceFd) -> Result<u32> {
fn get_interrupts_num(gic: &Arc<dyn hypervisor::Device>) -> Result<u32> {
let num_irq = 0;
let mut nr_irqs_attr = kvm_device_attr {
@@ -117,18 +120,12 @@ fn get_interrupts_num(gic: &DeviceFd) -> Result<u32> {
addr: &num_irq as *const u32 as u64,
flags: 0,
};
gic.get_device_attr(&mut nr_irqs_attr).map_err(|e| {
Error::GetDeviceAttribute(HypervisorDeviceError::GetDeviceAttribute(e.into()))
})?;
gic.get_device_attr(&mut nr_irqs_attr)
.map_err(Error::GetDeviceAttribute)?;
Ok(num_irq)
}
fn compute_reg_len(gic: &DeviceFd, reg: &DistReg, base: u32) -> Result<u32> {
// FIXME:
// Redefine some GIC constants to avoid the dependency on `layout` crate.
// This is temporary solution, will be fixed in future refactoring.
const LAYOUT_IRQ_BASE: u32 = 32;
fn compute_reg_len(gic: &Arc<dyn hypervisor::Device>, reg: &DistReg, base: u32) -> Result<u32> {
let mut end = base;
let num_irq = get_interrupts_num(gic)?;
if reg.length > 0 {
@@ -141,8 +138,8 @@ fn compute_reg_len(gic: &DeviceFd, reg: &DistReg, base: u32) -> Result<u32> {
// This is the type of register that takes into account the number of interrupts
// that the model has. It is also the type of register where
// a register relates to multiple interrupts.
end = base + (reg.bpi as u32 * (num_irq - LAYOUT_IRQ_BASE) / 8);
if reg.bpi as u32 * (num_irq - LAYOUT_IRQ_BASE) % 8 > 0 {
end = base + (reg.bpi as u32 * (num_irq - IRQ_BASE) / 8);
if reg.bpi as u32 * (num_irq - IRQ_BASE) % 8 > 0 {
end += REG_SIZE as u32;
}
}
@@ -150,7 +147,7 @@ fn compute_reg_len(gic: &DeviceFd, reg: &DistReg, base: u32) -> Result<u32> {
}
/// Set distributor registers of the GIC.
pub fn set_dist_regs(gic: &DeviceFd, state: &[u32]) -> Result<()> {
pub fn set_dist_regs(gic: &Arc<dyn hypervisor::Device>, state: &[u32]) -> Result<()> {
let mut idx = 0;
for dreg in VGIC_DIST_REGS {
@@ -167,7 +164,7 @@ pub fn set_dist_regs(gic: &DeviceFd, state: &[u32]) -> Result<()> {
Ok(())
}
/// Get distributor registers of the GIC.
pub fn get_dist_regs(gic: &DeviceFd) -> Result<Vec<u32>> {
pub fn get_dist_regs(gic: &Arc<dyn hypervisor::Device>) -> Result<Vec<u32>> {
let mut state = Vec::new();
for dreg in VGIC_DIST_REGS {

View File

@@ -0,0 +1,262 @@
// Copyright 2021 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
//
// This file implements the GicV3 device.
pub mod kvm {
use crate::aarch64::gic::dist_regs::{get_dist_regs, read_ctlr, set_dist_regs, write_ctlr};
use crate::aarch64::gic::icc_regs::{get_icc_regs, set_icc_regs};
use crate::aarch64::gic::kvm::{save_pending_tables, KvmGicDevice};
use crate::aarch64::gic::redist_regs::{
construct_gicr_typers, get_redist_regs, set_redist_regs,
};
use crate::aarch64::gic::GicDevice;
use crate::layout;
use anyhow::anyhow;
use hypervisor::kvm::kvm_bindings;
use hypervisor::CpuState;
use std::any::Any;
use std::convert::TryInto;
use std::sync::Arc;
use std::{boxed::Box, result};
use versionize::{VersionMap, Versionize, VersionizeResult};
use versionize_derive::Versionize;
use vm_memory::Address;
use vm_migration::{
Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable,
VersionMapped,
};
/// Errors thrown while saving/restoring the GICv3.
#[derive(Debug)]
pub enum Error {
/// Error in saving RDIST pending tables into guest RAM.
SavePendingTables(crate::aarch64::gic::Error),
/// Error in saving GIC distributor registers.
SaveDistributorRegisters(crate::aarch64::gic::Error),
/// Error in restoring GIC distributor registers.
RestoreDistributorRegisters(crate::aarch64::gic::Error),
/// Error in saving GIC distributor control registers.
SaveDistributorCtrlRegisters(crate::aarch64::gic::Error),
/// Error in restoring GIC distributor control registers.
RestoreDistributorCtrlRegisters(crate::aarch64::gic::Error),
/// Error in saving GIC redistributor registers.
SaveRedistributorRegisters(crate::aarch64::gic::Error),
/// Error in restoring GIC redistributor registers.
RestoreRedistributorRegisters(crate::aarch64::gic::Error),
/// Error in saving GIC CPU interface registers.
SaveIccRegisters(crate::aarch64::gic::Error),
/// Error in restoring GIC CPU interface registers.
RestoreIccRegisters(crate::aarch64::gic::Error),
}
type Result<T> = result::Result<T, Error>;
pub struct KvmGicV3 {
/// The hypervisor agnostic device for the GicV3
device: Arc<dyn hypervisor::Device>,
/// Vector holding values of GICR_TYPER for each vCPU
gicr_typers: Vec<u64>,
/// 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,
}
#[derive(Versionize)]
pub struct Gicv3State {
dist: Vec<u32>,
rdist: Vec<u32>,
icc: Vec<u32>,
// special register that enables interrupts and affinity routing
gicd_ctlr: u32,
}
impl VersionMapped for Gicv3State {}
impl KvmGicV3 {
// 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::GIC_V3_DIST_START.raw_value()
}
/// Get the size of the GIC distributor.
pub fn get_dist_size() -> u64 {
layout::GIC_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 * layout::GIC_V3_REDIST_SIZE
}
/// Save the state of GIC.
fn state(&self, gicr_typers: &[u64]) -> Result<Gicv3State> {
let gicd_ctlr =
read_ctlr(self.device()).map_err(Error::SaveDistributorCtrlRegisters)?;
let dist_state =
get_dist_regs(self.device()).map_err(Error::SaveDistributorRegisters)?;
let rdist_state = get_redist_regs(self.device(), gicr_typers)
.map_err(Error::SaveRedistributorRegisters)?;
let icc_state =
get_icc_regs(self.device(), gicr_typers).map_err(Error::SaveIccRegisters)?;
Ok(Gicv3State {
dist: dist_state,
rdist: rdist_state,
icc: icc_state,
gicd_ctlr,
})
}
/// Restore the state of GIC.
fn set_state(&mut self, gicr_typers: &[u64], state: &Gicv3State) -> Result<()> {
write_ctlr(self.device(), state.gicd_ctlr)
.map_err(Error::RestoreDistributorCtrlRegisters)?;
set_dist_regs(self.device(), &state.dist)
.map_err(Error::RestoreDistributorRegisters)?;
set_redist_regs(self.device(), gicr_typers, &state.rdist)
.map_err(Error::RestoreRedistributorRegisters)?;
set_icc_regs(self.device(), gicr_typers, &state.icc)
.map_err(Error::RestoreIccRegisters)?;
Ok(())
}
}
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
}
fn set_its_device(&mut self, _its_device: Option<Arc<dyn hypervisor::Device>>) {}
fn set_gicr_typers(&mut self, vcpu_states: &[CpuState]) {
let gicr_typers = construct_gicr_typers(vcpu_states);
self.gicr_typers = gicr_typers;
}
fn as_any_concrete_mut(&mut self) -> &mut dyn Any {
self
}
}
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,
gicr_typers: vec![0; vcpu_count.try_into().unwrap()],
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: &mut dyn GicDevice,
) -> crate::aarch64::gic::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(())
}
}
pub const GIC_V3_SNAPSHOT_ID: &str = "gic-v3";
impl Snapshottable for KvmGicV3 {
fn id(&self) -> String {
GIC_V3_SNAPSHOT_ID.to_string()
}
fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
let gicr_typers = self.gicr_typers.clone();
Snapshot::new_from_versioned_state(&self.id(), &self.state(&gicr_typers).unwrap())
}
fn restore(&mut self, snapshot: Snapshot) -> std::result::Result<(), MigratableError> {
let gicr_typers = self.gicr_typers.clone();
self.set_state(&gicr_typers, &snapshot.to_versioned_state(&self.id())?)
.map_err(|e| {
MigratableError::Restore(anyhow!("Could not restore GICv3 state {:?}", e))
})
}
}
impl Pausable for KvmGicV3 {
fn pause(&mut self) -> std::result::Result<(), MigratableError> {
// Flush redistributors pending tables to guest RAM.
save_pending_tables(self.device()).map_err(|e| {
MigratableError::Pause(anyhow!("Could not save GICv3 GIC pending tables {:?}", e))
})?;
Ok(())
}
}
impl Transportable for KvmGicV3 {}
impl Migratable for KvmGicV3 {}
}

View File

@@ -0,0 +1,514 @@
// Copyright 2021 Arm Limited (or its affiliates). All rights reserved.
// SPDX-License-Identifier: Apache-2.0
//
// This file implements the GicV3 device with ITS (Virtual Interrupt Translation Service).
pub mod kvm {
use crate::aarch64::gic::dist_regs::{get_dist_regs, read_ctlr, set_dist_regs, write_ctlr};
use crate::aarch64::gic::icc_regs::{get_icc_regs, set_icc_regs};
use crate::aarch64::gic::redist_regs::{
construct_gicr_typers, get_redist_regs, set_redist_regs,
};
use crate::aarch64::gic::gicv3::kvm::KvmGicV3;
use crate::aarch64::gic::kvm::{save_pending_tables, KvmGicDevice};
use crate::aarch64::gic::GicDevice;
use crate::layout;
use anyhow::anyhow;
use hypervisor::kvm::kvm_bindings;
use hypervisor::CpuState;
use std::any::Any;
use std::convert::TryInto;
use std::sync::Arc;
use std::{boxed::Box, result};
use versionize::{VersionMap, Versionize, VersionizeResult};
use versionize_derive::Versionize;
use vm_migration::{
Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable,
VersionMapped,
};
const GITS_CTLR: u32 = 0x0000;
const GITS_IIDR: u32 = 0x0004;
const GITS_CBASER: u32 = 0x0080;
const GITS_CWRITER: u32 = 0x0088;
const GITS_CREADR: u32 = 0x0090;
const GITS_BASER: u32 = 0x0100;
/// Errors thrown while saving/restoring the GICv3ITS.
#[derive(Debug)]
pub enum Error {
/// Error in saving RDIST pending tables into guest RAM.
SavePendingTables(crate::aarch64::gic::Error),
/// Error in saving GIC distributor registers.
SaveDistributorRegisters(crate::aarch64::gic::Error),
/// Error in restoring GIC distributor registers.
RestoreDistributorRegisters(crate::aarch64::gic::Error),
/// Error in saving GIC distributor control registers.
SaveDistributorCtrlRegisters(crate::aarch64::gic::Error),
/// Error in restoring GIC distributor control registers.
RestoreDistributorCtrlRegisters(crate::aarch64::gic::Error),
/// Error in saving GIC redistributor registers.
SaveRedistributorRegisters(crate::aarch64::gic::Error),
/// Error in restoring GIC redistributor registers.
RestoreRedistributorRegisters(crate::aarch64::gic::Error),
/// Error in saving GIC CPU interface registers.
SaveIccRegisters(crate::aarch64::gic::Error),
/// Error in restoring GIC CPU interface registers.
RestoreIccRegisters(crate::aarch64::gic::Error),
/// Error in saving GICv3ITS IIDR register.
SaveITSIIDR(crate::aarch64::gic::Error),
/// Error in restoring GICv3ITS IIDR register.
RestoreITSIIDR(crate::aarch64::gic::Error),
/// Error in saving GICv3ITS CBASER register.
SaveITSCBASER(crate::aarch64::gic::Error),
/// Error in restoring GICv3ITS CBASER register.
RestoreITSCBASER(crate::aarch64::gic::Error),
/// Error in saving GICv3ITS CREADR register.
SaveITSCREADR(crate::aarch64::gic::Error),
/// Error in restoring GICv3ITS CREADR register.
RestoreITSCREADR(crate::aarch64::gic::Error),
/// Error in saving GICv3ITS CWRITER register.
SaveITSCWRITER(crate::aarch64::gic::Error),
/// Error in restoring GICv3ITS CWRITER register.
RestoreITSCWRITER(crate::aarch64::gic::Error),
/// Error in saving GICv3ITS BASER register.
SaveITSBASER(crate::aarch64::gic::Error),
/// Error in restoring GICv3ITS BASER register.
RestoreITSBASER(crate::aarch64::gic::Error),
/// Error in saving GICv3ITS CTLR register.
SaveITSCTLR(crate::aarch64::gic::Error),
/// Error in restoring GICv3ITS CTLR register.
RestoreITSCTLR(crate::aarch64::gic::Error),
/// Error in saving GICv3ITS restore tables.
SaveITSTables(crate::aarch64::gic::Error),
/// Error in restoring GICv3ITS restore tables.
RestoreITSTables(crate::aarch64::gic::Error),
}
type Result<T> = result::Result<T, Error>;
/// Access an ITS device attribute.
///
/// This is a helper function to get/set the ITS device attribute depending
/// the bool parameter `set` provided.
pub fn gicv3_its_attr_access(
its_device: &Arc<dyn hypervisor::Device>,
group: u32,
attr: u32,
val: &u64,
set: bool,
) -> crate::aarch64::gic::Result<()> {
let mut gicv3_its_attr = kvm_bindings::kvm_device_attr {
group,
attr: attr as u64,
addr: val as *const u64 as u64,
flags: 0,
};
if set {
its_device
.set_device_attr(&gicv3_its_attr)
.map_err(crate::aarch64::gic::Error::SetDeviceAttribute)?;
} else {
its_device
.get_device_attr(&mut gicv3_its_attr)
.map_err(crate::aarch64::gic::Error::GetDeviceAttribute)?;
}
Ok(())
}
/// Function that saves/restores ITS tables into guest RAM.
///
/// The tables get flushed to guest RAM whenever the VM gets stopped.
pub fn gicv3_its_tables_access(
its_device: &Arc<dyn hypervisor::Device>,
save: bool,
) -> crate::aarch64::gic::Result<()> {
let attr = if save {
u64::from(kvm_bindings::KVM_DEV_ARM_ITS_SAVE_TABLES)
} else {
u64::from(kvm_bindings::KVM_DEV_ARM_ITS_RESTORE_TABLES)
};
let init_gic_attr = kvm_bindings::kvm_device_attr {
group: kvm_bindings::KVM_DEV_ARM_VGIC_GRP_CTRL,
attr,
addr: 0,
flags: 0,
};
its_device
.set_device_attr(&init_gic_attr)
.map_err(crate::aarch64::gic::Error::SetDeviceAttribute)
}
pub struct KvmGicV3Its {
/// The hypervisor agnostic device for the GicV3
device: Arc<dyn hypervisor::Device>,
/// The hypervisor agnostic device for the Its device
its_device: Option<Arc<dyn hypervisor::Device>>,
/// Vector holding values of GICR_TYPER for each vCPU
gicr_typers: Vec<u64>,
/// 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,
}
#[derive(Versionize)]
pub struct Gicv3ItsState {
dist: Vec<u32>,
rdist: Vec<u32>,
icc: Vec<u32>,
// special register that enables interrupts and affinity routing
gicd_ctlr: u32,
its_ctlr: u64,
its_iidr: u64,
its_cbaser: u64,
its_cwriter: u64,
its_creadr: u64,
its_baser: [u64; 8],
}
impl VersionMapped for Gicv3ItsState {}
impl KvmGicV3Its {
fn get_msi_size() -> u64 {
layout::GIC_V3_ITS_SIZE
}
fn get_msi_addr(vcpu_count: u64) -> u64 {
KvmGicV3::get_redists_addr(vcpu_count) - KvmGicV3Its::get_msi_size()
}
/// Save the state of GICv3ITS.
fn state(&self, gicr_typers: &[u64]) -> Result<Gicv3ItsState> {
let gicd_ctlr =
read_ctlr(self.device()).map_err(Error::SaveDistributorCtrlRegisters)?;
let dist_state =
get_dist_regs(self.device()).map_err(Error::SaveDistributorRegisters)?;
let rdist_state = get_redist_regs(self.device(), gicr_typers)
.map_err(Error::SaveRedistributorRegisters)?;
let icc_state =
get_icc_regs(self.device(), gicr_typers).map_err(Error::SaveIccRegisters)?;
let its_baser_state: [u64; 8] = [0; 8];
for i in 0..8 {
gicv3_its_attr_access(
self.its_device().unwrap(),
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
GITS_BASER + i * 8,
&its_baser_state[i as usize],
false,
)
.map_err(Error::SaveITSBASER)?;
}
let its_ctlr_state: u64 = 0;
gicv3_its_attr_access(
self.its_device().unwrap(),
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
GITS_CTLR,
&its_ctlr_state,
false,
)
.map_err(Error::SaveITSCTLR)?;
let its_cbaser_state: u64 = 0;
gicv3_its_attr_access(
self.its_device().unwrap(),
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
GITS_CBASER,
&its_cbaser_state,
false,
)
.map_err(Error::SaveITSCBASER)?;
let its_creadr_state: u64 = 0;
gicv3_its_attr_access(
self.its_device().unwrap(),
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
GITS_CREADR,
&its_creadr_state,
false,
)
.map_err(Error::SaveITSCREADR)?;
let its_cwriter_state: u64 = 0;
gicv3_its_attr_access(
self.its_device().unwrap(),
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
GITS_CWRITER,
&its_cwriter_state,
false,
)
.map_err(Error::SaveITSCWRITER)?;
let its_iidr_state: u64 = 0;
gicv3_its_attr_access(
self.its_device().unwrap(),
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
GITS_IIDR,
&its_iidr_state,
false,
)
.map_err(Error::SaveITSIIDR)?;
Ok(Gicv3ItsState {
dist: dist_state,
rdist: rdist_state,
icc: icc_state,
gicd_ctlr,
its_ctlr: its_ctlr_state,
its_iidr: its_iidr_state,
its_cbaser: its_cbaser_state,
its_cwriter: its_cwriter_state,
its_creadr: its_creadr_state,
its_baser: its_baser_state,
})
}
/// Restore the state of GICv3ITS.
fn set_state(&mut self, gicr_typers: &[u64], state: &Gicv3ItsState) -> Result<()> {
write_ctlr(self.device(), state.gicd_ctlr)
.map_err(Error::RestoreDistributorCtrlRegisters)?;
set_dist_regs(self.device(), &state.dist)
.map_err(Error::RestoreDistributorRegisters)?;
set_redist_regs(self.device(), gicr_typers, &state.rdist)
.map_err(Error::RestoreRedistributorRegisters)?;
set_icc_regs(self.device(), gicr_typers, &state.icc)
.map_err(Error::RestoreIccRegisters)?;
//Restore GICv3ITS registers
gicv3_its_attr_access(
self.its_device().unwrap(),
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
GITS_IIDR,
&state.its_iidr,
true,
)
.map_err(Error::RestoreITSIIDR)?;
gicv3_its_attr_access(
self.its_device().unwrap(),
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
GITS_CBASER,
&state.its_cbaser,
true,
)
.map_err(Error::RestoreITSCBASER)?;
gicv3_its_attr_access(
self.its_device().unwrap(),
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
GITS_CREADR,
&state.its_creadr,
true,
)
.map_err(Error::RestoreITSCREADR)?;
gicv3_its_attr_access(
self.its_device().unwrap(),
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
GITS_CWRITER,
&state.its_cwriter,
true,
)
.map_err(Error::RestoreITSCWRITER)?;
for i in 0..8 {
gicv3_its_attr_access(
self.its_device().unwrap(),
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
GITS_BASER + i * 8,
&state.its_baser[i as usize],
true,
)
.map_err(Error::RestoreITSBASER)?;
}
// Restore ITS tables
gicv3_its_tables_access(self.its_device().unwrap(), false)
.map_err(Error::RestoreITSTables)?;
gicv3_its_attr_access(
self.its_device().unwrap(),
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
GITS_CTLR,
&state.its_ctlr,
true,
)
.map_err(Error::RestoreITSCTLR)?;
Ok(())
}
}
impl GicDevice for KvmGicV3Its {
fn device(&self) -> &Arc<dyn hypervisor::Device> {
&self.device
}
fn its_device(&self) -> Option<&Arc<dyn hypervisor::Device>> {
self.its_device.as_ref()
}
fn fdt_compatibility(&self) -> &str {
"arm,gic-v3"
}
fn msi_compatible(&self) -> bool {
true
}
fn msi_compatibility(&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
}
fn set_its_device(&mut self, its_device: Option<Arc<dyn hypervisor::Device>>) {
self.its_device = its_device;
}
fn set_gicr_typers(&mut self, vcpu_states: &[CpuState]) {
let gicr_typers = construct_gicr_typers(vcpu_states);
self.gicr_typers = gicr_typers;
}
fn as_any_concrete_mut(&mut self) -> &mut dyn Any {
self
}
}
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,
its_device: None,
gicr_typers: vec![0; vcpu_count.try_into().unwrap()],
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: &mut dyn GicDevice,
) -> crate::aarch64::gic::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(crate::aarch64::gic::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,
)?;
gic_device.set_its_device(Some(its_fd));
Ok(())
}
}
pub const GIC_V3_ITS_SNAPSHOT_ID: &str = "gic-v3-its";
impl Snapshottable for KvmGicV3Its {
fn id(&self) -> String {
GIC_V3_ITS_SNAPSHOT_ID.to_string()
}
fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
let gicr_typers = self.gicr_typers.clone();
Snapshot::new_from_versioned_state(&self.id(), &self.state(&gicr_typers).unwrap())
}
fn restore(&mut self, snapshot: Snapshot) -> std::result::Result<(), MigratableError> {
let gicr_typers = self.gicr_typers.clone();
self.set_state(&gicr_typers, &snapshot.to_versioned_state(&self.id())?)
.map_err(|e| {
MigratableError::Restore(anyhow!("Could not restore GICv3ITS state {:?}", e))
})
}
}
impl Pausable for KvmGicV3Its {
fn pause(&mut self) -> std::result::Result<(), MigratableError> {
// Flush redistributors pending tables to guest RAM.
save_pending_tables(self.device()).map_err(|e| {
MigratableError::Pause(anyhow!(
"Could not save GICv3ITS GIC pending tables {:?}",
e
))
})?;
// Flush ITS tables to guest RAM.
gicv3_its_tables_access(self.its_device().unwrap(), true).map_err(|e| {
MigratableError::Pause(anyhow!("Could not save GICv3ITS ITS tables {:?}", e))
})?;
Ok(())
}
}
impl Transportable for KvmGicV3Its {}
impl Migratable for KvmGicV3Its {}
}

View File

@@ -1,16 +1,14 @@
// Copyright 2022 Arm Limited (or its affiliates). All rights reserved.
// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
use crate::arch::aarch64::gic::{Error, Result};
use crate::device::HypervisorDeviceError;
use crate::kvm::kvm_bindings::{
use super::{Error, Result};
use hypervisor::kvm::kvm_bindings::{
kvm_device_attr, KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS, 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,
};
use kvm_ioctls::DeviceFd;
use std::sync::Arc;
const KVM_DEV_ARM_VGIC_V3_MPIDR_SHIFT: u32 = 32;
const KVM_DEV_ARM_VGIC_V3_MPIDR_MASK: u64 = 0xffffffff << KVM_DEV_ARM_VGIC_V3_MPIDR_SHIFT as u64;
@@ -79,7 +77,13 @@ static VGIC_ICC_REGS: &[u64] = &[
SYS_ICC_AP1R3_EL1,
];
fn icc_attr_access(gic: &DeviceFd, offset: u64, typer: u64, val: &u32, set: bool) -> Result<()> {
fn icc_attr_access(
gic: &Arc<dyn hypervisor::Device>,
offset: u64,
typer: u64,
val: &u32,
set: bool,
) -> Result<()> {
let mut gic_icc_attr = kvm_device_attr {
group: KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS,
attr: ((typer & KVM_DEV_ARM_VGIC_V3_MPIDR_MASK) | offset), // this needs the mpidr
@@ -87,19 +91,17 @@ fn icc_attr_access(gic: &DeviceFd, offset: u64, typer: u64, val: &u32, set: bool
flags: 0,
};
if set {
gic.set_device_attr(&gic_icc_attr).map_err(|e| {
Error::SetDeviceAttribute(HypervisorDeviceError::SetDeviceAttribute(e.into()))
})?;
gic.set_device_attr(&gic_icc_attr)
.map_err(Error::SetDeviceAttribute)?;
} else {
gic.get_device_attr(&mut gic_icc_attr).map_err(|e| {
Error::GetDeviceAttribute(HypervisorDeviceError::GetDeviceAttribute(e.into()))
})?;
gic.get_device_attr(&mut gic_icc_attr)
.map_err(Error::GetDeviceAttribute)?;
}
Ok(())
}
/// Get ICC registers.
pub fn get_icc_regs(gic: &DeviceFd, gicr_typer: &[u64]) -> Result<Vec<u32>> {
pub fn get_icc_regs(gic: &Arc<dyn hypervisor::Device>, gicr_typer: &[u64]) -> Result<Vec<u32>> {
let mut state: Vec<u32> = Vec::new();
// We need this for the ICC_AP<m>R<n>_EL1 registers.
let mut num_priority_bits = 0;
@@ -152,7 +154,11 @@ pub fn get_icc_regs(gic: &DeviceFd, gicr_typer: &[u64]) -> Result<Vec<u32>> {
}
/// Set ICC registers.
pub fn set_icc_regs(gic: &DeviceFd, gicr_typer: &[u64], state: &[u32]) -> Result<()> {
pub fn set_icc_regs(
gic: &Arc<dyn hypervisor::Device>,
gicr_typer: &[u64],
state: &[u32],
) -> Result<()> {
let mut num_priority_bits = 0;
let mut idx = 0;
for ix in gicr_typer {

220
arch/src/aarch64/gic/mod.rs Normal file
View File

@@ -0,0 +1,220 @@
// Copyright 2021 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
pub mod dist_regs;
pub mod gicv3;
pub mod gicv3_its;
pub mod icc_regs;
pub mod redist_regs;
pub use self::dist_regs::{get_dist_regs, read_ctlr, set_dist_regs, write_ctlr};
pub use self::icc_regs::{get_icc_regs, set_icc_regs};
pub use self::redist_regs::{get_redist_regs, set_redist_regs};
use hypervisor::CpuState;
use std::any::Any;
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),
/// Error while getting device attributes for the GIC.
GetDeviceAttribute(hypervisor::HypervisorDeviceError),
}
type Result<T> = result::Result<T, Error>;
pub trait GicDevice: Send {
/// Returns the hypervisor agnostic Device of the GIC device
fn device(&self) -> &Arc<dyn hypervisor::Device>;
/// Returns the hypervisor agnostic Device of the ITS device
fn its_device(&self) -> Option<&Arc<dyn hypervisor::Device>> {
None
}
/// 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_compatibility(&self) -> &str {
""
}
/// Returns the MSI reg property of the device
fn msi_properties(&self) -> &[u64] {
&[]
}
fn set_its_device(&mut self, its_device: Option<Arc<dyn hypervisor::Device>>);
/// Get the values of GICR_TYPER for each vCPU.
fn set_gicr_typers(&mut self, vcpu_states: &[CpuState]);
/// Downcast the trait object to its concrete type.
fn as_any_concrete_mut(&mut self) -> &mut dyn Any;
}
pub mod kvm {
use super::GicDevice;
use super::Result;
use crate::aarch64::gic::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: &mut 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 {
flags,
group,
attr,
addr,
};
device
.set_device_attr(&attr)
.map_err(super::Error::SetDeviceAttribute)?;
Ok(())
}
/// Get a GIC device attribute
fn get_device_attribute(
device: &Arc<dyn hypervisor::Device>,
group: u32,
attr: u64,
addr: u64,
flags: u32,
) -> Result<()> {
let mut attr = kvm_bindings::kvm_device_attr {
flags,
group,
attr,
addr,
};
device
.get_device_attr(&mut attr)
.map_err(super::Error::GetDeviceAttribute)?;
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_NUM;
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
#[allow(clippy::new_ret_no_self)]
fn new(vm: &Arc<dyn hypervisor::Vm>, vcpu_count: u64) -> Result<Box<dyn GicDevice>> {
let vgic_fd = Self::init_device(vm)?;
let mut device = Self::create_device(vgic_fd, vcpu_count);
Self::init_device_attributes(vm, &mut *device)?;
Self::finalize_device(&*device)?;
Ok(device)
}
}
/// Create a GICv3-ITS device.
///
pub fn create_gic(vm: &Arc<dyn hypervisor::Vm>, vcpu_count: u64) -> Result<Box<dyn GicDevice>> {
debug!("creating a GICv3-ITS");
KvmGicV3Its::new(vm, vcpu_count)
}
/// Function that saves RDIST pending tables into guest RAM.
///
/// The tables get flushed to guest RAM whenever the VM gets stopped.
pub fn save_pending_tables(gic: &Arc<dyn hypervisor::Device>) -> Result<()> {
let init_gic_attr = kvm_bindings::kvm_device_attr {
group: kvm_bindings::KVM_DEV_ARM_VGIC_GRP_CTRL,
attr: u64::from(kvm_bindings::KVM_DEV_ARM_VGIC_SAVE_PENDING_TABLES),
addr: 0,
flags: 0,
};
gic.set_device_attr(&init_gic_attr)
.map_err(super::Error::SetDeviceAttribute)
}
}

View File

@@ -1,18 +1,10 @@
// Copyright 2022 Arm Limited (or its affiliates). All rights reserved.
// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
use crate::arch::aarch64::gic::{Error, Result};
use crate::device::HypervisorDeviceError;
use crate::kvm::kvm_bindings::{
kvm_device_attr, KVM_DEV_ARM_VGIC_GRP_REDIST_REGS, KVM_REG_ARM64, KVM_REG_ARM64_SYSREG,
KVM_REG_ARM64_SYSREG_OP0_MASK, KVM_REG_ARM64_SYSREG_OP0_SHIFT, KVM_REG_ARM64_SYSREG_OP2_MASK,
KVM_REG_ARM64_SYSREG_OP2_SHIFT, KVM_REG_SIZE_U64,
};
use crate::kvm::Register;
use crate::kvm::VcpuKvmState;
use crate::CpuState;
use kvm_ioctls::DeviceFd;
use super::{Error, Result};
use hypervisor::kvm::kvm_bindings::{kvm_device_attr, KVM_DEV_ARM_VGIC_GRP_REDIST_REGS};
use hypervisor::CpuState;
use std::sync::Arc;
// Relevant redistributor registers that we want to save/restore.
const GICR_CTLR: u32 = 0x0000;
@@ -39,12 +31,6 @@ const GICR_ICFGR0: u32 = GICR_SGI_OFFSET + 0x0C00;
const KVM_DEV_ARM_VGIC_V3_MPIDR_SHIFT: u32 = 32;
const KVM_DEV_ARM_VGIC_V3_MPIDR_MASK: u64 = 0xffffffff << KVM_DEV_ARM_VGIC_V3_MPIDR_SHIFT as u64;
const KVM_ARM64_SYSREG_MPIDR_EL1: u64 = KVM_REG_ARM64
| KVM_REG_SIZE_U64
| KVM_REG_ARM64_SYSREG as u64
| (((3_u64) << KVM_REG_ARM64_SYSREG_OP0_SHIFT) & KVM_REG_ARM64_SYSREG_OP0_MASK as u64)
| (((5_u64) << KVM_REG_ARM64_SYSREG_OP2_SHIFT) & KVM_REG_ARM64_SYSREG_OP2_MASK as u64);
/// This is how we represent the registers of a distributor.
/// It is relrvant their offset from the base address of the
/// distributor.
@@ -96,27 +82,31 @@ static VGIC_SGI_REGS: &[RdistReg] = &[
VGIC_RDIST_REG!(GICR_IPRIORITYR0, 32),
];
fn redist_attr_access(gic: &DeviceFd, offset: u32, typer: u64, val: &u32, set: bool) -> Result<()> {
let mut gic_redist_attr = kvm_device_attr {
fn redist_attr_access(
gic: &Arc<dyn hypervisor::Device>,
offset: u32,
typer: u64,
val: &u32,
set: bool,
) -> Result<()> {
let mut gic_dist_attr = kvm_device_attr {
group: KVM_DEV_ARM_VGIC_GRP_REDIST_REGS,
attr: (typer & KVM_DEV_ARM_VGIC_V3_MPIDR_MASK) | (offset as u64), // this needs the mpidr
addr: val as *const u32 as u64,
flags: 0,
};
if set {
gic.set_device_attr(&gic_redist_attr).map_err(|e| {
Error::SetDeviceAttribute(HypervisorDeviceError::SetDeviceAttribute(e.into()))
})?;
gic.set_device_attr(&gic_dist_attr)
.map_err(Error::SetDeviceAttribute)?;
} else {
gic.get_device_attr(&mut gic_redist_attr).map_err(|e| {
Error::GetDeviceAttribute(HypervisorDeviceError::GetDeviceAttribute(e.into()))
})?;
gic.get_device_attr(&mut gic_dist_attr)
.map_err(Error::GetDeviceAttribute)?;
}
Ok(())
}
fn access_redists_aux(
gic: &DeviceFd,
gic: &Arc<dyn hypervisor::Device>,
gicr_typer: &[u64],
state: &mut Vec<u32>,
reg_list: &[RdistReg],
@@ -146,7 +136,7 @@ fn access_redists_aux(
}
/// Get redistributor registers.
pub fn get_redist_regs(gic: &DeviceFd, gicr_typer: &[u64]) -> Result<Vec<u32>> {
pub fn get_redist_regs(gic: &Arc<dyn hypervisor::Device>, gicr_typer: &[u64]) -> Result<Vec<u32>> {
let mut state = Vec::new();
let mut idx: usize = 0;
access_redists_aux(
@@ -163,7 +153,11 @@ pub fn get_redist_regs(gic: &DeviceFd, gicr_typer: &[u64]) -> Result<Vec<u32>> {
}
/// Set redistributor registers.
pub fn set_redist_regs(gic: &DeviceFd, gicr_typer: &[u64], state: &[u32]) -> Result<()> {
pub fn set_redist_regs(
gic: &Arc<dyn hypervisor::Device>,
gicr_typer: &[u64],
state: &[u32],
) -> Result<()> {
let mut idx: usize = 0;
let mut mut_state = state.to_owned();
access_redists_aux(
@@ -199,16 +193,15 @@ pub fn construct_gicr_typers(vcpu_states: &[CpuState]) -> Vec<u64> {
*/
let mut gicr_typers: Vec<u64> = Vec::new();
for (index, state) in vcpu_states.iter().enumerate() {
let state: VcpuKvmState = state.clone().into();
let last = (index == vcpu_states.len() - 1) as u64;
// state.sys_regs is a big collection of system registers, including MIPDR_EL1
let mpidr: Vec<Register> = state
.sys_regs
.into_iter()
.filter(|reg| reg.id == KVM_ARM64_SYSREG_MPIDR_EL1)
.collect();
let last = {
if index == vcpu_states.len() - 1 {
1
} else {
0
}
};
//calculate affinity
let mut cpu_affid = mpidr[0].addr & 1095233437695;
let mut cpu_affid = state.mpidr & 1095233437695;
cpu_affid = ((cpu_affid & 0xFF00000000) >> 8) | (cpu_affid & 0xFFFFFF);
gicr_typers.push((cpu_affid << 32) | (1 << 24) | (index as u64) << 8 | (last << 4));
}

View File

@@ -59,7 +59,7 @@ pub const UEFI_START: GuestAddress = GuestAddress(0);
pub const UEFI_SIZE: u64 = 0x040_0000;
/// Below this address will reside the GIC, above this address will reside the MMIO devices.
const MAPPED_IO_START: GuestAddress = GuestAddress(0x0900_0000);
pub const MAPPED_IO_START: GuestAddress = GuestAddress(0x0900_0000);
/// See kernel file arch/arm64/include/uapi/asm/kvm.h for the GIC related definitions.
/// 0x08ff_0000 ~ 0x0900_0000 is reserved for GICv3 Distributor
@@ -73,7 +73,7 @@ pub const GIC_V3_REDIST_SIZE: u64 = 0x02_0000;
pub const GIC_V3_ITS_SIZE: u64 = 0x02_0000;
/// Space 0x0900_0000 ~ 0x0905_0000 is reserved for legacy devices.
pub const LEGACY_SERIAL_MAPPED_IO_START: GuestAddress = MAPPED_IO_START;
pub const LEGACY_SERIAL_MAPPED_IO_START: GuestAddress = GuestAddress(0x0900_0000);
pub const LEGACY_RTC_MAPPED_IO_START: GuestAddress = GuestAddress(0x0901_0000);
pub const LEGACY_GPIO_MAPPED_IO_START: GuestAddress = GuestAddress(0x0902_0000);
@@ -98,11 +98,6 @@ pub const RAM_START: GuestAddress = GuestAddress(0x4000_0000);
pub const MEM_32BIT_RESERVED_START: GuestAddress = GuestAddress(0xfc00_0000);
pub const MEM_32BIT_RESERVED_SIZE: u64 = 0x0400_0000;
/// TPM Address Range
/// This Address range is specific to CRB Interface
pub const TPM_START: GuestAddress = GuestAddress(0xfed4_0000);
pub const TPM_SIZE: u64 = 0x1000;
/// Start of 64-bit RAM.
pub const RAM_64BIT_START: GuestAddress = GuestAddress(0x1_0000_0000);

View File

@@ -4,24 +4,24 @@
/// Module for the flattened device tree.
pub mod fdt;
/// Module for the global interrupt controller configuration.
pub mod gic;
/// Layout for this aarch64 system.
pub mod layout;
/// Module for system registers definition
/// Logic for configuring aarch64 registers.
pub mod regs;
/// Module for loading UEFI binary.
pub mod uefi;
pub use self::fdt::DeviceInfoForFdt;
use crate::{DeviceType, GuestMemoryMmap, NumaNodes, PciSpaceInfo, RegionType};
use hypervisor::arch::aarch64::gic::Vgic;
use gic::GicDevice;
use log::{log_enabled, Level};
use std::collections::HashMap;
use std::convert::TryInto;
use std::fmt::Debug;
use std::sync::{Arc, Mutex};
use vm_memory::{Address, GuestAddress, GuestMemory, GuestMemoryAtomic};
pub const _NSIG: i32 = 65;
use std::sync::Arc;
use vm_memory::{Address, GuestAddress, GuestMemory, GuestUsize};
/// Errors thrown while configuring aarch64 system.
#[derive(Debug)]
@@ -33,13 +33,13 @@ pub enum Error {
WriteFdtToMemory(fdt::Error),
/// Failed to create a GIC.
SetupGic,
SetupGic(gic::Error),
/// Failed to compute the initramfs address.
InitramfsAddress,
/// Error configuring the general purpose registers
RegsConfiguration(hypervisor::HypervisorCpuError),
RegsConfiguration(regs::Error),
/// Error configuring the MPIDR register
VcpuRegMpidr(hypervisor::HypervisorCpuError),
@@ -50,7 +50,7 @@ pub enum Error {
impl From<Error> for super::Error {
fn from(e: Error) -> super::Error {
super::Error::PlatformSpecific(e)
super::Error::AArch64Setup(e)
}
}
@@ -64,27 +64,21 @@ pub struct EntryPoint {
/// Configure the specified VCPU, and return its MPIDR.
pub fn configure_vcpu(
vcpu: &Arc<dyn hypervisor::Vcpu>,
fd: &Arc<dyn hypervisor::Vcpu>,
id: u8,
boot_setup: Option<(EntryPoint, &GuestMemoryAtomic<GuestMemoryMmap>)>,
kernel_entry_point: Option<EntryPoint>,
) -> super::Result<u64> {
if let Some((kernel_entry_point, _guest_memory)) = boot_setup {
vcpu.setup_regs(
id,
kernel_entry_point.entry_addr.raw_value(),
super::layout::FDT_START.raw_value(),
)
.map_err(Error::RegsConfiguration)?;
if let Some(kernel_entry_point) = kernel_entry_point {
regs::setup_regs(fd, id, kernel_entry_point.entry_addr.raw_value())
.map_err(Error::RegsConfiguration)?;
}
let mpidr = vcpu
.get_sys_reg(regs::MPIDR_EL1)
.map_err(Error::VcpuRegMpidr)?;
let mpidr = fd.read_mpidr().map_err(Error::VcpuRegMpidr)?;
Ok(mpidr)
}
pub fn arch_memory_regions() -> Vec<(GuestAddress, usize, RegionType)> {
vec![
pub fn arch_memory_regions(size: GuestUsize) -> Vec<(GuestAddress, usize, RegionType)> {
let mut regions = vec![
// 0 MiB ~ 256 MiB: UEFI, GIC and legacy devices
(
GuestAddress(0),
@@ -103,21 +97,39 @@ pub fn arch_memory_regions() -> Vec<(GuestAddress, usize, RegionType)> {
layout::PCI_MMCONFIG_SIZE as usize,
RegionType::Reserved,
),
// 1GiB ~ 4032 MiB: RAM before the gap
(
];
let ram_32bit_space_size =
layout::MEM_32BIT_RESERVED_START.unchecked_offset_from(layout::RAM_START);
// RAM space
// Case1: guest memory fits before the gap
if size as u64 <= ram_32bit_space_size {
regions.push((layout::RAM_START, size as usize, RegionType::Ram));
// Case2: guest memory extends beyond the gap
} else {
// Push memory before the gap
regions.push((
layout::RAM_START,
layout::MEM_32BIT_RESERVED_START.unchecked_offset_from(layout::RAM_START) as usize,
ram_32bit_space_size as usize,
RegionType::Ram,
),
// 4GiB ~ inf: RAM after the gap
(layout::RAM_64BIT_START, usize::MAX, RegionType::Ram),
// Add the 32-bit reserved memory hole as a reserved region
(
layout::MEM_32BIT_RESERVED_START,
layout::MEM_32BIT_RESERVED_SIZE as usize,
RegionType::Reserved,
),
]
));
// Other memory is placed after 4GiB
regions.push((
layout::RAM_64BIT_START,
(size - ram_32bit_space_size) as usize,
RegionType::Ram,
));
}
// Add the 32-bit reserved memory hole as a reserved region
regions.push((
layout::MEM_32BIT_RESERVED_START,
layout::MEM_32BIT_RESERVED_SIZE as usize,
RegionType::Reserved,
));
regions
}
/// Configures the system and should be called once per vm before starting vcpu threads.
@@ -131,7 +143,7 @@ pub fn configure_system<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::Bui
initrd: &Option<super::InitramfsConfig>,
pci_space_info: &[PciSpaceInfo],
virtio_iommu_bdf: Option<u32>,
gic_device: &Arc<Mutex<dyn Vgic>>,
gic_device: &dyn GicDevice,
numa_nodes: &NumaNodes,
pmu_supported: bool,
) -> super::Result<()> {
@@ -173,15 +185,18 @@ pub fn initramfs_load_addr(
if guest_mem.address_in_range(offset) {
Ok(offset.raw_value())
} else {
Err(super::Error::PlatformSpecific(Error::InitramfsAddress))
Err(super::Error::AArch64Setup(Error::InitramfsAddress))
}
}
None => Err(super::Error::PlatformSpecific(Error::InitramfsAddress)),
None => Err(super::Error::AArch64Setup(Error::InitramfsAddress)),
}
}
pub fn get_host_cpu_phys_bits(hypervisor: &Arc<dyn hypervisor::Hypervisor>) -> u8 {
let host_cpu_phys_bits = hypervisor.get_host_ipa_limit().try_into().unwrap();
pub fn get_host_cpu_phys_bits() -> u8 {
// A dummy hypervisor created only for querying the host IPA size and will
// be freed after the query.
let hv = hypervisor::new().unwrap();
let host_cpu_phys_bits = hv.get_host_ipa_limit().try_into().unwrap();
if host_cpu_phys_bits == 0 {
// Host kernel does not support `get_host_ipa_limit`,
// we return the default value 40 here.
@@ -196,12 +211,26 @@ mod tests {
use super::*;
#[test]
fn test_arch_memory_regions_dram() {
let regions = arch_memory_regions();
fn test_arch_memory_regions_dram_2gb() {
let regions = arch_memory_regions((1usize << 31) as u64); //2GB
assert_eq!(5, regions.len());
assert_eq!(layout::RAM_START, regions[3].0);
assert_eq!((1usize << 31), regions[3].1);
assert_eq!(RegionType::Ram, regions[3].2);
assert_eq!(RegionType::Reserved, regions[4].2);
}
#[test]
fn test_arch_memory_regions_dram_4gb() {
let regions = arch_memory_regions((1usize << 32) as u64); //4GB
let ram_32bit_space_size =
layout::MEM_32BIT_RESERVED_START.unchecked_offset_from(layout::RAM_START) as usize;
assert_eq!(6, regions.len());
assert_eq!(layout::RAM_START, regions[3].0);
assert_eq!(ram_32bit_space_size as usize, regions[3].1);
assert_eq!(RegionType::Ram, regions[3].2);
assert_eq!(RegionType::Reserved, regions[5].2);
assert_eq!(RegionType::Ram, regions[4].2);
assert_eq!(((1usize << 32) - ram_32bit_space_size), regions[4].1);
}
}

View File

@@ -1,43 +1,75 @@
// Copyright 2022 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
// AArch64 system register encoding:
// See https://developer.arm.com/documentation/ddi0487 (chapter D12)
//
// 31 22 21 20 19 18 16 15 12 11 8 7 5 4 0
// +----------+---+-----+-----+-----+-----+-----+----+
// |1101010100| L | op0 | op1 | CRn | CRm | op2 | Rt |
// +----------+---+-----+-----+-----+-----+-----+----+
//
// Notes:
// - L and Rt are reserved as implementation defined fields, ignored.
// 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.
const SYSREG_HEAD: u32 = 0b1101010100u32 << 22;
const SYSREG_OP0_SHIFT: u32 = 19;
const SYSREG_OP0_MASK: u32 = 0b11u32 << 19;
const SYSREG_OP1_SHIFT: u32 = 16;
const SYSREG_OP1_MASK: u32 = 0b111u32 << 16;
const SYSREG_CRN_SHIFT: u32 = 12;
const SYSREG_CRN_MASK: u32 = 0b1111u32 << 12;
const SYSREG_CRM_SHIFT: u32 = 8;
const SYSREG_CRM_MASK: u32 = 0b1111u32 << 8;
const SYSREG_OP2_SHIFT: u32 = 5;
const SYSREG_OP2_MASK: u32 = 0b111u32 << 5;
use hypervisor::kvm::kvm_bindings::{
kvm_regs, user_pt_regs, KVM_REG_ARM64, KVM_REG_ARM_CORE, KVM_REG_SIZE_U64,
};
use hypervisor::{arm64_core_reg_id, offset__of};
use std::sync::Arc;
use std::{mem, result};
use vm_memory::Address;
/// Define the ID of system registers
#[macro_export]
macro_rules! arm64_sys_reg {
($name: tt, $op0: tt, $op1: tt, $crn: tt, $crm: tt, $op2: tt) => {
pub const $name: u32 = SYSREG_HEAD
| ((($op0 as u32) << SYSREG_OP0_SHIFT) & SYSREG_OP0_MASK as u32)
| ((($op1 as u32) << SYSREG_OP1_SHIFT) & SYSREG_OP1_MASK as u32)
| ((($crn as u32) << SYSREG_CRN_SHIFT) & SYSREG_CRN_MASK as u32)
| ((($crm as u32) << SYSREG_CRM_SHIFT) & SYSREG_CRM_MASK as u32)
| ((($op2 as u32) << SYSREG_OP2_SHIFT) & SYSREG_OP2_MASK as u32);
};
/// 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>;
arm64_sys_reg!(MPIDR_EL1, 3, 0, 0, 0, 5);
arm64_sys_reg!(ID_AA64MMFR0_EL1, 3, 0, 0, 7, 0);
arm64_sys_reg!(TTBR1_EL1, 3, 0, 2, 0, 1);
arm64_sys_reg!(TCR_EL1, 3, 0, 2, 0, 2);
#[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;
/// 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) -> Result<()> {
let kreg_off = offset__of!(kvm_regs, regs);
// Get the register index of the PSTATE (Processor State) register.
let pstate = offset__of!(user_pt_regs, pstate) + kreg_off;
vcpu.set_reg(
arm64_core_reg_id!(KVM_REG_SIZE_U64, 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).
let pc = offset__of!(user_pt_regs, pc) + kreg_off;
vcpu.set_reg(arm64_core_reg_id!(KVM_REG_SIZE_U64, pc), boot_ip as u64)
.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`.
let regs0 = offset__of!(user_pt_regs, regs) + kreg_off;
vcpu.set_reg(
arm64_core_reg_id!(KVM_REG_SIZE_U64, regs0),
super::layout::FDT_START.raw_value(),
)
.map_err(Error::SetCoreRegister)?;
}
Ok(())
}

View File

@@ -34,7 +34,9 @@ where
if uefi_size > 0x300000 {
return Err(Error::UefiTooBig);
}
uefi_image.rewind().map_err(|_| Error::SeekUefiStart)?;
uefi_image
.seek(SeekFrom::Start(0))
.map_err(|_| Error::SeekUefiStart)?;
guest_mem
.read_exact_from(guest_addr, uefi_image, uefi_size)
.map_err(|_| Error::ReadUefiImage)

View File

@@ -9,15 +9,15 @@
#[macro_use]
extern crate log;
#[macro_use]
extern crate serde_derive;
#[cfg(target_arch = "x86_64")]
use crate::x86_64::SgxEpcSection;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::fmt;
use std::result;
use std::sync::Arc;
use thiserror::Error;
use versionize::{VersionMap, Versionize, VersionizeError, VersionizeResult};
use versionize_derive::Versionize;
use vm_migration::VersionMapped;
@@ -26,27 +26,31 @@ type GuestMemoryMmap = vm_memory::GuestMemoryMmap<vm_memory::bitmap::AtomicBitma
type GuestRegionMmap = vm_memory::GuestRegionMmap<vm_memory::bitmap::AtomicBitmap>;
/// Type for returning error code.
#[derive(Debug, Error)]
#[derive(Debug)]
pub enum Error {
#[cfg(target_arch = "x86_64")]
#[error("Platform specific error (x86_64): {0:?}")]
PlatformSpecific(x86_64::Error),
/// X86_64 specific error triggered during system configuration.
X86_64Setup(x86_64::Error),
#[cfg(target_arch = "aarch64")]
#[error("Platform specific error (aarch64): {0:?}")]
PlatformSpecific(aarch64::Error),
#[error("The memory map table extends past the end of guest memory")]
/// 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("Error writing memory map table to guest memory")]
/// Error writing memory map table to guest memory.
MemmapTableSetup,
#[error("The hvm_start_info structure extends past the end of guest memory")]
/// The hvm_start_info structure extends past the end of guest memory.
StartInfoPastRamEnd,
#[error("Error writing hvm_start_info to guest memory")]
/// Error writing hvm_start_info to guest memory.
StartInfoSetup,
#[error("Failed to compute initramfs address")]
/// Failed to compute initramfs address.
InitramfsAddress,
#[error("Error writing module entry to guest memory: {0}")]
ModlistSetup(#[source] vm_memory::GuestMemoryError),
#[error("RSDP extends past the end of guest memory")]
/// Error writing module entry to guest memory.
ModlistSetup(vm_memory::GuestMemoryError),
/// RSDP Beyond Guest Memory
RsdpPastRamEnd,
}
@@ -54,7 +58,7 @@ pub enum Error {
pub type Result<T> = result::Result<T, Error>;
/// Type for memory region types.
#[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize, Versionize)]
#[derive(Clone, Copy, PartialEq, Debug, Serialize, Deserialize, Versionize)]
pub enum RegionType {
/// RAM type
Ram,
@@ -82,7 +86,7 @@ pub mod aarch64;
pub use aarch64::{
arch_memory_regions, configure_system, configure_vcpu, fdt::DeviceInfoForFdt,
get_host_cpu_phys_bits, initramfs_load_addr, layout, layout::CMDLINE_MAX_SIZE,
layout::IRQ_BASE, uefi, EntryPoint, _NSIG,
layout::IRQ_BASE, uefi, EntryPoint,
};
#[cfg(target_arch = "x86_64")]
@@ -92,14 +96,14 @@ pub mod x86_64;
pub use x86_64::{
arch_memory_regions, configure_system, configure_vcpu, generate_common_cpuid,
get_host_cpu_phys_bits, initramfs_load_addr, layout, layout::CMDLINE_MAX_SIZE,
layout::CMDLINE_START, regs, CpuidFeatureEntry, EntryPoint, _NSIG,
layout::CMDLINE_START, regs, CpuidFeatureEntry, EntryPoint,
};
/// Safe wrapper for `sysconf(_SC_PAGESIZE)`.
#[cfg(target_arch = "x86_64")]
#[inline(always)]
fn pagesize() -> usize {
// SAFETY: Trivially safe
// Trivially safe
unsafe { libc::sysconf(libc::_SC_PAGESIZE) as usize }
}
@@ -145,7 +149,7 @@ pub const PAGE_SIZE: usize = 4096;
impl fmt::Display for DeviceType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{self:?}")
write!(f, "{:?}", self)
}
}

View File

@@ -5,6 +5,10 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE-BSD-3-Clause file.
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use hypervisor::x86_64::LapicState;
use std::io::Cursor;
use std::mem;
use std::result;
use std::sync::Arc;
@@ -16,6 +20,32 @@ pub const APIC_LVT1: usize = 0x360;
pub const APIC_MODE_NMI: u32 = 0x4;
pub const APIC_MODE_EXTINT: u32 = 0x7;
pub fn get_klapic_reg(klapic: &LapicState, 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).
mem::transmute::<&[i8], &[u8]>(&klapic.regs[reg_offset..])
};
let mut reader = Cursor::new(sliceu8);
// Following call can't fail if the offsets defined above are correct.
reader
.read_u32::<LittleEndian>()
.expect("Failed to read klapic register")
}
pub fn set_klapic_reg(klapic: &mut LapicState, 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).
mem::transmute::<&mut [i8], &mut [u8]>(&mut klapic.regs[reg_offset..])
};
let mut writer = Cursor::new(sliceu8);
// Following call can't fail if the offsets defined above are correct.
writer
.write_u32::<LittleEndian>(value)
.expect("Failed to write klapic register")
}
pub fn set_apic_delivery_mode(reg: u32, mode: u32) -> u32 {
((reg) & !0x700) | ((mode) << 8)
}
@@ -27,13 +57,42 @@ pub fn set_apic_delivery_mode(reg: u32, mode: u32) -> u32 {
pub fn set_lint(vcpu: &Arc<dyn hypervisor::Vcpu>) -> Result<()> {
let mut klapic = vcpu.get_lapic()?;
let lvt_lint0 = klapic.get_klapic_reg(APIC_LVT0);
klapic.set_klapic_reg(
let lvt_lint0 = get_klapic_reg(&klapic, APIC_LVT0);
set_klapic_reg(
&mut klapic,
APIC_LVT0,
set_apic_delivery_mode(lvt_lint0, APIC_MODE_EXTINT),
);
let lvt_lint1 = klapic.get_klapic_reg(APIC_LVT1);
klapic.set_klapic_reg(APIC_LVT1, set_apic_delivery_mode(lvt_lint1, APIC_MODE_NMI));
let lvt_lint1 = get_klapic_reg(&klapic, APIC_LVT1);
set_klapic_reg(
&mut klapic,
APIC_LVT1,
set_apic_delivery_mode(lvt_lint1, APIC_MODE_NMI),
);
vcpu.set_lapic(&klapic)
}
#[cfg(test)]
mod tests {
use super::*;
const KVM_APIC_REG_SIZE: usize = 0x400;
#[test]
fn test_set_and_get_klapic_reg() {
let reg_offset = 0x340;
let mut klapic = LapicState::default();
set_klapic_reg(&mut klapic, reg_offset, 3);
let value = get_klapic_reg(&klapic, reg_offset);
assert_eq!(value, 3);
}
#[test]
#[should_panic]
fn test_set_and_get_klapic_out_of_bounds() {
let reg_offset = KVM_APIC_REG_SIZE + 10;
let mut klapic = LapicState::default();
set_klapic_reg(&mut klapic, reg_offset, 3);
}
}

View File

@@ -107,11 +107,6 @@ pub const KVM_TSS_SIZE: u64 = (3 * 4) << 10;
pub const KVM_IDENTITY_MAP_START: GuestAddress = GuestAddress(KVM_TSS_START.0 + KVM_TSS_SIZE);
pub const KVM_IDENTITY_MAP_SIZE: u64 = 4 << 10;
/// TPM Address Range
/// This Address range is specific to CRB Interface
pub const TPM_START: GuestAddress = GuestAddress(0xfed4_0000);
pub const TPM_SIZE: u64 = 0x1000;
// IOAPIC
pub const IOAPIC_START: GuestAddress = GuestAddress(0xfec0_0000);
pub const IOAPIC_SIZE: u64 = 0x20;

View File

@@ -15,8 +15,7 @@ pub mod regs;
use crate::GuestMemoryMmap;
use crate::InitramfsConfig;
use crate::RegionType;
use hypervisor::arch::x86::{CpuIdEntry, CPUID_FLAG_VALID_INDEX};
use hypervisor::{CpuVendor, HypervisorCpuError, HypervisorError};
use hypervisor::{CpuId, CpuIdEntry, HypervisorError, CPUID_FLAG_VALID_INDEX};
use linux_loader::loader::bootparam::boot_params;
use linux_loader::loader::elf::start_info::{
hvm_memmap_table_entry, hvm_modlist_entry, hvm_start_info,
@@ -36,9 +35,9 @@ pub mod tdx;
const TSC_DEADLINE_TIMER_ECX_BIT: u8 = 24; // tsc deadline timer ecx bit.
const HYPERVISOR_ECX_BIT: u8 = 31; // Hypervisor ecx bit.
const MTRR_EDX_BIT: u8 = 12; // Hypervisor ecx bit.
const INVARIANT_TSC_EDX_BIT: u8 = 8; // Invariant TSC bit on 0x8000_0007 EDX
// KVM feature bits
const KVM_FEATURE_ASYNC_PF_INT_BIT: u8 = 14;
#[cfg(feature = "tdx")]
const KVM_FEATURE_CLOCKSOURCE_BIT: u8 = 0;
#[cfg(feature = "tdx")]
@@ -52,15 +51,13 @@ const KVM_FEATURE_ASYNC_PF_VMEXIT_BIT: u8 = 10;
#[cfg(feature = "tdx")]
const KVM_FEATURE_STEAL_TIME_BIT: u8 = 5;
pub const _NSIG: i32 = 65;
#[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,
pub entry_addr: Option<GuestAddress>,
}
const E820_RAM: u32 = 1;
@@ -127,11 +124,9 @@ struct MemmapTableEntryWrapper(hvm_memmap_table_entry);
#[derive(Copy, Clone, Default)]
struct ModlistEntryWrapper(hvm_modlist_entry);
// SAFETY: data structure only contain a series of integers
// SAFETY: These data structures only contain a series of integers
unsafe impl ByteValued for StartInfoWrapper {}
// SAFETY: data structure only contain a series of integers
unsafe impl ByteValued for MemmapTableEntryWrapper {}
// SAFETY: data structure only contain a series of integers
unsafe impl ByteValued for ModlistEntryWrapper {}
// This is a workaround to the Rust enforcement specifying that any implementation of a foreign
@@ -195,9 +190,6 @@ pub enum Error {
// Error writing EBDA address
EbdaSetup(vm_memory::GuestMemoryError),
// Error getting CPU TSC frequency
GetTscFrequency(HypervisorCpuError),
/// Error retrieving TDX capabilities through the hypervisor (kvm/mshv) API
#[cfg(feature = "tdx")]
TdxCapabilities(HypervisorError),
@@ -205,10 +197,11 @@ pub enum Error {
impl From<Error> for super::Error {
fn from(e: Error) -> super::Error {
super::Error::PlatformSpecific(e)
super::Error::X86_64Setup(e)
}
}
#[allow(dead_code, clippy::upper_case_acronyms)]
#[derive(Copy, Clone, Debug)]
pub enum CpuidReg {
EAX,
@@ -229,15 +222,17 @@ pub struct CpuidPatch {
impl CpuidPatch {
pub fn set_cpuid_reg(
cpuid: &mut Vec<CpuIdEntry>,
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 cpuid.iter_mut() {
if entry.function == function && (index.is_none() || index.unwrap() == entry.index) {
for entry in entries.iter_mut() {
if entry.function == function && (index == None || index.unwrap() == entry.index) {
entry_found = true;
match reg {
CpuidReg::EAX => {
@@ -283,12 +278,16 @@ impl CpuidPatch {
}
}
cpuid.push(entry);
if let Err(e) = cpuid.push(entry) {
error!("Failed adding new CPUID entry: {:?}", e);
}
}
}
pub fn patch_cpuid(cpuid: &mut [CpuIdEntry], patches: Vec<CpuidPatch>) {
for entry in cpuid {
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 {
@@ -312,15 +311,16 @@ impl CpuidPatch {
}
pub fn is_feature_enabled(
cpuid: &[CpuIdEntry],
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 cpuid {
for entry in entries.iter() {
if entry.function == function && entry.index == index {
let reg_val = match reg {
CpuidReg::EAX => entry.eax,
@@ -469,12 +469,12 @@ impl CpuidFeatureEntry {
}
fn get_features_from_cpuid(
cpuid: &[CpuIdEntry],
cpuid: &CpuId,
feature_entry_list: &[CpuidFeatureEntry],
) -> Vec<u32> {
let mut features = vec![0; feature_entry_list.len()];
for (i, feature_entry) in feature_entry_list.iter().enumerate() {
for cpuid_entry in cpuid {
for cpuid_entry in cpuid.as_slice().iter() {
if cpuid_entry.function == feature_entry.function
&& cpuid_entry.index == feature_entry.index
{
@@ -504,8 +504,8 @@ impl CpuidFeatureEntry {
// The function returns `Error` (a.k.a. "incompatible"), when the CPUID features from `src_vm_cpuid`
// is not a subset of those of the `dest_vm_cpuid`.
pub fn check_cpuid_compatibility(
src_vm_cpuid: &[CpuIdEntry],
dest_vm_cpuid: &[CpuIdEntry],
src_vm_cpuid: &CpuId,
dest_vm_cpuid: &CpuId,
) -> Result<(), Error> {
let feature_entry_list = &Self::checked_feature_entry_list();
let src_vm_features = Self::get_features_from_cpuid(src_vm_cpuid, feature_entry_list);
@@ -551,30 +551,13 @@ impl CpuidFeatureEntry {
}
pub fn generate_common_cpuid(
hypervisor: &Arc<dyn hypervisor::Hypervisor>,
hypervisor: Arc<dyn hypervisor::Hypervisor>,
topology: Option<(u8, u8, u8)>,
sgx_epc_sections: Option<Vec<SgxEpcSection>>,
phys_bits: u8,
kvm_hyperv: bool,
#[cfg(feature = "tdx")] tdx_enabled: bool,
) -> super::Result<Vec<CpuIdEntry>> {
// SAFETY: cpuid called with valid leaves
if unsafe { x86_64::__cpuid(1) }.ecx & 1 << HYPERVISOR_ECX_BIT == 1 << HYPERVISOR_ECX_BIT {
// SAFETY: cpuid called with valid leaves
let hypervisor_cpuid = unsafe { x86_64::__cpuid(0x4000_0000) };
let mut identifier: [u8; 12] = [0; 12];
identifier[0..4].copy_from_slice(&hypervisor_cpuid.ebx.to_le_bytes()[..]);
identifier[4..8].copy_from_slice(&hypervisor_cpuid.ecx.to_le_bytes()[..]);
identifier[8..12].copy_from_slice(&hypervisor_cpuid.edx.to_le_bytes()[..]);
info!(
"Running under nested virtualisation. Hypervisor string: {}",
String::from_utf8_lossy(&identifier)
);
}
info!("Generating guest CPUID for with physical address size: {phys_bits}");
) -> super::Result<CpuId> {
let cpuid_patches = vec![
// Patch tsc deadline timer bit
CpuidPatch {
@@ -609,9 +592,7 @@ pub fn generate_common_cpuid(
];
// Supported CPUID
let mut cpuid = hypervisor
.get_supported_cpuid()
.map_err(Error::CpuidGetSupported)?;
let mut cpuid = hypervisor.get_cpuid().map_err(Error::CpuidGetSupported)?;
CpuidPatch::patch_cpuid(&mut cpuid, cpuid_patches);
@@ -656,25 +637,18 @@ pub fn generate_common_cpuid(
}
}
}
// Copy host L2 cache details if not populated by KVM
0x8000_0006 => {
if entry.eax == 0 && entry.ebx == 0 && entry.ecx == 0 && entry.edx == 0 {
// SAFETY: cpuid called with valid leaves
if unsafe { std::arch::x86_64::__cpuid(0x8000_0000).eax } >= 0x8000_0006 {
// SAFETY: cpuid called with valid leaves
let leaf = unsafe { std::arch::x86_64::__cpuid(0x8000_0006) };
entry.eax = leaf.eax;
entry.ebx = leaf.ebx;
entry.ecx = leaf.ecx;
entry.edx = leaf.edx;
}
}
}
// Set CPU physical bits
0x8000_0008 => {
entry.eax = (entry.eax & 0xffff_ff00) | (phys_bits as u32 & 0xff);
}
// Disable KVM_FEATURE_ASYNC_PF_INT
// This is required until we find out why the asynchronous page
// fault is generating unexpected behavior when using interrupt
// mechanism.
// TODO: Re-enable KVM_FEATURE_ASYNC_PF_INT (#2277)
0x4000_0001 => {
entry.eax &= !(1 << KVM_FEATURE_ASYNC_PF_INT_BIT);
// These features are not supported by TDX
#[cfg(feature = "tdx")]
if tdx_enabled {
@@ -693,16 +667,17 @@ pub fn generate_common_cpuid(
// Copy CPU identification string
for i in 0x8000_0002..=0x8000_0004 {
cpuid.retain(|c| c.function != i);
// SAFETY: call cpuid with valid leaves
let leaf = unsafe { std::arch::x86_64::__cpuid(i) };
cpuid.push(CpuIdEntry {
function: i,
eax: leaf.eax,
ebx: leaf.ebx,
ecx: leaf.ecx,
edx: leaf.edx,
..Default::default()
});
cpuid
.push(CpuIdEntry {
function: i,
eax: leaf.eax,
ebx: leaf.ebx,
ecx: leaf.ecx,
edx: leaf.edx,
..Default::default()
})
.map_err(Error::CpuidIdentification)?;
}
if kvm_hyperv {
@@ -711,44 +686,56 @@ pub fn generate_common_cpuid(
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()
});
cpuid.push(CpuIdEntry {
function: 0x40000001,
eax: 0x31237648, // "Hv#1"
..Default::default()
});
cpuid.push(CpuIdEntry {
function: 0x40000002,
eax: 0x3839, // "Build number"
ebx: 0xa0000, // "Version"
..Default::default()
});
cpuid.push(CpuIdEntry {
function: 0x4000_0003,
eax: 1 << 1 // AccessPartitionReferenceCounter
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::CpuidKvmHyperV)?;
cpuid
.push(CpuIdEntry {
function: 0x40000001,
eax: 0x31237648, // "Hv#1"
..Default::default()
})
.map_err(Error::CpuidKvmHyperV)?;
cpuid
.push(CpuIdEntry {
function: 0x40000002,
eax: 0x3839, // "Build number"
ebx: 0xa0000, // "Version"
..Default::default()
})
.map_err(Error::CpuidKvmHyperV)?;
cpuid
.push(CpuIdEntry {
function: 0x4000_0003,
eax: 1 << 1 // AccessPartitionReferenceCounter
| 1 << 2 // AccessSynicRegs
| 1 << 3 // AccessSyntheticTimerRegs
| 1 << 9, // AccessPartitionReferenceTsc
edx: 1 << 3, // CPU dynamic partitioning
..Default::default()
});
cpuid.push(CpuIdEntry {
function: 0x4000_0004,
eax: 1 << 5, // Recommend relaxed timing
..Default::default()
});
for i in 0x4000_0005..=0x4000_000a {
cpuid.push(CpuIdEntry {
function: i,
edx: 1 << 3, // CPU dynamic partitioning
..Default::default()
});
})
.map_err(Error::CpuidKvmHyperV)?;
cpuid
.push(CpuIdEntry {
function: 0x4000_0004,
eax: 1 << 5, // Recommend relaxed timing
..Default::default()
})
.map_err(Error::CpuidKvmHyperV)?;
for i in 0x4000_0005..=0x4000_000a {
cpuid
.push(CpuIdEntry {
function: i,
..Default::default()
})
.map_err(Error::CpuidKvmHyperV)?;
}
}
@@ -756,10 +743,11 @@ pub fn generate_common_cpuid(
}
pub fn configure_vcpu(
vcpu: &Arc<dyn hypervisor::Vcpu>,
fd: &Arc<dyn hypervisor::Vcpu>,
id: u8,
boot_setup: Option<(EntryPoint, &GuestMemoryAtomic<GuestMemoryMmap>)>,
cpuid: Vec<CpuIdEntry>,
kernel_entry_point: Option<EntryPoint>,
vm_memory: &GuestMemoryAtomic<GuestMemoryMmap>,
cpuid: CpuId,
kvm_hyperv: bool,
) -> super::Result<()> {
// Per vCPU CPUID changes; common are handled via generate_common_cpuid()
@@ -767,56 +755,23 @@ pub fn configure_vcpu(
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));
// Set ApicId in cpuid for each vcpu
// SAFETY: get host cpuid when eax=1
let mut cpu_ebx = unsafe { core::arch::x86_64::__cpuid(1) }.ebx;
cpu_ebx &= 0xffffff;
cpu_ebx |= (id as u32) << 24;
CpuidPatch::set_cpuid_reg(&mut cpuid, 0x1, None, CpuidReg::EBX, cpu_ebx);
// The TSC frequency CPUID leaf should not be included when running with HyperV emulation
if !kvm_hyperv {
if let Some(tsc_khz) = vcpu.tsc_khz().map_err(Error::GetTscFrequency)? {
// Need to check that the TSC doesn't vary with dynamic frequency
// SAFETY: cpuid called with valid leaves
if unsafe { std::arch::x86_64::__cpuid(0x8000_0007) }.edx
& (1u32 << INVARIANT_TSC_EDX_BIT)
> 0
{
CpuidPatch::set_cpuid_reg(
&mut cpuid,
0x4000_0000,
None,
CpuidReg::EAX,
0x4000_0010,
);
cpuid.retain(|c| c.function != 0x4000_0010);
cpuid.push(CpuIdEntry {
function: 0x4000_0010,
eax: tsc_khz,
ebx: 1000000, /* LAPIC resolution of 1ns (freq: 1GHz) is hardcoded in KVM's
* APIC_BUS_CYCLE_NS */
..Default::default()
});
};
}
}
vcpu.set_cpuid2(&cpuid)
fd.set_cpuid2(&cpuid)
.map_err(|e| Error::SetSupportedCpusFailed(e.into()))?;
if kvm_hyperv {
vcpu.enable_hyperv_synic().unwrap();
fd.enable_hyperv_synic().unwrap();
}
regs::setup_msrs(vcpu).map_err(Error::MsrsConfiguration)?;
if let Some((kernel_entry_point, guest_memory)) = boot_setup {
regs::setup_regs(vcpu, kernel_entry_point.entry_addr.raw_value())
.map_err(Error::RegsConfiguration)?;
regs::setup_fpu(vcpu).map_err(Error::FpuConfiguration)?;
regs::setup_sregs(&guest_memory.memory(), vcpu).map_err(Error::SregsConfiguration)?;
regs::setup_msrs(fd).map_err(Error::MsrsConfiguration)?;
if let Some(kernel_entry_point) = kernel_entry_point {
if let Some(entry_addr) = kernel_entry_point.entry_addr {
// Safe to unwrap because this method is called after the VM is configured
regs::setup_regs(fd, entry_addr.raw_value()).map_err(Error::RegsConfiguration)?;
regs::setup_fpu(fd).map_err(Error::FpuConfiguration)?;
regs::setup_sregs(&vm_memory.memory(), fd).map_err(Error::SregsConfiguration)?;
}
}
interrupts::set_lint(vcpu).map_err(|e| Error::LocalIntConfiguration(e.into()))?;
interrupts::set_lint(fd).map_err(|e| Error::LocalIntConfiguration(e.into()))?;
Ok(())
}
@@ -824,29 +779,47 @@ pub fn configure_vcpu(
/// 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
/// carve out at the end of 32bit address space.
pub fn arch_memory_regions() -> Vec<(GuestAddress, usize, RegionType)> {
vec![
// 0 GiB ~ 3GiB: memory before the gap
(
pub fn arch_memory_regions(size: GuestUsize) -> Vec<(GuestAddress, usize, RegionType)> {
let reserved_memory_gap_start = layout::MEM_32BIT_RESERVED_START
.checked_add(layout::MEM_32BIT_DEVICES_SIZE)
.expect("32-bit reserved region is too large");
let requested_memory_size = GuestAddress(size as u64);
let mut regions = Vec::new();
// case1: guest memory fits before the gap
if size as u64 <= layout::MEM_32BIT_RESERVED_START.raw_value() {
regions.push((GuestAddress(0), size as usize, RegionType::Ram));
// case2: guest memory extends beyond the gap
} else {
// push memory before the gap
regions.push((
GuestAddress(0),
layout::MEM_32BIT_RESERVED_START.raw_value() as usize,
RegionType::Ram,
),
// 4 GiB ~ inf: memory after the gap
(layout::RAM_64BIT_START, usize::MAX, RegionType::Ram),
// 3 GiB ~ 3712 MiB: 32-bit device memory hole
(
layout::MEM_32BIT_RESERVED_START,
layout::MEM_32BIT_DEVICES_SIZE as usize,
RegionType::SubRegion,
),
// 3712 MiB ~ 3968 MiB: 32-bit reserved memory hole
(
layout::MEM_32BIT_RESERVED_START.unchecked_add(layout::MEM_32BIT_DEVICES_SIZE),
(layout::MEM_32BIT_RESERVED_SIZE - layout::MEM_32BIT_DEVICES_SIZE) as usize,
RegionType::Reserved,
),
]
));
regions.push((
layout::RAM_64BIT_START,
requested_memory_size.unchecked_offset_from(layout::MEM_32BIT_RESERVED_START) as usize,
RegionType::Ram,
));
}
// Add the 32-bit device memory hole as a sub region.
regions.push((
layout::MEM_32BIT_RESERVED_START,
layout::MEM_32BIT_DEVICES_SIZE as usize,
RegionType::SubRegion,
));
// Add the 32-bit reserved memory hole as a sub region.
regions.push((
reserved_memory_gap_start,
(layout::MEM_32BIT_RESERVED_SIZE - layout::MEM_32BIT_DEVICES_SIZE) as usize,
RegionType::Reserved,
));
regions
}
/// Configures the system and should be called once per vm before starting vcpu threads.
@@ -865,17 +838,13 @@ pub fn configure_system(
_num_cpus: u8,
rsdp_addr: Option<GuestAddress>,
sgx_epc_region: Option<SgxEpcRegion>,
serial_number: Option<&str>,
uuid: Option<&str>,
oem_strings: Option<&[&str]>,
) -> super::Result<()> {
// Write EBDA address to location where ACPICA expects to find it
guest_mem
.write_obj((layout::EBDA_START.0 >> 4) as u16, layout::EBDA_POINTER)
.map_err(Error::EbdaSetup)?;
let size = smbios::setup_smbios(guest_mem, serial_number, uuid, oem_strings)
.map_err(Error::SmbiosSetup)?;
let size = smbios::setup_smbios(guest_mem).map_err(Error::SmbiosSetup)?;
// Place the MP table after the SMIOS table aligned to 16 bytes
let offset = GuestAddress(layout::SMBIOS_START).unchecked_add(size);
@@ -912,7 +881,7 @@ fn configure_pvh(
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();
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 {
@@ -944,102 +913,30 @@ fn configure_pvh(
// Create the memory map entries.
add_memmap_entry(&mut memmap, 0, layout::EBDA_START.raw_value(), E820_RAM);
// Merge continuous memory regions into one region.
// Note: memory regions from "GuestMemory" are sorted and non-zero sized.
let ram_regions = {
let mut ram_regions = Vec::new();
let mut current_start = guest_mem
.iter()
.next()
.map(GuestMemoryRegion::start_addr)
.expect("GuestMemory must have one memory region at least")
.raw_value();
let mut current_end = current_start;
for (start, size) in guest_mem
.iter()
.map(|m| (m.start_addr().raw_value(), m.len()))
{
if current_end == start {
// This zone is continuous with the previous one.
current_end += size;
} else {
ram_regions.push((current_start, current_end));
current_start = start;
current_end = start + size;
}
}
ram_regions.push((current_start, current_end));
ram_regions
};
if ram_regions.len() > 2 {
error!(
"There should be up to two non-continuous regions, devidided by the
gap at the end of 32bit address space (e.g. between 3G and 4G)."
);
return Err(super::Error::MemmapTableSetup);
}
// Create the memory map entry for memory region before the gap
{
let (first_region_start, first_region_end) =
ram_regions.first().ok_or(super::Error::MemmapTableSetup)?;
let high_ram_start = layout::HIGH_RAM_START.raw_value();
let mem_32bit_reserved_start = layout::MEM_32BIT_RESERVED_START.raw_value();
if !((first_region_start <= &high_ram_start)
&& (first_region_end > &high_ram_start)
&& (first_region_end <= &mem_32bit_reserved_start))
{
error!(
"Unexpected first memory region layout: (start: 0x{:08x}, end: 0x{:08x}).
high_ram_start: 0x{:08x}, mem_32bit_reserved_start: 0x{:08x}",
first_region_start, first_region_end, high_ram_start, mem_32bit_reserved_start
);
return Err(super::Error::MemmapTableSetup);
}
info!(
"create_memmap_entry, start: 0x{:08x}, end: 0x{:08x}",
high_ram_start, first_region_end
);
let mem_end = guest_mem.last_addr();
if mem_end < layout::MEM_32BIT_RESERVED_START {
add_memmap_entry(
&mut memmap,
high_ram_start,
first_region_end - high_ram_start,
layout::HIGH_RAM_START.raw_value(),
mem_end.unchecked_offset_from(layout::HIGH_RAM_START) + 1,
E820_RAM,
);
}
// Create the memory map entry for memory region after the gap if any
if let Some((second_region_start, second_region_end)) = ram_regions.get(1) {
let ram_64bit_start = layout::RAM_64BIT_START.raw_value();
if second_region_start != &ram_64bit_start {
error!(
"Unexpected second memory region layout: start: 0x{:08x}, ram_64bit_start: 0x{:08x}",
second_region_start, ram_64bit_start
);
return Err(super::Error::MemmapTableSetup);
}
info!(
"create_memmap_entry, start: 0x{:08x}, end: 0x{:08x}",
ram_64bit_start, second_region_end
);
} else {
add_memmap_entry(
&mut memmap,
ram_64bit_start,
second_region_end - ram_64bit_start,
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(
@@ -1053,7 +950,7 @@ fn configure_pvh(
add_memmap_entry(
&mut memmap,
sgx_epc_region.start().raw_value(),
sgx_epc_region.size(),
sgx_epc_region.size() as u64,
E820_RESERVED,
);
}
@@ -1129,8 +1026,7 @@ pub fn initramfs_load_addr(
Ok(aligned_addr)
}
pub fn get_host_cpu_phys_bits(hypervisor: &Arc<dyn hypervisor::Hypervisor>) -> u8 {
// SAFETY: call cpuid with valid leaves
pub fn get_host_cpu_phys_bits() -> u8 {
unsafe {
let leaf = x86_64::__cpuid(0x8000_0000);
@@ -1138,7 +1034,9 @@ pub fn get_host_cpu_phys_bits(hypervisor: &Arc<dyn hypervisor::Hypervisor>) -> u
// 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
&& matches!(hypervisor.get_cpu_vendor(), CpuVendor::AMD)
&& 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
@@ -1156,7 +1054,7 @@ pub fn get_host_cpu_phys_bits(hypervisor: &Arc<dyn hypervisor::Hypervisor>) -> u
}
fn update_cpuid_topology(
cpuid: &mut Vec<CpuIdEntry>,
cpuid: &mut CpuId,
threads_per_core: u8,
cores_per_die: u8,
dies_per_package: u8,
@@ -1220,10 +1118,7 @@ fn update_cpuid_topology(
// The goal is to update the CPUID sub-leaves to reflect the number of EPC
// sections exposed to the guest.
fn update_cpuid_sgx(
cpuid: &mut Vec<CpuIdEntry>,
epc_sections: Vec<SgxEpcSection>,
) -> Result<(), Error> {
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);
@@ -1239,13 +1134,12 @@ fn update_cpuid_sgx(
// Get host CPUID for leaf 0x12, subleaf 0x2. This is to retrieve EPC
// properties such as confidentiality and integrity.
// SAFETY: call cpuid with valid leaves
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();
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);
@@ -1273,8 +1167,16 @@ mod tests {
use super::*;
#[test]
fn regions_base_addr() {
let regions = arch_memory_regions();
fn regions_lt_4gb() {
let regions = arch_memory_regions(1 << 29);
assert_eq!(3, regions.len());
assert_eq!(GuestAddress(0), regions[0].0);
assert_eq!(1usize << 29, regions[0].1);
}
#[test]
fn regions_gt_4gb() {
let regions = arch_memory_regions((1 << 32) + 0x8000);
assert_eq!(4, regions.len());
assert_eq!(GuestAddress(0), regions[0].0);
assert_eq!(GuestAddress(1 << 32), regions[1].0);
@@ -1291,73 +1193,46 @@ mod tests {
1,
Some(layout::RSDP_POINTER),
None,
None,
None,
None,
);
assert!(config_err.is_err());
// Now assigning some memory that falls before the 32bit memory hole.
let arch_mem_regions = arch_memory_regions();
let mem_size = 128 << 20;
let arch_mem_regions = arch_memory_regions(mem_size);
let ram_regions: Vec<(GuestAddress, usize)> = arch_mem_regions
.iter()
.filter(|r| r.2 == RegionType::Ram && r.1 != usize::MAX)
.filter(|r| r.2 == RegionType::Ram)
.map(|r| (r.0, r.1))
.collect();
let gm = GuestMemoryMmap::from_ranges(&ram_regions).unwrap();
configure_system(
&gm,
GuestAddress(0),
&None,
no_vcpus,
None,
None,
None,
None,
None,
)
.unwrap();
configure_system(&gm, GuestAddress(0), &None, no_vcpus, None, None).unwrap();
// Now assigning some memory that falls after the 32bit memory hole.
let arch_mem_regions = arch_memory_regions();
// Now assigning some memory that is equal to the start of the 32bit memory hole.
let mem_size = 3328 << 20;
let arch_mem_regions = arch_memory_regions(mem_size);
let ram_regions: Vec<(GuestAddress, usize)> = arch_mem_regions
.iter()
.filter(|r| r.2 == RegionType::Ram)
.map(|r| {
if r.1 == usize::MAX {
(r.0, 128 << 20)
} else {
(r.0, r.1)
}
})
.map(|r| (r.0, r.1))
.collect();
let gm = GuestMemoryMmap::from_ranges(&ram_regions).unwrap();
configure_system(
&gm,
GuestAddress(0),
&None,
no_vcpus,
None,
None,
None,
None,
None,
)
.unwrap();
configure_system(&gm, GuestAddress(0), &None, no_vcpus, None, None).unwrap();
configure_system(
&gm,
GuestAddress(0),
&None,
no_vcpus,
None,
None,
None,
None,
None,
)
.unwrap();
configure_system(&gm, GuestAddress(0), &None, no_vcpus, None, None).unwrap();
// Now assigning some memory that falls after the 32bit memory hole.
let mem_size = 3330 << 20;
let arch_mem_regions = arch_memory_regions(mem_size);
let ram_regions: Vec<(GuestAddress, usize)> = arch_mem_regions
.iter()
.filter(|r| r.2 == RegionType::Ram)
.map(|r| (r.0, r.1))
.collect();
let gm = GuestMemoryMmap::from_ranges(&ram_regions).unwrap();
configure_system(&gm, GuestAddress(0), &None, no_vcpus, None, None).unwrap();
configure_system(&gm, GuestAddress(0), &None, no_vcpus, None, None).unwrap();
}
#[test]
@@ -1382,6 +1257,6 @@ mod tests {
add_memmap_entry(&mut memmap, 0, 0x1000, E820_RAM);
add_memmap_entry(&mut memmap, 0x10000, 0xa000, E820_RESERVED);
assert_eq!(format!("{memmap:?}"), format!("{expected_memmap:?}"));
assert_eq!(format!("{:?}", memmap), format!("{:?}", expected_memmap));
}
}

View File

@@ -37,17 +37,11 @@ struct MpfIntelWrapper(mpspec::mpf_intel);
// SAFETY: These `mpspec` wrapper types are only data, reading them from data is a safe initialization.
unsafe impl ByteValued for MpcBusWrapper {}
// SAFETY: see above
unsafe impl ByteValued for MpcCpuWrapper {}
// SAFETY: see above
unsafe impl ByteValued for MpcIntsrcWrapper {}
// SAFETY: see above
unsafe impl ByteValued for MpcIoapicWrapper {}
// SAFETY: see above
unsafe impl ByteValued for MpcTableWrapper {}
// SAFETY: see above
unsafe impl ByteValued for MpcLintsrcWrapper {}
// SAFETY: see above
unsafe impl ByteValued for MpfIntelWrapper {}
#[derive(Debug)]
@@ -101,7 +95,7 @@ const CPU_FEATURE_APIC: u32 = 0x200;
const CPU_FEATURE_FPU: u32 = 0x001;
fn compute_checksum<T: Copy>(v: &T) -> u8 {
// SAFETY: we are only reading the bytes within the size of the `T` reference `v`.
// 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() {
@@ -300,7 +294,7 @@ mod tests {
mpspec::MP_IOAPIC => mem::size_of::<MpcIoapicWrapper>(),
mpspec::MP_INTSRC => mem::size_of::<MpcIntsrcWrapper>(),
mpspec::MP_LINTSRC => mem::size_of::<MpcLintsrcWrapper>(),
_ => panic!("unrecognized mpc table entry type: {type_}"),
_ => panic!("unrecognized mpc table entry type: {}", type_),
}
}

View File

@@ -9,8 +9,8 @@
use crate::layout::{BOOT_GDT_START, BOOT_IDT_START, PVH_INFO_START};
use crate::GuestMemoryMmap;
use hypervisor::arch::x86::gdt::{gdt_entry, segment_from_gdt};
use hypervisor::arch::x86::regs::CR0_PE;
use hypervisor::arch::x86::{FpuState, SpecialRegisters, StandardRegisters};
use hypervisor::arch::x86::regs::*;
use hypervisor::x86_64::{FpuState, SpecialRegisters, StandardRegisters};
use std::sync::Arc;
use std::{mem, result};
use vm_memory::{Address, Bytes, GuestMemory, GuestMemoryError};
@@ -66,7 +66,7 @@ pub fn setup_fpu(vcpu: &Arc<dyn hypervisor::Vcpu>) -> Result<()> {
///
/// * `vcpu` - Structure for the VCPU that holds the VCPU's fd.
pub fn setup_msrs(vcpu: &Arc<dyn hypervisor::Vcpu>) -> Result<()> {
vcpu.set_msrs(&vcpu.boot_msr_entries())
vcpu.set_msrs(&hypervisor::x86_64::boot_msr_entries())
.map_err(Error::SetModelSpecificRegisters)?;
Ok(())
@@ -124,7 +124,7 @@ pub fn configure_segments_and_sregs(
mem: &GuestMemoryMmap,
sregs: &mut SpecialRegisters,
) -> Result<()> {
let gdt_table: [u64; BOOT_GDT_MAX] = {
let gdt_table: [u64; BOOT_GDT_MAX as usize] = {
// Configure GDT entries as specified by PVH boot protocol
[
gdt_entry(0, 0, 0), // NULL

View File

@@ -12,10 +12,10 @@ use std::fmt::{self, Display};
use std::mem;
use std::result;
use std::slice;
use uuid::Uuid;
use vm_memory::ByteValued;
use vm_memory::{Address, Bytes, GuestAddress};
#[allow(unused_variables)]
#[derive(Debug)]
pub enum Error {
/// There was too little guest memory to store the entire SMBIOS table.
@@ -28,8 +28,6 @@ pub enum Error {
WriteSmbiosEp,
/// Failure to write additional data to memory
WriteData,
/// Failure to parse uuid, uuid format may be error
ParseUuid(uuid::Error),
}
impl std::error::Error for Error {}
@@ -39,19 +37,14 @@ impl Display for Error {
use self::Error::*;
let description = match self {
NotEnoughMemory => {
"There was too little guest memory to store the SMBIOS table".to_string()
}
AddressOverflow => {
"The SMBIOS table has too little address space to be stored".to_string()
}
Clear => "Failure while zeroing out the memory for the SMBIOS table".to_string(),
WriteSmbiosEp => "Failure to write SMBIOS entrypoint structure".to_string(),
WriteData => "Failure to write additional data to memory".to_string(),
ParseUuid(e) => format!("Failure to parse uuid: {e}"),
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}")
write!(f, "SMBIOS error: {}", description)
}
}
@@ -61,13 +54,12 @@ pub type Result<T> = result::Result<T, Error>;
const SM3_MAGIC_IDENT: &[u8; 5usize] = b"_SM3_";
const BIOS_INFORMATION: u8 = 0;
const SYSTEM_INFORMATION: u8 = 1;
const OEM_STRINGS: u8 = 11;
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 {
// SAFETY: we are only reading the bytes within the size of the `T` reference `v`.
// 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() {
@@ -76,85 +68,75 @@ fn compute_checksum<T: Copy>(v: &T) -> u8 {
(!checksum).wrapping_add(1)
}
#[repr(C)]
#[repr(packed)]
#[derive(Default, Copy, Clone)]
struct Smbios30Entrypoint {
signature: [u8; 5usize],
checksum: u8,
length: u8,
majorver: u8,
minorver: u8,
docrev: u8,
revision: u8,
reserved: u8,
max_size: u32,
physptr: u64,
#[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,
}
#[repr(C)]
#[repr(packed)]
#[derive(Default, Copy, Clone)]
struct SmbiosBiosInfo {
r#type: u8,
length: u8,
handle: u16,
vendor: u8,
version: u8,
start_addr: u16,
release_date: u8,
rom_size: u8,
characteristics: u64,
characteristics_ext1: u8,
characteristics_ext2: u8,
impl Clone for Smbios30Entrypoint {
fn clone(&self) -> Self {
*self
}
}
#[repr(C)]
#[repr(packed)]
#[derive(Default, Copy, Clone)]
struct SmbiosSysInfo {
r#type: u8,
length: u8,
handle: u16,
manufacturer: u8,
product_name: u8,
version: u8,
serial_number: u8,
uuid: [u8; 16usize],
wake_up_type: u8,
sku: u8,
family: u8,
#[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,
}
#[repr(C)]
#[repr(packed)]
#[derive(Default, Copy, Clone)]
struct SmbiosOemStrings {
r#type: u8,
length: u8,
handle: u16,
count: u8,
impl Clone for SmbiosBiosInfo {
fn clone(&self) -> Self {
*self
}
}
#[repr(C)]
#[repr(packed)]
#[derive(Default, Copy, Clone)]
struct SmbiosEndOfTable {
r#type: u8,
length: u8,
handle: u16,
#[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,
}
// SAFETY: data structure only contain a series of integers
impl Clone for SmbiosSysInfo {
fn clone(&self) -> Self {
*self
}
}
// SAFETY: These data structures only contain a series of integers
unsafe impl ByteValued for Smbios30Entrypoint {}
// SAFETY: data structure only contain a series of integers
unsafe impl ByteValued for SmbiosBiosInfo {}
// SAFETY: data structure only contain a series of integers
unsafe impl ByteValued for SmbiosSysInfo {}
// SAFETY: data structure only contain a series of integers
unsafe impl ByteValued for SmbiosOemStrings {}
// SAFETY: data structure only contain a series of integers
unsafe impl ByteValued for SmbiosEndOfTable {}
fn write_and_incr<T: ByteValued>(
mem: &GuestMemoryMmap,
@@ -180,12 +162,7 @@ fn write_string(
Ok(curptr)
}
pub fn setup_smbios(
mem: &GuestMemoryMmap,
serial_number: Option<&str>,
uuid: Option<&str>,
oem_strings: Option<&[&str]>,
) -> Result<u64> {
pub fn setup_smbios(mem: &GuestMemoryMmap) -> Result<u64> {
let physptr = GuestAddress(SMBIOS_START)
.checked_add(mem::size_of::<Smbios30Entrypoint>() as u64)
.ok_or(Error::NotEnoughMemory)?;
@@ -195,7 +172,7 @@ pub fn setup_smbios(
{
handle += 1;
let smbios_biosinfo = SmbiosBiosInfo {
r#type: BIOS_INFORMATION,
typ: BIOS_INFORMATION,
length: mem::size_of::<SmbiosBiosInfo>() as u8,
handle,
vendor: 1, // First string written in this section
@@ -212,59 +189,29 @@ pub fn setup_smbios(
{
handle += 1;
let uuid_number = uuid
.map(Uuid::parse_str)
.transpose()
.map_err(Error::ParseUuid)?
.unwrap_or(Uuid::nil());
let smbios_sysinfo = SmbiosSysInfo {
r#type: SYSTEM_INFORMATION,
typ: SYSTEM_INFORMATION,
length: mem::size_of::<SmbiosSysInfo>() as u8,
handle,
manufacturer: 1, // First string written in this section
product_name: 2, // Second string written in this section
serial_number: serial_number.map(|_| 3).unwrap_or_default(), // 3rd string
uuid: uuid_number.to_bytes_le(), // set uuid
..Default::default()
};
curptr = write_and_incr(mem, smbios_sysinfo, curptr)?;
curptr = write_string(mem, "Cloud Hypervisor", curptr)?;
curptr = write_string(mem, "cloud-hypervisor", curptr)?;
if let Some(serial_number) = serial_number {
curptr = write_string(mem, serial_number, curptr)?;
}
curptr = write_and_incr(mem, 0u8, curptr)?;
}
if let Some(oem_strings) = oem_strings {
handle += 1;
let smbios_oemstrings = SmbiosOemStrings {
r#type: OEM_STRINGS,
length: mem::size_of::<SmbiosOemStrings>() as u8,
handle,
count: oem_strings.len() as u8,
};
curptr = write_and_incr(mem, smbios_oemstrings, curptr)?;
for s in oem_strings {
curptr = write_string(mem, s, curptr)?;
}
curptr = write_and_incr(mem, 0u8, curptr)?;
}
{
handle += 1;
let smbios_end = SmbiosEndOfTable {
r#type: END_OF_TABLE,
length: mem::size_of::<SmbiosEndOfTable>() as u8,
let smbios_sysinfo = SmbiosSysInfo {
typ: END_OF_TABLE,
length: mem::size_of::<SmbiosSysInfo>() as u8,
handle,
..Default::default()
};
curptr = write_and_incr(mem, smbios_end, curptr)?;
curptr = write_and_incr(mem, 0u8, curptr)?;
curptr = write_and_incr(mem, smbios_sysinfo, curptr)?;
curptr = write_and_incr(mem, 0u8, curptr)?;
}
@@ -286,7 +233,7 @@ pub fn setup_smbios(
.map_err(|_| Error::WriteSmbiosEp)?;
}
Ok(curptr.unchecked_offset_from(physptr) + std::mem::size_of::<Smbios30Entrypoint>() as u64)
Ok(curptr.unchecked_offset_from(physptr))
}
#[cfg(test)]
@@ -316,7 +263,7 @@ mod tests {
fn entrypoint_checksum() {
let mem = GuestMemoryMmap::from_ranges(&[(GuestAddress(SMBIOS_START), 4096)]).unwrap();
setup_smbios(&mem, None, None, None).unwrap();
setup_smbios(&mem).unwrap();
let smbios_ep: Smbios30Entrypoint = mem.read_obj(GuestAddress(SMBIOS_START)).unwrap();

View File

@@ -4,9 +4,7 @@
use crate::GuestMemoryMmap;
use std::fs::File;
use std::io::{Read, Seek, SeekFrom};
use std::str::FromStr;
use thiserror::Error;
use uuid::Uuid;
use vm_memory::{ByteValued, Bytes, GuestAddress, GuestMemoryError};
#[derive(Error, Debug)]
@@ -15,8 +13,6 @@ pub enum TdvfError {
ReadDescriptor(#[source] std::io::Error),
#[error("Failed read TDVF descriptor offset: {0}")]
ReadDescriptorOffset(#[source] std::io::Error),
#[error("Failed read GUID table: {0}")]
ReadGuidTable(#[source] std::io::Error),
#[error("Invalid descriptor signature")]
InvalidDescriptorSignature,
#[error("Invalid descriptor size")]
@@ -25,13 +21,8 @@ pub enum TdvfError {
InvalidDescriptorVersion,
#[error("Failed to write HOB details to guest memory: {0}")]
GuestMemoryWriteHob(#[source] GuestMemoryError),
#[error("Failed to create Uuid: {0}")]
UuidCreation(#[source] uuid::Error),
}
const TABLE_FOOTER_GUID: &str = "96b582de-1fb2-45f7-baea-a366c55a082d";
const TDVF_METADATA_OFFSET_GUID: &str = "e47a6535-984a-4798-865e-4685a7bf8ec2";
// TDVF_DESCRIPTOR
#[repr(packed)]
#[derive(Default)]
@@ -55,7 +46,7 @@ pub struct TdvfSection {
}
#[repr(u32)]
#[derive(Clone, Copy, Debug, Default)]
#[derive(Clone, Copy, Debug)]
pub enum TdvfSectionType {
Bfv,
Cfv,
@@ -64,76 +55,16 @@ pub enum TdvfSectionType {
PermMem,
Payload,
PayloadParam,
#[default]
Reserved = 0xffffffff,
}
fn tdvf_descriptor_offset(file: &mut File) -> Result<(SeekFrom, bool), TdvfError> {
// Let's first try to identify the presence of the table footer GUID
file.seek(SeekFrom::End(-0x30))
.map_err(TdvfError::ReadGuidTable)?;
let mut table_footer_guid: [u8; 16] = [0; 16];
file.read_exact(&mut table_footer_guid)
.map_err(TdvfError::ReadGuidTable)?;
let uuid =
Uuid::from_slice_le(table_footer_guid.as_slice()).map_err(TdvfError::UuidCreation)?;
let expected_uuid = Uuid::from_str(TABLE_FOOTER_GUID).map_err(TdvfError::UuidCreation)?;
if uuid == expected_uuid {
// Retrieve the table size
file.seek(SeekFrom::End(-0x32))
.map_err(TdvfError::ReadGuidTable)?;
let mut table_size: [u8; 2] = [0; 2];
file.read_exact(&mut table_size)
.map_err(TdvfError::ReadGuidTable)?;
let table_size = u16::from_le_bytes(table_size) as usize;
let mut table: Vec<u8> = vec![0; table_size];
// Read the entire table
file.seek(SeekFrom::End(-(table_size as i64 + 0x20)))
.map_err(TdvfError::ReadGuidTable)?;
file.read_exact(table.as_mut_slice())
.map_err(TdvfError::ReadGuidTable)?;
// Let's start from the top and go backward down the table.
// We start after the footer GUID and the table length.
let mut offset = table_size - 18;
debug!("Parsing GUID structure");
while offset >= 18 {
let entry_uuid = Uuid::from_slice_le(&table[offset - 16..offset])
.map_err(TdvfError::UuidCreation)?;
let entry_size =
u16::from_le_bytes(table[offset - 18..offset - 16].try_into().unwrap()) as usize;
debug!(
"Entry GUID = {}, size = {}",
entry_uuid.hyphenated().to_string(),
entry_size
);
// Avoid going through an infinite loop if the entry size is 0
if entry_size == 0 {
break;
}
offset -= entry_size;
let expected_uuid =
Uuid::from_str(TDVF_METADATA_OFFSET_GUID).map_err(TdvfError::UuidCreation)?;
if entry_uuid == expected_uuid && entry_size == 22 {
return Ok((
SeekFrom::End(
-(u32::from_le_bytes(table[offset..offset + 4].try_into().unwrap()) as i64),
),
true,
));
}
}
impl Default for TdvfSectionType {
fn default() -> Self {
TdvfSectionType::Reserved
}
}
// If we end up here, this means the firmware doesn't support the new way
// of exposing the TDVF descriptor offset through the table of GUIDs.
// That's why we fallback onto the deprecated method.
pub fn parse_tdvf_sections(file: &mut File) -> Result<Vec<TdvfSection>, TdvfError> {
// The 32-bit offset to the TDVF metadata is located 32 bytes from
// the end of the file.
// See "TDVF Metadata Pointer" in "TDX Virtual Firmware Design Guide
@@ -143,21 +74,13 @@ fn tdvf_descriptor_offset(file: &mut File) -> Result<(SeekFrom, bool), TdvfError
let mut descriptor_offset: [u8; 4] = [0; 4];
file.read_exact(&mut descriptor_offset)
.map_err(TdvfError::ReadDescriptorOffset)?;
let descriptor_offset = u32::from_le_bytes(descriptor_offset) as u64;
Ok((
SeekFrom::Start(u32::from_le_bytes(descriptor_offset) as u64),
false,
))
}
pub fn parse_tdvf_sections(file: &mut File) -> Result<(Vec<TdvfSection>, bool), TdvfError> {
let (descriptor_offset, guid_found) = tdvf_descriptor_offset(file)?;
file.seek(descriptor_offset)
file.seek(SeekFrom::Start(descriptor_offset))
.map_err(TdvfError::ReadDescriptor)?;
let mut descriptor: TdvfDescriptor = Default::default();
// SAFETY: we read exactly the size of the descriptor header
// Safe as we read exactly the size of the descriptor header
file.read_exact(unsafe {
std::slice::from_raw_parts_mut(
&mut descriptor as *mut _ as *mut u8,
@@ -184,7 +107,7 @@ pub fn parse_tdvf_sections(file: &mut File) -> Result<(Vec<TdvfSection>, bool),
let mut sections = Vec::new();
sections.resize_with(descriptor.num_sections as usize, TdvfSection::default);
// SAFETY: we read exactly the advertised sections
// Safe as we read exactly the advertised sections
file.read_exact(unsafe {
std::slice::from_raw_parts_mut(
sections.as_mut_ptr() as *mut u8,
@@ -193,20 +116,25 @@ pub fn parse_tdvf_sections(file: &mut File) -> Result<(Vec<TdvfSection>, bool),
})
.map_err(TdvfError::ReadDescriptor)?;
Ok((sections, guid_found))
Ok(sections)
}
#[repr(u16)]
#[derive(Copy, Clone, Debug, Default)]
#[derive(Copy, Clone, Debug)]
enum HobType {
Handoff = 0x1,
ResourceDescriptor = 0x3,
GuidExtension = 0x4,
#[default]
Unused = 0xfffe,
EndOfHobList = 0xffff,
}
impl Default for HobType {
fn default() -> Self {
HobType::Unused
}
}
#[repr(C)]
#[derive(Copy, Clone, Default, Debug)]
struct HobHeader {
@@ -256,14 +184,19 @@ struct HobGuidType {
}
#[repr(u32)]
#[derive(Clone, Copy, Debug, Default)]
#[derive(Clone, Copy, Debug)]
pub enum PayloadImageType {
#[default]
ExecutablePayload,
BzImage,
RawVmLinux,
}
impl Default for PayloadImageType {
fn default() -> Self {
PayloadImageType::ExecutablePayload
}
}
#[repr(C)]
#[derive(Copy, Clone, Default, Debug)]
pub struct PayloadInfo {
@@ -278,17 +211,12 @@ struct TdPayload {
payload_info: PayloadInfo,
}
// SAFETY: data structure only contain a series of integers
// SAFETY: These data structures only contain a series of integers
unsafe impl ByteValued for HobHeader {}
// SAFETY: data structure only contain a series of integers
unsafe impl ByteValued for HobHandoffInfoTable {}
// SAFETY: data structure only contain a series of integers
unsafe impl ByteValued for HobResourceDescriptor {}
// SAFETY: data structure only contain a series of integers
unsafe impl ByteValued for HobGuidType {}
// SAFETY: data structure only contain a series of integers
unsafe impl ByteValued for PayloadInfo {}
// SAFETY: data structure only contain a series of integers
unsafe impl ByteValued for TdPayload {}
pub struct TdHob {
@@ -384,19 +312,12 @@ impl TdHob {
physical_start: u64,
resource_length: u64,
ram: bool,
guid_found: bool,
) -> Result<(), TdvfError> {
self.add_resource(
mem,
physical_start,
resource_length,
if ram {
if guid_found {
0x7 /* EFI_RESOURCE_MEMORY_UNACCEPTED */
} else {
0 /* EFI_RESOURCE_SYSTEM_MEMORY */
}
} else if guid_found {
0 /* EFI_RESOURCE_SYSTEM_MEMORY */
} else {
0x5 /*EFI_RESOURCE_MEMORY_RESERVED */
@@ -524,9 +445,9 @@ mod tests {
#[ignore]
fn test_parse_tdvf_sections() {
let mut f = std::fs::File::open("tdvf.fd").unwrap();
let (sections, _) = parse_tdvf_sections(&mut f).unwrap();
let sections = parse_tdvf_sections(&mut f).unwrap();
for section in sections {
eprintln!("{section:x?}")
eprintln!("{:x?}", section)
}
}
}

View File

@@ -1,27 +0,0 @@
[package]
name = "block"
version = "0.1.0"
edition = "2021"
authors = ["The Cloud Hypervisor Authors", "The Chromium OS Authors"]
[features]
default = []
io_uring = ["dep:io-uring"]
[dependencies]
byteorder = "1.4.3"
crc32c = "0.6.4"
io-uring = { version = "0.6.1", optional = true }
libc = "0.2.147"
log = "0.4.17"
remain = "0.2.11"
smallvec = "1.11.0"
thiserror = "1.0.40"
uuid = { version = "1.3.4", features = ["v4"] }
versionize = "0.1.10"
versionize_derive = "0.1.4"
virtio-bindings = { version = "0.2.0", features = ["virtio-v5_0_0"] }
virtio-queue = "0.9.0"
vm-memory = { version = "0.12.2", features = ["backend-mmap", "backend-atomic", "backend-bitmap"] }
vm-virtio = { path = "../vm-virtio" }
vmm-sys-util = "0.11.0"

View File

@@ -1,60 +0,0 @@
// Copyright © 2021 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
use crate::DiskTopology;
use thiserror::Error;
use vmm_sys_util::eventfd::EventFd;
#[derive(Error, Debug)]
pub enum DiskFileError {
/// Failed getting disk file size.
#[error("Failed getting disk file size: {0}")]
Size(#[source] std::io::Error),
/// Failed creating a new AsyncIo.
#[error("Failed creating a new AsyncIo: {0}")]
NewAsyncIo(#[source] std::io::Error),
}
pub type DiskFileResult<T> = std::result::Result<T, DiskFileError>;
pub trait DiskFile: Send {
fn size(&mut self) -> DiskFileResult<u64>;
fn new_async_io(&self, ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>>;
fn topology(&mut self) -> DiskTopology {
DiskTopology::default()
}
}
#[derive(Error, Debug)]
pub enum AsyncIoError {
/// Failed vectored reading from file.
#[error("Failed vectored reading from file: {0}")]
ReadVectored(#[source] std::io::Error),
/// Failed vectored writing to file.
#[error("Failed vectored writing to file: {0}")]
WriteVectored(#[source] std::io::Error),
/// Failed synchronizing file.
#[error("Failed synchronizing file: {0}")]
Fsync(#[source] std::io::Error),
}
pub type AsyncIoResult<T> = std::result::Result<T, AsyncIoError>;
pub trait AsyncIo: Send {
fn notifier(&self) -> &EventFd;
fn read_vectored(
&mut self,
offset: libc::off_t,
iovecs: &[libc::iovec],
user_data: u64,
) -> AsyncIoResult<()>;
fn write_vectored(
&mut self,
offset: libc::off_t,
iovecs: &[libc::iovec],
user_data: u64,
) -> AsyncIoResult<()>;
fn fsync(&mut self, user_data: Option<u64>) -> AsyncIoResult<()>;
fn next_completed_request(&mut self) -> Option<(u64, i32)>;
}

View File

@@ -1,90 +0,0 @@
// Copyright © 2021 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
use crate::vhd::VhdFooter;
use crate::BlockBackend;
use std::fs::File;
use std::io::{Read, Seek, SeekFrom, Write};
use std::os::unix::io::{AsRawFd, RawFd};
#[derive(Debug)]
pub struct FixedVhd {
file: File,
size: u64,
position: u64,
}
impl FixedVhd {
pub fn new(mut file: File) -> std::io::Result<Self> {
let footer = VhdFooter::new(&mut file)?;
Ok(Self {
file,
size: footer.current_size(),
position: 0,
})
}
}
impl AsRawFd for FixedVhd {
fn as_raw_fd(&self) -> RawFd {
self.file.as_raw_fd()
}
}
impl Read for FixedVhd {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
match self.file.read(buf) {
Ok(r) => {
self.position = self.position.checked_add(r.try_into().unwrap()).unwrap();
Ok(r)
}
Err(e) => Err(e),
}
}
}
impl Write for FixedVhd {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
match self.file.write(buf) {
Ok(r) => {
self.position = self.position.checked_add(r.try_into().unwrap()).unwrap();
Ok(r)
}
Err(e) => Err(e),
}
}
fn flush(&mut self) -> std::io::Result<()> {
self.file.sync_all()
}
}
impl Seek for FixedVhd {
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 BlockBackend for FixedVhd {
fn size(&self) -> std::result::Result<u64, crate::Error> {
Ok(self.size)
}
}
impl Clone for FixedVhd {
fn clone(&self) -> Self {
Self {
file: self.file.try_clone().expect("FixedVhd cloning failed"),
size: self.size,
position: self.position,
}
}
}

24
block_util/Cargo.toml Normal file
View File

@@ -0,0 +1,24 @@
[package]
name = "block_util"
version = "0.1.0"
authors = ["The Cloud Hypervisor Authors"]
edition = "2021"
[features]
default = []
[dependencies]
io-uring = "0.5.2"
libc = "0.2.123"
log = "0.4.16"
qcow = { path = "../qcow" }
thiserror = "1.0.30"
versionize = "0.1.6"
versionize_derive = "0.1.4"
vhdx = { path = "../vhdx" }
virtio-bindings = { version = "0.1.0", features = ["virtio-v5_0_0"] }
virtio-queue = "0.2.0"
vm-memory = { version = "0.7.0", features = ["backend-mmap", "backend-atomic", "backend-bitmap"] }
vm-virtio = { path = "../vm-virtio" }
vmm-sys-util = "0.9.0"

146
block_util/src/async_io.rs Normal file
View File

@@ -0,0 +1,146 @@
// Copyright © 2021 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
use libc::{ioctl, S_IFBLK, S_IFMT};
use std::convert::TryInto;
use std::fs::File;
use std::os::unix::io::AsRawFd;
use thiserror::Error;
use vmm_sys_util::eventfd::EventFd;
use vmm_sys_util::{ioctl_expr, ioctl_io_nr, ioctl_ioc_nr};
#[derive(Error, Debug)]
pub enum DiskFileError {
/// Failed getting disk file size.
#[error("Failed getting disk file size: {0}")]
Size(#[source] std::io::Error),
/// Failed creating a new AsyncIo.
#[error("Failed creating a new AsyncIo: {0}")]
NewAsyncIo(#[source] std::io::Error),
}
#[derive(Debug)]
pub struct DiskTopology {
pub logical_block_size: u64,
pub physical_block_size: u64,
pub minimum_io_size: u64,
pub optimal_io_size: u64,
}
impl Default for DiskTopology {
fn default() -> Self {
Self {
logical_block_size: 512,
physical_block_size: 512,
minimum_io_size: 512,
optimal_io_size: 0,
}
}
}
ioctl_io_nr!(BLKSSZGET, 0x12, 104);
ioctl_io_nr!(BLKPBSZGET, 0x12, 123);
ioctl_io_nr!(BLKIOMIN, 0x12, 120);
ioctl_io_nr!(BLKIOOPT, 0x12, 121);
enum BlockSize {
LogicalBlock,
PhysicalBlock,
MinimumIo,
OptimalIo,
}
impl DiskTopology {
fn is_block_device(f: &mut File) -> std::io::Result<bool> {
let mut stat = std::mem::MaybeUninit::<libc::stat>::uninit();
let ret = unsafe { libc::fstat(f.as_raw_fd(), stat.as_mut_ptr()) };
if ret != 0 {
return Err(std::io::Error::last_os_error());
}
let is_block = unsafe { (*stat.as_ptr()).st_mode & S_IFMT == S_IFBLK };
Ok(is_block)
}
// libc::ioctl() takes different types on different architectures
#[allow(clippy::useless_conversion)]
fn query_block_size(f: &mut File, block_size_type: BlockSize) -> std::io::Result<u64> {
let mut block_size = 0;
let ret = unsafe {
ioctl(
f.as_raw_fd(),
match block_size_type {
BlockSize::LogicalBlock => BLKSSZGET(),
BlockSize::PhysicalBlock => BLKPBSZGET(),
BlockSize::MinimumIo => BLKIOMIN(),
BlockSize::OptimalIo => BLKIOOPT(),
}
.try_into()
.unwrap(),
&mut block_size,
)
};
if ret != 0 {
return Err(std::io::Error::last_os_error());
};
Ok(block_size)
}
pub fn probe(f: &mut File) -> std::io::Result<Self> {
if !Self::is_block_device(f)? {
return Ok(DiskTopology::default());
}
Ok(DiskTopology {
logical_block_size: Self::query_block_size(f, BlockSize::LogicalBlock)?,
physical_block_size: Self::query_block_size(f, BlockSize::PhysicalBlock)?,
minimum_io_size: Self::query_block_size(f, BlockSize::MinimumIo)?,
optimal_io_size: Self::query_block_size(f, BlockSize::OptimalIo)?,
})
}
}
pub type DiskFileResult<T> = std::result::Result<T, DiskFileError>;
pub trait DiskFile: Send + Sync {
fn size(&mut self) -> DiskFileResult<u64>;
fn new_async_io(&self, ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>>;
fn topology(&mut self) -> DiskTopology {
DiskTopology::default()
}
}
#[derive(Error, Debug)]
pub enum AsyncIoError {
/// Failed vectored reading from file.
#[error("Failed vectored reading from file: {0}")]
ReadVectored(#[source] std::io::Error),
/// Failed vectored writing to file.
#[error("Failed vectored writing to file: {0}")]
WriteVectored(#[source] std::io::Error),
/// Failed synchronizing file.
#[error("Failed synchronizing file: {0}")]
Fsync(#[source] std::io::Error),
}
pub type AsyncIoResult<T> = std::result::Result<T, AsyncIoError>;
pub trait AsyncIo: Send + Sync {
fn notifier(&self) -> &EventFd;
fn read_vectored(
&mut self,
offset: libc::off_t,
iovecs: Vec<libc::iovec>,
user_data: u64,
) -> AsyncIoResult<()>;
fn write_vectored(
&mut self,
offset: libc::off_t,
iovecs: Vec<libc::iovec>,
user_data: u64,
) -> AsyncIoResult<()>;
fn fsync(&mut self, user_data: Option<u64>) -> AsyncIoResult<()>;
fn complete(&mut self) -> Vec<(u64, i32)>;
}

View File

@@ -5,29 +5,36 @@
use crate::async_io::{
AsyncIo, AsyncIoError, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult,
};
use crate::fixed_vhd::FixedVhd;
use crate::raw_async::RawFileAsync;
use crate::BlockBackend;
use crate::vhd::VhdFooter;
use std::fs::File;
use std::os::unix::io::{AsRawFd, RawFd};
use vmm_sys_util::eventfd::EventFd;
pub struct FixedVhdDiskAsync(FixedVhd);
pub struct FixedVhdDiskAsync {
file: File,
size: u64,
}
impl FixedVhdDiskAsync {
pub fn new(file: File) -> std::io::Result<Self> {
Ok(Self(FixedVhd::new(file)?))
pub fn new(mut file: File) -> std::io::Result<Self> {
let footer = VhdFooter::new(&mut file)?;
Ok(FixedVhdDiskAsync {
file,
size: footer.current_size(),
})
}
}
impl DiskFile for FixedVhdDiskAsync {
fn size(&mut self) -> DiskFileResult<u64> {
Ok(self.0.size().unwrap())
Ok(self.size)
}
fn new_async_io(&self, ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
Ok(Box::new(
FixedVhdAsync::new(self.0.as_raw_fd(), ring_depth, self.0.size().unwrap())
FixedVhdAsync::new(self.file.as_raw_fd(), ring_depth, self.size)
.map_err(DiskFileError::NewAsyncIo)?,
) as Box<dyn AsyncIo>)
}
@@ -57,7 +64,7 @@ impl AsyncIo for FixedVhdAsync {
fn read_vectored(
&mut self,
offset: libc::off_t,
iovecs: &[libc::iovec],
iovecs: Vec<libc::iovec>,
user_data: u64,
) -> AsyncIoResult<()> {
if offset as u64 >= self.size {
@@ -76,7 +83,7 @@ impl AsyncIo for FixedVhdAsync {
fn write_vectored(
&mut self,
offset: libc::off_t,
iovecs: &[libc::iovec],
iovecs: Vec<libc::iovec>,
user_data: u64,
) -> AsyncIoResult<()> {
if offset as u64 >= self.size {
@@ -97,7 +104,7 @@ impl AsyncIo for FixedVhdAsync {
self.raw_file_async.fsync(user_data)
}
fn next_completed_request(&mut self) -> Option<(u64, i32)> {
self.raw_file_async.next_completed_request()
fn complete(&mut self) -> Vec<(u64, i32)> {
self.raw_file_async.complete()
}
}

View File

@@ -5,29 +5,36 @@
use crate::async_io::{
AsyncIo, AsyncIoError, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult,
};
use crate::fixed_vhd::FixedVhd;
use crate::raw_sync::RawFileSync;
use crate::BlockBackend;
use crate::vhd::VhdFooter;
use std::fs::File;
use std::os::unix::io::{AsRawFd, RawFd};
use vmm_sys_util::eventfd::EventFd;
pub struct FixedVhdDiskSync(FixedVhd);
pub struct FixedVhdDiskSync {
file: File,
size: u64,
}
impl FixedVhdDiskSync {
pub fn new(file: File) -> std::io::Result<Self> {
Ok(Self(FixedVhd::new(file)?))
pub fn new(mut file: File) -> std::io::Result<Self> {
let footer = VhdFooter::new(&mut file)?;
Ok(FixedVhdDiskSync {
file,
size: footer.current_size(),
})
}
}
impl DiskFile for FixedVhdDiskSync {
fn size(&mut self) -> DiskFileResult<u64> {
Ok(self.0.size().unwrap())
Ok(self.size)
}
fn new_async_io(&self, _ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
Ok(Box::new(
FixedVhdSync::new(self.0.as_raw_fd(), self.0.size().unwrap())
FixedVhdSync::new(self.file.as_raw_fd(), self.size)
.map_err(DiskFileError::NewAsyncIo)?,
) as Box<dyn AsyncIo>)
}
@@ -55,7 +62,7 @@ impl AsyncIo for FixedVhdSync {
fn read_vectored(
&mut self,
offset: libc::off_t,
iovecs: &[libc::iovec],
iovecs: Vec<libc::iovec>,
user_data: u64,
) -> AsyncIoResult<()> {
if offset as u64 >= self.size {
@@ -74,7 +81,7 @@ impl AsyncIo for FixedVhdSync {
fn write_vectored(
&mut self,
offset: libc::off_t,
iovecs: &[libc::iovec],
iovecs: Vec<libc::iovec>,
user_data: u64,
) -> AsyncIoResult<()> {
if offset as u64 >= self.size {
@@ -94,7 +101,7 @@ impl AsyncIo for FixedVhdSync {
self.raw_file_sync.fsync(user_data)
}
fn next_completed_request(&mut self) -> Option<(u64, i32)> {
self.raw_file_sync.next_completed_request()
fn complete(&mut self) -> Vec<(u64, i32)> {
self.raw_file_sync.complete()
}
}

View File

@@ -12,49 +12,29 @@
extern crate log;
pub mod async_io;
pub mod fixed_vhd;
#[cfg(feature = "io_uring")]
/// Enabled with the `"io_uring"` feature
pub mod fixed_vhd_async;
pub mod fixed_vhd_sync;
pub mod qcow;
pub mod qcow_sync;
#[cfg(feature = "io_uring")]
/// Async primitives based on `io-uring`
///
/// Enabled with the `"io_uring"` feature
pub mod raw_async;
pub mod raw_sync;
pub mod vhd;
pub mod vhdx;
pub mod vhdx_sync;
use crate::async_io::{AsyncIo, AsyncIoError, AsyncIoResult};
use crate::fixed_vhd::FixedVhd;
use crate::qcow::{QcowFile, RawFile};
use crate::vhdx::{Vhdx, VhdxError};
#[cfg(feature = "io_uring")]
use io_uring::{opcode, IoUring, Probe};
use libc::{ioctl, S_IFBLK, S_IFMT};
use smallvec::SmallVec;
use std::alloc::{alloc_zeroed, dealloc, Layout};
use std::cmp;
use std::collections::VecDeque;
use std::convert::TryInto;
use std::fmt::Debug;
use std::fs::File;
use std::io::{self, IoSlice, IoSliceMut, Read, Seek, SeekFrom, Write};
use std::os::linux::fs::MetadataExt;
use std::os::unix::io::AsRawFd;
use std::path::Path;
use std::result;
use std::sync::Arc;
use std::sync::MutexGuard;
use std::time::Instant;
use thiserror::Error;
use versionize::{VersionMap, Versionize, VersionizeResult};
use versionize_derive::Versionize;
use virtio_bindings::virtio_blk::*;
use virtio_bindings::bindings::virtio_blk::*;
use virtio_queue::DescriptorChain;
use vm_memory::{
bitmap::AtomicBitmap, bitmap::Bitmap, ByteValued, Bytes, GuestAddress, GuestMemory,
@@ -62,43 +42,32 @@ use vm_memory::{
};
use vm_virtio::{AccessPlatform, Translatable};
use vmm_sys_util::eventfd::EventFd;
use vmm_sys_util::{ioctl_io_nr, ioctl_ioc_nr};
type GuestMemoryMmap = vm_memory::GuestMemoryMmap<AtomicBitmap>;
const SECTOR_SHIFT: u8 = 9;
pub const SECTOR_SIZE: u64 = 0x01 << SECTOR_SHIFT;
#[derive(Error, Debug)]
#[derive(Debug)]
pub enum Error {
#[error("Guest gave us bad memory addresses")]
/// Guest gave us bad memory addresses.
GuestMemory(GuestMemoryError),
#[error("Guest gave us offsets that would have overflowed a usize")]
/// Guest gave us offsets that would have overflowed a usize.
CheckedOffset(GuestAddress, usize),
#[error("Guest gave us a write only descriptor that protocol says to read from")]
/// Guest gave us a write only descriptor that protocol says to read from.
UnexpectedWriteOnlyDescriptor,
#[error("Guest gave us a read only descriptor that protocol says to write to")]
/// Guest gave us a read only descriptor that protocol says to write to.
UnexpectedReadOnlyDescriptor,
#[error("Guest gave us too few descriptors in a descriptor chain")]
/// Guest gave us too few descriptors in a descriptor chain.
DescriptorChainTooShort,
#[error("Guest gave us a descriptor that was too short to use")]
/// Guest gave us a descriptor that was too short to use.
DescriptorLengthTooSmall,
#[error("Failed to detect image type: {0}")]
DetectImageType(std::io::Error),
#[error("Failure in fixed vhd: {0}")]
FixedVhdError(std::io::Error),
#[error("Getting a block's metadata fails for any reason")]
/// Getting a block's metadata fails for any reason.
GetFileMetadata,
#[error("The requested operation would cause a seek beyond disk end")]
/// The requested operation would cause a seek beyond disk end.
InvalidOffset,
#[error("Failure in qcow: {0}")]
QcowError(qcow::Error),
#[error("Failure in raw file: {0}")]
RawFileError(std::io::Error),
#[error("The requested operation does not support multiple descriptors")]
/// The requested operation does not support multiple descriptors.
TooManyDescriptors,
#[error("Failure in vhdx: {0}")]
VhdxError(VhdxError),
}
fn build_device_id(disk_path: &Path) -> result::Result<String, Error> {
@@ -116,8 +85,8 @@ fn build_device_id(disk_path: &Path) -> result::Result<String, Error> {
Ok(device_id)
}
pub fn build_serial(disk_path: &Path) -> Vec<u8> {
let mut default_serial = vec![0; VIRTIO_BLK_ID_BYTES as usize];
pub fn build_disk_image_id(disk_path: &Path) -> 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.");
@@ -127,37 +96,26 @@ pub fn build_serial(disk_path: &Path) -> Vec<u8> {
// 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_serial[..bytes_to_copy].clone_from_slice(&disk_id[..bytes_to_copy])
default_disk_image_id[..bytes_to_copy].clone_from_slice(&disk_id[..bytes_to_copy])
}
}
default_serial
default_disk_image_id
}
#[derive(Error, Debug)]
#[derive(Debug)]
pub enum ExecuteError {
#[error("Bad request: {0}")]
BadRequest(Error),
#[error("Failed to flush: {0}")]
Flush(io::Error),
#[error("Failed to read: {0}")]
Read(GuestMemoryError),
#[error("Failed to seek: {0}")]
Seek(io::Error),
#[error("Failed to write: {0}")]
Write(GuestMemoryError),
#[error("Unsupported request: {0}")]
Unsupported(u32),
#[error("Failed to submit io uring: {0}")]
SubmitIoUring(io::Error),
#[error("Failed to get guest address: {0}")]
GetHostAddress(GuestMemoryError),
#[error("Failed to async read: {0}")]
AsyncRead(AsyncIoError),
#[error("Failed to async write: {0}")]
AsyncWrite(AsyncIoError),
#[error("failed to async flush: {0}")]
AsyncFlush(AsyncIoError),
#[error("Failed allocating a temporary buffer: {0}")]
/// Failed allocating a temporary buffer.
TemporaryBufferAllocation(io::Error),
}
@@ -180,7 +138,7 @@ impl ExecuteError {
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum RequestType {
In,
Out,
@@ -225,11 +183,10 @@ pub struct AlignedOperation {
pub struct Request {
pub request_type: RequestType,
pub sector: u64,
pub data_descriptors: SmallVec<[(GuestAddress, u32); 1]>,
pub data_descriptors: Vec<(GuestAddress, u32)>,
pub status_addr: GuestAddress,
pub writeback: bool,
pub aligned_operations: SmallVec<[AlignedOperation; 1]>,
pub start: Instant,
pub aligned_operations: Vec<AlignedOperation>,
}
impl Request {
@@ -257,11 +214,10 @@ impl Request {
let mut req = Request {
request_type: request_type(desc_chain.memory(), hdr_desc_addr)?,
sector: sector(desc_chain.memory(), hdr_desc_addr)?,
data_descriptors: SmallVec::with_capacity(1),
data_descriptors: Vec::new(),
status_addr: GuestAddress(0),
writeback: true,
aligned_operations: SmallVec::with_capacity(1),
start: Instant::now(),
aligned_operations: Vec::new(),
};
let status_desc;
@@ -281,7 +237,6 @@ impl Request {
return Err(Error::DescriptorChainTooShort);
}
} else {
req.data_descriptors.reserve_exact(1);
while desc.has_next() {
if desc.is_write_only() && req.request_type == RequestType::Out {
return Err(Error::UnexpectedWriteOnlyDescriptor);
@@ -330,7 +285,7 @@ impl Request {
disk: &mut T,
disk_nsectors: u64,
mem: &GuestMemoryMmap,
serial: &[u8],
disk_id: &[u8],
) -> result::Result<u32, ExecuteError> {
disk.seek(SeekFrom::Start(self.sector << SECTOR_SHIFT))
.map_err(ExecuteError::Seek)?;
@@ -362,10 +317,10 @@ impl Request {
}
RequestType::Flush => disk.flush().map_err(ExecuteError::Flush)?,
RequestType::GetDeviceId => {
if (*data_len as usize) < serial.len() {
if (*data_len as usize) < disk_id.len() {
return Err(ExecuteError::BadRequest(Error::InvalidOffset));
}
mem.write_slice(serial, *data_addr)
mem.write_slice(disk_id, *data_addr)
.map_err(ExecuteError::Write)?;
}
RequestType::Unsupported(t) => return Err(ExecuteError::Unsupported(t)),
@@ -379,15 +334,14 @@ impl Request {
mem: &GuestMemoryMmap,
disk_nsectors: u64,
disk_image: &mut dyn AsyncIo,
serial: &[u8],
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 mut iovecs: SmallVec<[libc::iovec; 1]> =
SmallVec::with_capacity(self.data_descriptors.len());
let mut iovecs = Vec::new();
for (data_addr, data_len) in &self.data_descriptors {
if *data_len == 0 {
continue;
@@ -406,16 +360,16 @@ impl Request {
let origin_ptr = mem
.get_slice(*data_addr, *data_len as usize)
.map_err(ExecuteError::GetHostAddress)?
.ptr_guard();
.as_ptr();
// Verify the buffer alignment.
// In case it's not properly aligned, an intermediate buffer is
// created with the correct alignment, and a copy from/to the
// origin buffer is performed, depending on the type of operation.
let iov_base = if (origin_ptr.as_ptr() as u64) % SECTOR_SIZE != 0 {
let iov_base = if (origin_ptr as u64) % SECTOR_SIZE != 0 {
let layout =
Layout::from_size_align(*data_len as usize, SECTOR_SIZE as usize).unwrap();
// SAFETY: layout has non-zero size
// Safe because layout has non-zero size
let aligned_ptr = unsafe { alloc_zeroed(layout) };
if aligned_ptr.is_null() {
return Err(ExecuteError::TemporaryBufferAllocation(
@@ -426,15 +380,17 @@ impl Request {
// We need to perform the copy beforehand in case we're writing
// data out.
if request_type == RequestType::Out {
// SAFETY: destination buffer has been allocated with
// Safe because destination buffer has been allocated with
// the proper size.
unsafe { std::ptr::copy(origin_ptr.as_ptr(), aligned_ptr, *data_len as usize) };
unsafe {
std::ptr::copy(origin_ptr as *const u8, aligned_ptr, *data_len as usize)
};
}
// Store both origin and aligned pointers for complete_async()
// to process them.
self.aligned_operations.push(AlignedOperation {
origin_ptr: origin_ptr.as_ptr() as u64,
origin_ptr: origin_ptr as u64,
aligned_ptr: aligned_ptr as u64,
size: *data_len as usize,
layout,
@@ -442,7 +398,7 @@ impl Request {
aligned_ptr as *mut libc::c_void
} else {
origin_ptr.as_ptr() as *mut libc::c_void
origin_ptr as *mut libc::c_void
};
let iovec = libc::iovec {
@@ -462,12 +418,12 @@ impl Request {
.mark_dirty(0, *data_len as usize);
}
disk_image
.read_vectored(offset, &iovecs, user_data)
.read_vectored(offset, iovecs, user_data)
.map_err(ExecuteError::AsyncRead)?;
}
RequestType::Out => {
disk_image
.write_vectored(offset, &iovecs, user_data)
.write_vectored(offset, iovecs, user_data)
.map_err(ExecuteError::AsyncWrite)?;
}
RequestType::Flush => {
@@ -481,10 +437,10 @@ impl Request {
} else {
return Err(ExecuteError::BadRequest(Error::TooManyDescriptors));
};
if (data_len as usize) < serial.len() {
if (data_len as usize) < disk_id.len() {
return Err(ExecuteError::BadRequest(Error::InvalidOffset));
}
mem.write_slice(serial, data_addr)
mem.write_slice(disk_id, data_addr)
.map_err(ExecuteError::Write)?;
return Ok(false);
}
@@ -499,7 +455,7 @@ impl Request {
// We need to perform the copy after the data has been read inside
// the aligned buffer in case we're reading data in.
if self.request_type == RequestType::In {
// SAFETY: origin buffer has been allocated with the
// Safe because origin buffer has been allocated with the
// proper size.
unsafe {
std::ptr::copy(
@@ -511,7 +467,7 @@ impl Request {
}
// Free the temporary aligned buffer.
// SAFETY: aligned_ptr was allocated by alloc_zeroed with the same
// Safe because aligned_ptr was allocated by alloc_zeroed with the same
// layout
unsafe {
dealloc(
@@ -560,67 +516,57 @@ pub struct VirtioBlockGeometry {
pub sectors: u8,
}
// SAFETY: data structure only contain a series of integers
// SAFETY: these data structures only contain a series of integers
unsafe impl ByteValued for VirtioBlockConfig {}
// SAFETY: data structure only contain a series of integers
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.
pub fn block_io_uring_is_supported() -> bool {
#[cfg(not(feature = "io_uring"))]
{
info!("io_uring is disabled by crate features");
false
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 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;
}
}
#[cfg(feature = "io_uring")]
{
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 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_READV is supported
if !probe.is_supported(opcode::Readv::CODE) {
info!("{} IORING_OP_READV operation not supported", error_msg);
return false;
}
// Check IORING_OP_WRITEV is supported
if !probe.is_supported(opcode::Writev::CODE) {
info!("{} IORING_OP_WRITEV operation not supported", error_msg);
return false;
}
true
// 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
}
pub trait AsyncAdaptor<F>
@@ -630,15 +576,14 @@ where
fn read_vectored_sync(
&mut self,
offset: libc::off_t,
iovecs: &[libc::iovec],
iovecs: Vec<libc::iovec>,
user_data: u64,
eventfd: &EventFd,
completion_list: &mut VecDeque<(u64, i32)>,
completion_list: &mut Vec<(u64, i32)>,
) -> AsyncIoResult<()> {
// Convert libc::iovec into IoSliceMut
let mut slices: SmallVec<[IoSliceMut; 1]> = SmallVec::with_capacity(iovecs.len());
let mut slices = Vec::new();
for iovec in iovecs.iter() {
// SAFETY: on Linux IoSliceMut wraps around libc::iovec
slices.push(IoSliceMut::new(unsafe { std::mem::transmute(*iovec) }));
}
@@ -654,7 +599,7 @@ where
.map_err(AsyncIoError::ReadVectored)?
};
completion_list.push_back((user_data, result as i32));
completion_list.push((user_data, result as i32));
eventfd.write(1).unwrap();
Ok(())
@@ -663,15 +608,14 @@ where
fn write_vectored_sync(
&mut self,
offset: libc::off_t,
iovecs: &[libc::iovec],
iovecs: Vec<libc::iovec>,
user_data: u64,
eventfd: &EventFd,
completion_list: &mut VecDeque<(u64, i32)>,
completion_list: &mut Vec<(u64, i32)>,
) -> AsyncIoResult<()> {
// Convert libc::iovec into IoSlice
let mut slices: SmallVec<[IoSlice; 1]> = SmallVec::with_capacity(iovecs.len());
let mut slices = Vec::new();
for iovec in iovecs.iter() {
// SAFETY: on Linux IoSlice wraps around libc::iovec
slices.push(IoSlice::new(unsafe { std::mem::transmute(*iovec) }));
}
@@ -687,7 +631,7 @@ where
.map_err(AsyncIoError::WriteVectored)?
};
completion_list.push_back((user_data, result as i32));
completion_list.push((user_data, result as i32));
eventfd.write(1).unwrap();
Ok(())
@@ -697,7 +641,7 @@ where
&mut self,
user_data: Option<u64>,
eventfd: &EventFd,
completion_list: &mut VecDeque<(u64, i32)>,
completion_list: &mut Vec<(u64, i32)>,
) -> AsyncIoResult<()> {
let result: i32 = {
let mut file = self.file();
@@ -709,7 +653,7 @@ where
};
if let Some(user_data) = user_data {
completion_list.push_back((user_data, result));
completion_list.push((user_data, result));
eventfd.write(1).unwrap();
}
@@ -729,33 +673,24 @@ pub enum ImageType {
const QCOW_MAGIC: u32 = 0x5146_49fb;
const VHDX_SIGN: u64 = 0x656C_6966_7864_6876;
/// Read a block into memory aligned by the source block size (needed for O_DIRECT)
pub fn read_aligned_block_size(f: &mut File) -> std::io::Result<Vec<u8>> {
let blocksize = DiskTopology::probe(f)?.logical_block_size as usize;
// SAFETY: We are allocating memory that is naturally aligned (size = alignment) and we meet
// requirements for safety from Vec::from_raw_parts() as we are using the global allocator
// and transferring ownership of the memory.
let mut data = unsafe {
Vec::from_raw_parts(
alloc_zeroed(Layout::from_size_align_unchecked(blocksize, blocksize)),
blocksize,
blocksize,
)
};
f.read_exact(&mut data)?;
Ok(data)
}
/// Determine image type through file parsing.
pub fn detect_image_type(f: &mut File) -> std::io::Result<ImageType> {
let block = read_aligned_block_size(f)?;
// We must create a buffer aligned on 512 bytes with a size being a
// multiple of 512 bytes as the file might be opened with O_DIRECT flag.
#[repr(align(512))]
struct Sector {
data: [u8; 512],
}
let mut s = Sector { data: [0; 512] };
f.read_exact(&mut s.data)?;
// Check 4 first bytes to get the header value and determine the image type
let image_type = if u32::from_be_bytes(block[0..4].try_into().unwrap()) == QCOW_MAGIC {
let image_type = if u32::from_be_bytes(s.data[0..4].try_into().unwrap()) == QCOW_MAGIC {
ImageType::Qcow2
} else if vhd::is_fixed_vhd(f)? {
ImageType::FixedVhd
} else if u64::from_le_bytes(block[0..8].try_into().unwrap()) == VHDX_SIGN {
} else if u64::from_le_bytes(s.data[0..8].try_into().unwrap()) == VHDX_SIGN {
ImageType::Vhdx
} else {
ImageType::Raw
@@ -763,108 +698,3 @@ pub fn detect_image_type(f: &mut File) -> std::io::Result<ImageType> {
Ok(image_type)
}
pub trait BlockBackend: Read + Write + Seek + Send + Debug {
fn size(&self) -> Result<u64, Error>;
}
/// Inspect the image file type and create an appropriate disk file to match it.
pub fn create_disk_file(mut file: File, direct_io: bool) -> Result<Box<dyn BlockBackend>, Error> {
let image_type = detect_image_type(&mut file).map_err(Error::DetectImageType)?;
Ok(match image_type {
ImageType::Qcow2 => {
Box::new(QcowFile::from(RawFile::new(file, direct_io)).map_err(Error::QcowError)?)
as Box<dyn BlockBackend>
}
ImageType::FixedVhd => {
Box::new(FixedVhd::new(file).map_err(Error::FixedVhdError)?) as Box<dyn BlockBackend>
}
ImageType::Vhdx => {
Box::new(Vhdx::new(file).map_err(Error::VhdxError)?) as Box<dyn BlockBackend>
}
ImageType::Raw => Box::new(RawFile::new(file, direct_io)) as Box<dyn BlockBackend>,
})
}
#[derive(Debug)]
pub struct DiskTopology {
pub logical_block_size: u64,
pub physical_block_size: u64,
pub minimum_io_size: u64,
pub optimal_io_size: u64,
}
impl Default for DiskTopology {
fn default() -> Self {
Self {
logical_block_size: 512,
physical_block_size: 512,
minimum_io_size: 512,
optimal_io_size: 0,
}
}
}
ioctl_io_nr!(BLKSSZGET, 0x12, 104);
ioctl_io_nr!(BLKPBSZGET, 0x12, 123);
ioctl_io_nr!(BLKIOMIN, 0x12, 120);
ioctl_io_nr!(BLKIOOPT, 0x12, 121);
enum BlockSize {
LogicalBlock,
PhysicalBlock,
MinimumIo,
OptimalIo,
}
impl DiskTopology {
fn is_block_device(f: &File) -> std::io::Result<bool> {
let mut stat = std::mem::MaybeUninit::<libc::stat>::uninit();
// SAFETY: FFI call with a valid fd and buffer
let ret = unsafe { libc::fstat(f.as_raw_fd(), stat.as_mut_ptr()) };
if ret != 0 {
return Err(std::io::Error::last_os_error());
}
// SAFETY: stat is valid at this point
let is_block = unsafe { (*stat.as_ptr()).st_mode & S_IFMT == S_IFBLK };
Ok(is_block)
}
// libc::ioctl() takes different types on different architectures
fn query_block_size(f: &File, block_size_type: BlockSize) -> std::io::Result<u64> {
let mut block_size = 0;
// SAFETY: FFI call with correct arguments
let ret = unsafe {
ioctl(
f.as_raw_fd(),
match block_size_type {
BlockSize::LogicalBlock => BLKSSZGET(),
BlockSize::PhysicalBlock => BLKPBSZGET(),
BlockSize::MinimumIo => BLKIOMIN(),
BlockSize::OptimalIo => BLKIOOPT(),
} as _,
&mut block_size,
)
};
if ret != 0 {
return Err(std::io::Error::last_os_error());
};
Ok(block_size)
}
pub fn probe(f: &File) -> std::io::Result<Self> {
if !Self::is_block_device(f)? {
return Ok(DiskTopology::default());
}
Ok(DiskTopology {
logical_block_size: Self::query_block_size(f, BlockSize::LogicalBlock)?,
physical_block_size: Self::query_block_size(f, BlockSize::PhysicalBlock)?,
minimum_io_size: Self::query_block_size(f, BlockSize::MinimumIo)?,
optimal_io_size: Self::query_block_size(f, BlockSize::OptimalIo)?,
})
}
}

View File

@@ -3,9 +3,8 @@
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
use crate::async_io::{AsyncIo, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult};
use crate::qcow::{QcowFile, RawFile, Result as QcowResult};
use crate::AsyncAdaptor;
use std::collections::VecDeque;
use qcow::{QcowFile, RawFile, Result as QcowResult};
use std::fs::File;
use std::io::{Seek, SeekFrom};
use std::sync::{Arc, Mutex, MutexGuard};
@@ -27,7 +26,7 @@ impl DiskFile for QcowDiskSync {
fn size(&mut self) -> DiskFileResult<u64> {
let mut file = self.qcow_file.lock().unwrap();
file.seek(SeekFrom::End(0)).map_err(DiskFileError::Size)
Ok(file.seek(SeekFrom::End(0)).map_err(DiskFileError::Size)? as u64)
}
fn new_async_io(&self, _ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
@@ -38,7 +37,7 @@ impl DiskFile for QcowDiskSync {
pub struct QcowSync {
qcow_file: Arc<Mutex<QcowFile>>,
eventfd: EventFd,
completion_list: VecDeque<(u64, i32)>,
completion_list: Vec<(u64, i32)>,
}
impl QcowSync {
@@ -47,7 +46,7 @@ impl QcowSync {
qcow_file,
eventfd: EventFd::new(libc::EFD_NONBLOCK)
.expect("Failed creating EventFd for QcowSync"),
completion_list: VecDeque::new(),
completion_list: Vec::new(),
}
}
}
@@ -66,7 +65,7 @@ impl AsyncIo for QcowSync {
fn read_vectored(
&mut self,
offset: libc::off_t,
iovecs: &[libc::iovec],
iovecs: Vec<libc::iovec>,
user_data: u64,
) -> AsyncIoResult<()> {
self.qcow_file.read_vectored_sync(
@@ -81,7 +80,7 @@ impl AsyncIo for QcowSync {
fn write_vectored(
&mut self,
offset: libc::off_t,
iovecs: &[libc::iovec],
iovecs: Vec<libc::iovec>,
user_data: u64,
) -> AsyncIoResult<()> {
self.qcow_file.write_vectored_sync(
@@ -98,7 +97,7 @@ impl AsyncIo for QcowSync {
.fsync_sync(user_data, &self.eventfd, &mut self.completion_list)
}
fn next_completed_request(&mut self) -> Option<(u64, i32)> {
self.completion_list.pop_front()
fn complete(&mut self) -> Vec<(u64, i32)> {
self.completion_list.drain(..).collect()
}
}

View File

@@ -3,9 +3,8 @@
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
use crate::async_io::{
AsyncIo, AsyncIoError, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult,
AsyncIo, AsyncIoError, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult, DiskTopology,
};
use crate::DiskTopology;
use io_uring::{opcode, squeue, types, IoUring};
use std::fs::File;
use std::io::{Seek, SeekFrom};
@@ -24,9 +23,10 @@ impl RawFileDisk {
impl DiskFile for RawFileDisk {
fn size(&mut self) -> DiskFileResult<u64> {
self.file
Ok(self
.file
.seek(SeekFrom::End(0))
.map_err(DiskFileError::Size)
.map_err(DiskFileError::Size)? as u64)
}
fn new_async_io(&self, ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
@@ -37,7 +37,7 @@ impl DiskFile for RawFileDisk {
}
fn topology(&mut self) -> DiskTopology {
if let Ok(topology) = DiskTopology::probe(&self.file) {
if let Ok(topology) = DiskTopology::probe(&mut self.file) {
topology
} else {
warn!("Unable to get device topology. Using default topology");
@@ -77,17 +77,17 @@ impl AsyncIo for RawFileAsync {
fn read_vectored(
&mut self,
offset: libc::off_t,
iovecs: &[libc::iovec],
iovecs: Vec<libc::iovec>,
user_data: u64,
) -> AsyncIoResult<()> {
let (submitter, mut sq, _) = self.io_uring.split();
// SAFETY: we know the file descriptor is valid and we
// Safe because we know the file descriptor is valid and we
// relied on vm-memory to provide the buffer address.
let _ = unsafe {
sq.push(
&opcode::Readv::new(types::Fd(self.fd), iovecs.as_ptr(), iovecs.len() as u32)
.offset(offset.try_into().unwrap())
.offset(offset)
.build()
.flags(squeue::Flags::ASYNC)
.user_data(user_data),
@@ -105,17 +105,17 @@ impl AsyncIo for RawFileAsync {
fn write_vectored(
&mut self,
offset: libc::off_t,
iovecs: &[libc::iovec],
iovecs: Vec<libc::iovec>,
user_data: u64,
) -> AsyncIoResult<()> {
let (submitter, mut sq, _) = self.io_uring.split();
// SAFETY: we know the file descriptor is valid and we
// Safe because we know the file descriptor is valid and we
// relied on vm-memory to provide the buffer address.
let _ = unsafe {
sq.push(
&opcode::Writev::new(types::Fd(self.fd), iovecs.as_ptr(), iovecs.len() as u32)
.offset(offset.try_into().unwrap())
.offset(offset)
.build()
.flags(squeue::Flags::ASYNC)
.user_data(user_data),
@@ -134,7 +134,7 @@ impl AsyncIo for RawFileAsync {
if let Some(user_data) = user_data {
let (submitter, mut sq, _) = self.io_uring.split();
// SAFETY: we know the file descriptor is valid.
// Safe because we know the file descriptor is valid.
let _ = unsafe {
sq.push(
&opcode::Fsync::new(types::Fd(self.fd))
@@ -149,17 +149,20 @@ impl AsyncIo for RawFileAsync {
sq.sync();
submitter.submit().map_err(AsyncIoError::Fsync)?;
} else {
// SAFETY: FFI call with a valid fd
unsafe { libc::fsync(self.fd) };
}
Ok(())
}
fn next_completed_request(&mut self) -> Option<(u64, i32)> {
self.io_uring
.completion()
.next()
.map(|entry| (entry.user_data(), entry.result()))
fn complete(&mut self) -> Vec<(u64, i32)> {
let mut completion_list = Vec::new();
let cq = self.io_uring.completion();
for cq_entry in cq {
completion_list.push((cq_entry.user_data(), cq_entry.result()));
}
completion_list
}
}

View File

@@ -3,10 +3,8 @@
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
use crate::async_io::{
AsyncIo, AsyncIoError, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult,
AsyncIo, AsyncIoError, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult, DiskTopology,
};
use crate::DiskTopology;
use std::collections::VecDeque;
use std::fs::File;
use std::io::{Seek, SeekFrom};
use std::os::unix::io::{AsRawFd, RawFd};
@@ -24,9 +22,10 @@ impl RawFileDiskSync {
impl DiskFile for RawFileDiskSync {
fn size(&mut self) -> DiskFileResult<u64> {
self.file
Ok(self
.file
.seek(SeekFrom::End(0))
.map_err(DiskFileError::Size)
.map_err(DiskFileError::Size)? as u64)
}
fn new_async_io(&self, _ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
@@ -34,7 +33,7 @@ impl DiskFile for RawFileDiskSync {
}
fn topology(&mut self) -> DiskTopology {
if let Ok(topology) = DiskTopology::probe(&self.file) {
if let Ok(topology) = DiskTopology::probe(&mut self.file) {
topology
} else {
warn!("Unable to get device topology. Using default topology");
@@ -46,7 +45,7 @@ impl DiskFile for RawFileDiskSync {
pub struct RawFileSync {
fd: RawFd,
eventfd: EventFd,
completion_list: VecDeque<(u64, i32)>,
completion_list: Vec<(u64, i32)>,
}
impl RawFileSync {
@@ -54,7 +53,7 @@ impl RawFileSync {
RawFileSync {
fd,
eventfd: EventFd::new(libc::EFD_NONBLOCK).expect("Failed creating EventFd for RawFile"),
completion_list: VecDeque::new(),
completion_list: Vec::new(),
}
}
}
@@ -67,14 +66,13 @@ impl AsyncIo for RawFileSync {
fn read_vectored(
&mut self,
offset: libc::off_t,
iovecs: &[libc::iovec],
iovecs: Vec<libc::iovec>,
user_data: u64,
) -> AsyncIoResult<()> {
// SAFETY: FFI call with valid arguments
let result = unsafe {
libc::preadv(
self.fd as libc::c_int,
iovecs.as_ptr(),
iovecs.as_ptr() as *const libc::iovec,
iovecs.len() as libc::c_int,
offset,
)
@@ -83,7 +81,7 @@ impl AsyncIo for RawFileSync {
return Err(AsyncIoError::ReadVectored(std::io::Error::last_os_error()));
}
self.completion_list.push_back((user_data, result as i32));
self.completion_list.push((user_data, result as i32));
self.eventfd.write(1).unwrap();
Ok(())
@@ -92,14 +90,13 @@ impl AsyncIo for RawFileSync {
fn write_vectored(
&mut self,
offset: libc::off_t,
iovecs: &[libc::iovec],
iovecs: Vec<libc::iovec>,
user_data: u64,
) -> AsyncIoResult<()> {
// SAFETY: FFI call with valid arguments
let result = unsafe {
libc::pwritev(
self.fd as libc::c_int,
iovecs.as_ptr(),
iovecs.as_ptr() as *const libc::iovec,
iovecs.len() as libc::c_int,
offset,
)
@@ -108,28 +105,27 @@ impl AsyncIo for RawFileSync {
return Err(AsyncIoError::WriteVectored(std::io::Error::last_os_error()));
}
self.completion_list.push_back((user_data, result as i32));
self.completion_list.push((user_data, result as i32));
self.eventfd.write(1).unwrap();
Ok(())
}
fn fsync(&mut self, user_data: Option<u64>) -> AsyncIoResult<()> {
// SAFETY: FFI call
let result = unsafe { libc::fsync(self.fd as libc::c_int) };
if result < 0 {
return Err(AsyncIoError::Fsync(std::io::Error::last_os_error()));
}
if let Some(user_data) = user_data {
self.completion_list.push_back((user_data, result));
self.completion_list.push((user_data, result as i32));
self.eventfd.write(1).unwrap();
}
Ok(())
}
fn next_completed_request(&mut self) -> Option<(u64, i32)> {
self.completion_list.pop_front()
fn complete(&mut self) -> Vec<(u64, i32)> {
self.completion_list.drain(..).collect()
}
}

View File

@@ -2,10 +2,9 @@
//
// SPDX-License-Identifier: Apache-2.0
use crate::{read_aligned_block_size, DiskTopology};
use std::convert::TryInto;
use std::fs::File;
use std::io::{Seek, SeekFrom};
use std::io::{Read, Seek, SeekFrom};
#[derive(Clone, Copy)]
pub struct VhdFooter {
@@ -28,33 +27,37 @@ pub struct VhdFooter {
impl VhdFooter {
pub fn new(file: &mut File) -> std::io::Result<VhdFooter> {
let blocksize = DiskTopology::probe(file)?.logical_block_size as usize;
// We must create a buffer aligned on 512 bytes with a size being a
// multiple of 512 bytes as the file might be opened with O_DIRECT flag.
#[repr(align(512))]
struct Sector {
data: [u8; 512],
}
let mut s = Sector { data: [0; 512] };
// Place the cursor in the last block of the file
file.seek(SeekFrom::End(0 - (blocksize as i64)))?;
// Read in the last block
let data = read_aligned_block_size(file)?;
// Place the cursor 512 bytes before the end of the file, as this is
// where the footer starts.
file.seek(SeekFrom::End(-512))?;
// We only care about the last sector
let offset = blocksize - 512;
let sector = &data[offset..];
// Fill in the VhdFooter structure
file.read_exact(&mut s.data)?;
Ok(VhdFooter {
cookie: u64::from_be_bytes(sector[0..8].try_into().unwrap()),
features: u32::from_be_bytes(sector[8..12].try_into().unwrap()),
file_format_version: u32::from_be_bytes(sector[12..16].try_into().unwrap()),
data_offset: u64::from_be_bytes(sector[16..24].try_into().unwrap()),
time_stamp: u32::from_be_bytes(sector[24..28].try_into().unwrap()),
creator_application: u32::from_be_bytes(sector[28..32].try_into().unwrap()),
creator_version: u32::from_be_bytes(sector[32..36].try_into().unwrap()),
creator_host_os: u32::from_be_bytes(sector[36..40].try_into().unwrap()),
original_size: u64::from_be_bytes(sector[40..48].try_into().unwrap()),
current_size: u64::from_be_bytes(sector[48..56].try_into().unwrap()),
disk_geometry: u32::from_be_bytes(sector[56..60].try_into().unwrap()),
disk_type: u32::from_be_bytes(sector[60..64].try_into().unwrap()),
checksum: u32::from_be_bytes(sector[64..68].try_into().unwrap()),
unique_id: u128::from_be_bytes(sector[68..84].try_into().unwrap()),
saved_state: u8::from_be_bytes(sector[84..85].try_into().unwrap()),
cookie: u64::from_be_bytes(s.data[0..8].try_into().unwrap()),
features: u32::from_be_bytes(s.data[8..12].try_into().unwrap()),
file_format_version: u32::from_be_bytes(s.data[12..16].try_into().unwrap()),
data_offset: u64::from_be_bytes(s.data[16..24].try_into().unwrap()),
time_stamp: u32::from_be_bytes(s.data[24..28].try_into().unwrap()),
creator_application: u32::from_be_bytes(s.data[28..32].try_into().unwrap()),
creator_version: u32::from_be_bytes(s.data[32..36].try_into().unwrap()),
creator_host_os: u32::from_be_bytes(s.data[36..40].try_into().unwrap()),
original_size: u64::from_be_bytes(s.data[40..48].try_into().unwrap()),
current_size: u64::from_be_bytes(s.data[48..56].try_into().unwrap()),
disk_geometry: u32::from_be_bytes(s.data[56..60].try_into().unwrap()),
disk_type: u32::from_be_bytes(s.data[60..64].try_into().unwrap()),
checksum: u32::from_be_bytes(s.data[64..68].try_into().unwrap()),
unique_id: u128::from_be_bytes(s.data[68..84].try_into().unwrap()),
saved_state: u8::from_be_bytes(s.data[84..85].try_into().unwrap()),
})
}

View File

@@ -3,11 +3,10 @@
// SPDX-License-Identifier: Apache-2.0
use crate::async_io::{AsyncIo, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult};
use crate::vhdx::{Result as VhdxResult, Vhdx};
use crate::AsyncAdaptor;
use std::collections::VecDeque;
use std::fs::File;
use std::sync::{Arc, Mutex, MutexGuard};
use vhdx::vhdx::{Result as VhdxResult, Vhdx};
use vmm_sys_util::eventfd::EventFd;
pub struct VhdxDiskSync {
@@ -38,7 +37,7 @@ impl DiskFile for VhdxDiskSync {
pub struct VhdxSync {
vhdx_file: Arc<Mutex<Vhdx>>,
eventfd: EventFd,
completion_list: VecDeque<(u64, i32)>,
completion_list: Vec<(u64, i32)>,
}
impl VhdxSync {
@@ -46,7 +45,7 @@ impl VhdxSync {
Ok(VhdxSync {
vhdx_file,
eventfd: EventFd::new(libc::EFD_NONBLOCK)?,
completion_list: VecDeque::new(),
completion_list: Vec::new(),
})
}
}
@@ -65,7 +64,7 @@ impl AsyncIo for VhdxSync {
fn read_vectored(
&mut self,
offset: libc::off_t,
iovecs: &[libc::iovec],
iovecs: Vec<libc::iovec>,
user_data: u64,
) -> AsyncIoResult<()> {
self.vhdx_file.read_vectored_sync(
@@ -80,7 +79,7 @@ impl AsyncIo for VhdxSync {
fn write_vectored(
&mut self,
offset: libc::off_t,
iovecs: &[libc::iovec],
iovecs: Vec<libc::iovec>,
user_data: u64,
) -> AsyncIoResult<()> {
self.vhdx_file.write_vectored_sync(
@@ -97,7 +96,7 @@ impl AsyncIo for VhdxSync {
.fsync_sync(user_data, &self.eventfd, &mut self.completion_list)
}
fn next_completed_request(&mut self) -> Option<(u64, i32)> {
self.completion_list.pop_front()
fn complete(&mut self) -> Vec<(u64, i32)> {
self.completion_list.drain(..).collect()
}
}

View File

@@ -3,12 +3,15 @@
// SPDX-License-Identifier: Apache-2.0
//
#[macro_use(crate_version)]
extern crate clap;
use std::process::Command;
fn main() {
let mut version = "v".to_owned() + env!("CARGO_PKG_VERSION");
let mut version = "v".to_owned() + crate_version!();
if let Ok(git_out) = Command::new("git").args(["describe", "--dirty"]).output() {
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;
@@ -17,8 +20,8 @@ fn main() {
}
// This println!() has a special behavior, as it will set the environment
// variable BUILD_VERSION, so that it can be reused from the binary.
// 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=BUILD_VERSION={version}");
println!("cargo:rustc-env=BUILT_VERSION={}", version);
}

View File

@@ -5,28 +5,22 @@ authors = ["The Chromium OS Authors"]
edition = "2021"
[dependencies]
acpi_tables = { git = "https://github.com/rust-vmm/acpi_tables", branch = "main" }
anyhow = "1.0.75"
acpi_tables = { path = "../acpi_tables" }
anyhow = "1.0.56"
arch = { path = "../arch" }
bitflags = "2.3.3"
bitflags = "1.3.2"
byteorder = "1.4.3"
event_monitor = { path = "../event_monitor" }
hypervisor = { path = "../hypervisor" }
libc = "0.2.147"
log = "0.4.17"
pci = { path = "../pci" }
thiserror = "1.0.40"
tpm = { path = "../tpm" }
versionize = "0.1.10"
epoll = "4.3.1"
libc = "0.2.123"
log = "0.4.16"
versionize = "0.1.6"
versionize_derive = "0.1.4"
vm-allocator = { path = "../vm-allocator" }
vm-device = { path = "../vm-device" }
vm-memory = "0.12.2"
vm-memory = "0.7.0"
vm-migration = { path = "../vm-migration" }
vmm-sys-util = "0.11.0"
[target.'cfg(target_arch = "aarch64")'.dependencies]
arch = { path = "../arch" }
vmm-sys-util = "0.9.0"
[features]
default = []
cmos = []
fwdebug = []

View File

@@ -4,10 +4,8 @@
//
use super::AcpiNotificationFlags;
use acpi_tables::{aml, Aml, AmlSink};
use std::sync::atomic::{AtomicBool, Ordering};
use acpi_tables::{aml, aml::Aml};
use std::sync::{Arc, Barrier};
use std::thread;
use std::time::Instant;
use vm_device::interrupt::InterruptSourceGroup;
use vm_device::BusDevice;
@@ -20,20 +18,14 @@ pub const GED_DEVICE_ACPI_SIZE: usize = 0x1;
pub struct AcpiShutdownDevice {
exit_evt: EventFd,
reset_evt: EventFd,
vcpus_kill_signalled: Arc<AtomicBool>,
}
impl AcpiShutdownDevice {
/// Constructs a device that will signal the given event when the guest requests it.
pub fn new(
exit_evt: EventFd,
reset_evt: EventFd,
vcpus_kill_signalled: Arc<AtomicBool>,
) -> AcpiShutdownDevice {
pub fn new(exit_evt: EventFd, reset_evt: EventFd) -> AcpiShutdownDevice {
AcpiShutdownDevice {
exit_evt,
reset_evt,
vcpus_kill_signalled,
}
}
}
@@ -51,13 +43,6 @@ impl BusDevice for AcpiShutdownDevice {
if let Err(e) = self.reset_evt.write(1) {
error!("Error triggering ACPI reset event: {}", e);
}
// Spin until we are sure the reset_evt has been handled and that when
// we return from the KVM_RUN we will exit rather than re-enter the guest.
while !self.vcpus_kill_signalled.load(Ordering::SeqCst) {
// This is more effective than thread::yield_now() at
// avoiding a priority inversion with the VMM thread
thread::sleep(std::time::Duration::from_millis(1));
}
}
// The ACPI DSDT table specifies the S5 sleep state (shutdown) as value 5
const S5_SLEEP_VALUE: u8 = 5;
@@ -68,13 +53,6 @@ impl BusDevice for AcpiShutdownDevice {
if let Err(e) = self.exit_evt.write(1) {
error!("Error triggering ACPI shutdown event: {}", e);
}
// Spin until we are sure the reset_evt has been handled and that when
// we return from the KVM_RUN we will exit rather than re-enter the guest.
while !self.vcpus_kill_signalled.load(Ordering::SeqCst) {
// This is more effective than thread::yield_now() at
// avoiding a priority inversion with the VMM thread
thread::sleep(std::time::Duration::from_millis(1));
}
}
None
}
@@ -125,38 +103,37 @@ impl BusDevice for AcpiGedDevice {
}
impl Aml for AcpiGedDevice {
fn to_aml_bytes(&self, sink: &mut dyn AmlSink) {
fn append_aml_bytes(&self, bytes: &mut Vec<u8>) {
aml::Device::new(
"_SB_.GEC_".into(),
"_SB_.GED_".into(),
vec![
&aml::Name::new("_HID".into(), &aml::EISAName::new("PNP0A06")),
&aml::Name::new("_UID".into(), &"Generic Event Controller"),
&aml::Name::new("_HID".into(), &"ACPI0013"),
&aml::Name::new("_UID".into(), &aml::ZERO),
&aml::Name::new(
"_CRS".into(),
&aml::ResourceTemplate::new(vec![&aml::AddressSpace::new_memory(
aml::AddressSpaceCacheable::NotCacheable,
&aml::ResourceTemplate::new(vec![&aml::Interrupt::new(
true,
self.address.0,
self.address.0 + GED_DEVICE_ACPI_SIZE as u64 - 1,
None,
true,
false,
false,
self.ged_irq,
)]),
),
&aml::OpRegion::new(
"GDST".into(),
aml::OpRegionSpace::SystemMemory,
&(self.address.0 as usize),
&GED_DEVICE_ACPI_SIZE,
self.address.0 as usize,
GED_DEVICE_ACPI_SIZE,
),
&aml::Field::new(
"GDST".into(),
aml::FieldAccessType::Byte,
aml::FieldLockRule::NoLock,
aml::FieldUpdateRule::WriteAsZeroes,
vec![aml::FieldEntry::Named(*b"GDAT", 8)],
),
&aml::Method::new(
"ESCN".into(),
0,
"_EVT".into(),
1,
true,
vec![
&aml::Store::new(&aml::Local(0), &aml::Path::new("GDAT")),
@@ -187,31 +164,7 @@ impl Aml for AcpiGedDevice {
),
],
)
.to_aml_bytes(sink);
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::Method::new(
"_EVT".into(),
1,
true,
vec![&aml::MethodCall::new("\\_SB_.GEC_.ESCN".into(), vec![])],
),
],
)
.to_aml_bytes(sink)
.append_aml_bytes(bytes)
}
}

View File

@@ -4,45 +4,36 @@
use super::interrupt_controller::{Error, InterruptController};
extern crate arch;
use anyhow::anyhow;
use arch::layout;
use hypervisor::{
arch::aarch64::gic::{Vgic, VgicConfig},
CpuState, GicState,
};
use arch::aarch64::gic::GicDevice;
use std::result;
use std::sync::{Arc, Mutex};
use vm_device::interrupt::{
InterruptIndex, InterruptManager, InterruptSourceConfig, InterruptSourceGroup,
LegacyIrqSourceConfig, MsiIrqGroupConfig,
};
use vm_memory::address::Address;
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
use vmm_sys_util::eventfd::EventFd;
type Result<T> = result::Result<T, Error>;
// Reserve 32 IRQs for legacy devices.
pub const IRQ_LEGACY_BASE: usize = layout::IRQ_BASE as usize;
// Reserve 32 IRQs for legacy device.
pub const IRQ_LEGACY_BASE: usize = arch::layout::IRQ_BASE as usize;
pub const IRQ_LEGACY_COUNT: usize = 32;
pub const GIC_SNAPSHOT_ID: &str = "gic-v3-its";
// Gic (Generic Interrupt Controller) struct provides all the functionality of a
// GIC device. It wraps a hypervisor-emulated GIC device (Vgic) provided by the
// `hypervisor` crate.
// Gic struct also implements InterruptController to provide interrupt delivery
// service.
// 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<dyn InterruptSourceGroup>,
// The hypervisor agnostic virtual GIC
vgic: Option<Arc<Mutex<dyn Vgic>>>,
gic_device: Option<Arc<Mutex<Box<dyn GicDevice>>>>,
}
impl Gic {
pub fn new(
vcpu_count: u8,
_vcpu_count: u8,
interrupt_manager: Arc<dyn InterruptManager<GroupConfig = MsiIrqGroupConfig>>,
vm: Arc<dyn hypervisor::Vm>,
) -> Result<Gic> {
let interrupt_source_group = interrupt_manager
.create_group(MsiIrqGroupConfig {
@@ -51,34 +42,22 @@ impl Gic {
})
.map_err(Error::CreateInterruptSourceGroup)?;
let vgic = vm
.create_vgic(Gic::create_default_config(vcpu_count as u64))
.map_err(Error::CreateGic)?;
let gic = Gic {
Ok(Gic {
interrupt_source_group,
vgic: Some(vgic),
};
gic.enable()?;
Ok(gic)
gic_device: None,
})
}
pub fn restore_vgic(
&mut self,
state: Option<GicState>,
saved_vcpu_states: &[CpuState],
) -> Result<()> {
self.set_gicr_typers(saved_vcpu_states);
self.vgic
.clone()
.unwrap()
.lock()
.unwrap()
.set_state(&state.unwrap())
.map_err(Error::RestoreGic)
pub fn set_gic_device(&mut self, gic_device: Arc<Mutex<Box<dyn GicDevice>>>) {
self.gic_device = Some(gic_device);
}
pub fn get_gic_device(&self) -> Option<&Arc<Mutex<Box<dyn GicDevice>>>> {
self.gic_device.as_ref()
}
}
impl InterruptController for Gic {
fn enable(&self) -> Result<()> {
// Set irqfd for legacy interrupts
self.interrupt_source_group
@@ -98,44 +77,12 @@ impl Gic {
i as InterruptIndex,
InterruptSourceConfig::LegacyIrq(config),
false,
false,
)
.map_err(Error::EnableInterrupt)?;
}
self.interrupt_source_group
.set_gsi()
.map_err(Error::EnableInterrupt)?;
Ok(())
}
/// Default config implied by arch::layout
pub fn create_default_config(vcpu_count: u64) -> VgicConfig {
let redists_size = layout::GIC_V3_REDIST_SIZE * vcpu_count;
let redists_addr = layout::GIC_V3_DIST_START.raw_value() - redists_size;
VgicConfig {
vcpu_count,
dist_addr: layout::GIC_V3_DIST_START.raw_value(),
dist_size: layout::GIC_V3_DIST_SIZE,
redists_addr,
redists_size,
msi_addr: redists_addr - layout::GIC_V3_ITS_SIZE,
msi_size: layout::GIC_V3_ITS_SIZE,
nr_irqs: layout::IRQ_NUM,
}
}
pub fn get_vgic(&mut self) -> Result<Arc<Mutex<dyn Vgic>>> {
Ok(self.vgic.clone().unwrap())
}
pub fn set_gicr_typers(&mut self, vcpu_states: &[CpuState]) {
let vgic = self.vgic.as_ref().unwrap().clone();
vgic.lock().unwrap().set_gicr_typers(vcpu_states);
}
}
impl InterruptController for Gic {
// This should be called anytime an interrupt needs to be injected into the
// running guest.
fn service_irq(&mut self, irq: usize) -> Result<()> {
@@ -150,31 +97,3 @@ impl InterruptController for Gic {
self.interrupt_source_group.notifier(irq as InterruptIndex)
}
}
impl Snapshottable for Gic {
fn id(&self) -> String {
GIC_SNAPSHOT_ID.to_string()
}
fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
let vgic = self.vgic.as_ref().unwrap().clone();
let state = vgic.lock().unwrap().state().unwrap();
Snapshot::new_from_state(&state)
}
}
impl Pausable for Gic {
fn pause(&mut self) -> std::result::Result<(), MigratableError> {
// Flush tables to guest RAM
let vgic = self.vgic.as_ref().unwrap().clone();
vgic.lock().unwrap().save_data_tables().map_err(|e| {
MigratableError::Pause(anyhow!(
"Could not save GICv3ITS GIC pending tables {:?}",
e
))
})?;
Ok(())
}
}
impl Transportable for Gic {}
impl Migratable for Gic {}

View File

@@ -24,12 +24,6 @@ pub enum Error {
UpdateInterrupt(io::Error),
/// Failed enabling the interrupt.
EnableInterrupt(io::Error),
#[cfg(target_arch = "aarch64")]
/// Failed creating GIC device.
CreateGic(hypervisor::HypervisorVmError),
#[cfg(target_arch = "aarch64")]
/// Failed restoring GIC device.
RestoreGic(hypervisor::arch::aarch64::gic::Error),
}
type Result<T> = result::Result<T, Error>;
@@ -59,6 +53,8 @@ pub struct MsiMessage {
// 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);
fn notifier(&self, irq: usize) -> Option<EventFd>;

View File

@@ -10,6 +10,7 @@
// 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 byteorder::{ByteOrder, LittleEndian};
use std::result;
use std::sync::{Arc, Barrier};
@@ -193,7 +194,6 @@ impl Ioapic {
id: String,
apic_address: GuestAddress,
interrupt_manager: Arc<dyn InterruptManager<GroupConfig = MsiIrqGroupConfig>>,
state: Option<IoapicState>,
) -> Result<Ioapic> {
let interrupt_source_group = interrupt_manager
.create_group(MsiIrqGroupConfig {
@@ -202,52 +202,17 @@ impl Ioapic {
})
.map_err(Error::CreateInterruptSourceGroup)?;
let (id_reg, reg_sel, reg_entries, used_entries, apic_address) = if let Some(state) = &state
{
(
state.id_reg,
state.reg_sel,
state.reg_entries,
state.used_entries,
GuestAddress(state.apic_address),
)
} else {
(
0,
0,
[0x10000; NUM_IOAPIC_PINS],
[false; NUM_IOAPIC_PINS],
apic_address,
)
};
// The IOAPIC is created with entries already masked. The guest will be
// in charge of unmasking them if/when necessary.
let ioapic = Ioapic {
Ok(Ioapic {
id,
id_reg,
reg_sel,
reg_entries,
used_entries,
id_reg: 0,
reg_sel: 0,
reg_entries: [0x10000; NUM_IOAPIC_PINS],
used_entries: [false; NUM_IOAPIC_PINS],
apic_address,
interrupt_source_group,
};
// When restoring the Ioapic, we must enable used entries.
if state.is_some() {
for (irq, entry) in ioapic.used_entries.iter().enumerate() {
if *entry {
ioapic.update_entry(irq, false)?;
}
}
ioapic
.interrupt_source_group
.set_gsi()
.map_err(Error::UpdateInterrupt)?;
}
Ok(ioapic)
})
}
fn ioapic_write(&mut self, val: u32) {
@@ -283,7 +248,7 @@ impl Ioapic {
}
// The entry must be updated through the interrupt source
// group.
if let Err(e) = self.update_entry(index, true) {
if let Err(e) = self.update_entry(index) {
error!("Failed updating IOAPIC entry: {:?}", e);
}
// Store the information this IRQ is now being used.
@@ -334,7 +299,22 @@ impl Ioapic {
}
}
fn update_entry(&self, irq: usize, set_gsi: bool) -> Result<()> {
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 = GuestAddress(state.apic_address);
for (irq, entry) in self.used_entries.iter().enumerate() {
if *entry {
self.update_entry(irq)?;
}
}
Ok(())
}
fn update_entry(&self, irq: usize) -> Result<()> {
let entry = self.reg_entries[irq];
// Validate Destination Mode value, and retrieve Destination ID
@@ -391,7 +371,6 @@ impl Ioapic {
irq as InterruptIndex,
InterruptSourceConfig::MsiIrq(config),
interrupt_mask(entry) == 1,
set_gsi,
)
.map_err(Error::UpdateInterrupt)?;
@@ -444,7 +423,18 @@ impl Snapshottable for Ioapic {
}
fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
Snapshot::new_from_versioned_state(&self.state())
Snapshot::new_from_versioned_state(&self.id, &self.state())
}
fn restore(&mut self, snapshot: Snapshot) -> std::result::Result<(), MigratableError> {
self.set_state(&snapshot.to_versioned_state(&self.id)?)
.map_err(|e| {
MigratableError::Restore(anyhow!(
"Could not restore state for {}: {:?}",
self.id,
e
))
})
}
}

View File

@@ -5,9 +5,7 @@
use libc::{clock_gettime, gmtime_r, timespec, tm, CLOCK_REALTIME};
use std::cmp::min;
use std::mem;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Barrier};
use std::thread;
use vm_device::BusDevice;
use vmm_sys_util::eventfd::EventFd;
@@ -25,19 +23,13 @@ pub struct Cmos {
index: u8,
data: [u8; DATA_LEN],
reset_evt: EventFd,
vcpus_kill_signalled: Option<Arc<AtomicBool>>,
}
impl Cmos {
/// Constructs a CMOS/RTC device with initial data.
/// `mem_below_4g` is the size of memory in bytes below the 32-bit gap.
/// `mem_above_4g` is the size of memory in bytes above the 32-bit gap.
pub fn new(
mem_below_4g: u64,
mem_above_4g: u64,
reset_evt: EventFd,
vcpus_kill_signalled: Option<Arc<AtomicBool>>,
) -> Cmos {
pub fn new(mem_below_4g: u64, mem_above_4g: u64, reset_evt: EventFd) -> Cmos {
let mut data = [0u8; DATA_LEN];
// Extended memory from 16 MB to 4 GB in units of 64 KB
@@ -58,7 +50,6 @@ impl Cmos {
index: 0,
data,
reset_evt,
vcpus_kill_signalled,
}
}
}
@@ -76,15 +67,6 @@ impl BusDevice for Cmos {
if self.index == 0x8f && data[0] == 0 {
info!("CMOS reset");
self.reset_evt.write(1).unwrap();
if let Some(vcpus_kill_signalled) = self.vcpus_kill_signalled.take() {
// Spin until we are sure the reset_evt has been handled and that when
// we return from the KVM_RUN we will exit rather than re-enter the guest.
while !vcpus_kill_signalled.load(Ordering::SeqCst) {
// This is more effective than thread::yield_now() at
// avoiding a priority inversion with the VMM thread
thread::sleep(std::time::Duration::from_millis(1));
}
}
} else {
self.data[(self.index & INDEX_MASK) as usize] = data[0]
}
@@ -115,7 +97,7 @@ impl BusDevice for Cmos {
let day;
let month;
let year;
// SAFETY: The clock_gettime and gmtime_r calls are safe as long as the structs they are
// 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 {

View File

@@ -1,86 +0,0 @@
// Copyright © 2022 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
use std::fmt;
use std::time::Instant;
use vm_device::BusDevice;
/// Debug I/O port, see:
/// https://www.intel.com/content/www/us/en/support/articles/000005500/boards-and-kits.html
///
/// Since we're not a physical platform, we can freely assign code ranges for
/// debugging specific parts of our virtual platform.
pub enum DebugIoPortRange {
Firmware,
Bootloader,
Kernel,
Userspace,
Custom,
}
#[cfg(target_arch = "x86_64")]
const DEBUG_IOPORT_PREFIX: &str = "Debug I/O port";
#[cfg(target_arch = "x86_64")]
impl DebugIoPortRange {
fn from_u8(value: u8) -> DebugIoPortRange {
match value {
0x00..=0x1f => DebugIoPortRange::Firmware,
0x20..=0x3f => DebugIoPortRange::Bootloader,
0x40..=0x5f => DebugIoPortRange::Kernel,
0x60..=0x7f => DebugIoPortRange::Userspace,
_ => DebugIoPortRange::Custom,
}
}
}
#[cfg(target_arch = "x86_64")]
impl fmt::Display for DebugIoPortRange {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
DebugIoPortRange::Firmware => write!(f, "{DEBUG_IOPORT_PREFIX}: Firmware"),
DebugIoPortRange::Bootloader => write!(f, "{DEBUG_IOPORT_PREFIX}: Bootloader"),
DebugIoPortRange::Kernel => write!(f, "{DEBUG_IOPORT_PREFIX}: Kernel"),
DebugIoPortRange::Userspace => write!(f, "{DEBUG_IOPORT_PREFIX}: Userspace"),
DebugIoPortRange::Custom => write!(f, "{DEBUG_IOPORT_PREFIX}: Custom"),
}
}
}
pub struct DebugPort {
timestamp: Instant,
}
impl DebugPort {
pub fn new(timestamp: Instant) -> Self {
Self { timestamp }
}
}
impl BusDevice for DebugPort {
fn read(&mut self, _base: u64, _offset: u64, _data: &mut [u8]) {
error!("Invalid read to debug port")
}
fn write(
&mut self,
_base: u64,
_offset: u64,
data: &[u8],
) -> Option<std::sync::Arc<std::sync::Barrier>> {
let elapsed = self.timestamp.elapsed();
let code = data[0];
warn!(
"[{} code 0x{:x}] {}.{:>06} seconds",
DebugIoPortRange::from_u8(code),
code,
elapsed.as_secs(),
elapsed.as_micros()
);
None
}
}

View File

@@ -31,7 +31,7 @@ const GPIOIC: u64 = 0x41c; // Interrupt Clear Register
const GPIOAFSEL: u64 = 0x420; // Mode Control Select Register
// From 0x424 to 0xFDC => reserved space.
// From 0xFE0 to 0xFFC => Peripheral and PrimeCell Identification Registers which are Read Only registers.
// These registers can conceptually be treated as a 32-bit register, and PartNumber[11:0] is used to identify the peripheral.
// Thses registers can conceptually be treated as a 32-bit register, and PartNumber[11:0] is used to identify the peripheral.
// We are putting the expected values (look at 'Reset value' column from above mentioned document) in an array.
const GPIO_ID: [u8; 8] = [0x61, 0x10, 0x14, 0x00, 0x0d, 0xf0, 0x05, 0xb1];
// ID Margins
@@ -51,13 +51,13 @@ pub enum 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::BadWriteOffset(offset) => write!(f, "Bad Write Offset: {}", offset),
Error::GpioInterruptDisabled => write!(f, "GPIO interrupt disabled by guest driver.",),
Error::GpioInterruptFailure(ref e) => {
write!(f, "Could not trigger GPIO interrupt: {e}.")
write!(f, "Could not trigger GPIO interrupt: {}.", e)
}
Error::GpioTriggerKeyFailure(key) => {
write!(f, "Invalid GPIO Input key triggered: {key}.")
write!(f, "Invalid GPIO Input key triggerd: {}.", key)
}
}
}
@@ -106,39 +106,18 @@ impl VersionMapped for GpioState {}
impl Gpio {
/// Constructs an PL061 GPIO device.
pub fn new(
id: String,
interrupt: Arc<dyn InterruptSourceGroup>,
state: Option<GpioState>,
) -> Self {
let (data, old_in_data, dir, isense, ibe, iev, im, istate, afsel) =
if let Some(state) = state {
(
state.data,
state.old_in_data,
state.dir,
state.isense,
state.ibe,
state.iev,
state.im,
state.istate,
state.afsel,
)
} else {
(0, 0, 0, 0, 0, 0, 0, 0, 0)
};
pub fn new(id: String, interrupt: Arc<dyn InterruptSourceGroup>) -> Self {
Self {
id,
data,
old_in_data,
dir,
isense,
ibe,
iev,
im,
istate,
afsel,
data: 0,
old_in_data: 0,
dir: 0,
isense: 0,
ibe: 0,
iev: 0,
im: 0,
istate: 0,
afsel: 0,
interrupt,
}
}
@@ -157,12 +136,24 @@ impl Gpio {
}
}
fn set_state(&mut self, state: &GpioState) {
self.data = state.data;
self.old_in_data = state.old_in_data;
self.dir = state.dir;
self.isense = state.isense;
self.ibe = state.ibe;
self.iev = state.iev;
self.im = state.im;
self.istate = state.istate;
self.afsel = state.afsel;
}
fn pl061_internal_update(&mut self) {
// FIXME:
// Missing Output Interrupt Emulation.
// Input Edging Interrupt Emulation.
let changed = (self.old_in_data ^ self.data) & !self.dir;
let changed = ((self.old_in_data ^ self.data) & !self.dir) as u32;
if changed > 0 {
self.old_in_data = self.data;
for i in 0..N_GPIOS {
@@ -328,7 +319,12 @@ impl Snapshottable for Gpio {
}
fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
Snapshot::new_from_versioned_state(&self.state())
Snapshot::new_from_versioned_state(&self.id, &self.state())
}
fn restore(&mut self, snapshot: Snapshot) -> std::result::Result<(), MigratableError> {
self.set_state(&snapshot.to_versioned_state(&self.id)?);
Ok(())
}
}
@@ -361,15 +357,10 @@ mod tests {
_index: InterruptIndex,
_config: InterruptSourceConfig,
_masked: bool,
_set_gsi: bool,
) -> result::Result<(), std::io::Error> {
Ok(())
}
fn set_gsi(&self) -> result::Result<(), std::io::Error> {
Ok(())
}
fn notifier(&self, _index: InterruptIndex) -> Option<EventFd> {
Some(self.event_fd.try_clone().unwrap())
}
@@ -387,7 +378,6 @@ mod tests {
let mut gpio = Gpio::new(
String::from(GPIO_NAME),
Arc::new(TestInterrupt::new(intr_evt.try_clone().unwrap())),
None,
);
let mut data = [0; 4];

View File

@@ -2,27 +2,19 @@
// 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::{
atomic::{AtomicBool, Ordering},
Arc, Barrier,
};
use std::thread;
use std::sync::{Arc, Barrier};
use vm_device::BusDevice;
use vmm_sys_util::eventfd::EventFd;
/// A i8042 PS/2 controller that emulates just enough to shutdown the machine.
pub struct I8042Device {
reset_evt: EventFd,
vcpus_kill_signalled: Arc<AtomicBool>,
}
impl I8042Device {
/// Constructs a i8042 device that will signal the given event when the guest requests it.
pub fn new(reset_evt: EventFd, vcpus_kill_signalled: Arc<AtomicBool>) -> I8042Device {
I8042Device {
reset_evt,
vcpus_kill_signalled,
}
pub fn new(reset_evt: EventFd) -> I8042Device {
I8042Device { reset_evt }
}
}
@@ -46,13 +38,6 @@ impl BusDevice for I8042Device {
if let Err(e) = self.reset_evt.write(1) {
error!("Error triggering i8042 reset event: {}", e);
}
// Spin until we are sure the reset_evt has been handled and that when
// we return from the KVM_RUN we will exit rather than re-enter the guest.
while !self.vcpus_kill_signalled.load(Ordering::SeqCst) {
// This is more effective than thread::yield_now() at
// avoiding a priority inversion with the VMM thread
thread::sleep(std::time::Duration::from_millis(1));
}
}
None

View File

@@ -6,9 +6,7 @@
// found in the LICENSE-BSD-3-Clause file.
mod cmos;
#[cfg(target_arch = "x86_64")]
mod debug_port;
#[cfg(target_arch = "x86_64")]
#[cfg(feature = "fwdebug")]
mod fwdebug;
#[cfg(target_arch = "aarch64")]
mod gpio_pl061;
@@ -20,9 +18,7 @@ mod serial;
mod uart_pl011;
pub use self::cmos::Cmos;
#[cfg(target_arch = "x86_64")]
pub use self::debug_port::DebugPort;
#[cfg(target_arch = "x86_64")]
#[cfg(feature = "fwdebug")]
pub use self::fwdebug::FwDebugDevice;
pub use self::i8042::I8042Device;
pub use self::serial::Serial;

View File

@@ -48,8 +48,8 @@ pub enum 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}"),
Error::BadWriteOffset(offset) => write!(f, "Bad Write Offset: {}", offset),
Error::InterruptFailure(e) => write!(f, "Failed to trigger interrupt: {}", e),
}
}
}
@@ -61,10 +61,12 @@ 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,
}
@@ -99,7 +101,7 @@ pub struct LocalTime {
impl LocalTime {
/// Returns the [LocalTime](struct.LocalTime.html) structure for the calling moment.
#[cfg(test)]
#[allow(dead_code)]
pub fn now() -> LocalTime {
let mut timespec = libc::timespec {
tv_sec: 0,
@@ -119,7 +121,7 @@ impl LocalTime {
tm_zone: std::ptr::null(),
};
// SAFETY: the parameters are valid.
// Safe because the parameters are valid.
unsafe {
libc::clock_gettime(libc::CLOCK_REALTIME, &mut timespec);
libc::localtime_r(&timespec.tv_sec, &mut tm);
@@ -171,6 +173,22 @@ impl Default for TimestampUs {
}
}
/// 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
@@ -181,7 +199,7 @@ pub fn get_time(clock_type: ClockType) -> u64 {
tv_sec: 0,
tv_nsec: 0,
};
// SAFETY: the parameters are valid.
// 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)
}
@@ -413,15 +431,10 @@ mod tests {
_index: InterruptIndex,
_config: InterruptSourceConfig,
_masked: bool,
_set_gsi: bool,
) -> result::Result<(), std::io::Error> {
Ok(())
}
fn set_gsi(&self) -> result::Result<(), std::io::Error> {
Ok(())
}
fn notifier(&self, _index: InterruptIndex) -> Option<EventFd> {
Some(self.event_fd.try_clone().unwrap())
}
@@ -512,6 +525,7 @@ mod tests {
($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,

View File

@@ -63,6 +63,7 @@ pub struct Serial {
id: String,
interrupt_enable: u8,
interrupt_identification: u8,
interrupt: Arc<dyn InterruptSourceGroup>,
line_control: u8,
line_status: u8,
modem_control: u8,
@@ -70,7 +71,6 @@ pub struct Serial {
scratch: u8,
baud_divisor: u16,
in_buffer: VecDeque<u8>,
interrupt: Arc<dyn InterruptSourceGroup>,
out: Option<Box<dyn io::Write + Send>>,
}
@@ -93,56 +93,19 @@ impl Serial {
id: String,
interrupt: Arc<dyn InterruptSourceGroup>,
out: Option<Box<dyn io::Write + Send>>,
state: Option<SerialState>,
) -> Serial {
let (
interrupt_enable,
interrupt_identification,
line_control,
line_status,
modem_control,
modem_status,
scratch,
baud_divisor,
in_buffer,
) = if let Some(state) = state {
(
state.interrupt_enable,
state.interrupt_identification,
state.line_control,
state.line_status,
state.modem_control,
state.modem_status,
state.scratch,
state.baud_divisor,
state.in_buffer.into(),
)
} else {
(
0,
DEFAULT_INTERRUPT_IDENTIFICATION,
DEFAULT_LINE_CONTROL,
DEFAULT_LINE_STATUS,
DEFAULT_MODEM_CONTROL,
DEFAULT_MODEM_STATUS,
0,
DEFAULT_BAUD_DIVISOR,
VecDeque::new(),
)
};
Serial {
id,
interrupt_enable,
interrupt_identification,
line_control,
line_status,
modem_control,
modem_status,
scratch,
baud_divisor,
in_buffer,
interrupt_enable: 0,
interrupt_identification: DEFAULT_INTERRUPT_IDENTIFICATION,
interrupt,
line_control: DEFAULT_LINE_CONTROL,
line_status: DEFAULT_LINE_STATUS,
modem_control: DEFAULT_MODEM_CONTROL,
modem_status: DEFAULT_MODEM_STATUS,
scratch: 0,
baud_divisor: DEFAULT_BAUD_DIVISOR,
in_buffer: VecDeque::new(),
out,
}
}
@@ -152,18 +115,13 @@ impl Serial {
id: String,
interrupt: Arc<dyn InterruptSourceGroup>,
out: Box<dyn io::Write + Send>,
state: Option<SerialState>,
) -> Serial {
Self::new(id, interrupt, Some(out), state)
Self::new(id, interrupt, Some(out))
}
/// Constructs a Serial port with no connected output.
pub fn new_sink(
id: String,
interrupt: Arc<dyn InterruptSourceGroup>,
state: Option<SerialState>,
) -> Serial {
Self::new(id, interrupt, None, state)
pub fn new_sink(id: String, interrupt: Arc<dyn InterruptSourceGroup>) -> Serial {
Self::new(id, interrupt, None)
}
pub fn set_out(&mut self, out: Box<dyn io::Write + Send>) {
@@ -241,7 +199,7 @@ impl Serial {
}
fn handle_write(&mut self, offset: u8, v: u8) -> Result<()> {
match offset {
match offset as u8 {
DLAB_LOW if self.is_dlab_set() => {
self.baud_divisor = (self.baud_divisor & 0xff00) | u16::from(v)
}
@@ -284,6 +242,18 @@ impl Serial {
in_buffer: self.in_buffer.clone().into(),
}
}
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().into();
}
}
impl BusDevice for Serial {
@@ -334,7 +304,12 @@ impl Snapshottable for Serial {
}
fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
Snapshot::new_from_versioned_state(&self.state())
Snapshot::new_from_versioned_state(&self.id, &self.state())
}
fn restore(&mut self, snapshot: Snapshot) -> std::result::Result<(), MigratableError> {
self.set_state(&snapshot.to_versioned_state(&self.id)?);
Ok(())
}
}
@@ -365,13 +340,9 @@ mod tests {
_index: InterruptIndex,
_config: InterruptSourceConfig,
_masked: bool,
_set_gsi: bool,
) -> result::Result<(), std::io::Error> {
Ok(())
}
fn set_gsi(&self) -> result::Result<(), std::io::Error> {
Ok(())
}
fn notifier(&self, _index: InterruptIndex) -> Option<EventFd> {
Some(self.event_fd.try_clone().unwrap())
}
@@ -413,7 +384,6 @@ mod tests {
String::from(SERIAL_NAME),
Arc::new(TestInterrupt::new(intr_evt.try_clone().unwrap())),
Box::new(serial_out.clone()),
None,
);
serial.write(0, DATA as u64, &[b'x', b'y']);
@@ -434,7 +404,6 @@ mod tests {
String::from(SERIAL_NAME),
Arc::new(TestInterrupt::new(intr_evt.try_clone().unwrap())),
Box::new(serial_out),
None,
);
// write 1 to the interrupt event fd, so that read doesn't block in case the event fd
@@ -471,7 +440,6 @@ mod tests {
let mut serial = Serial::new_sink(
String::from(SERIAL_NAME),
Arc::new(TestInterrupt::new(intr_evt.try_clone().unwrap())),
None,
);
// write 1 to the interrupt event fd, so that read doesn't block in case the event fd
@@ -494,7 +462,6 @@ mod tests {
let mut serial = Serial::new_sink(
String::from(SERIAL_NAME),
Arc::new(TestInterrupt::new(intr_evt.try_clone().unwrap())),
None,
);
serial.write(0, LCR as u64, &[LCR_DLAB_BIT]);
@@ -516,7 +483,6 @@ mod tests {
let mut serial = Serial::new_sink(
String::from(SERIAL_NAME),
Arc::new(TestInterrupt::new(intr_evt.try_clone().unwrap())),
None,
);
serial.write(0, MCR as u64, &[MCR_LOOP_BIT]);
@@ -543,7 +509,6 @@ mod tests {
let mut serial = Serial::new_sink(
String::from(SERIAL_NAME),
Arc::new(TestInterrupt::new(intr_evt.try_clone().unwrap())),
None,
);
serial.write(0, SCR as u64, &[0x12]);

View File

@@ -10,7 +10,6 @@ use crate::{read_le_u32, write_le_u32};
use std::collections::VecDeque;
use std::fmt;
use std::sync::{Arc, Barrier};
use std::time::Instant;
use std::{io, result};
use versionize::{VersionMap, Versionize, VersionizeResult};
use versionize_derive::Versionize;
@@ -60,11 +59,11 @@ pub enum Error {
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Error::BadWriteOffset(offset) => write!(f, "pl011_write: Bad Write Offset: {offset}"),
Error::BadWriteOffset(offset) => write!(f, "pl011_write: Bad Write Offset: {}", offset),
Error::DmaNotImplemented => write!(f, "pl011: DMA not implemented."),
Error::InterruptFailure(e) => write!(f, "Failed to trigger interrupt: {e}"),
Error::WriteAllFailure(e) => write!(f, "Failed to write: {e}"),
Error::FlushFailure(e) => write!(f, "Failed to flush: {e}"),
Error::InterruptFailure(e) => write!(f, "Failed to trigger interrupt: {}", e),
Error::WriteAllFailure(e) => write!(f, "Failed to write: {}", e),
Error::FlushFailure(e) => write!(f, "Failed to flush: {}", e),
}
}
}
@@ -121,83 +120,27 @@ impl Pl011 {
id: String,
irq: Arc<dyn InterruptSourceGroup>,
out: Option<Box<dyn io::Write + Send>>,
timestamp: Instant,
state: Option<Pl011State>,
) -> Self {
let (
flags,
lcr,
rsr,
cr,
dmacr,
debug,
int_enabled,
int_level,
read_fifo,
ilpr,
ibrd,
fbrd,
ifl,
read_count,
read_trigger,
) = if let Some(state) = state {
(
state.flags,
state.lcr,
state.rsr,
state.cr,
state.dmacr,
state.debug,
state.int_enabled,
state.int_level,
state.read_fifo.into(),
state.ilpr,
state.ibrd,
state.fbrd,
state.ifl,
state.read_count,
state.read_trigger,
)
} else {
(
0x90,
0,
0,
0x300,
0,
0,
0,
0,
VecDeque::new(),
0,
0,
0,
0x12,
0,
1,
)
};
Self {
id,
flags,
lcr,
rsr,
cr,
dmacr,
debug,
int_enabled,
int_level,
read_fifo,
ilpr,
ibrd,
fbrd,
ifl,
read_count,
read_trigger,
flags: 0x90u32,
lcr: 0u32,
rsr: 0u32,
cr: 0x300u32,
dmacr: 0u32,
debug: 0u32,
int_enabled: 0u32,
int_level: 0u32,
read_fifo: VecDeque::new(),
ilpr: 0u32,
ibrd: 0u32,
fbrd: 0u32,
ifl: 0x12u32,
read_count: 0u32,
read_trigger: 1u32,
irq,
out,
timestamp,
timestamp: std::time::Instant::now(),
}
}
@@ -225,6 +168,24 @@ impl Pl011 {
}
}
fn set_state(&mut self, state: &Pl011State) {
self.flags = state.flags;
self.lcr = state.lcr;
self.rsr = state.rsr;
self.cr = state.cr;
self.dmacr = state.dmacr;
self.debug = state.debug;
self.int_enabled = state.int_enabled;
self.int_level = state.int_level;
self.read_fifo = state.read_fifo.clone().into();
self.ilpr = state.ilpr;
self.ibrd = state.ibrd;
self.fbrd = state.fbrd;
self.ifl = state.ifl;
self.read_count = state.read_count;
self.read_trigger = state.read_trigger;
}
/// Queues raw bytes for the guest to read and signals the interrupt
pub fn queue_input_bytes(&mut self, c: &[u8]) -> vmm_sys_util::errno::Result<()> {
self.read_fifo.extend(c);
@@ -343,25 +304,25 @@ impl Pl011 {
let elapsed = self.timestamp.elapsed();
match self.debug {
0x00..=0x1f => warn!(
0x00..=0x1f => info!(
"[Debug I/O port: Firmware code: 0x{:x}] {}.{:>06} seconds",
self.debug,
elapsed.as_secs(),
elapsed.as_micros()
),
0x20..=0x3f => warn!(
0x20..=0x3f => info!(
"[Debug I/O port: Bootloader code: 0x{:x}] {}.{:>06} seconds",
self.debug,
elapsed.as_secs(),
elapsed.as_micros()
),
0x40..=0x5f => warn!(
0x40..=0x5f => info!(
"[Debug I/O port: Kernel code: 0x{:x}] {}.{:>06} seconds",
self.debug,
elapsed.as_secs(),
elapsed.as_micros()
),
0x60..=0x7f => warn!(
0x60..=0x7f => info!(
"[Debug I/O port: Userspace code: 0x{:x}] {}.{:>06} seconds",
self.debug,
elapsed.as_secs(),
@@ -409,7 +370,7 @@ impl BusDevice for Pl011 {
UARTIFLS => self.ifl,
UARTIMSC => self.int_enabled,
UARTRIS => self.int_level,
UARTMIS => self.int_level & self.int_enabled,
UARTMIS => (self.int_level & self.int_enabled),
UARTDMACR => self.dmacr,
UARTDEBUG => self.debug,
_ => {
@@ -454,7 +415,12 @@ impl Snapshottable for Pl011 {
}
fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
Snapshot::new_from_versioned_state(&self.state())
Snapshot::new_from_versioned_state(&self.id, &self.state())
}
fn restore(&mut self, snapshot: Snapshot) -> std::result::Result<(), MigratableError> {
self.set_state(&snapshot.to_versioned_state(&self.id)?);
Ok(())
}
}
@@ -485,13 +451,9 @@ mod tests {
_index: InterruptIndex,
_config: InterruptSourceConfig,
_masked: bool,
_set_gsi: bool,
) -> result::Result<(), std::io::Error> {
Ok(())
}
fn set_gsi(&self) -> result::Result<(), std::io::Error> {
Ok(())
}
fn notifier(&self, _index: InterruptIndex) -> Option<EventFd> {
Some(self.event_fd.try_clone().unwrap())
}
@@ -533,14 +495,12 @@ mod tests {
String::from(SERIAL_NAME),
Arc::new(TestInterrupt::new(intr_evt.try_clone().unwrap())),
Some(Box::new(pl011_out.clone())),
Instant::now(),
None,
);
pl011.write(0, UARTDR, &[b'x', b'y']);
pl011.write(0, UARTDR, &[b'a']);
pl011.write(0, UARTDR, &[b'b']);
pl011.write(0, UARTDR, &[b'c']);
pl011.write(0, UARTDR as u64, &[b'x', b'y']);
pl011.write(0, UARTDR as u64, &[b'a']);
pl011.write(0, UARTDR as u64, &[b'b']);
pl011.write(0, UARTDR as u64, &[b'c']);
assert_eq!(
pl011_out.buf.lock().unwrap().as_slice(),
&[b'x', b'a', b'b', b'c']
@@ -555,8 +515,6 @@ mod tests {
String::from(SERIAL_NAME),
Arc::new(TestInterrupt::new(intr_evt.try_clone().unwrap())),
Some(Box::new(pl011_out)),
Instant::now(),
None,
);
// write 1 to the interrupt event fd, so that read doesn't block in case the event fd
@@ -567,11 +525,11 @@ mod tests {
assert_eq!(intr_evt.read().unwrap(), 2);
let mut data = [0u8];
pl011.read(0, UARTDR, &mut data);
pl011.read(0, UARTDR as u64, &mut data);
assert_eq!(data[0], b'a');
pl011.read(0, UARTDR, &mut data);
pl011.read(0, UARTDR as u64, &mut data);
assert_eq!(data[0], b'b');
pl011.read(0, UARTDR, &mut data);
pl011.read(0, UARTDR as u64, &mut data);
assert_eq!(data[0], b'c');
}
}

View File

@@ -10,8 +10,6 @@
#[macro_use]
extern crate bitflags;
#[macro_use]
extern crate event_monitor;
#[macro_use]
extern crate log;
pub mod acpi;
@@ -21,11 +19,8 @@ pub mod interrupt_controller;
#[cfg(target_arch = "x86_64")]
pub mod ioapic;
pub mod legacy;
pub mod pvpanic;
pub mod tpm;
pub use self::acpi::{AcpiGedDevice, AcpiPmTimerDevice, AcpiShutdownDevice};
pub use self::pvpanic::{PvPanicDevice, PVPANIC_DEVICE_MMIO_SIZE};
bitflags! {
pub struct AcpiNotificationFlags: u8 {
@@ -37,9 +32,11 @@ bitflags! {
}
}
#[allow(unused_macros)]
#[cfg(target_arch = "aarch64")]
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];
@@ -51,9 +48,11 @@ macro_rules! generate_read_fn {
};
}
#[allow(unused_macros)]
#[cfg(target_arch = "aarch64")]
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()

View File

@@ -1,272 +0,0 @@
// Copyright © 2023 Tencent Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
use anyhow::anyhow;
use pci::{
BarReprogrammingParams, PciBarConfiguration, PciBarPrefetchable, PciBarRegionType,
PciClassCode, PciConfiguration, PciDevice, PciDeviceError, PciHeaderType, PciSubclass,
PCI_CONFIGURATION_ID,
};
use std::any::Any;
use std::result;
use std::sync::{Arc, Barrier, Mutex};
use thiserror::Error;
use versionize::{VersionMap, Versionize, VersionizeResult};
use versionize_derive::Versionize;
use vm_allocator::{AddressAllocator, SystemAllocator};
use vm_device::{BusDevice, Resource};
use vm_memory::{Address, GuestAddress};
use vm_migration::{
Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable, VersionMapped,
};
const PVPANIC_VENDOR_ID: u16 = 0x1b36;
const PVPANIC_DEVICE_ID: u16 = 0x0011;
pub const PVPANIC_DEVICE_MMIO_SIZE: u64 = 0x2;
pub const PVPANIC_DEVICE_MMIO_ALIGNMENT: u64 = 0x10;
const PVPANIC_PANICKED: u8 = 1 << 0;
const PVPANIC_CRASH_LOADED: u8 = 1 << 1;
#[derive(Debug, Error)]
pub enum PvPanicError {
#[error("Failed creating PvPanicDevice: {0}")]
CreatePvPanicDevice(#[source] anyhow::Error),
#[error("Failed to retrieve PciConfigurationState: {0}")]
RetrievePciConfigurationState(#[source] anyhow::Error),
}
#[derive(Copy, Clone)]
enum PvPanicSubclass {
Other = 0x80,
}
impl PciSubclass for PvPanicSubclass {
fn get_register_value(&self) -> u8 {
*self as u8
}
}
/// A device for handling guest panic event
pub struct PvPanicDevice {
id: String,
events: u8,
// PCI configuration registers.
configuration: PciConfiguration,
bar_regions: Vec<PciBarConfiguration>,
}
#[derive(Versionize)]
pub struct PvPanicDeviceState {
events: u8,
}
impl VersionMapped for PvPanicDeviceState {}
impl PvPanicDevice {
pub fn new(id: String, snapshot: Option<Snapshot>) -> Result<Self, PvPanicError> {
let pci_configuration_state =
vm_migration::versioned_state_from_id(snapshot.as_ref(), PCI_CONFIGURATION_ID)
.map_err(|e| {
PvPanicError::RetrievePciConfigurationState(anyhow!(
"Failed to get PciConfigurationState from Snapshot: {}",
e
))
})?;
let mut configuration = PciConfiguration::new(
PVPANIC_VENDOR_ID,
PVPANIC_DEVICE_ID,
0x1, // modern pci devices
PciClassCode::BaseSystemPeripheral,
&PvPanicSubclass::Other,
None,
PciHeaderType::Device,
0,
0,
None,
pci_configuration_state,
);
let command: [u8; 2] = [0x03, 0x01];
configuration.write_config_register(1, 0, &command);
let state: Option<PvPanicDeviceState> = snapshot
.as_ref()
.map(|s| s.to_versioned_state())
.transpose()
.map_err(|e| {
PvPanicError::CreatePvPanicDevice(anyhow!(
"Failed to get PvPanicDeviceState from Snapshot: {}",
e
))
})?;
let events = if let Some(state) = state {
state.events
} else {
PVPANIC_PANICKED | PVPANIC_CRASH_LOADED
};
let pvpanic_device = PvPanicDevice {
id,
events,
configuration,
bar_regions: vec![],
};
Ok(pvpanic_device)
}
pub fn event_to_string(&self, event: u8) -> String {
if event == PVPANIC_PANICKED {
"panic".to_string()
} else if event == PVPANIC_CRASH_LOADED {
"crash_loaded".to_string()
} else {
"unknown_event".to_string()
}
}
fn state(&self) -> PvPanicDeviceState {
PvPanicDeviceState {
events: self.events,
}
}
pub fn config_bar_addr(&self) -> u64 {
self.configuration.get_bar_addr(0)
}
}
impl BusDevice for PvPanicDevice {
fn read(&mut self, base: u64, offset: u64, data: &mut [u8]) {
self.read_bar(base, offset, data)
}
fn write(&mut self, _base: u64, _offset: u64, data: &[u8]) -> Option<Arc<Barrier>> {
let event = self.event_to_string(data[0]);
info!("pvpanic got guest event {}", event);
event!("guest", "panic", "event", &event);
None
}
}
impl PciDevice for PvPanicDevice {
fn write_config_register(
&mut self,
reg_idx: usize,
offset: u64,
data: &[u8],
) -> Option<Arc<Barrier>> {
self.configuration
.write_config_register(reg_idx, offset, data);
None
}
fn read_config_register(&mut self, reg_idx: usize) -> u32 {
self.configuration.read_reg(reg_idx)
}
fn detect_bar_reprogramming(
&mut self,
reg_idx: usize,
data: &[u8],
) -> Option<BarReprogrammingParams> {
self.configuration.detect_bar_reprogramming(reg_idx, data)
}
fn allocate_bars(
&mut self,
allocator: &Arc<Mutex<SystemAllocator>>,
_mmio_allocator: &mut AddressAllocator,
resources: Option<Vec<Resource>>,
) -> std::result::Result<Vec<PciBarConfiguration>, PciDeviceError> {
let mut bars = Vec::new();
let region_type = PciBarRegionType::Memory32BitRegion;
let bar_id = 0;
let region_size = PVPANIC_DEVICE_MMIO_SIZE;
let restoring = resources.is_some();
let bar_addr = allocator
.lock()
.unwrap()
.allocate_mmio_hole_addresses(None, region_size, Some(PVPANIC_DEVICE_MMIO_ALIGNMENT))
.ok_or(PciDeviceError::IoAllocationFailed(region_size))?;
let bar = PciBarConfiguration::default()
.set_index(bar_id as usize)
.set_address(bar_addr.raw_value())
.set_size(region_size)
.set_region_type(region_type)
.set_prefetchable(PciBarPrefetchable::NotPrefetchable);
debug!("pvpanic bar address 0x{:x}", bar_addr.0);
if !restoring {
self.configuration
.add_pci_bar(&bar)
.map_err(|e| PciDeviceError::IoRegistrationFailed(bar_addr.raw_value(), e))?;
}
bars.push(bar);
self.bar_regions = bars.clone();
Ok(bars)
}
fn free_bars(
&mut self,
allocator: &mut SystemAllocator,
_mmio_allocator: &mut AddressAllocator,
) -> std::result::Result<(), PciDeviceError> {
for bar in self.bar_regions.drain(..) {
allocator.free_mmio_hole_addresses(GuestAddress(bar.addr()), bar.size());
}
Ok(())
}
fn move_bar(&mut self, old_base: u64, new_base: u64) -> result::Result<(), std::io::Error> {
for bar in self.bar_regions.iter_mut() {
if bar.addr() == old_base {
*bar = bar.set_address(new_base);
}
}
Ok(())
}
fn read_bar(&mut self, _base: u64, _offset: u64, data: &mut [u8]) {
data[0] = self.events;
}
fn as_any(&mut self) -> &mut dyn Any {
self
}
fn id(&self) -> Option<String> {
Some(self.id.clone())
}
}
impl Pausable for PvPanicDevice {}
impl Snapshottable for PvPanicDevice {
fn id(&self) -> String {
self.id.clone()
}
fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
let mut snapshot = Snapshot::new_from_versioned_state(&self.state())?;
// Snapshot PciConfiguration
snapshot.add_snapshot(self.configuration.id(), self.configuration.snapshot()?);
Ok(snapshot)
}
}
impl Transportable for PvPanicDevice {}
impl Migratable for PvPanicDevice {}

View File

@@ -1,551 +0,0 @@
// Copyright © 2022, Microsoft Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
use anyhow::anyhow;
#[cfg(target_arch = "aarch64")]
use arch::aarch64::layout::{TPM_SIZE, TPM_START};
#[cfg(target_arch = "x86_64")]
use arch::x86_64::layout::{TPM_SIZE, TPM_START};
use std::cmp;
use std::sync::{Arc, Barrier};
use thiserror::Error;
use tpm::emulator::{BackendCmd, Emulator};
use tpm::TPM_CRB_BUFFER_MAX;
use vm_device::BusDevice;
#[derive(Error, Debug)]
pub enum Error {
#[error("Emulator doesn't implement min required capabilities: {0}")]
CheckCaps(#[source] anyhow::Error),
#[error("Failed to initialize tpm: {0}")]
Init(#[source] anyhow::Error),
}
type Result<T> = anyhow::Result<T, Error>;
#[allow(dead_code)]
enum LocStateFields {
TpmEstablished,
LocAssigned,
ActiveLocality,
Reserved,
TpmRegValidSts,
}
enum LocStsFields {
Granted,
BeenSeized,
}
#[allow(dead_code)]
enum IntfIdFields {
InterfaceType,
InterfaceVersion,
CapLocality,
CapCRBIdleBypass,
Reserved1,
CapDataXferSizeSupport,
CapFIFO,
CapCRB,
CapIFRes,
InterfaceSelector,
IntfSelLock,
Reserved2,
Rid,
}
#[allow(dead_code)]
enum IntfId2Fields {
Vid,
Did,
}
enum CtrlStsFields {
TpmSts,
TpmIdle,
}
enum CrbRegister {
LocState(LocStateFields),
LocSts(LocStsFields),
IntfId(IntfIdFields),
IntfId2(IntfId2Fields),
CtrlSts(CtrlStsFields),
}
/* crb 32-bit registers */
const CRB_LOC_STATE: u32 = 0x0;
//Register Fields
// Field => (base, offset, length)
// base: starting position of the register
// offset: lowest bit in the bit field numbered from 0
// length: length of the bit field
const fn get_crb_loc_state_field(f: LocStateFields) -> (u32, u32, u32) {
let (offset, len) = match f {
LocStateFields::TpmEstablished => (0, 1),
LocStateFields::LocAssigned => (1, 1),
LocStateFields::ActiveLocality => (2, 3),
LocStateFields::Reserved => (5, 2),
LocStateFields::TpmRegValidSts => (7, 1),
};
(CRB_LOC_STATE, offset, len)
}
const CRB_LOC_CTRL: u32 = 0x08;
const CRB_LOC_CTRL_REQUEST_ACCESS: u32 = 1 << 0;
const CRB_LOC_CTRL_RELINQUISH: u32 = 1 << 1;
const CRB_LOC_CTRL_RESET_ESTABLISHMENT_BIT: u32 = 1 << 3;
const CRB_LOC_STS: u32 = 0x0C;
const fn get_crb_loc_sts_field(f: LocStsFields) -> (u32, u32, u32) {
let (offset, len) = match f {
LocStsFields::Granted => (0, 1),
LocStsFields::BeenSeized => (1, 1),
};
(CRB_LOC_STS, offset, len)
}
const CRB_INTF_ID: u32 = 0x30;
const fn get_crb_intf_id_field(f: IntfIdFields) -> (u32, u32, u32) {
let (offset, len) = match f {
IntfIdFields::InterfaceType => (0, 4),
IntfIdFields::InterfaceVersion => (4, 4),
IntfIdFields::CapLocality => (8, 1),
IntfIdFields::CapCRBIdleBypass => (9, 1),
IntfIdFields::Reserved1 => (10, 1),
IntfIdFields::CapDataXferSizeSupport => (11, 2),
IntfIdFields::CapFIFO => (13, 1),
IntfIdFields::CapCRB => (14, 1),
IntfIdFields::CapIFRes => (15, 2),
IntfIdFields::InterfaceSelector => (17, 2),
IntfIdFields::IntfSelLock => (19, 1),
IntfIdFields::Reserved2 => (20, 4),
IntfIdFields::Rid => (24, 8),
};
(CRB_INTF_ID, offset, len)
}
const CRB_INTF_ID2: u32 = 0x34;
const fn get_crb_intf_id2_field(f: IntfId2Fields) -> (u32, u32, u32) {
let (offset, len) = match f {
IntfId2Fields::Vid => (0, 16),
IntfId2Fields::Did => (16, 16),
};
(CRB_INTF_ID2, offset, len)
}
const CRB_CTRL_REQ: u32 = 0x40;
const CRB_CTRL_REQ_CMD_READY: u32 = 1 << 0;
const CRB_CTRL_REQ_GO_IDLE: u32 = 1 << 1;
const CRB_CTRL_STS: u32 = 0x44;
const fn get_crb_ctrl_sts_field(f: CtrlStsFields) -> (u32, u32, u32) {
let (offset, len) = match f {
CtrlStsFields::TpmSts => (0, 1),
CtrlStsFields::TpmIdle => (1, 1),
};
(CRB_CTRL_STS, offset, len)
}
const CRB_CTRL_CANCEL: u32 = 0x48;
const CRB_CANCEL_INVOKE: u32 = 1 << 0;
const CRB_CTRL_START: u32 = 0x4C;
const CRB_START_INVOKE: u32 = 1 << 0;
const CRB_CTRL_CMD_LADDR: u32 = 0x5C;
const CRB_CTRL_CMD_HADDR: u32 = 0x60;
const CRB_CTRL_RSP_SIZE: u32 = 0x64;
const CRB_CTRL_RSP_ADDR: u32 = 0x68;
const CRB_DATA_BUFFER: u32 = 0x80;
const TPM_CRB_NO_LOCALITY: u32 = 0xff;
const TPM_CRB_ADDR_BASE: u32 = TPM_START.0 as u32;
const TPM_CRB_ADDR_SIZE: usize = TPM_SIZE as usize;
const TPM_CRB_R_MAX: usize = CRB_DATA_BUFFER as usize;
// CRB Protocol details
const CRB_INTF_TYPE_CRB_ACTIVE: u32 = 0b1;
const CRB_INTF_VERSION_CRB: u32 = 0b1;
const CRB_INTF_CAP_LOCALITY_0_ONLY: u32 = 0b0;
const CRB_INTF_CAP_IDLE_FAST: u32 = 0b0;
const CRB_INTF_CAP_XFER_SIZE_64: u32 = 0b11;
const CRB_INTF_CAP_FIFO_NOT_SUPPORTED: u32 = 0b0;
const CRB_INTF_CAP_CRB_SUPPORTED: u32 = 0b1;
const CRB_INTF_IF_SELECTOR_CRB: u32 = 0b1;
const PCI_VENDOR_ID_IBM: u32 = 0x1014;
const CRB_CTRL_CMD_SIZE_REG: u32 = 0x58;
const CRB_CTRL_CMD_SIZE: usize = TPM_CRB_ADDR_SIZE - CRB_DATA_BUFFER as usize;
// Returns (register base, offset, len)
const fn get_field(reg: CrbRegister) -> (u32, u32, u32) {
match reg {
CrbRegister::LocState(f) => get_crb_loc_state_field(f),
CrbRegister::LocSts(f) => get_crb_loc_sts_field(f),
CrbRegister::IntfId(f) => get_crb_intf_id_field(f),
CrbRegister::IntfId2(f) => get_crb_intf_id2_field(f),
CrbRegister::CtrlSts(f) => get_crb_ctrl_sts_field(f),
}
}
// Set a particular field in a Register
fn set_reg_field(regs: &mut [u32; TPM_CRB_R_MAX], reg: CrbRegister, value: u32) {
let (base, offset, len) = get_field(reg);
let mask = (!(0_u32) >> (32 - len)) << offset;
regs[base as usize] = (regs[base as usize] & !mask) | ((value << offset) & mask);
}
// Get the value of a particular field in a Register
const fn get_reg_field(regs: &[u32; TPM_CRB_R_MAX], reg: CrbRegister) -> u32 {
let (base, offset, len) = get_field(reg);
let mask = (!(0_u32) >> (32 - len)) << offset;
(regs[base as usize] & mask) >> offset
}
fn locality_from_addr(addr: u32) -> u8 {
(addr >> 12) as u8
}
pub struct Tpm {
emulator: Emulator,
regs: [u32; TPM_CRB_R_MAX],
backend_buff_size: usize,
data_buff: [u8; TPM_CRB_BUFFER_MAX],
data_buff_len: usize,
}
impl Tpm {
pub fn new(path: String) -> Result<Self> {
let emulator = Emulator::new(path)
.map_err(|e| Error::Init(anyhow!("Failed while initializing tpm Emulator: {:?}", e)))?;
let mut tpm = Tpm {
emulator,
regs: [0; TPM_CRB_R_MAX],
backend_buff_size: TPM_CRB_BUFFER_MAX,
data_buff: [0; TPM_CRB_BUFFER_MAX],
data_buff_len: 0,
};
tpm.reset()?;
Ok(tpm)
}
fn get_active_locality(&mut self) -> u32 {
if get_reg_field(
&self.regs,
CrbRegister::LocState(LocStateFields::LocAssigned),
) == 0
{
return TPM_CRB_NO_LOCALITY;
}
get_reg_field(
&self.regs,
CrbRegister::LocState(LocStateFields::ActiveLocality),
)
}
fn request_completed(&mut self, success: bool) {
self.regs[CRB_CTRL_START as usize] = !CRB_START_INVOKE;
if !success {
set_reg_field(
&mut self.regs,
CrbRegister::CtrlSts(CtrlStsFields::TpmSts),
1,
);
}
}
fn reset(&mut self) -> Result<()> {
let cur_buff_size = self.emulator.get_buffer_size();
self.regs = [0; TPM_CRB_R_MAX];
set_reg_field(
&mut self.regs,
CrbRegister::LocState(LocStateFields::TpmRegValidSts),
1,
);
set_reg_field(
&mut self.regs,
CrbRegister::CtrlSts(CtrlStsFields::TpmIdle),
1,
);
set_reg_field(
&mut self.regs,
CrbRegister::IntfId(IntfIdFields::InterfaceType),
CRB_INTF_TYPE_CRB_ACTIVE,
);
set_reg_field(
&mut self.regs,
CrbRegister::IntfId(IntfIdFields::InterfaceVersion),
CRB_INTF_VERSION_CRB,
);
set_reg_field(
&mut self.regs,
CrbRegister::IntfId(IntfIdFields::CapLocality),
CRB_INTF_CAP_LOCALITY_0_ONLY,
);
set_reg_field(
&mut self.regs,
CrbRegister::IntfId(IntfIdFields::CapCRBIdleBypass),
CRB_INTF_CAP_IDLE_FAST,
);
set_reg_field(
&mut self.regs,
CrbRegister::IntfId(IntfIdFields::CapDataXferSizeSupport),
CRB_INTF_CAP_XFER_SIZE_64,
);
set_reg_field(
&mut self.regs,
CrbRegister::IntfId(IntfIdFields::CapFIFO),
CRB_INTF_CAP_FIFO_NOT_SUPPORTED,
);
set_reg_field(
&mut self.regs,
CrbRegister::IntfId(IntfIdFields::CapCRB),
CRB_INTF_CAP_CRB_SUPPORTED,
);
set_reg_field(
&mut self.regs,
CrbRegister::IntfId(IntfIdFields::InterfaceSelector),
CRB_INTF_IF_SELECTOR_CRB,
);
set_reg_field(
&mut self.regs,
CrbRegister::IntfId(IntfIdFields::Rid),
0b0000,
);
set_reg_field(
&mut self.regs,
CrbRegister::IntfId2(IntfId2Fields::Vid),
PCI_VENDOR_ID_IBM,
);
self.regs[CRB_CTRL_CMD_SIZE_REG as usize] = CRB_CTRL_CMD_SIZE as u32;
self.regs[CRB_CTRL_CMD_LADDR as usize] = TPM_CRB_ADDR_BASE + CRB_DATA_BUFFER;
self.regs[CRB_CTRL_RSP_SIZE as usize] = CRB_CTRL_CMD_SIZE as u32;
self.regs[CRB_CTRL_RSP_ADDR as usize] = TPM_CRB_ADDR_BASE + CRB_DATA_BUFFER;
self.backend_buff_size = cmp::min(cur_buff_size, TPM_CRB_BUFFER_MAX);
if let Err(e) = self.emulator.startup_tpm(self.backend_buff_size) {
return Err(Error::Init(anyhow!(
"Failed while running Startup TPM. Error: {:?}",
e
)));
}
Ok(())
}
}
impl BusDevice for Tpm {
fn read(&mut self, _base: u64, offset: u64, data: &mut [u8]) {
let mut offset: u32 = offset as u32;
let read_len: usize = data.len();
if offset >= CRB_DATA_BUFFER
&& (offset + read_len as u32) < (CRB_DATA_BUFFER + self.data_buff.len() as u32)
{
// Read from Data Buffer
let start: usize = (offset as usize) - (CRB_DATA_BUFFER as usize);
let end: usize = start + read_len;
data[..].clone_from_slice(&self.data_buff[start..end]);
} else {
offset &= 0xff;
let mut val = self.regs[offset as usize];
if offset == CRB_LOC_STATE && !self.emulator.get_established_flag() {
val |= 0x1;
}
if data.len() <= 4 {
data.clone_from_slice(val.to_ne_bytes()[0..read_len].as_ref());
} else {
error!(
"Invalid tpm read: offset {:#X}, data length {:?}",
offset,
data.len()
);
}
}
debug!(
"MMIO Read: offset {:#X} len {:?} val = {:02X?} ",
offset,
data.len(),
data
);
}
fn write(&mut self, _base: u64, offset: u64, data: &[u8]) -> Option<Arc<Barrier>> {
debug!(
"MMIO Write: offset {:#X} len {:?} input data {:02X?}",
offset,
data.len(),
data
);
let mut offset: u32 = offset as u32;
if offset < CRB_DATA_BUFFER {
offset &= 0xff;
}
let locality = locality_from_addr(offset) as u32;
let write_len = data.len();
if offset >= CRB_DATA_BUFFER
&& (offset + write_len as u32) < (CRB_DATA_BUFFER + self.data_buff.len() as u32)
{
let start: usize = (offset as usize) - (CRB_DATA_BUFFER as usize);
if start == 0 {
// If filling data_buff at index 0, reset length to 0
self.data_buff_len = 0;
self.data_buff.fill(0);
}
let end: usize = start + data.len();
self.data_buff[start..end].clone_from_slice(data);
self.data_buff_len += data.len();
} else {
// Ctrl Commands that take more than 4 bytes as input are not yet supported
// CTRL_RSP_ADDR usually gets 8 byte write request. Last 4 bytes are zeros.
if write_len > 4 && offset != CRB_CTRL_RSP_ADDR {
error!(
"Invalid tpm write: offset {:#X}, data length {}",
offset,
data.len()
);
return None;
}
let mut input: [u8; 4] = [0; 4];
input.copy_from_slice(&data[0..4]);
let v = u32::from_le_bytes(input);
match offset {
CRB_CTRL_CMD_SIZE_REG => {
self.regs[CRB_CTRL_CMD_SIZE_REG as usize] = v;
}
CRB_CTRL_CMD_LADDR => {
self.regs[CRB_CTRL_CMD_LADDR as usize] = v;
}
CRB_CTRL_CMD_HADDR => {
self.regs[CRB_CTRL_CMD_HADDR as usize] = v;
}
CRB_CTRL_RSP_SIZE => {
self.regs[CRB_CTRL_RSP_SIZE as usize] = v;
}
CRB_CTRL_RSP_ADDR => {
self.regs[CRB_CTRL_RSP_ADDR as usize] = v;
}
CRB_CTRL_REQ => match v {
CRB_CTRL_REQ_CMD_READY => {
set_reg_field(
&mut self.regs,
CrbRegister::CtrlSts(CtrlStsFields::TpmIdle),
0,
);
}
CRB_CTRL_REQ_GO_IDLE => {
set_reg_field(
&mut self.regs,
CrbRegister::CtrlSts(CtrlStsFields::TpmIdle),
1,
);
}
_ => {
error!("Invalid value passed to CRTL_REQ register");
return None;
}
},
CRB_CTRL_CANCEL => {
if v == CRB_CANCEL_INVOKE
&& (self.regs[CRB_CTRL_START as usize] & CRB_START_INVOKE != 0)
{
if let Err(e) = self.emulator.cancel_cmd() {
error!("Failed to run cancel command. Error: {:?}", e);
}
}
}
CRB_CTRL_START => {
if v == CRB_START_INVOKE
&& ((self.regs[CRB_CTRL_START as usize] & CRB_START_INVOKE) == 0)
&& self.get_active_locality() == locality
{
self.regs[CRB_CTRL_START as usize] |= CRB_START_INVOKE;
let mut cmd = BackendCmd {
buffer: &mut self.data_buff,
input_len: cmp::min(self.data_buff_len, TPM_CRB_BUFFER_MAX),
};
let status = self.emulator.deliver_request(&mut cmd).is_ok();
self.request_completed(status);
}
}
CRB_LOC_CTRL => {
warn!(
"CRB_LOC_CTRL locality to write = {:?} val = {:?}",
locality, v
);
match v {
CRB_LOC_CTRL_RESET_ESTABLISHMENT_BIT => {}
CRB_LOC_CTRL_RELINQUISH => {
set_reg_field(
&mut self.regs,
CrbRegister::LocState(LocStateFields::LocAssigned),
0,
);
set_reg_field(
&mut self.regs,
CrbRegister::LocSts(LocStsFields::Granted),
0,
);
}
CRB_LOC_CTRL_REQUEST_ACCESS => {
set_reg_field(
&mut self.regs,
CrbRegister::LocSts(LocStsFields::Granted),
1,
);
set_reg_field(
&mut self.regs,
CrbRegister::LocSts(LocStsFields::BeenSeized),
0,
);
set_reg_field(
&mut self.regs,
CrbRegister::LocState(LocStateFields::LocAssigned),
1,
);
}
_ => {
error!("Invalid value to write in CRB_LOC_CTRL {:#X} ", v);
}
}
}
_ => {
error!(
"Invalid tpm write: offset {:#X}, data length {:?}",
offset,
data.len()
);
}
}
}
None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_set_get_reg_field() {
let mut regs: [u32; TPM_CRB_R_MAX] = [0; TPM_CRB_R_MAX];
set_reg_field(&mut regs, CrbRegister::IntfId(IntfIdFields::Rid), 0xAC);
assert_eq!(
get_reg_field(&regs, CrbRegister::IntfId(IntfIdFields::Rid)),
0xAC,
concat!("Test: ", stringify!(set_get_reg_field))
);
}
}

View File

@@ -1,38 +0,0 @@
# AMD SEV-SNP
### WARNING
This feature is only currently supported on MSHV.
AMD Secure Encrypted Virtualization & Secure Nested Paging (SEV-SNP) is an AMD
technology designed to add strong memory integrity protection to help prevent
malicious hypervisor-based attacks like data replay, memory-remapping and more
in order to create an isolated execution environment. Here are some useful
links:
* [SNP Homepage] (https://www.amd.com/en/processors/amd-secure-encrypted-virtualization)
more information about SEV-SNP technical aspects, design and specification.
## Cloud Hypervisor support
It is required to use a machine which has enabled support for AMD SEV-SNP in
the BIOS.
On the Cloud Hypervisor side, all you need is to build the project with the
`sev_snp` feature enabled:
```bash
cargo build --no-default-features --features "sev_snp"
```
**Note**
Please note that `sev_snp` cannot be enabled in conjunction with `tdx` feature flag.
You can run a SEV-SNP VM using the following command:
```bash
./cloud-hypervisor \
--platform sev_snp=on \
--cpus boot=1 \
--memory size=1G \
--disk path=ubuntu.img
```

View File

@@ -1,34 +1,29 @@
- [Cloud Hypervisor API](#cloud-hypervisor-api)
- [External API](#external-api)
- [REST API](#rest-api)
- [REST API Location and availability](#rest-api-location-and-availability)
- [REST API Endpoints](#rest-api-endpoints)
- [Virtual Machine Manager (VMM) Actions](#virtual-machine-manager-vmm-actions)
- [Virtual Machine (VM) Actions](#virtual-machine-vm-actions)
* [External API](#external-api)
+ [REST API](#rest-api)
- [Location and availability](#location-and-availability)
- [Endpoints](#endpoints)
* [Virtual Machine Manager (VMM) Actions](#virtual-machine-manager-vmm-actions)
* [Virtual Machine (VM) Actions](#virtual-machine-vm-actions)
- [REST API Examples](#rest-api-examples)
- [Create a Virtual Machine](#create-a-virtual-machine)
- [Boot a Virtual Machine](#boot-a-virtual-machine)
- [Dump a Virtual Machine Information](#dump-a-virtual-machine-information)
- [Reboot a Virtual Machine](#reboot-a-virtual-machine)
- [Shut a Virtual Machine Down](#shut-a-virtual-machine-down)
- [D-Bus API](#d-bus-api)
- [D-Bus API Location and availability](#d-bus-api-location-and-availability)
- [D-Bus API Interface](#d-bus-api-interface)
- [Command Line Interface](#command-line-interface)
- [REST API, D-Bus API and CLI Architectural Relationship](#rest-api-and-cli-architectural-relationship)
- [Internal API](#internal-api)
- [Goals and Design](#goals-and-design)
- [End to End Example](#end-to-end-example)
* [Create a Virtual Machine](#create-a-virtual-machine)
* [Boot a Virtual Machine](#boot-a-virtual-machine)
* [Dump a Virtual Machine Information](#dump-a-virtual-machine-information)
* [Reboot a Virtual Machine](#reboot-a-virtual-machine)
* [Shut a Virtual Machine Down](#shut-a-virtual-machine-down)
+ [Command Line Interface](#command-line-interface)
+ [REST API and CLI Architectural Relationship](#rest-api-and-cli-architectural-relationship)
* [Internal API](#internal-api)
+ [Goals and Design](#goals-and-design)
* [End to End Example](#end-to-end-example)
# Cloud Hypervisor API
The Cloud Hypervisor API is made of 2 distinct interfaces:
1. **The External API** This is the user facing API. Users and operators
can control and manage the Cloud Hypervisor through various options
including a REST API, a Command Line Interface (CLI) or a D-Bus based API,
which is not compiled into Cloud Hypervisor by default.
1. **The external API**. This is the user facing API. Users and operators can
control and manage Cloud Hypervisor through either a REST API or a Command
Line Interface (CLI).
1. **The internal API**, based on [rust's Multi-Producer, Single-Consumer (MPSC)](https://doc.rust-lang.org/std/sync/mpsc/)
module. This API is used internally by the Cloud Hypervisor threads to
communicate between each others.
@@ -45,10 +40,10 @@ API triggers VM and VMM specific actions, and as such it is designed as a
collection of RPC-style, static methods.
The API is [OpenAPI 3.0](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md)
compliant. Please consult the [Cloud Hypervisor OpenAPI Document](https://raw.githubusercontent.com/cloud-hypervisor/cloud-hypervisor/master/vmm/src/api/openapi/cloud-hypervisor.yaml)
for more details about the API payloads and responses.
compliant. Please consult the [Cloud Hypervisor API](https://raw.githubusercontent.com/cloud-hypervisor/cloud-hypervisor/master/vmm/src/api/openapi/cloud-hypervisor.yaml)
document for more details about the API payloads and responses.
#### REST API Location and availability
### Location and availability
The REST API is available as soon as the Cloud Hypervisor binary is started,
through a local UNIX socket.
@@ -70,55 +65,47 @@ Cloud Hypervisor Guest
Disk(s): None
```
#### REST API Endpoints
### Endpoints
The Cloud Hypervisor API exposes the following actions through its endpoints:
##### Virtual Machine Manager (VMM) Actions
#### Virtual Machine Manager (VMM) Actions
| Action | Endpoint | Request Body | Response Body | Prerequisites |
| ----------------------------------- | --------------- | ------------ | -------------------------- | ------------------ |
| Check for the REST API availability | `/vmm.ping` | N/A | `/schemas/VmmPingResponse` | N/A |
| Shut the VMM down | `/vmm.shutdown` | N/A | N/A | The VMM is running |
Action | Endpoint | Request Body | Response Body | Prerequisites
------------------------------------|-----------------|--------------|----------------------------|---------------------------
Check for the REST API availability | `/vmm.ping` | N/A | `/schemas/VmmPingResponse` | N/A
Shut the VMM down | `/vmm.shutdown` | N/A | N/A | The VMM is running
##### Virtual Machine (VM) Actions
#### 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 |
| Trigger power button of the VM | `/vm.power-button` | 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 |
| Task a snapshot of the VM | `/vm.snapshot` | `/schemas/VmSnapshotConfig` | N/A | The VM is paused |
| Perform a coredump of the VM* | `/vm.coredump` | `/schemas/VmCoredumpData` | N/A | The VM is paused |
| Restore the VM from a snapshot | `/vm.restore` | `/schemas/RestoreConfig` | N/A | The VM is created but not booted |
| 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 userspace PCI device to the VM | `/vm.add-user-device` | `/schemas/VmAddUserDevice` | `/schemas/PciDeviceInfo` | The VM is booted |
| Add vdpa device to the VM | `/vm.add-vdpa` | `/schemas/VdpaConfig` | `/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 |
| Prepare to receive a migration | `/vm.receive-migration` | `/schemas/ReceiveMigrationData` | N/A | N/A |
| Start to send migration to target | `/vm.send-migration` | `/schemas/SendMigrationData` | N/A | The VM is booted and (shared mem or hugepages enabled) |
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
Trigger power button of the VM | `/vm.power-button` | 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
Task a snapshot of the VM | `/vm.snapshot` | `/schemas/VmSnapshotConfig`| N/A | The VM is paused
Restore the VM from a snapshot | `/vm.restore` | `/schemas/RestoreConfig` | N/A | The VM is created but not booted
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 userspace PCI device to the VM | `/vm.add-user-device`| `/schemas/VmAddUserDevice`| `/schemas/PciDeviceInfo` | The VM is booted
Add vdpa device to the VM | `/vm.add-vdpa` | `/schemas/VdpaConfig` | `/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
* The `vmcoredump` action is available exclusively for the `x86_64`
architecture and can be executed only when the `guest_debug` feature is
enabled. Without this feature, the corresponding [REST API](#rest-api) or
[D-Bus API](#d-bus-api) endpoints are not available.
#### REST API Examples
### REST API Examples
For the following set of examples, we assume Cloud Hypervisor is started with
the REST API available at `/tmp/cloud-hypervisor.sock`:
@@ -134,7 +121,7 @@ Cloud Hypervisor Guest
Disk(s): None
```
##### Create a Virtual Machine
#### Create a Virtual Machine
We want to create a virtual machine with the following characteristics:
@@ -155,14 +142,15 @@ curl --unix-socket /tmp/cloud-hypervisor.sock -i \
-H 'Content-Type: application/json' \
-d '{
"cpus":{"boot_vcpus": 4, "max_vcpus": 4},
"payload":{"kernel":"/opt/clh/kernel/vmlinux-virtio-fs-virtio-iommu", "cmdline":"console=ttyS0 console=hvc0 root=/dev/vda1 rw"},
"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"}],
"rng":{"src":"/dev/urandom"},
"net":[{"ip":"192.168.10.10", "mask":"255.255.255.0", "mac":"12:34:56:78:90:01"}]
}'
```
##### Boot a Virtual Machine
#### Boot a Virtual Machine
Once the VM is created, we can boot it:
@@ -172,7 +160,7 @@ Once the VM is created, we can boot it:
curl --unix-socket /tmp/cloud-hypervisor.sock -i -X PUT 'http://localhost/api/v1/vm.boot'
```
##### Dump a Virtual Machine Information
#### Dump a Virtual Machine Information
We can fetch information about any VM, as soon as it's created:
@@ -184,7 +172,7 @@ curl --unix-socket /tmp/cloud-hypervisor.sock -i \
-H 'Accept: application/json'
```
##### Reboot a Virtual Machine
#### Reboot a Virtual Machine
We can reboot a VM that's already booted:
@@ -194,7 +182,7 @@ We can reboot a VM that's already booted:
curl --unix-socket /tmp/cloud-hypervisor.sock -i -X PUT 'http://localhost/api/v1/vm.reboot'
```
##### Shut a Virtual Machine Down
#### Shut a Virtual Machine Down
Once booted, we can shut a VM down from the REST API:
@@ -204,68 +192,6 @@ Once booted, we can shut a VM down from the REST API:
curl --unix-socket /tmp/cloud-hypervisor.sock -i -X PUT 'http://localhost/api/v1/vm.shutdown'
```
### D-Bus API
Cloud Hypervisor offers a D-Bus API as an alternative to its REST API. This
D-Bus API fully reflects the functionality of the REST API, exposing the
same group of endpoints. It can be a drop-in replacement since it also
consumes/produces JSON.
In addition, the D-Bus API also exposes events from `event-monitor` in the
form of a D-Bus signal to which users can subscribe. For more information,
see [D-Bus API Interface](#d-bus-api-interface).
#### D-Bus API Location and availability
This feature is not compiled into Cloud Hypervisor by default. Users who
wish to use the D-Bus API, must explicitly enable it with the `dbus_api`
feature flag when compiling Cloud Hypervisor.
```sh
$ ./scripts/dev_cli.sh build --release --libc musl -- --features dbus_api
```
Once this feature is enabled, it can be configured with the following
CLI options:
```
--dbus-service-name
well known name of the service
--dbus-object-path
object path to serve the dbus interface
--dbus-system-bus use the system bus instead of a session bus
```
Example invocation:
```sh
$ ./cloud-hypervisor --dbus-service-name "org.cloudhypervisor.DBusApi" \
--dbus-object-path "/org/cloudhypervisor/DBusApi"
```
This will start serving a service with the name `org.cloudhypervisor.DBusApi1`
which in turn can be used to control and manage Cloud Hypervisor.
#### D-Bus API Interface
Please refer to the [REST API](#rest-api) documentation for everything that
is in common with the REST API. As previously mentioned, the D-Bus API can
be used as a drop-in replacement for the [REST API](#rest-api).
The D-Bus interface also exposes a signal, named `Event`, which is emitted
whenever a new event is published from the `event-monitor` crate. Here is its
definition in XML format:
```xml
<node>
<interface name="org.cloudhypervisor.DBusApi1">
<signal name="Event">
<arg name="event" type="s"/>
</signal>
</interface>
</node>
```
### Command Line Interface
The Cloud Hypervisor Command Line Interface (CLI) can only be used for launching
@@ -273,41 +199,33 @@ the Cloud Hypervisor binary, i.e. it can not be used for controlling the VMM or
the launched VM once they're up and running.
If you want to inspect the VMM, or control the VM after launching Cloud
Hypervisor from the CLI, you must use either the [REST API](#rest-api)
or the [D-Bus API](#d-bus-api).
Hypervisor from the CLI, you must use the [REST API](#rest-api).
From the CLI, one can:
From the CLI, one can either:
1. Create and boot a complete virtual machine by using the CLI options to build
the VM config. Run `cloud-hypervisor --help` for a complete list of CLI
options. As soon as the `cloud-hypervisor` binary is launched, contrary
to the [D-Bus API](#d-bus-api), the [REST API](#rest-api) is available
for controlling and managing the VM. The [D-Bus API](#d-bus-api) doesn't start
automatically and needs to be explicitly configured in order to be run.
1. Start either the REST API, D-Bus API or both simultaneously without passing
any VM configuration options. The VM can then be asynchronously created and
booted by calling API methods of choice. It should be noted that one external
API does not exclude another; it is possible to have both the REST and D-Bus
APIs running simultaneously.
options. As soon as the `cloud-hypervisor` binary is launched, the
[REST API](#rest-api) is available for controlling and managing the VM.
1. Start the [REST API](#rest-api) server only, by not passing any VM
configuration options. The VM can then be asynchronously created and booted
by sending HTTP commands to the [REST API](#rest-api). Check the
[REST API examples](#rest-api-examples) section for more details.
### REST API, D-Bus API and CLI Architectural Relationship
### REST API and CLI Architectural Relationship
The REST API, D-Bus API and the CLI all rely on a common, [internal API](#internal-api).
The REST API and the CLI both rely on a common, [internal API](#internal-api).
The CLI options are parsed by the
[argh crate](https://docs.rs/argh/latest/argh/) and then translated into
[clap crate](https://docs.rs/clap/2.33.0/clap/) and then translated into
[internal API](#internal-api) commands.
The REST API is processed by an HTTP thread using the
[Firecracker's `micro_http`](https://github.com/firecracker-microvm/micro-http)
[Firecracker's `micro_http`](https://github.com/firecracker-microvm/firecracker/tree/master/src/micro_http)
crate. As with the CLI, the HTTP requests eventually get translated into
[internal API](#internal-api) commands.
The D-Bus API is implemented using the [zbus](https://github.com/dbus2/zbus)
crate and runs in its own thread. Whenever it needs to call the [internal API](#internal-api),
the [blocking](https://github.com/smol-rs/blocking) crate is used perform the call in zbus' async context.
As a summary, the REST API, the D-Bus API and the CLI are essentially frontends for the
As a summary, the REST API and the CLI are essentially frontends for the
[internal API](#internal-api):
```
@@ -318,16 +236,16 @@ As a summary, the REST API, the D-Bus API and the CLI are essentially frontends
| +------------------+ |
| | +------------------------+
| | | |
+------------+ | +----------+ | | |
| | | D-Bus API | | | | +--------------+ |
| User +---------+----------->+ zbus +--------------+------> | Internal API | |
| | | | | | | +--------------+ |
+------------+ | +----------+ | | |
+------------+ | | | |
| | | | | +--------------+ |
| User +---------+ +------> | Internal API | |
| | | | | +--------------+ |
+------------+ | | | |
| | | |
| | +------------------------+
| +----------+ | VMM
| CLI | | |
+----------->+ argh +--------------+
+----------->+ clap +--------------+
| |
+----------+
@@ -337,23 +255,22 @@ As a summary, the REST API, the D-Bus API and the CLI are essentially frontends
## Internal API
The Cloud Hypervisor internal API, as its name suggests, is used internally
by the different Cloud Hypervisor threads (VMM, HTTP, D-Bus, control loop,
etc) to send commands and responses to each others.
by the different Cloud Hypervisor threads (VMM, HTTP, control loop, etc) to
send commands and responses to each others.
It is based on [rust's Multi-Producer, Single-Consumer (MPSC)](https://doc.rust-lang.org/std/sync/mpsc/),
and the single consumer (a.k.a. the API receiver) is the Cloud Hypervisor
control loop.
API producers are the HTTP thread handling the [REST API](#rest-api), the
D-Bus thread handling the [D-Bus API](#d-bus-api) and the main thread that
initially parses the [CLI](#command-line-interface).
API producers are the HTTP thread handling the [REST API](#rest-api) and the
main thread that initially parses the [CLI](#command-line-interface).
### Goals and Design
The internal API is designed for controlling, managing and inspecting a Cloud
Hypervisor VMM and its guest. It is a backend for handling external, user
visible requests through the [REST API](#rest-api), the [D-Bus API](#d-bus-api)
or the [CLI](#command-line-interface) interfaces.
visible requests through either the [REST API](#rest-api) or the
[CLI](#command-line-interface) interfaces.
The API follows a command-response scheme that closely maps the [REST API](#rest-api).
Any command must be replied to with a response.
@@ -393,7 +310,8 @@ APIs work together, let's look at a complete VM creation flow, from the
-H 'Content-Type: application/json' \
-d '{
"cpus":{"boot_vcpus": 4, "max_vcpus": 4},
"payload":{"kernel":"/opt/clh/kernel/vmlinux-virtio-fs-virtio-iommu", "cmdline":"console=ttyS0 console=hvc0 root=/dev/vda1 rw"},
"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"}],
"rng":{"src":"/dev/urandom"},
"net":[{"ip":"192.168.10.10", "mask":"255.255.255.0", "mac":"12:34:56:78:90:01"}]
@@ -462,5 +380,5 @@ APIs work together, let's look at a complete VM creation flow, from the
```
1. The Cloud Hypervisor HTTP thread sends the formed HTTP response back to the
user. This is abstracted by the
[micro_http](https://github.com/firecracker-microvm/micro-http)
[micro_http](https://github.com/firecracker-microvm/firecracker/tree/master/src/micro_http)
crate.

152
docs/arm64.md Normal file
View File

@@ -0,0 +1,152 @@
# How to build and test Cloud Hypervisor on AArch64
This document introduces how to build and test Cloud Hypervisor on AArch64.
Currently, Cloud Hypervisor supports 2 methods of booting on AArch64: UEFI
booting and direct-kernel booting. The document covers both methods.
All the steps are based on Ubuntu. We use the Ubuntu cloud image for guest VM
disk.
## Hardware requirements
- AArch64 servers (recommended) or development boards equipped with the GICv3
interrupt controller.
- On development boards that have constrained RAM resources, if the creation of
a VM consumes a large portion of the free memory on the host, it may be required
to enable swap. For example, this was required on a board with 3 GB of RAM
booting a 2 GB VM at a point in time when 2.8 GB were free. Without enabling
swap the `cloud-hypervisor` process was terminated by the OOM killer. In this
situation memory was allocated for the virtual machine using memfd while the
page cache was filled, leading to a situation where the kernel could not even
drop caches. Making a small section of swap available (observably, 1 to 15 MB),
this situation can be resolved and the resulting memory footprint of
`cloud-hypervisor` is as expected.
## Getting started
We create a folder to build and run Cloud Hypervisor at `$HOME/cloud-hypervisor`
```shell
$ export CLOUDH=$HOME/cloud-hypervisor
$ mkdir $CLOUDH
```
## Prerequisites
You need to install some prerequisite packages to build and test Cloud Hypervisor.
### Tools
```bash
# Install rust tool chain
$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Install the tools used for building guest kernel, EDK2 and converting guest disk
$ sudo apt-get update
$ sudo apt-get install git build-essential m4 bison flex uuid-dev qemu-utils
```
### Building Cloud Hypervisor
```bash
$ pushd $CLOUDH
$ git clone https://github.com/cloud-hypervisor/cloud-hypervisor.git
$ cd cloud-hypervisor
$ cargo build
$ popd
```
### Disk image
Download the Ubuntu cloud image and convert the image type.
```bash
$ pushd $CLOUDH
$ wget https://cloud-images.ubuntu.com/focal/current/focal-server-cloudimg-arm64.img
$ qemu-img convert -p -f qcow2 -O raw focal-server-cloudimg-arm64.img focal-server-cloudimg-arm64.raw
$ popd
```
## UEFI booting
This part introduces how to build EDK2 firmware and boot Cloud Hypervisor with it.
### Building EDK2
```bash
$ pushd $CLOUDH
# Clone source code repos
$ git clone --depth 1 https://github.com/tianocore/edk2.git -b master
$ cd edk2
$ git submodule update --init
$ cd ..
$ git clone --depth 1 https://github.com/tianocore/edk2-platforms.git -b master
$ git clone --depth 1 https://github.com/acpica/acpica.git -b master
# Build tools
$ export PACKAGES_PATH="$PWD/edk2:$PWD/edk2-platforms"
$ export IASL_PREFIX="$PWD/acpica/generate/unix/bin/"
$ make -C acpica
$ cd edk2/
$ . edksetup.sh
$ cd ..
$ make -C edk2/BaseTools
# Build EDK2
$ build -a AARCH64 -t GCC5 -p ArmVirtPkg/ArmVirtCloudHv.dsc -b RELEASE
$ popd
```
If the build goes well, the EDK2 binary is available at
`edk2/Build/ArmVirtCloudHv-AARCH64/RELEASE_GCC5/FV/CLOUDHV_EFI.fd`.
### Booting the guest VM
```bash
$ pushd $CLOUDH
$ sudo RUST_BACKTRACE=1 $CLOUDH/cloud-hypervisor/target/debug/cloud-hypervisor \
--api-socket /tmp/cloud-hypervisor.sock \
--kernel $CLOUDH/edk2/Build/ArmVirtCloudHv-AARCH64/RELEASE_GCC5/FV/CLOUDHV_EFI.fd \
--disk path=$CLOUDH/focal-server-cloudimg-arm64.raw \
--cpus boot=4 \
--memory size=4096M \
--net tap=,mac=12:34:56:78:90:01,ip=192.168.1.1,mask=255.255.255.0 \
--serial tty \
--console off
$ popd
```
## Direct-kernel booting
Alternativelly, you can build your own kernel for guest VM. This way, UEFI is
not involved and ACPI cannot be enabled.
### Building kernel
```bash
$ pushd $CLOUDH
$ git clone --depth 1 "https://github.com/cloud-hypervisor/linux.git" -b ch-5.12
$ cd linux
$ cp $CLOUDH/cloud-hypervisor/resources/linux-config-aarch64 .config
$ make -j `nproc`
$ popd
```
### Booting the guest VM
```bash
$ pushd $CLOUDH
$ sudo $CLOUDH/cloud-hypervisor/target/debug/cloud-hypervisor \
--api-socket /tmp/cloud-hypervisor.sock \
--kernel $CLOUDH/linux/arch/arm64/boot/Image \
--disk path=focal-server-cloudimg-arm64.raw \
--cmdline "keep_bootcon console=ttyAMA0 reboot=k panic=1 root=/dev/vda1 rw" \
--cpus boot=4 \
--memory size=4096M \
--net tap=,mac=12:34:56:78:90:01,ip=192.168.1.1,mask=255.255.255.0 \
--serial tty \
--console off
$ popd
```

View File

@@ -1,86 +0,0 @@
- [Building Cloud Hypervisor](#building-cloud-hypervisor)
- [Preparation](#preparation)
- [Install prerequisites](#install-prerequisites)
- [Clone and build](#clone-and-build)
- [Containerized builds and tests](#containerized-builds-and-tests)
# Building Cloud Hypervisor
We recommend users use the pre-built binaries that are mentioned in the README.md file in the root of the repository. Building from source is only necessary if you wish to make modifications.
## Preparation
We create a folder to build and run `cloud-hypervisor` at `$HOME/cloud-hypervisor`
```shell
$ export CLOUDH=$HOME/cloud-hypervisor
$ mkdir $CLOUDH
```
## Install prerequisites
You need to install some prerequisite packages in order to build and test Cloud
Hypervisor. Here, all the steps are based on Ubuntu, for other Linux
distributions please replace the package manager and package name.
```shell
# Install basic packages needed. For a package list targeting for more
# functionalities for example the test, please see resources/Dockerfile.
$ sudo apt-get update
$ sudo apt install git build-essential m4 bison flex uuid-dev qemu-utils musl-tools
# Install rust tool chain
$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# If you want to build statically linked binary please add musl target
$ rustup target add x86_64-unknown-linux-musl # x86-64
$ rustup target add aarch64-unknown-linux-musl # AArch64
```
## Clone and build
First you need to clone and build the Cloud Hypervisor repository:
```shell
$ pushd $CLOUDH
$ git clone https://github.com/cloud-hypervisor/cloud-hypervisor.git
$ cd cloud-hypervisor
$ cargo build --release
# We need to give the cloud-hypervisor binary the NET_ADMIN capabilities for it to set TAP interfaces up on the host.
$ sudo setcap cap_net_admin+ep ./target/release/cloud-hypervisor
# If you want to build statically linked binary
$ cargo build --release --target=x86_64-unknown-linux-musl --all # x86-64
$ cargo build --release --target=aarch64-unknown-linux-musl --all # AArch64
$ popd
```
This will build a `cloud-hypervisor` binary under
`$CLOUDH/cloud-hypervisor/target/release/cloud-hypervisor`.
### Containerized builds and tests
If you want to build and test Cloud Hypervisor without having to install all the
required dependencies (The rust toolchain, cargo tools, etc), you can also use
Cloud Hypervisor's development script: `dev_cli.sh`. Please note that upon its
first invocation, this script will pull a fairly large container image.
For example, to build the Cloud Hypervisor release binary:
```shell
$ pushd $CLOUDH
$ cd cloud-hypervisor
$ ./scripts/dev_cli.sh build --release
```
With `dev_cli.sh`, one can also run the Cloud Hypervisor CI locally. This can be
very convenient for debugging CI errors without having to fully rely on the
Cloud Hypervisor CI infrastructure.
For example, to run the Cloud Hypervisor unit tests:
```shell
$ ./scripts/dev_cli.sh tests --unit
```
Run the `./scripts/dev_cli.sh --help` command to view all the supported
development script commands and their related options.

View File

@@ -59,16 +59,6 @@ mkdir -p /mnt
sudo mount -o loop,offset=$((227328 * 512)) focal-server-cloudimg-amd64.raw /mnt
```
### Set up DNS
The next step describes changing the root directory to the rootfs contained by
the cloud image. For DNS to work in the root directory, you will need to first bind-mount
the host `/etc/resolv.conf` onto the mounted linux partition of the cloud image.
```bash
sudo mount -o bind /etc/resolv.conf /mnt/etc/resolv.conf
```
### Change root directory
Changing the root directory will allow us to install new packages to the rootfs
@@ -88,7 +78,7 @@ Ubuntu distributions.
```bash
apt update
apt install fio iperf iperf3 socat stress cpuid tpm2-tools
apt install fio iperf iperf3 socat stress
```
### Remove counterproductive packages
@@ -118,7 +108,6 @@ umount /dev/pts
umount /proc
history -c
exit
umount /mnt/etc/resolv.conf
umount /mnt
```
@@ -158,172 +147,3 @@ as we might need to update the direct kernel boot command line, replacing
`/dev/vda1` with the appropriate partition number.
Update all references to the previous image name to the new one.
## NVIDIA image for VFIO baremetal CI
Here we are going to describe how to create a cloud image that contains the
necessary NVIDIA drivers for our VFIO baremetal CI.
### Download base image
We usually start from one of the custom cloud image we have previously created
but we can use a stock cloud image as well.
```bash
wget https://cloud-hypervisor.azureedge.net/jammy-server-cloudimg-amd64-custom-20230119-0.raw
mv jammy-server-cloudimg-amd64-custom-20230119-0.raw jammy-server-cloudimg-amd64-nvidia.raw
```
### Extend the image size
The NVIDIA drivers consume lots of space, which is why we must resize the image
before we proceed any further.
```bash
qemu-img resize jammy-server-cloudimg-amd64-nvidia.raw 5G
```
### Resize the partition
We use `parted` for fixing the GPT after the image was resized, as well as for
resizing the `Linux` partition.
```bash
sudo parted jammy-server-cloudimg-amd64-nvidia.raw
(parted) print
Warning: Not all of the space available to jammy-server-cloudimg-amd64-nvidia.raw
appears to be used, you can fix the GPT to use all of the space (an extra 5873664
blocks) or continue with the current setting?
Fix/Ignore? Fix
Model: (file)
Disk jammy-server-cloudimg-amd64-nvidia.raw: 5369MB
Sector size (logical/physical): 512B/512B
Partition Table: gpt
Disk Flags:
Number Start End Size File system Name Flags
14 1049kB 5243kB 4194kB bios_grub
15 5243kB 116MB 111MB fat32 boot, esp
1 116MB 2361MB 2245MB ext4
(parted) resizepart 1 5369MB
(parted) print
Model: (file)
Disk jammy-server-cloudimg-amd64-nvidia.raw: 5369MB
Sector size (logical/physical): 512B/512B
Partition Table: gpt
Disk Flags:
Number Start End Size File system Name Flags
14 1049kB 5243kB 4194kB bios_grub
15 5243kB 116MB 111MB fat32 boot, esp
1 116MB 5369MB 5252MB ext4
(parted) quit
```
### Create a macvtap interface
Rely on the following [documentation](macvtap-bridge.md) to set up a
macvtap interface to provide your VM with proper connectivity.
### Boot the image
It is particularly important to boot with a `cloud-init` disk attached to the
VM as it will automatically resize the Linux `ext4` filesystem based on the
partition that we have previously resized.
```bash
./cloud-hypervisor \
--kernel hypervisor-fw \
--disk path=focal-server-cloudimg-amd64-nvidia.raw path=/tmp/ubuntu-cloudinit.img \
--cpus boot=4 \
--memory size=4G \
--net fd=3,mac=$mac 3<>$"$tapdevice"
```
### Bring up connectivity
If your network has a DHCP server, run the following from your VM
```bash
sudo dhclient
```
But if that's not the case, let's give it an IP manually (the IP addresses
depend on your actual network) and set the DNS server IP address as well.
```bash
sudo ip addr add 192.168.2.10/24 dev ens4
sudo ip link set up dev ens4
sudo ip route add default via 192.168.2.1
sudo resolvectl dns ens4 8.8.8.8
```
#### Check connectivity and update the image
```bash
sudo apt update
sudo apt upgrade
```
### Install NVIDIA drivers
The following steps and commands are referenced from the
[NVIDIA official documentation](https://docs.nvidia.com/datacenter/tesla/tesla-installation-notes/index.html#ubuntu-lts)
about Tesla compute cards.
```bash
distribution=$(. /etc/os-release;echo $ID$VERSION_ID | sed -e 's/\.//g')
wget https://developer.download.nvidia.com/compute/cuda/repos/$distribution/x86_64/cuda-keyring_1.0-1_all.deb
sudo dpkg -i cuda-keyring_1.0-1_all.deb
sudo apt-key del 7fa2af80
sudo apt update
sudo apt -y install cuda-drivers
```
### Check the `nvidia-smi` tool
Quickly validate that you can find and run the `nvidia-smi` command from your
VM. At this point it should fail given no NVIDIA card has been passed through
the VM, therefore no NVIDIA driver is loaded.
### Workaround LA57 reboot issue
Add `reboot=a` to `GRUB_CMDLINE_LINUX` in `etc/default/grub` so that the VM
will be booted with the ACPI reboot type. This resolves a reboot issue when
running on 5-level paging systems.
```bash
sudo vim /etc/default/grub
sudo update-grub
sudo reboot
```
### Remove previous logins
Since our integration tests rely on past logins to count the number of reboots,
we must ensure to clear the list.
```bash
>/var/log/lastlog
>/var/log/wtmp
>/var/log/btmp
```
### Clear history
```
history -c
rm /home/cloud/.bash_history
```
### Reset cloud-init
This is mandatory as we want `cloud-init` provisioning to work again when a new
VM will be booted with this image.
```
sudo cloud-init clean
```

View File

@@ -36,7 +36,7 @@ Assuming parts of the guest software stack have been instrumented to use the
`cloud-hypervisor` debug I/O port, we may want to gather the related logs.
To do so we need to start `cloud-hypervisor` with the right debug level
(`-v -v -v`). It is also recommended to have it log into a dedicated file in order
(`-vvv`). It is also recommended to have it log into a dedicated file in order
to easily grep for the tracing logs (e.g.
`--log-file /tmp/cloud-hypervisor.log`):
@@ -48,7 +48,7 @@ to easily grep for the tracing logs (e.g.
--memory size=1024M \
--rng \
--log-file /tmp/ch-fw.log \
-v -v -v
-vvv
```
After booting the guest, we then have to grep for the debug I/O port traces in

View File

@@ -6,22 +6,22 @@ This document describes the device model supported by `cloud-hypervisor`.
| Device | Build configurable | Enabled by default | Runtime configurable |
| :----: | :----: | :----: | :----: |
| Serial port | :x: | :x: | :heavy_check_mark: |
| RTC/CMOS | :heavy_check_mark: | :heavy_check_mark: | :x: |
| I/O APIC | :x: | :x: | :heavy_check_mark: |
| i8042 shutdown/reboot | :x: | :x: | :x: |
| ACPI shutdown/reboot | :x: | :heavy_check_mark: | :x: |
| virtio-blk | :x: | :x: | :heavy_check_mark: |
| virtio-console | :x: | :x: | :heavy_check_mark: |
| virtio-iommu | :x: | :x: | :heavy_check_mark: |
| virtio-net | :x: | :x: | :heavy_check_mark: |
| virtio-pmem | :x: | :x: | :heavy_check_mark: |
| virtio-rng | :x: | :x: | :heavy_check_mark: |
| virtio-vsock | :x: | :x: | :heavy_check_mark: |
| vhost-user-blk | :x: | :x: | :heavy_check_mark: |
| vhost-user-fs | :x: | :x: | :heavy_check_mark: |
| vhost-user-net | :x: | :x: | :heavy_check_mark: |
| VFIO | :heavy_check_mark: | :x: | :heavy_check_mark: |
| Serial port | :negative_squared_cross_mark: | :negative_squared_cross_mark: | :heavy_check_mark: |
| RTC/CMOS | :heavy_check_mark: | :heavy_check_mark: | :negative_squared_cross_mark: |
| I/O APIC | :negative_squared_cross_mark: | :negative_squared_cross_mark: | :heavy_check_mark: |
| i8042 shutdown/reboot | :negative_squared_cross_mark: | :negative_squared_cross_mark: | :negative_squared_cross_mark: |
| ACPI shutdown/reboot | :negative_squared_cross_mark: | :heavy_check_mark: | :negative_squared_cross_mark: |
| virtio-blk | :negative_squared_cross_mark: | :negative_squared_cross_mark: | :heavy_check_mark: |
| virtio-console | :negative_squared_cross_mark: | :negative_squared_cross_mark: | :heavy_check_mark: |
| virtio-iommu | :negative_squared_cross_mark: | :negative_squared_cross_mark: | :heavy_check_mark: |
| virtio-net | :negative_squared_cross_mark: | :negative_squared_cross_mark: | :heavy_check_mark: |
| virtio-pmem | :negative_squared_cross_mark: | :negative_squared_cross_mark: | :heavy_check_mark: |
| virtio-rng | :negative_squared_cross_mark: | :negative_squared_cross_mark: | :heavy_check_mark: |
| virtio-vsock | :negative_squared_cross_mark: | :negative_squared_cross_mark: | :heavy_check_mark: |
| vhost-user-blk | :negative_squared_cross_mark: | :negative_squared_cross_mark: | :heavy_check_mark: |
| vhost-user-fs | :negative_squared_cross_mark: | :negative_squared_cross_mark: | :heavy_check_mark: |
| vhost-user-net | :negative_squared_cross_mark: | :negative_squared_cross_mark: | :heavy_check_mark: |
| VFIO | :heavy_check_mark: | :negative_squared_cross_mark: | :heavy_check_mark: |
## Legacy devices

View File

@@ -29,11 +29,10 @@ mkdir /tmp/shared_dir
_Run virtiofsd_
```bash
./virtiofsd \
--log-level debug \
-d \
--socket-path=/tmp/virtiofs \
--shared-dir=/tmp/shared_dir \
--cache=never \
--thread-pool-size=$N
--cache=never
```
The `cache=never` option is the default when using `virtiofsd` with
@@ -45,10 +44,6 @@ The `cache=always` option will allow the host page cache to be used, which can
result in better performance for the guest's workload at the cost of increasing
the footprint on host memory.
The `thread-pool-size` option controls how many IO threads are spawned. For
very fast storage like NVMe spawning enough worker threads is critical to
getting an acceptable performance compared to native.
### Kernel support
Modern Linux kernels (at least v5.10) have support for virtio-fs. Use of older
@@ -95,4 +90,4 @@ Cloud Hypervisor command line, which happens to be `myfs` in this example.
## DAX feature
Given the DAX feature is not stable yet from a daemon standpoint, it is not
available in Cloud Hypervisor.
available in Cloud Hypervisor.

View File

@@ -2,13 +2,13 @@
This feature allows remote guest debugging using GDB. Note that this feature is only supported on x86_64/KVM.
To enable debugging with GDB, build with the `guest_debug` feature enabled:
To enable debugging with GDB, build with the `gdb` feature enabled:
```bash
cargo build --features guest_debug
cargo build --features gdb
```
To use the `--gdb` option, specify the Unix Domain Socket with `path` that Cloud Hypervisor will use to communicate with the host's GDB:
To use the `--gdb` option, specify the Unix Domain Socket with `--path` that Cloud Hypervisor will use to communicate with the host's GDB:
```bash
./cloud-hypervisor \
@@ -44,4 +44,4 @@ Continuing.
Breakpoint 1, 0x00000000001121b7 in ?? ()
(gdb)
```
```

View File

@@ -1,41 +0,0 @@
# Heap profiling
Cloud Hypervisor supports generating a profile using
[dhat](https://docs.rs/dhat/latest/dhat/) of the heap allocations made during
the runtime of the process.
## Building a suitable binary
This adds the symbol information to the release binary but does not otherwise
affect the performance.
```
$ cargo build --profile profiling --features "dhat-heap"
```
## Generating output
Cloud Hypervisor can then be run as usual. However it is necessary to run with `--seccomp false` as the profiling requires extra syscalls.
```
$ target/profiling/cloud-hypervisor \
--kernel ~/src/linux/vmlinux \
--pmem file=~/workloads/focal.raw \
--cpus boot=1 --memory size=1G \
--cmdline "root=/dev/pmem0p1 console=ttyS0" \
--serial tty --console off \
--api-socket /tmp/api1 \
--seccomp false
```
When the VMM exits a message like the following will be shown:
```
dhat: Total: 384,582 bytes in 3,512 blocks
dhat: At t-gmax: 133,885 bytes in 379 blocks
dhat: At t-end: 12,160 bytes in 20 blocks
dhat: The data has been saved to dhat-heap.json, and is viewable with dhat/dh_view.html
```
The JSON output can then be uploaded to [the dh_view tool](https://nnethercote.github.io/dh_view/dh_view.html) for analysis.

View File

@@ -4,7 +4,7 @@ Currently Cloud Hypervisor supports hot plugging of CPUs devices (x86 only), PCI
## 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`
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
## CPU Hot Plug
@@ -27,16 +27,16 @@ $ ./cloud-hypervisor/target/release/cloud-hypervisor \
--memory size=1024M \
--net "tap=,mac=,ip=,mask=" \
--rng \
--api-socket /tmp/ch-socket
--api-socket=/tmp/ch-socket
$ popd
```
Notice the addition of `--api-socket /tmp/ch-socket` and a `max` parameter on `--cpus boot=4,max=8`.
Notice the addition of `--api-socket=/tmp/ch-socket` and a `max` parameter on `--cpus boot=4,max=8`.
To ask the VMM to add additional vCPUs then use the resize API:
```shell
./ch-remote --api-socket /tmp/ch-socket resize --cpus 8
./ch-remote --api-socket=/tmp/ch-socket resize --cpus 8
```
The extra vCPU threads will be created and advertised to the running kernel. The kernel does not bring up the CPUs immediately and instead the user must "online" them from inside the VM:
@@ -56,7 +56,7 @@ After a reboot the added CPUs will remain.
Removing CPUs works similarly by reducing the number in the "desired_vcpus" field of the reisze API. The CPUs will be automatically offlined inside the guest so there is no need to run any commands inside the guest:
```shell
./ch-remote --api-socket /tmp/ch-socket resize --cpus 2
./ch-remote --api-socket=/tmp/ch-socket resize --cpus 2
```
As per adding CPUs to the guest, after a reboot the VM will be running with the reduced number of vCPUs.
@@ -85,7 +85,7 @@ $ ./cloud-hypervisor/target/release/cloud-hypervisor \
--memory size=1024M,hotplug_size=8192M \
--net "tap=,mac=,ip=,mask=" \
--rng \
--api-socket /tmp/ch-socket
--api-socket=/tmp/ch-socket
$ popd
```
@@ -98,7 +98,7 @@ root@ch-guest ~ # echo online | sudo tee /sys/devices/system/memory/auto_online_
To ask the VMM to expand the RAM for the VM:
```shell
./ch-remote --api-socket /tmp/ch-socket resize --memory 3G
./ch-remote --api-socket=/tmp/ch-socket resize --memory 3G
```
The new memory is now available to use inside the VM:
@@ -134,14 +134,14 @@ $ ./cloud-hypervisor/target/release/cloud-hypervisor \
--disk path=focal-server-cloudimg-amd64.raw \
--memory size=1024M,hotplug_size=8192M,hotplug_method=virtio-mem \
--net "tap=,mac=,ip=,mask=" \
--api-socket /tmp/ch-socket
--api-socket=/tmp/ch-socket
$ popd
```
To ask the VMM to expand the RAM for the VM (request is in bytes):
```shell
./ch-remote --api-socket /tmp/ch-socket resize --memory 3G
./ch-remote --api-socket=/tmp/ch-socket resize --memory 3G
```
The new memory is now available to use inside the VM:
@@ -159,7 +159,7 @@ The same API can also be used to reduce the desired RAM for a VM. It is importan
Extra PCI devices can be added and removed from a running `cloud-hypervisor` instance. This is controlled by making a HTTP API request to the VMM to ask for the additional device to be added, or for the existing device to be removed.
Note: On AArch64 platform, PCI device hotplug can only be achieved using ACPI. Please refer to the [documentation](uefi.md#building-uefi-firmware-for-aarch64) for more information.
Note: On AArch64 platform, PCI device hotplug can only be achieved using ACPI. Please refer to the [documentation](arm64.md#uefi-booting) for more information.
To use PCI device hotplug start the VM with the HTTP server.
@@ -172,17 +172,17 @@ $ ./cloud-hypervisor/target/release/cloud-hypervisor \
--cpus boot=4 \
--memory size=1024M \
--net "tap=,mac=,ip=,mask=" \
--api-socket /tmp/ch-socket
--api-socket=/tmp/ch-socket
```
Notice the addition of `--api-socket /tmp/ch-socket`.
Notice the addition of `--api-socket=/tmp/ch-socket`.
### Add VFIO Device
To ask the VMM to add additional VFIO device then use the `add-device` API.
```shell
./ch-remote --api-socket /tmp/ch-socket add-device path=/sys/bus/pci/devices/0000:01:00.0/
./ch-remote --api-socket=/tmp/ch-socket add-device path=/sys/bus/pci/devices/0000:01:00.0/
```
### Add Disk Device
@@ -190,7 +190,7 @@ To ask the VMM to add additional VFIO device then use the `add-device` API.
To ask the VMM to add additional disk device then use the `add-disk` API.
```shell
./ch-remote --api-socket /tmp/ch-socket add-disk path=/foo/bar/cloud.img
./ch-remote --api-socket=/tmp/ch-socket add-disk path=/foo/bar/cloud.img
```
### Add Fs Device
@@ -198,7 +198,7 @@ To ask the VMM to add additional disk device then use the `add-disk` API.
To ask the VMM to add additional fs device then use the `add-fs` API.
```shell
./ch-remote --api-socket /tmp/ch-socket add-fs tag=myfs,socket=/foo/bar/virtiofs.sock
./ch-remote --api-socket=/tmp/ch-socket add-fs tag=myfs,socket=/foo/bar/virtiofs.sock
```
### Add Net Device
@@ -206,7 +206,7 @@ To ask the VMM to add additional fs device then use the `add-fs` API.
To ask the VMM to add additional network device then use the `add-net` API.
```shell
./ch-remote --api-socket /tmp/ch-socket add-net tap=chtap0
./ch-remote --api-socket=/tmp/ch-socket add-net tap=chtap0
```
### Add Pmem Device
@@ -214,7 +214,7 @@ To ask the VMM to add additional network device then use the `add-net` API.
To ask the VMM to add additional PMEM device then use the `add-pmem` API.
```shell
./ch-remote --api-socket /tmp/ch-socket add-pmem file=/foo/bar.cloud.img
./ch-remote --api-socket=/tmp/ch-socket add-pmem file=/foo/bar.cloud.img
```
### Add Vsock Device
@@ -222,7 +222,7 @@ To ask the VMM to add additional PMEM device then use the `add-pmem` API.
To ask the VMM to add additional vsock device then use the `add-vsock` API.
```shell
./ch-remote --api-socket /tmp/ch-socket add-vsock cid=3,socket=/foo/bar/vsock.sock
./ch-remote --api-socket=/tmp/ch-socket add-vsock cid=3,socket=/foo/bar/vsock.sock
```
### Common Across All PCI Devices
@@ -244,7 +244,7 @@ After a reboot the added PCI device will remain.
Removing a PCI device works the same way for all kind of PCI devices. The unique identifier related to the device must be provided. This identifier can be provided by the user when adding the new device, or by default Cloud Hypervisor will assign one.
```shell
./ch-remote --api-socket /tmp/ch-socket remove-device _disk0
./ch-remote --api-socket=/tmp/ch-socket remove-device _disk0
```
As per adding a PCI device to the guest, after a reboot the VM will be running without the removed PCI device.

View File

@@ -2,67 +2,49 @@
Intel® Trust Domain Extensions (Intel® TDX) is an Intel technology designed to
isolate virtual machines from the VMM, hypervisor and any other software on the
host platform. Here are some useful links:
host platform.
* [TDX Homepage](https://www.intel.com/content/www/us/en/developer/articles/technical/intel-trust-domain-extensions.html):
more information about TDX technical aspects, design and specification
For more information about TDX technical aspects, design and specification
please refer to the
[TDX Homepage](https://www.intel.com/content/www/us/en/developer/articles/technical/intel-trust-domain-extensions.html).
* [KVM TDX tree](https://github.com/intel/tdx/tree/kvm): the required
Linux kernel changes for the host side
The required Linux changes for the host side can be found in the
[KVM TDX tree](https://github.com/intel/tdx/tree/kvm) while the changes for
the guest side can be found in the [Guest TDX tree](https://github.com/intel/tdx/tree/guest).
* [Guest TDX tree](https://github.com/intel/tdx/tree/guest): the Linux
kernel changes for the guest side
The TDVF firmware can be found in the
[EDK2 staging project](https://github.com/tianocore/edk2-staging/tree/TDVF).
* [EDK2 project](https://github.com/tianocore/edk2): the TDVF firmware
* [Confidential Containers project](https://github.com/confidential-containers/td-shim):
the TDShim firmware
* [TDX Tools](https://github.com/intel/tdx-tools): a collection of tools
and scripts to setup TDX environment for testing purpose (such as
installing required packages on the host, creating guest images, and
building the custom Linux kernel for TDX host and guest)
The TDShim firmware can be found in the
[Confidential Containers project](https://github.com/confidential-containers/td-shim).
## Cloud Hypervisor support
It is required to use a machine with TDX enabled in hardware and
First, you must be running on a machine with TDX enabled in hardware, and
with the host OS compiled from the [KVM TDX tree](https://github.com/intel/tdx/tree/kvm).
The host environment can also be setup with the [TDX Tools](https://github.com/intel/tdx-tools).
Cloud Hypervisor can run TDX VM (Trust Domain) by loading a TD firmware ([TDVF](https://github.com/tianocore/edk2)),
Cloud Hypervisor can run TDX VM (Trust Domain) by loading a TD firmware,
which will then load the guest kernel from the image. The image must be custom
as it must include a kernel built from the [Guest TDX tree](https://github.com/intel/tdx/tree/guest).
Cloud Hypervisor can also boot a TDX VM with direct kernel boot using [TDshim](https://github.com/confidential-containers/td-shim).
The custom Linux kernel for the guest can be built with the [TDX Tools](https://github.com/intel/tdx-tools).
> **Note**
> The latest version of custom host and guest kernel being tested is
> from [TDX Tools - 2023ww01](https://github.com/intel/tdx-tools/commits/2023ww01).
### TDVF
> **Note**
> The latest version of TDVF being tested is [_13b9773_](https://github.com/tianocore/edk2/commit/13b97736c876919b9786055829caaa4fa46984b7).
The firmware can be built as follows:
```bash
sudo apt-get update
sudo apt-get install uuid-dev nasm iasl build-essential python3-distutils git
git clone https://github.com/tianocore/edk2.git
cd edk2
git checkout 13b97736c876919b9786055829caaa4fa46984b7
source ./edksetup.sh
git clone https://github.com/tianocore/edk2-staging.git
cd edk2-staging
git checkout origin/TDVF
git submodule update --init --recursive
make -C BaseTools -j `nproc`
build -p OvmfPkg/IntelTdx/IntelTdxX64.dsc -a X64 -t GCC5 -b RELEASE
make -C BaseTools
source ./edksetup.sh
build -p OvmfPkg/OvmfCh.dsc -a X64 -t GCC5 -b RELEASE
```
If debug logs are needed, here is the alternative command:
```bash
build -p OvmfPkg/IntelTdx/IntelTdxX64.dsc -a X64 -t GCC5 -D DEBUG_ON_SERIAL_PORT=TRUE
build -p OvmfPkg/OvmfCh.dsc -a X64 -t GCC5 -D DEBUG_ON_SERIAL_PORT=TRUE
```
On the Cloud Hypervisor side, all you need is to build the project with the
@@ -79,8 +61,7 @@ meaning it will be printing guest kernel logs to the `virtio-console` device.
```bash
./cloud-hypervisor \
--platform tdx=on
--firmware edk2/Build/IntelTdx/RELEASE_GCC5/FV/OVMF.fd \
--tdx firmware=edk2-staging/Build/OvmfCh/RELEASE_GCC5/FV/OVMF.fd \
--cpus boot=1 \
--memory size=1G \
--disk path=tdx_guest_img
@@ -91,8 +72,7 @@ firmware:
```bash
./cloud-hypervisor \
--platform tdx=on
--firmware edk2/Build/IntelTdx/DEBUG_GCC5/FV/OVMF.fd \
--tdx firmware=edk2-staging/Build/OvmfCh/DEBUG_GCC5/FV/OVMF.fd \
--cpus boot=1 \
--memory size=1G \
--disk path=tdx_guest_img \
@@ -102,46 +82,20 @@ firmware:
### TDShim
> **Note**
> The latest version of TDShim being tested is [_66bb334_](https://github.com/confidential-containers/td-shim/tree/66bb33451befbf1291abe3cfea7ee9e99d922b0d).
This is a lightweight version of the TDVF, written in Rust and designed for
direct kernel boot, which is useful for containers use cases.
To build TDShim from source, it is required to install `Rust`, `NASM`,
and `LLVM` first. The TDshim can be build as follows:
```bash
git clone https://github.com/confidential-containers/td-shim
cd td-shim
git checkout 66bb33451befbf1291abe3cfea7ee9e99d922b0d
cargo install cargo-xbuild
export CC=clang
export AR=llvm-ar
export CC_x86_64_unknown_none=clang
export AR_x86_64_unknown_none=llvm-ar
git submodule update --init --recursive
./sh_script/preparation.sh
cargo xbuild -p td-shim --target x86_64-unknown-none --release --features=main,tdx
cargo run -p td-shim-tools --bin td-shim-ld --features=linker -- target/x86_64-unknown-none/release/ResetVector.bin target/x86_64-unknown-none/release/td-shim -o target/release/final.bin
```
If debug logs from the TDShim is needed, here are the alternative
commands:
```bash
cargo xbuild -p td-shim --target x86_64-unknown-none --features=main,tdx
cargo run -p td-shim-tools --bin td-shim-ld --features=linker -- target/x86_64-unknown-none/debug/ResetVector.bin target/x86_64-unknown-none/debug/td-shim -o target/debug/final.bin
```
You can find the instructions for building the firmware directly from the
project [documentation](https://github.com/confidential-containers/td-shim/tree/staging#how-to-build).
And run a TDX VM by providing the firmware previously built, along with a guest
kernel built from the [Guest TDX tree](https://github.com/intel/tdx/tree/guest)
or the [TDX Tools](https://github.com/intel/tdx-tools).
kernel built from the [Guest TDX tree](https://github.com/intel/tdx/tree/guest).
The appropriate kernel boot options must be provided through the `--cmdline`
option as well.
```bash
./cloud-hypervisor \
--platform tdx=on
--firmware td-shim/target/release/final.bin \
--tdx firmware=tdshim \
--kernel bzImage \
--cmdline "root=/dev/vda3 console=hvc0 rw"
--cpus boot=1 \
@@ -149,32 +103,8 @@ option as well.
--disk path=tdx_guest_img
```
And here is the alternative command when looking for debug logs from the
TDShim:
```bash
./cloud-hypervisor \
--platform tdx=on
--firmware td-shim/target/debug/final.bin \
--kernel bzImage \
--cmdline "root=/dev/vda3 console=hvc0 rw"
--cpus boot=1 \
--memory size=1G \
--disk path=tdx_guest_img
```
### Guest kernel limitations
#### Serial ports disabled
### Guest kernel disables serial ports
The latest guest kernel that can be found in the latest image
`td-guest-rhel8.5.raw` disabled the support for serial ports. This means adding
`console=ttyS0` will have no effect and will not print any log from the guest.
#### PCI hotplug through ACPI
Unless you run the guest kernel with the parameter `tdx_disable_filter`, ACPI
devices responsible for handling PCI hotplug (PCI hotplug controller, PCI
Express Bus and Generic Event Device) will not be allowed, therefore the
corresponding drivers will not be loaded and the PCI hotplug feature will not
be supported.
`console=ttyS0` will have no effect and will not print any log from the guest.

View File

@@ -245,7 +245,7 @@ e.g.
```bash
./cloud-hypervisor \
--api-socket /tmp/api \
--api-socket=/tmp/api \
--cpus boot=1 \
--memory size=4G,hugepages=on \
--disk path=focal-server-cloudimg-amd64.raw \
@@ -260,7 +260,7 @@ requiring the IOMMU then may be hotplugged:
e.g.
```bash
./ch-remote --api-socket /tmp/api add-device path=/sys/bus/pci/devices/0000:00:04.0,iommu=on,pci_segment=1
./ch-remote --api-socket=/tmp/api add-device path=/sys/bus/pci/devices/0000:00:04.0,iommu=on,pci_segment=1
```
Devices that cannot be placed behind an IOMMU (e.g. lacking an `iommu=` option)

View File

@@ -16,22 +16,22 @@ $ target/release/cloud-hypervisor
--disk path=~/workloads/focal.raw \
--cpus boot=1 --memory size=1G,shared=on \
--cmdline "root=/dev/vda1 console=ttyS0" \
--serial tty --console off --api-socket /tmp/api1
--serial tty --console off --api-socket=/tmp/api1
```
Launch the destination VM from the same directory (on the host machine):
```bash
$ target/release/cloud-hypervisor --api-socket /tmp/api2
$ target/release/cloud-hypervisor --api-socket=/tmp/api2
```
Get ready for receiving migration for the destination VM (on the host machine):
```bash
$ target/release/ch-remote --api-socket /tmp/api2 receive-migration unix:/tmp/sock
$ target/release/ch-remote --api-socket=/tmp/api2 receive-migration unix:/tmp/sock
```
Start to send migration for the source VM (on the host machine):
```bash
$ target/release/ch-remote --api-socket /tmp/api1 send-migration --local unix:/tmp/sock
$ target/release/ch-remote --api-socket=/tmp/api1 send-migration --local unix:/tmp/sock
```
When the above commands completed, the source VM should be successfully
@@ -51,7 +51,7 @@ $ sudo /target/release/cloud-hypervisor \
--cpus boot=1 --memory size=512M \
--kernel vmlinux \
--cmdline "root=/dev/vda1 console=ttyS0" \
--disk path=focal-1.raw path=focal-nested.raw --disk path=tmp.img\
--disk path=focal-1.raw path=focal-nested.raw path=tmp.img\
--net ip=192.168.101.1
```
@@ -63,7 +63,7 @@ $ sudo /target/release/cloud-hypervisor \
--cpus boot=1 --memory size=512M \
--kernel vmlinux \
--cmdline "root=/dev/vda1 console=ttyS0" \
--disk path=focal-2.raw path=focal-nested.raw --disk path=tmp.img\
--disk path=focal-2.raw path=focal-nested.raw path=tmp.img\
--net ip=192.168.102.1
```
@@ -74,8 +74,8 @@ vm-1:~$ sudo ./cloud-hypervisor \
--memory size=128M \
--kernel vmlinux \
--cmdline "console=ttyS0 root=/dev/vda1" \
--disk path=/dev/vdb --disk path=/dev/vdc \
--api-socket /tmp/api1 \
--disk path=/dev/vdb path=/dev/vdc \
--api-socket=/tmp/api1 \
--net ip=192.168.100.1
vm-1:~$ # setup the guest network if needed
vm-1:~$ sudo ip addr add 192.168.101.2/24 dev ens4
@@ -108,7 +108,7 @@ echo "tmp = $tmp"
Launch the nested destination VM (inside the guest OS of the VM 2):
```bash
vm-2:~$ sudo ./cloud-hypervisor --api-socket /tmp/api2
vm-2:~$ sudo ./cloud-hypervisor --api-socket=/tmp/api2
vm-2:~$ # setup the guest network with the following commands if needed
vm-2:~$ sudo ip addr add 192.168.102.2/24 dev ens4
vm-2:~$ sudo ip link set up dev ens4
@@ -122,7 +122,7 @@ vm-2:~$ ping 192.168.101.2 # This should succeed
Get ready for receiving migration for the nested destination VM (inside
the guest OS of the VM 2):
```bash
vm-2:~$ sudo ./ch-remote --api-socket /tmp/api2 receive-migration unix:/tmp/sock2
vm-2:~$ sudo ./ch-remote --api-socket=/tmp/api2 receive-migration unix:/tmp/sock2
vm-2:~$ sudo socat TCP-LISTEN:6000,reuseaddr UNIX-CLIENT:/tmp/sock2
```
@@ -130,7 +130,7 @@ Start to send migration for the nested source VM (inside the guest OS of
the VM 1):
```bash
vm-1:~$ sudo socat UNIX-LISTEN:/tmp/sock1,reuseaddr TCP:192.168.102.2:6000
vm-1:~$ sudo ./ch-remote --api-socket /tmp/api1 send-migration unix:/tmp/sock1
vm-1:~$ sudo ./ch-remote --api-socket=/tmp/api1 send-migration unix:/tmp/sock1
```
When the above commands completed, the source VM should be successfully

View File

@@ -7,13 +7,13 @@ The target audience of this document is both:
## Control
The number of `-v` parameters passed to the `cloud-hypervisor` binary will determine the log level. Currently the default is log messages up to `WARN:` (`warn!`) are included by default. The `--log-file` allows the log to be sent to a location other than `stderr`.
The number of `-v` parameters passed to the `cloud-hypervisor` binary will determine the log level. Currenly the default is log messages up to `WARN:` (`warn!`) are included by default. The `--log-file` allows the log to be sent to a location other than `stderr`.
## Levels
### `error!()`
For immediate, unrecoverable errors where it does not make sense for the execution to continue as the behaviour of the VM is considerably impacted.
For immediate, unrecoverable errors where it does not make sense for the execution to continue as the behaviour of the VM is considerablely impacted.
Cloud Hypervisor should exit shortly after reporting this error (with a non-zero exit code). Generally this should be used during initial construction of the VM state before the virtual CPUs have begun running code.
@@ -23,7 +23,7 @@ Users should react to this error by checking their initial VM configuration.
### `warn!()`
A serious problem has occurred but the execution of the VM can continue although some functionality might be impacted.
A serious problem has occured but the execution of the VM can continue although some functionality might be impacted.
A typical example of where this level of message should be generated is during an API call request that cannot be fulfilled.
@@ -38,6 +38,6 @@ This level is for the benefit of developers. It should be used for sporadic and
### `debug!()`
Use `-v -v` to enable.
Use `-vv` to enable.
For the most verbose of logging messages. It is acceptable to "spam" the log with repeated invocations of the same message. This level of logging would be combined with `--log-file`.
For the most verbose of logging messages. It is acceptable to "spam" the log with repeated invocations of the same message. This level of logging would be combined with `--log-file`.

View File

@@ -20,13 +20,12 @@ struct MemoryConfig {
hugepages: bool,
hugepage_size: Option<u64>,
prefault: bool,
thp: bool
zones: Option<Vec<MemoryZoneConfig>>,
}
```
```
--memory <memory> Memory parameters "size=<guest_memory_size>,mergeable=on|off,shared=on|off,hugepages=on|off,hugepage_size=<hugepage_size>,hotplug_method=acpi|virtio-mem,hotplug_size=<hotpluggable_memory_size>,hotplugged_size=<hotplugged_memory_size>,prefault=on|off,thp=on|off" [default: size=512M,thp=on]
--memory <memory> Memory parameters "size=<guest_memory_size>,mergeable=on|off,shared=on|off,hugepages=on|off,hugepage_size=<hugepage_size>,hotplug_method=acpi|virtio-mem,hotplug_size=<hotpluggable_memory_size>,hotplugged_size=<hotplugged_memory_size>,prefault=on|off" [default: size=512M]
```
### `size`
@@ -118,9 +117,6 @@ needing access to the guest RAM content.
By default this option is turned off, which results in performing `mmap(2)`
with `MAP_PRIVATE` flag.
If `hugepages=on` then the value of this field is ignored as huge pages always
requires `MAP_SHARED`.
_Example_
```
@@ -144,9 +140,6 @@ behaviour, e.g. error with `ReadKernelImage` is common. If there is a strange
error with `hugepages` enabled, just disable it or check whether there are enough
huge pages.
If `hugepages=on` then the value of `shared` is ignored as huge pages always
requires `MAP_SHARED`.
By default this option is turned off.
_Example_
@@ -178,25 +171,6 @@ _Example_
--memory size=1G,prefault=on
```
### `thp`
Specifies if private anonymous memory for the guest (i.e. `shared=off` and no
backing file) should be labelled `MADV_HUGEPAGE` with `madvise(2)` indicating
to the kernel that this memory may be backed with huge pages transparently.
The use of transparent huge pages can improve the performance of the guest as
there will fewer virtualisation related page faults. Unlike using
`hugepages=on` a specific number of huge pages do not need to be allocated by
the kernel.
By default this option is turned on.
_Example_
```
--memory size=1G,thp=on
```
## Advanced Parameters
`MemoryZoneConfig` or what is known as `--memory-zone` from the CLI perspective
@@ -222,7 +196,7 @@ struct MemoryZoneConfig {
--memory-zone <memory-zone> User defined memory zone parameters "size=<guest_memory_region_size>,file=<backing_file>,shared=on|off,hugepages=on|off,hugepage_size=<hugepage_size>,host_numa_node=<node_id>,id=<zone_identifier>,hotplug_size=<hotpluggable_memory_size>,hotplugged_size=<hotplugged_memory_size>,prefault=on|off"
```
This parameter expects one or more occurrences, allowing for a list of memory
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.
@@ -266,8 +240,11 @@ _Example_
### `file`
Path to the file backing the memory zone. The file will be opened and used as
the backing file for the `mmap(2)` operation.
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
@@ -294,9 +271,6 @@ 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.
If `hugepages=on` then the value of this field is ignored as huge pages always
requires `MAP_SHARED`.
By default this option is turned off, which result in performing `mmap(2)`
with `MAP_PRIVATE` flag.
@@ -324,9 +298,6 @@ behaviour, e.g. error with `ReadKernelImage` is common. If there is a strange
error with `hugepages` enabled, just disable it or check whether there are enough
huge pages.
If `hugepages=on` then the value of `shared` is ignored as huge pages always
requires `MAP_SHARED`.
By default this option is turned off.
_Example_
@@ -516,7 +487,7 @@ 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] guest_numa_id=2,distances=[0@25,1@20]
--numa guest_numa_id=0,distances=[1@15,2@25] guest_numa_id=1,distances=[0@15,2@20] guest_numa_id=2,distances=[0@25,1@20]
```
### `memory_zones`
@@ -540,14 +511,14 @@ demarcate the list.
Note that a memory zone must belong to a single NUMA node. The following
configuration is incorrect, therefore not allowed:
`--numa guest_numa_id=0,memory_zones=mem0 --numa guest_numa_id=1,memory_zones=mem0`
`--numa guest_numa_id=0,memory_zones=mem0 guest_numa_id=1,memory_zones=mem0`
_Example_
```
--memory size=0
--memory-zone id=mem0,size=1G 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
--memory-zone id=mem0,size=1G id=mem1,size=1G id=mem2,size=1G
--numa guest_numa_id=0,memory_zones=[mem0,mem2] guest_numa_id=1,memory_zones=mem1
```
### `sgx_epc_sections`
@@ -567,7 +538,7 @@ _Example_
```
--sgx-epc id=epc0,size=32M id=epc1,size=64M id=epc2,size=32M
--numa guest_numa_id=0,sgx_epc_sections=epc1 --numa guest_numa_id=1,sgx_epc_sections=[epc0,epc2]
--numa guest_numa_id=0,sgx_epc_sections=epc1 guest_numa_id=1,sgx_epc_sections=[epc0,epc2]
```
### PCI bus

View File

@@ -4,12 +4,20 @@
## Building a suitable binary
Modify the `Cargo.toml` file to add `debug = 1` to the `[profile.release]` block. It should look like this:
```
[profile.release]
lto = true
debug = 1
```
This adds the symbol information to the release binary but does not otherwise affect the performance.
The binary must also be built with frame pointers included so that the call graph can be captured by the profiler.
```
$ cargo clean && RUSTFLAGS='-C force-frame-pointers=y' cargo build --profile profiling
$ cargo clean && RUSTFLAGS='-C force-frame-pointers=y' cargo build --release
```
## Profiling
@@ -19,13 +27,13 @@ $ cargo clean && RUSTFLAGS='-C force-frame-pointers=y' cargo build --profile pro
e.g.
```
$ perf record -g target/profiling/cloud-hypervisor \
$ perf record -g target/release/cloud-hypervisor \
--kernel ~/src/linux/vmlinux \
--pmem file=~/workloads/focal.raw \
--cpus boot=1 --memory size=1G \
--cmdline "root=/dev/pmem0p1 console=ttyS0" \
--serial tty --console off \
--api-socket /tmp/api1
--api-socket=/tmp/api1
```
For analysing the samples:
@@ -35,22 +43,3 @@ $ perf report -g
```
If profiling with a network device attached either the TAP device must be already created and configured or the profiling must be done as root so that the TAP device can be created.
## Userspace only profiling with LBR
The use of LBR (Last Branch Record; available since Haswell) offers lower
overhead if only userspace profiling is required. This lower overhead can allow
a higher frequency of sampling. This also removes the requirement to compile
with custom `RUSTFLAGS` however debug symbols should still be included:
e.g.
```
$ perf record --call-graph lbr --all-user --user-callchains -g target/release/cloud-hypervisor \
--kernel ~/src/linux/vmlinux \
--pmem file=~/workloads/focal.raw \
--cpus boot=1 --memory size=1G \
--cmdline "root=/dev/pmem0p1 console=ttyS0" \
--serial tty --console off \
--api-socket /tmp/api1
```

View File

@@ -1,103 +0,0 @@
# Release Documentation
## Abstract
This document provides guidance to users, downstream maintainers and
any other consumers of the Cloud Hypervisor project, this document
describes the release process, release cadence, stability expectations and
related topics.
## Basic Terms
### Stability
For Cloud Hypervisor the following areas are subject to stability guarantees:
- [REST API](api.md#rest-api)
- [Command line options](api.md#command-line-interface)
- [Device Model](device_model.md)
- Device tree, device list, ACPI, Hyper-V enlightenments and any other
features exposed to guest
- KVM compatibility
- Rust edition compatibility
This list is incomplete but this document serves as a best effort guide to stability
across releases.
### Experimental features
Experimental features are under active development and no guarantees are made about their stability.
List of experimental features:
- TDX
- vfio-user
- vDPA
### Security
Security fixes should be included in a new point release.
For security issues an advisory will be published via the GitHub security advisory process along with the release. Watching the project on GitHub will notify you of those issues.
## Releases
### Versioning
The versioning scheme uses `MAJOR.POINT` pattern:
- `MAJOR` can introduce incompatible changes along with support for new features. Changes to the [API](api.md#rest-api),
[CLI options](api.md#command-line-interface) and [device model](device_model.md)
require a notice at least 2 releases in advance for the actual change to take
place.
- `POINT` contains bug fixes and/or security fixes.
### Major Release Cadence
Cloud Hypervisor is under active development. A new major release is issued approximately
every 6 weeks. Point releases are issued on demand, when important bug fixes are in
the queue. A major release would receive bug fixes for the next two cycles (~12 weeks)
and then be considered EOL.
```
+ - Active release support
E - EOL
2021 2022 2023
| | | | | | | | |
18.0 | | | ++++++++E
19.0 | | | |++++++++E
20.0 | | | | ++++++++E
21.0 | | | | | ++++++++E
22.0 | | | | | +++++++++E
23.0 | | | | | | +++++++++E
```
### Major Release Stability Considerations
Snapshot/restore support is not compatible across `MAJOR` versions.
Live migration support is not compatible across `MAJOR` versions.
### LTS Release Cadence
A regular release is promoted to LTS every 12 months. An LTS release is supported for 18 months. This gives a 6 months window for users to move to the new LTS.
```
+ - Active release support
E - EOL
2022 2023 2024 2025 2026
| | | | | | | | | | | | | | | | |
23.0 | |+++++++++++++++++++++++++++++E
43.0 | | | | | |+++++++++++++++++++++++++++++E
63.0 | | | | | | | | | |+++++++++++++++++++++++++++++E
```
### LTS Stablity Considerations
An LTS release is just a `MAJOR` release for which point releases are made for
longer following the same rules for what can be backported to a `POINT` release.
The focus lays on critical and security bug fixes which are pulled at the
maintainer's discretion.

View File

@@ -64,5 +64,5 @@ it will log every system call issued by the process. It is important to use
`-f` option in order to trace each and every thread belonging to the process.
```
strace --decode-pids=comm -f ./cloud-hypervisor ...
strace -f ./cloud-hypervisor ...
```

View File

@@ -25,14 +25,14 @@ First thing, we must run a Cloud Hypervisor VM:
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
./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
./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
@@ -79,7 +79,7 @@ Or using two different commands from two terminals:
./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
./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
@@ -87,7 +87,7 @@ 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
./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

View File

@@ -1,122 +0,0 @@
# TPM
Tpm in Cloud-Hypervisor is emulated using `swtpm` as the backend. [swtpm](https://github.com/stefanberger/swtpm) is the link to swtpm project.
Current implementation only supports TPM `2.0` version. At the moment only
`CRB Interface` is implemented. This interface is described in
[TCG PC Client Platform TPM Profile Specification for TPM 2.0, Revision 01.05 v4](https://trustedcomputinggroup.org/wp-content/uploads/PC-Client-Specific-Platform-TPM-Profile-for-TPM-2p0-v1p05p_r14_pub.pdf).
## Usage
`--tpm`, an optional argument, can be passed to enable tpm device.
This argument takes an UNIX domain Socket as a `socket` value.
_Example_
An Example invocation with `--tpm` argument:
```
./cloud-hypervisor/target/release/cloud-hypervisor \
--kernel ./hypervisor-fw \
--disk path=focal-server-cloudimg-amd64.raw \
--cpus boot=4 \
--memory size=1024M \
--net "tap=,mac=,ip=,mask=" \
--tpm socket="/var/run/swtpm.socket"
```
## swtpm
Before invoking cloud-hypervisor with `--tpm` argument, a `swtpm`
process should be started to listen at the input socket. Below is an
example invocation of swtpm process.
```
swtpm socket --tpmstate dir=/var/run/swtpm \
--ctrl type=unixio,path="/var/run/swtpm.socket" \
--flags startup-clear \
--tpm2
```
## Guest
After starting a guest with the above commands, ensure below listed modules are
loaded in the guest:
```
# lsmod | grep tpm
tpm_crb 20480 0
tpm 81920 1 tpm_crb
```
Below is the IO Memory map configured in the guest:
```
# cat /proc/iomem | grep MSFT
fed40000-fed40fff : MSFT0101:00
fed40000-fed40fff : MSFT0101:00
```
Below are the devices created in the guest:
```
# ls /dev/tpm*
/dev/tpm0 /dev/tpmrm0
```
## Testing
Inside the guest install `tpm2-tools` package. This package provides some
commands to run against TPM that supports 2.0 version.
_Examples_
```
// Run Self Test
# tpm2_selftest -f
# echo $?
0
# echo "hello" > input.txt
// this command generates hash of the input file using all the algos supported by TPM
# tpm2_pcrevent input.txt
sha1: f572d396fae9206628714fb2ce00f72e94f2258f
sha256: 5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03
sha384: 1d0f284efe3edea4b9ca3bd514fa134b17eae361ccc7a1eefeff801b9bd6604e01f21f6bf249ef030599f0c
218f2ba8c
sha512: e7c22b994c59d9cf2b48e549b1e24666636045930d3da7c1acb299d1c3b7f931f94aae41edda2c2b207a36e
10f8bcb8d45223e54878f5b316e7ce3b6bc019629
// verify one of the hashes
# sha256sum input.txt
5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03 input.txt
```
### Bundled Functional Test
Build time dependencies for `tpm2-tss` are captured in [INSTALL](https://github.com/tpm2-software/tpm2-tss/blob/master/INSTALL.md).
```
# git clone https://github.com/tpm2-software/tpm2-tss.git
# cd tpm2-tss
# ./configure --enable-integration --with-devicetests="mandatory,optional" --with-device=/dev/tpm0
# sudo make check-device
.
.
.
.
============================================================================
Testsuite summary for tpm2-tss 3.2.0-74-ge03617d9
============================================================================
# TOTAL: 154
# PASS: 88
# SKIP: 7
# XFAIL: 0
# FAIL: 59
# XPASS: 0
# ERROR: 0
============================================================================
See ./test-suite.log
Please report to https://github.com/tpm2-software/tpm2-tss/issues
============================================================================
```
The same set of failures are noticed while running these tests on `Qemu` with
its TPM implementation.

View File

@@ -1,42 +0,0 @@
# Tracing
Cloud Hypervisor has a basic tracing infrastructure, particularly focussed on
the tracing of the initial VM setup.
## Usage
To enabling tracing, build with "tracing" feature. When compiled without the
feature the tracing is compiled out.
```bash
cargo build --features "tracing"
```
And then run Cloud Hypervisor as you wish, the trace will be written to the current directory as `cloud-hypervisor-<pid>.trace`. This is JSON file which you can inspect yourself.
Alternatively you can use the provided script in
`scripts/ch-trace-visualiser.py` to generate an SVG:
For example:
```bash
scripts/ch-trace-visualiser.py cloud-hypervisor-39466.trace output.svg
```
## Tracing in the codebase
There are existing tracepoints in the code base; extra ones can be added for
more detailed tracing.
The `tracer::trace_scoped!()` macro is used to add the current existing scope
appears as a block in the trace. Other than providing a useful name for the
event nothing else is required from the developer.
The `tracer::start()` and `tracer::end()` functions are already in place for
generating traces of the boot. These can be relocated for focus tracing on a
narrow part of the code base.
A `tracer::trace_point!()` macro is also provided for an instantaneous trace
point however this is not in use in the code base currently nor is handled by
the visualisation script due to the difficulty in representation in the SVG.

View File

@@ -2,19 +2,17 @@
Cloud Hypervisor supports UEFI boot through the utilization of the EDK II based UEFI firmware.
## Building UEFI Firmware for x86-64
## Building UEFI Firmware
To avoid any unnecessary issues, it is recommended to use Ubuntu 18.04 and its default toolset. Any other compatible Linux distribution is otherwise suitable, however it is suggested to use a temporary Docker container with Ubuntu 18.04 for a quick build on an existing Linux machine.
Please note that nasm-2.15 is required for the build to succeed.
The commands below will compile an OVMF firmware suitable for Cloud Hypervisor.
```shell
sudo apt-get update
sudo apt-get install uuid-dev nasm iasl build-essential python3-distutils git
git clone https://github.com/tianocore/edk2
git clone https://github.com/cloud-hypervisor/edk2 -b ch
cd edk2
. edksetup.sh
git submodule update --init
@@ -29,40 +27,11 @@ build
After the successful build, the resulting firmware binaries are available under `Build/CloudHvX64/DEBUG_GCC5/FV` underneath the edk2 checkout.
## Building UEFI Firmware for AArch64
```shell
# On an AArch64 machine:
$ sudo apt-get update
$ sudo apt-get install uuid-dev nasm iasl build-essential python3-distutils git
$ git clone --depth 1 https://github.com/tianocore/edk2.git -b master
$ cd edk2
$ git submodule update --init
$ cd ..
$ git clone --depth 1 https://github.com/tianocore/edk2-platforms.git -b master
$ git clone --depth 1 https://github.com/acpica/acpica.git -b master
# Build tools
$ export PACKAGES_PATH="$PWD/edk2:$PWD/edk2-platforms"
$ export IASL_PREFIX="$PWD/acpica/generate/unix/bin/"
$ make -C acpica
$ cd edk2/
$ . edksetup.sh
$ cd ..
$ make -C edk2/BaseTools
# Build EDK2
$ build -a AARCH64 -t GCC5 -p ArmVirtPkg/ArmVirtCloudHv.dsc -b RELEASE
```
If the build goes well, the EDK2 binary is available at
`edk2/Build/ArmVirtCloudHv-AARCH64/RELEASE_GCC5/FV/CLOUDHV_EFI.fd`.
## Using OVMF Binaries
Any UEFI capable image can be booted using the Cloud Hypervisor specific firmware. Windows guests under Cloud Hypervisor only support UEFI boot, therefore OVMF is mandatory there.
To make Cloud Hypervisor use UEFI boot, pass the `CLOUDHV.fd` (for x86-64) / `CLOUDHV_EFI.fd` (for AArch64) file path as an argument to the `--kernel` option. The firmware file will be opened in read only mode.
To make Cloud Hypervisor use UEFI boot, pass the `CLOUDHV.fd` file path as an argument to the `--kernel` option. The firmware file will be opened in read only mode.
# Links

View File

@@ -94,7 +94,7 @@ VMs run in client mode. They connect to the socket created by the `dpdkvhostuser
--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 --disk vhost_user=true,socket=/var/tmp/vhost.1,num_queues=4,queue_size=128 \
--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

View File

@@ -75,7 +75,8 @@ Here is an example how to create a bridge and add two DPDK ports to it
ovs-vsctl add-br ovsbr0 -- set bridge ovsbr0 datapath_type=netdev
# create two DPDK ports and add them to the bridge
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 add-port ovsbr0 vhost-user2 -- set Interface vhost-user2
type=dpdkvhostuser
# set the number of rx queues
ovs-vsctl set Interface vhost-user1 options:n_rxq=2
ovs-vsctl set Interface vhost-user2 options:n_rxq=2

View File

@@ -1,84 +0,0 @@
# VSOCK support
VSOCK provides a way for guest and host to communicate through a socket. VSOCK sockets support both stream and datagram types.
The `virtio-vsock` is based on the [Firecracker](https://github.com/firecracker-microvm/firecracker/blob/main/docs/vsock.md) implementation, where additional details can be found.
## What is a CID?
CID is a 32-bit context identifier describing the source or destination. In combination with the port, the complete addressing can be achieved to describe multiple listeners running on the same machine.
The table below depicts the well known CID values:
| CID | Description |
|-----|-------------|
| -1 | Random CID |
| 0 | Hypervisor |
| 1 | Loopback |
| 2 | Host |
## Prerequisites
### Kernel Requirements
Host kernel: CONFIG_VHOST_VSOCK
Guest kernel: CONFIG_VIRTIO_VSOCKETS
### Nested VM support
Linux __v5.5__ or newer is required for the L1 VM.
### Loopback support
Linux __v5.6__ or newer is required.
## Establishing VSOCK Connection
VSOCK device becomes available with `--vsock` option passed by the VM start. Cloud Hypervisor can be invoked for instance as below:
```bash
cloud-hypervisor \
--cpus boot=1 \
--memory size=4G \
--firmware CLOUDHV.fd \
--disk path=jammy-server-cloudimg.raw \
--vsock cid=3,socket=/tmp/ch.vsock
```
The examples use __socat__ `>=1.7.4` to illustrate the VSOCK functionality. However, there are other tools supporting VSOCK, like [ncat](https://stefano-garzarella.github.io/posts/2019-11-08-kvmforum-2019-vsock/).
### Connecting from Host to Guest
The host starts to listen on the defined port:
`$ socat - VSOCK-LISTEN:1234`
Once the host is listening, the guest can send data:
`echo -e "CONNECT 1234\\nHello from host!" | socat - UNIX-CONNECT:/tmp/ch.vsock
Note the string `CONNECT <port>` prepended to the actual data. It is possible for the guest to start listening on different ports, thus the specific command is needed to instruct VSOCK to which listener the host wants to connect. It needs to be sent once per connection. Once the connection established, data transfers can take place directly.
### Connecting from Guest to Host
This first requires a listening UNIX socket on the host side. The UNIX socket path has to be constructed by using the socket path used at the VM launch time with appended `_` and the port number to be used on the guest side. As in the example above, if we'd intended to connect from the guest to the port `1234`, the Unix socket path on the host side would be `/tmp/ch.vsock_1234`.
Also note that the CID used on the guest side is the well known CID value `2`.
Listening on the host side:
`$ socat - UNIX-LISTEN:/tmp/ch.vsock_1234`
From the guest:
`$ echo -e "Hello from guest!" | socat - VSOCK-CONNECT:2:1234`
## Links
- [virtio-vsock in QEMU, Firecracker and Linux: Status, Performance and Challenges](https://kvmforum2019.sched.com/event/TmwK)
- [Leveraging virtio-vsock in the cloud and containers](https://archive.fosdem.org/2021/schedule/event/vai_virtio_vsock/)
- [VSOCK man page](https://manpages.ubuntu.com/manpages/focal/man7/vsock.7.html)
- [https://stefano-garzarella.github.io/posts/2020-02-20-vsock-nested-vms-loopback/](https://stefano-garzarella.github.io/posts/2020-02-20-vsock-nested-vms-loopback/)
- [https://github.com/firecracker-microvm/firecracker/blob/main/docs/vsock.md](https://github.com/firecracker-microvm/firecracker/blob/main/docs/vsock.md)

View File

@@ -131,11 +131,11 @@ See also the [links](#Links) section for a more extended SAC documentation.
## Network
This section illustrates the Windows specific aspects of the VM network configuration.
This section illustrates the Windows specific corner points for the VM network configuration. For the extended networking guide, including bridging for multiple VMs, follow [networking.md](networking.md).
### Basic Networking
As the simplest option, using `--net tap=` in the Cloud Hypervisor command line will create a `vmtapX` device on the host with the default IPv4 address `192.168.249.1`. After SAC becomes available, the guest configuration can be set with
As the simplest option, using `--net tap=` in the Cloud Hypervisor command line will create a `vmtapX` device on the host with the default IPv4 adress `192.168.249.1`. After SAC becomes available, the guest configuration can be set with
<pre>
SAC>i 10 192.168.249.2 255.255.255.0 192.168.249.1
@@ -203,13 +203,13 @@ The Windows guest debugging process relies heavily on QEMU and [socat](http://ww
The connection between both guests happens over TCP, whereby on the guest side it is automatically translated to a COM port. Because the VMs are connected through TCP, the debugging infrastructure can be distributed over the network. The serial port, while slowly transferring data, is common enough to support a wide range of cases and tools.
In this exercise, [WinDbg](https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/) is used. Any other debugger of choice with the ability to use serial connection can be used instead.
In this excercise, [WinDbg](https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/) is used. Any other debugger of choice with the ability to use serial connection can be used instead.
### Debugger and Debuggee
#### WinDbg VM
For simplicity, the debugger VM is supposed to be only running under QEMU. It will require VGA and doesn't necessarily depend on UEFI. As an OS, it can carry any supported Windows OS where the debugger of choice can be installed. The simplest way is to follow the image preparation instructions from the previous chapter, but avoid using the OVMF firmware. It is also not required to use VirtIO drivers, whereby it might be useful in some case. Though, while creating the image file for the debugger VM, be sure to choose a sufficient disk size that counts in the need to save the corresponding debug symbols and sources.
For simplicity, the debugger VM is supposed to be only running under QEMU. It will require VGA and doesn't neccessarily depend on UEFI. As an OS, it can carry any supported Windows OS where the debugger of choice can be installed. The simplest way is to follow the image preparation instructions from the previous chapter, but avoid using the OVMF firmware. It is also not required to use VirtIO drivers, whereby it might be useful in some case. Though, while creating the image file for the debugger VM, be sure to choose a sufficient disk size that counts in the need to save the corresponding debug symbols and sources.
To create the debugger Windows VM, the following command can be used:
@@ -244,7 +244,7 @@ This will configure the debugging to be enabled and instruct to use the serial p
bcdedit /dbgsettings serial debugport:1 baudrate:115200
```
##### Turn On Kernel Debugging
##### Turn On Kernel Debuging
```cmd
bcdedit /debug on

View File

@@ -5,8 +5,7 @@ authors = ["The Cloud Hypervisor Authors"]
edition = "2021"
[dependencies]
flume = "0.10.14"
libc = "0.2.147"
once_cell = "1.18.0"
serde = { version = "1.0.168", features = ["rc", "derive"] }
serde_json = "1.0.107"
libc = "0.2.123"
serde = { version = "1.0.136", features = ["rc"] }
serde_derive = "1.0.136"
serde_json = "1.0.79"

Some files were not shown because too many files have changed in this diff Show More