Compare commits

..

35 Commits
v34.0 ... v28.3

Author SHA1 Message Date
Bo Chen
11b1ab40a5 build: Release v28.3 (bug fix release)
Signed-off-by: Bo Chen <chen.bo@intel.com>
2023-04-18 18:09:27 -07:00
Rob Bradford
10e77ebd1e build: Bump MSRV to 1.62
Needed for #[derive(Default)] on enums which is now clippy checked in
1.68.

Fixes: #5140

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2023-04-18 16:40:57 -07:00
Rob Bradford
167ae5a78a build: Document the project's MSRV policy
To me the most logical place to document the policy is right next to the
version itself.

Fixes: #4318

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2023-04-18 16:40:57 -07:00
Bo Chen
8736256564 misc: Automatically fix cargo clippy issues added in 1.68 (beta)
Signed-off-by: Bo Chen <chen.bo@intel.com>
2023-04-18 16:40:57 -07:00
Bo Chen
55e08edd7f vmm: Remove unnecessary parentheses (beta 1.69 clippy check)
Signed-off-by: Bo Chen <chen.bo@intel.com>
2023-04-18 16:40:57 -07:00
Bo Chen
54f66aaadc tests: Extend '_test_macvtap()' with reboot
In this way, we can cover the scenario where a VM with hotplugged net
device using FDs can work properly with reboot.

Signed-off-by: Bo Chen <chen.bo@intel.com>
2023-04-18 16:40:57 -07:00
Bo Chen
d656058c21 vmm: Add valid FDs for TAP devices to 'VmConfig::preserved_fds'
In this way, valid FDs for TAP devices will be closed when the holding
VmConfig instance is destroyed.

Signed-off-by: Bo Chen <chen.bo@intel.com>
2023-04-18 16:40:57 -07:00
Bo Chen
3227372fe0 vmm: Add unit test for 'VmConfig::preserved_fds'
Signed-off-by: Bo Chen <chen.bo@intel.com>
2023-04-18 16:40:57 -07:00
Bo Chen
ac3f51002d vmm: Implement Clone and Drop for VmConfig
The custom 'clone' duplicates 'preserved_fds' so that the validation
logic can be safely carried out on the clone of the VmConfig.

The custom 'drop' ensures 'preserved_fds' are safely closed when the
holding VmConfig instance is destroyed.

Signed-off-by: Bo Chen <chen.bo@intel.com>
2023-04-18 16:40:57 -07:00
Bo Chen
0d2e2cd4c3 vmm: config: Extend 'VmConfig' with 'preserved_fds'
Preserved FDs are the ones that share the same life-time as its holding
VmConfig instance, such as FDs for creating TAP devices.

Preserved FDs will stay open as long as the holding VmConfig instance is
valid, and will be closed when the holding VmConfig instance is destroyed.

Signed-off-by: Bo Chen <chen.bo@intel.com>
2023-04-18 16:40:57 -07:00
Alyssa Ross
1fc969d37a vmm: only use KVM_ARM_VCPU_PMU_V3 if available
Having PMU in guests isn't critical, and not all hardware supports
it (e.g. Apple Silicon).

CpuManager::init_pmu already has a fallback for if PMU is not
supported by the VCPU, but we weren't getting that far, because we
would always try to initialise the VCPU with KVM_ARM_VCPU_PMU_V3, and
then bail when it returned with EINVAL.

Signed-off-by: Alyssa Ross <hi@alyssa.is>
2023-04-18 16:40:57 -07:00
dependabot[bot]
27f5016ad3 build: Bump kvm-ioctls from 0.12.0 to 0.13.0
Bumps [kvm-ioctls](https://github.com/rust-vmm/kvm-ioctls) from 0.12.0 to 0.13.0.
- [Release notes](https://github.com/rust-vmm/kvm-ioctls/releases)
- [Changelog](https://github.com/rust-vmm/kvm-ioctls/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-vmm/kvm-ioctls/commits)

---
updated-dependencies:
- dependency-name: kvm-ioctls
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
Signed-off-by: Bo Chen <chen.bo@intel.com>
2023-04-18 16:40:57 -07:00
Alyssa Ross
a0ddcc68d7 virtio-devices: seccomp: add vhost-user syscalls
Cloud Hypervisor's vhost-user implementation will reconnect if it gets
disconnected from the backend.  That means connections happen inside
the vhost-user seccomp sandbox, so all syscalls used in reconnecting
have to be allowed in that sandbox.

clock_nanosleep is used by Glibc, and nanosleep is used by musl.

Signed-off-by: Alyssa Ross <hi@alyssa.is>
2023-04-18 16:40:57 -07:00
Bo Chen
df5aecd144 vmm: Ignore and warn TAP FDs sent via the HTTP request body
Valid FDs can only be sent from another process via `SCM_RIGHTS`.

Signed-off-by: Bo Chen <chen.bo@intel.com>
2023-04-18 16:40:57 -07:00
Hao Xu
862d267302 virtio-devices: Reset offset properly upon unmap for virtio-fs.
We should reset the offset to 0, when asked to remove the whole dax
mapping.

Signed-off-by: Hao Xu <howeyxu@tencent.com>
2023-04-18 16:40:57 -07:00
Yong He
44d9c7fd42 vmm: properly set vcpu state when thread exited
Once error occur, vcpu thread may exit, this should
be critical event for the whole VM, we should fire
exit event and set vcpu state.

If we don't set vcpu state, the shutdown process
will hang at signal_thread, which is waiting the
vcpu state to change.

Signed-off-by: Yong He <alexyonghe@tencent.com>
2023-04-18 16:40:57 -07:00
Kaihang Zhang
12abe2dd2b openapi: Make 'vcpu' and 'host_cpus' required in CpuAffinity
Signed-off-by: Kaihang Zhang <kaihang.zhang@smartx.com>
2023-04-18 16:40:57 -07:00
Rob Bradford
39a81c596f arch, hypervisor: Populate CPUID leaf 0x4000_0010 (TSC frequency)
This hypervisor leaf includes details of the TSC frequency if that is
available from KVM. This can be used to efficiently calculate time
passed when there is an invariant TSC.

TEST=Run `cpuid` in the guest and observe the frequency populated.

Fixes: #5178

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2023-04-18 16:40:57 -07:00
Bo Chen
3b0d2e796b build: Release v28.2 (bug fix release)
Signed-off-by: Bo Chen <chen.bo@intel.com>
2023-01-25 08:44:46 -08:00
Rob Bradford
42357c01f3 .github: Don't try and create releases for created branches
Dependabot will create a branch on the repo for it's updates this
triggers the release action (because it's the same event as a tag) which
will then fail leading to dependabot PRs not being automerged. Instead
only run the release check test on PRs or tag creation.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
(cherry picked from commit f6c058da56)
2023-01-24 06:04:01 -08:00
Rob Bradford
b7b5b9d7e6 .github: Re-order release steps to ensure binaries are available
Since we run "cargo clean" before running the aarch64 build we need to
create the release and upload the x86-64 assets before the clean.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
(cherry picked from commit 6e9172bf6f)
Signed-off-by: Bo Chen <chen.bo@intel.com>
2023-01-24 06:04:01 -08:00
Rob Bradford
a63e064004 .github: Clean source tree before cross building release assets
This address issues with leaking symbols into the cross build.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
(cherry picked from commit 547230bb77)
Signed-off-by: Bo Chen <chen.bo@intel.com>
2023-01-24 06:04:01 -08:00
Rob Bradford
62c1f39ea2 .github: Run release style builds on all PRs
Adjust the release workflow to move the conditional check on the tag
creation into the steps that create the release/upload the assets.

This allows us to ensure we're always in a releaseable state.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
(cherry picked from commit ef7e177df2)
Signed-off-by: Bo Chen <chen.bo@intel.com>
2023-01-24 06:04:01 -08:00
Bo Chen
460ba718d4 github,Cargo.toml: Strip release binaries using toolchain
From Rust 1.59, the cargo command is now able to strip a binary [1].
This can be enabled in Cargo.toml by adding a `strip = "true"` to
the `[profile.release]` section.

Adding such binary stripping support in Cargo.toml of the project,
also change the stripping process in the release workflow to the one
using toolchain, so that the AArch64 release binaries can also
be stripped.

Fixes: https://github.com/cloud-hypervisor/cloud-hypervisor/issues/4916

[1] https://doc.rust-lang.org/beta/cargo/reference/profiles.html#strip

Signed-off-by: Henry Wang <Henry.Wang@arm.com>
(cherry picked from commit 1ff0191b30)
Signed-off-by: Bo Chen <chen.bo@intel.com>
2023-01-24 06:04:01 -08:00
Bo Chen
c91a8e1324 misc: Automatically fix cargo clippy issues added in 1.65 (stable)
The code of the stable branch diverges from the main branch, so we
can't directly backport the corresponding commit to fix the clippy
issues.

See: commit 5e52729453

Signed-off-by: Bo Chen <chen.bo@intel.com>
2023-01-19 09:12:38 -08:00
Philipp Schuster
1adfb7e9f8 virtio-devices: properly join all threads on Drop
This change is important to do a proper resource cleanup. We decided
to do this repetitive approach as VirtioCommon can't implement Drop
without major changes to the corresponding code. Also, devices such as
Net can't easily use the epoll_threads-abstraction from VirtioCommon as
it has multiple threads with different semantics.

Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
(cherry picked from commit ad6c0ee52b)
2023-01-19 09:12:38 -08:00
Muminul Islam
8dd4d42053 vmm: Ensure PIO/MMIO exits complete before pausing only for KVM
MSHV does not require to ensure MMIO/PIO exits complete
before pausing. This patch makes sure the above requirement
by checking the hypervisor type run-time.

Fixes #5037

Signed-off-by: Muminul Islam <muislam@microsoft.com>
(cherry picked from commit 4e3bc20f2c)
2023-01-19 09:12:38 -08:00
Sebastien Boeuf
3834b43878 qcow: Fix number of refcount table entries
The number of entries in the refcount table was incorrectly calculated
given there was no need for dividing the number of refblock clusters.
The number of refblock clusters is the number of entries in the refcount
table.

Suggested-by: lv_mz <lv.mengzhao@zte.com.cn>
Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
(cherry picked from commit 0e7d5d2761)
2023-01-19 09:12:38 -08:00
Bo Chen
52605cc0e4 build: Release v28.1 (bug fix release)
Signed-off-by: Bo Chen <chen.bo@intel.com>
2022-12-13 13:02:57 -08:00
Rob Bradford
92beda1e32 README: Use consistent path to cloud-hypervisor binary
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
(cherry picked from commit 00becda899)
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2022-12-13 15:12:40 +00:00
Bo Chen
167fef382a net_util: queue_pair: Avoid integer overflow
This integer overflow was triggered with fuzzing on the virtio-net
device. The integer overflow is from the wrong assumption that the
packets read from or written to the tap device is always larger than the
size of a virtio-net header.

Signed-off-by: Bo Chen <chen.bo@intel.com>
(cherry picked from commit 559faa272a)
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2022-12-13 15:12:40 +00:00
Yuji Hagiwara
5126e9b26e docs: Fix a typo on the doc for tpm
swtpm accepts --tpmstate option

Signed-off-by: Yuji Hagiwara <yuuzi41@gmail.com>
(cherry picked from commit 47a7ebe434)
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2022-12-13 15:12:40 +00:00
Bo Chen
59bd682b1f net_util: queue_pair: Avoid panic and handle error properly
This panic was triggered with fuzzing on the virtio-net device. This
commits handles the error explicitly to avoid the panic, which also
makes the fuzzer happy (as panic is treated as bugs).

Signed-off-by: Bo Chen <chen.bo@intel.com>
(cherry picked from commit 4d9a2b17a7)
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2022-12-13 15:12:40 +00:00
Rob Bradford
8e3b351038 arch: x86_64: Use host cpuid information for L2 cache for older KVM
If the KVM version is too old (pre Linux 5.7) then fetch the CPUID
information from the host and use that in the guest. We prefer the KVM
version over the host version as that would use the CPUID for the
running CPU vs the CPU that runs this code which might be different due
to a hybrid topology.

Fixes: #4918

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
(cherry picked from commit 7c3110e6d5)
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2022-12-13 15:12:40 +00:00
Rob Bradford
3f8d06b47e build: Update dependencies in v28.x stable branch
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2022-12-13 14:29:22 +00:00
240 changed files with 10588 additions and 13985 deletions

View File

@@ -13,7 +13,7 @@ jobs:
- stable
- beta
- nightly
- "1.64"
- "1.62"
target:
- x86_64-unknown-linux-gnu
- x86_64-unknown-linux-musl
@@ -34,25 +34,22 @@ jobs:
override: true
- name: Build (default features)
run: cargo rustc --locked --bin cloud-hypervisor -- -D warnings -D clippy::undocumented_unsafe_blocks
run: cargo rustc --locked --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
run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "kvm" -- -D warnings
- name: Build (default features + tdx)
run: cargo rustc --locked --bin cloud-hypervisor --features "tdx" -- -D warnings -D clippy::undocumented_unsafe_blocks
- name: Build (default features + dbus_api)
run: cargo rustc --locked --bin cloud-hypervisor --features "dbus_api" -- -D warnings -D clippy::undocumented_unsafe_blocks
run: cargo rustc --locked --bin cloud-hypervisor --features "tdx" -- -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
run: cargo rustc --locked --bin cloud-hypervisor --features "guest_debug" -- -D warnings
- name: Build (mshv)
run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "mshv" -- -D warnings -D clippy::undocumented_unsafe_blocks
run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "mshv" -- -D warnings
- name: Build (mshv + kvm)
run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "mshv,kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks
run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "mshv,kvm" -- -D warnings
- name: Release Build (default features)
run: cargo build --locked --all --release --target=${{ matrix.target }}

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

View File

@@ -13,24 +13,15 @@ jobs:
rust:
- stable
target:
- aarch64-unknown-linux-gnu
- aarch64-unknown-linux-musl
- x86_64-unknown-linux-gnu
- x86_64-unknown-linux-musl
- aarch64-unknown-linux-gnu
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
target: aarch64-unknown-linux-gnu
experimental: true
steps:
- name: Code checkout
@@ -51,7 +42,7 @@ jobs:
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
for commit in $commits; do git checkout $commit; cargo check --tests --all --target=${{ matrix.target }}; done
git checkout ${{ github.sha }}
- name: Formatting (rustfmt)
@@ -62,28 +53,28 @@ jobs:
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
args: --locked --all --all-targets --no-default-features --tests --features "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
args: --locked --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
args: --locked --all --all-targets --tests --features "guest_debug" -- -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
args: --locked --all --all-targets --tests --features "tracing" -- -D warnings
- name: Clippy (mshv)
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
@@ -91,7 +82,7 @@ jobs:
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
args: --locked --all --all-targets --no-default-features --tests --features "mshv" -- -D warnings
- name: Clippy (mshv + kvm)
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
@@ -99,7 +90,7 @@ jobs:
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
args: --locked --all --all-targets --no-default-features --tests --features "mshv,kvm" -- -D warnings
- name: Clippy (kvm + tdx)
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
@@ -107,7 +98,7 @@ jobs:
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
args: --locked --all --all-targets --no-default-features --tests --features "tdx,kvm" -- -D warnings
- name: Check build did not modify any files
run: test -z "$(git status --porcelain)"

View File

@@ -16,29 +16,29 @@ jobs:
- name: Install Rust toolchain (x86_64-unknown-linux-gnu)
uses: actions-rs/toolchain@v1
with:
toolchain: "1.67.1"
toolchain: "1.62"
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"
toolchain: "1.62"
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.62"
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.62"
command: build
args: --all --release --target=x86_64-unknown-linux-musl
- name: Install Rust toolchain (aarch64-unknown-linux-musl)
uses: actions-rs/toolchain@v1
with:
toolchain: "1.67.1"
toolchain: "1.62"
target: aarch64-unknown-linux-musl
override: true
- name: Create Release

1
.gitignore vendored
View File

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

1664
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[package]
name = "cloud-hypervisor"
version = "34.0.0"
version = "28.3.0"
authors = ["The Cloud Hypervisor Authors"]
edition = "2021"
default-run = "cloud-hypervisor"
@@ -12,10 +12,10 @@ homepage = "https://github.com/cloud-hypervisor/cloud-hypervisor"
# 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,
# 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.62"
[profile.release]
lto = true
@@ -23,54 +23,46 @@ codegen-units = 1
opt-level = "s"
strip = true
[profile.profiling]
inherits = "release"
strip = false
debug = true
[dependencies]
anyhow = "1.0.71"
anyhow = "1.0.66"
api_client = { path = "api_client" }
argh = "0.1.9"
dhat = { version = "0.3.2", optional = true }
epoll = "4.3.3"
clap = { version = "4.0.29", features = ["wrap_help","cargo","string"] }
epoll = "4.3.1"
event_monitor = { path = "event_monitor" }
hypervisor = { path = "hypervisor" }
libc = "0.2.139"
libc = "0.2.138"
log = { version = "0.4.17", features = ["std"] }
option_parser = { path = "option_parser" }
seccompiler = "0.3.0"
serde_json = "1.0.96"
signal-hook = "0.3.17"
thiserror = "1.0.40"
serde_json = "1.0.89"
signal-hook = "0.3.14"
thiserror = "1.0.37"
tpm = { path = "tpm"}
tracer = { path = "tracer" }
vmm = { path = "vmm" }
vmm-sys-util = "0.11.0"
vm-memory = "0.11.0"
zbus = { version = "3.11.1", optional = true }
vm-memory = "0.10.0"
[build-dependencies]
clap = { version = "4.0.29", 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-ioctls = { git = "https://github.com/rust-vmm/kvm-ioctls", branch = "main" }
versionize_derive = { git = "https://github.com/cloud-hypervisor/versionize_derive", branch = "ch" }
vhost = { git = "https://github.com/rust-vmm/vhost", branch = "main" }
[dev-dependencies]
dirs = "5.0.0"
dirs = "4.0.0"
net_util = { path = "net_util" }
once_cell = "1.18.0"
serde_json = "1.0.96"
once_cell = "1.16.0"
serde_json = "1.0.89"
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
default = ["kvm"]
guest_debug = ["vmm/guest_debug"]
io_uring = ["vmm/io_uring"]
kvm = ["vmm/kvm"]
mshv = ["vmm/mshv"]
tdx = ["vmm/tdx"]
@@ -78,9 +70,10 @@ tracing = ["vmm/tracing", "tracer/tracing"]
[workspace]
members = [
"acpi_tables",
"api_client",
"arch",
"block",
"block_util",
"devices",
"event_monitor",
"hypervisor",
@@ -89,10 +82,13 @@ 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",

281
Jenkinsfile vendored
View File

@@ -1,9 +1,6 @@
def runWorkers = true
pipeline {
agent none
options {
timeout(time: 4, unit: 'HOURS')
}
stages {
stage('Early checks') {
agent { node { label 'built-in' } }
@@ -13,16 +10,29 @@ pipeline {
checkout scm
}
}
stage('Check if worker build can be skipped') {
stage('Check for documentation only changes') {
when {
expression {
return skipWorkerBuild()
return docsFileOnly()
}
}
steps {
script {
runWorkers = false
echo 'No changes requring a build'
echo 'Documentation only changes, no need to run the CI'
}
}
}
stage('Check for fuzzer files only changes') {
when {
expression {
return fuzzFileOnly()
}
}
steps {
script {
runWorkers = false
echo 'Fuzzer cargo files only changes, no need to run the CI'
}
}
}
@@ -117,63 +127,6 @@ pipeline {
}
}
}
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'
}
}
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 --integration -- -- --skip live_migration::live_migration_parallel::test_live_upgrade_watchdog --skip live_migration::live_migration_parallel::test_live_upgrade_watchdog_local'
}
}
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 -- -- --skip live_migration::live_migration_parallel::test_live_upgrade_watchdog --skip live_migration::live_migration_parallel::test_live_upgrade_watchdog_local'
}
}
}
}
stage('AArch64 worker build') {
agent { node { label 'bionic-arm64' } }
when {
@@ -274,7 +227,7 @@ pipeline {
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"'
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') {
@@ -295,146 +248,39 @@ pipeline {
}
}
}
// 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()
// }
// }
// }
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'
}
}
}
}
}
}
}
@@ -479,31 +325,24 @@ def installAzureCli(distro, arch) {
sh 'sudo apt install -y azure-cli'
}
def boolean skipWorkerBuild() {
def boolean docsFileOnly() {
if (env.CHANGE_TARGET == null) {
return false
}
if (sh(
return sh(
returnStatus: true,
script: "git diff --name-only origin/${env.CHANGE_TARGET}... | grep -v '\\.md'"
) != 0) {
return true
) != 0
}
def boolean fuzzFileOnly() {
if (env.CHANGE_TARGET == null) {
return false
}
if (sh(
return 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

112
README.md
View File

@@ -69,18 +69,16 @@ 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.
## Prerequisites for AArch64
- AArch64 servers (recommended) or development boards equipped with the GICv3
interrupt controller.
The following 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
[AArch64 documentation](docs/arm64.md).
## 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.
For required KVM functionality the minimum host kernel version is 4.11. For
adequate performance the minimum recommended host kernel version is 5.6. The
majority of the CI currently tests with kernel version 5.15.
## Use Pre-built Binaries
@@ -107,13 +105,16 @@ 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
The instructions below are for the `x86-64` platform. For `AArch64` please see
the [AArch64 specific documentation](docs/arm64.md).
Cloud Hypervisor supports direct kernel boot (if the kernel is 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`.)
edk2 UEFI firmware called `CLOUDHV`.)
Binary builds of the firmware files are available for the latest release of
[Rust Hypervisor
[Rust Hyperivor
Firmware](https://github.com/cloud-hypervisor/rust-hypervisor-firmware/releases/latest)
and [our edk2
repository](https://github.com/cloud-hypervisor/edk2/releases/latest)
@@ -124,7 +125,7 @@ may be required.
### Firmware Booting
Cloud Hypervisor supports booting disk images containing all needed components
to run cloud workloads, a.k.a. cloud images.
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
@@ -140,17 +141,14 @@ 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.
is only necessary to add this disk image on the first boot.
```shell
$ sudo setcap cap_net_admin+ep ./cloud-hypervisor
$ ./create-cloud-init.sh
$ ./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 path=/tmp/ubuntu-cloudinit.img \
--cpus boot=4 \
--memory size=1024M \
--net "tap=,mac=,ip=,mask="
@@ -163,7 +161,7 @@ GRUB) is required then it necessary to switch to the serial console instead of
```shell
$ ./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 path=/tmp/ubuntu-cloudinit.img \
--cpus boot=4 \
--memory size=1024M \
--net "tap=,mac=,ip=,mask=" \
@@ -175,31 +173,23 @@ $ ./cloud-hypervisor \
#### 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 (compiled with PVH support). In order to support development there is a custom branch; however provided the required options are enabled any recent kernel will suffice.
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
$ 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
# Use the cloud-hypervisor kernel config to build your kernel
$ 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
$ cp 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
@@ -207,10 +197,8 @@ For the disk image the same Ubuntu image as before can be used. This contains
an `ext4` root filesystem.
```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
$ 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
```
#### Booting the guest VM
@@ -218,28 +206,12 @@ $ qemu-img convert -p -f qcow2 -O raw focal-server-cloudimg-arm64.img focal-serv
These sample commands boot the disk image using the custom kernel whilst also
supplying the desired kernel command line.
- x86-64
```shell
$ sudo setcap cap_net_admin+ep ./cloud-hypervisor
$ ./create-cloud-init.sh
$ ./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 \
--cmdline "console=hvc0 root=/dev/vda1 rw" \
--cpus boot=4 \
--memory size=1024M \
--net "tap=,mac=,ip=,mask="
```
- AArch64
```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 \
--disk path=focal-server-cloudimg-amd64.raw path=/tmp/ubuntu-cloudinit.img \
--cmdline "console=hvc0 root=/dev/vda1 rw" \
--cpus boot=4 \
--memory size=1024M \
@@ -248,10 +220,7 @@ $ ./cloud-hypervisor \
If earlier kernel messages are required the serial console should be used instead of `virtio-console`.
- x86-64
```shell
$ ./cloud-hypervisor \
```./cloud-hypervisor \
--kernel ./linux-cloud-hypervisor/arch/x86/boot/compressed/vmlinux.bin \
--console off \
--serial tty \
@@ -262,20 +231,6 @@ $ ./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
@@ -299,11 +254,12 @@ Currently the following items are **not** guaranteed across updates:
Further details can be found in the [release documentation](docs/releases.md).
As of 2023-01-03, the following cloud images are supported:
As of 2022-10-13, 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/) (bionic-server-cloudimg-amd64.img)
- [Ubuntu Focal](https://cloud-images.ubuntu.com/focal/current/) (focal-server-cloudimg-amd64.img)
- [Ubuntu Jammy](https://cloud-images.ubuntu.com/jammy/current/) (jammy-server-cloudimg-amd64.img )
- [Fedora 36](https://fedora.mirrorservice.org/fedora/linux/releases/36/Cloud/x86_64/images/) (Fedora-Cloud-Base-36-1.5.x86_64.raw.xz)
Direct kernel boot to userspace should work with a rootfs from most
distributions although you may need to enable exotic filesystem types in the
@@ -375,8 +331,8 @@ are all equal and welcome means of contribution. See the
## Slack
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).
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/).
## Mailing list

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.10.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);
}
}

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

@@ -0,0 +1,147 @@
// Copyright © 2019 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
#[repr(packed)]
#[derive(Clone, Copy)]
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

@@ -9,18 +9,18 @@ default = []
tdx = []
[dependencies]
anyhow = "1.0.71"
anyhow = "1.0.66"
byteorder = "1.4.3"
hypervisor = { path = "../hypervisor" }
libc = "0.2.139"
linux-loader = { version = "0.9.0", features = ["elf", "bzimage", "pe"] }
libc = "0.2.138"
linux-loader = { version = "0.8.1", features = ["elf", "bzimage", "pe"] }
log = "0.4.17"
serde = { version = "1.0.164", features = ["rc", "derive"] }
thiserror = "1.0.40"
uuid = "1.3.4"
versionize = "0.1.10"
serde = { version = "1.0.150", features = ["rc", "derive"] }
thiserror = "1.0.37"
uuid = "1.2.2"
versionize = "0.1.9"
versionize_derive = "0.1.4"
vm-memory = { version = "0.11.0", features = ["backend-mmap", "backend-bitmap"] }
vm-memory = { version = "0.10.0", features = ["backend-mmap", "backend-bitmap"] }
vm-migration = { path = "../vm-migration" }
vmm-sys-util = { version = "0.11.0", features = ["with-serde"] }

View File

@@ -24,8 +24,6 @@ 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 +40,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;
@@ -87,97 +81,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() {
error!("File: {} 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: CACHE 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() {
error!("File: {} 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: CACHE 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() {
error!("File: {} 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()
}
}
/// 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>(
@@ -256,54 +159,6 @@ fn create_cpu_nodes(
let num_cpus = vcpu_mpidr.len();
// 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;
let cache_path = Path::new("/sys/devices/system/cpu/cpu0/cache");
let cache_exist: bool = cache_path.exists();
if !cache_exist {
error!("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);
}
for (cpu_id, mpidr) in vcpu_mpidr.iter().enumerate().take(num_cpus) {
let cpu_name = format!("cpu@{cpu_id:x}");
let cpu_node = fdt.begin_node(&cpu_name)?;
@@ -318,21 +173,6 @@ fn create_cpu_nodes(
fdt.property_u32("reg", (mpidr & 0x7FFFFF) as u32)?;
fdt.property_u32("phandle", cpu_id as u32 + FIRST_VCPU_PHANDLE)?;
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 {
fdt.property_u32("next-level-cache", L2_CACHE_PHANDLE)?;
}
}
// Add `numa-node-id` property if there is any numa config.
if numa_nodes.len() > 1 {
for numa_node_idx in 0..numa_nodes.len() {
@@ -346,48 +186,14 @@ fn create_cpu_nodes(
fdt.end_node(cpu_node)?;
}
if cache_exist && l2_cache_size != 0 {
let l2_cache_name = "l2-cache0";
let l2_cache_node = fdt.begin_node(l2_cache_name)?;
fdt.property_u32("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 {
fdt.property_u32("next-level-cache", L3_CACHE_PHANDLE)?;
}
fdt.end_node(l2_cache_node)?;
}
if cache_exist && l3_cache_size != 0 {
let l3_cache_name = "l3-cache0";
let l3_cache_node = fdt.begin_node(l3_cache_name)?;
fdt.property_u32("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)?;
}
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{cluster_idx:x}");
let cluster_node = fdt.begin_node(&cluster_name)?;
for core_idx in 0..cores_per_package {
let core_name = format!("core{core_idx:x}");
@@ -396,7 +202,7 @@ fn create_cpu_nodes(
for thread_idx in 0..threads_per_core {
let thread_name = format!("thread{thread_idx:x}");
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)?;
@@ -406,7 +212,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 {
@@ -450,86 +255,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)?;
@@ -549,7 +303,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)?;

View File

@@ -19,9 +19,7 @@ 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 vm_memory::{Address, GuestAddress, GuestMemory, GuestUsize};
/// Errors thrown while configuring aarch64 system.
#[derive(Debug)]
@@ -66,9 +64,9 @@ pub struct EntryPoint {
pub fn configure_vcpu(
vcpu: &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 {
if let Some(kernel_entry_point) = kernel_entry_point {
vcpu.setup_regs(
id,
kernel_entry_point.entry_addr.raw_value(),
@@ -83,8 +81,8 @@ pub fn configure_vcpu(
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 +101,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.
@@ -180,8 +196,11 @@ pub fn initramfs_load_addr(
}
}
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 +215,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,17 +1,18 @@
// Copyright 2022 Arm Limited (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.
///
/// 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.
///
const SYSREG_HEAD: u32 = 0b1101010100u32 << 22;
const SYSREG_OP0_SHIFT: u32 = 19;
const SYSREG_OP0_MASK: u32 = 0b11u32 << 19;

View File

@@ -82,7 +82,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 +92,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 }
}

View File

@@ -16,7 +16,7 @@ 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::{HypervisorCpuError, HypervisorError};
use linux_loader::loader::bootparam::boot_params;
use linux_loader::loader::elf::start_info::{
hvm_memmap_table_entry, hvm_modlist_entry, hvm_start_info,
@@ -39,6 +39,7 @@ 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,8 +53,6 @@ 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
@@ -127,11 +126,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
@@ -209,6 +206,7 @@ impl From<Error> for super::Error {
}
}
#[allow(clippy::upper_case_acronyms)]
#[derive(Copy, Clone, Debug)]
pub enum CpuidReg {
EAX,
@@ -551,30 +549,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}");
let cpuid_patches = vec![
// Patch tsc deadline timer bit
CpuidPatch {
@@ -609,9 +590,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);
@@ -674,7 +653,14 @@ pub fn generate_common_cpuid(
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,7 +679,6 @@ 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,
@@ -758,7 +743,8 @@ pub fn generate_common_cpuid(
pub fn configure_vcpu(
vcpu: &Arc<dyn hypervisor::Vcpu>,
id: u8,
boot_setup: Option<(EntryPoint, &GuestMemoryAtomic<GuestMemoryMmap>)>,
kernel_entry_point: Option<EntryPoint>,
vm_memory: &GuestMemoryAtomic<GuestMemoryMmap>,
cpuid: Vec<CpuIdEntry>,
kvm_hyperv: bool,
) -> super::Result<()> {
@@ -767,13 +753,6 @@ 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)? {
@@ -810,12 +789,12 @@ pub fn configure_vcpu(
}
regs::setup_msrs(vcpu).map_err(Error::MsrsConfiguration)?;
if let Some((kernel_entry_point, guest_memory)) = boot_setup {
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(vcpu, 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_sregs(&vm_memory.memory(), vcpu).map_err(Error::SregsConfiguration)?;
}
}
interrupts::set_lint(vcpu).map_err(|e| Error::LocalIntConfiguration(e.into()))?;
@@ -826,29 +805,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);
let mut regions = Vec::new();
// case1: guest memory fits before the gap
if size <= 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.
@@ -946,102 +943,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(
@@ -1131,8 +1056,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);
@@ -1140,7 +1064,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
@@ -1241,7 +1167,6 @@ 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() {
@@ -1275,8 +1200,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);
@@ -1300,10 +1233,11 @@ mod tests {
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();
@@ -1321,18 +1255,48 @@ mod tests {
)
.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,
None,
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(

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() {

View File

@@ -67,7 +67,7 @@ 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() {
@@ -145,15 +145,11 @@ struct SmbiosEndOfTable {
handle: u16,
}
// SAFETY: data structure only contain a series of integers
// 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>(

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)]
@@ -68,72 +59,7 @@ pub enum TdvfSectionType {
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 GUIDed 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,
));
}
}
}
// 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 +69,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 +102,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,7 +111,7 @@ pub fn parse_tdvf_sections(file: &mut File) -> Result<(Vec<TdvfSection>, bool),
})
.map_err(TdvfError::ReadDescriptor)?;
Ok((sections, guid_found))
Ok(sections)
}
#[repr(u16)]
@@ -278,17 +196,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 +297,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,7 +430,7 @@ 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?}")
}

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.3"
io-uring = { version = "0.6.0", optional = true }
libc = "0.2.139"
log = "0.4.17"
remain = "0.2.11"
smallvec = "1.10.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.8.0"
vm-memory = { version = "0.11.0", features = ["backend-mmap", "backend-atomic", "backend-bitmap"] }
vm-virtio = { path = "../vm-virtio" }
vmm-sys-util = "0.11.0"

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.9"
libc = "0.2.138"
log = "0.4.17"
qcow = { path = "../qcow" }
thiserror = "1.0.37"
versionize = "0.1.9"
versionize_derive = "0.1.4"
vhdx = { path = "../vhdx" }
virtio-bindings = { version = "0.1.0", features = ["virtio-v5_0_0"] }
virtio-queue = "0.7.0"
vm-memory = { version = "0.10.0", features = ["backend-mmap", "backend-atomic", "backend-bitmap"] }
vm-virtio = { path = "../vm-virtio" }
vmm-sys-util = "0.11.0"

View File

@@ -3,6 +3,7 @@
// 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;
@@ -53,21 +54,19 @@ enum BlockSize {
impl DiskTopology {
fn is_block_device(f: &mut 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
#[allow(clippy::useless_conversion)]
fn query_block_size(f: &mut 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(),
@@ -76,7 +75,9 @@ impl DiskTopology {
BlockSize::PhysicalBlock => BLKPBSZGET(),
BlockSize::MinimumIo => BLKIOMIN(),
BlockSize::OptimalIo => BLKIOOPT(),
} as _,
}
.try_into()
.unwrap(),
&mut block_size,
)
};
@@ -131,15 +132,15 @@ pub trait AsyncIo: Send {
fn read_vectored(
&mut self,
offset: libc::off_t,
iovecs: &[libc::iovec],
iovecs: Vec<libc::iovec>,
user_data: u64,
) -> AsyncIoResult<()>;
fn write_vectored(
&mut self,
offset: libc::off_t,
iovecs: &[libc::iovec],
iovecs: Vec<libc::iovec>,
user_data: u64,
) -> AsyncIoResult<()>;
fn fsync(&mut self, user_data: Option<u64>) -> AsyncIoResult<()>;
fn next_completed_request(&mut self) -> Option<(u64, i32)>;
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,35 +12,19 @@
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 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;
@@ -48,11 +32,10 @@ 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,
@@ -80,22 +63,12 @@ pub enum Error {
DescriptorChainTooShort,
#[error("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")]
GetFileMetadata,
#[error("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")]
TooManyDescriptors,
#[error("Failure in vhdx: {0}")]
VhdxError(VhdxError),
}
fn build_device_id(disk_path: &Path) -> result::Result<String, Error> {
@@ -222,11 +195,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 {
@@ -254,11 +226,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;
@@ -278,7 +249,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);
@@ -383,8 +353,7 @@ impl Request {
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;
@@ -412,7 +381,7 @@ impl Request {
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(
@@ -423,7 +392,7 @@ 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 *const u8, aligned_ptr, *data_len as usize)
@@ -461,12 +430,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 => {
@@ -498,7 +467,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(
@@ -510,7 +479,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(
@@ -559,67 +528,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_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
}
pub trait AsyncAdaptor<F>
@@ -629,15 +588,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) }));
}
@@ -653,7 +611,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(())
@@ -662,15 +620,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) }));
}
@@ -686,7 +643,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(())
@@ -696,7 +653,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();
@@ -708,7 +665,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();
}
@@ -753,26 +710,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>,
})
}

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};
@@ -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

@@ -76,17 +76,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),
@@ -104,17 +104,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),
@@ -133,7 +133,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))
@@ -148,17 +148,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

@@ -5,7 +5,6 @@
use crate::async_io::{
AsyncIo, AsyncIoError, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult, DiskTopology,
};
use std::collections::VecDeque;
use std::fs::File;
use std::io::{Seek, SeekFrom};
use std::os::unix::io::{AsRawFd, RawFd};
@@ -45,7 +44,7 @@ impl DiskFile for RawFileDiskSync {
pub struct RawFileSync {
fd: RawFd,
eventfd: EventFd,
completion_list: VecDeque<(u64, i32)>,
completion_list: Vec<(u64, i32)>,
}
impl RawFileSync {
@@ -53,7 +52,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(),
}
}
}
@@ -66,14 +65,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,
)
@@ -82,7 +80,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(())
@@ -91,14 +89,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,
)
@@ -107,28 +104,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));
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

@@ -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,10 +3,13 @@
// 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 git_out.status.success() {
@@ -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,23 +5,21 @@ authors = ["The Chromium OS Authors"]
edition = "2021"
[dependencies]
acpi_tables = { git = "https://github.com/rust-vmm/acpi_tables", branch = "main" }
anyhow = "1.0.71"
acpi_tables = { path = "../acpi_tables" }
anyhow = "1.0.66"
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.139"
libc = "0.2.138"
log = "0.4.17"
pci = { path = "../pci" }
thiserror = "1.0.40"
phf = { version = "0.11.1", features = ["macros"] }
thiserror = "1.0.37"
tpm = { path = "../tpm" }
versionize = "0.1.10"
versionize = "0.1.9"
versionize_derive = "0.1.4"
vm-allocator = { path = "../vm-allocator" }
vm-device = { path = "../vm-device" }
vm-memory = "0.11.0"
vm-memory = "0.10.0"
vm-migration = { path = "../vm-migration" }
vmm-sys-util = "0.11.0"

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,11 +103,11 @@ 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(),
vec![
&aml::Name::new("_HID".into(), &aml::EISAName::new("PNP0A06")),
&aml::Name::new("_HID".into(), &aml::EisaName::new("PNP0A06")),
&aml::Name::new("_UID".into(), &"Generic Event Controller"),
&aml::Name::new(
"_CRS".into(),
@@ -138,19 +116,17 @@ impl Aml for AcpiGedDevice {
true,
self.address.0,
self.address.0 + GED_DEVICE_ACPI_SIZE as u64 - 1,
None,
)]),
),
&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)],
),
@@ -187,7 +163,7 @@ impl Aml for AcpiGedDevice {
),
],
)
.to_aml_bytes(sink);
.append_aml_bytes(bytes);
aml::Device::new(
"_SB_.GED_".into(),
vec![
@@ -211,7 +187,7 @@ impl Aml for AcpiGedDevice {
),
],
)
.to_aml_bytes(sink)
.append_aml_bytes(bytes)
}
}

View File

@@ -8,7 +8,7 @@ use anyhow::anyhow;
use arch::layout;
use hypervisor::{
arch::aarch64::gic::{Vgic, VgicConfig},
CpuState, GicState,
CpuState,
};
use std::result;
use std::sync::{Arc, Mutex};
@@ -25,7 +25,6 @@ type Result<T> = result::Result<T, Error>;
// Reserve 32 IRQs for legacy devices.
pub const IRQ_LEGACY_BASE: usize = layout::IRQ_BASE as usize;
pub const IRQ_LEGACY_COUNT: usize = 32;
pub const GIC_SNAPSHOT_ID: &str = "gic-v3-its";
// Gic (Generic Interupt Controller) struct provides all the functionality of a
// GIC device. It wraps a hypervisor-emulated GIC device (Vgic) provided by the
@@ -40,9 +39,8 @@ pub struct Gic {
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 +49,45 @@ 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)
vgic: None,
})
}
pub fn restore_vgic(
/// 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 create_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)
vm: &Arc<dyn hypervisor::Vm>,
config: VgicConfig,
) -> Result<Arc<Mutex<dyn Vgic>>> {
let vgic = vm.create_vgic(config).map_err(Error::CreateGic)?;
self.vgic = Some(vgic.clone());
Ok(vgic.clone())
}
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 {
fn enable(&self) -> Result<()> {
// Set irqfd for legacy interrupts
self.interrupt_source_group
@@ -98,44 +107,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<()> {
@@ -151,15 +128,27 @@ impl InterruptController for Gic {
}
}
pub const GIC_V3_ITS_SNAPSHOT_ID: &str = "gic-v3-its";
impl Snapshottable for Gic {
fn id(&self) -> String {
GIC_SNAPSHOT_ID.to_string()
GIC_V3_ITS_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)
Snapshot::new_from_state(&self.id(), &state)
}
fn restore(&mut self, snapshot: Snapshot) -> std::result::Result<(), MigratableError> {
let vgic = self.vgic.as_ref().unwrap().clone();
vgic.lock()
.unwrap()
.set_state(&snapshot.to_state(&self.id())?)
.map_err(|e| {
MigratableError::Restore(anyhow!("Could not restore GICv3ITS state {:?}", e))
})?;
Ok(())
}
}

View File

@@ -24,12 +24,8 @@ 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 +55,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

@@ -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

@@ -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>) {
@@ -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

@@ -122,79 +122,24 @@ impl Pl011 {
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,
@@ -225,6 +170,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);
@@ -454,7 +417,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 +453,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())
}
@@ -534,13 +498,12 @@ mod tests {
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']
@@ -556,7 +519,6 @@ mod tests {
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 +529,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,9 @@ 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 +33,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 +49,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;
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),
}
#[allow(dead_code)]
#[derive(Copy, Clone)]
pub 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, None)
.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

@@ -8,11 +8,13 @@ use anyhow::anyhow;
use arch::aarch64::layout::{TPM_SIZE, TPM_START};
#[cfg(target_arch = "x86_64")]
use arch::x86_64::layout::{TPM_SIZE, TPM_START};
use phf::phf_map;
use std::cmp;
use std::sync::{Arc, Barrier};
use thiserror::Error;
use tpm::emulator::{BackendCmd, Emulator};
use tpm::TPM_CRB_BUFFER_MAX;
use tpm::TPM_SUCCESS;
use vm_device::BusDevice;
#[derive(Error, Debug)]
@@ -21,135 +23,62 @@ pub enum Error {
CheckCaps(#[source] anyhow::Error),
#[error("Failed to initialize tpm: {0}")]
Init(#[source] anyhow::Error),
#[error("Failed to deliver tpm Command: {0}")]
DeliverRequest(#[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
// Field => (start, length)
// start: 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_STATE_FIELDS: phf::Map<&str, [u32; 2]> = phf_map! {
"tpmEstablished" => [0, 1],
"locAssigned" => [1,1],
"activeLocality"=> [2, 3],
"reserved" => [5, 2],
"tpmRegValidSts" => [7, 1]
};
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_LOC_STS_FIELDS: phf::Map<&str, [u32; 2]> = phf_map! {
"Granted" => [0, 1],
"beenSeized" => [1,1]
};
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_ID_FIELDS: phf::Map<&str, [u32; 2]> = phf_map! {
"InterfaceType" => [0, 4],
"InterfaceVersion" => [4, 4],
"CapLocality" => [8, 1],
"CapCRBIdleBypass" => [9, 1],
"Reserved1" => [10, 1],
"CapDataXferSizeSupport" => [11, 2],
"CapFIFO" => [13, 1],
"CapCRB" => [14, 1],
"CapIFRes" => [15, 2],
"InterfaceSelector" => [17, 2],
"IntfSelLock" => [19, 1],
"Reserved2" => [20, 4],
"RID" => [24, 8]
};
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_INTF_ID2_FIELDS: phf::Map<&str, [u32; 2]> = phf_map! {
"VID" => [0, 16],
"DID" => [16, 16]
};
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_STS_FIELDS: phf::Map<&str, [u32; 2]> = phf_map! {
"tpmSts" => [0, 1],
"tpmIdle" => [1, 1]
};
const CRB_CTRL_CANCEL: u32 = 0x48;
const CRB_CANCEL_INVOKE: u32 = 1 << 0;
const CRB_CTRL_START: u32 = 0x4C;
@@ -165,7 +94,7 @@ 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;
const TPM_CRB_R_MAX: u32 = CRB_DATA_BUFFER;
// CRB Protocol details
const CRB_INTF_TYPE_CRB_ACTIVE: u32 = 0b1;
@@ -180,29 +109,47 @@ 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) {
fn get_fields_map(reg: u32) -> phf::Map<&'static str, [u32; 2]> {
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),
CRB_LOC_STATE => CRB_LOC_STATE_FIELDS,
CRB_LOC_STS => CRB_LOC_STS_FIELDS,
CRB_INTF_ID => CRB_INTF_ID_FIELDS,
CRB_INTF_ID2 => CRB_INTF_ID2_FIELDS,
CRB_CTRL_STS => CRB_CTRL_STS_FIELDS,
_ => {
panic!("Fields in '{reg:?}' register were accessed which are Invalid");
}
}
}
// 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);
/// Set a particular field in a Register
fn set_reg_field(regs: &mut [u32; TPM_CRB_R_MAX as usize], reg: u32, field: &str, value: u32) {
let reg_fields = get_fields_map(reg);
if reg_fields.contains_key(field) {
let start = reg_fields.get(field).unwrap()[0];
let len = reg_fields.get(field).unwrap()[1];
let mask = (!(0_u32) >> (32 - len)) << start;
regs[reg as usize] = (regs[reg as usize] & !mask) | ((value << start) & mask);
} else {
error!(
"Failed to tpm Register. {:?} is not a valid field in Reg {:#X}",
field, reg
)
}
}
// 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
/// Get the value of a particular field in a Register
fn get_reg_field(regs: &[u32; TPM_CRB_R_MAX as usize], reg: u32, field: &str) -> u32 {
let reg_fields = get_fields_map(reg);
if reg_fields.contains_key(field) {
let start = reg_fields.get(field).unwrap()[0];
let len = reg_fields.get(field).unwrap()[1];
let mask = (!(0_u32) >> (32 - len)) << start;
(regs[reg as usize] & mask) >> start
} else {
// TODO: Sensible return value if fields do not exist
0x0
}
}
fn locality_from_addr(addr: u32) -> u8 {
@@ -211,7 +158,8 @@ fn locality_from_addr(addr: u32) -> u8 {
pub struct Tpm {
emulator: Emulator,
regs: [u32; TPM_CRB_R_MAX],
cmd: Option<BackendCmd>,
regs: [u32; TPM_CRB_R_MAX as usize],
backend_buff_size: usize,
data_buff: [u8; TPM_CRB_BUFFER_MAX],
data_buff_len: usize,
@@ -223,7 +171,8 @@ impl Tpm {
.map_err(|e| Error::Init(anyhow!("Failed while initializing tpm Emulator: {:?}", e)))?;
let mut tpm = Tpm {
emulator,
regs: [0; TPM_CRB_R_MAX],
cmd: None,
regs: [0; TPM_CRB_R_MAX as usize],
backend_buff_size: TPM_CRB_BUFFER_MAX,
data_buff: [0; TPM_CRB_BUFFER_MAX],
data_buff_len: 0,
@@ -233,93 +182,74 @@ impl Tpm {
}
fn get_active_locality(&mut self) -> u32 {
if get_reg_field(
&self.regs,
CrbRegister::LocState(LocStateFields::LocAssigned),
) == 0
{
if get_reg_field(&self.regs, CRB_LOC_STATE, "locAssigned") == 0 {
return TPM_CRB_NO_LOCALITY;
}
get_reg_field(
&self.regs,
CrbRegister::LocState(LocStateFields::ActiveLocality),
)
get_reg_field(&self.regs, CRB_LOC_STATE, "activeLocality")
}
fn request_completed(&mut self, success: bool) {
fn request_completed(&mut self, result: isize) {
self.regs[CRB_CTRL_START as usize] = !CRB_START_INVOKE;
if !success {
set_reg_field(
&mut self.regs,
CrbRegister::CtrlSts(CtrlStsFields::TpmSts),
1,
);
if result != 0 {
set_reg_field(&mut self.regs, CRB_CTRL_STS, "tpmSts", 1);
}
}
fn reset(&mut self) -> Result<()> {
let cur_buff_size = self.emulator.get_buffer_size();
self.regs = [0; TPM_CRB_R_MAX];
let cur_buff_size = self.emulator.get_buffer_size().unwrap();
self.regs = [0; TPM_CRB_R_MAX as usize];
set_reg_field(&mut self.regs, CRB_LOC_STATE, "tpmRegValidSts", 1);
set_reg_field(&mut self.regs, CRB_CTRL_STS, "tpmIdle", 1);
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_ID,
"InterfaceType",
CRB_INTF_TYPE_CRB_ACTIVE,
);
set_reg_field(
&mut self.regs,
CrbRegister::IntfId(IntfIdFields::InterfaceVersion),
CRB_INTF_ID,
"InterfaceVersion",
CRB_INTF_VERSION_CRB,
);
set_reg_field(
&mut self.regs,
CrbRegister::IntfId(IntfIdFields::CapLocality),
CRB_INTF_ID,
"CapLocality",
CRB_INTF_CAP_LOCALITY_0_ONLY,
);
set_reg_field(
&mut self.regs,
CrbRegister::IntfId(IntfIdFields::CapCRBIdleBypass),
CRB_INTF_ID,
"CapCRBIdleBypass",
CRB_INTF_CAP_IDLE_FAST,
);
set_reg_field(
&mut self.regs,
CrbRegister::IntfId(IntfIdFields::CapDataXferSizeSupport),
CRB_INTF_ID,
"CapDataXferSizeSupport",
CRB_INTF_CAP_XFER_SIZE_64,
);
set_reg_field(
&mut self.regs,
CrbRegister::IntfId(IntfIdFields::CapFIFO),
CRB_INTF_ID,
"CapFIFO",
CRB_INTF_CAP_FIFO_NOT_SUPPORTED,
);
set_reg_field(
&mut self.regs,
CrbRegister::IntfId(IntfIdFields::CapCRB),
CRB_INTF_ID,
"CapCRB",
CRB_INTF_CAP_CRB_SUPPORTED,
);
set_reg_field(
&mut self.regs,
CrbRegister::IntfId(IntfIdFields::InterfaceSelector),
CRB_INTF_ID,
"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,
);
set_reg_field(&mut self.regs, CRB_INTF_ID, "RID", 0b0000);
set_reg_field(&mut self.regs, CRB_INTF_ID2, "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;
@@ -338,6 +268,7 @@ impl Tpm {
}
}
//impl BusDevice for TPM
impl BusDevice for Tpm {
fn read(&mut self, _base: u64, offset: u64, data: &mut [u8]) {
let mut offset: u32 = offset as u32;
@@ -436,18 +367,10 @@ impl BusDevice for Tpm {
}
CRB_CTRL_REQ => match v {
CRB_CTRL_REQ_CMD_READY => {
set_reg_field(
&mut self.regs,
CrbRegister::CtrlSts(CtrlStsFields::TpmIdle),
0,
);
set_reg_field(&mut self.regs, CRB_CTRL_STS, "tpmIdle", 0);
}
CRB_CTRL_REQ_GO_IDLE => {
set_reg_field(
&mut self.regs,
CrbRegister::CtrlSts(CtrlStsFields::TpmIdle),
1,
);
set_reg_field(&mut self.regs, CRB_CTRL_STS, "tpmIdle", 1);
}
_ => {
error!("Invalid value passed to CRTL_REQ register");
@@ -470,14 +393,27 @@ impl BusDevice for Tpm {
{
self.regs[CRB_CTRL_START as usize] |= CRB_START_INVOKE;
let mut cmd = BackendCmd {
buffer: &mut self.data_buff,
self.cmd = Some(BackendCmd {
locality: locality as u8,
input: self.data_buff[0..self.data_buff_len].to_vec(),
input_len: cmp::min(self.data_buff_len, TPM_CRB_BUFFER_MAX),
};
output: self.data_buff.to_vec(),
output_len: TPM_CRB_BUFFER_MAX,
selftest_done: false,
});
let status = self.emulator.deliver_request(&mut cmd).is_ok();
let mut cmd = self.cmd.as_ref().unwrap().clone();
let output = self.emulator.deliver_request(&mut cmd).map_err(|e| {
Error::DeliverRequest(anyhow!(
"Failed to deliver tpm request. Error :{:?}",
e
))
});
//TODO: drop the copy here
self.data_buff.fill(0);
self.data_buff.clone_from_slice(output.unwrap().as_slice());
self.request_completed(status);
self.request_completed(TPM_SUCCESS as isize);
}
}
CRB_LOC_CTRL => {
@@ -488,33 +424,13 @@ impl BusDevice for Tpm {
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,
);
set_reg_field(&mut self.regs, CRB_LOC_STATE, "locAssigned", 0);
set_reg_field(&mut self.regs, CRB_LOC_STS, "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,
);
set_reg_field(&mut self.regs, CRB_LOC_STS, "Granted", 1);
set_reg_field(&mut self.regs, CRB_LOC_STS, "beenSeized", 0);
set_reg_field(&mut self.regs, CRB_LOC_STATE, "locAssigned", 1);
}
_ => {
error!("Invalid value to write in CRB_LOC_CTRL {:#X} ", v);
@@ -540,10 +456,10 @@ mod tests {
#[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);
let mut regs: [u32; TPM_CRB_R_MAX as usize] = [0; TPM_CRB_R_MAX as usize];
set_reg_field(&mut regs, CRB_INTF_ID, "RID", 0xAC);
assert_eq!(
get_reg_field(&regs, CrbRegister::IntfId(IntfIdFields::Rid)),
get_reg_field(&regs, CRB_INTF_ID, "RID"),
0xAC,
concat!("Test: ", stringify!(set_get_reg_field))
);

View File

@@ -1,21 +1,18 @@
- [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)
- [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)
- [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)
- [Command Line Interface](#command-line-interface)
- [REST API, D-Bus API and CLI Architectural Relationship](#rest-api-and-cli-architectural-relationship)
- [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)
@@ -24,11 +21,9 @@
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,48 @@ 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 |
##### 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 |
| 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 |
* 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 +122,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:
@@ -162,7 +150,7 @@ curl --unix-socket /tmp/cloud-hypervisor.sock -i \
}'
```
##### 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,50 +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. As of
writing this document, the D-Bus API mirrors the functionality of the REST
API and shares the same set of endpoints, meaning that it supports every call
that is supported by the REST API and can be a drop-in replacement since it
also consumes/produces JSON.
#### 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. As previously
mentioned, the D-Bus API currently mirrors the behaviour of the REST API.
### Command Line Interface
The Cloud Hypervisor Command Line Interface (CLI) can only be used for launching
@@ -255,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):
```
@@ -300,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 +--------------+
| |
+----------+
@@ -319,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.
@@ -444,5 +379,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

@@ -24,15 +24,12 @@ 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 build-essential, git, and qemu-utils
$ sudo apt install git build-essential qemu-utils
# 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
$ rustup target add x86_64-unknown-linux-musl
```
## Clone and build
@@ -49,8 +46,7 @@ $ cargo build --release
$ 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
$ cargo build --release --target=x86_64-unknown-linux-musl --all
$ popd
```

View File

@@ -88,7 +88,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
@@ -158,172 +158,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

@@ -8,7 +8,7 @@ To enable debugging with GDB, build with the `guest_debug` feature enabled:
cargo build --features guest_debug
```
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 \

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,64 +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
git clone https://github.com/tianocore/edk2.git
cd edk2
git checkout 13b97736c876919b9786055829caaa4fa46984b7
git clone https://github.com/tianocore/edk2-staging.git
cd edk2-staging
git checkout origin/TDVF
git submodule update --init --recursive
make -C BaseTools
source ./edksetup.sh
build -p OvmfPkg/IntelTdx/IntelTdxX64.dsc -a X64 -t GCC5 -b RELEASE
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
@@ -77,7 +62,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 \
--firmware edk2-staging/Build/OvmfCh/RELEASE_GCC5/FV/OVMF.fd \
--cpus boot=1 \
--memory size=1G \
--disk path=tdx_guest_img
@@ -89,7 +74,7 @@ firmware:
```bash
./cloud-hypervisor \
--platform tdx=on
--firmware edk2/Build/IntelTdx/DEBUG_GCC5/FV/OVMF.fd \
--firmware edk2-staging/Build/OvmfCh/DEBUG_GCC5/FV/OVMF.fd \
--cpus boot=1 \
--memory size=1G \
--disk path=tdx_guest_img \
@@ -99,60 +84,21 @@ 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 \
--kernel bzImage \
--cmdline "root=/dev/vda3 console=hvc0 rw"
--cpus boot=1 \
--memory size=1G \
--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 \
--firmware tdshim \
--kernel bzImage \
--cmdline "root=/dev/vda3 console=hvc0 rw"
--cpus boot=1 \

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

@@ -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

@@ -266,8 +266,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
@@ -516,7 +519,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 +543,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 +570,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:
@@ -52,5 +60,5 @@ $ perf record --call-graph lbr --all-user --user-callchains -g target/release/cl
--cpus boot=1 --memory size=1G \
--cmdline "root=/dev/pmem0p1 console=ttyS0" \
--serial tty --console off \
--api-socket /tmp/api1
--api-socket=/tmp/api1
```

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

@@ -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

@@ -5,7 +5,6 @@ authors = ["The Cloud Hypervisor Authors"]
edition = "2021"
[dependencies]
flume = "0.10.14"
libc = "0.2.139"
serde = { version = "1.0.164", features = ["rc", "derive"] }
serde_json = "1.0.96"
libc = "0.2.138"
serde = { version = "1.0.150", features = ["rc", "derive"] }
serde_json = "1.0.89"

View File

@@ -7,11 +7,30 @@ use serde::Serialize;
use std::borrow::Cow;
use std::collections::HashMap;
use std::fs::File;
use std::io;
use std::io::Write;
use std::os::unix::io::AsRawFd;
use std::time::{Duration, Instant};
static mut MONITOR: Option<MonitorHandle> = None;
static mut MONITOR: Option<(File, Instant)> = None;
/// This function must only be called once from the main process before any threads
/// are created to avoid race conditions
pub fn set_monitor(file: File) -> Result<(), std::io::Error> {
assert!(unsafe { MONITOR.is_none() });
let fd = file.as_raw_fd();
let ret = unsafe {
let mut flags = libc::fcntl(fd, libc::F_GETFL);
flags |= libc::O_NONBLOCK;
libc::fcntl(fd, libc::F_SETFL, flags)
};
if ret < 0 {
return Err(std::io::Error::last_os_error());
}
unsafe {
MONITOR = Some((file, Instant::now()));
};
Ok(())
}
#[derive(Serialize)]
struct Event<'a> {
@@ -21,71 +40,18 @@ struct Event<'a> {
properties: Option<&'a HashMap<Cow<'a, str>, Cow<'a, str>>>,
}
pub struct Monitor {
pub rx: flume::Receiver<String>,
pub file: File,
}
struct MonitorHandle {
tx: flume::Sender<String>,
start: Instant,
}
fn set_file_nonblocking(file: &File) -> io::Result<()> {
let fd = file.as_raw_fd();
// SAFETY: FFI call to configure the fd
let ret = unsafe {
let mut flags = libc::fcntl(fd, libc::F_GETFL);
flags |= libc::O_NONBLOCK;
libc::fcntl(fd, libc::F_SETFL, flags)
};
if ret < 0 {
Err(io::Error::last_os_error())
} else {
Ok(())
}
}
/// This function must only be called once from the main thread before any threads
/// are created to avoid race conditions.
pub fn set_monitor(file: File) -> io::Result<Monitor> {
// SAFETY: there is only one caller of this function, so MONITOR is written to only once
assert!(unsafe { MONITOR.is_none() });
set_file_nonblocking(&file)?;
let (tx, rx) = flume::unbounded();
let monitor = Monitor { rx, file };
// SAFETY: MONITOR is None. Nobody else can hold a reference to it.
unsafe {
MONITOR = Some(MonitorHandle {
tx,
start: Instant::now(),
});
};
Ok(monitor)
}
pub fn event_log(source: &str, event: &str, properties: Option<&HashMap<Cow<str>, Cow<str>>>) {
// SAFETY: `MONITOR` is always in a valid state (None or Some), because it
// is set only once before any threads are spawned, and it's not mutated
// afterwards. This function only creates immutable references to `MONITOR`.
// Because `MONITOR.tx` is `Sync`, it's safe to share `MONITOR` across
// threads, making this function thread-safe.
if let Some(monitor_handle) = unsafe { MONITOR.as_ref() } {
let event = Event {
timestamp: monitor_handle.start.elapsed(),
if let Some((file, start)) = unsafe { MONITOR.as_ref() } {
let e = Event {
timestamp: start.elapsed(),
source,
event,
properties,
};
serde_json::to_writer_pretty(file, &e).ok();
if let Ok(event) = serde_json::to_string_pretty(&event) {
monitor_handle.tx.send(event).ok();
}
let mut file = file;
file.write_all(b"\n\n").ok();
}
}
@@ -109,4 +75,5 @@ macro_rules! event {
$crate::event_log($source, $event, Some(&properties))
}
};
}

708
fuzz/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -9,21 +9,21 @@ edition = "2021"
cargo-fuzz = true
[dependencies]
block = { path = "../block" }
block_util = { path = "../block_util" }
devices = { path = "../devices" }
epoll = "4.3.1"
libc = "0.2.147"
libfuzzer-sys = "0.4.6"
linux-loader = { version = "0.9.0", features = ["elf", "bzimage", "pe"] }
libc = "0.2.135"
libfuzzer-sys = "0.4.5"
micro_http = { git = "https://github.com/firecracker-microvm/micro-http", branch = "main" }
net_util = { path = "../net_util" }
once_cell = "1.18.0"
once_cell = "1.16.0"
qcow = { path = "../qcow" }
seccompiler = "0.3.0"
vhdx = { path = "../vhdx" }
virtio-devices = { path = "../virtio-devices" }
virtio-queue = "0.8.0"
virtio-queue = "0.7.0"
vmm = { path = "../vmm" }
vmm-sys-util = "0.11.1"
vm-memory = "0.11.0"
vmm-sys-util = "0.11.0"
vm-memory = "0.10.0"
vm-device = { path = "../vm-device" }
vm-virtio = { path = "../vm-virtio" }
@@ -74,30 +74,12 @@ path = "fuzz_targets/iommu.rs"
test = false
doc = false
[[bin]]
name = "linux_loader"
path = "fuzz_targets/linux_loader.rs"
test = false
doc = false
[[bin]]
name = "linux_loader_cmdline"
path = "fuzz_targets/linux_loader_cmdline.rs"
test = false
doc = false
[[bin]]
name = "mem"
path = "fuzz_targets/mem.rs"
test = false
doc = false
[[bin]]
name = "net"
path = "fuzz_targets/net.rs"
test = false
doc = false
[[bin]]
name = "pmem"
path = "fuzz_targets/pmem.rs"

View File

@@ -8,7 +8,7 @@
#![no_main]
use block::{async_io::DiskFile, raw_sync::RawFileDiskSync};
use block_util::{async_io::DiskFile, raw_sync::RawFileDiskSync};
use libfuzzer_sys::fuzz_target;
use seccompiler::SeccompAction;
use std::ffi;

View File

@@ -6,8 +6,6 @@
use devices::legacy::Cmos;
use libc::EFD_NONBLOCK;
use libfuzzer_sys::fuzz_target;
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
use vm_device::BusDevice;
use vmm_sys_util::eventfd::EventFd;
@@ -27,7 +25,6 @@ fuzz_target!(|bytes| {
u64::from_le_bytes(below_4g),
u64::from_le_bytes(above_4g),
EventFd::new(EFD_NONBLOCK).unwrap(),
None,
);
let mut i = 16;

View File

@@ -1,50 +0,0 @@
// Copyright 2018 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// Copyright © 2022 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
#![no_main]
use libfuzzer_sys::fuzz_target;
use linux_loader::loader::KernelLoader;
use std::ffi;
use std::fs::File;
use std::io;
use std::io::{Seek, SeekFrom, Write};
use std::os::unix::io::{FromRawFd, RawFd};
use vm_memory::{bitmap::AtomicBitmap, GuestAddress};
type GuestMemoryMmap = vm_memory::GuestMemoryMmap<AtomicBitmap>;
const MEM_SIZE: usize = 256 * 1024 * 1024;
// From 'arch::x86_64::layout::HIGH_RAM_START'
const HIGH_RAM_START: GuestAddress = GuestAddress(0x100000);
fuzz_target!(|bytes| {
let shm = memfd_create(&ffi::CString::new("fuzz_load_kernel").unwrap(), 0).unwrap();
let mut kernel_file: File = unsafe { File::from_raw_fd(shm) };
kernel_file.write_all(&bytes).unwrap();
kernel_file.seek(SeekFrom::Start(0)).unwrap();
let guest_memory = GuestMemoryMmap::from_ranges(&[(GuestAddress(0), MEM_SIZE)]).unwrap();
linux_loader::loader::elf::Elf::load(
&guest_memory,
None,
&mut kernel_file,
Some(HIGH_RAM_START),
)
.ok();
});
fn memfd_create(name: &ffi::CStr, flags: u32) -> Result<RawFd, io::Error> {
let res = unsafe { libc::syscall(libc::SYS_memfd_create, name.as_ptr(), flags) };
if res < 0 {
Err(io::Error::last_os_error())
} else {
Ok(res as RawFd)
}
}

View File

@@ -1,34 +0,0 @@
// Copyright 2018 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// Copyright © 2022 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
#![no_main]
use libfuzzer_sys::fuzz_target;
use vm_memory::{bitmap::AtomicBitmap, GuestAddress};
type GuestMemoryMmap = vm_memory::GuestMemoryMmap<AtomicBitmap>;
const MEM_SIZE: usize = 256 * 1024 * 1024;
// From 'arch::x86_64::layout::CMDLINE_START'
const CMDLINE_START: GuestAddress = GuestAddress(0x20000);
fuzz_target!(|bytes| {
let payload_config = vmm::config::PayloadConfig {
firmware: None,
kernel: None,
cmdline: Some(String::from_utf8_lossy(&bytes).to_string()),
initramfs: None,
};
let kernel_cmdline = match vmm::vm::Vm::generate_cmdline(&payload_config) {
Ok(cmdline) => cmdline,
_ => return,
};
let guest_memory = GuestMemoryMmap::from_ranges(&[(GuestAddress(0), MEM_SIZE)]).unwrap();
linux_loader::loader::load_cmdline(&guest_memory, CMDLINE_START, &kernel_cmdline).ok();
});

View File

@@ -1,285 +0,0 @@
// Copyright © 2022 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
#![no_main]
use libfuzzer_sys::fuzz_target;
use seccompiler::SeccompAction;
use std::fs::File;
use std::io::{Read, Write};
use std::os::unix::io::{AsRawFd, FromRawFd};
use std::sync::Arc;
use virtio_devices::{VirtioDevice, VirtioInterrupt, VirtioInterruptType};
use virtio_queue::{Queue, QueueT};
use vm_memory::{bitmap::AtomicBitmap, Bytes, GuestAddress, GuestMemoryAtomic};
use vmm::EpollContext;
use vmm_sys_util::eventfd::{EventFd, EFD_NONBLOCK};
type GuestMemoryMmap = vm_memory::GuestMemoryMmap<AtomicBitmap>;
macro_rules! align {
($n:expr, $align:expr) => {{
(($n + $align - 1) / $align) * $align
}};
}
const TAP_INPUT_SIZE: usize = 128;
const QUEUE_DATA_SIZE: usize = 4;
const MEM_SIZE: usize = 32 * 1024 * 1024;
// Guest memory gap
const GUEST_MEM_GAP: u64 = 1 * 1024 * 1024;
// Guest physical address for the first virt queue
const BASE_VIRT_QUEUE_ADDR: u64 = MEM_SIZE as u64 + GUEST_MEM_GAP;
// Number of queues
const QUEUE_NUM: usize = 2;
// Max entries in the queue.
const QUEUE_SIZE: u16 = 256;
// Descriptor table alignment
const DESC_TABLE_ALIGN_SIZE: u64 = 16;
// Used ring alignment
const USED_RING_ALIGN_SIZE: u64 = 4;
// Descriptor table size
const DESC_TABLE_SIZE: u64 = 16_u64 * QUEUE_SIZE as u64;
// Available ring size
const AVAIL_RING_SIZE: u64 = 6_u64 + 2 * QUEUE_SIZE as u64;
// Padding size before used ring
const PADDING_SIZE: u64 = align!(AVAIL_RING_SIZE, USED_RING_ALIGN_SIZE) - AVAIL_RING_SIZE;
// Used ring size
const USED_RING_SIZE: u64 = 6_u64 + 8 * QUEUE_SIZE as u64;
// Virtio-queue size in bytes
const QUEUE_BYTES_SIZE: usize = align!(
DESC_TABLE_SIZE + AVAIL_RING_SIZE + PADDING_SIZE + USED_RING_SIZE,
DESC_TABLE_ALIGN_SIZE
) as usize;
fuzz_target!(|bytes| {
if bytes.len() < TAP_INPUT_SIZE + (QUEUE_DATA_SIZE + QUEUE_BYTES_SIZE) * QUEUE_NUM
|| bytes.len()
> TAP_INPUT_SIZE + (QUEUE_DATA_SIZE + QUEUE_BYTES_SIZE) * QUEUE_NUM + MEM_SIZE
{
return;
}
let (dummy_tap_frontend, dummy_tap_backend) = create_socketpair().unwrap();
let if_name = "fuzzer_tap_name".as_bytes().to_vec();
let tap = net_util::Tap::new_for_fuzzing(dummy_tap_frontend, if_name);
let mut net = virtio_devices::Net::new_with_tap(
"fuzzer_net".to_owned(),
vec![tap],
None, // guest_mac
false, // iommu
QUEUE_NUM,
QUEUE_SIZE,
SeccompAction::Allow,
None,
EventFd::new(EFD_NONBLOCK).unwrap(),
None,
true,
true,
true,
)
.unwrap();
let tap_input_bytes = &bytes[..TAP_INPUT_SIZE];
let queue_data = &bytes[TAP_INPUT_SIZE..TAP_INPUT_SIZE + QUEUE_DATA_SIZE * QUEUE_NUM];
let queue_bytes = &bytes[TAP_INPUT_SIZE + QUEUE_DATA_SIZE * QUEUE_NUM
..TAP_INPUT_SIZE + (QUEUE_DATA_SIZE + QUEUE_BYTES_SIZE) * QUEUE_NUM];
let mem_bytes = &bytes[TAP_INPUT_SIZE + (QUEUE_DATA_SIZE + QUEUE_BYTES_SIZE) * QUEUE_NUM..];
// Setup the virt queues with the input bytes
let mut queues = setup_virt_queues(
&[
&queue_data[..QUEUE_DATA_SIZE].try_into().unwrap(),
&queue_data[QUEUE_DATA_SIZE..QUEUE_DATA_SIZE * 2]
.try_into()
.unwrap(),
],
BASE_VIRT_QUEUE_ADDR,
);
// Setup the guest memory with the input bytes
let mem = GuestMemoryMmap::from_ranges(&[
(GuestAddress(0), MEM_SIZE),
(GuestAddress(BASE_VIRT_QUEUE_ADDR), queue_bytes.len()),
])
.unwrap();
if mem
.write_slice(queue_bytes, GuestAddress(BASE_VIRT_QUEUE_ADDR))
.is_err()
{
return;
}
if mem.write_slice(mem_bytes, GuestAddress(0 as u64)).is_err() {
return;
}
let guest_memory = GuestMemoryAtomic::new(mem);
let input_queue = queues.remove(0);
let input_evt = EventFd::new(0).unwrap();
let input_queue_evt = unsafe { EventFd::from_raw_fd(libc::dup(input_evt.as_raw_fd())) };
let output_queue = queues.remove(0);
let output_evt = EventFd::new(0).unwrap();
let output_queue_evt = unsafe { EventFd::from_raw_fd(libc::dup(output_evt.as_raw_fd())) };
// Start the thread of dummy tap backend to handle the rx and tx from the virtio-net
let exit_evt = EventFd::new(libc::EFD_NONBLOCK).unwrap();
let tap_backend_thread = {
let dummy_tap_backend = dummy_tap_backend.try_clone().unwrap();
let tap_input_bytes: [u8; TAP_INPUT_SIZE] = tap_input_bytes[..].try_into().unwrap();
let exit_evt = exit_evt.try_clone().unwrap();
std::thread::Builder::new()
.name("dummy_tap_backend".to_string())
.spawn(move || {
tap_backend_stub(dummy_tap_backend, &tap_input_bytes, exit_evt);
})
.unwrap()
};
// Kick the 'queue' events and endpoint event before activate the net device
input_queue_evt.write(1).unwrap();
output_queue_evt.write(1).unwrap();
net.activate(
guest_memory,
Arc::new(NoopVirtioInterrupt {}),
vec![(0, input_queue, input_evt), (1, output_queue, output_evt)],
)
.unwrap();
// Wait for the events to finish and net device worker thread to return
net.wait_for_epoll_threads();
// Terminate the thread for the dummy tap backend
exit_evt.write(1).ok();
tap_backend_thread.join().unwrap();
});
pub struct NoopVirtioInterrupt {}
impl VirtioInterrupt for NoopVirtioInterrupt {
fn trigger(&self, _int_type: VirtioInterruptType) -> std::result::Result<(), std::io::Error> {
Ok(())
}
}
fn setup_virt_queues(bytes: &[&[u8; QUEUE_DATA_SIZE]], base_addr: u64) -> Vec<Queue> {
let mut queues = Vec::new();
for (i, b) in bytes.iter().enumerate() {
let mut q = Queue::new(QUEUE_SIZE).unwrap();
let desc_table_addr = base_addr + (QUEUE_BYTES_SIZE * i) as u64;
let avail_ring_addr = desc_table_addr + DESC_TABLE_SIZE;
let used_ring_addr = avail_ring_addr + PADDING_SIZE + AVAIL_RING_SIZE;
q.try_set_desc_table_address(GuestAddress(desc_table_addr))
.unwrap();
q.try_set_avail_ring_address(GuestAddress(avail_ring_addr))
.unwrap();
q.try_set_used_ring_address(GuestAddress(used_ring_addr))
.unwrap();
q.set_next_avail(b[0] as u16); // 'u8' is enough given the 'QUEUE_SIZE' is small
q.set_next_used(b[1] as u16);
q.set_event_idx(b[2] % 2 != 0);
q.set_size(b[3] as u16 % QUEUE_SIZE);
q.set_ready(true);
queues.push(q);
}
queues
}
fn create_socketpair() -> Result<(File, File), std::io::Error> {
let mut fds = [-1, -1];
unsafe {
let ret = libc::socketpair(
libc::AF_UNIX,
libc::SOCK_STREAM | libc::SOCK_NONBLOCK,
0,
fds.as_mut_ptr(),
);
if ret == -1 {
return Err(std::io::Error::last_os_error());
}
}
let socket1 = unsafe { File::from_raw_fd(fds[0]) };
let socket2 = unsafe { File::from_raw_fd(fds[1]) };
Ok((socket1, socket2))
}
enum EpollEvent {
Exit = 0,
Rx = 1,
Tx = 2,
Unknown,
}
impl From<u64> for EpollEvent {
fn from(v: u64) -> Self {
use EpollEvent::*;
match v {
0 => Exit,
1 => Rx,
2 => Tx,
_ => Unknown,
}
}
}
// Handle the rx and tx requests from the virtio-net device
fn tap_backend_stub(
mut dummy_tap: File,
tap_input_bytes: &[u8; TAP_INPUT_SIZE],
exit_evt: EventFd,
) {
let mut epoll = EpollContext::new().unwrap();
epoll
.add_event_custom(&exit_evt, EpollEvent::Exit as u64, epoll::Events::EPOLLIN)
.unwrap();
let dummy_tap_write = dummy_tap.try_clone().unwrap();
epoll
.add_event_custom(
&dummy_tap_write,
EpollEvent::Rx as u64,
epoll::Events::EPOLLOUT,
)
.unwrap();
epoll
.add_event_custom(&dummy_tap, EpollEvent::Tx as u64, epoll::Events::EPOLLIN)
.unwrap();
let epoll_fd = epoll.as_raw_fd();
let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); 3];
loop {
let num_events = match epoll::wait(epoll_fd, -1, &mut events[..]) {
Ok(num_events) => num_events,
Err(e) => match e.raw_os_error() {
Some(libc::EAGAIN) | Some(libc::EINTR) => continue,
_ => panic!("Unexpected epoll::wait error!"),
},
};
for event in events.iter().take(num_events) {
let dispatch_event: EpollEvent = event.data.into();
match dispatch_event {
EpollEvent::Exit => {
return;
}
EpollEvent::Rx => {
dummy_tap.write_all(tap_input_bytes).unwrap();
break;
}
EpollEvent::Tx => {
let mut buffer = Vec::new();
dummy_tap.read_to_end(&mut buffer).ok();
break;
}
_ => {
panic!("Unexpected Epoll event");
}
}
}
}
}

View File

@@ -4,7 +4,7 @@
#![no_main]
use libfuzzer_sys::fuzz_target;
use block::qcow::{QcowFile, RawFile};
use qcow::{QcowFile, RawFile};
use std::ffi;
use std::fs::File;
use std::io::{self, Cursor, Read, Seek, SeekFrom, Write};

View File

@@ -15,7 +15,6 @@ fuzz_target!(|bytes| {
let mut serial = Serial::new_sink(
"serial".into(),
Arc::new(TestInterrupt::new(EventFd::new(EFD_NONBLOCK).unwrap())),
None,
);
let mut i = 0;
@@ -59,13 +58,9 @@ impl InterruptSourceGroup for TestInterrupt {
_index: InterruptIndex,
_config: InterruptSourceConfig,
_masked: bool,
_set_gsi: bool,
) -> Result<(), std::io::Error> {
Ok(())
}
fn set_gsi(&self) -> Result<(), std::io::Error> {
Ok(())
}
fn notifier(&self, _index: InterruptIndex) -> Option<EventFd> {
Some(self.event_fd.try_clone().unwrap())
}

View File

@@ -8,7 +8,7 @@ use std::ffi;
use std::fs::File;
use std::io::{self, Read, Seek, SeekFrom, Write};
use std::os::unix::io::{FromRawFd, RawFd};
use block::vhdx::Vhdx;
use vhdx::vhdx::Vhdx;
// Populate the corpus directory with a test file:
// truncate -s 16M /tmp/source

View File

@@ -6,29 +6,28 @@ edition = "2021"
license = "Apache-2.0 OR BSD-3-Clause"
[features]
kvm = ["kvm-ioctls", "kvm-bindings", "vfio-ioctls/kvm"]
mshv = ["mshv-ioctls", "mshv-bindings", "vfio-ioctls/mshv", "iced-x86"]
kvm = ["kvm-ioctls", "kvm-bindings"]
mshv = ["mshv-ioctls", "mshv-bindings"]
tdx = []
[dependencies]
anyhow = "1.0.71"
anyhow = "1.0.66"
byteorder = "1.4.3"
thiserror = "1.0.40"
libc = "0.2.139"
thiserror = "1.0.37"
libc = "0.2.138"
log = "0.4.17"
kvm-ioctls = { version = "0.13.0", optional = true }
kvm-bindings = { git = "https://github.com/cloud-hypervisor/kvm-bindings", branch = "ch-v0.6.0-tdx", features = ["with-serde", "fam-wrappers"], optional = true }
mshv-bindings = { git = "https://github.com/rust-vmm/mshv", branch = "main", features = ["with-serde", "fam-wrappers"], optional = true }
mshv-ioctls = { git = "https://github.com/rust-vmm/mshv", branch = "main", optional = true}
serde = { version = "1.0.164", features = ["rc", "derive"] }
serde_with = { version = "3.0.0", default-features = false, features = ["macros"] }
serde = { version = "1.0.150", features = ["rc", "derive"] }
serde_with = { version = "2.1.0", default-features = false, features = ["macros"] }
vfio-ioctls = { git = "https://github.com/rust-vmm/vfio", branch = "main", default-features = false }
vm-memory = { version = "0.11.0", features = ["backend-mmap", "backend-atomic"] }
vm-memory = { version = "0.10.0", features = ["backend-mmap", "backend-atomic"] }
vmm-sys-util = { version = "0.11.0", features = ["with-serde"] }
[target.'cfg(target_arch = "x86_64")'.dependencies.iced-x86]
optional = true
version = "1.19.0"
version = "1.17.0"
default-features = false
features = ["std", "decoder", "op_code_info", "instr_info", "fast_fmt"]

View File

@@ -239,36 +239,6 @@ impl<T: CpuStateManager> InstructionHandler<T> for Movzx_r64_rm16 {
movzx!(u64, u16);
}
pub struct Mov_moffs16_AX;
impl<T: CpuStateManager> InstructionHandler<T> for Mov_moffs16_AX {
movzx!(u16, u16);
}
pub struct Mov_AX_moffs16;
impl<T: CpuStateManager> InstructionHandler<T> for Mov_AX_moffs16 {
movzx!(u16, u16);
}
pub struct Mov_moffs32_EAX;
impl<T: CpuStateManager> InstructionHandler<T> for Mov_moffs32_EAX {
movzx!(u32, u32);
}
pub struct Mov_EAX_moffs32;
impl<T: CpuStateManager> InstructionHandler<T> for Mov_EAX_moffs32 {
movzx!(u32, u32);
}
pub struct Mov_moffs64_RAX;
impl<T: CpuStateManager> InstructionHandler<T> for Mov_moffs64_RAX {
movzx!(u64, u64);
}
pub struct Mov_RAX_moffs64;
impl<T: CpuStateManager> InstructionHandler<T> for Mov_RAX_moffs64 {
movzx!(u64, u64);
}
#[cfg(test)]
mod tests {
#![allow(unused_mut)]
@@ -647,123 +617,4 @@ mod tests {
.unwrap();
assert_eq!(eax, value as u64);
}
#[test]
// movabs ax, ds:0x1337
// movabs eax, ds:0x1337
// movabs rax, ds:0x1337
fn test_mov_memoff_ax() {
let test_inputs: [(Register, &[u8]); 3] = [
(Register::AX, &[0x66, 0xa1]),
(Register::EAX, &[0xa1]),
(Register::RAX, &[0x48, 0xa1]),
];
// Constructs the instruction with the provided inputs and emulates it.
fn helper(register: Register, instruction_prefix: &[u8]) {
let mem_addr: u64 = 0x1337;
let mem_value: u64 = 0x13371337deadbeef;
let ip: u64 = 0x1000;
let cpu_id = 0;
let mut instruction_bytes = Vec::new();
// instruction prefix with specified register
instruction_bytes.extend(instruction_prefix);
// 64-bit memory operand
instruction_bytes.extend([
mem_addr.to_le_bytes()[0],
mem_addr.to_le_bytes()[1],
0,
0,
0,
0,
0,
0,
]);
let memory: [u8; 8] = mem_value.to_le_bytes();
let mut vmm = MockVmm::new(ip, vec![], Some((mem_addr, &memory)));
assert!(vmm.emulate_first_insn(cpu_id, &instruction_bytes).is_ok());
let ax: u64 = vmm.cpu_state(cpu_id).unwrap().read_reg(register).unwrap();
match register {
Register::AX => {
assert_eq!(ax as u16, mem_value as u16);
}
Register::EAX => {
assert_eq!(ax as u32, mem_value as u32);
}
Register::RAX => {
assert_eq!(ax, mem_value);
}
_ => panic!(),
}
}
for (register, instruction_prefix) in test_inputs {
helper(register, instruction_prefix)
}
}
#[test]
// movabs ds:0x1337, ax
// movabs ds:0x1337, eax
// movabs ds:0x1337, rax
fn test_mov_ax_memoff() {
let test_inputs: [(Register, &[u8]); 3] = [
(Register::AX, &[0x66, 0xa3]),
(Register::EAX, &[0xa3]),
(Register::RAX, &[0x48, 0xa3]),
];
// Constructs the instruction with the provided inputs and emulates it.
fn helper(register: Register, instruction_prefix: &[u8]) {
let mem_addr: u64 = 0x1337;
let ax: u64 = 0x13371337deadbeef;
let ip: u64 = 0x1000;
let cpu_id = 0;
let mut instruction_bytes = Vec::new();
// instruction prefix with specified register
instruction_bytes.extend(instruction_prefix);
// 64-bit memory operand
instruction_bytes.extend([
mem_addr.to_le_bytes()[0],
mem_addr.to_le_bytes()[1],
0,
0,
0,
0,
0,
0,
]);
let mut vmm = MockVmm::new(ip, vec![(Register::RAX, ax)], None);
assert!(vmm.emulate_first_insn(cpu_id, &instruction_bytes).is_ok());
match register {
Register::AX => {
let mut memory: [u8; 2] = [0; 2];
vmm.read_memory(mem_addr, &mut memory).unwrap();
assert_eq!(u16::from_le_bytes(memory), ax as u16);
}
Register::EAX => {
let mut memory: [u8; 4] = [0; 4];
vmm.read_memory(mem_addr, &mut memory).unwrap();
assert_eq!(u32::from_le_bytes(memory), ax as u32);
}
Register::RAX => {
let mut memory: [u8; 8] = [0; 8];
vmm.read_memory(mem_addr, &mut memory).unwrap();
assert_eq!(u64::from_le_bytes(memory), ax);
}
_ => panic!(),
}
}
for (register, instruction_prefix) in test_inputs {
helper(register, instruction_prefix)
}
}
}

View File

@@ -15,89 +15,73 @@ use crate::arch::x86::emulator::instructions::*;
use crate::arch::x86::regs::DF;
use crate::arch::x86::Exception;
macro_rules! movs {
($bound:ty) => {
fn emulate(
&self,
insn: &Instruction,
state: &mut T,
platform: &mut dyn PlatformEmulator<CpuState = T>,
) -> Result<(), EmulationError<Exception>> {
let mut count: u64 = if insn.has_rep_prefix() {
state
.read_reg(Register::ECX)
.map_err(|e| EmulationError::InvalidOperand(anyhow!(e)))?
} else {
1
};
let mut rsi = state
.read_reg(Register::RSI)
.map_err(|e| EmulationError::InvalidOperand(anyhow!(e)))?;
let mut rdi = state
.read_reg(Register::RDI)
.map_err(|e| EmulationError::InvalidOperand(anyhow!(e)))?;
let df = (state.flags() & DF) != 0;
let len = std::mem::size_of::<$bound>();
while count > 0 {
let mut memory: [u8; 4] = [0; 4];
let src = state
.linearize(Register::DS, rsi, false)
.map_err(|e| EmulationError::InvalidOperand(anyhow!(e)))?;
let dst = state
.linearize(Register::ES, rdi, true)
.map_err(|e| EmulationError::InvalidOperand(anyhow!(e)))?;
platform
.read_memory(src, &mut memory[0..len])
.map_err(EmulationError::PlatformEmulationError)?;
platform
.write_memory(dst, &memory[0..len])
.map_err(EmulationError::PlatformEmulationError)?;
if df {
rsi = rsi.wrapping_sub(len as u64);
rdi = rdi.wrapping_sub(len as u64);
} else {
rsi = rsi.wrapping_add(len as u64);
rdi = rdi.wrapping_add(len as u64);
}
count -= 1;
}
state
.write_reg(Register::RSI, rsi)
.map_err(|e| EmulationError::InvalidOperand(anyhow!(e)))?;
state
.write_reg(Register::RDI, rdi)
.map_err(|e| EmulationError::InvalidOperand(anyhow!(e)))?;
if insn.has_rep_prefix() {
state
.write_reg(Register::ECX, 0)
.map_err(|e| EmulationError::InvalidOperand(anyhow!(e)))?;
}
Ok(())
}
};
}
pub struct Movsd_m32_m32;
impl<T: CpuStateManager> InstructionHandler<T> for Movsd_m32_m32 {
movs!(u32);
}
fn emulate(
&self,
insn: &Instruction,
state: &mut T,
platform: &mut dyn PlatformEmulator<CpuState = T>,
) -> Result<(), EmulationError<Exception>> {
let mut count: u64 = if insn.has_rep_prefix() {
state
.read_reg(Register::ECX)
.map_err(|e| EmulationError::InvalidOperand(anyhow!(e)))?
} else {
1
};
pub struct Movsw_m16_m16;
impl<T: CpuStateManager> InstructionHandler<T> for Movsw_m16_m16 {
movs!(u16);
}
let mut rsi = state
.read_reg(Register::RSI)
.map_err(|e| EmulationError::InvalidOperand(anyhow!(e)))?;
let mut rdi = state
.read_reg(Register::RDI)
.map_err(|e| EmulationError::InvalidOperand(anyhow!(e)))?;
pub struct Movsb_m8_m8;
impl<T: CpuStateManager> InstructionHandler<T> for Movsb_m8_m8 {
movs!(u8);
let df = (state.flags() & DF) != 0;
let len = std::mem::size_of::<u32>();
while count > 0 {
let mut memory: [u8; 4] = [0; 4];
let src = state
.linearize(Register::DS, rsi, false)
.map_err(|e| EmulationError::InvalidOperand(anyhow!(e)))?;
let dst = state
.linearize(Register::ES, rdi, true)
.map_err(|e| EmulationError::InvalidOperand(anyhow!(e)))?;
platform
.read_memory(src, &mut memory[0..len])
.map_err(EmulationError::PlatformEmulationError)?;
platform
.write_memory(dst, &memory[0..len])
.map_err(EmulationError::PlatformEmulationError)?;
if df {
rsi = rsi.wrapping_sub(len as u64);
rdi = rdi.wrapping_sub(len as u64);
} else {
rsi = rsi.wrapping_add(len as u64);
rdi = rdi.wrapping_add(len as u64);
}
count -= 1;
}
state
.write_reg(Register::RSI, rsi)
.map_err(|e| EmulationError::InvalidOperand(anyhow!(e)))?;
state
.write_reg(Register::RDI, rdi)
.map_err(|e| EmulationError::InvalidOperand(anyhow!(e)))?;
if insn.has_rep_prefix() {
state
.write_reg(Register::ECX, 0)
.map_err(|e| EmulationError::InvalidOperand(anyhow!(e)))?;
}
Ok(())
}
}
#[cfg(test)]
@@ -158,127 +142,4 @@ mod tests {
vmm.read_memory(0x8 + 8, &mut data).unwrap();
assert_eq!(0x0, <u32>::from_le_bytes(data));
}
#[test]
fn test_rep_movsw_m16_m16() {
let ip: u64 = 0x1000;
let memory: [u8; 24] = [
0x78, 0x56, 0x34, 0x12, // 0x12345678
0xdd, 0xcc, 0xbb, 0xaa, // 0xaabbccdd
0xa5, 0x5a, 0xa5, 0x5a, // 0x5aa55aa5
0x00, 0x00, 0x00, 0x00, // 0x00000000
0x00, 0x00, 0x00, 0x00, // 0x00000000
0x00, 0x00, 0x00, 0x00, // 0x00000000
];
let insn = [0x66, 0xf3, 0xa5]; // rep movsw
let regs = vec![(Register::ECX, 6), (Register::ESI, 0), (Register::EDI, 0xc)];
let mut data = [0u8; 2];
let mut vmm = MockVmm::new(ip, regs, Some((0, &memory)));
assert!(vmm.emulate_first_insn(0, &insn).is_ok());
vmm.read_memory(0xc, &mut data).unwrap();
assert_eq!(0x5678, <u16>::from_le_bytes(data));
vmm.read_memory(0xc + 2, &mut data).unwrap();
assert_eq!(0x1234, <u16>::from_le_bytes(data));
vmm.read_memory(0xc + 4, &mut data).unwrap();
assert_eq!(0xccdd, <u16>::from_le_bytes(data));
vmm.read_memory(0xc + 6, &mut data).unwrap();
assert_eq!(0xaabb, <u16>::from_le_bytes(data));
vmm.read_memory(0xc + 8, &mut data).unwrap();
assert_eq!(0x5aa5, <u16>::from_le_bytes(data));
vmm.read_memory(0xc + 10, &mut data).unwrap();
assert_eq!(0x5aa5, <u16>::from_le_bytes(data));
// The rest should be default value 0 from MockVmm
vmm.read_memory(0xc + 12, &mut data).unwrap();
assert_eq!(0x0, <u16>::from_le_bytes(data));
}
#[test]
fn test_movsw_m16_m16() {
let ip: u64 = 0x1000;
let memory: [u8; 4] = [
0x78, 0x56, 0x34, 0x12, // 0x12345678
];
let insn = [0x66, 0xa5]; // movsw
let regs = vec![(Register::ESI, 0), (Register::EDI, 0x8)];
let mut data = [0u8; 2];
let mut vmm = MockVmm::new(ip, regs, Some((0, &memory)));
assert!(vmm.emulate_first_insn(0, &insn).is_ok());
vmm.read_memory(0x8, &mut data).unwrap();
assert_eq!(0x5678, <u16>::from_le_bytes(data));
// Only two bytes were copied, so the value at 0xa should be zero
vmm.read_memory(0xa, &mut data).unwrap();
assert_eq!(0x0, <u16>::from_le_bytes(data));
// The rest should be default value 0 from MockVmm
vmm.read_memory(0x4, &mut data).unwrap();
assert_eq!(0x0, <u16>::from_le_bytes(data));
vmm.read_memory(0x8 + 8, &mut data).unwrap();
assert_eq!(0x0, <u16>::from_le_bytes(data));
}
#[test]
fn test_movsb_m8_m8() {
let ip: u64 = 0x1000;
let memory: [u8; 4] = [
0x78, 0x56, 0x34, 0x12, // 0x12345678
];
let insn = [0x66, 0xa4]; // movsb
let regs = vec![(Register::ESI, 0), (Register::EDI, 0x8)];
let mut data = [0u8; 1];
let mut vmm = MockVmm::new(ip, regs, Some((0, &memory)));
assert!(vmm.emulate_first_insn(0, &insn).is_ok());
vmm.read_memory(0x8, &mut data).unwrap();
assert_eq!(0x78, data[0]);
// Only one byte was copied, so the value at 0x9 should be zero
vmm.read_memory(0x9, &mut data).unwrap();
assert_eq!(0x0, data[0]);
// The rest should be default value 0 from MockVmm
vmm.read_memory(0x4, &mut data).unwrap();
assert_eq!(0x0, data[0]);
// the src value is left as is after movb
vmm.read_memory(0x0, &mut data).unwrap();
assert_eq!(0x78, data[0]);
}
#[test]
fn test_rep_movsb_m8_m8() {
let ip: u64 = 0x1000;
let memory: [u8; 16] = [
0x78, 0x56, 0x34, 0x12, // 0x12345678
0xbb, 0xaa, 0x00, 0x00, // 0x0000aabb
0x00, 0x00, 0x00, 0x00, // 0x00000000
0x00, 0x00, 0x00, 0x00, // 0x00000000
];
let insn = [0x66, 0xf3, 0xa4]; // rep movsw
let regs = vec![(Register::ECX, 6), (Register::ESI, 0), (Register::EDI, 0x8)];
let mut data = [0u8; 1];
let mut vmm = MockVmm::new(ip, regs, Some((0, &memory)));
assert!(vmm.emulate_first_insn(0, &insn).is_ok());
vmm.read_memory(0x8, &mut data).unwrap();
assert_eq!(0x78, data[0]);
vmm.read_memory(0x8 + 1, &mut data).unwrap();
assert_eq!(0x56, data[0]);
vmm.read_memory(0x8 + 2, &mut data).unwrap();
assert_eq!(0x34, data[0]);
vmm.read_memory(0x8 + 3, &mut data).unwrap();
assert_eq!(0x12, data[0]);
vmm.read_memory(0x8 + 4, &mut data).unwrap();
assert_eq!(0xbb, data[0]);
vmm.read_memory(0x8 + 5, &mut data).unwrap();
assert_eq!(0xaa, data[0]);
// The rest should be default value 0 from MockVmm
vmm.read_memory(0x8 + 6, &mut data).unwrap();
assert_eq!(0x0, data[0]);
}
}

View File

@@ -524,17 +524,8 @@ impl<'a, T: CpuStateManager> Emulator<'a, T> {
(mov, Movzx_r64_rm8),
(mov, Movzx_r32_rm16),
(mov, Movzx_r64_rm16),
// MOV MOFFS
(mov, Mov_moffs16_AX),
(mov, Mov_AX_moffs16),
(mov, Mov_moffs32_EAX),
(mov, Mov_EAX_moffs32),
(mov, Mov_moffs64_RAX),
(mov, Mov_RAX_moffs64),
// MOVS
(movs, Movsd_m32_m32),
(movs, Movsw_m16_m16),
(movs, Movsb_m8_m8),
// OR
(or, Or_rm8_r8)
);

View File

@@ -11,7 +11,6 @@
// Copyright © 2020, Microsoft Corporation
//
#[cfg(all(feature = "mshv", target_arch = "x86_64"))]
pub mod emulator;
pub mod gdt;
#[allow(non_camel_case_types)]
@@ -27,6 +26,7 @@ pub const MTRR_MEM_TYPE_WB: u64 = 0x6;
pub const NUM_IOAPIC_PINS: usize = 24;
// X86 Exceptions
#[allow(clippy::upper_case_acronyms)]
#[derive(Clone, Debug)]
pub enum Exception {
DE = 0, // Divide Error
@@ -272,7 +272,6 @@ impl LapicState {
use std::io::Cursor;
use std::mem;
// SAFETY: plain old data type
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).
@@ -291,7 +290,6 @@ impl LapicState {
use std::io::Cursor;
use std::mem;
// SAFETY: plain old data type
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).

View File

@@ -21,15 +21,6 @@ use crate::MpState;
use thiserror::Error;
use vm_memory::GuestAddress;
#[cfg(target_arch = "x86_64")]
#[derive(Copy, Clone, Default)]
pub enum CpuVendor {
#[default]
Unknown,
Intel,
AMD,
}
#[derive(Error, Debug)]
///
/// Enum for CPU error

View File

@@ -9,14 +9,10 @@
//
#[cfg(target_arch = "x86_64")]
use crate::arch::x86::CpuIdEntry;
#[cfg(target_arch = "x86_64")]
use crate::cpu::CpuVendor;
#[cfg(feature = "tdx")]
use crate::kvm::TdxCapabilities;
use crate::vm::Vm;
use crate::HypervisorType;
#[cfg(target_arch = "x86_64")]
use std::arch::x86_64;
use std::sync::Arc;
use thiserror::Error;
@@ -79,11 +75,6 @@ pub enum HypervisorError {
///
#[error("Failed to set partition property:{0}")]
SetPartitionProperty(#[source] anyhow::Error),
///
/// Running on an unsupported CPU
///
#[error("Unsupported CPU:{0}")]
UnsupportedCpu(#[source] anyhow::Error),
}
///
@@ -117,7 +108,7 @@ pub trait Hypervisor: Send + Sync {
///
/// Get the supported CpuID
///
fn get_supported_cpuid(&self) -> Result<Vec<CpuIdEntry>>;
fn get_cpuid(&self) -> Result<Vec<CpuIdEntry>>;
///
/// Check particular extensions if any
///
@@ -142,30 +133,4 @@ pub trait Hypervisor: Send + Sync {
fn get_guest_debug_hw_bps(&self) -> usize {
unimplemented!()
}
/// Get maximum number of vCPUs
fn get_max_vcpus(&self) -> u32;
#[cfg(target_arch = "x86_64")]
///
/// Determine CPU vendor
///
fn get_cpu_vendor(&self) -> CpuVendor {
// SAFETY: call cpuid with valid leaves
unsafe {
let leaf = x86_64::__cpuid(0x0);
if leaf.ebx == 0x756e_6547 && leaf.ecx == 0x6c65_746e && leaf.edx == 0x4965_6e69 {
// Vendor string GenuineIntel
CpuVendor::Intel
} else if leaf.ebx == 0x6874_7541 && leaf.ecx == 0x444d_4163 && leaf.edx == 0x6974_6e65
{
// Vendor string AuthenticAMD
CpuVendor::AMD
} else {
// Not known yet, the corresponding manufacturer manual should contain the
// necesssary info. See also https://wiki.osdev.org/CPUID#CPU_Vendor_ID_String
CpuVendor::default()
}
}
}
}

View File

@@ -243,6 +243,7 @@ impl KvmGicV3Its {
}
/// Method to initialize the GIC device
#[allow(clippy::new_ret_no_self)]
pub fn new(vm: &dyn Vm, config: VgicConfig) -> Result<KvmGicV3Its> {
// This is inside KVM module
let vm = vm.as_any().downcast_ref::<KvmVm>().expect("Wrong VM type?");

View File

@@ -39,8 +39,8 @@ 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
const KVM_ARM64_SYSREG_MPIDR_EL1: u64 = KVM_REG_ARM64 as u64
| KVM_REG_SIZE_U64 as 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);

View File

@@ -24,8 +24,8 @@ pub use {kvm_ioctls::Cap, kvm_ioctls::Kvm};
// This macro gets the offset of a structure (i.e `str`) member (i.e `field`) without having
// an instance of that structure.
#[macro_export]
macro_rules! offset_of {
($str:ty, $field:ident) => {{
macro_rules! offset__of {
($str:ty, $($field:ident)+) => ({
let tmp: std::mem::MaybeUninit<$str> = std::mem::MaybeUninit::uninit();
let base = tmp.as_ptr();
@@ -34,16 +34,14 @@ macro_rules! offset_of {
// SAFETY: The pointer is valid and aligned, just not initialised. Using `addr_of` ensures
// that we don't actually read from `base` (which would be UB) nor create an intermediate
// reference.
let member = unsafe { core::ptr::addr_of!((*base).$field) } as *const u8;
let member = unsafe { core::ptr::addr_of!((*base).$($field)*) } as *const u8;
// Avoid warnings when nesting `unsafe` blocks.
#[allow(unused_unsafe)]
// SAFETY: The two pointers are within the same allocated object `tmp`. All requirements
// from offset_from are upheld.
unsafe {
member.offset_from(base as *const u8) as usize
}
}};
unsafe { member.offset_from(base as *const u8) as usize }
});
}
// Following are macros that help with getting the ID of a aarch64 core register.

View File

@@ -23,7 +23,7 @@ use crate::vec_with_array_field;
use crate::vm::{self, InterruptSourceConfig, VmOps};
use crate::HypervisorType;
#[cfg(target_arch = "aarch64")]
use crate::{arm64_core_reg_id, offset_of};
use crate::{arm64_core_reg_id, offset__of};
use kvm_ioctls::{NoDatamatch, VcpuFd, VmFd};
use std::any::Any;
use std::collections::HashMap;
@@ -106,7 +106,7 @@ pub use {
const KVM_CAP_SGX_ATTRIBUTE: u32 = 196;
#[cfg(feature = "tdx")]
const KVM_EXIT_TDX: u32 = 50;
const KVM_EXIT_TDX: u32 = 35;
#[cfg(feature = "tdx")]
const TDG_VP_VMCALL_GET_QUOTE: u64 = 0x10002;
#[cfg(feature = "tdx")]
@@ -335,17 +335,16 @@ impl KvmVm {
}
}
///
/// Implementation of Vm trait for KVM
///
/// # Examples
///
/// ```
/// # use hypervisor::kvm::KvmHypervisor;
/// # use std::sync::Arc;
/// let kvm = KvmHypervisor::new().unwrap();
/// let hypervisor = Arc::new(kvm);
/// Example:
/// #[cfg(feature = "kvm")]
/// extern crate hypervisor
/// let kvm = hypervisor::kvm::KvmHypervisor::new().unwrap();
/// let hypervisor: Arc<dyn hypervisor::Hypervisor> = Arc::new(kvm);
/// let vm = hypervisor.create_vm().expect("new VM fd creation failed");
/// ```
/// vm.set/get().unwrap()
///
impl vm::Vm for KvmVm {
#[cfg(target_arch = "x86_64")]
///
@@ -747,34 +746,36 @@ impl vm::Vm for KvmVm {
///
#[cfg(feature = "tdx")]
fn tdx_init(&self, cpuid: &[CpuIdEntry], max_vcpus: u32) -> vm::Result<()> {
const TDX_ATTR_SEPT_VE_DISABLE: usize = 28;
let mut cpuid: Vec<kvm_bindings::kvm_cpuid_entry2> =
use std::io::{Error, ErrorKind};
let cpuid: Vec<kvm_bindings::kvm_cpuid_entry2> =
cpuid.iter().map(|e| (*e).into()).collect();
cpuid.resize(256, kvm_bindings::kvm_cpuid_entry2::default());
let kvm_cpuid = kvm_bindings::CpuId::from_entries(&cpuid).map_err(|_| {
vm::HypervisorVmError::InitializeTdx(Error::new(
ErrorKind::Other,
"failed to allocate CpuId",
))
})?;
#[repr(C)]
struct TdxInitVm {
attributes: u64,
max_vcpus: u32,
padding: u32,
tsc_khz: u32,
attributes: u64,
cpuid: u64,
mrconfigid: [u64; 6],
mrowner: [u64; 6],
mrownerconfig: [u64; 6],
cpuid_nent: u32,
cpuid_padding: u32,
cpuid_entries: [kvm_bindings::kvm_cpuid_entry2; 256],
reserved: [u64; 43],
}
let data = TdxInitVm {
attributes: 1 << TDX_ATTR_SEPT_VE_DISABLE,
max_vcpus,
padding: 0,
tsc_khz: 0,
attributes: 0,
cpuid: kvm_cpuid.as_fam_struct_ptr() as u64,
mrconfigid: [0; 6],
mrowner: [0; 6],
mrownerconfig: [0; 6],
cpuid_nent: cpuid.len() as u32,
cpuid_padding: 0,
cpuid_entries: cpuid.as_slice().try_into().unwrap(),
reserved: [0; 43],
};
tdx_command(
@@ -836,23 +837,19 @@ impl vm::Vm for KvmVm {
fn tdx_command(
fd: &RawFd,
command: TdxCommand,
flags: u32,
metadata: u32,
data: u64,
) -> std::result::Result<(), std::io::Error> {
#[repr(C)]
struct TdxIoctlCmd {
command: TdxCommand,
flags: u32,
metadata: u32,
data: u64,
error: u64,
unused: u64,
}
let cmd = TdxIoctlCmd {
command,
flags,
metadata,
data,
error: 0,
unused: 0,
};
// SAFETY: FFI call. All input parameters are valid.
let ret = unsafe {
@@ -918,16 +915,13 @@ impl KvmHypervisor {
}
}
/// Implementation of Hypervisor trait for KVM
///
/// # Examples
///
/// ```
/// # use hypervisor::kvm::KvmHypervisor;
/// # use std::sync::Arc;
/// let kvm = KvmHypervisor::new().unwrap();
/// let hypervisor = Arc::new(kvm);
/// Example:
/// #[cfg(feature = "kvm")]
/// extern crate hypervisor
/// let kvm = hypervisor::kvm::KvmHypervisor::new().unwrap();
/// let hypervisor: Arc<dyn hypervisor::Hypervisor> = Arc::new(kvm);
/// let vm = hypervisor.create_vm().expect("new VM fd creation failed");
/// ```
///
impl hypervisor::Hypervisor for KvmHypervisor {
///
/// Returns the type of the hypervisor
@@ -936,15 +930,13 @@ impl hypervisor::Hypervisor for KvmHypervisor {
HypervisorType::Kvm
}
/// Create a KVM vm object of a specific VM type and return the object as Vm trait object
///
/// # Examples
///
/// ```
/// # use hypervisor::kvm::KvmHypervisor;
/// use hypervisor::kvm::KvmVm;
/// Example
/// # extern crate hypervisor;
/// # use hypervisor::KvmHypervisor;
/// use hypervisor::KvmVm;
/// let hypervisor = KvmHypervisor::new().unwrap();
/// let vm = hypervisor.create_vm_with_type(0).unwrap();
/// ```
/// let vm = hypervisor.create_vm_with_type(KvmVmType::LegacyVm).unwrap()
///
fn create_vm_with_type(&self, vm_type: u64) -> hypervisor::Result<Arc<dyn vm::Vm>> {
let fd: VmFd;
loop {
@@ -998,15 +990,13 @@ impl hypervisor::Hypervisor for KvmHypervisor {
}
/// Create a KVM vm object and return the object as Vm trait object
///
/// # Examples
///
/// ```
/// # use hypervisor::kvm::KvmHypervisor;
/// use hypervisor::kvm::KvmVm;
/// Example
/// # extern crate hypervisor;
/// # use hypervisor::KvmHypervisor;
/// use hypervisor::KvmVm;
/// let hypervisor = KvmHypervisor::new().unwrap();
/// let vm = hypervisor.create_vm().unwrap();
/// ```
/// let vm = hypervisor.create_vm().unwrap()
///
fn create_vm(&self) -> hypervisor::Result<Arc<dyn vm::Vm>> {
#[allow(unused_mut)]
let mut vm_type: u64 = 0; // Create with default platform type
@@ -1031,7 +1021,7 @@ impl hypervisor::Hypervisor for KvmHypervisor {
///
/// X86 specific call to get the system supported CPUID values.
///
fn get_supported_cpuid(&self) -> hypervisor::Result<Vec<CpuIdEntry>> {
fn get_cpuid(&self) -> hypervisor::Result<Vec<CpuIdEntry>> {
let kvm_cpuid = self
.kvm
.get_supported_cpuid(kvm_bindings::KVM_MAX_CPUID_ENTRIES)
@@ -1084,11 +1074,6 @@ impl hypervisor::Hypervisor for KvmHypervisor {
self.kvm.get_guest_debug_hw_bps() as usize
}
}
/// Get maximum number of vCPUs
fn get_max_vcpus(&self) -> u32 {
self.kvm.get_max_vcpus().min(u32::MAX as usize) as u32
}
}
/// Vcpu struct for KVM
pub struct KvmVcpu {
@@ -1100,17 +1085,15 @@ pub struct KvmVcpu {
hyperv_synic: AtomicBool,
}
/// Implementation of Vcpu trait for KVM
///
/// # Examples
///
/// ```
/// # use hypervisor::kvm::KvmHypervisor;
/// # use std::sync::Arc;
/// let kvm = KvmHypervisor::new().unwrap();
/// let hypervisor = Arc::new(kvm);
/// Example:
/// #[cfg(feature = "kvm")]
/// extern crate hypervisor
/// let kvm = hypervisor::kvm::KvmHypervisor::new().unwrap();
/// let hypervisor: Arc<dyn hypervisor::Hypervisor> = Arc::new(kvm);
/// let vm = hypervisor.create_vm().expect("new VM fd creation failed");
/// let vcpu = vm.create_vcpu(0, None).unwrap();
/// ```
/// vcpu.get/set().unwrap()
///
impl cpu::Vcpu for KvmVcpu {
#[cfg(target_arch = "x86_64")]
///
@@ -1131,7 +1114,7 @@ impl cpu::Vcpu for KvmVcpu {
#[cfg(target_arch = "aarch64")]
fn get_regs(&self) -> cpu::Result<StandardRegisters> {
let mut state: StandardRegisters = kvm_regs::default();
let mut off = offset_of!(user_pt_regs, regs);
let mut off = offset__of!(user_pt_regs, regs);
// There are 31 user_pt_regs:
// https://elixir.free-electrons.com/linux/v4.14.174/source/arch/arm64/include/uapi/asm/ptrace.h#L72
// These actually are the general-purpose registers of the Armv8-a
@@ -1148,7 +1131,7 @@ impl cpu::Vcpu for KvmVcpu {
// We are now entering the "Other register" section of the ARMv8-a architecture.
// First one, stack pointer.
let off = offset_of!(user_pt_regs, sp);
let off = offset__of!(user_pt_regs, sp);
state.regs.sp = self
.fd
.get_one_reg(arm64_core_reg_id!(KVM_REG_SIZE_U64, off))
@@ -1157,7 +1140,7 @@ impl cpu::Vcpu for KvmVcpu {
.unwrap();
// Second one, the program counter.
let off = offset_of!(user_pt_regs, pc);
let off = offset__of!(user_pt_regs, pc);
state.regs.pc = self
.fd
.get_one_reg(arm64_core_reg_id!(KVM_REG_SIZE_U64, off))
@@ -1166,7 +1149,7 @@ impl cpu::Vcpu for KvmVcpu {
.unwrap();
// Next is the processor state.
let off = offset_of!(user_pt_regs, pstate);
let off = offset__of!(user_pt_regs, pstate);
state.regs.pstate = self
.fd
.get_one_reg(arm64_core_reg_id!(KVM_REG_SIZE_U64, off))
@@ -1175,7 +1158,7 @@ impl cpu::Vcpu for KvmVcpu {
.unwrap();
// The stack pointer associated with EL1
let off = offset_of!(kvm_regs, sp_el1);
let off = offset__of!(kvm_regs, sp_el1);
state.sp_el1 = self
.fd
.get_one_reg(arm64_core_reg_id!(KVM_REG_SIZE_U64, off))
@@ -1185,7 +1168,7 @@ impl cpu::Vcpu for KvmVcpu {
// Exception Link Register for EL1, when taking an exception to EL1, this register
// holds the address to which to return afterwards.
let off = offset_of!(kvm_regs, elr_el1);
let off = offset__of!(kvm_regs, elr_el1);
state.elr_el1 = self
.fd
.get_one_reg(arm64_core_reg_id!(KVM_REG_SIZE_U64, off))
@@ -1194,7 +1177,7 @@ impl cpu::Vcpu for KvmVcpu {
.unwrap();
// Saved Program Status Registers, there are 5 of them used in the kernel.
let mut off = offset_of!(kvm_regs, spsr);
let mut off = offset__of!(kvm_regs, spsr);
for i in 0..KVM_NR_SPSR as usize {
state.spsr[i] = self
.fd
@@ -1207,7 +1190,7 @@ impl cpu::Vcpu for KvmVcpu {
// Now moving on to floting point registers which are stored in the user_fpsimd_state in the kernel:
// https://elixir.free-electrons.com/linux/v4.9.62/source/arch/arm64/include/uapi/asm/kvm.h#L53
let mut off = offset_of!(kvm_regs, fp_regs) + offset_of!(user_fpsimd_state, vregs);
let mut off = offset__of!(kvm_regs, fp_regs) + offset__of!(user_fpsimd_state, vregs);
for i in 0..32 {
state.fp_regs.vregs[i] = self
.fd
@@ -1217,7 +1200,7 @@ impl cpu::Vcpu for KvmVcpu {
}
// Floating-point Status Register
let off = offset_of!(kvm_regs, fp_regs) + offset_of!(user_fpsimd_state, fpsr);
let off = offset__of!(kvm_regs, fp_regs) + offset__of!(user_fpsimd_state, fpsr);
state.fp_regs.fpsr = self
.fd
.get_one_reg(arm64_core_reg_id!(KVM_REG_SIZE_U32, off))
@@ -1226,7 +1209,7 @@ impl cpu::Vcpu for KvmVcpu {
.unwrap();
// Floating-point Control Register
let off = offset_of!(kvm_regs, fp_regs) + offset_of!(user_fpsimd_state, fpcr);
let off = offset__of!(kvm_regs, fp_regs) + offset__of!(user_fpsimd_state, fpcr);
state.fp_regs.fpcr = self
.fd
.get_one_reg(arm64_core_reg_id!(KVM_REG_SIZE_U32, off))
@@ -1255,7 +1238,7 @@ impl cpu::Vcpu for KvmVcpu {
fn set_regs(&self, state: &StandardRegisters) -> cpu::Result<()> {
// The function follows the exact identical order from `state`. Look there
// for some additional info on registers.
let mut off = offset_of!(user_pt_regs, regs);
let mut off = offset__of!(user_pt_regs, regs);
for i in 0..31 {
self.fd
.set_one_reg(
@@ -1266,7 +1249,7 @@ impl cpu::Vcpu for KvmVcpu {
off += std::mem::size_of::<u64>();
}
let off = offset_of!(user_pt_regs, sp);
let off = offset__of!(user_pt_regs, sp);
self.fd
.set_one_reg(
arm64_core_reg_id!(KVM_REG_SIZE_U64, off),
@@ -1274,7 +1257,7 @@ impl cpu::Vcpu for KvmVcpu {
)
.map_err(|e| cpu::HypervisorCpuError::SetCoreRegister(e.into()))?;
let off = offset_of!(user_pt_regs, pc);
let off = offset__of!(user_pt_regs, pc);
self.fd
.set_one_reg(
arm64_core_reg_id!(KVM_REG_SIZE_U64, off),
@@ -1282,7 +1265,7 @@ impl cpu::Vcpu for KvmVcpu {
)
.map_err(|e| cpu::HypervisorCpuError::SetCoreRegister(e.into()))?;
let off = offset_of!(user_pt_regs, pstate);
let off = offset__of!(user_pt_regs, pstate);
self.fd
.set_one_reg(
arm64_core_reg_id!(KVM_REG_SIZE_U64, off),
@@ -1290,7 +1273,7 @@ impl cpu::Vcpu for KvmVcpu {
)
.map_err(|e| cpu::HypervisorCpuError::SetCoreRegister(e.into()))?;
let off = offset_of!(kvm_regs, sp_el1);
let off = offset__of!(kvm_regs, sp_el1);
self.fd
.set_one_reg(
arm64_core_reg_id!(KVM_REG_SIZE_U64, off),
@@ -1298,7 +1281,7 @@ impl cpu::Vcpu for KvmVcpu {
)
.map_err(|e| cpu::HypervisorCpuError::SetCoreRegister(e.into()))?;
let off = offset_of!(kvm_regs, elr_el1);
let off = offset__of!(kvm_regs, elr_el1);
self.fd
.set_one_reg(
arm64_core_reg_id!(KVM_REG_SIZE_U64, off),
@@ -1306,7 +1289,7 @@ impl cpu::Vcpu for KvmVcpu {
)
.map_err(|e| cpu::HypervisorCpuError::SetCoreRegister(e.into()))?;
let mut off = offset_of!(kvm_regs, spsr);
let mut off = offset__of!(kvm_regs, spsr);
for i in 0..KVM_NR_SPSR as usize {
self.fd
.set_one_reg(
@@ -1317,7 +1300,7 @@ impl cpu::Vcpu for KvmVcpu {
off += std::mem::size_of::<u64>();
}
let mut off = offset_of!(kvm_regs, fp_regs) + offset_of!(user_fpsimd_state, vregs);
let mut off = offset__of!(kvm_regs, fp_regs) + offset__of!(user_fpsimd_state, vregs);
for i in 0..32 {
self.fd
.set_one_reg(
@@ -1328,7 +1311,7 @@ impl cpu::Vcpu for KvmVcpu {
off += mem::size_of::<u128>();
}
let off = offset_of!(kvm_regs, fp_regs) + offset_of!(user_fpsimd_state, fpsr);
let off = offset__of!(kvm_regs, fp_regs) + offset__of!(user_fpsimd_state, fpsr);
self.fd
.set_one_reg(
arm64_core_reg_id!(KVM_REG_SIZE_U32, off),
@@ -1336,7 +1319,7 @@ impl cpu::Vcpu for KvmVcpu {
)
.map_err(|e| cpu::HypervisorCpuError::SetCoreRegister(e.into()))?;
let off = offset_of!(kvm_regs, fp_regs) + offset_of!(user_fpsimd_state, fpcr);
let off = offset__of!(kvm_regs, fp_regs) + offset__of!(user_fpsimd_state, fpcr);
self.fd
.set_one_reg(
arm64_core_reg_id!(KVM_REG_SIZE_U32, off),
@@ -1717,8 +1700,8 @@ impl cpu::Vcpu for KvmVcpu {
// it to the corresponding KVM ID, and call `KVM_GET_ONE_REG` API to
// get the value of the system parameter.
//
let id: u64 = KVM_REG_ARM64
| KVM_REG_SIZE_U64
let id: u64 = KVM_REG_ARM64 as u64
| KVM_REG_SIZE_U64 as u64
| KVM_REG_ARM64_SYSREG as u64
| ((((sys_reg) >> 5)
& (KVM_REG_ARM64_SYSREG_OP0_MASK
@@ -1750,10 +1733,10 @@ impl cpu::Vcpu for KvmVcpu {
const PSTATE_FAULT_BITS_64: u64 =
PSR_MODE_EL1h | PSR_A_BIT | PSR_F_BIT | PSR_I_BIT | PSR_D_BIT;
let kreg_off = offset_of!(kvm_regs, regs);
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;
let pstate = offset__of!(user_pt_regs, pstate) + kreg_off;
self.fd
.set_one_reg(
arm64_core_reg_id!(KVM_REG_SIZE_U64, pstate),
@@ -1764,7 +1747,7 @@ impl cpu::Vcpu for KvmVcpu {
// 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;
let pc = offset__of!(user_pt_regs, pc) + kreg_off;
self.fd
.set_one_reg(arm64_core_reg_id!(KVM_REG_SIZE_U64, pc), boot_ip.into())
.map_err(|e| cpu::HypervisorCpuError::SetCoreRegister(e.into()))?;
@@ -1773,7 +1756,7 @@ impl cpu::Vcpu for KvmVcpu {
// "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;
let regs0 = offset__of!(user_pt_regs, regs) + kreg_off;
self.fd
.set_one_reg(
arm64_core_reg_id!(KVM_REG_SIZE_U64, regs0),
@@ -1812,10 +1795,11 @@ impl cpu::Vcpu for KvmVcpu {
/// # Example
///
/// ```rust
/// # use hypervisor::kvm::KvmHypervisor;
/// # extern crate hypervisor;
/// # use hypervisor::KvmHypervisor;
/// # use std::sync::Arc;
/// let kvm = KvmHypervisor::new().unwrap();
/// let hv = Arc::new(kvm);
/// let kvm = hypervisor::kvm::KvmHypervisor::new().unwrap();
/// let hv: Arc<dyn hypervisor::Hypervisor> = Arc::new(kvm);
/// let vm = hv.create_vm().expect("new VM fd creation failed");
/// vm.enable_split_irq().unwrap();
/// let vcpu = vm.create_vcpu(0, None).unwrap();
@@ -1983,10 +1967,11 @@ impl cpu::Vcpu for KvmVcpu {
/// # Example
///
/// ```rust
/// # use hypervisor::kvm::KvmHypervisor;
/// # extern crate hypervisor;
/// # use hypervisor::KvmHypervisor;
/// # use std::sync::Arc;
/// let kvm = KvmHypervisor::new().unwrap();
/// let hv = Arc::new(kvm);
/// let kvm = hypervisor::kvm::KvmHypervisor::new().unwrap();
/// let hv: Arc<dyn hypervisor::Hypervisor> = Arc::new(kvm);
/// let vm = hv.create_vm().expect("new VM fd creation failed");
/// vm.enable_split_irq().unwrap();
/// let vcpu = vm.create_vcpu(0, None).unwrap();
@@ -2081,7 +2066,6 @@ impl cpu::Vcpu for KvmVcpu {
#[cfg(feature = "tdx")]
fn get_tdx_exit_details(&mut self) -> cpu::Result<TdxExitDetails> {
let kvm_run = self.fd.get_kvm_run();
// SAFETY: accessing a union field in a valid structure
let tdx_vmcall = unsafe { &mut kvm_run.__bindgen_anon_1.tdx.u.vmcall };
tdx_vmcall.status_code = TDG_VP_VMCALL_INVALID_OPERAND;
@@ -2105,7 +2089,6 @@ impl cpu::Vcpu for KvmVcpu {
#[cfg(feature = "tdx")]
fn set_tdx_status(&mut self, status: TdxExitStatus) {
let kvm_run = self.fd.get_kvm_run();
// SAFETY: accessing a union field in a valid structure
let tdx_vmcall = unsafe { &mut kvm_run.__bindgen_anon_1.tdx.u.vmcall };
tdx_vmcall.status_code = match status {

View File

@@ -18,6 +18,8 @@
//! - arm64
//!
#![allow(clippy::significant_drop_in_scrutinee)]
#[macro_use]
extern crate anyhow;
#[cfg(target_arch = "x86_64")]
@@ -36,7 +38,7 @@ pub mod kvm;
#[cfg(all(feature = "mshv", target_arch = "x86_64"))]
pub mod mshv;
/// Hypervisor related module
/// Hypevisor related module
mod hypervisor;
/// Vm related module
@@ -48,11 +50,9 @@ mod cpu;
/// Device related module
mod device;
pub use crate::hypervisor::{Hypervisor, HypervisorError};
#[cfg(target_arch = "x86_64")]
pub use cpu::CpuVendor;
pub use cpu::{HypervisorCpuError, Vcpu, VmExit};
pub use device::HypervisorDeviceError;
pub use hypervisor::{Hypervisor, HypervisorError};
#[cfg(all(feature = "kvm", target_arch = "aarch64"))]
pub use kvm::{aarch64, GicState};
use std::sync::Arc;
@@ -61,11 +61,9 @@ pub use vm::{
Vm, VmOps,
};
#[derive(Debug, Copy, Clone)]
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum HypervisorType {
#[cfg(feature = "kvm")]
Kvm,
#[cfg(feature = "mshv")]
Mshv,
}

View File

@@ -38,7 +38,9 @@ use std::fs::File;
use std::os::unix::io::AsRawFd;
#[cfg(target_arch = "x86_64")]
use crate::arch::x86::{CpuIdEntry, FpuState, MsrEntry};
use crate::arch::x86::{
CpuIdEntry, FpuState, LapicState, MsrEntry, SpecialRegisters, StandardRegisters,
};
const DIRTY_BITMAP_CLEAR_DIRTY: u64 = 0x4;
const DIRTY_BITMAP_SET_DIRTY: u64 = 0x8;
@@ -193,16 +195,13 @@ impl MshvHypervisor {
}
}
/// Implementation of Hypervisor trait for Mshv
///
/// # Examples
///
/// ```
/// # use hypervisor::mshv::MshvHypervisor;
/// # use std::sync::Arc;
/// let mshv = MshvHypervisor::new().unwrap();
/// let hypervisor = Arc::new(mshv);
/// Example:
/// #[cfg(feature = "mshv")]
/// extern crate hypervisor
/// let mshv = hypervisor::mshv::MshvHypervisor::new().unwrap();
/// let hypervisor: Arc<dyn hypervisor::Hypervisor> = Arc::new(mshv);
/// let vm = hypervisor.create_vm().expect("new VM fd creation failed");
/// ```
///
impl hypervisor::Hypervisor for MshvHypervisor {
///
/// Returns the type of the hypervisor
@@ -211,16 +210,13 @@ impl hypervisor::Hypervisor for MshvHypervisor {
HypervisorType::Mshv
}
/// Create a mshv vm object and return the object as Vm trait object
///
/// # Examples
///
/// ```
/// Example
/// # extern crate hypervisor;
/// # use hypervisor::mshv::MshvHypervisor;
/// use hypervisor::mshv::MshvVm;
/// # use hypervisor::MshvHypervisor;
/// use hypervisor::MshvVm;
/// let hypervisor = MshvHypervisor::new().unwrap();
/// let vm = hypervisor.create_vm().unwrap();
/// ```
/// let vm = hypervisor.create_vm().unwrap()
///
fn create_vm(&self) -> hypervisor::Result<Arc<dyn vm::Vm>> {
let fd: VmFd;
loop {
@@ -274,16 +270,9 @@ impl hypervisor::Hypervisor for MshvHypervisor {
///
/// Get the supported CpuID
///
fn get_supported_cpuid(&self) -> hypervisor::Result<Vec<CpuIdEntry>> {
fn get_cpuid(&self) -> hypervisor::Result<Vec<CpuIdEntry>> {
Ok(Vec::new())
}
/// Get maximum number of vCPUs
fn get_max_vcpus(&self) -> u32 {
// TODO: Using HV_MAXIMUM_PROCESSORS would be better
// but the ioctl API is limited to u8
256
}
}
/// Vcpu struct for Microsoft Hypervisor
@@ -296,23 +285,21 @@ pub struct MshvVcpu {
}
/// Implementation of Vcpu trait for Microsoft Hypervisor
///
/// # Examples
///
/// ```
/// # use hypervisor::mshv::MshvHypervisor;
/// # use std::sync::Arc;
/// let mshv = MshvHypervisor::new().unwrap();
/// let hypervisor = Arc::new(mshv);
/// Example:
/// #[cfg(feature = "mshv")]
/// extern crate hypervisor
/// let mshv = hypervisor::mshv::MshvHypervisor::new().unwrap();
/// let hypervisor: Arc<dyn hypervisor::Hypervisor> = Arc::new(mshv);
/// let vm = hypervisor.create_vm().expect("new VM fd creation failed");
/// let vcpu = vm.create_vcpu(0, None).unwrap();
/// ```
/// let vcpu = vm.create_vcpu(0).unwrap();
/// vcpu.get/set().unwrap()
///
impl cpu::Vcpu for MshvVcpu {
#[cfg(target_arch = "x86_64")]
///
/// Returns the vCPU general purpose registers.
///
fn get_regs(&self) -> cpu::Result<crate::arch::x86::StandardRegisters> {
fn get_regs(&self) -> cpu::Result<StandardRegisters> {
Ok(self
.fd
.get_regs()
@@ -323,7 +310,7 @@ impl cpu::Vcpu for MshvVcpu {
///
/// Sets the vCPU general purpose registers.
///
fn set_regs(&self, regs: &crate::arch::x86::StandardRegisters) -> cpu::Result<()> {
fn set_regs(&self, regs: &StandardRegisters) -> cpu::Result<()> {
let regs = (*regs).into();
self.fd
.set_regs(&regs)
@@ -333,7 +320,7 @@ impl cpu::Vcpu for MshvVcpu {
///
/// Returns the vCPU special registers.
///
fn get_sregs(&self) -> cpu::Result<crate::arch::x86::SpecialRegisters> {
fn get_sregs(&self) -> cpu::Result<SpecialRegisters> {
Ok(self
.fd
.get_sregs()
@@ -344,7 +331,7 @@ impl cpu::Vcpu for MshvVcpu {
///
/// Sets the vCPU special registers.
///
fn set_sregs(&self, sregs: &crate::arch::x86::SpecialRegisters) -> cpu::Result<()> {
fn set_sregs(&self, sregs: &SpecialRegisters) -> cpu::Result<()> {
let sregs = (*sregs).into();
self.fd
.set_sregs(&sregs)
@@ -459,10 +446,10 @@ impl cpu::Vcpu for MshvVcpu {
/* Advance RIP and update RAX */
let arr_reg_name_value = [
(
hv_register_name_HV_X64_REGISTER_RIP,
hv_register_name::HV_X64_REGISTER_RIP,
info.header.rip + insn_len,
),
(hv_register_name_HV_X64_REGISTER_RAX, ret_rax),
(hv_register_name::HV_X64_REGISTER_RAX, ret_rax),
];
set_registers_64!(self.fd, arr_reg_name_value)
.map_err(|e| cpu::HypervisorCpuError::SetRegister(e.into()))?;
@@ -471,13 +458,12 @@ impl cpu::Vcpu for MshvVcpu {
_ => {}
}
// SAFETY: access_info is valid, otherwise we won't be here
assert!(
// SAFETY: access_info is valid, otherwise we won't be here
(unsafe { access_info.__bindgen_anon_1.string_op() } != 1),
"String IN/OUT not supported"
);
assert!(
// SAFETY: access_info is valid, otherwise we won't be here
(unsafe { access_info.__bindgen_anon_1.rep_prefix() } != 1),
"Rep IN/OUT not supported"
);
@@ -508,10 +494,10 @@ impl cpu::Vcpu for MshvVcpu {
/* Advance RIP and update RAX */
let arr_reg_name_value = [
(
hv_register_name_HV_X64_REGISTER_RIP,
hv_register_name::HV_X64_REGISTER_RIP,
info.header.rip + insn_len,
),
(hv_register_name_HV_X64_REGISTER_RAX, ret_rax),
(hv_register_name::HV_X64_REGISTER_RAX, ret_rax),
];
set_registers_64!(self.fd, arr_reg_name_value)
.map_err(|e| cpu::HypervisorCpuError::SetRegister(e.into()))?;
@@ -581,14 +567,8 @@ impl cpu::Vcpu for MshvVcpu {
///
/// X86 specific call to setup the CPUID registers.
///
fn set_cpuid2(&self, cpuid: &[CpuIdEntry]) -> cpu::Result<()> {
let cpuid: Vec<mshv_bindings::hv_cpuid_entry> = cpuid.iter().map(|e| (*e).into()).collect();
let mshv_cpuid = <CpuId>::from_entries(&cpuid)
.map_err(|_| cpu::HypervisorCpuError::SetCpuid(anyhow!("failed to create CpuId")))?;
self.fd
.register_intercept_result_cpuid(&mshv_cpuid)
.map_err(|e| cpu::HypervisorCpuError::SetCpuid(e.into()))
fn set_cpuid2(&self, _cpuid: &[CpuIdEntry]) -> cpu::Result<()> {
Ok(())
}
#[cfg(target_arch = "x86_64")]
///
@@ -601,7 +581,7 @@ impl cpu::Vcpu for MshvVcpu {
///
/// Returns the state of the LAPIC (Local Advanced Programmable Interrupt Controller).
///
fn get_lapic(&self) -> cpu::Result<crate::arch::x86::LapicState> {
fn get_lapic(&self) -> cpu::Result<LapicState> {
Ok(self
.fd
.get_lapic()
@@ -612,7 +592,7 @@ impl cpu::Vcpu for MshvVcpu {
///
/// Sets the state of the LAPIC (Local Advanced Programmable Interrupt Controller).
///
fn set_lapic(&self, lapic: &crate::arch::x86::LapicState) -> cpu::Result<()> {
fn set_lapic(&self, lapic: &LapicState) -> cpu::Result<()> {
let lapic: mshv_bindings::LapicState = (*lapic).clone().into();
self.fd
.set_lapic(&lapic)
@@ -936,17 +916,15 @@ impl MshvVm {
///
/// Implementation of Vm trait for Mshv
///
/// # Examples
///
/// ```
/// Example:
/// #[cfg(feature = "mshv")]
/// # extern crate hypervisor;
/// # use hypervisor::mshv::MshvHypervisor;
/// # use std::sync::Arc;
/// # use hypervisor::MshvHypervisor;
/// let mshv = MshvHypervisor::new().unwrap();
/// let hypervisor = Arc::new(mshv);
/// let hypervisor: Arc<dyn hypervisor::Hypervisor> = Arc::new(mshv);
/// let vm = hypervisor.create_vm().expect("new VM fd creation failed");
/// ```
/// vm.set/get().unwrap()
///
impl vm::Vm for MshvVm {
#[cfg(target_arch = "x86_64")]
///

View File

@@ -10,7 +10,6 @@
#[repr(C)]
#[derive(Default)]
pub struct __IncompleteArrayField<T>(::std::marker::PhantomData<T>, [T; 0]);
#[allow(clippy::missing_safety_doc)]
impl<T> __IncompleteArrayField<T> {
#[inline]
pub const fn new() -> Self {

View File

@@ -10,7 +10,6 @@
#[repr(C)]
#[derive(Default)]
pub struct __IncompleteArrayField<T>(::std::marker::PhantomData<T>, [T; 0]);
#[allow(clippy::missing_safety_doc)]
impl<T> __IncompleteArrayField<T> {
#[inline]
pub const fn new() -> Self {
@@ -1188,7 +1187,6 @@ pub struct ifreq {
impl Default for ifreq {
fn default() -> Self {
// SAFETY: all zeros is a valid pattern for this data type
unsafe { std::mem::zeroed() }
}
}

View File

@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the THIRD-PARTY file.
#![allow(clippy::all)]
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
@@ -27,12 +28,10 @@ pub mod inn;
// generated with bindgen /usr/include/linux/sockios.h --no-unstable-rust
// --constified-enum '*' --with-derive-default
pub mod sockios;
pub use if_tun::{
sock_fprog, IFF_MULTI_QUEUE, IFF_NO_PI, IFF_TAP, IFF_VNET_HDR, TUN_F_CSUM, TUN_F_TSO4,
TUN_F_TSO6, TUN_F_TSO_ECN, TUN_F_UFO,
};
pub use iff::{ifreq, net_device_flags_IFF_UP, setsockopt, sockaddr, AF_INET};
pub use inn::sockaddr_in;
pub use if_tun::*;
pub use iff::*;
pub use inn::*;
pub use sockios::*;
pub const TUNTAP: ::std::os::raw::c_uint = 84;

View File

@@ -5,24 +5,24 @@ authors = ["The Chromium OS Authors"]
edition = "2021"
[dependencies]
epoll = "4.3.3"
getrandom = "0.2.10"
libc = "0.2.139"
epoll = "4.3.1"
getrandom = "0.2.8"
libc = "0.2.138"
log = "0.4.17"
net_gen = { path = "../net_gen" }
rate_limiter = { path = "../rate_limiter" }
serde = "1.0.164"
thiserror = "1.0.40"
versionize = "0.1.10"
serde = "1.0.150"
thiserror = "1.0.37"
versionize = "0.1.9"
versionize_derive = "0.1.4"
virtio-bindings = "0.2.0"
virtio-queue = "0.8.0"
vm-memory = { version = "0.11.0", features = ["backend-mmap", "backend-atomic", "backend-bitmap"] }
virtio-bindings = "0.1.0"
virtio-queue = "0.7.0"
vm-memory = { version = "0.10.0", features = ["backend-mmap", "backend-atomic", "backend-bitmap"] }
vm-virtio = { path = "../vm-virtio" }
vmm-sys-util = "0.11.0"
[dev-dependencies]
once_cell = "1.18.0"
pnet = "0.33.0"
pnet_datalink = "0.33.0"
serde_json = "1.0.96"
once_cell = "1.16.0"
pnet = "0.31.0"
pnet_datalink = "0.31.0"
serde_json = "1.0.89"

View File

@@ -6,7 +6,7 @@ use crate::GuestMemoryMmap;
use crate::Tap;
use libc::c_uint;
use std::sync::Arc;
use virtio_bindings::virtio_net::{
use virtio_bindings::bindings::virtio_net::{
VIRTIO_NET_CTRL_GUEST_OFFLOADS, VIRTIO_NET_CTRL_GUEST_OFFLOADS_SET, VIRTIO_NET_CTRL_MQ,
VIRTIO_NET_CTRL_MQ_VQ_PAIRS_MAX, VIRTIO_NET_CTRL_MQ_VQ_PAIRS_MIN,
VIRTIO_NET_CTRL_MQ_VQ_PAIRS_SET, VIRTIO_NET_ERR, VIRTIO_NET_F_GUEST_CSUM,

View File

@@ -21,7 +21,7 @@ use std::{io, mem, net};
use thiserror::Error;
use versionize::{VersionMap, Versionize, VersionizeResult};
use versionize_derive::Versionize;
use virtio_bindings::virtio_net::{
use virtio_bindings::bindings::virtio_net::{
virtio_net_hdr_v1, VIRTIO_NET_CTRL_MQ_VQ_PAIRS_MAX, VIRTIO_NET_CTRL_MQ_VQ_PAIRS_MIN,
VIRTIO_NET_F_GUEST_CSUM, VIRTIO_NET_F_GUEST_ECN, VIRTIO_NET_F_GUEST_TSO4,
VIRTIO_NET_F_GUEST_TSO6, VIRTIO_NET_F_GUEST_UFO, VIRTIO_NET_F_MAC, VIRTIO_NET_F_MQ,
@@ -66,34 +66,32 @@ fn create_sockaddr(ip_addr: net::Ipv4Addr) -> net_gen::sockaddr {
let addr_in = net_gen::sockaddr_in {
sin_family: net_gen::AF_INET as u16,
sin_port: 0,
// SAFETY: ip_addr can be safely transmute to in_addr
sin_addr: unsafe { mem::transmute(ip_addr.octets()) },
__pad: [0; 8usize],
};
// SAFETY: addr_in can be safely transmute to sockaddr
unsafe { mem::transmute(addr_in) }
}
fn create_inet_socket() -> Result<net::UdpSocket> {
// SAFETY: we check the return value.
// This is safe since we check the return value.
let sock = unsafe { libc::socket(libc::AF_INET, libc::SOCK_DGRAM, 0) };
if sock < 0 {
return Err(Error::CreateSocket(IoError::last_os_error()));
}
// SAFETY: nothing else will use or hold onto the raw sock fd.
// This is safe; nothing else will use or hold onto the raw sock fd.
Ok(unsafe { net::UdpSocket::from_raw_fd(sock) })
}
fn create_unix_socket() -> Result<net::UdpSocket> {
// SAFETY: we check the return value.
// This is safe since we check the return value.
let sock = unsafe { libc::socket(libc::AF_UNIX, libc::SOCK_DGRAM, 0) };
if sock < 0 {
return Err(Error::CreateSocket(IoError::last_os_error()));
}
// SAFETY: nothing else will use or hold onto the raw sock fd.
// This is safe; nothing else will use or hold onto the raw sock fd.
Ok(unsafe { net::UdpSocket::from_raw_fd(sock) })
}

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