mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
Compare commits
44 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3a63fa2613 | ||
|
|
3a63143f33 | ||
|
|
064c1e2c8b | ||
|
|
15b9d14876 | ||
|
|
9da363e79b | ||
|
|
bbfd810c3b | ||
|
|
8fb86d2284 | ||
|
|
797110cca1 | ||
|
|
19ca5c0b84 | ||
|
|
9fe9b8504d | ||
|
|
4fc3dd5004 | ||
|
|
6bc6365c35 | ||
|
|
e139cdfd69 | ||
|
|
541de8b757 | ||
|
|
184dac70a0 | ||
|
|
022b489e7b | ||
|
|
399e2f9f7d | ||
|
|
22cc96494f | ||
|
|
f98402ec15 | ||
|
|
acc54ade7b | ||
|
|
0ebbb3f8a2 | ||
|
|
95511287ec | ||
|
|
5a3af30e6a | ||
|
|
034b48faf7 | ||
|
|
ba3e02ce86 | ||
|
|
5492259af9 | ||
|
|
cc1254d5e1 | ||
|
|
34bb3319d4 | ||
|
|
d530569ac2 | ||
|
|
75956e64ec | ||
|
|
ae646c2a00 | ||
|
|
04d3e5bbf5 | ||
|
|
cbe972659c | ||
|
|
1e4e03d110 | ||
|
|
4876f7550d | ||
|
|
c0146e3ef1 | ||
|
|
77a205881b | ||
|
|
321421c53e | ||
|
|
48a87e699d | ||
|
|
147a800d5d | ||
|
|
eaf8cbd47d | ||
|
|
9d24e862eb | ||
|
|
11324ac21c | ||
|
|
ce75865e2c |
7
.github/workflows/audit.yaml
vendored
7
.github/workflows/audit.yaml
vendored
@@ -4,13 +4,12 @@ on:
|
|||||||
paths:
|
paths:
|
||||||
- '**/Cargo.toml'
|
- '**/Cargo.toml'
|
||||||
- '**/Cargo.lock'
|
- '**/Cargo.lock'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
security_audit:
|
security_audit:
|
||||||
name: Audit
|
name: Audit
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v1
|
||||||
- uses: actions-rust-lang/audit@v1
|
- uses: actions-rs/audit-check@v1
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
22
.github/workflows/build.yaml
vendored
22
.github/workflows/build.yaml
vendored
@@ -1,11 +1,9 @@
|
|||||||
name: Cloud Hypervisor Build
|
name: Cloud Hypervisor Build
|
||||||
on: [pull_request, merge_group]
|
on: [pull_request, create]
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
if: github.event_name == 'pull_request'
|
||||||
name: Build
|
name: Build
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
@@ -15,13 +13,13 @@ jobs:
|
|||||||
- stable
|
- stable
|
||||||
- beta
|
- beta
|
||||||
- nightly
|
- nightly
|
||||||
- "1.77.0"
|
- "1.62"
|
||||||
target:
|
target:
|
||||||
- x86_64-unknown-linux-gnu
|
- x86_64-unknown-linux-gnu
|
||||||
- x86_64-unknown-linux-musl
|
- x86_64-unknown-linux-musl
|
||||||
steps:
|
steps:
|
||||||
- name: Code checkout
|
- name: Code checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
@@ -29,10 +27,11 @@ jobs:
|
|||||||
run: sudo apt install -y musl-tools
|
run: sudo apt install -y musl-tools
|
||||||
|
|
||||||
- name: Install Rust toolchain (${{ matrix.rust }})
|
- name: Install Rust toolchain (${{ matrix.rust }})
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
toolchain: ${{ matrix.rust }}
|
toolchain: ${{ matrix.rust }}
|
||||||
target: ${{ matrix.target }}
|
target: ${{ matrix.target }}
|
||||||
|
override: true
|
||||||
|
|
||||||
- name: Build (default features)
|
- 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 -D clippy::undocumented_unsafe_blocks
|
||||||
@@ -43,21 +42,12 @@ jobs:
|
|||||||
- name: Build (default features + tdx)
|
- name: Build (default features + tdx)
|
||||||
run: cargo rustc --locked --bin cloud-hypervisor --features "tdx" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
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
|
|
||||||
|
|
||||||
- name: Build (default features + guest_debug)
|
- 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 -D clippy::undocumented_unsafe_blocks
|
||||||
|
|
||||||
- name: Build (mshv)
|
- 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 -D clippy::undocumented_unsafe_blocks
|
||||||
|
|
||||||
- name: Build (sev_snp)
|
|
||||||
run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "sev_snp" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
|
||||||
|
|
||||||
- name: Build (igvm)
|
|
||||||
run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "igvm" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
|
||||||
|
|
||||||
- name: Build (mshv + kvm)
|
- 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 -D clippy::undocumented_unsafe_blocks
|
||||||
|
|
||||||
|
|||||||
8
.github/workflows/dco.yaml
vendored
8
.github/workflows/dco.yaml
vendored
@@ -1,18 +1,16 @@
|
|||||||
name: DCO
|
name: DCO
|
||||||
on: [pull_request, merge_group]
|
on:
|
||||||
|
pull_request:
|
||||||
jobs:
|
jobs:
|
||||||
check:
|
check:
|
||||||
name: DCO Check ("Signed-Off-By")
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v2
|
||||||
- name: Set up Python 3.x
|
- name: Set up Python 3.x
|
||||||
uses: actions/setup-python@v1
|
uses: actions/setup-python@v1
|
||||||
with:
|
with:
|
||||||
python-version: '3.x'
|
python-version: '3.x'
|
||||||
- name: Check DCO
|
- name: Check DCO
|
||||||
if: ${{ github.event_name == 'pull_request' }}
|
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
8
.github/workflows/docker-image.yaml
vendored
8
.github/workflows/docker-image.yaml
vendored
@@ -1,13 +1,11 @@
|
|||||||
name: Cloud Hypervisor's Docker image update
|
name: Cloud Hypervisor's Docker image update
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: main
|
branches: main
|
||||||
paths: resources/Dockerfile
|
paths: resources/Dockerfile
|
||||||
pull_request:
|
pull_request:
|
||||||
paths: resources/Dockerfile
|
paths: resources/Dockerfile
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
env:
|
env:
|
||||||
REGISTRY: ghcr.io
|
REGISTRY: ghcr.io
|
||||||
@@ -18,7 +16,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Code checkout
|
- name: Code checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v1
|
uses: docker/setup-qemu-action@v1
|
||||||
@@ -41,7 +39,7 @@ jobs:
|
|||||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||||
# generate Docker tags based on the following events/attributes
|
# generate Docker tags based on the following events/attributes
|
||||||
tags: |
|
tags: |
|
||||||
type=raw,value=20240507-0
|
type=raw,value={{date 'YYYYMMDD'}}-0
|
||||||
type=sha
|
type=sha
|
||||||
|
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
|
|||||||
21
.github/workflows/fuzz-build.yaml
vendored
21
.github/workflows/fuzz-build.yaml
vendored
@@ -1,11 +1,9 @@
|
|||||||
name: Cloud Hypervisor Cargo Fuzz Build
|
name: Cloud Hypervisor Cargo Fuzz Build
|
||||||
on: [pull_request, merge_group]
|
on: [pull_request, create]
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
if: github.event_name == 'pull_request'
|
||||||
name: Cargo Fuzz Build
|
name: Cargo Fuzz Build
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
@@ -14,19 +12,18 @@ jobs:
|
|||||||
- nightly
|
- nightly
|
||||||
target:
|
target:
|
||||||
- x86_64-unknown-linux-gnu
|
- x86_64-unknown-linux-gnu
|
||||||
env:
|
|
||||||
RUSTFLAGS: -D warnings
|
|
||||||
steps:
|
steps:
|
||||||
- name: Code checkout
|
- name: Code checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v2
|
||||||
- name: Install Rust toolchain (${{ matrix.rust }})
|
- name: Install Rust toolchain (${{ matrix.rust }})
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
toolchain: ${{ matrix.rust }}
|
toolchain: ${{ matrix.rust }}
|
||||||
target: ${{ matrix.target }}
|
target: ${{ matrix.target }}
|
||||||
|
override: true
|
||||||
- name: Install Cargo fuzz
|
- name: Install Cargo fuzz
|
||||||
run: cargo install cargo-fuzz
|
# Temporary fix for cargo-fuzz on latest nightly: https://github.com/rust-fuzz/cargo-fuzz/issues/276
|
||||||
- name: Fuzz Build
|
#run: cargo install cargo-fuzz
|
||||||
|
run: cargo install --git https://github.com/rust-fuzz/cargo-fuzz --rev b4df3e58f767b5cad8d1aa6753961003f56f3609
|
||||||
|
- name: Cargo Fuzz Build
|
||||||
run: cargo fuzz build
|
run: cargo fuzz build
|
||||||
- name: Fuzz Check
|
|
||||||
run: cargo fuzz check
|
|
||||||
|
|||||||
25
.github/workflows/gitlint.yaml
vendored
25
.github/workflows/gitlint.yaml
vendored
@@ -1,25 +0,0 @@
|
|||||||
name: Commit messages check
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
gitlint:
|
|
||||||
name: Check commit messages
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
ref: ${{ github.event.pull_request.head.sha }}
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Set up Python 3.10
|
|
||||||
uses: actions/setup-python@v3
|
|
||||||
with:
|
|
||||||
python-version: "3.10"
|
|
||||||
- name: Install dependencies
|
|
||||||
run: |
|
|
||||||
python -m pip install --upgrade pip
|
|
||||||
pip install --upgrade gitlint
|
|
||||||
- name: Lint git commit messages
|
|
||||||
run: |
|
|
||||||
gitlint --commits origin/$GITHUB_BASE_REF..
|
|
||||||
25
.github/workflows/hadolint.yaml
vendored
25
.github/workflows/hadolint.yaml
vendored
@@ -1,25 +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@v4
|
|
||||||
|
|
||||||
- name: Lint Dockerfile
|
|
||||||
uses: hadolint/hadolint-action@master
|
|
||||||
with:
|
|
||||||
dockerfile: ./resources/Dockerfile
|
|
||||||
format: tty
|
|
||||||
no-fail: false
|
|
||||||
verbose: true
|
|
||||||
failure-threshold: info
|
|
||||||
54
.github/workflows/integration-arm64.yaml
vendored
54
.github/workflows/integration-arm64.yaml
vendored
@@ -1,54 +0,0 @@
|
|||||||
name: Cloud Hypervisor Tests (ARM64)
|
|
||||||
on: [pull_request, merge_group]
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
timeout-minutes: 60
|
|
||||||
name: Tests (ARM64)
|
|
||||||
runs-on: focal-arm64
|
|
||||||
steps:
|
|
||||||
- name: Fix workspace permissions
|
|
||||||
run: sudo chown -R github-runner:github-runner ${GITHUB_WORKSPACE}
|
|
||||||
- name: Code checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Run unit tests (musl)
|
|
||||||
run: scripts/dev_cli.sh tests --unit --libc musl
|
|
||||||
- name: Load openvswitch module
|
|
||||||
run: sudo modprobe openvswitch
|
|
||||||
- name: Run integration tests (musl)
|
|
||||||
timeout-minutes: 30
|
|
||||||
run: scripts/dev_cli.sh tests --integration --libc musl
|
|
||||||
- name: Install Azure CLI
|
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
|
||||||
run: |
|
|
||||||
sudo apt install -y ca-certificates curl apt-transport-https lsb-release gnupg
|
|
||||||
curl -sL https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/microsoft.gpg > /dev/null
|
|
||||||
echo "deb [arch=arm64] https://packages.microsoft.com/repos/azure-cli/ focal main" | sudo tee /etc/apt/sources.list.d/azure-cli.list
|
|
||||||
sudo apt update
|
|
||||||
sudo apt install -y azure-cli
|
|
||||||
- name: Download Windows image
|
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
IMG_BASENAME=windows-11-iot-enterprise-aarch64.raw
|
|
||||||
IMG_PATH=$HOME/workloads/$IMG_BASENAME
|
|
||||||
IMG_GZ_PATH=$HOME/workloads/$IMG_BASENAME.gz
|
|
||||||
IMG_GZ_BLOB_NAME=windows-11-iot-enterprise-aarch64-9-min.raw.gz
|
|
||||||
cp "scripts/$IMG_BASENAME.sha1" "$HOME/workloads/"
|
|
||||||
pushd "$HOME/workloads"
|
|
||||||
if sha1sum "$IMG_BASENAME.sha1" --check; then
|
|
||||||
exit
|
|
||||||
fi
|
|
||||||
popd
|
|
||||||
mkdir -p "$HOME/workloads"
|
|
||||||
az storage blob download --container-name private-images --file "$IMG_GZ_PATH" --name "$IMG_GZ_BLOB_NAME" --connection-string "${{ secrets.CH_PRIVATE_IMAGES }}"
|
|
||||||
gzip -d $IMG_GZ_PATH
|
|
||||||
- name: Run Windows guest integration tests
|
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
|
||||||
timeout-minutes: 30
|
|
||||||
run: scripts/dev_cli.sh tests --integration-windows --libc musl
|
|
||||||
22
.github/workflows/integration-metrics.yaml
vendored
22
.github/workflows/integration-metrics.yaml
vendored
@@ -1,22 +0,0 @@
|
|||||||
name: Cloud Hypervisor Tests (Metrics)
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
name: Tests (Metrics)
|
|
||||||
runs-on: jammy-metrics
|
|
||||||
env:
|
|
||||||
METRICS_PUBLISH_KEY: ${{ secrets.METRICS_PUBLISH_KEY }}
|
|
||||||
steps:
|
|
||||||
- name: Code checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Run metrics tests
|
|
||||||
timeout-minutes: 60
|
|
||||||
run: scripts/dev_cli.sh tests --metrics -- -- --report-file /root/workloads/metrics.json
|
|
||||||
- name: Upload metrics report
|
|
||||||
run: 'curl -X PUT https://ch-metrics.azurewebsites.net/api/publishmetrics -H "x-functions-key: $METRICS_PUBLISH_KEY" -T ~/workloads/metrics.json'
|
|
||||||
28
.github/workflows/integration-rate-limiter.yaml
vendored
28
.github/workflows/integration-rate-limiter.yaml
vendored
@@ -1,28 +0,0 @@
|
|||||||
name: Cloud Hypervisor Tests (Rate-Limiter)
|
|
||||||
on: [merge_group, pull_request]
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
name: Tests (Rate-Limiter)
|
|
||||||
runs-on: ${{ github.event_name == 'pull_request' && 'ubuntu-latest' || 'jammy-rate-limiter' }}
|
|
||||||
env:
|
|
||||||
AUTH_DOWNLOAD_TOKEN: ${{ secrets.AUTH_DOWNLOAD_TOKEN }}
|
|
||||||
steps:
|
|
||||||
- name: Fix workspace permissions
|
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
|
||||||
run: sudo chown -R github-runner:github-runner ${GITHUB_WORKSPACE}
|
|
||||||
- name: Code checkout
|
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Run rate-limiter integration tests
|
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
|
||||||
timeout-minutes: 10
|
|
||||||
run: scripts/dev_cli.sh tests --integration-rate-limiter
|
|
||||||
- name: Skipping build for PR
|
|
||||||
if: ${{ github.event_name == 'pull_request' }}
|
|
||||||
run: echo "Skipping build for PR"
|
|
||||||
32
.github/workflows/integration-sgx.yaml
vendored
32
.github/workflows/integration-sgx.yaml
vendored
@@ -1,32 +0,0 @@
|
|||||||
name: Cloud Hypervisor Tests (SGX)
|
|
||||||
on: [merge_group, pull_request]
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
name: Tests (SGX)
|
|
||||||
runs-on: ${{ github.event_name == 'pull_request' && 'ubuntu-latest' || 'jammy-sgx' }}
|
|
||||||
env:
|
|
||||||
AUTH_DOWNLOAD_TOKEN: ${{ secrets.AUTH_DOWNLOAD_TOKEN }}
|
|
||||||
steps:
|
|
||||||
- name: Fix workspace permissions
|
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
|
||||||
run: sudo chown -R github-runner:github-runner ${GITHUB_WORKSPACE}
|
|
||||||
- name: Code checkout
|
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Run SGX integration tests
|
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
|
||||||
timeout-minutes: 10
|
|
||||||
run: scripts/dev_cli.sh tests --integration-sgx
|
|
||||||
- name: Run SGX integration tests for musl
|
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
|
||||||
timeout-minutes: 10
|
|
||||||
run: scripts/dev_cli.sh tests --integration-sgx --libc musl
|
|
||||||
- name: Skipping build for PR
|
|
||||||
if: ${{ github.event_name == 'pull_request' }}
|
|
||||||
run: echo "Skipping build for PR"
|
|
||||||
32
.github/workflows/integration-vfio.yaml
vendored
32
.github/workflows/integration-vfio.yaml
vendored
@@ -1,32 +0,0 @@
|
|||||||
name: Cloud Hypervisor Tests (VFIO)
|
|
||||||
on: [merge_group, pull_request]
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
name: Tests (VFIO)
|
|
||||||
runs-on: ${{ github.event_name == 'pull_request' && 'ubuntu-latest' || 'jammy-vfio' }}
|
|
||||||
env:
|
|
||||||
AUTH_DOWNLOAD_TOKEN: ${{ secrets.AUTH_DOWNLOAD_TOKEN }}
|
|
||||||
steps:
|
|
||||||
- name: Fix workspace permissions
|
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
|
||||||
run: sudo chown -R github-runner:github-runner ${GITHUB_WORKSPACE}
|
|
||||||
- name: Code checkout
|
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Run VFIO integration tests
|
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
|
||||||
timeout-minutes: 15
|
|
||||||
run: scripts/dev_cli.sh tests --integration-vfio
|
|
||||||
- name: Run VFIO integration tests for musl
|
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
|
||||||
timeout-minutes: 15
|
|
||||||
run: scripts/dev_cli.sh tests --integration-vfio --libc musl
|
|
||||||
- name: Skipping build for PR
|
|
||||||
if: ${{ github.event_name == 'pull_request' }}
|
|
||||||
run: echo "Skipping build for PR"
|
|
||||||
50
.github/workflows/integration-windows.yaml
vendored
50
.github/workflows/integration-windows.yaml
vendored
@@ -1,50 +0,0 @@
|
|||||||
name: Cloud Hypervisor Tests (Windows Guest)
|
|
||||||
on: [merge_group, pull_request]
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
name: Tests (Windows Guest)
|
|
||||||
runs-on: ${{ github.event_name == 'pull_request' && 'ubuntu-latest' || 'garm-jammy-16' }}
|
|
||||||
steps:
|
|
||||||
- name: Code checkout
|
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Install Docker
|
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
|
||||||
run: |
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get -y install ca-certificates curl gnupg
|
|
||||||
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
|
|
||||||
sudo chmod a+r /usr/share/keyrings/docker-archive-keyring.gpg
|
|
||||||
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt install -y docker-ce docker-ce-cli
|
|
||||||
- name: Install Azure CLI
|
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
|
||||||
run: |
|
|
||||||
sudo apt install -y ca-certificates curl apt-transport-https lsb-release gnupg
|
|
||||||
curl -sL https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/microsoft.gpg > /dev/null
|
|
||||||
echo "deb [arch=amd64] https://packages.microsoft.com/repos/azure-cli/ jammy main" | sudo tee /etc/apt/sources.list.d/azure-cli.list
|
|
||||||
sudo apt update
|
|
||||||
sudo apt install -y azure-cli
|
|
||||||
- name: Download Windows image
|
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
|
||||||
run: |
|
|
||||||
mkdir $HOME/workloads
|
|
||||||
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 "${{ secrets.CH_PRIVATE_IMAGES }}"
|
|
||||||
- name: Run Windows guest integration tests
|
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
|
||||||
timeout-minutes: 15
|
|
||||||
run: scripts/dev_cli.sh tests --integration-windows
|
|
||||||
- name: Run Windows guest integration tests for musl
|
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
|
||||||
timeout-minutes: 15
|
|
||||||
run: scripts/dev_cli.sh tests --integration-windows --libc musl
|
|
||||||
- name: Skipping build for PR
|
|
||||||
if: ${{ github.event_name == 'pull_request' }}
|
|
||||||
run: echo "Skipping build for PR"
|
|
||||||
52
.github/workflows/integration-x86-64.yaml
vendored
52
.github/workflows/integration-x86-64.yaml
vendored
@@ -1,52 +0,0 @@
|
|||||||
name: Cloud Hypervisor Tests (x86-64)
|
|
||||||
on: [pull_request, merge_group]
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
timeout-minutes: 60
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
runner: ['garm-jammy', "garm-jammy-amd"]
|
|
||||||
libc: ["musl", 'gnu']
|
|
||||||
name: Tests (x86-64)
|
|
||||||
runs-on: ${{ github.event_name == 'pull_request' && !(matrix.runner == 'garm-jammy' && matrix.libc == 'gnu') && 'ubuntu-latest' || format('{0}-16', matrix.runner) }}
|
|
||||||
steps:
|
|
||||||
- name: Code checkout
|
|
||||||
if: ${{ github.event_name != 'pull_request' || (matrix.runner == 'garm-jammy' && matrix.libc == 'gnu') }}
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Install Docker
|
|
||||||
if: ${{ github.event_name != 'pull_request' || (matrix.runner == 'garm-jammy' && matrix.libc == 'gnu') }}
|
|
||||||
run: |
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get -y install ca-certificates curl gnupg
|
|
||||||
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
|
|
||||||
sudo chmod a+r /usr/share/keyrings/docker-archive-keyring.gpg
|
|
||||||
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt install -y docker-ce docker-ce-cli
|
|
||||||
- name: Prepare for VDPA
|
|
||||||
if: ${{ github.event_name != 'pull_request' || (matrix.runner == 'garm-jammy' && matrix.libc == 'gnu') }}
|
|
||||||
run: scripts/prepare_vdpa.sh
|
|
||||||
- name: Run unit tests
|
|
||||||
if: ${{ github.event_name != 'pull_request' || (matrix.runner == 'garm-jammy' && matrix.libc == 'gnu') }}
|
|
||||||
run: scripts/dev_cli.sh tests --unit --libc ${{ matrix.libc }}
|
|
||||||
- name: Load openvswitch module
|
|
||||||
if: ${{ github.event_name != 'pull_request' || (matrix.runner == 'garm-jammy' && matrix.libc == 'gnu') }}
|
|
||||||
run: sudo modprobe openvswitch
|
|
||||||
- name: Run integration tests
|
|
||||||
if: ${{ github.event_name != 'pull_request' || (matrix.runner == 'garm-jammy' && matrix.libc == 'gnu') }}
|
|
||||||
timeout-minutes: 40
|
|
||||||
run: scripts/dev_cli.sh tests --integration --libc ${{ matrix.libc }}
|
|
||||||
- name: Run live-migration integration tests
|
|
||||||
if: ${{ github.event_name != 'pull_request' || (matrix.runner == 'garm-jammy' && matrix.libc == 'gnu') }}
|
|
||||||
timeout-minutes: 20
|
|
||||||
run: scripts/dev_cli.sh tests --integration-live-migration --libc ${{ matrix.libc }}
|
|
||||||
- name: Skipping build for PR
|
|
||||||
if: ${{ github.event_name == 'pull_request' && matrix.runner != 'garm-jammy' && matrix.libc != 'gnu' }}
|
|
||||||
run: echo "Skipping build for PR"
|
|
||||||
14
.github/workflows/openapi.yaml
vendored
14
.github/workflows/openapi.yaml
vendored
@@ -1,14 +0,0 @@
|
|||||||
name: Cloud Hypervisor OpenAPI Validation
|
|
||||||
on: [pull_request, merge_group]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
Validate:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
container: openapitools/openapi-generator-cli
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- name: Validate OpenAPI
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
run: |
|
|
||||||
/usr/local/bin/docker-entrypoint.sh validate -i vmm/src/api/openapi/cloud-hypervisor.yaml
|
|
||||||
37
.github/workflows/quality.yaml
vendored
37
.github/workflows/quality.yaml
vendored
@@ -1,11 +1,9 @@
|
|||||||
name: Cloud Hypervisor Quality Checks
|
name: Cloud Hypervisor Quality Checks
|
||||||
on: [pull_request, merge_group]
|
on: [pull_request, create]
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
if: github.event_name == 'pull_request'
|
||||||
name: Quality (clippy, rustfmt)
|
name: Quality (clippy, rustfmt)
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
continue-on-error: ${{ matrix.experimental }}
|
continue-on-error: ${{ matrix.experimental }}
|
||||||
@@ -36,7 +34,7 @@ jobs:
|
|||||||
experimental: true
|
experimental: true
|
||||||
steps:
|
steps:
|
||||||
- name: Code checkout
|
- name: Code checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
@@ -48,8 +46,8 @@ jobs:
|
|||||||
override: true
|
override: true
|
||||||
components: rustfmt, clippy
|
components: rustfmt, clippy
|
||||||
|
|
||||||
- name: Bisectability Check (default features)
|
- name: Debug Check (default features)
|
||||||
if: ${{ github.event_name == 'pull_request' && matrix.target == 'x86_64-unknown-linux-gnu' }}
|
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
|
||||||
run: |
|
run: |
|
||||||
set -e
|
set -e
|
||||||
commits=$(git rev-list origin/${{ github.base_ref }}..${{ github.sha }})
|
commits=$(git rev-list origin/${{ github.base_ref }}..${{ github.sha }})
|
||||||
@@ -103,22 +101,6 @@ jobs:
|
|||||||
command: clippy
|
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: --target=${{ matrix.target }} --locked --all --all-targets --no-default-features --tests --examples --features "mshv,kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||||
|
|
||||||
- name: Clippy (sev_snp)
|
|
||||||
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
|
|
||||||
uses: actions-rs/cargo@v1
|
|
||||||
with:
|
|
||||||
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
|
|
||||||
command: clippy
|
|
||||||
args: --target=${{ matrix.target }} --locked --all --all-targets --no-default-features --tests --examples --features "sev_snp" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
|
||||||
|
|
||||||
- name: Clippy (igvm)
|
|
||||||
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
|
|
||||||
uses: actions-rs/cargo@v1
|
|
||||||
with:
|
|
||||||
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
|
|
||||||
command: clippy
|
|
||||||
args: --target=${{ matrix.target }} --locked --all --all-targets --no-default-features --tests --examples --features "igvm" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
|
||||||
|
|
||||||
- name: Clippy (kvm + tdx)
|
- name: Clippy (kvm + tdx)
|
||||||
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
|
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
@@ -129,12 +111,3 @@ jobs:
|
|||||||
|
|
||||||
- name: Check build did not modify any files
|
- name: Check build did not modify any files
|
||||||
run: test -z "$(git status --porcelain)"
|
run: test -z "$(git status --porcelain)"
|
||||||
|
|
||||||
typos:
|
|
||||||
if: github.event_name == 'pull_request'
|
|
||||||
name: Typos / Spellcheck
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
# Executes "typos ."
|
|
||||||
- uses: crate-ci/typos@v1.16.11
|
|
||||||
|
|||||||
196
.github/workflows/release.yaml
vendored
196
.github/workflows/release.yaml
vendored
@@ -1,69 +1,134 @@
|
|||||||
name: Cloud Hypervisor Release
|
name: Cloud Hypervisor Release
|
||||||
on: [create, merge_group]
|
on: [pull_request, create]
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ github.token }}
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
if: (github.event_name == 'create' && github.event.ref_type == 'tag') || github.event_name == 'merge_group'
|
if: (github.event_name == 'create' && github.event.ref_type == 'tag') || github.event_name == 'pull_request'
|
||||||
name: Release ${{ matrix.platform.target }}
|
name: Release
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
platform:
|
|
||||||
- target: x86_64-unknown-linux-gnu
|
|
||||||
args: --all --release --features mshv
|
|
||||||
name_ch: cloud-hypervisor
|
|
||||||
name_ch_remote: ch-remote
|
|
||||||
- target: x86_64-unknown-linux-musl
|
|
||||||
args: --all --release --features mshv
|
|
||||||
name_ch: cloud-hypervisor-static
|
|
||||||
name_ch_remote: ch-remote-static
|
|
||||||
- target: aarch64-unknown-linux-musl
|
|
||||||
args: --all --release
|
|
||||||
name_ch: cloud-hypervisor-static-aarch64
|
|
||||||
name_ch_remote: ch-remote-static-aarch64
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Code checkout
|
- name: Code checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v2
|
||||||
- name: Install musl-gcc
|
- name: Install musl-gcc
|
||||||
if: contains(matrix.platform.target, 'musl')
|
|
||||||
run: sudo apt install -y musl-tools
|
run: sudo apt install -y musl-tools
|
||||||
- name: Create release directory
|
- name: Create release directory
|
||||||
if: |
|
|
||||||
github.event_name == 'create' && github.event.ref_type == 'tag' &&
|
|
||||||
matrix.platform.target == 'x86_64-unknown-linux-gnu'
|
|
||||||
run: rsync -rv --exclude=.git . ../cloud-hypervisor-${{ github.event.ref }}
|
run: rsync -rv --exclude=.git . ../cloud-hypervisor-${{ github.event.ref }}
|
||||||
- name: Build ${{ matrix.platform.target }}
|
- name: Install Rust toolchain (x86_64-unknown-linux-gnu)
|
||||||
uses: houseabsolute/actions-rust-cross@v0
|
uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
|
toolchain: "1.67.1"
|
||||||
|
target: x86_64-unknown-linux-gnu
|
||||||
|
- name: Install Rust toolchain (x86_64-unknown-linux-musl)
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
toolchain: "1.67.1"
|
||||||
|
target: x86_64-unknown-linux-musl
|
||||||
|
- name: Build
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
toolchain: "1.67.1"
|
||||||
command: build
|
command: build
|
||||||
target: ${{ matrix.platform.target }}
|
args: --all --release --no-default-features --features "kvm,mshv" --target=x86_64-unknown-linux-gnu
|
||||||
args: ${{ matrix.platform.args }}
|
- name: Static Build
|
||||||
strip: true
|
uses: actions-rs/cargo@v1
|
||||||
toolchain: "1.77.0"
|
|
||||||
- name: Copy Release Binaries
|
|
||||||
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
cp target/${{ matrix.platform.target }}/release/cloud-hypervisor ./${{ matrix.platform.name_ch }}
|
|
||||||
cp target/${{ matrix.platform.target }}/release/ch-remote ./${{ matrix.platform.name_ch_remote }}
|
|
||||||
- name: Upload Release Artifacts
|
|
||||||
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
|
||||||
uses: actions/upload-artifact@v3
|
|
||||||
with:
|
with:
|
||||||
name: Artifacts for ${{ matrix.platform.target }}
|
toolchain: "1.67.1"
|
||||||
path: |
|
command: build
|
||||||
./${{ matrix.platform.name_ch }}
|
args: --all --release --no-default-features --features "kvm,mshv" --target=x86_64-unknown-linux-musl
|
||||||
./${{ matrix.platform.name_ch_remote }}
|
- name: Install Rust toolchain (aarch64-unknown-linux-musl)
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
toolchain: "1.67.1"
|
||||||
|
target: aarch64-unknown-linux-musl
|
||||||
|
override: true
|
||||||
|
- name: Create Release
|
||||||
|
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
||||||
|
id: create_release
|
||||||
|
uses: actions/create-release@v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
tag_name: ${{ github.ref }}
|
||||||
|
release_name: ${{ github.ref }}
|
||||||
|
draft: true
|
||||||
|
prerelease: true
|
||||||
|
- name: Upload cloud-hypervisor
|
||||||
|
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
||||||
|
id: upload-release-cloud-hypervisor
|
||||||
|
uses: actions/upload-release-asset@v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||||
|
asset_path: target/x86_64-unknown-linux-gnu/release/cloud-hypervisor
|
||||||
|
asset_name: cloud-hypervisor
|
||||||
|
asset_content_type: application/octet-stream
|
||||||
|
- name: Upload static cloud-hypervisor
|
||||||
|
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
||||||
|
id: upload-release-static-cloud-hypervisor
|
||||||
|
uses: actions/upload-release-asset@v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||||
|
asset_path: target/x86_64-unknown-linux-musl/release/cloud-hypervisor
|
||||||
|
asset_name: cloud-hypervisor-static
|
||||||
|
asset_content_type: application/octet-stream
|
||||||
|
- name: Upload ch-remote
|
||||||
|
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
||||||
|
id: upload-release-ch-remote
|
||||||
|
uses: actions/upload-release-asset@v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||||
|
asset_path: target/x86_64-unknown-linux-gnu/release/ch-remote
|
||||||
|
asset_name: ch-remote
|
||||||
|
asset_content_type: application/octet-stream
|
||||||
|
- name: Upload static-ch-remote
|
||||||
|
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
||||||
|
id: upload-release-static-ch-remote
|
||||||
|
uses: actions/upload-release-asset@v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||||
|
asset_path: target/x86_64-unknown-linux-musl/release/ch-remote
|
||||||
|
asset_name: ch-remote-static
|
||||||
|
asset_content_type: application/octet-stream
|
||||||
|
- name: Clean build tree ahead of cross build
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
command: clean
|
||||||
|
- name: Static Build (AArch64)
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
use-cross: true
|
||||||
|
command: build
|
||||||
|
args: --all --release --target=aarch64-unknown-linux-musl
|
||||||
|
- name: Upload static AArch64 cloud-hypervisor
|
||||||
|
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
||||||
|
id: upload-release-static-aarch64-cloud-hypervisor
|
||||||
|
uses: actions/upload-release-asset@v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||||
|
asset_path: target/aarch64-unknown-linux-musl/release/cloud-hypervisor
|
||||||
|
asset_name: cloud-hypervisor-static-aarch64
|
||||||
|
asset_content_type: application/octet-stream
|
||||||
|
- name: Upload static AArch64 ch-remote
|
||||||
|
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
||||||
|
id: upload-release-static-aarch64-ch-remote
|
||||||
|
uses: actions/upload-release-asset@v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||||
|
asset_path: target/aarch64-unknown-linux-musl/release/ch-remote
|
||||||
|
asset_name: ch-remote-static-aarch64
|
||||||
|
asset_content_type: application/octet-stream
|
||||||
- name: Vendor
|
- name: Vendor
|
||||||
if: |
|
|
||||||
github.event_name == 'create' && github.event.ref_type == 'tag' &&
|
|
||||||
matrix.platform.target == 'x86_64-unknown-linux-gnu'
|
|
||||||
working-directory: ../cloud-hypervisor-${{ github.event.ref }}
|
working-directory: ../cloud-hypervisor-${{ github.event.ref }}
|
||||||
run: |
|
run: |
|
||||||
mkdir ../vendor-cargo-home
|
mkdir ../vendor-cargo-home
|
||||||
@@ -71,25 +136,16 @@ jobs:
|
|||||||
mkdir .cargo
|
mkdir .cargo
|
||||||
cargo vendor > .cargo/config.toml
|
cargo vendor > .cargo/config.toml
|
||||||
- name: Create vendored source archive
|
- name: Create vendored source archive
|
||||||
if: |
|
working-directory: ../
|
||||||
github.event_name == 'create' && github.event.ref_type == 'tag' &&
|
run: tar cJf cloud-hypervisor-${{ github.event.ref }}.tar.xz cloud-hypervisor-${{ github.event.ref }}
|
||||||
matrix.platform.target == 'x86_64-unknown-linux-gnu'
|
|
||||||
run: tar cJf cloud-hypervisor-${{ github.event.ref }}.tar.xz ../cloud-hypervisor-${{ github.event.ref }}
|
|
||||||
- name: Upload cloud-hypervisor vendored source archive
|
- name: Upload cloud-hypervisor vendored source archive
|
||||||
if: |
|
|
||||||
github.event_name == 'create' && github.event.ref_type == 'tag' &&
|
|
||||||
matrix.platform.target == 'x86_64-unknown-linux-gnu'
|
|
||||||
id: upload-release-cloud-hypervisor-vendored-sources
|
|
||||||
uses: actions/upload-artifact@v3
|
|
||||||
with:
|
|
||||||
path: cloud-hypervisor-${{ github.event.ref }}.tar.xz
|
|
||||||
name: cloud-hypervisor-${{ github.event.ref }}.tar.xz
|
|
||||||
- name: Create GitHub Release
|
|
||||||
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
||||||
uses: softprops/action-gh-release@v1
|
id: upload-release-cloud-hypervisor-vendored-sources
|
||||||
|
uses: actions/upload-release-asset@v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
with:
|
||||||
draft: true
|
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||||
files: |
|
asset_path: ../cloud-hypervisor-${{ github.event.ref }}.tar.xz
|
||||||
./${{ matrix.platform.name_ch }}
|
asset_name: cloud-hypervisor-${{ github.event.ref }}.tar.xz
|
||||||
./${{ matrix.platform.name_ch_remote }}
|
asset_content_type: application/x-xz
|
||||||
./cloud-hypervisor-${{ github.event.ref }}.tar.xz
|
|
||||||
|
|||||||
12
.github/workflows/reuse.yaml
vendored
12
.github/workflows/reuse.yaml
vendored
@@ -1,12 +0,0 @@
|
|||||||
name: REUSE Compliance Check
|
|
||||||
|
|
||||||
on: [push, pull_request]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
reuse:
|
|
||||||
name: REUSE Compliance Check
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- name: REUSE Compliance Check
|
|
||||||
uses: fsfe/reuse-action@v3
|
|
||||||
20
.github/workflows/shlint.yaml
vendored
20
.github/workflows/shlint.yaml
vendored
@@ -1,20 +0,0 @@
|
|||||||
name: Shell scripts check
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
merge_group:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
sh-checker:
|
|
||||||
name: Check shell scripts
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
- name: Run the shell script checkers
|
|
||||||
uses: luizm/action-sh-checker@master
|
|
||||||
env:
|
|
||||||
SHFMT_OPTS: -i 4 -d
|
|
||||||
SHELLCHECK_OPTS: -x --source-path scripts
|
|
||||||
21
.github/workflows/taplo.yaml
vendored
21
.github/workflows/taplo.yaml
vendored
@@ -1,21 +0,0 @@
|
|||||||
name: Cargo.toml Formatting (taplo)
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
paths:
|
|
||||||
- '**/Cargo.toml'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
cargo_toml_format:
|
|
||||||
name: Cargo.toml Formatting
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Code checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
- name: Install Rust toolchain
|
|
||||||
uses: dtolnay/rust-toolchain@stable
|
|
||||||
- name: Install build dependencies
|
|
||||||
run: sudo apt-get update && sudo apt-get -yqq install build-essential libssl-dev
|
|
||||||
- name: Install taplo
|
|
||||||
run: cargo install taplo-cli --locked
|
|
||||||
- name: Check formatting
|
|
||||||
run: taplo fmt --check
|
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -6,4 +6,3 @@
|
|||||||
**/rusty-tags.vi
|
**/rusty-tags.vi
|
||||||
/rpm/SOURCES
|
/rpm/SOURCES
|
||||||
/.vscode
|
/.vscode
|
||||||
/vendor
|
|
||||||
|
|||||||
15
.gitlint
15
.gitlint
@@ -1,15 +0,0 @@
|
|||||||
[general]
|
|
||||||
extra-path=scripts/gitlint/rules.py
|
|
||||||
regex-style-search=true
|
|
||||||
|
|
||||||
[ignore-by-author-name]
|
|
||||||
regex=dependabot
|
|
||||||
ignore=all
|
|
||||||
|
|
||||||
# default 72
|
|
||||||
[title-max-length]
|
|
||||||
line-length=72
|
|
||||||
|
|
||||||
# default 80
|
|
||||||
[body-max-line-length]
|
|
||||||
line-length=72
|
|
||||||
12
.reuse/dep5
12
.reuse/dep5
@@ -1,12 +0,0 @@
|
|||||||
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
|
||||||
Upstream-Name: cloud-hypervisor
|
|
||||||
Upstream-Contact: <>
|
|
||||||
Source: https://www.cloudhypervisor.org
|
|
||||||
|
|
||||||
Files: docs/*.md *.md
|
|
||||||
Copyright: 2024
|
|
||||||
License: CC-BY-4.0
|
|
||||||
|
|
||||||
Files: scripts/* test_data/* *.toml .git* fuzz/Cargo.lock fuzz/.gitignore resources/linux-config-* vmm/src/api/openapi/cloud-hypervisor.yaml CODEOWNERS Cargo.lock
|
|
||||||
Copyright: 2024
|
|
||||||
License: Apache-2.0
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
include = ["**/Cargo.toml"]
|
|
||||||
|
|
||||||
[formatting]
|
|
||||||
reorder_arrays = true
|
|
||||||
reorder_keys = true
|
|
||||||
21
.typos.toml
21
.typos.toml
@@ -1,21 +0,0 @@
|
|||||||
# Configuration for https://github.com/crate-ci/typos
|
|
||||||
|
|
||||||
[files]
|
|
||||||
extend-exclude = [
|
|
||||||
"hypervisor/src/kvm/x86_64/mod.rs",
|
|
||||||
"resources/linux-config-*",
|
|
||||||
]
|
|
||||||
|
|
||||||
[default.extend-words]
|
|
||||||
ba = "ba"
|
|
||||||
CLASSE = "CLASSE"
|
|
||||||
conectix = "conectix"
|
|
||||||
Dake = "Dake"
|
|
||||||
EXTINT = "EXTINT"
|
|
||||||
INOUT = "INOUT"
|
|
||||||
liness = "liness"
|
|
||||||
outout = "outout"
|
|
||||||
|
|
||||||
[default.extend-identifiers]
|
|
||||||
fo = "fo"
|
|
||||||
fpr = "fpr"
|
|
||||||
@@ -5,7 +5,7 @@ License](https://opensource.org/licenses/Apache-2.0) and the [BSD 3
|
|||||||
Clause](https://opensource.org/licenses/BSD-3-Clause) license. Individual files
|
Clause](https://opensource.org/licenses/BSD-3-Clause) license. Individual files
|
||||||
contain details of their licensing and changes to that file are under the same
|
contain details of their licensing and changes to that file are under the same
|
||||||
license unless the contribution changes the license of the file. When importing
|
license unless the contribution changes the license of the file. When importing
|
||||||
code from a third party project (e.g. Firecracker or crosvm) please respect the
|
code from a third party project (e.g. Firecracker or CrosVM) please respect the
|
||||||
license of those projects.
|
license of those projects.
|
||||||
|
|
||||||
New code should be under the [Apache v2
|
New code should be under the [Apache v2
|
||||||
@@ -112,5 +112,5 @@ Fixes #88
|
|||||||
Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
|
Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
|
||||||
```
|
```
|
||||||
|
|
||||||
Then, after the corresponding PR is merged, GitHub will automatically close that issue when parsing the
|
Then, after the corresponding PR is merged, Github will automatically close that issue when parsing the
|
||||||
[commit message](https://help.github.com/articles/closing-issues-via-commit-messages/).
|
[commit message](https://help.github.com/articles/closing-issues-via-commit-messages/).
|
||||||
|
|||||||
1946
Cargo.lock
generated
1946
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
121
Cargo.toml
121
Cargo.toml
@@ -1,13 +1,13 @@
|
|||||||
[package]
|
[package]
|
||||||
authors = ["The Cloud Hypervisor Authors"]
|
|
||||||
build = "build.rs"
|
|
||||||
default-run = "cloud-hypervisor"
|
|
||||||
description = "Open source Virtual Machine Monitor (VMM) that runs on top of KVM & MSHV"
|
|
||||||
edition = "2021"
|
|
||||||
homepage = "https://github.com/cloud-hypervisor/cloud-hypervisor"
|
|
||||||
license = "LICENSE-APACHE & LICENSE-BSD-3-Clause"
|
|
||||||
name = "cloud-hypervisor"
|
name = "cloud-hypervisor"
|
||||||
version = "40.0.0"
|
version = "31.2.0"
|
||||||
|
authors = ["The Cloud Hypervisor Authors"]
|
||||||
|
edition = "2021"
|
||||||
|
default-run = "cloud-hypervisor"
|
||||||
|
build = "build.rs"
|
||||||
|
license = "LICENSE-APACHE & LICENSE-BSD-3-Clause"
|
||||||
|
description = "Open source Virtual Machine Monitor (VMM) that runs on top of KVM"
|
||||||
|
homepage = "https://github.com/cloud-hypervisor/cloud-hypervisor"
|
||||||
# Minimum buildable version:
|
# Minimum buildable version:
|
||||||
# Keep in sync with version in .github/workflows/build.yaml
|
# Keep in sync with version in .github/workflows/build.yaml
|
||||||
# Policy on MSRV (see #4318):
|
# Policy on MSRV (see #4318):
|
||||||
@@ -15,87 +15,88 @@ version = "40.0.0"
|
|||||||
# 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,
|
# 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.
|
# c.) There is a security issue that is addressed by the toolchain update.
|
||||||
rust-version = "1.77.0"
|
rust-version = "1.62"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
codegen-units = 1
|
|
||||||
lto = true
|
lto = true
|
||||||
|
codegen-units = 1
|
||||||
opt-level = "s"
|
opt-level = "s"
|
||||||
strip = true
|
strip = true
|
||||||
|
|
||||||
[profile.profiling]
|
[profile.profiling]
|
||||||
debug = true
|
|
||||||
inherits = "release"
|
inherits = "release"
|
||||||
strip = false
|
strip = false
|
||||||
|
debug = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.81"
|
anyhow = "1.0.69"
|
||||||
api_client = { path = "api_client" }
|
api_client = { path = "api_client" }
|
||||||
clap = { version = "4.5.4", features = ["string"] }
|
argh = "0.1.9"
|
||||||
dhat = { version = "0.3.3", optional = true }
|
dhat = { version = "0.3.2", optional = true }
|
||||||
epoll = "4.3.3"
|
epoll = "4.3.1"
|
||||||
event_monitor = { path = "event_monitor" }
|
event_monitor = { path = "event_monitor" }
|
||||||
hypervisor = { path = "hypervisor" }
|
hypervisor = { path = "hypervisor" }
|
||||||
libc = "0.2.153"
|
libc = "0.2.139"
|
||||||
log = { version = "0.4.21", features = ["std"] }
|
log = { version = "0.4.17", features = ["std"] }
|
||||||
option_parser = { path = "option_parser" }
|
option_parser = { path = "option_parser" }
|
||||||
seccompiler = "0.4.0"
|
seccompiler = "0.3.0"
|
||||||
serde_json = "1.0.115"
|
serde_json = "1.0.95"
|
||||||
signal-hook = "0.3.17"
|
signal-hook = "0.3.15"
|
||||||
thiserror = "1.0.60"
|
thiserror = "1.0.39"
|
||||||
tpm = { path = "tpm" }
|
tpm = { path = "tpm"}
|
||||||
tracer = { path = "tracer" }
|
tracer = { path = "tracer" }
|
||||||
vm-memory = "0.14.1"
|
|
||||||
vmm = { path = "vmm" }
|
vmm = { path = "vmm" }
|
||||||
vmm-sys-util = "0.12.1"
|
vmm-sys-util = "0.11.0"
|
||||||
zbus = { version = "4.1.2", optional = true }
|
vm-memory = "0.10.0"
|
||||||
|
|
||||||
|
# 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" }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
dirs = "5.0.1"
|
dirs = "4.0.0"
|
||||||
net_util = { path = "net_util" }
|
net_util = { path = "net_util" }
|
||||||
once_cell = "1.19.0"
|
once_cell = "1.17.1"
|
||||||
serde_json = "1.0.115"
|
serde_json = "1.0.95"
|
||||||
test_infra = { path = "test_infra" }
|
test_infra = { path = "test_infra" }
|
||||||
wait-timeout = "0.2.0"
|
wait-timeout = "0.2.0"
|
||||||
|
|
||||||
# Please adjust `vmm::feature_list()` accordingly when changing the
|
|
||||||
# feature list below
|
|
||||||
[features]
|
[features]
|
||||||
dbus_api = ["vmm/dbus_api", "zbus"]
|
default = ["kvm"]
|
||||||
default = ["io_uring", "kvm"]
|
dhat-heap = ["dhat"] # For heap profiling
|
||||||
dhat-heap = ["dhat", "vmm/dhat-heap"] # For heap profiling
|
|
||||||
guest_debug = ["vmm/guest_debug"]
|
guest_debug = ["vmm/guest_debug"]
|
||||||
igvm = ["mshv", "vmm/igvm"]
|
|
||||||
io_uring = ["vmm/io_uring"]
|
|
||||||
kvm = ["vmm/kvm"]
|
kvm = ["vmm/kvm"]
|
||||||
mshv = ["vmm/mshv"]
|
mshv = ["vmm/mshv"]
|
||||||
sev_snp = ["igvm", "mshv", "vmm/sev_snp"]
|
|
||||||
tdx = ["vmm/tdx"]
|
tdx = ["vmm/tdx"]
|
||||||
tracing = ["tracer/tracing", "vmm/tracing"]
|
tracing = ["vmm/tracing", "tracer/tracing"]
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
"api_client",
|
"api_client",
|
||||||
"arch",
|
"arch",
|
||||||
"block",
|
"block_util",
|
||||||
"devices",
|
"devices",
|
||||||
"event_monitor",
|
"event_monitor",
|
||||||
"hypervisor",
|
"hypervisor",
|
||||||
"net_gen",
|
"net_gen",
|
||||||
"net_util",
|
"net_util",
|
||||||
"option_parser",
|
"option_parser",
|
||||||
"pci",
|
"pci",
|
||||||
"performance-metrics",
|
"performance-metrics",
|
||||||
"rate_limiter",
|
"qcow",
|
||||||
"serial_buffer",
|
"rate_limiter",
|
||||||
"test_infra",
|
"serial_buffer",
|
||||||
"tracer",
|
"test_infra",
|
||||||
"vhost_user_block",
|
"tracer",
|
||||||
"vhost_user_net",
|
"vhdx",
|
||||||
"virtio-devices",
|
"vhost_user_block",
|
||||||
"vm-allocator",
|
"vhost_user_net",
|
||||||
"vm-device",
|
"virtio-devices",
|
||||||
"vm-migration",
|
"vmm",
|
||||||
"vm-virtio",
|
"vm-allocator",
|
||||||
"vmm",
|
"vm-device",
|
||||||
|
"vm-migration",
|
||||||
|
"vm-virtio"
|
||||||
]
|
]
|
||||||
|
|||||||
452
Jenkinsfile
vendored
Normal file
452
Jenkinsfile
vendored
Normal file
@@ -0,0 +1,452 @@
|
|||||||
|
def runWorkers = true
|
||||||
|
pipeline {
|
||||||
|
agent none
|
||||||
|
options {
|
||||||
|
timeout(time: 4, unit: 'HOURS')
|
||||||
|
}
|
||||||
|
stages {
|
||||||
|
stage('Early checks') {
|
||||||
|
agent { node { label 'built-in' } }
|
||||||
|
stages {
|
||||||
|
stage('Checkout') {
|
||||||
|
steps {
|
||||||
|
checkout scm
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Check if worker build can be skipped') {
|
||||||
|
when {
|
||||||
|
expression {
|
||||||
|
return skipWorkerBuild()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
script {
|
||||||
|
runWorkers = false
|
||||||
|
echo 'No changes requring a build'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Check for RFC/WIP builds') {
|
||||||
|
when {
|
||||||
|
changeRequest comparator: 'REGEXP', title: '.*(rfc|RFC|wip|WIP).*'
|
||||||
|
beforeAgent true
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
error('Failing as this is marked as a WIP or RFC PR.')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Cancel older builds') {
|
||||||
|
when { not { branch 'main' } }
|
||||||
|
steps {
|
||||||
|
cancelPreviousBuilds()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Build') {
|
||||||
|
parallel {
|
||||||
|
stage('Worker build') {
|
||||||
|
agent { node { label 'jammy' } }
|
||||||
|
when {
|
||||||
|
beforeAgent true
|
||||||
|
expression {
|
||||||
|
return runWorkers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stages {
|
||||||
|
stage('Checkout') {
|
||||||
|
steps {
|
||||||
|
checkout scm
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Prepare environment') {
|
||||||
|
steps {
|
||||||
|
sh 'scripts/prepare_vdpa.sh'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Run OpenAPI tests') {
|
||||||
|
steps {
|
||||||
|
sh 'scripts/run_openapi_tests.sh'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Run unit tests') {
|
||||||
|
steps {
|
||||||
|
sh 'scripts/dev_cli.sh tests --unit'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Run integration tests') {
|
||||||
|
options {
|
||||||
|
timeout(time: 1, unit: 'HOURS')
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
sh 'sudo modprobe openvswitch'
|
||||||
|
sh 'scripts/dev_cli.sh tests --integration'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Run live-migration integration tests') {
|
||||||
|
options {
|
||||||
|
timeout(time: 1, unit: 'HOURS')
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
sh 'sudo modprobe openvswitch'
|
||||||
|
sh 'scripts/dev_cli.sh tests --integration-live-migration'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Run unit tests for musl') {
|
||||||
|
steps {
|
||||||
|
sh 'scripts/dev_cli.sh tests --unit --libc musl'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Run integration tests for musl') {
|
||||||
|
options {
|
||||||
|
timeout(time: 1, unit: 'HOURS')
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
sh 'sudo modprobe openvswitch'
|
||||||
|
sh 'scripts/dev_cli.sh tests --integration --libc musl'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Run live-migration integration tests for musl') {
|
||||||
|
options {
|
||||||
|
timeout(time: 1, unit: 'HOURS')
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
sh 'sudo modprobe openvswitch'
|
||||||
|
sh 'scripts/dev_cli.sh tests --integration-live-migration --libc musl'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('AArch64 worker build') {
|
||||||
|
agent { node { label 'bionic-arm64' } }
|
||||||
|
when {
|
||||||
|
beforeAgent true
|
||||||
|
expression {
|
||||||
|
return runWorkers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
environment {
|
||||||
|
AZURE_CONNECTION_STRING = credentials('46b4e7d6-315f-4cc1-8333-b58780863b9b')
|
||||||
|
}
|
||||||
|
stages {
|
||||||
|
stage('Checkout') {
|
||||||
|
steps {
|
||||||
|
checkout scm
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Run unit tests') {
|
||||||
|
steps {
|
||||||
|
sh 'scripts/dev_cli.sh tests --unit --libc musl'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Run integration tests') {
|
||||||
|
options {
|
||||||
|
timeout(time: 1, unit: 'HOURS')
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
sh 'sudo modprobe openvswitch'
|
||||||
|
sh 'scripts/dev_cli.sh tests --integration --libc musl'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Install azure-cli') {
|
||||||
|
steps {
|
||||||
|
installAzureCli('bionic', 'arm64')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Download Windows image') {
|
||||||
|
steps {
|
||||||
|
sh '''#!/bin/bash -x
|
||||||
|
IMG_BASENAME=windows-11-iot-enterprise-aarch64.raw
|
||||||
|
IMG_PATH=$HOME/workloads/$IMG_BASENAME
|
||||||
|
IMG_GZ_PATH=$HOME/workloads/$IMG_BASENAME.gz
|
||||||
|
IMG_GZ_BLOB_NAME=windows-11-iot-enterprise-aarch64-9-min.raw.gz
|
||||||
|
cp "scripts/$IMG_BASENAME.sha1" "$HOME/workloads/"
|
||||||
|
pushd "$HOME/workloads"
|
||||||
|
if sha1sum "$IMG_BASENAME.sha1" --check; then
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
popd
|
||||||
|
mkdir -p "$HOME/workloads"
|
||||||
|
az storage blob download \
|
||||||
|
--container-name private-images \
|
||||||
|
--file "$IMG_GZ_PATH" \
|
||||||
|
--name "$IMG_GZ_BLOB_NAME" \
|
||||||
|
--connection-string "$AZURE_CONNECTION_STRING"
|
||||||
|
gzip -d $IMG_GZ_PATH
|
||||||
|
'''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Run Windows guest integration tests') {
|
||||||
|
options {
|
||||||
|
timeout(time: 1, unit: 'HOURS')
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
sh 'scripts/dev_cli.sh tests --integration-windows --libc musl'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
post {
|
||||||
|
always {
|
||||||
|
sh "sudo chown -R jenkins.jenkins ${WORKSPACE}"
|
||||||
|
deleteDir()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Worker build - Windows guest') {
|
||||||
|
agent { node { label 'jammy' } }
|
||||||
|
when {
|
||||||
|
beforeAgent true
|
||||||
|
expression {
|
||||||
|
return runWorkers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
environment {
|
||||||
|
AZURE_CONNECTION_STRING = credentials('46b4e7d6-315f-4cc1-8333-b58780863b9b')
|
||||||
|
}
|
||||||
|
stages {
|
||||||
|
stage('Checkout') {
|
||||||
|
steps {
|
||||||
|
checkout scm
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Install azure-cli') {
|
||||||
|
steps {
|
||||||
|
installAzureCli('jammy', 'amd64')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Download assets') {
|
||||||
|
steps {
|
||||||
|
sh "mkdir ${env.HOME}/workloads"
|
||||||
|
sh 'az storage blob download --container-name private-images --file "$HOME/workloads/windows-server-2022-amd64-2.raw" --name windows-server-2022-amd64-2.raw --connection-string "$AZURE_CONNECTION_STRING"'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Run Windows guest integration tests') {
|
||||||
|
options {
|
||||||
|
timeout(time: 1, unit: 'HOURS')
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
sh 'scripts/dev_cli.sh tests --integration-windows'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Run Windows guest integration tests for musl') {
|
||||||
|
options {
|
||||||
|
timeout(time: 1, unit: 'HOURS')
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
sh 'scripts/dev_cli.sh tests --integration-windows --libc musl'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Worker build - Metrics') {
|
||||||
|
agent { node { label 'jammy-metrics' } }
|
||||||
|
when {
|
||||||
|
branch 'main'
|
||||||
|
beforeAgent true
|
||||||
|
expression {
|
||||||
|
return runWorkers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
environment {
|
||||||
|
METRICS_PUBLISH_KEY = credentials('52e0945f-ce7a-43d1-87af-67d1d87cc40f')
|
||||||
|
}
|
||||||
|
stages {
|
||||||
|
stage('Checkout') {
|
||||||
|
steps {
|
||||||
|
checkout scm
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Run metrics tests') {
|
||||||
|
options {
|
||||||
|
timeout(time: 1, unit: 'HOURS')
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
sh 'scripts/dev_cli.sh tests --metrics -- -- --report-file /root/workloads/metrics.json'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Upload metrics report') {
|
||||||
|
steps {
|
||||||
|
sh 'curl -X PUT https://cloud-hypervisor-metrics.azurewebsites.net/api/publishmetrics -H "x-functions-key: $METRICS_PUBLISH_KEY" -T ~/workloads/metrics.json'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Worker build - Rate Limiter') {
|
||||||
|
agent { node { label 'focal-metrics' } }
|
||||||
|
when {
|
||||||
|
branch 'main'
|
||||||
|
beforeAgent true
|
||||||
|
expression {
|
||||||
|
return runWorkers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stages {
|
||||||
|
stage('Checkout') {
|
||||||
|
steps {
|
||||||
|
checkout scm
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Run rate-limiter integration tests') {
|
||||||
|
options {
|
||||||
|
timeout(time: 10, unit: 'MINUTES')
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
sh 'scripts/dev_cli.sh tests --integration-rate-limiter'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Worker build - SGX') {
|
||||||
|
agent { node { label 'jammy-sgx' } }
|
||||||
|
when {
|
||||||
|
beforeAgent true
|
||||||
|
allOf {
|
||||||
|
branch 'main'
|
||||||
|
expression {
|
||||||
|
return runWorkers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stages {
|
||||||
|
stage('Checkout') {
|
||||||
|
steps {
|
||||||
|
checkout scm
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Run SGX integration tests') {
|
||||||
|
options {
|
||||||
|
timeout(time: 1, unit: 'HOURS')
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
sh 'scripts/dev_cli.sh tests --integration-sgx'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Run SGX integration tests for musl') {
|
||||||
|
options {
|
||||||
|
timeout(time: 1, unit: 'HOURS')
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
sh 'scripts/dev_cli.sh tests --integration-sgx --libc musl'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
post {
|
||||||
|
always {
|
||||||
|
sh "sudo chown -R jenkins.jenkins ${WORKSPACE}"
|
||||||
|
deleteDir()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Worker build - VFIO') {
|
||||||
|
agent { node { label 'jammy-vfio' } }
|
||||||
|
when {
|
||||||
|
beforeAgent true
|
||||||
|
allOf {
|
||||||
|
branch 'main'
|
||||||
|
expression {
|
||||||
|
return runWorkers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stages {
|
||||||
|
stage('Checkout') {
|
||||||
|
steps {
|
||||||
|
checkout scm
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Run VFIO integration tests') {
|
||||||
|
options {
|
||||||
|
timeout(time: 1, unit: 'HOURS')
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
sh 'scripts/dev_cli.sh tests --integration-vfio'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Run VFIO integration tests for musl') {
|
||||||
|
options {
|
||||||
|
timeout(time: 1, unit: 'HOURS')
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
sh 'scripts/dev_cli.sh tests --integration-vfio --libc musl'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
post {
|
||||||
|
always {
|
||||||
|
sh "sudo chown -R jenkins.jenkins ${WORKSPACE}"
|
||||||
|
deleteDir()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
post {
|
||||||
|
regression {
|
||||||
|
script {
|
||||||
|
if (env.BRANCH_NAME == 'main') {
|
||||||
|
slackSend(color: '#ff0000', message: '"main" branch build is now failing', channel: '#jenkins-ci')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fixed {
|
||||||
|
script {
|
||||||
|
if (env.BRANCH_NAME == 'main') {
|
||||||
|
slackSend(color: '#00ff00', message: '"main" branch build is now fixed', channel: '#jenkins-ci')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def cancelPreviousBuilds() {
|
||||||
|
// Check for other instances of this particular build, cancel any that are older than the current one
|
||||||
|
def jobName = env.JOB_NAME
|
||||||
|
def currentBuildNumber = env.BUILD_NUMBER.toInteger()
|
||||||
|
def currentJob = Jenkins.instance.getItemByFullName(jobName)
|
||||||
|
|
||||||
|
// Loop through all instances of this particular job/branch
|
||||||
|
for (def build : currentJob.builds) {
|
||||||
|
if (build.isBuilding() && (build.number.toInteger() < currentBuildNumber)) {
|
||||||
|
echo "Older build still queued. Sending kill signal to build number: ${build.number}"
|
||||||
|
build.doStop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def installAzureCli(distro, arch) {
|
||||||
|
sh 'sudo apt install -y ca-certificates curl apt-transport-https lsb-release gnupg'
|
||||||
|
sh 'curl -sL https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/microsoft.gpg > /dev/null'
|
||||||
|
sh "echo \"deb [arch=${arch}] https://packages.microsoft.com/repos/azure-cli/ ${distro} main\" | sudo tee /etc/apt/sources.list.d/azure-cli.list"
|
||||||
|
sh 'sudo apt update'
|
||||||
|
sh 'sudo apt install -y azure-cli'
|
||||||
|
}
|
||||||
|
|
||||||
|
def boolean skipWorkerBuild() {
|
||||||
|
if (env.CHANGE_TARGET == null) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sh(
|
||||||
|
returnStatus: true,
|
||||||
|
script: "git diff --name-only origin/${env.CHANGE_TARGET}... | grep -v '\\.md'"
|
||||||
|
) != 0) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sh(
|
||||||
|
returnStatus: true,
|
||||||
|
script: "git diff --name-only origin/${env.CHANGE_TARGET}... | grep -v -E 'fuzz/'"
|
||||||
|
) != 0) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sh(
|
||||||
|
returnStatus: true,
|
||||||
|
script: "git diff --name-only origin/${env.CHANGE_TARGET}... | grep -v -E '.github/'"
|
||||||
|
) != 0) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
23
README.md
23
README.md
@@ -78,9 +78,9 @@ The following sections describe how to build and run Cloud Hypervisor.
|
|||||||
|
|
||||||
## Host OS
|
## Host OS
|
||||||
|
|
||||||
For required KVM functionality and adequate performance the recommended host
|
For required KVM functionality the minimum host kernel version is 4.11. For
|
||||||
kernel version is 5.13. The majority of the CI currently tests with kernel
|
adequate performance the minimum recommended host kernel version is 5.6. The
|
||||||
version 5.15.
|
majority of the CI currently tests with kernel version 5.15.
|
||||||
|
|
||||||
## Use Pre-built Binaries
|
## Use Pre-built Binaries
|
||||||
|
|
||||||
@@ -108,7 +108,7 @@ do not wish to use the pre-built binaries.
|
|||||||
## Booting Linux
|
## Booting Linux
|
||||||
|
|
||||||
Cloud Hypervisor supports direct kernel boot (the x86-64 kernel requires the kernel
|
Cloud Hypervisor supports direct kernel boot (the x86-64 kernel requires the kernel
|
||||||
built with PVH support or a bzImage) or booting via a firmware (either [Rust Hypervisor
|
built with PVH support) or booting via a firmware (either [Rust Hypervisor
|
||||||
Firmware](https://github.com/cloud-hypervisor/rust-hypervisor-firmware) or an
|
Firmware](https://github.com/cloud-hypervisor/rust-hypervisor-firmware) or an
|
||||||
edk2 UEFI firmware called `CLOUDHV` / `CLOUDHV_EFI`.)
|
edk2 UEFI firmware called `CLOUDHV` / `CLOUDHV_EFI`.)
|
||||||
|
|
||||||
@@ -140,17 +140,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
|
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).
|
`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
|
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
|
is only necessary to add this disk image on the first boot.
|
||||||
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.
|
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
$ sudo setcap cap_net_admin+ep ./cloud-hypervisor
|
$ sudo setcap cap_net_admin+ep ./cloud-hypervisor
|
||||||
$ ./create-cloud-init.sh
|
$ ./create-cloud-init.sh
|
||||||
$ ./cloud-hypervisor \
|
$ ./cloud-hypervisor \
|
||||||
--kernel ./hypervisor-fw \
|
--kernel ./hypervisor-fw \
|
||||||
--disk path=focal-server-cloudimg-amd64.raw path=/tmp/ubuntu-cloudinit.img \
|
--disk path=focal-server-cloudimg-amd64.raw --disk path=/tmp/ubuntu-cloudinit.img \
|
||||||
--cpus boot=4 \
|
--cpus boot=4 \
|
||||||
--memory size=1024M \
|
--memory size=1024M \
|
||||||
--net "tap=,mac=,ip=,mask="
|
--net "tap=,mac=,ip=,mask="
|
||||||
@@ -163,7 +160,7 @@ GRUB) is required then it necessary to switch to the serial console instead of
|
|||||||
```shell
|
```shell
|
||||||
$ ./cloud-hypervisor \
|
$ ./cloud-hypervisor \
|
||||||
--kernel ./hypervisor-fw \
|
--kernel ./hypervisor-fw \
|
||||||
--disk path=focal-server-cloudimg-amd64.raw path=/tmp/ubuntu-cloudinit.img \
|
--disk path=focal-server-cloudimg-amd64.raw --disk path=/tmp/ubuntu-cloudinit.img \
|
||||||
--cpus boot=4 \
|
--cpus boot=4 \
|
||||||
--memory size=1024M \
|
--memory size=1024M \
|
||||||
--net "tap=,mac=,ip=,mask=" \
|
--net "tap=,mac=,ip=,mask=" \
|
||||||
@@ -175,7 +172,7 @@ $ ./cloud-hypervisor \
|
|||||||
|
|
||||||
#### Building your Kernel
|
#### Building your Kernel
|
||||||
|
|
||||||
Cloud Hypervisor also supports direct kernel boot. For x86-64, a `vmlinux` ELF kernel (compiled with PVH support) or a regular bzImage are supported. 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. 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.
|
||||||
|
|
||||||
To build the kernel:
|
To build the kernel:
|
||||||
|
|
||||||
@@ -225,7 +222,7 @@ $ sudo setcap cap_net_admin+ep ./cloud-hypervisor
|
|||||||
$ ./create-cloud-init.sh
|
$ ./create-cloud-init.sh
|
||||||
$ ./cloud-hypervisor \
|
$ ./cloud-hypervisor \
|
||||||
--kernel ./linux-cloud-hypervisor/arch/x86/boot/compressed/vmlinux.bin \
|
--kernel ./linux-cloud-hypervisor/arch/x86/boot/compressed/vmlinux.bin \
|
||||||
--disk path=focal-server-cloudimg-amd64.raw path=/tmp/ubuntu-cloudinit.img \
|
--disk path=focal-server-cloudimg-amd64.raw --disk path=/tmp/ubuntu-cloudinit.img \
|
||||||
--cmdline "console=hvc0 root=/dev/vda1 rw" \
|
--cmdline "console=hvc0 root=/dev/vda1 rw" \
|
||||||
--cpus boot=4 \
|
--cpus boot=4 \
|
||||||
--memory size=1024M \
|
--memory size=1024M \
|
||||||
@@ -239,7 +236,7 @@ $ sudo setcap cap_net_admin+ep ./cloud-hypervisor
|
|||||||
$ ./create-cloud-init.sh
|
$ ./create-cloud-init.sh
|
||||||
$ ./cloud-hypervisor \
|
$ ./cloud-hypervisor \
|
||||||
--kernel ./linux-cloud-hypervisor/arch/arm64/boot/Image \
|
--kernel ./linux-cloud-hypervisor/arch/arm64/boot/Image \
|
||||||
--disk path=focal-server-cloudimg-arm64.raw path=/tmp/ubuntu-cloudinit.img \
|
--disk path=focal-server-cloudimg-arm64.raw --disk path=/tmp/ubuntu-cloudinit.img \
|
||||||
--cmdline "console=hvc0 root=/dev/vda1 rw" \
|
--cmdline "console=hvc0 root=/dev/vda1 rw" \
|
||||||
--cpus boot=4 \
|
--cpus boot=4 \
|
||||||
--memory size=1024M \
|
--memory size=1024M \
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
[package]
|
[package]
|
||||||
authors = ["The Cloud Hypervisor Authors"]
|
|
||||||
edition = "2021"
|
|
||||||
name = "api_client"
|
name = "api_client"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
authors = ["The Cloud Hypervisor Authors"]
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
thiserror = "1.0.61"
|
vmm-sys-util = "0.11.0"
|
||||||
vmm-sys-util = "0.12.1"
|
|
||||||
|
|||||||
@@ -3,27 +3,41 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
//
|
//
|
||||||
|
|
||||||
|
use std::fmt;
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
use std::os::unix::io::RawFd;
|
use std::os::unix::io::RawFd;
|
||||||
use thiserror::Error;
|
|
||||||
use vmm_sys_util::sock_ctrl_msg::ScmSocket;
|
use vmm_sys_util::sock_ctrl_msg::ScmSocket;
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[error("Error writing to or reading from HTTP socket: {0}")]
|
|
||||||
Socket(std::io::Error),
|
Socket(std::io::Error),
|
||||||
#[error("Error sending file descriptors: {0}")]
|
|
||||||
SocketSendFds(vmm_sys_util::errno::Error),
|
SocketSendFds(vmm_sys_util::errno::Error),
|
||||||
#[error("Error parsing HTTP status code: {0}")]
|
|
||||||
StatusCodeParsing(std::num::ParseIntError),
|
StatusCodeParsing(std::num::ParseIntError),
|
||||||
#[error("HTTP output is missing protocol statement")]
|
|
||||||
MissingProtocol,
|
MissingProtocol,
|
||||||
#[error("Error parsing HTTP Content-Length field: {0}")]
|
|
||||||
ContentLengthParsing(std::num::ParseIntError),
|
ContentLengthParsing(std::num::ParseIntError),
|
||||||
#[error("Server responded with an error: {0:?}")]
|
|
||||||
ServerResponse(StatusCode, Option<String>),
|
ServerResponse(StatusCode, Option<String>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Error {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
use Error::*;
|
||||||
|
match self {
|
||||||
|
Socket(e) => write!(f, "Error writing to or reading from HTTP socket: {e}"),
|
||||||
|
SocketSendFds(e) => write!(f, "Error writing to or reading from HTTP socket: {e}"),
|
||||||
|
StatusCodeParsing(e) => write!(f, "Error parsing HTTP status code: {e}"),
|
||||||
|
MissingProtocol => write!(f, "HTTP output is missing protocol statement"),
|
||||||
|
ContentLengthParsing(e) => write!(f, "Error parsing HTTP Content-Length field: {e}"),
|
||||||
|
ServerResponse(s, o) => {
|
||||||
|
if let Some(o) = o {
|
||||||
|
write!(f, "Server responded with an error: {s:?}: {o}")
|
||||||
|
} else {
|
||||||
|
write!(f, "Server responded with an error: {s:?}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub enum StatusCode {
|
pub enum StatusCode {
|
||||||
Continue,
|
Continue,
|
||||||
|
|||||||
@@ -1,31 +1,29 @@
|
|||||||
[package]
|
[package]
|
||||||
authors = ["The Chromium OS Authors"]
|
|
||||||
edition = "2021"
|
|
||||||
name = "arch"
|
name = "arch"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
authors = ["The Chromium OS Authors"]
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
sev_snp = []
|
|
||||||
tdx = []
|
tdx = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.81"
|
anyhow = "1.0.69"
|
||||||
byteorder = "1.5.0"
|
byteorder = "1.4.3"
|
||||||
hypervisor = { path = "../hypervisor" }
|
hypervisor = { path = "../hypervisor" }
|
||||||
libc = "0.2.153"
|
libc = "0.2.139"
|
||||||
linux-loader = { version = "0.11.0", features = ["bzimage", "elf", "pe"] }
|
linux-loader = { version = "0.8.1", features = ["elf", "bzimage", "pe"] }
|
||||||
log = "0.4.21"
|
log = "0.4.17"
|
||||||
serde = { version = "1.0.197", features = ["derive", "rc"] }
|
serde = { version = "1.0.151", features = ["rc", "derive"] }
|
||||||
thiserror = "1.0.60"
|
thiserror = "1.0.39"
|
||||||
uuid = "1.8.0"
|
uuid = "1.3.0"
|
||||||
vm-memory = { version = "0.14.1", features = [
|
versionize = "0.1.10"
|
||||||
"backend-bitmap",
|
versionize_derive = "0.1.4"
|
||||||
"backend-mmap",
|
vm-memory = { version = "0.10.0", features = ["backend-mmap", "backend-bitmap"] }
|
||||||
] }
|
|
||||||
vm-migration = { path = "../vm-migration" }
|
vm-migration = { path = "../vm-migration" }
|
||||||
vmm-sys-util = { version = "0.12.1", features = ["with-serde"] }
|
vmm-sys-util = { version = "0.11.0", features = ["with-serde"] }
|
||||||
|
|
||||||
[target.'cfg(target_arch = "aarch64")'.dependencies]
|
[target.'cfg(target_arch = "aarch64")'.dependencies]
|
||||||
fdt_parser = { version = "0.1.5", package = "fdt" }
|
fdt_parser = { version = "0.1.4", package = "fdt" }
|
||||||
vm-fdt = { git = "https://github.com/rust-vmm/vm-fdt", branch = "main" }
|
vm-fdt = { git = "https://github.com/rust-vmm/vm-fdt", branch = "main" }
|
||||||
|
|||||||
@@ -24,9 +24,6 @@ use super::layout::{
|
|||||||
IRQ_BASE, MEM_32BIT_DEVICES_SIZE, MEM_32BIT_DEVICES_START, MEM_PCI_IO_SIZE, MEM_PCI_IO_START,
|
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,
|
PCI_HIGH_BASE, PCI_MMIO_CONFIG_SIZE_PER_SEGMENT,
|
||||||
};
|
};
|
||||||
use std::fs;
|
|
||||||
use std::path::Path;
|
|
||||||
use thiserror::Error;
|
|
||||||
use vm_fdt::{FdtWriter, FdtWriterResult};
|
use vm_fdt::{FdtWriter, FdtWriterResult};
|
||||||
use vm_memory::{Address, Bytes, GuestMemory, GuestMemoryError, GuestMemoryRegion};
|
use vm_memory::{Address, Bytes, GuestMemory, GuestMemoryError, GuestMemoryRegion};
|
||||||
|
|
||||||
@@ -43,12 +40,8 @@ const VIRTIO_IOMMU_PHANDLE: u32 = 5;
|
|||||||
// NOTE: Keep FIRST_VCPU_PHANDLE the last PHANDLE defined.
|
// NOTE: Keep FIRST_VCPU_PHANDLE the last PHANDLE defined.
|
||||||
// This is a value for uniquely identifying the FDT node containing the first vCPU.
|
// 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.
|
// 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.
|
// Read the documentation specified when appending the root node to the FDT.
|
||||||
const ADDRESS_CELLS: u32 = 0x2;
|
const ADDRESS_CELLS: u32 = 0x2;
|
||||||
const SIZE_CELLS: u32 = 0x2;
|
const SIZE_CELLS: u32 = 0x2;
|
||||||
@@ -81,140 +74,13 @@ pub trait DeviceInfoForFdt {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Errors thrown while configuring the Flattened Device Tree for aarch64.
|
/// Errors thrown while configuring the Flattened Device Tree for aarch64.
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
/// Failure in writing FDT in memory.
|
/// Failure in writing FDT in memory.
|
||||||
#[error("Failure in writing FDT in memory: {0}")]
|
|
||||||
WriteFdtToMemory(GuestMemoryError),
|
WriteFdtToMemory(GuestMemoryError),
|
||||||
}
|
}
|
||||||
type Result<T> = result::Result<T, Error>;
|
type Result<T> = result::Result<T, Error>;
|
||||||
|
|
||||||
pub enum CacheLevel {
|
|
||||||
/// L1 data cache
|
|
||||||
L1D = 0,
|
|
||||||
/// L1 instruction cache
|
|
||||||
L1I = 1,
|
|
||||||
/// L2 cache
|
|
||||||
L2 = 2,
|
|
||||||
/// L3 cache
|
|
||||||
L3 = 3,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// NOTE: cache size file directory example,
|
|
||||||
/// "/sys/devices/system/cpu/cpu0/cache/index0/size".
|
|
||||||
pub fn get_cache_size(cache_level: CacheLevel) -> u32 {
|
|
||||||
let mut file_directory: String = "/sys/devices/system/cpu/cpu0/cache".to_string();
|
|
||||||
match cache_level {
|
|
||||||
CacheLevel::L1D => file_directory += "/index0/size",
|
|
||||||
CacheLevel::L1I => file_directory += "/index1/size",
|
|
||||||
CacheLevel::L2 => file_directory += "/index2/size",
|
|
||||||
CacheLevel::L3 => file_directory += "/index3/size",
|
|
||||||
}
|
|
||||||
|
|
||||||
let file_path = Path::new(&file_directory);
|
|
||||||
if !file_path.exists() {
|
|
||||||
warn!("File: {} does not exist.", file_directory);
|
|
||||||
0
|
|
||||||
} else {
|
|
||||||
info!("File: {} exist.", file_directory);
|
|
||||||
|
|
||||||
let src = fs::read_to_string(file_directory).expect("File not exists or file corrupted.");
|
|
||||||
// The content of the file is as simple as a size, like: "32K"
|
|
||||||
let src = src.trim();
|
|
||||||
let src_digits: u32 = src[0..src.len() - 1].parse().unwrap();
|
|
||||||
let src_unit = &src[src.len() - 1..];
|
|
||||||
|
|
||||||
src_digits
|
|
||||||
* match src_unit {
|
|
||||||
"K" => 1024,
|
|
||||||
"M" => 1024u32.pow(2),
|
|
||||||
"G" => 1024u32.pow(3),
|
|
||||||
_ => 1,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// NOTE: coherency_line_size file directory example,
|
|
||||||
/// "/sys/devices/system/cpu/cpu0/cache/index0/coherency_line_size".
|
|
||||||
pub fn get_cache_coherency_line_size(cache_level: CacheLevel) -> u32 {
|
|
||||||
let mut file_directory: String = "/sys/devices/system/cpu/cpu0/cache".to_string();
|
|
||||||
match cache_level {
|
|
||||||
CacheLevel::L1D => file_directory += "/index0/coherency_line_size",
|
|
||||||
CacheLevel::L1I => file_directory += "/index1/coherency_line_size",
|
|
||||||
CacheLevel::L2 => file_directory += "/index2/coherency_line_size",
|
|
||||||
CacheLevel::L3 => file_directory += "/index3/coherency_line_size",
|
|
||||||
}
|
|
||||||
|
|
||||||
let file_path = Path::new(&file_directory);
|
|
||||||
if !file_path.exists() {
|
|
||||||
warn!("File: {} does not exist.", file_directory);
|
|
||||||
0
|
|
||||||
} else {
|
|
||||||
info!("File: {} exist.", file_directory);
|
|
||||||
|
|
||||||
let src = fs::read_to_string(file_directory).expect("File not exists or file corrupted.");
|
|
||||||
src.trim().parse::<u32>().unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// NOTE: number_of_sets file directory example,
|
|
||||||
/// "/sys/devices/system/cpu/cpu0/cache/index0/number_of_sets".
|
|
||||||
pub fn get_cache_number_of_sets(cache_level: CacheLevel) -> u32 {
|
|
||||||
let mut file_directory: String = "/sys/devices/system/cpu/cpu0/cache".to_string();
|
|
||||||
match cache_level {
|
|
||||||
CacheLevel::L1D => file_directory += "/index0/number_of_sets",
|
|
||||||
CacheLevel::L1I => file_directory += "/index1/number_of_sets",
|
|
||||||
CacheLevel::L2 => file_directory += "/index2/number_of_sets",
|
|
||||||
CacheLevel::L3 => file_directory += "/index3/number_of_sets",
|
|
||||||
}
|
|
||||||
|
|
||||||
let file_path = Path::new(&file_directory);
|
|
||||||
if !file_path.exists() {
|
|
||||||
warn!("File: {} does not exist.", file_directory);
|
|
||||||
0
|
|
||||||
} else {
|
|
||||||
info!("File: {} exist.", file_directory);
|
|
||||||
|
|
||||||
let src = fs::read_to_string(file_directory).expect("File not exists or file corrupted.");
|
|
||||||
src.trim().parse::<u32>().unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// NOTE: shared_cpu_list file directory example,
|
|
||||||
/// "/sys/devices/system/cpu/cpu0/cache/index0/shared_cpu_list".
|
|
||||||
pub fn get_cache_shared(cache_level: CacheLevel) -> bool {
|
|
||||||
let mut file_directory: String = "/sys/devices/system/cpu/cpu0/cache".to_string();
|
|
||||||
let mut result = true;
|
|
||||||
|
|
||||||
match cache_level {
|
|
||||||
CacheLevel::L1D | CacheLevel::L1I => result = false,
|
|
||||||
CacheLevel::L2 => file_directory += "/index2/shared_cpu_list",
|
|
||||||
CacheLevel::L3 => file_directory += "/index3/shared_cpu_list",
|
|
||||||
}
|
|
||||||
|
|
||||||
if !result {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let file_path = Path::new(&file_directory);
|
|
||||||
if !file_path.exists() {
|
|
||||||
warn!("File: {} does not exist.", file_directory);
|
|
||||||
result = false;
|
|
||||||
} else {
|
|
||||||
info!("File: {} exist.", file_directory);
|
|
||||||
|
|
||||||
let src = fs::read_to_string(file_directory).expect("File not exists or file corrupted.");
|
|
||||||
let src = src.trim();
|
|
||||||
if src.is_empty() {
|
|
||||||
result = false;
|
|
||||||
} else {
|
|
||||||
result = src.contains('-') || src.contains(',');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates the flattened device tree for this aarch64 VM.
|
/// Creates the flattened device tree for this aarch64 VM.
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn create_fdt<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHasher>(
|
pub fn create_fdt<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHasher>(
|
||||||
@@ -292,68 +158,6 @@ fn create_cpu_nodes(
|
|||||||
fdt.property_u32("#size-cells", 0x0)?;
|
fdt.property_u32("#size-cells", 0x0)?;
|
||||||
|
|
||||||
let num_cpus = vcpu_mpidr.len();
|
let num_cpus = vcpu_mpidr.len();
|
||||||
let (threads_per_core, cores_per_package, packages) = vcpu_topology.unwrap_or((1, 1, 1));
|
|
||||||
let max_cpus: u32 = (threads_per_core * cores_per_package * packages).into();
|
|
||||||
|
|
||||||
// Add cache info.
|
|
||||||
// L1 Data Cache Info.
|
|
||||||
let mut l1_d_cache_size: u32 = 0;
|
|
||||||
let mut l1_d_cache_line_size: u32 = 0;
|
|
||||||
let mut l1_d_cache_sets: u32 = 0;
|
|
||||||
|
|
||||||
// L1 Instruction Cache Info.
|
|
||||||
let mut l1_i_cache_size: u32 = 0;
|
|
||||||
let mut l1_i_cache_line_size: u32 = 0;
|
|
||||||
let mut l1_i_cache_sets: u32 = 0;
|
|
||||||
|
|
||||||
// L2 Cache Info.
|
|
||||||
let mut l2_cache_size: u32 = 0;
|
|
||||||
let mut l2_cache_line_size: u32 = 0;
|
|
||||||
let mut l2_cache_sets: u32 = 0;
|
|
||||||
|
|
||||||
// L3 Cache Info.
|
|
||||||
let mut l3_cache_size: u32 = 0;
|
|
||||||
let mut l3_cache_line_size: u32 = 0;
|
|
||||||
let mut l3_cache_sets: u32 = 0;
|
|
||||||
|
|
||||||
// Cache Shared Info.
|
|
||||||
let mut l2_cache_shared: bool = false;
|
|
||||||
let mut l3_cache_shared: bool = false;
|
|
||||||
|
|
||||||
let cache_path = Path::new("/sys/devices/system/cpu/cpu0/cache");
|
|
||||||
let cache_exist: bool = cache_path.exists();
|
|
||||||
if !cache_exist {
|
|
||||||
warn!("cache sysfs system does not exist.");
|
|
||||||
} else {
|
|
||||||
info!("cache sysfs system exists.");
|
|
||||||
// L1 Data Cache Info.
|
|
||||||
l1_d_cache_size = get_cache_size(CacheLevel::L1D);
|
|
||||||
l1_d_cache_line_size = get_cache_coherency_line_size(CacheLevel::L1D);
|
|
||||||
l1_d_cache_sets = get_cache_number_of_sets(CacheLevel::L1D);
|
|
||||||
|
|
||||||
// L1 Instruction Cache Info.
|
|
||||||
l1_i_cache_size = get_cache_size(CacheLevel::L1I);
|
|
||||||
l1_i_cache_line_size = get_cache_coherency_line_size(CacheLevel::L1I);
|
|
||||||
l1_i_cache_sets = get_cache_number_of_sets(CacheLevel::L1I);
|
|
||||||
|
|
||||||
// L2 Cache Info.
|
|
||||||
l2_cache_size = get_cache_size(CacheLevel::L2);
|
|
||||||
l2_cache_line_size = get_cache_coherency_line_size(CacheLevel::L2);
|
|
||||||
l2_cache_sets = get_cache_number_of_sets(CacheLevel::L2);
|
|
||||||
|
|
||||||
// L3 Cache Info.
|
|
||||||
l3_cache_size = get_cache_size(CacheLevel::L3);
|
|
||||||
l3_cache_line_size = get_cache_coherency_line_size(CacheLevel::L3);
|
|
||||||
l3_cache_sets = get_cache_number_of_sets(CacheLevel::L3);
|
|
||||||
|
|
||||||
// Cache Shared Info.
|
|
||||||
if l2_cache_size != 0 {
|
|
||||||
l2_cache_shared = get_cache_shared(CacheLevel::L2);
|
|
||||||
}
|
|
||||||
if l3_cache_size != 0 {
|
|
||||||
l3_cache_shared = get_cache_shared(CacheLevel::L3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (cpu_id, mpidr) in vcpu_mpidr.iter().enumerate().take(num_cpus) {
|
for (cpu_id, mpidr) in vcpu_mpidr.iter().enumerate().take(num_cpus) {
|
||||||
let cpu_name = format!("cpu@{cpu_id:x}");
|
let cpu_name = format!("cpu@{cpu_id:x}");
|
||||||
@@ -379,91 +183,9 @@ fn create_cpu_nodes(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if cache_exist && l1_d_cache_size != 0 && l1_i_cache_size != 0 {
|
|
||||||
// Add cache info.
|
|
||||||
fdt.property_u32("d-cache-size", l1_d_cache_size)?;
|
|
||||||
fdt.property_u32("d-cache-line-size", l1_d_cache_line_size)?;
|
|
||||||
fdt.property_u32("d-cache-sets", l1_d_cache_sets)?;
|
|
||||||
|
|
||||||
fdt.property_u32("i-cache-size", l1_i_cache_size)?;
|
|
||||||
fdt.property_u32("i-cache-line-size", l1_i_cache_line_size)?;
|
|
||||||
fdt.property_u32("i-cache-sets", l1_i_cache_sets)?;
|
|
||||||
|
|
||||||
if l2_cache_size != 0 && !l2_cache_shared {
|
|
||||||
fdt.property_u32(
|
|
||||||
"next-level-cache",
|
|
||||||
cpu_id as u32 + max_cpus + FIRST_VCPU_PHANDLE + L2_CACHE_PHANDLE,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let l2_cache_name = "l2-cache0";
|
|
||||||
let l2_cache_node = fdt.begin_node(l2_cache_name)?;
|
|
||||||
// PHANDLE is used to mark device node, and PHANDLE is unique. To avoid phandle
|
|
||||||
// conflicts with other device nodes, consider the previous CPU PHANDLE, so the
|
|
||||||
// CPU L2 cache PHANDLE must start from the largest CPU PHANDLE plus 1.
|
|
||||||
fdt.property_u32(
|
|
||||||
"phandle",
|
|
||||||
cpu_id as u32 + max_cpus + FIRST_VCPU_PHANDLE + L2_CACHE_PHANDLE,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
fdt.property_string("compatible", "cache")?;
|
|
||||||
fdt.property_u32("cache-size", l2_cache_size)?;
|
|
||||||
fdt.property_u32("cache-line-size", l2_cache_line_size)?;
|
|
||||||
fdt.property_u32("cache-sets", l2_cache_sets)?;
|
|
||||||
fdt.property_u32("cache-level", 2)?;
|
|
||||||
|
|
||||||
if l3_cache_size != 0 && l3_cache_shared {
|
|
||||||
let package_id: u32 = cpu_id as u32 / cores_per_package as u32;
|
|
||||||
fdt.property_u32(
|
|
||||||
"next-level-cache",
|
|
||||||
package_id
|
|
||||||
+ num_cpus as u32
|
|
||||||
+ max_cpus
|
|
||||||
+ FIRST_VCPU_PHANDLE
|
|
||||||
+ L2_CACHE_PHANDLE
|
|
||||||
+ L3_CACHE_PHANDLE,
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
fdt.end_node(l2_cache_node)?;
|
|
||||||
}
|
|
||||||
if l2_cache_size != 0 && l2_cache_shared {
|
|
||||||
warn!("L2 cache shared with other cpus");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fdt.end_node(cpu_node)?;
|
fdt.end_node(cpu_node)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if cache_exist && l3_cache_size != 0 && !l2_cache_shared && l3_cache_shared {
|
|
||||||
let mut i: u32 = 0;
|
|
||||||
while i < packages.into() {
|
|
||||||
let l3_cache_name = "l3-cache0";
|
|
||||||
let l3_cache_node = fdt.begin_node(l3_cache_name)?;
|
|
||||||
// ARM L3 cache is generally shared within the package (socket), so the
|
|
||||||
// L3 cache node pointed to by the CPU in the package has the same L3
|
|
||||||
// cache PHANDLE. The L3 cache phandle must start from the largest L2
|
|
||||||
// cache PHANDLE plus 1 to avoid duplication.
|
|
||||||
fdt.property_u32(
|
|
||||||
"phandle",
|
|
||||||
i + num_cpus as u32
|
|
||||||
+ max_cpus
|
|
||||||
+ FIRST_VCPU_PHANDLE
|
|
||||||
+ L2_CACHE_PHANDLE
|
|
||||||
+ L3_CACHE_PHANDLE,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
fdt.property_string("compatible", "cache")?;
|
|
||||||
fdt.property_null("cache-unified")?;
|
|
||||||
fdt.property_u32("cache-size", l3_cache_size)?;
|
|
||||||
fdt.property_u32("cache-line-size", l3_cache_line_size)?;
|
|
||||||
fdt.property_u32("cache-sets", l3_cache_sets)?;
|
|
||||||
fdt.property_u32("cache-level", 3)?;
|
|
||||||
fdt.end_node(l3_cache_node)?;
|
|
||||||
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(topology) = vcpu_topology {
|
if let Some(topology) = vcpu_topology {
|
||||||
let (threads_per_core, cores_per_package, packages) = topology;
|
let (threads_per_core, cores_per_package, packages) = topology;
|
||||||
let cpu_map_node = fdt.begin_node("cpu-map")?;
|
let cpu_map_node = fdt.begin_node("cpu-map")?;
|
||||||
@@ -525,7 +247,7 @@ fn create_memory_node(
|
|||||||
let memory_region_size: u64 = memory_region.size() as u64;
|
let memory_region_size: u64 = memory_region.size() as u64;
|
||||||
mem_reg_prop.push(memory_region_start_addr);
|
mem_reg_prop.push(memory_region_start_addr);
|
||||||
mem_reg_prop.push(memory_region_size);
|
mem_reg_prop.push(memory_region_size);
|
||||||
// Set the node address the first non-zero region address
|
// Set the node address the first non-zero regison address
|
||||||
if node_memory_addr == 0 {
|
if node_memory_addr == 0 {
|
||||||
node_memory_addr = memory_region_start_addr;
|
node_memory_addr = memory_region_start_addr;
|
||||||
}
|
}
|
||||||
@@ -571,7 +293,7 @@ fn create_memory_node(
|
|||||||
|
|
||||||
if ram_regions.len() > 2 {
|
if ram_regions.len() > 2 {
|
||||||
panic!(
|
panic!(
|
||||||
"There should be up to two non-continuous regions, divided by the
|
"There should be up to two non-continuous regions, devidided by the
|
||||||
gap at the end of 32bit address space."
|
gap at the end of 32bit address space."
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -896,7 +618,7 @@ fn create_pci_nodes(
|
|||||||
for pci_device_info_elem in pci_device_info.iter() {
|
for pci_device_info_elem in pci_device_info.iter() {
|
||||||
// EDK2 requires the PCIe high space above 4G address.
|
// EDK2 requires the PCIe high space above 4G address.
|
||||||
// The actual space in CLH follows the RAM. If the RAM space is small, the PCIe high space
|
// The actual space in CLH follows the RAM. If the RAM space is small, the PCIe high space
|
||||||
// could fall below 4G.
|
// could fall bellow 4G.
|
||||||
// Here we cut off PCI device space below 8G in FDT to workaround the EDK2 check.
|
// Here we cut off PCI device space below 8G in FDT to workaround the EDK2 check.
|
||||||
// But the address written in ACPI is not impacted.
|
// But the address written in ACPI is not impacted.
|
||||||
let (pci_device_base_64bit, pci_device_size_64bit) =
|
let (pci_device_base_64bit, pci_device_size_64bit) =
|
||||||
|
|||||||
@@ -111,9 +111,8 @@ pub const RAM_64BIT_START: GuestAddress = GuestAddress(0x1_0000_0000);
|
|||||||
pub const CMDLINE_MAX_SIZE: usize = 2048;
|
pub const CMDLINE_MAX_SIZE: usize = 2048;
|
||||||
|
|
||||||
/// FDT is at the beginning of RAM.
|
/// FDT is at the beginning of RAM.
|
||||||
|
/// Maximum size of the device tree blob as specified in https://www.kernel.org/doc/Documentation/arm64/booting.txt.
|
||||||
pub const FDT_START: GuestAddress = RAM_START;
|
pub const FDT_START: GuestAddress = RAM_START;
|
||||||
/// Maximum size of the device tree blob as specified in [the kernel
|
|
||||||
/// documentation](https://www.kernel.org/doc/Documentation/arm64/booting.txt).
|
|
||||||
pub const FDT_MAX_SIZE: u64 = 0x20_0000;
|
pub const FDT_MAX_SIZE: u64 = 0x20_0000;
|
||||||
|
|
||||||
/// Put ACPI table above dtb
|
/// Put ACPI table above dtb
|
||||||
|
|||||||
@@ -16,42 +16,35 @@ use crate::{DeviceType, GuestMemoryMmap, NumaNodes, PciSpaceInfo, RegionType};
|
|||||||
use hypervisor::arch::aarch64::gic::Vgic;
|
use hypervisor::arch::aarch64::gic::Vgic;
|
||||||
use log::{log_enabled, Level};
|
use log::{log_enabled, Level};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::convert::TryInto;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use thiserror::Error;
|
|
||||||
use vm_memory::{Address, GuestAddress, GuestMemory, GuestMemoryAtomic};
|
use vm_memory::{Address, GuestAddress, GuestMemory, GuestMemoryAtomic};
|
||||||
|
|
||||||
pub const _NSIG: i32 = 65;
|
pub const _NSIG: i32 = 65;
|
||||||
|
|
||||||
/// Errors thrown while configuring aarch64 system.
|
/// Errors thrown while configuring aarch64 system.
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
/// Failed to create a FDT.
|
/// Failed to create a FDT.
|
||||||
#[error("Failed to create a FDT")]
|
|
||||||
SetupFdt,
|
SetupFdt,
|
||||||
|
|
||||||
/// Failed to write FDT to memory.
|
/// Failed to write FDT to memory.
|
||||||
#[error("Failed to write FDT to memory: {0}")]
|
|
||||||
WriteFdtToMemory(fdt::Error),
|
WriteFdtToMemory(fdt::Error),
|
||||||
|
|
||||||
/// Failed to create a GIC.
|
/// Failed to create a GIC.
|
||||||
#[error("Failed to create a GIC")]
|
|
||||||
SetupGic,
|
SetupGic,
|
||||||
|
|
||||||
/// Failed to compute the initramfs address.
|
/// Failed to compute the initramfs address.
|
||||||
#[error("Failed to compute the initramfs address")]
|
|
||||||
InitramfsAddress,
|
InitramfsAddress,
|
||||||
|
|
||||||
/// Error configuring the general purpose registers
|
/// Error configuring the general purpose registers
|
||||||
#[error("Error configuring the general purpose registers: {0}")]
|
|
||||||
RegsConfiguration(hypervisor::HypervisorCpuError),
|
RegsConfiguration(hypervisor::HypervisorCpuError),
|
||||||
|
|
||||||
/// Error configuring the MPIDR register
|
/// Error configuring the MPIDR register
|
||||||
#[error("Error configuring the MPIDR register: {0}")]
|
|
||||||
VcpuRegMpidr(hypervisor::HypervisorCpuError),
|
VcpuRegMpidr(hypervisor::HypervisorCpuError),
|
||||||
|
|
||||||
/// Error initializing PMU for vcpu
|
/// Error initializing PMU for vcpu
|
||||||
#[error("Error initializing PMU for vcpu")]
|
|
||||||
VcpuInitPmu,
|
VcpuInitPmu,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,8 +180,11 @@ pub fn initramfs_load_addr(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_host_cpu_phys_bits(hypervisor: &Arc<dyn hypervisor::Hypervisor>) -> u8 {
|
pub fn get_host_cpu_phys_bits() -> u8 {
|
||||||
let host_cpu_phys_bits = hypervisor.get_host_ipa_limit().try_into().unwrap();
|
// 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 {
|
if host_cpu_phys_bits == 0 {
|
||||||
// Host kernel does not support `get_host_ipa_limit`,
|
// Host kernel does not support `get_host_ipa_limit`,
|
||||||
// we return the default value 40 here.
|
// we return the default value 40 here.
|
||||||
|
|||||||
@@ -1,27 +1,19 @@
|
|||||||
// Copyright 2020 Arm Limited (or its affiliates). All rights reserved.
|
// Copyright 2020 Arm Limited (or its affiliates). All rights reserved.
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
use std::io::{Read, Seek, SeekFrom};
|
use std::io::{Read, Seek, SeekFrom};
|
||||||
use std::os::fd::AsFd;
|
|
||||||
use std::result;
|
use std::result;
|
||||||
use thiserror::Error;
|
use vm_memory::{Bytes, GuestAddress, GuestMemory};
|
||||||
use vm_memory::{GuestAddress, GuestMemory};
|
|
||||||
|
|
||||||
/// Errors thrown while loading UEFI binary
|
/// Errors thrown while loading UEFI binary
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
/// Unable to seek to UEFI image start.
|
/// Unable to seek to UEFI image start.
|
||||||
#[error("Unable to seek to UEFI image start")]
|
|
||||||
SeekUefiStart,
|
SeekUefiStart,
|
||||||
/// Unable to seek to UEFI image end.
|
/// Unable to seek to UEFI image end.
|
||||||
#[error("Unable to seek to UEFI image end")]
|
|
||||||
SeekUefiEnd,
|
SeekUefiEnd,
|
||||||
/// UEFI image too big.
|
/// UEFI image too big.
|
||||||
#[error("UEFI image too big")]
|
|
||||||
UefiTooBig,
|
UefiTooBig,
|
||||||
/// Unable to read UEFI image
|
/// Unable to read UEFI image
|
||||||
#[error("Unable to read UEFI image")]
|
|
||||||
ReadUefiImage,
|
ReadUefiImage,
|
||||||
}
|
}
|
||||||
type Result<T> = result::Result<T, Error>;
|
type Result<T> = result::Result<T, Error>;
|
||||||
@@ -32,7 +24,7 @@ pub fn load_uefi<F, M: GuestMemory>(
|
|||||||
uefi_image: &mut F,
|
uefi_image: &mut F,
|
||||||
) -> Result<()>
|
) -> Result<()>
|
||||||
where
|
where
|
||||||
F: Read + Seek + AsFd,
|
F: Read + Seek,
|
||||||
{
|
{
|
||||||
let uefi_size = uefi_image
|
let uefi_size = uefi_image
|
||||||
.seek(SeekFrom::End(0))
|
.seek(SeekFrom::End(0))
|
||||||
@@ -44,6 +36,6 @@ where
|
|||||||
}
|
}
|
||||||
uefi_image.rewind().map_err(|_| Error::SeekUefiStart)?;
|
uefi_image.rewind().map_err(|_| Error::SeekUefiStart)?;
|
||||||
guest_mem
|
guest_mem
|
||||||
.read_exact_volatile_from(guest_addr, &mut uefi_image.as_fd(), uefi_size)
|
.read_exact_from(guest_addr, uefi_image, uefi_size)
|
||||||
.map_err(|_| Error::ReadUefiImage)
|
.map_err(|_| Error::ReadUefiImage)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,9 @@ use std::fmt;
|
|||||||
use std::result;
|
use std::result;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
use versionize::{VersionMap, Versionize, VersionizeError, VersionizeResult};
|
||||||
|
use versionize_derive::Versionize;
|
||||||
|
use vm_migration::VersionMapped;
|
||||||
|
|
||||||
type GuestMemoryMmap = vm_memory::GuestMemoryMmap<vm_memory::bitmap::AtomicBitmap>;
|
type GuestMemoryMmap = vm_memory::GuestMemoryMmap<vm_memory::bitmap::AtomicBitmap>;
|
||||||
type GuestRegionMmap = vm_memory::GuestRegionMmap<vm_memory::bitmap::AtomicBitmap>;
|
type GuestRegionMmap = vm_memory::GuestRegionMmap<vm_memory::bitmap::AtomicBitmap>;
|
||||||
@@ -45,17 +48,13 @@ pub enum Error {
|
|||||||
ModlistSetup(#[source] vm_memory::GuestMemoryError),
|
ModlistSetup(#[source] vm_memory::GuestMemoryError),
|
||||||
#[error("RSDP extends past the end of guest memory")]
|
#[error("RSDP extends past the end of guest memory")]
|
||||||
RsdpPastRamEnd,
|
RsdpPastRamEnd,
|
||||||
#[error("Failed to setup Zero Page for bzImage")]
|
|
||||||
ZeroPageSetup(#[source] vm_memory::GuestMemoryError),
|
|
||||||
#[error("Zero Page for bzImage past RAM end")]
|
|
||||||
ZeroPagePastRamEnd,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Type for returning public functions outcome.
|
/// Type for returning public functions outcome.
|
||||||
pub type Result<T> = result::Result<T, Error>;
|
pub type Result<T> = result::Result<T, Error>;
|
||||||
|
|
||||||
/// Type for memory region types.
|
/// Type for memory region types.
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize, Versionize)]
|
||||||
pub enum RegionType {
|
pub enum RegionType {
|
||||||
/// RAM type
|
/// RAM type
|
||||||
Ram,
|
Ram,
|
||||||
@@ -73,6 +72,8 @@ pub enum RegionType {
|
|||||||
Reserved,
|
Reserved,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl VersionMapped for RegionType {}
|
||||||
|
|
||||||
/// Module for aarch64 related functionality.
|
/// Module for aarch64 related functionality.
|
||||||
#[cfg(target_arch = "aarch64")]
|
#[cfg(target_arch = "aarch64")]
|
||||||
pub mod aarch64;
|
pub mod aarch64;
|
||||||
@@ -90,9 +91,8 @@ pub mod x86_64;
|
|||||||
#[cfg(target_arch = "x86_64")]
|
#[cfg(target_arch = "x86_64")]
|
||||||
pub use x86_64::{
|
pub use x86_64::{
|
||||||
arch_memory_regions, configure_system, configure_vcpu, generate_common_cpuid,
|
arch_memory_regions, configure_system, configure_vcpu, generate_common_cpuid,
|
||||||
generate_ram_ranges, get_host_cpu_phys_bits, initramfs_load_addr, layout,
|
get_host_cpu_phys_bits, initramfs_load_addr, layout, layout::CMDLINE_MAX_SIZE,
|
||||||
layout::CMDLINE_MAX_SIZE, layout::CMDLINE_START, regs, CpuidConfig, CpuidFeatureEntry,
|
layout::CMDLINE_START, regs, CpuidFeatureEntry, EntryPoint, _NSIG,
|
||||||
EntryPoint, _NSIG,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Safe wrapper for `sysconf(_SC_PAGESIZE)`.
|
/// Safe wrapper for `sysconf(_SC_PAGESIZE)`.
|
||||||
@@ -108,7 +108,6 @@ pub struct NumaNode {
|
|||||||
pub memory_regions: Vec<Arc<GuestRegionMmap>>,
|
pub memory_regions: Vec<Arc<GuestRegionMmap>>,
|
||||||
pub hotplug_regions: Vec<Arc<GuestRegionMmap>>,
|
pub hotplug_regions: Vec<Arc<GuestRegionMmap>>,
|
||||||
pub cpus: Vec<u8>,
|
pub cpus: Vec<u8>,
|
||||||
pub pci_segments: Vec<u16>,
|
|
||||||
pub distances: BTreeMap<u32, u8>,
|
pub distances: BTreeMap<u32, u8>,
|
||||||
pub memory_zones: Vec<String>,
|
pub memory_zones: Vec<String>,
|
||||||
#[cfg(target_arch = "x86_64")]
|
#[cfg(target_arch = "x86_64")]
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,4 @@
|
|||||||
// Copyright 2017 The Chromium OS Authors. All rights reserved.
|
// Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE-BSD-3-Clause file.
|
// found in the LICENSE-BSD-3-Clause file.
|
||||||
|
|
||||||
@@ -17,7 +15,7 @@ pub const MP_IRQDIR_DEFAULT: ::std::os::raw::c_uint = 0;
|
|||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Debug, Default, Copy, Clone)]
|
#[derive(Debug, Default, Copy, Clone)]
|
||||||
pub struct mpf_intel {
|
pub struct mpf_intel {
|
||||||
pub signature: [::std::os::raw::c_uchar; 4usize],
|
pub signature: [::std::os::raw::c_char; 4usize],
|
||||||
pub physptr: ::std::os::raw::c_uint,
|
pub physptr: ::std::os::raw::c_uint,
|
||||||
pub length: ::std::os::raw::c_uchar,
|
pub length: ::std::os::raw::c_uchar,
|
||||||
pub specification: ::std::os::raw::c_uchar,
|
pub specification: ::std::os::raw::c_uchar,
|
||||||
@@ -32,12 +30,12 @@ pub struct mpf_intel {
|
|||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Debug, Default, Copy, Clone)]
|
#[derive(Debug, Default, Copy, Clone)]
|
||||||
pub struct mpc_table {
|
pub struct mpc_table {
|
||||||
pub signature: [::std::os::raw::c_uchar; 4usize],
|
pub signature: [::std::os::raw::c_char; 4usize],
|
||||||
pub length: ::std::os::raw::c_ushort,
|
pub length: ::std::os::raw::c_ushort,
|
||||||
pub spec: ::std::os::raw::c_uchar,
|
pub spec: ::std::os::raw::c_char,
|
||||||
pub checksum: ::std::os::raw::c_uchar,
|
pub checksum: ::std::os::raw::c_char,
|
||||||
pub oem: [::std::os::raw::c_uchar; 8usize],
|
pub oem: [::std::os::raw::c_char; 8usize],
|
||||||
pub productid: [::std::os::raw::c_uchar; 12usize],
|
pub productid: [::std::os::raw::c_char; 12usize],
|
||||||
pub oemptr: ::std::os::raw::c_uint,
|
pub oemptr: ::std::os::raw::c_uint,
|
||||||
pub oemsize: ::std::os::raw::c_ushort,
|
pub oemsize: ::std::os::raw::c_ushort,
|
||||||
pub oemcount: ::std::os::raw::c_ushort,
|
pub oemcount: ::std::os::raw::c_ushort,
|
||||||
@@ -106,9 +104,9 @@ pub struct mpc_lintsrc {
|
|||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Debug, Default, Copy, Clone)]
|
#[derive(Debug, Default, Copy, Clone)]
|
||||||
pub struct mpc_oemtable {
|
pub struct mpc_oemtable {
|
||||||
pub signature: [::std::os::raw::c_uchar; 4usize],
|
pub signature: [::std::os::raw::c_char; 4usize],
|
||||||
pub length: ::std::os::raw::c_ushort,
|
pub length: ::std::os::raw::c_ushort,
|
||||||
pub rev: ::std::os::raw::c_uchar,
|
pub rev: ::std::os::raw::c_char,
|
||||||
pub checksum: ::std::os::raw::c_uchar,
|
pub checksum: ::std::os::raw::c_char,
|
||||||
pub mpc: [::std::os::raw::c_uchar; 8usize],
|
pub mpc: [::std::os::raw::c_char; 8usize],
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,13 +6,13 @@
|
|||||||
// found in the LICENSE-BSD-3-Clause file.
|
// found in the LICENSE-BSD-3-Clause file.
|
||||||
|
|
||||||
use crate::layout::{APIC_START, HIGH_RAM_START, IOAPIC_START};
|
use crate::layout::{APIC_START, HIGH_RAM_START, IOAPIC_START};
|
||||||
use crate::x86_64::{get_x2apic_id, mpspec};
|
use crate::x86_64::mpspec;
|
||||||
use crate::GuestMemoryMmap;
|
use crate::GuestMemoryMmap;
|
||||||
use libc::c_uchar;
|
use libc::c_char;
|
||||||
|
use std::io;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::result;
|
use std::result;
|
||||||
use std::slice;
|
use std::slice;
|
||||||
use thiserror::Error;
|
|
||||||
use vm_memory::{Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryError};
|
use vm_memory::{Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryError};
|
||||||
|
|
||||||
// This is a workaround to the Rust enforcement specifying that any implementation of a foreign
|
// This is a workaround to the Rust enforcement specifying that any implementation of a foreign
|
||||||
@@ -50,40 +50,29 @@ unsafe impl ByteValued for MpcLintsrcWrapper {}
|
|||||||
// SAFETY: see above
|
// SAFETY: see above
|
||||||
unsafe impl ByteValued for MpfIntelWrapper {}
|
unsafe impl ByteValued for MpfIntelWrapper {}
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
/// There was too little guest memory to store the entire MP table.
|
/// There was too little guest memory to store the entire MP table.
|
||||||
#[error("There was too little guest memory to store the entire MP table")]
|
|
||||||
NotEnoughMemory,
|
NotEnoughMemory,
|
||||||
/// The MP table has too little address space to be stored.
|
/// The MP table has too little address space to be stored.
|
||||||
#[error("The MP table has too little address space to be stored")]
|
|
||||||
AddressOverflow,
|
AddressOverflow,
|
||||||
/// Failure while zeroing out the memory for the MP table.
|
/// Failure while zeroing out the memory for the MP table.
|
||||||
#[error("Failure while zeroing out the memory for the MP table: {0}")]
|
|
||||||
Clear(GuestMemoryError),
|
Clear(GuestMemoryError),
|
||||||
/// Number of CPUs exceeds the maximum supported CPUs
|
/// Number of CPUs exceeds the maximum supported CPUs
|
||||||
#[error("Number of CPUs exceeds the maximum supported CPUs")]
|
|
||||||
TooManyCpus,
|
TooManyCpus,
|
||||||
/// Failure to write the MP floating pointer.
|
/// Failure to write the MP floating pointer.
|
||||||
#[error("Failure to write the MP floating pointer: {0}")]
|
|
||||||
WriteMpfIntel(GuestMemoryError),
|
WriteMpfIntel(GuestMemoryError),
|
||||||
/// Failure to write MP CPU entry.
|
/// Failure to write MP CPU entry.
|
||||||
#[error("Failure to write MP CPU entry: {0}")]
|
|
||||||
WriteMpcCpu(GuestMemoryError),
|
WriteMpcCpu(GuestMemoryError),
|
||||||
/// Failure to write MP ioapic entry.
|
/// Failure to write MP ioapic entry.
|
||||||
#[error("Failure to write MP ioapic entry: {0}")]
|
|
||||||
WriteMpcIoapic(GuestMemoryError),
|
WriteMpcIoapic(GuestMemoryError),
|
||||||
/// Failure to write MP bus entry.
|
/// Failure to write MP bus entry.
|
||||||
#[error("Failure to write MP bus entry: {0}")]
|
|
||||||
WriteMpcBus(GuestMemoryError),
|
WriteMpcBus(GuestMemoryError),
|
||||||
/// Failure to write MP interrupt source entry.
|
/// Failure to write MP interrupt source entry.
|
||||||
#[error("Failure to write MP interrupt source entry: {0}")]
|
|
||||||
WriteMpcIntsrc(GuestMemoryError),
|
WriteMpcIntsrc(GuestMemoryError),
|
||||||
/// Failure to write MP local interrupt source entry.
|
/// Failure to write MP local interrupt source entry.
|
||||||
#[error("Failure to write MP local interrupt source entry: {0}")]
|
|
||||||
WriteMpcLintsrc(GuestMemoryError),
|
WriteMpcLintsrc(GuestMemoryError),
|
||||||
/// Failure to write MP table header.
|
/// Failure to write MP table header.
|
||||||
#[error("Failure to write MP table header: {0}")]
|
|
||||||
WriteMpcTable(GuestMemoryError),
|
WriteMpcTable(GuestMemoryError),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,13 +83,18 @@ pub type Result<T> = result::Result<T, Error>;
|
|||||||
// a large number for FC usecases.
|
// a large number for FC usecases.
|
||||||
pub const MAX_SUPPORTED_CPUS: u32 = 254;
|
pub const MAX_SUPPORTED_CPUS: u32 = 254;
|
||||||
|
|
||||||
|
// Convenience macro for making arrays of diverse character types.
|
||||||
|
macro_rules! char_array {
|
||||||
|
($t:ty; $( $c:expr ),*) => ( [ $( $c as $t ),* ] )
|
||||||
|
}
|
||||||
|
|
||||||
// Most of these variables are sourced from the Intel MP Spec 1.4.
|
// Most of these variables are sourced from the Intel MP Spec 1.4.
|
||||||
const SMP_MAGIC_IDENT: &[c_uchar; 4] = b"_MP_";
|
const SMP_MAGIC_IDENT: [c_char; 4] = char_array!(c_char; '_', 'M', 'P', '_');
|
||||||
const MPC_SIGNATURE: &[c_uchar; 4] = b"PCMP";
|
const MPC_SIGNATURE: [c_char; 4] = char_array!(c_char; 'P', 'C', 'M', 'P');
|
||||||
const MPC_SPEC: u8 = 4;
|
const MPC_SPEC: i8 = 4;
|
||||||
const MPC_OEM: &[c_uchar; 8] = b"FC ";
|
const MPC_OEM: [c_char; 8] = char_array!(c_char; 'F', 'C', ' ', ' ', ' ', ' ', ' ', ' ');
|
||||||
const MPC_PRODUCT_ID: &[c_uchar; 12] = &[b'0'; 12];
|
const MPC_PRODUCT_ID: [c_char; 12] = ['0' as c_char; 12];
|
||||||
const BUS_TYPE_ISA: &[c_uchar; 6] = b"ISA ";
|
const BUS_TYPE_ISA: [u8; 6] = char_array!(u8; 'I', 'S', 'A', ' ', ' ', ' ');
|
||||||
const APIC_VERSION: u8 = 0x14;
|
const APIC_VERSION: u8 = 0x14;
|
||||||
const CPU_STEPPING: u32 = 0x600;
|
const CPU_STEPPING: u32 = 0x600;
|
||||||
const CPU_FEATURE_APIC: u32 = 0x200;
|
const CPU_FEATURE_APIC: u32 = 0x200;
|
||||||
@@ -132,18 +126,9 @@ fn compute_mp_size(num_cpus: u8) -> usize {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Performs setup of the MP table for the given `num_cpus`.
|
/// Performs setup of the MP table for the given `num_cpus`.
|
||||||
pub fn setup_mptable(
|
pub fn setup_mptable(offset: GuestAddress, mem: &GuestMemoryMmap, num_cpus: u8) -> Result<()> {
|
||||||
offset: GuestAddress,
|
if num_cpus as u32 > MAX_SUPPORTED_CPUS {
|
||||||
mem: &GuestMemoryMmap,
|
return Err(Error::TooManyCpus);
|
||||||
num_cpus: u8,
|
|
||||||
topology: Option<(u8, u8, u8)>,
|
|
||||||
) -> Result<()> {
|
|
||||||
if num_cpus > 0 {
|
|
||||||
let cpu_id_max = num_cpus - 1;
|
|
||||||
let x2apic_id_max = get_x2apic_id(cpu_id_max.into(), topology);
|
|
||||||
if x2apic_id_max >= MAX_SUPPORTED_CPUS {
|
|
||||||
return Err(Error::TooManyCpus);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Used to keep track of the next base pointer into the MP table.
|
// Used to keep track of the next base pointer into the MP table.
|
||||||
@@ -157,7 +142,7 @@ pub fn setup_mptable(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut checksum: u8 = 0;
|
let mut checksum: u8 = 0;
|
||||||
let ioapicid: u8 = MAX_SUPPORTED_CPUS as u8 + 1;
|
let ioapicid: u8 = num_cpus + 1;
|
||||||
|
|
||||||
// The checked_add here ensures the all of the following base_mp.unchecked_add's will be without
|
// The checked_add here ensures the all of the following base_mp.unchecked_add's will be without
|
||||||
// overflow.
|
// overflow.
|
||||||
@@ -169,13 +154,13 @@ pub fn setup_mptable(
|
|||||||
return Err(Error::AddressOverflow);
|
return Err(Error::AddressOverflow);
|
||||||
}
|
}
|
||||||
|
|
||||||
mem.read_exact_volatile_from(base_mp, &mut vec![0; mp_size].as_slice(), mp_size)
|
mem.read_exact_from(base_mp, &mut io::repeat(0), mp_size)
|
||||||
.map_err(Error::Clear)?;
|
.map_err(Error::Clear)?;
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut mpf_intel = MpfIntelWrapper(mpspec::mpf_intel::default());
|
let mut mpf_intel = MpfIntelWrapper(mpspec::mpf_intel::default());
|
||||||
let size = mem::size_of::<MpfIntelWrapper>() as u64;
|
let size = mem::size_of::<MpfIntelWrapper>() as u64;
|
||||||
mpf_intel.0.signature = *SMP_MAGIC_IDENT;
|
mpf_intel.0.signature = SMP_MAGIC_IDENT;
|
||||||
mpf_intel.0.length = 1;
|
mpf_intel.0.length = 1;
|
||||||
mpf_intel.0.specification = 4;
|
mpf_intel.0.specification = 4;
|
||||||
mpf_intel.0.physptr = (base_mp.raw_value() + size) as u32;
|
mpf_intel.0.physptr = (base_mp.raw_value() + size) as u32;
|
||||||
@@ -195,7 +180,7 @@ pub fn setup_mptable(
|
|||||||
for cpu_id in 0..num_cpus {
|
for cpu_id in 0..num_cpus {
|
||||||
let mut mpc_cpu = MpcCpuWrapper(mpspec::mpc_cpu::default());
|
let mut mpc_cpu = MpcCpuWrapper(mpspec::mpc_cpu::default());
|
||||||
mpc_cpu.0.type_ = mpspec::MP_PROCESSOR as u8;
|
mpc_cpu.0.type_ = mpspec::MP_PROCESSOR as u8;
|
||||||
mpc_cpu.0.apicid = get_x2apic_id(cpu_id as u32, topology) as u8;
|
mpc_cpu.0.apicid = cpu_id;
|
||||||
mpc_cpu.0.apicver = APIC_VERSION;
|
mpc_cpu.0.apicver = APIC_VERSION;
|
||||||
mpc_cpu.0.cpuflag = mpspec::CPU_ENABLED as u8
|
mpc_cpu.0.cpuflag = mpspec::CPU_ENABLED as u8
|
||||||
| if cpu_id == 0 {
|
| if cpu_id == 0 {
|
||||||
@@ -216,7 +201,7 @@ pub fn setup_mptable(
|
|||||||
let mut mpc_bus = MpcBusWrapper(mpspec::mpc_bus::default());
|
let mut mpc_bus = MpcBusWrapper(mpspec::mpc_bus::default());
|
||||||
mpc_bus.0.type_ = mpspec::MP_BUS as u8;
|
mpc_bus.0.type_ = mpspec::MP_BUS as u8;
|
||||||
mpc_bus.0.busid = 0;
|
mpc_bus.0.busid = 0;
|
||||||
mpc_bus.0.bustype = *BUS_TYPE_ISA;
|
mpc_bus.0.bustype = BUS_TYPE_ISA;
|
||||||
mem.write_obj(mpc_bus, base_mp)
|
mem.write_obj(mpc_bus, base_mp)
|
||||||
.map_err(Error::WriteMpcBus)?;
|
.map_err(Error::WriteMpcBus)?;
|
||||||
base_mp = base_mp.unchecked_add(size as u64);
|
base_mp = base_mp.unchecked_add(size as u64);
|
||||||
@@ -287,14 +272,14 @@ pub fn setup_mptable(
|
|||||||
|
|
||||||
{
|
{
|
||||||
let mut mpc_table = MpcTableWrapper(mpspec::mpc_table::default());
|
let mut mpc_table = MpcTableWrapper(mpspec::mpc_table::default());
|
||||||
mpc_table.0.signature = *MPC_SIGNATURE;
|
mpc_table.0.signature = MPC_SIGNATURE;
|
||||||
mpc_table.0.length = table_end.unchecked_offset_from(table_base) as u16;
|
mpc_table.0.length = table_end.unchecked_offset_from(table_base) as u16;
|
||||||
mpc_table.0.spec = MPC_SPEC;
|
mpc_table.0.spec = MPC_SPEC;
|
||||||
mpc_table.0.oem = *MPC_OEM;
|
mpc_table.0.oem = MPC_OEM;
|
||||||
mpc_table.0.productid = *MPC_PRODUCT_ID;
|
mpc_table.0.productid = MPC_PRODUCT_ID;
|
||||||
mpc_table.0.lapic = APIC_START.0 as u32;
|
mpc_table.0.lapic = APIC_START.0 as u32;
|
||||||
checksum = checksum.wrapping_add(compute_checksum(&mpc_table.0));
|
checksum = checksum.wrapping_add(compute_checksum(&mpc_table.0));
|
||||||
mpc_table.0.checksum = (!checksum).wrapping_add(1);
|
mpc_table.0.checksum = (!checksum).wrapping_add(1) as i8;
|
||||||
mem.write_obj(mpc_table, table_base)
|
mem.write_obj(mpc_table, table_base)
|
||||||
.map_err(Error::WriteMpcTable)?;
|
.map_err(Error::WriteMpcTable)?;
|
||||||
}
|
}
|
||||||
@@ -306,9 +291,7 @@ pub fn setup_mptable(
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::layout::MPTABLE_START;
|
use crate::layout::MPTABLE_START;
|
||||||
use vm_memory::{
|
use vm_memory::{GuestAddress, GuestUsize};
|
||||||
bitmap::BitmapSlice, GuestUsize, VolatileMemoryError, VolatileSlice, WriteVolatile,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn table_entry_size(type_: u8) -> usize {
|
fn table_entry_size(type_: u8) -> usize {
|
||||||
match type_ as u32 {
|
match type_ as u32 {
|
||||||
@@ -327,7 +310,7 @@ mod tests {
|
|||||||
let mem =
|
let mem =
|
||||||
GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(num_cpus))]).unwrap();
|
GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(num_cpus))]).unwrap();
|
||||||
|
|
||||||
setup_mptable(MPTABLE_START, &mem, num_cpus, None).unwrap();
|
setup_mptable(MPTABLE_START, &mem, num_cpus).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -336,7 +319,7 @@ mod tests {
|
|||||||
let mem = GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(num_cpus) - 1)])
|
let mem = GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(num_cpus) - 1)])
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert!(setup_mptable(MPTABLE_START, &mem, num_cpus, None).is_err());
|
assert!(setup_mptable(MPTABLE_START, &mem, num_cpus).is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -345,7 +328,7 @@ mod tests {
|
|||||||
let mem =
|
let mem =
|
||||||
GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(num_cpus))]).unwrap();
|
GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(num_cpus))]).unwrap();
|
||||||
|
|
||||||
setup_mptable(MPTABLE_START, &mem, num_cpus, None).unwrap();
|
setup_mptable(MPTABLE_START, &mem, num_cpus).unwrap();
|
||||||
|
|
||||||
let mpf_intel: MpfIntelWrapper = mem.read_obj(MPTABLE_START).unwrap();
|
let mpf_intel: MpfIntelWrapper = mem.read_obj(MPTABLE_START).unwrap();
|
||||||
|
|
||||||
@@ -361,31 +344,27 @@ mod tests {
|
|||||||
let mem =
|
let mem =
|
||||||
GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(num_cpus))]).unwrap();
|
GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(num_cpus))]).unwrap();
|
||||||
|
|
||||||
setup_mptable(MPTABLE_START, &mem, num_cpus, None).unwrap();
|
setup_mptable(MPTABLE_START, &mem, num_cpus).unwrap();
|
||||||
|
|
||||||
let mpf_intel: MpfIntelWrapper = mem.read_obj(MPTABLE_START).unwrap();
|
let mpf_intel: MpfIntelWrapper = mem.read_obj(MPTABLE_START).unwrap();
|
||||||
let mpc_offset = GuestAddress(mpf_intel.0.physptr as GuestUsize);
|
let mpc_offset = GuestAddress(mpf_intel.0.physptr as GuestUsize);
|
||||||
let mpc_table: MpcTableWrapper = mem.read_obj(mpc_offset).unwrap();
|
let mpc_table: MpcTableWrapper = mem.read_obj(mpc_offset).unwrap();
|
||||||
|
|
||||||
struct Sum(u8);
|
struct Sum(u8);
|
||||||
impl WriteVolatile for Sum {
|
impl io::Write for Sum {
|
||||||
fn write_volatile<B: BitmapSlice>(
|
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||||
&mut self,
|
for v in buf.iter() {
|
||||||
buf: &VolatileSlice<B>,
|
|
||||||
) -> result::Result<usize, VolatileMemoryError> {
|
|
||||||
let mut tmp = vec![0u8; buf.len()];
|
|
||||||
tmp.write_all_volatile(buf)?;
|
|
||||||
|
|
||||||
for v in tmp.iter() {
|
|
||||||
self.0 = self.0.wrapping_add(*v);
|
self.0 = self.0.wrapping_add(*v);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(buf.len())
|
Ok(buf.len())
|
||||||
}
|
}
|
||||||
|
fn flush(&mut self) -> io::Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut sum = Sum(0);
|
let mut sum = Sum(0);
|
||||||
mem.write_volatile_to(mpc_offset, &mut sum, mpc_table.0.length as usize)
|
mem.write_to(mpc_offset, &mut sum, mpc_table.0.length as usize)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(sum.0, 0);
|
assert_eq!(sum.0, 0);
|
||||||
}
|
}
|
||||||
@@ -399,7 +378,7 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
for i in 0..MAX_SUPPORTED_CPUS as u8 {
|
for i in 0..MAX_SUPPORTED_CPUS as u8 {
|
||||||
setup_mptable(MPTABLE_START, &mem, i, None).unwrap();
|
setup_mptable(MPTABLE_START, &mem, i).unwrap();
|
||||||
|
|
||||||
let mpf_intel: MpfIntelWrapper = mem.read_obj(MPTABLE_START).unwrap();
|
let mpf_intel: MpfIntelWrapper = mem.read_obj(MPTABLE_START).unwrap();
|
||||||
let mpc_offset = GuestAddress(mpf_intel.0.physptr as GuestUsize);
|
let mpc_offset = GuestAddress(mpf_intel.0.physptr as GuestUsize);
|
||||||
@@ -432,7 +411,7 @@ mod tests {
|
|||||||
let mem =
|
let mem =
|
||||||
GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(cpus as u8))]).unwrap();
|
GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(cpus as u8))]).unwrap();
|
||||||
|
|
||||||
let result = setup_mptable(MPTABLE_START, &mem, cpus as u8, None);
|
let result = setup_mptable(MPTABLE_START, &mem, cpus as u8);
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,55 +6,40 @@
|
|||||||
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE-BSD-3-Clause file.
|
// found in the LICENSE-BSD-3-Clause file.
|
||||||
use crate::layout::{
|
use crate::layout::{BOOT_GDT_START, BOOT_IDT_START, PVH_INFO_START};
|
||||||
BOOT_GDT_START, BOOT_IDT_START, BOOT_STACK_POINTER, PVH_INFO_START, ZERO_PAGE_START,
|
use crate::GuestMemoryMmap;
|
||||||
};
|
|
||||||
use crate::{EntryPoint, GuestMemoryMmap};
|
|
||||||
use hypervisor::arch::x86::gdt::{gdt_entry, segment_from_gdt};
|
use hypervisor::arch::x86::gdt::{gdt_entry, segment_from_gdt};
|
||||||
use hypervisor::arch::x86::regs::CR0_PE;
|
use hypervisor::arch::x86::regs::CR0_PE;
|
||||||
use hypervisor::arch::x86::{FpuState, SpecialRegisters, StandardRegisters};
|
use hypervisor::arch::x86::{FpuState, SpecialRegisters, StandardRegisters};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::{mem, result};
|
use std::{mem, result};
|
||||||
use thiserror::Error;
|
|
||||||
use vm_memory::{Address, Bytes, GuestMemory, GuestMemoryError};
|
use vm_memory::{Address, Bytes, GuestMemory, GuestMemoryError};
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
/// Failed to get SREGs for this CPU.
|
/// Failed to get SREGs for this CPU.
|
||||||
#[error("Failed to get SREGs for this CPU: {0}")]
|
|
||||||
GetStatusRegisters(hypervisor::HypervisorCpuError),
|
GetStatusRegisters(hypervisor::HypervisorCpuError),
|
||||||
/// Failed to set base registers for this CPU.
|
/// Failed to set base registers for this CPU.
|
||||||
#[error("Failed to set base registers for this CPU: {0}")]
|
|
||||||
SetBaseRegisters(hypervisor::HypervisorCpuError),
|
SetBaseRegisters(hypervisor::HypervisorCpuError),
|
||||||
/// Failed to configure the FPU.
|
/// Failed to configure the FPU.
|
||||||
#[error("Failed to configure the FPU: {0}")]
|
|
||||||
SetFpuRegisters(hypervisor::HypervisorCpuError),
|
SetFpuRegisters(hypervisor::HypervisorCpuError),
|
||||||
/// Setting up MSRs failed.
|
/// Setting up MSRs failed.
|
||||||
#[error("Setting up MSRs failed: {0}")]
|
|
||||||
SetModelSpecificRegisters(hypervisor::HypervisorCpuError),
|
SetModelSpecificRegisters(hypervisor::HypervisorCpuError),
|
||||||
/// Failed to set SREGs for this CPU.
|
/// Failed to set SREGs for this CPU.
|
||||||
#[error("Failed to set SREGs for this CPU: {0}")]
|
|
||||||
SetStatusRegisters(hypervisor::HypervisorCpuError),
|
SetStatusRegisters(hypervisor::HypervisorCpuError),
|
||||||
/// Checking the GDT address failed.
|
/// Checking the GDT address failed.
|
||||||
#[error("Checking the GDT address failed")]
|
|
||||||
CheckGdtAddr,
|
CheckGdtAddr,
|
||||||
/// Writing the GDT to RAM failed.
|
/// Writing the GDT to RAM failed.
|
||||||
#[error("Writing the GDT to RAM failed: {0}")]
|
|
||||||
WriteGdt(GuestMemoryError),
|
WriteGdt(GuestMemoryError),
|
||||||
/// Writing the IDT to RAM failed.
|
/// Writing the IDT to RAM failed.
|
||||||
#[error("Writing the IDT to RAM failed: {0}")]
|
|
||||||
WriteIdt(GuestMemoryError),
|
WriteIdt(GuestMemoryError),
|
||||||
/// Writing PDPTE to RAM failed.
|
/// Writing PDPTE to RAM failed.
|
||||||
#[error("Writing PDPTE to RAM failed: {0}")]
|
|
||||||
WritePdpteAddress(GuestMemoryError),
|
WritePdpteAddress(GuestMemoryError),
|
||||||
/// Writing PDE to RAM failed.
|
/// Writing PDE to RAM failed.
|
||||||
#[error("Writing PDE to RAM failed: {0}")]
|
|
||||||
WritePdeAddress(GuestMemoryError),
|
WritePdeAddress(GuestMemoryError),
|
||||||
/// Writing PML4 to RAM failed.
|
/// Writing PML4 to RAM failed.
|
||||||
#[error("Writing PML4 to RAM failed: {0}")]
|
|
||||||
WritePml4Address(GuestMemoryError),
|
WritePml4Address(GuestMemoryError),
|
||||||
/// Writing PML5 to RAM failed.
|
/// Writing PML5 to RAM failed.
|
||||||
#[error("Writing PML5 to RAM failed: {0}")]
|
|
||||||
WritePml5Address(GuestMemoryError),
|
WritePml5Address(GuestMemoryError),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,22 +77,13 @@ pub fn setup_msrs(vcpu: &Arc<dyn hypervisor::Vcpu>) -> Result<()> {
|
|||||||
/// # Arguments
|
/// # Arguments
|
||||||
///
|
///
|
||||||
/// * `vcpu` - Structure for the VCPU that holds the VCPU's fd.
|
/// * `vcpu` - Structure for the VCPU that holds the VCPU's fd.
|
||||||
/// * `entry_point` - Description of the boot entry to set up.
|
/// * `boot_ip` - Starting instruction pointer.
|
||||||
pub fn setup_regs(vcpu: &Arc<dyn hypervisor::Vcpu>, entry_point: EntryPoint) -> Result<()> {
|
pub fn setup_regs(vcpu: &Arc<dyn hypervisor::Vcpu>, boot_ip: u64) -> Result<()> {
|
||||||
let regs = match entry_point.setup_header {
|
let regs = StandardRegisters {
|
||||||
None => StandardRegisters {
|
rflags: 0x0000000000000002u64,
|
||||||
rflags: 0x0000000000000002u64,
|
rbx: PVH_INFO_START.raw_value(),
|
||||||
rip: entry_point.entry_addr.raw_value(),
|
rip: boot_ip,
|
||||||
rbx: PVH_INFO_START.raw_value(),
|
..Default::default()
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
Some(_) => StandardRegisters {
|
|
||||||
rflags: 0x0000000000000002u64,
|
|
||||||
rip: entry_point.entry_addr.raw_value(),
|
|
||||||
rsp: BOOT_STACK_POINTER.raw_value(),
|
|
||||||
rsi: ZERO_PAGE_START.raw_value(),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
vcpu.set_regs(®s).map_err(Error::SetBaseRegisters)
|
vcpu.set_regs(®s).map_err(Error::SetBaseRegisters)
|
||||||
}
|
}
|
||||||
@@ -188,6 +164,7 @@ pub fn configure_segments_and_sregs(
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::GuestMemoryMmap;
|
||||||
use vm_memory::GuestAddress;
|
use vm_memory::GuestAddress;
|
||||||
|
|
||||||
fn create_guest_mem() -> GuestMemoryMmap {
|
fn create_guest_mem() -> GuestMemoryMmap {
|
||||||
|
|||||||
@@ -8,36 +8,53 @@
|
|||||||
|
|
||||||
use crate::layout::SMBIOS_START;
|
use crate::layout::SMBIOS_START;
|
||||||
use crate::GuestMemoryMmap;
|
use crate::GuestMemoryMmap;
|
||||||
|
use std::fmt::{self, Display};
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::result;
|
use std::result;
|
||||||
use std::slice;
|
use std::slice;
|
||||||
use thiserror::Error;
|
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use vm_memory::ByteValued;
|
use vm_memory::ByteValued;
|
||||||
use vm_memory::{Address, Bytes, GuestAddress};
|
use vm_memory::{Address, Bytes, GuestAddress};
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
/// There was too little guest memory to store the entire SMBIOS table.
|
/// There was too little guest memory to store the entire SMBIOS table.
|
||||||
#[error("There was too little guest memory to store the SMBIOS table")]
|
|
||||||
NotEnoughMemory,
|
NotEnoughMemory,
|
||||||
/// The SMBIOS table has too little address space to be stored.
|
/// The SMBIOS table has too little address space to be stored.
|
||||||
#[error("The SMBIOS table has too little address space to be stored")]
|
|
||||||
AddressOverflow,
|
AddressOverflow,
|
||||||
/// Failure while zeroing out the memory for the SMBIOS table.
|
/// Failure while zeroing out the memory for the SMBIOS table.
|
||||||
#[error("Failure while zeroing out the memory for the SMBIOS table")]
|
|
||||||
Clear,
|
Clear,
|
||||||
/// Failure to write SMBIOS entrypoint structure
|
/// Failure to write SMBIOS entrypoint structure
|
||||||
#[error("Failure to write SMBIOS entrypoint structure")]
|
|
||||||
WriteSmbiosEp,
|
WriteSmbiosEp,
|
||||||
/// Failure to write additional data to memory
|
/// Failure to write additional data to memory
|
||||||
#[error("Failure to write additional data to memory")]
|
|
||||||
WriteData,
|
WriteData,
|
||||||
/// Failure to parse uuid, uuid format may be error
|
/// Failure to parse uuid, uuid format may be error
|
||||||
#[error("Failure to parse uuid: {0}")]
|
|
||||||
ParseUuid(uuid::Error),
|
ParseUuid(uuid::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for Error {}
|
||||||
|
|
||||||
|
impl Display for Error {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
use self::Error::*;
|
||||||
|
|
||||||
|
let description = match self {
|
||||||
|
NotEnoughMemory => {
|
||||||
|
"There was too little guest memory to store the SMBIOS table".to_string()
|
||||||
|
}
|
||||||
|
AddressOverflow => {
|
||||||
|
"The SMBIOS table has too little address space to be stored".to_string()
|
||||||
|
}
|
||||||
|
Clear => "Failure while zeroing out the memory for the SMBIOS table".to_string(),
|
||||||
|
WriteSmbiosEp => "Failure to write SMBIOS entrypoint structure".to_string(),
|
||||||
|
WriteData => "Failure to write additional data to memory".to_string(),
|
||||||
|
ParseUuid(e) => format!("Failure to parse uuid: {e}"),
|
||||||
|
};
|
||||||
|
|
||||||
|
write!(f, "SMBIOS error: {description}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub type Result<T> = result::Result<T, Error>;
|
pub type Result<T> = result::Result<T, Error>;
|
||||||
|
|
||||||
// Constants sourced from SMBIOS Spec 3.2.0.
|
// Constants sourced from SMBIOS Spec 3.2.0.
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ fn tdvf_descriptor_offset(file: &mut File) -> Result<(SeekFrom, bool), TdvfError
|
|||||||
// We start after the footer GUID and the table length.
|
// We start after the footer GUID and the table length.
|
||||||
let mut offset = table_size - 18;
|
let mut offset = table_size - 18;
|
||||||
|
|
||||||
debug!("Parsing GUID structure");
|
debug!("Parsing GUIDed structure");
|
||||||
while offset >= 18 {
|
while offset >= 18 {
|
||||||
let entry_uuid = Uuid::from_slice_le(&table[offset - 16..offset])
|
let entry_uuid = Uuid::from_slice_le(&table[offset - 16..offset])
|
||||||
.map_err(TdvfError::UuidCreation)?;
|
.map_err(TdvfError::UuidCreation)?;
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
[package]
|
|
||||||
authors = ["The Chromium OS Authors", "The Cloud Hypervisor Authors"]
|
|
||||||
edition = "2021"
|
|
||||||
name = "block"
|
|
||||||
version = "0.1.0"
|
|
||||||
|
|
||||||
[features]
|
|
||||||
default = []
|
|
||||||
io_uring = ["dep:io-uring"]
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
byteorder = "1.5.0"
|
|
||||||
crc-any = "2.4.4"
|
|
||||||
io-uring = { version = "0.6.3", optional = true }
|
|
||||||
libc = "0.2.153"
|
|
||||||
log = "0.4.21"
|
|
||||||
remain = "0.2.13"
|
|
||||||
serde = { version = "1.0.197", features = ["derive"] }
|
|
||||||
smallvec = "1.13.2"
|
|
||||||
thiserror = "1.0.60"
|
|
||||||
uuid = { version = "1.8.0", features = ["v4"] }
|
|
||||||
virtio-bindings = { version = "0.2.2", features = ["virtio-v5_0_0"] }
|
|
||||||
virtio-queue = "0.12.0"
|
|
||||||
vm-memory = { version = "0.14.1", features = [
|
|
||||||
"backend-atomic",
|
|
||||||
"backend-bitmap",
|
|
||||||
"backend-mmap",
|
|
||||||
] }
|
|
||||||
vm-virtio = { path = "../vm-virtio" }
|
|
||||||
vmm-sys-util = "0.12.1"
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
// Copyright © 2021 Intel Corporation
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
|
||||||
|
|
||||||
use crate::DiskTopology;
|
|
||||||
use thiserror::Error;
|
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
|
||||||
pub enum DiskFileError {
|
|
||||||
/// Failed getting disk file size.
|
|
||||||
#[error("Failed getting disk file size: {0}")]
|
|
||||||
Size(#[source] std::io::Error),
|
|
||||||
/// Failed creating a new AsyncIo.
|
|
||||||
#[error("Failed creating a new AsyncIo: {0}")]
|
|
||||||
NewAsyncIo(#[source] std::io::Error),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type DiskFileResult<T> = std::result::Result<T, DiskFileError>;
|
|
||||||
|
|
||||||
pub trait DiskFile: Send {
|
|
||||||
fn size(&mut self) -> DiskFileResult<u64>;
|
|
||||||
fn new_async_io(&self, ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>>;
|
|
||||||
fn topology(&mut self) -> DiskTopology {
|
|
||||||
DiskTopology::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
|
||||||
pub enum AsyncIoError {
|
|
||||||
/// Failed vectored reading from file.
|
|
||||||
#[error("Failed vectored reading from file: {0}")]
|
|
||||||
ReadVectored(#[source] std::io::Error),
|
|
||||||
/// Failed vectored writing to file.
|
|
||||||
#[error("Failed vectored writing to file: {0}")]
|
|
||||||
WriteVectored(#[source] std::io::Error),
|
|
||||||
/// Failed synchronizing file.
|
|
||||||
#[error("Failed synchronizing file: {0}")]
|
|
||||||
Fsync(#[source] std::io::Error),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type AsyncIoResult<T> = std::result::Result<T, AsyncIoError>;
|
|
||||||
|
|
||||||
pub trait AsyncIo: Send {
|
|
||||||
fn notifier(&self) -> &EventFd;
|
|
||||||
fn read_vectored(
|
|
||||||
&mut self,
|
|
||||||
offset: libc::off_t,
|
|
||||||
iovecs: &[libc::iovec],
|
|
||||||
user_data: u64,
|
|
||||||
) -> AsyncIoResult<()>;
|
|
||||||
fn write_vectored(
|
|
||||||
&mut self,
|
|
||||||
offset: libc::off_t,
|
|
||||||
iovecs: &[libc::iovec],
|
|
||||||
user_data: u64,
|
|
||||||
) -> AsyncIoResult<()>;
|
|
||||||
fn fsync(&mut self, user_data: Option<u64>) -> AsyncIoResult<()>;
|
|
||||||
fn next_completed_request(&mut self) -> Option<(u64, i32)>;
|
|
||||||
}
|
|
||||||
@@ -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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,150 +0,0 @@
|
|||||||
// Copyright © 2023 Intel Corporation
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
|
||||||
//
|
|
||||||
// Copyright © 2023 Crusoe Energy Systems LLC
|
|
||||||
//
|
|
||||||
|
|
||||||
use crate::async_io::{
|
|
||||||
AsyncIo, AsyncIoError, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult,
|
|
||||||
};
|
|
||||||
use crate::DiskTopology;
|
|
||||||
use std::fs::File;
|
|
||||||
use std::io::{Seek, SeekFrom};
|
|
||||||
use std::os::unix::io::{AsRawFd, RawFd};
|
|
||||||
use vmm_sys_util::aio;
|
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
|
||||||
|
|
||||||
pub struct RawFileDiskAio {
|
|
||||||
file: File,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RawFileDiskAio {
|
|
||||||
pub fn new(file: File) -> Self {
|
|
||||||
RawFileDiskAio { file }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DiskFile for RawFileDiskAio {
|
|
||||||
fn size(&mut self) -> DiskFileResult<u64> {
|
|
||||||
self.file
|
|
||||||
.seek(SeekFrom::End(0))
|
|
||||||
.map_err(DiskFileError::Size)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new_async_io(&self, ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
|
|
||||||
Ok(Box::new(
|
|
||||||
RawFileAsyncAio::new(self.file.as_raw_fd(), ring_depth)
|
|
||||||
.map_err(DiskFileError::NewAsyncIo)?,
|
|
||||||
) as Box<dyn AsyncIo>)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn topology(&mut self) -> DiskTopology {
|
|
||||||
if let Ok(topology) = DiskTopology::probe(&self.file) {
|
|
||||||
topology
|
|
||||||
} else {
|
|
||||||
warn!("Unable to get device topology. Using default topology");
|
|
||||||
DiskTopology::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct RawFileAsyncAio {
|
|
||||||
fd: RawFd,
|
|
||||||
ctx: aio::IoContext,
|
|
||||||
eventfd: EventFd,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RawFileAsyncAio {
|
|
||||||
pub fn new(fd: RawFd, queue_depth: u32) -> std::io::Result<Self> {
|
|
||||||
let eventfd = EventFd::new(libc::EFD_NONBLOCK)?;
|
|
||||||
let ctx = aio::IoContext::new(queue_depth)?;
|
|
||||||
|
|
||||||
Ok(RawFileAsyncAio { fd, ctx, eventfd })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsyncIo for RawFileAsyncAio {
|
|
||||||
fn notifier(&self) -> &EventFd {
|
|
||||||
&self.eventfd
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_vectored(
|
|
||||||
&mut self,
|
|
||||||
offset: libc::off_t,
|
|
||||||
iovecs: &[libc::iovec],
|
|
||||||
user_data: u64,
|
|
||||||
) -> AsyncIoResult<()> {
|
|
||||||
let iocbs = [&mut aio::IoControlBlock {
|
|
||||||
aio_fildes: self.fd.as_raw_fd() as u32,
|
|
||||||
aio_lio_opcode: aio::IOCB_CMD_PREADV as u16,
|
|
||||||
aio_buf: iovecs.as_ptr() as u64,
|
|
||||||
aio_nbytes: iovecs.len() as u64,
|
|
||||||
aio_offset: offset,
|
|
||||||
aio_data: user_data,
|
|
||||||
aio_flags: aio::IOCB_FLAG_RESFD,
|
|
||||||
aio_resfd: self.eventfd.as_raw_fd() as u32,
|
|
||||||
..Default::default()
|
|
||||||
}];
|
|
||||||
let _ = self
|
|
||||||
.ctx
|
|
||||||
.submit(&iocbs[..])
|
|
||||||
.map_err(AsyncIoError::ReadVectored)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_vectored(
|
|
||||||
&mut self,
|
|
||||||
offset: libc::off_t,
|
|
||||||
iovecs: &[libc::iovec],
|
|
||||||
user_data: u64,
|
|
||||||
) -> AsyncIoResult<()> {
|
|
||||||
let iocbs = [&mut aio::IoControlBlock {
|
|
||||||
aio_fildes: self.fd.as_raw_fd() as u32,
|
|
||||||
aio_lio_opcode: aio::IOCB_CMD_PWRITEV as u16,
|
|
||||||
aio_buf: iovecs.as_ptr() as u64,
|
|
||||||
aio_nbytes: iovecs.len() as u64,
|
|
||||||
aio_offset: offset,
|
|
||||||
aio_data: user_data,
|
|
||||||
aio_flags: aio::IOCB_FLAG_RESFD,
|
|
||||||
aio_resfd: self.eventfd.as_raw_fd() as u32,
|
|
||||||
..Default::default()
|
|
||||||
}];
|
|
||||||
let _ = self
|
|
||||||
.ctx
|
|
||||||
.submit(&iocbs[..])
|
|
||||||
.map_err(AsyncIoError::WriteVectored)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fsync(&mut self, user_data: Option<u64>) -> AsyncIoResult<()> {
|
|
||||||
if let Some(user_data) = user_data {
|
|
||||||
let iocbs = [&mut aio::IoControlBlock {
|
|
||||||
aio_fildes: self.fd.as_raw_fd() as u32,
|
|
||||||
aio_lio_opcode: aio::IOCB_CMD_FSYNC as u16,
|
|
||||||
aio_data: user_data,
|
|
||||||
aio_flags: aio::IOCB_FLAG_RESFD,
|
|
||||||
aio_resfd: self.eventfd.as_raw_fd() as u32,
|
|
||||||
..Default::default()
|
|
||||||
}];
|
|
||||||
let _ = self.ctx.submit(&iocbs[..]).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)> {
|
|
||||||
let mut events: [aio::IoEvent; 1] = [aio::IoEvent::default()];
|
|
||||||
let rc = self.ctx.get_events(0, &mut events, None).unwrap();
|
|
||||||
if rc == 0 {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some((events[0].data, events[0].res as i32))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
25
block_util/Cargo.toml
Normal file
25
block_util/Cargo.toml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
[package]
|
||||||
|
name = "block_util"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["The Cloud Hypervisor Authors"]
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = []
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
io-uring = "0.5.13"
|
||||||
|
libc = "0.2.139"
|
||||||
|
log = "0.4.17"
|
||||||
|
qcow = { path = "../qcow" }
|
||||||
|
smallvec = "1.10.0"
|
||||||
|
thiserror = "1.0.39"
|
||||||
|
versionize = "0.1.10"
|
||||||
|
versionize_derive = "0.1.4"
|
||||||
|
vhdx = { path = "../vhdx" }
|
||||||
|
virtio-bindings = { version = "0.2.0", features = ["virtio-v5_0_0"] }
|
||||||
|
virtio-queue = "0.7.1"
|
||||||
|
vm-memory = { version = "0.10.0", features = ["backend-mmap", "backend-atomic", "backend-bitmap"] }
|
||||||
|
vm-virtio = { path = "../vm-virtio" }
|
||||||
|
vmm-sys-util = "0.11.0"
|
||||||
|
|
||||||
145
block_util/src/async_io.rs
Normal file
145
block_util/src/async_io.rs
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
// Copyright © 2021 Intel Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||||
|
|
||||||
|
use libc::{ioctl, S_IFBLK, S_IFMT};
|
||||||
|
use std::fs::File;
|
||||||
|
use std::os::unix::io::AsRawFd;
|
||||||
|
use thiserror::Error;
|
||||||
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
use vmm_sys_util::{ioctl_io_nr, ioctl_ioc_nr};
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum DiskFileError {
|
||||||
|
/// Failed getting disk file size.
|
||||||
|
#[error("Failed getting disk file size: {0}")]
|
||||||
|
Size(#[source] std::io::Error),
|
||||||
|
/// Failed creating a new AsyncIo.
|
||||||
|
#[error("Failed creating a new AsyncIo: {0}")]
|
||||||
|
NewAsyncIo(#[source] std::io::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct DiskTopology {
|
||||||
|
pub logical_block_size: u64,
|
||||||
|
pub physical_block_size: u64,
|
||||||
|
pub minimum_io_size: u64,
|
||||||
|
pub optimal_io_size: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for DiskTopology {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
logical_block_size: 512,
|
||||||
|
physical_block_size: 512,
|
||||||
|
minimum_io_size: 512,
|
||||||
|
optimal_io_size: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ioctl_io_nr!(BLKSSZGET, 0x12, 104);
|
||||||
|
ioctl_io_nr!(BLKPBSZGET, 0x12, 123);
|
||||||
|
ioctl_io_nr!(BLKIOMIN, 0x12, 120);
|
||||||
|
ioctl_io_nr!(BLKIOOPT, 0x12, 121);
|
||||||
|
|
||||||
|
enum BlockSize {
|
||||||
|
LogicalBlock,
|
||||||
|
PhysicalBlock,
|
||||||
|
MinimumIo,
|
||||||
|
OptimalIo,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DiskTopology {
|
||||||
|
fn is_block_device(f: &mut File) -> std::io::Result<bool> {
|
||||||
|
let mut stat = std::mem::MaybeUninit::<libc::stat>::uninit();
|
||||||
|
// SAFETY: FFI call with a valid fd and buffer
|
||||||
|
let ret = unsafe { libc::fstat(f.as_raw_fd(), stat.as_mut_ptr()) };
|
||||||
|
if ret != 0 {
|
||||||
|
return Err(std::io::Error::last_os_error());
|
||||||
|
}
|
||||||
|
|
||||||
|
// SAFETY: stat is valid at this point
|
||||||
|
let is_block = unsafe { (*stat.as_ptr()).st_mode & S_IFMT == S_IFBLK };
|
||||||
|
Ok(is_block)
|
||||||
|
}
|
||||||
|
|
||||||
|
// libc::ioctl() takes different types on different architectures
|
||||||
|
fn query_block_size(f: &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(),
|
||||||
|
match block_size_type {
|
||||||
|
BlockSize::LogicalBlock => BLKSSZGET(),
|
||||||
|
BlockSize::PhysicalBlock => BLKPBSZGET(),
|
||||||
|
BlockSize::MinimumIo => BLKIOMIN(),
|
||||||
|
BlockSize::OptimalIo => BLKIOOPT(),
|
||||||
|
} as _,
|
||||||
|
&mut block_size,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
if ret != 0 {
|
||||||
|
return Err(std::io::Error::last_os_error());
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(block_size)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn probe(f: &mut File) -> std::io::Result<Self> {
|
||||||
|
if !Self::is_block_device(f)? {
|
||||||
|
return Ok(DiskTopology::default());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(DiskTopology {
|
||||||
|
logical_block_size: Self::query_block_size(f, BlockSize::LogicalBlock)?,
|
||||||
|
physical_block_size: Self::query_block_size(f, BlockSize::PhysicalBlock)?,
|
||||||
|
minimum_io_size: Self::query_block_size(f, BlockSize::MinimumIo)?,
|
||||||
|
optimal_io_size: Self::query_block_size(f, BlockSize::OptimalIo)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type DiskFileResult<T> = std::result::Result<T, DiskFileError>;
|
||||||
|
|
||||||
|
pub trait DiskFile: Send {
|
||||||
|
fn size(&mut self) -> DiskFileResult<u64>;
|
||||||
|
fn new_async_io(&self, ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>>;
|
||||||
|
fn topology(&mut self) -> DiskTopology {
|
||||||
|
DiskTopology::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum AsyncIoError {
|
||||||
|
/// Failed vectored reading from file.
|
||||||
|
#[error("Failed vectored reading from file: {0}")]
|
||||||
|
ReadVectored(#[source] std::io::Error),
|
||||||
|
/// Failed vectored writing to file.
|
||||||
|
#[error("Failed vectored writing to file: {0}")]
|
||||||
|
WriteVectored(#[source] std::io::Error),
|
||||||
|
/// Failed synchronizing file.
|
||||||
|
#[error("Failed synchronizing file: {0}")]
|
||||||
|
Fsync(#[source] std::io::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type AsyncIoResult<T> = std::result::Result<T, AsyncIoError>;
|
||||||
|
|
||||||
|
pub trait AsyncIo: Send {
|
||||||
|
fn notifier(&self) -> &EventFd;
|
||||||
|
fn read_vectored(
|
||||||
|
&mut self,
|
||||||
|
offset: libc::off_t,
|
||||||
|
iovecs: &[libc::iovec],
|
||||||
|
user_data: u64,
|
||||||
|
) -> AsyncIoResult<()>;
|
||||||
|
fn write_vectored(
|
||||||
|
&mut self,
|
||||||
|
offset: libc::off_t,
|
||||||
|
iovecs: &[libc::iovec],
|
||||||
|
user_data: u64,
|
||||||
|
) -> AsyncIoResult<()>;
|
||||||
|
fn fsync(&mut self, user_data: Option<u64>) -> AsyncIoResult<()>;
|
||||||
|
fn next_completed_request(&mut self) -> Option<(u64, i32)>;
|
||||||
|
}
|
||||||
@@ -5,29 +5,36 @@
|
|||||||
use crate::async_io::{
|
use crate::async_io::{
|
||||||
AsyncIo, AsyncIoError, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult,
|
AsyncIo, AsyncIoError, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult,
|
||||||
};
|
};
|
||||||
use crate::fixed_vhd::FixedVhd;
|
|
||||||
use crate::raw_async::RawFileAsync;
|
use crate::raw_async::RawFileAsync;
|
||||||
use crate::BlockBackend;
|
use crate::vhd::VhdFooter;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::os::unix::io::{AsRawFd, RawFd};
|
use std::os::unix::io::{AsRawFd, RawFd};
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
pub struct FixedVhdDiskAsync(FixedVhd);
|
pub struct FixedVhdDiskAsync {
|
||||||
|
file: File,
|
||||||
|
size: u64,
|
||||||
|
}
|
||||||
|
|
||||||
impl FixedVhdDiskAsync {
|
impl FixedVhdDiskAsync {
|
||||||
pub fn new(file: File) -> std::io::Result<Self> {
|
pub fn new(mut file: File) -> std::io::Result<Self> {
|
||||||
Ok(Self(FixedVhd::new(file)?))
|
let footer = VhdFooter::new(&mut file)?;
|
||||||
|
|
||||||
|
Ok(FixedVhdDiskAsync {
|
||||||
|
file,
|
||||||
|
size: footer.current_size(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DiskFile for FixedVhdDiskAsync {
|
impl DiskFile for FixedVhdDiskAsync {
|
||||||
fn size(&mut self) -> DiskFileResult<u64> {
|
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>> {
|
fn new_async_io(&self, ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
|
||||||
Ok(Box::new(
|
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)?,
|
.map_err(DiskFileError::NewAsyncIo)?,
|
||||||
) as Box<dyn AsyncIo>)
|
) as Box<dyn AsyncIo>)
|
||||||
}
|
}
|
||||||
@@ -5,29 +5,36 @@
|
|||||||
use crate::async_io::{
|
use crate::async_io::{
|
||||||
AsyncIo, AsyncIoError, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult,
|
AsyncIo, AsyncIoError, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult,
|
||||||
};
|
};
|
||||||
use crate::fixed_vhd::FixedVhd;
|
|
||||||
use crate::raw_sync::RawFileSync;
|
use crate::raw_sync::RawFileSync;
|
||||||
use crate::BlockBackend;
|
use crate::vhd::VhdFooter;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::os::unix::io::{AsRawFd, RawFd};
|
use std::os::unix::io::{AsRawFd, RawFd};
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
pub struct FixedVhdDiskSync(FixedVhd);
|
pub struct FixedVhdDiskSync {
|
||||||
|
file: File,
|
||||||
|
size: u64,
|
||||||
|
}
|
||||||
|
|
||||||
impl FixedVhdDiskSync {
|
impl FixedVhdDiskSync {
|
||||||
pub fn new(file: File) -> std::io::Result<Self> {
|
pub fn new(mut file: File) -> std::io::Result<Self> {
|
||||||
Ok(Self(FixedVhd::new(file)?))
|
let footer = VhdFooter::new(&mut file)?;
|
||||||
|
|
||||||
|
Ok(FixedVhdDiskSync {
|
||||||
|
file,
|
||||||
|
size: footer.current_size(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DiskFile for FixedVhdDiskSync {
|
impl DiskFile for FixedVhdDiskSync {
|
||||||
fn size(&mut self) -> DiskFileResult<u64> {
|
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>> {
|
fn new_async_io(&self, _ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
|
||||||
Ok(Box::new(
|
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)?,
|
.map_err(DiskFileError::NewAsyncIo)?,
|
||||||
) as Box<dyn AsyncIo>)
|
) as Box<dyn AsyncIo>)
|
||||||
}
|
}
|
||||||
@@ -12,57 +12,42 @@
|
|||||||
extern crate log;
|
extern crate log;
|
||||||
|
|
||||||
pub mod async_io;
|
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_async;
|
||||||
pub mod fixed_vhd_sync;
|
pub mod fixed_vhd_sync;
|
||||||
pub mod qcow;
|
|
||||||
pub mod qcow_sync;
|
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_async;
|
||||||
pub mod raw_async_aio;
|
|
||||||
pub mod raw_sync;
|
pub mod raw_sync;
|
||||||
pub mod vhd;
|
pub mod vhd;
|
||||||
pub mod vhdx;
|
|
||||||
pub mod vhdx_sync;
|
pub mod vhdx_sync;
|
||||||
|
|
||||||
use crate::async_io::{AsyncIo, AsyncIoError, AsyncIoResult};
|
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 io_uring::{opcode, IoUring, Probe};
|
||||||
use libc::{ioctl, S_IFBLK, S_IFMT};
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use std::alloc::{alloc_zeroed, dealloc, Layout};
|
use std::alloc::{alloc_zeroed, dealloc, Layout};
|
||||||
use std::cmp;
|
use std::cmp;
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
use std::fmt::Debug;
|
use std::convert::TryInto;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{self, IoSlice, IoSliceMut, Read, Seek, SeekFrom, Write};
|
use std::io::{self, IoSlice, IoSliceMut, Read, Seek, SeekFrom, Write};
|
||||||
use std::os::linux::fs::MetadataExt;
|
use std::os::linux::fs::MetadataExt;
|
||||||
use std::os::unix::io::AsRawFd;
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::result;
|
use std::result;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::MutexGuard;
|
use std::sync::MutexGuard;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
use versionize::{VersionMap, Versionize, VersionizeResult};
|
||||||
|
use versionize_derive::Versionize;
|
||||||
use virtio_bindings::virtio_blk::*;
|
use virtio_bindings::virtio_blk::*;
|
||||||
use virtio_queue::DescriptorChain;
|
use virtio_queue::DescriptorChain;
|
||||||
use vm_memory::{
|
use vm_memory::{
|
||||||
bitmap::Bitmap, ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryError,
|
bitmap::AtomicBitmap, bitmap::Bitmap, ByteValued, Bytes, GuestAddress, GuestMemory,
|
||||||
GuestMemoryLoadGuard,
|
GuestMemoryError, GuestMemoryLoadGuard,
|
||||||
};
|
};
|
||||||
use vm_virtio::{AccessPlatform, Translatable};
|
use vm_virtio::{AccessPlatform, Translatable};
|
||||||
use vmm_sys_util::aio;
|
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
use vmm_sys_util::{ioctl_io_nr, ioctl_ioc_nr};
|
|
||||||
|
type GuestMemoryMmap = vm_memory::GuestMemoryMmap<AtomicBitmap>;
|
||||||
|
|
||||||
const SECTOR_SHIFT: u8 = 9;
|
const SECTOR_SHIFT: u8 = 9;
|
||||||
pub const SECTOR_SIZE: u64 = 0x01 << SECTOR_SHIFT;
|
pub const SECTOR_SIZE: u64 = 0x01 << SECTOR_SHIFT;
|
||||||
@@ -81,22 +66,12 @@ pub enum Error {
|
|||||||
DescriptorChainTooShort,
|
DescriptorChainTooShort,
|
||||||
#[error("Guest gave us a descriptor that was too short to use")]
|
#[error("Guest gave us a descriptor that was too short to use")]
|
||||||
DescriptorLengthTooSmall,
|
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")]
|
#[error("Getting a block's metadata fails for any reason")]
|
||||||
GetFileMetadata,
|
GetFileMetadata,
|
||||||
#[error("The requested operation would cause a seek beyond disk end")]
|
#[error("The requested operation would cause a seek beyond disk end")]
|
||||||
InvalidOffset,
|
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")]
|
#[error("The requested operation does not support multiple descriptors")]
|
||||||
TooManyDescriptors,
|
TooManyDescriptors,
|
||||||
#[error("Failure in vhdx: {0}")]
|
|
||||||
VhdxError(VhdxError),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_device_id(disk_path: &Path) -> result::Result<String, Error> {
|
fn build_device_id(disk_path: &Path) -> result::Result<String, Error> {
|
||||||
@@ -114,8 +89,8 @@ fn build_device_id(disk_path: &Path) -> result::Result<String, Error> {
|
|||||||
Ok(device_id)
|
Ok(device_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build_serial(disk_path: &Path) -> Vec<u8> {
|
pub fn build_disk_image_id(disk_path: &Path) -> Vec<u8> {
|
||||||
let mut default_serial = vec![0; VIRTIO_BLK_ID_BYTES as usize];
|
let mut default_disk_image_id = vec![0; VIRTIO_BLK_ID_BYTES as usize];
|
||||||
match build_device_id(disk_path) {
|
match build_device_id(disk_path) {
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
warn!("Could not generate device id. We'll use a default.");
|
warn!("Could not generate device id. We'll use a default.");
|
||||||
@@ -125,28 +100,24 @@ pub fn build_serial(disk_path: &Path) -> Vec<u8> {
|
|||||||
// This will also zero out any leftover bytes.
|
// This will also zero out any leftover bytes.
|
||||||
let disk_id = m.as_bytes();
|
let disk_id = m.as_bytes();
|
||||||
let bytes_to_copy = cmp::min(disk_id.len(), VIRTIO_BLK_ID_BYTES as usize);
|
let bytes_to_copy = cmp::min(disk_id.len(), VIRTIO_BLK_ID_BYTES as usize);
|
||||||
default_serial[..bytes_to_copy].clone_from_slice(&disk_id[..bytes_to_copy])
|
default_disk_image_id[..bytes_to_copy].clone_from_slice(&disk_id[..bytes_to_copy])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default_serial
|
default_disk_image_id
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum ExecuteError {
|
pub enum ExecuteError {
|
||||||
#[error("Bad request: {0}")]
|
#[error("Bad request: {0}")]
|
||||||
BadRequest(Error),
|
BadRequest(Error),
|
||||||
#[error("Failed to flush: {0}")]
|
#[error("Falied to flush: {0}")]
|
||||||
Flush(io::Error),
|
Flush(io::Error),
|
||||||
#[error("Failed to read: {0}")]
|
#[error("Failed to read: {0}")]
|
||||||
Read(GuestMemoryError),
|
Read(GuestMemoryError),
|
||||||
#[error("Failed to read_exact: {0}")]
|
|
||||||
ReadExact(io::Error),
|
|
||||||
#[error("Failed to seek: {0}")]
|
#[error("Failed to seek: {0}")]
|
||||||
Seek(io::Error),
|
Seek(io::Error),
|
||||||
#[error("Failed to write: {0}")]
|
#[error("Failed to write: {0}")]
|
||||||
Write(GuestMemoryError),
|
Write(GuestMemoryError),
|
||||||
#[error("Failed to write_all: {0}")]
|
|
||||||
WriteAll(io::Error),
|
|
||||||
#[error("Unsupported request: {0}")]
|
#[error("Unsupported request: {0}")]
|
||||||
Unsupported(u32),
|
Unsupported(u32),
|
||||||
#[error("Failed to submit io uring: {0}")]
|
#[error("Failed to submit io uring: {0}")]
|
||||||
@@ -169,10 +140,8 @@ impl ExecuteError {
|
|||||||
ExecuteError::BadRequest(_) => VIRTIO_BLK_S_IOERR,
|
ExecuteError::BadRequest(_) => VIRTIO_BLK_S_IOERR,
|
||||||
ExecuteError::Flush(_) => VIRTIO_BLK_S_IOERR,
|
ExecuteError::Flush(_) => VIRTIO_BLK_S_IOERR,
|
||||||
ExecuteError::Read(_) => VIRTIO_BLK_S_IOERR,
|
ExecuteError::Read(_) => VIRTIO_BLK_S_IOERR,
|
||||||
ExecuteError::ReadExact(_) => VIRTIO_BLK_S_IOERR,
|
|
||||||
ExecuteError::Seek(_) => VIRTIO_BLK_S_IOERR,
|
ExecuteError::Seek(_) => VIRTIO_BLK_S_IOERR,
|
||||||
ExecuteError::Write(_) => VIRTIO_BLK_S_IOERR,
|
ExecuteError::Write(_) => VIRTIO_BLK_S_IOERR,
|
||||||
ExecuteError::WriteAll(_) => VIRTIO_BLK_S_IOERR,
|
|
||||||
ExecuteError::Unsupported(_) => VIRTIO_BLK_S_UNSUPP,
|
ExecuteError::Unsupported(_) => VIRTIO_BLK_S_UNSUPP,
|
||||||
ExecuteError::SubmitIoUring(_) => VIRTIO_BLK_S_IOERR,
|
ExecuteError::SubmitIoUring(_) => VIRTIO_BLK_S_IOERR,
|
||||||
ExecuteError::GetHostAddress(_) => VIRTIO_BLK_S_IOERR,
|
ExecuteError::GetHostAddress(_) => VIRTIO_BLK_S_IOERR,
|
||||||
@@ -193,8 +162,8 @@ pub enum RequestType {
|
|||||||
Unsupported(u32),
|
Unsupported(u32),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn request_type<B: Bitmap + 'static>(
|
pub fn request_type(
|
||||||
mem: &vm_memory::GuestMemoryMmap<B>,
|
mem: &GuestMemoryMmap,
|
||||||
desc_addr: GuestAddress,
|
desc_addr: GuestAddress,
|
||||||
) -> result::Result<RequestType, Error> {
|
) -> result::Result<RequestType, Error> {
|
||||||
let type_ = mem.read_obj(desc_addr).map_err(Error::GuestMemory)?;
|
let type_ = mem.read_obj(desc_addr).map_err(Error::GuestMemory)?;
|
||||||
@@ -207,10 +176,7 @@ pub fn request_type<B: Bitmap + 'static>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sector<B: Bitmap + 'static>(
|
fn sector(mem: &GuestMemoryMmap, desc_addr: GuestAddress) -> result::Result<u64, Error> {
|
||||||
mem: &vm_memory::GuestMemoryMmap<B>,
|
|
||||||
desc_addr: GuestAddress,
|
|
||||||
) -> result::Result<u64, Error> {
|
|
||||||
const SECTOR_OFFSET: usize = 8;
|
const SECTOR_OFFSET: usize = 8;
|
||||||
let addr = match mem.checked_offset(desc_addr, SECTOR_OFFSET) {
|
let addr = match mem.checked_offset(desc_addr, SECTOR_OFFSET) {
|
||||||
Some(v) => v,
|
Some(v) => v,
|
||||||
@@ -240,8 +206,8 @@ pub struct Request {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Request {
|
impl Request {
|
||||||
pub fn parse<B: Bitmap + 'static>(
|
pub fn parse(
|
||||||
desc_chain: &mut DescriptorChain<GuestMemoryLoadGuard<vm_memory::GuestMemoryMmap<B>>>,
|
desc_chain: &mut DescriptorChain<GuestMemoryLoadGuard<GuestMemoryMmap>>,
|
||||||
access_platform: Option<&Arc<dyn AccessPlatform>>,
|
access_platform: Option<&Arc<dyn AccessPlatform>>,
|
||||||
) -> result::Result<Request, Error> {
|
) -> result::Result<Request, Error> {
|
||||||
let hdr_desc = desc_chain
|
let hdr_desc = desc_chain
|
||||||
@@ -332,12 +298,12 @@ impl Request {
|
|||||||
Ok(req)
|
Ok(req)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn execute<T: Seek + Read + Write, B: Bitmap + 'static>(
|
pub fn execute<T: Seek + Read + Write>(
|
||||||
&self,
|
&self,
|
||||||
disk: &mut T,
|
disk: &mut T,
|
||||||
disk_nsectors: u64,
|
disk_nsectors: u64,
|
||||||
mem: &vm_memory::GuestMemoryMmap<B>,
|
mem: &GuestMemoryMmap,
|
||||||
serial: &[u8],
|
disk_id: &[u8],
|
||||||
) -> result::Result<u32, ExecuteError> {
|
) -> result::Result<u32, ExecuteError> {
|
||||||
disk.seek(SeekFrom::Start(self.sector << SECTOR_SHIFT))
|
disk.seek(SeekFrom::Start(self.sector << SECTOR_SHIFT))
|
||||||
.map_err(ExecuteError::Seek)?;
|
.map_err(ExecuteError::Seek)?;
|
||||||
@@ -356,31 +322,23 @@ impl Request {
|
|||||||
|
|
||||||
match self.request_type {
|
match self.request_type {
|
||||||
RequestType::In => {
|
RequestType::In => {
|
||||||
let mut buf = vec![0u8; *data_len as usize];
|
mem.read_exact_from(*data_addr, disk, *data_len as usize)
|
||||||
disk.read_exact(&mut buf).map_err(ExecuteError::ReadExact)?;
|
.map_err(ExecuteError::Read)?;
|
||||||
mem.read_exact_volatile_from(
|
|
||||||
*data_addr,
|
|
||||||
&mut buf.as_slice(),
|
|
||||||
*data_len as usize,
|
|
||||||
)
|
|
||||||
.map_err(ExecuteError::Read)?;
|
|
||||||
len += data_len;
|
len += data_len;
|
||||||
}
|
}
|
||||||
RequestType::Out => {
|
RequestType::Out => {
|
||||||
let mut buf: Vec<u8> = Vec::new();
|
mem.write_all_to(*data_addr, disk, *data_len as usize)
|
||||||
mem.write_all_volatile_to(*data_addr, &mut buf, *data_len as usize)
|
|
||||||
.map_err(ExecuteError::Write)?;
|
.map_err(ExecuteError::Write)?;
|
||||||
disk.write_all(&buf).map_err(ExecuteError::WriteAll)?;
|
|
||||||
if !self.writeback {
|
if !self.writeback {
|
||||||
disk.flush().map_err(ExecuteError::Flush)?;
|
disk.flush().map_err(ExecuteError::Flush)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RequestType::Flush => disk.flush().map_err(ExecuteError::Flush)?,
|
RequestType::Flush => disk.flush().map_err(ExecuteError::Flush)?,
|
||||||
RequestType::GetDeviceId => {
|
RequestType::GetDeviceId => {
|
||||||
if (*data_len as usize) < serial.len() {
|
if (*data_len as usize) < disk_id.len() {
|
||||||
return Err(ExecuteError::BadRequest(Error::InvalidOffset));
|
return Err(ExecuteError::BadRequest(Error::InvalidOffset));
|
||||||
}
|
}
|
||||||
mem.write_slice(serial, *data_addr)
|
mem.write_slice(disk_id, *data_addr)
|
||||||
.map_err(ExecuteError::Write)?;
|
.map_err(ExecuteError::Write)?;
|
||||||
}
|
}
|
||||||
RequestType::Unsupported(t) => return Err(ExecuteError::Unsupported(t)),
|
RequestType::Unsupported(t) => return Err(ExecuteError::Unsupported(t)),
|
||||||
@@ -389,12 +347,12 @@ impl Request {
|
|||||||
Ok(len)
|
Ok(len)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn execute_async<B: Bitmap + 'static>(
|
pub fn execute_async(
|
||||||
&mut self,
|
&mut self,
|
||||||
mem: &vm_memory::GuestMemoryMmap<B>,
|
mem: &GuestMemoryMmap,
|
||||||
disk_nsectors: u64,
|
disk_nsectors: u64,
|
||||||
disk_image: &mut dyn AsyncIo,
|
disk_image: &mut dyn AsyncIo,
|
||||||
serial: &[u8],
|
disk_id: &[u8],
|
||||||
user_data: u64,
|
user_data: u64,
|
||||||
) -> result::Result<bool, ExecuteError> {
|
) -> result::Result<bool, ExecuteError> {
|
||||||
let sector = self.sector;
|
let sector = self.sector;
|
||||||
@@ -421,13 +379,13 @@ impl Request {
|
|||||||
let origin_ptr = mem
|
let origin_ptr = mem
|
||||||
.get_slice(*data_addr, *data_len as usize)
|
.get_slice(*data_addr, *data_len as usize)
|
||||||
.map_err(ExecuteError::GetHostAddress)?
|
.map_err(ExecuteError::GetHostAddress)?
|
||||||
.ptr_guard();
|
.as_ptr();
|
||||||
|
|
||||||
// Verify the buffer alignment.
|
// Verify the buffer alignment.
|
||||||
// In case it's not properly aligned, an intermediate buffer is
|
// In case it's not properly aligned, an intermediate buffer is
|
||||||
// created with the correct alignment, and a copy from/to the
|
// created with the correct alignment, and a copy from/to the
|
||||||
// origin buffer is performed, depending on the type of operation.
|
// origin buffer is performed, depending on the type of operation.
|
||||||
let iov_base = if (origin_ptr.as_ptr() as u64) % SECTOR_SIZE != 0 {
|
let iov_base = if (origin_ptr as u64) % SECTOR_SIZE != 0 {
|
||||||
let layout =
|
let layout =
|
||||||
Layout::from_size_align(*data_len as usize, SECTOR_SIZE as usize).unwrap();
|
Layout::from_size_align(*data_len as usize, SECTOR_SIZE as usize).unwrap();
|
||||||
// SAFETY: layout has non-zero size
|
// SAFETY: layout has non-zero size
|
||||||
@@ -443,13 +401,15 @@ impl Request {
|
|||||||
if request_type == RequestType::Out {
|
if request_type == RequestType::Out {
|
||||||
// SAFETY: destination buffer has been allocated with
|
// SAFETY: destination buffer has been allocated with
|
||||||
// the proper size.
|
// the proper size.
|
||||||
unsafe { std::ptr::copy(origin_ptr.as_ptr(), aligned_ptr, *data_len as usize) };
|
unsafe {
|
||||||
|
std::ptr::copy(origin_ptr as *const u8, aligned_ptr, *data_len as usize)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store both origin and aligned pointers for complete_async()
|
// Store both origin and aligned pointers for complete_async()
|
||||||
// to process them.
|
// to process them.
|
||||||
self.aligned_operations.push(AlignedOperation {
|
self.aligned_operations.push(AlignedOperation {
|
||||||
origin_ptr: origin_ptr.as_ptr() as u64,
|
origin_ptr: origin_ptr as u64,
|
||||||
aligned_ptr: aligned_ptr as u64,
|
aligned_ptr: aligned_ptr as u64,
|
||||||
size: *data_len as usize,
|
size: *data_len as usize,
|
||||||
layout,
|
layout,
|
||||||
@@ -457,7 +417,7 @@ impl Request {
|
|||||||
|
|
||||||
aligned_ptr as *mut libc::c_void
|
aligned_ptr as *mut libc::c_void
|
||||||
} else {
|
} else {
|
||||||
origin_ptr.as_ptr() as *mut libc::c_void
|
origin_ptr as *mut libc::c_void
|
||||||
};
|
};
|
||||||
|
|
||||||
let iovec = libc::iovec {
|
let iovec = libc::iovec {
|
||||||
@@ -496,10 +456,10 @@ impl Request {
|
|||||||
} else {
|
} else {
|
||||||
return Err(ExecuteError::BadRequest(Error::TooManyDescriptors));
|
return Err(ExecuteError::BadRequest(Error::TooManyDescriptors));
|
||||||
};
|
};
|
||||||
if (data_len as usize) < serial.len() {
|
if (data_len as usize) < disk_id.len() {
|
||||||
return Err(ExecuteError::BadRequest(Error::InvalidOffset));
|
return Err(ExecuteError::BadRequest(Error::InvalidOffset));
|
||||||
}
|
}
|
||||||
mem.write_slice(serial, data_addr)
|
mem.write_slice(disk_id, data_addr)
|
||||||
.map_err(ExecuteError::Write)?;
|
.map_err(ExecuteError::Write)?;
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
@@ -544,7 +504,7 @@ impl Request {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize)]
|
#[derive(Copy, Clone, Debug, Default, Versionize)]
|
||||||
#[repr(C, packed)]
|
#[repr(C, packed)]
|
||||||
pub struct VirtioBlockConfig {
|
pub struct VirtioBlockConfig {
|
||||||
pub capacity: u64,
|
pub capacity: u64,
|
||||||
@@ -567,7 +527,7 @@ pub struct VirtioBlockConfig {
|
|||||||
pub write_zeroes_may_unmap: u8,
|
pub write_zeroes_may_unmap: u8,
|
||||||
pub unused1: [u8; 3],
|
pub unused1: [u8; 3],
|
||||||
}
|
}
|
||||||
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize)]
|
#[derive(Copy, Clone, Debug, Default, Versionize)]
|
||||||
#[repr(C, packed)]
|
#[repr(C, packed)]
|
||||||
pub struct VirtioBlockGeometry {
|
pub struct VirtioBlockGeometry {
|
||||||
pub cylinders: u16,
|
pub cylinders: u16,
|
||||||
@@ -580,67 +540,53 @@ unsafe impl ByteValued for VirtioBlockConfig {}
|
|||||||
// SAFETY: data structure only contain a series of integers
|
// SAFETY: data structure only contain a series of integers
|
||||||
unsafe impl ByteValued for VirtioBlockGeometry {}
|
unsafe impl ByteValued for VirtioBlockGeometry {}
|
||||||
|
|
||||||
/// Check if aio can be used on the current system.
|
|
||||||
pub fn block_aio_is_supported() -> bool {
|
|
||||||
aio::IoContext::new(1).is_ok()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check if io_uring for block device can be used on the current system, as
|
/// Check if io_uring for block device can be used on the current system, as
|
||||||
/// it correctly supports the expected io_uring features.
|
/// it correctly supports the expected io_uring features.
|
||||||
pub fn block_io_uring_is_supported() -> bool {
|
pub fn block_io_uring_is_supported() -> bool {
|
||||||
#[cfg(not(feature = "io_uring"))]
|
let error_msg = "io_uring not supported:";
|
||||||
{
|
|
||||||
info!("io_uring is disabled by crate features");
|
// Check we can create an io_uring instance, which effectively verifies
|
||||||
false
|
// 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")]
|
// Check IORING_OP_FSYNC is supported
|
||||||
{
|
if !probe.is_supported(opcode::Fsync::CODE) {
|
||||||
let error_msg = "io_uring not supported:";
|
info!("{} IORING_OP_FSYNC operation not supported", error_msg);
|
||||||
|
return false;
|
||||||
// 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_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>
|
pub trait AsyncAdaptor<F>
|
||||||
@@ -659,9 +605,7 @@ where
|
|||||||
let mut slices: SmallVec<[IoSliceMut; 1]> = SmallVec::with_capacity(iovecs.len());
|
let mut slices: SmallVec<[IoSliceMut; 1]> = SmallVec::with_capacity(iovecs.len());
|
||||||
for iovec in iovecs.iter() {
|
for iovec in iovecs.iter() {
|
||||||
// SAFETY: on Linux IoSliceMut wraps around libc::iovec
|
// SAFETY: on Linux IoSliceMut wraps around libc::iovec
|
||||||
slices.push(IoSliceMut::new(unsafe {
|
slices.push(IoSliceMut::new(unsafe { std::mem::transmute(*iovec) }));
|
||||||
std::mem::transmute::<libc::iovec, &mut [u8]>(*iovec)
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = {
|
let result = {
|
||||||
@@ -694,9 +638,7 @@ where
|
|||||||
let mut slices: SmallVec<[IoSlice; 1]> = SmallVec::with_capacity(iovecs.len());
|
let mut slices: SmallVec<[IoSlice; 1]> = SmallVec::with_capacity(iovecs.len());
|
||||||
for iovec in iovecs.iter() {
|
for iovec in iovecs.iter() {
|
||||||
// SAFETY: on Linux IoSlice wraps around libc::iovec
|
// SAFETY: on Linux IoSlice wraps around libc::iovec
|
||||||
slices.push(IoSlice::new(unsafe {
|
slices.push(IoSlice::new(unsafe { std::mem::transmute(*iovec) }));
|
||||||
std::mem::transmute::<libc::iovec, &mut [u8]>(*iovec)
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = {
|
let result = {
|
||||||
@@ -753,33 +695,24 @@ pub enum ImageType {
|
|||||||
const QCOW_MAGIC: u32 = 0x5146_49fb;
|
const QCOW_MAGIC: u32 = 0x5146_49fb;
|
||||||
const VHDX_SIGN: u64 = 0x656C_6966_7864_6876;
|
const VHDX_SIGN: u64 = 0x656C_6966_7864_6876;
|
||||||
|
|
||||||
/// Read a block into memory aligned by the source block size (needed for O_DIRECT)
|
|
||||||
pub fn read_aligned_block_size(f: &mut File) -> std::io::Result<Vec<u8>> {
|
|
||||||
let blocksize = DiskTopology::probe(f)?.logical_block_size as usize;
|
|
||||||
// SAFETY: We are allocating memory that is naturally aligned (size = alignment) and we meet
|
|
||||||
// requirements for safety from Vec::from_raw_parts() as we are using the global allocator
|
|
||||||
// and transferring ownership of the memory.
|
|
||||||
let mut data = unsafe {
|
|
||||||
Vec::from_raw_parts(
|
|
||||||
alloc_zeroed(Layout::from_size_align_unchecked(blocksize, blocksize)),
|
|
||||||
blocksize,
|
|
||||||
blocksize,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
f.read_exact(&mut data)?;
|
|
||||||
Ok(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Determine image type through file parsing.
|
/// Determine image type through file parsing.
|
||||||
pub fn detect_image_type(f: &mut File) -> std::io::Result<ImageType> {
|
pub fn detect_image_type(f: &mut File) -> std::io::Result<ImageType> {
|
||||||
let block = read_aligned_block_size(f)?;
|
// We must create a buffer aligned on 512 bytes with a size being a
|
||||||
|
// multiple of 512 bytes as the file might be opened with O_DIRECT flag.
|
||||||
|
#[repr(align(512))]
|
||||||
|
struct Sector {
|
||||||
|
data: [u8; 512],
|
||||||
|
}
|
||||||
|
let mut s = Sector { data: [0; 512] };
|
||||||
|
|
||||||
|
f.read_exact(&mut s.data)?;
|
||||||
|
|
||||||
// Check 4 first bytes to get the header value and determine the image type
|
// Check 4 first bytes to get the header value and determine the image type
|
||||||
let image_type = if u32::from_be_bytes(block[0..4].try_into().unwrap()) == QCOW_MAGIC {
|
let image_type = if u32::from_be_bytes(s.data[0..4].try_into().unwrap()) == QCOW_MAGIC {
|
||||||
ImageType::Qcow2
|
ImageType::Qcow2
|
||||||
} else if vhd::is_fixed_vhd(f)? {
|
} else if vhd::is_fixed_vhd(f)? {
|
||||||
ImageType::FixedVhd
|
ImageType::FixedVhd
|
||||||
} else if u64::from_le_bytes(block[0..8].try_into().unwrap()) == VHDX_SIGN {
|
} else if u64::from_le_bytes(s.data[0..8].try_into().unwrap()) == VHDX_SIGN {
|
||||||
ImageType::Vhdx
|
ImageType::Vhdx
|
||||||
} else {
|
} else {
|
||||||
ImageType::Raw
|
ImageType::Raw
|
||||||
@@ -787,108 +720,3 @@ pub fn detect_image_type(f: &mut File) -> std::io::Result<ImageType> {
|
|||||||
|
|
||||||
Ok(image_type)
|
Ok(image_type)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait BlockBackend: Read + Write + Seek + Send + Debug {
|
|
||||||
fn size(&self) -> Result<u64, Error>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Inspect the image file type and create an appropriate disk file to match it.
|
|
||||||
pub fn create_disk_file(mut file: File, direct_io: bool) -> Result<Box<dyn BlockBackend>, Error> {
|
|
||||||
let image_type = detect_image_type(&mut file).map_err(Error::DetectImageType)?;
|
|
||||||
|
|
||||||
Ok(match image_type {
|
|
||||||
ImageType::Qcow2 => {
|
|
||||||
Box::new(QcowFile::from(RawFile::new(file, direct_io)).map_err(Error::QcowError)?)
|
|
||||||
as Box<dyn BlockBackend>
|
|
||||||
}
|
|
||||||
ImageType::FixedVhd => {
|
|
||||||
Box::new(FixedVhd::new(file).map_err(Error::FixedVhdError)?) as Box<dyn BlockBackend>
|
|
||||||
}
|
|
||||||
ImageType::Vhdx => {
|
|
||||||
Box::new(Vhdx::new(file).map_err(Error::VhdxError)?) as Box<dyn BlockBackend>
|
|
||||||
}
|
|
||||||
ImageType::Raw => Box::new(RawFile::new(file, direct_io)) as Box<dyn BlockBackend>,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct DiskTopology {
|
|
||||||
pub logical_block_size: u64,
|
|
||||||
pub physical_block_size: u64,
|
|
||||||
pub minimum_io_size: u64,
|
|
||||||
pub optimal_io_size: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for DiskTopology {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
logical_block_size: 512,
|
|
||||||
physical_block_size: 512,
|
|
||||||
minimum_io_size: 512,
|
|
||||||
optimal_io_size: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ioctl_io_nr!(BLKSSZGET, 0x12, 104);
|
|
||||||
ioctl_io_nr!(BLKPBSZGET, 0x12, 123);
|
|
||||||
ioctl_io_nr!(BLKIOMIN, 0x12, 120);
|
|
||||||
ioctl_io_nr!(BLKIOOPT, 0x12, 121);
|
|
||||||
|
|
||||||
enum BlockSize {
|
|
||||||
LogicalBlock,
|
|
||||||
PhysicalBlock,
|
|
||||||
MinimumIo,
|
|
||||||
OptimalIo,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DiskTopology {
|
|
||||||
fn is_block_device(f: &File) -> std::io::Result<bool> {
|
|
||||||
let mut stat = std::mem::MaybeUninit::<libc::stat>::uninit();
|
|
||||||
// SAFETY: FFI call with a valid fd and buffer
|
|
||||||
let ret = unsafe { libc::fstat(f.as_raw_fd(), stat.as_mut_ptr()) };
|
|
||||||
if ret != 0 {
|
|
||||||
return Err(std::io::Error::last_os_error());
|
|
||||||
}
|
|
||||||
|
|
||||||
// SAFETY: stat is valid at this point
|
|
||||||
let is_block = unsafe { (*stat.as_ptr()).st_mode & S_IFMT == S_IFBLK };
|
|
||||||
Ok(is_block)
|
|
||||||
}
|
|
||||||
|
|
||||||
// libc::ioctl() takes different types on different architectures
|
|
||||||
fn query_block_size(f: &File, block_size_type: BlockSize) -> std::io::Result<u64> {
|
|
||||||
let mut block_size = 0;
|
|
||||||
// SAFETY: FFI call with correct arguments
|
|
||||||
let ret = unsafe {
|
|
||||||
ioctl(
|
|
||||||
f.as_raw_fd(),
|
|
||||||
match block_size_type {
|
|
||||||
BlockSize::LogicalBlock => BLKSSZGET(),
|
|
||||||
BlockSize::PhysicalBlock => BLKPBSZGET(),
|
|
||||||
BlockSize::MinimumIo => BLKIOMIN(),
|
|
||||||
BlockSize::OptimalIo => BLKIOOPT(),
|
|
||||||
} as _,
|
|
||||||
&mut block_size,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
if ret != 0 {
|
|
||||||
return Err(std::io::Error::last_os_error());
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(block_size)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn probe(f: &File) -> std::io::Result<Self> {
|
|
||||||
if !Self::is_block_device(f)? {
|
|
||||||
return Ok(DiskTopology::default());
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(DiskTopology {
|
|
||||||
logical_block_size: Self::query_block_size(f, BlockSize::LogicalBlock)?,
|
|
||||||
physical_block_size: Self::query_block_size(f, BlockSize::PhysicalBlock)?,
|
|
||||||
minimum_io_size: Self::query_block_size(f, BlockSize::MinimumIo)?,
|
|
||||||
optimal_io_size: Self::query_block_size(f, BlockSize::OptimalIo)?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,8 +3,8 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||||
|
|
||||||
use crate::async_io::{AsyncIo, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult};
|
use crate::async_io::{AsyncIo, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult};
|
||||||
use crate::qcow::{QcowFile, RawFile, Result as QcowResult};
|
|
||||||
use crate::AsyncAdaptor;
|
use crate::AsyncAdaptor;
|
||||||
|
use qcow::{QcowFile, RawFile, Result as QcowResult};
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{Seek, SeekFrom};
|
use std::io::{Seek, SeekFrom};
|
||||||
@@ -3,9 +3,8 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||||
|
|
||||||
use crate::async_io::{
|
use crate::async_io::{
|
||||||
AsyncIo, AsyncIoError, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult,
|
AsyncIo, AsyncIoError, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult, DiskTopology,
|
||||||
};
|
};
|
||||||
use crate::DiskTopology;
|
|
||||||
use io_uring::{opcode, squeue, types, IoUring};
|
use io_uring::{opcode, squeue, types, IoUring};
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{Seek, SeekFrom};
|
use std::io::{Seek, SeekFrom};
|
||||||
@@ -37,7 +36,7 @@ impl DiskFile for RawFileDisk {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn topology(&mut self) -> DiskTopology {
|
fn topology(&mut self) -> DiskTopology {
|
||||||
if let Ok(topology) = DiskTopology::probe(&self.file) {
|
if let Ok(topology) = DiskTopology::probe(&mut self.file) {
|
||||||
topology
|
topology
|
||||||
} else {
|
} else {
|
||||||
warn!("Unable to get device topology. Using default topology");
|
warn!("Unable to get device topology. Using default topology");
|
||||||
@@ -87,7 +86,7 @@ impl AsyncIo for RawFileAsync {
|
|||||||
let _ = unsafe {
|
let _ = unsafe {
|
||||||
sq.push(
|
sq.push(
|
||||||
&opcode::Readv::new(types::Fd(self.fd), iovecs.as_ptr(), iovecs.len() as u32)
|
&opcode::Readv::new(types::Fd(self.fd), iovecs.as_ptr(), iovecs.len() as u32)
|
||||||
.offset(offset.try_into().unwrap())
|
.offset(offset)
|
||||||
.build()
|
.build()
|
||||||
.flags(squeue::Flags::ASYNC)
|
.flags(squeue::Flags::ASYNC)
|
||||||
.user_data(user_data),
|
.user_data(user_data),
|
||||||
@@ -115,7 +114,7 @@ impl AsyncIo for RawFileAsync {
|
|||||||
let _ = unsafe {
|
let _ = unsafe {
|
||||||
sq.push(
|
sq.push(
|
||||||
&opcode::Writev::new(types::Fd(self.fd), iovecs.as_ptr(), iovecs.len() as u32)
|
&opcode::Writev::new(types::Fd(self.fd), iovecs.as_ptr(), iovecs.len() as u32)
|
||||||
.offset(offset.try_into().unwrap())
|
.offset(offset)
|
||||||
.build()
|
.build()
|
||||||
.flags(squeue::Flags::ASYNC)
|
.flags(squeue::Flags::ASYNC)
|
||||||
.user_data(user_data),
|
.user_data(user_data),
|
||||||
@@ -3,9 +3,8 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||||
|
|
||||||
use crate::async_io::{
|
use crate::async_io::{
|
||||||
AsyncIo, AsyncIoError, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult,
|
AsyncIo, AsyncIoError, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult, DiskTopology,
|
||||||
};
|
};
|
||||||
use crate::DiskTopology;
|
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{Seek, SeekFrom};
|
use std::io::{Seek, SeekFrom};
|
||||||
@@ -34,7 +33,7 @@ impl DiskFile for RawFileDiskSync {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn topology(&mut self) -> DiskTopology {
|
fn topology(&mut self) -> DiskTopology {
|
||||||
if let Ok(topology) = DiskTopology::probe(&self.file) {
|
if let Ok(topology) = DiskTopology::probe(&mut self.file) {
|
||||||
topology
|
topology
|
||||||
} else {
|
} else {
|
||||||
warn!("Unable to get device topology. Using default topology");
|
warn!("Unable to get device topology. Using default topology");
|
||||||
@@ -74,7 +73,7 @@ impl AsyncIo for RawFileSync {
|
|||||||
let result = unsafe {
|
let result = unsafe {
|
||||||
libc::preadv(
|
libc::preadv(
|
||||||
self.fd as libc::c_int,
|
self.fd as libc::c_int,
|
||||||
iovecs.as_ptr(),
|
iovecs.as_ptr() as *const libc::iovec,
|
||||||
iovecs.len() as libc::c_int,
|
iovecs.len() as libc::c_int,
|
||||||
offset,
|
offset,
|
||||||
)
|
)
|
||||||
@@ -99,7 +98,7 @@ impl AsyncIo for RawFileSync {
|
|||||||
let result = unsafe {
|
let result = unsafe {
|
||||||
libc::pwritev(
|
libc::pwritev(
|
||||||
self.fd as libc::c_int,
|
self.fd as libc::c_int,
|
||||||
iovecs.as_ptr(),
|
iovecs.as_ptr() as *const libc::iovec,
|
||||||
iovecs.len() as libc::c_int,
|
iovecs.len() as libc::c_int,
|
||||||
offset,
|
offset,
|
||||||
)
|
)
|
||||||
@@ -2,9 +2,9 @@
|
|||||||
//
|
//
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use crate::{read_aligned_block_size, DiskTopology};
|
use std::convert::TryInto;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{Seek, SeekFrom};
|
use std::io::{Read, Seek, SeekFrom};
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub struct VhdFooter {
|
pub struct VhdFooter {
|
||||||
@@ -27,33 +27,37 @@ pub struct VhdFooter {
|
|||||||
|
|
||||||
impl VhdFooter {
|
impl VhdFooter {
|
||||||
pub fn new(file: &mut File) -> std::io::Result<VhdFooter> {
|
pub fn new(file: &mut File) -> std::io::Result<VhdFooter> {
|
||||||
let blocksize = DiskTopology::probe(file)?.logical_block_size as usize;
|
// We must create a buffer aligned on 512 bytes with a size being a
|
||||||
|
// multiple of 512 bytes as the file might be opened with O_DIRECT flag.
|
||||||
|
#[repr(align(512))]
|
||||||
|
struct Sector {
|
||||||
|
data: [u8; 512],
|
||||||
|
}
|
||||||
|
let mut s = Sector { data: [0; 512] };
|
||||||
|
|
||||||
// Place the cursor in the last block of the file
|
// Place the cursor 512 bytes before the end of the file, as this is
|
||||||
file.seek(SeekFrom::End(0 - (blocksize as i64)))?;
|
// where the footer starts.
|
||||||
// Read in the last block
|
file.seek(SeekFrom::End(-512))?;
|
||||||
let data = read_aligned_block_size(file)?;
|
|
||||||
|
|
||||||
// We only care about the last sector
|
// Fill in the VhdFooter structure
|
||||||
let offset = blocksize - 512;
|
file.read_exact(&mut s.data)?;
|
||||||
let sector = &data[offset..];
|
|
||||||
|
|
||||||
Ok(VhdFooter {
|
Ok(VhdFooter {
|
||||||
cookie: u64::from_be_bytes(sector[0..8].try_into().unwrap()),
|
cookie: u64::from_be_bytes(s.data[0..8].try_into().unwrap()),
|
||||||
features: u32::from_be_bytes(sector[8..12].try_into().unwrap()),
|
features: u32::from_be_bytes(s.data[8..12].try_into().unwrap()),
|
||||||
file_format_version: u32::from_be_bytes(sector[12..16].try_into().unwrap()),
|
file_format_version: u32::from_be_bytes(s.data[12..16].try_into().unwrap()),
|
||||||
data_offset: u64::from_be_bytes(sector[16..24].try_into().unwrap()),
|
data_offset: u64::from_be_bytes(s.data[16..24].try_into().unwrap()),
|
||||||
time_stamp: u32::from_be_bytes(sector[24..28].try_into().unwrap()),
|
time_stamp: u32::from_be_bytes(s.data[24..28].try_into().unwrap()),
|
||||||
creator_application: u32::from_be_bytes(sector[28..32].try_into().unwrap()),
|
creator_application: u32::from_be_bytes(s.data[28..32].try_into().unwrap()),
|
||||||
creator_version: u32::from_be_bytes(sector[32..36].try_into().unwrap()),
|
creator_version: u32::from_be_bytes(s.data[32..36].try_into().unwrap()),
|
||||||
creator_host_os: u32::from_be_bytes(sector[36..40].try_into().unwrap()),
|
creator_host_os: u32::from_be_bytes(s.data[36..40].try_into().unwrap()),
|
||||||
original_size: u64::from_be_bytes(sector[40..48].try_into().unwrap()),
|
original_size: u64::from_be_bytes(s.data[40..48].try_into().unwrap()),
|
||||||
current_size: u64::from_be_bytes(sector[48..56].try_into().unwrap()),
|
current_size: u64::from_be_bytes(s.data[48..56].try_into().unwrap()),
|
||||||
disk_geometry: u32::from_be_bytes(sector[56..60].try_into().unwrap()),
|
disk_geometry: u32::from_be_bytes(s.data[56..60].try_into().unwrap()),
|
||||||
disk_type: u32::from_be_bytes(sector[60..64].try_into().unwrap()),
|
disk_type: u32::from_be_bytes(s.data[60..64].try_into().unwrap()),
|
||||||
checksum: u32::from_be_bytes(sector[64..68].try_into().unwrap()),
|
checksum: u32::from_be_bytes(s.data[64..68].try_into().unwrap()),
|
||||||
unique_id: u128::from_be_bytes(sector[68..84].try_into().unwrap()),
|
unique_id: u128::from_be_bytes(s.data[68..84].try_into().unwrap()),
|
||||||
saved_state: u8::from_be_bytes(sector[84..85].try_into().unwrap()),
|
saved_state: u8::from_be_bytes(s.data[84..85].try_into().unwrap()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3,11 +3,11 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use crate::async_io::{AsyncIo, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult};
|
use crate::async_io::{AsyncIo, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult};
|
||||||
use crate::vhdx::{Result as VhdxResult, Vhdx};
|
|
||||||
use crate::AsyncAdaptor;
|
use crate::AsyncAdaptor;
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::sync::{Arc, Mutex, MutexGuard};
|
use std::sync::{Arc, Mutex, MutexGuard};
|
||||||
|
use vhdx::vhdx::{Result as VhdxResult, Vhdx};
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
pub struct VhdxDiskSync {
|
pub struct VhdxDiskSync {
|
||||||
4
build.rs
4
build.rs
@@ -17,8 +17,8 @@ fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// This println!() has a special behavior, as it will set the environment
|
// 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
|
// Particularly, this is used from src/main.rs to display the exact
|
||||||
// version.
|
// version.
|
||||||
println!("cargo:rustc-env=BUILD_VERSION={version}");
|
println!("cargo:rustc-env=BUILT_VERSION={version}");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +1,26 @@
|
|||||||
[package]
|
[package]
|
||||||
authors = ["The Chromium OS Authors"]
|
|
||||||
edition = "2021"
|
|
||||||
name = "devices"
|
name = "devices"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
authors = ["The Chromium OS Authors"]
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
acpi_tables = { git = "https://github.com/rust-vmm/acpi_tables", branch = "main" }
|
acpi_tables = { git = "https://github.com/rust-vmm/acpi_tables", branch = "main" }
|
||||||
anyhow = "1.0.81"
|
anyhow = "1.0.69"
|
||||||
arch = { path = "../arch" }
|
arch = { path = "../arch" }
|
||||||
bitflags = "2.5.0"
|
bitflags = "1.3.2"
|
||||||
byteorder = "1.5.0"
|
byteorder = "1.4.3"
|
||||||
event_monitor = { path = "../event_monitor" }
|
|
||||||
hypervisor = { path = "../hypervisor" }
|
hypervisor = { path = "../hypervisor" }
|
||||||
libc = "0.2.153"
|
libc = "0.2.139"
|
||||||
log = "0.4.21"
|
log = "0.4.17"
|
||||||
pci = { path = "../pci" }
|
thiserror = "1.0.39"
|
||||||
serde = { version = "1.0.197", features = ["derive"] }
|
|
||||||
thiserror = "1.0.60"
|
|
||||||
tpm = { path = "../tpm" }
|
tpm = { path = "../tpm" }
|
||||||
vm-allocator = { path = "../vm-allocator" }
|
versionize = "0.1.10"
|
||||||
|
versionize_derive = "0.1.4"
|
||||||
vm-device = { path = "../vm-device" }
|
vm-device = { path = "../vm-device" }
|
||||||
vm-memory = "0.14.1"
|
vm-memory = "0.10.0"
|
||||||
vm-migration = { path = "../vm-migration" }
|
vm-migration = { path = "../vm-migration" }
|
||||||
vmm-sys-util = "0.12.1"
|
vmm-sys-util = "0.11.0"
|
||||||
|
|
||||||
[target.'cfg(target_arch = "aarch64")'.dependencies]
|
[target.'cfg(target_arch = "aarch64")'.dependencies]
|
||||||
arch = { path = "../arch" }
|
arch = { path = "../arch" }
|
||||||
|
|||||||
@@ -5,9 +5,7 @@
|
|||||||
|
|
||||||
use super::AcpiNotificationFlags;
|
use super::AcpiNotificationFlags;
|
||||||
use acpi_tables::{aml, Aml, AmlSink};
|
use acpi_tables::{aml, Aml, AmlSink};
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
|
||||||
use std::sync::{Arc, Barrier};
|
use std::sync::{Arc, Barrier};
|
||||||
use std::thread;
|
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
use vm_device::interrupt::InterruptSourceGroup;
|
use vm_device::interrupt::InterruptSourceGroup;
|
||||||
use vm_device::BusDevice;
|
use vm_device::BusDevice;
|
||||||
@@ -20,20 +18,14 @@ pub const GED_DEVICE_ACPI_SIZE: usize = 0x1;
|
|||||||
pub struct AcpiShutdownDevice {
|
pub struct AcpiShutdownDevice {
|
||||||
exit_evt: EventFd,
|
exit_evt: EventFd,
|
||||||
reset_evt: EventFd,
|
reset_evt: EventFd,
|
||||||
vcpus_kill_signalled: Arc<AtomicBool>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AcpiShutdownDevice {
|
impl AcpiShutdownDevice {
|
||||||
/// Constructs a device that will signal the given event when the guest requests it.
|
/// Constructs a device that will signal the given event when the guest requests it.
|
||||||
pub fn new(
|
pub fn new(exit_evt: EventFd, reset_evt: EventFd) -> AcpiShutdownDevice {
|
||||||
exit_evt: EventFd,
|
|
||||||
reset_evt: EventFd,
|
|
||||||
vcpus_kill_signalled: Arc<AtomicBool>,
|
|
||||||
) -> AcpiShutdownDevice {
|
|
||||||
AcpiShutdownDevice {
|
AcpiShutdownDevice {
|
||||||
exit_evt,
|
exit_evt,
|
||||||
reset_evt,
|
reset_evt,
|
||||||
vcpus_kill_signalled,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -51,13 +43,6 @@ impl BusDevice for AcpiShutdownDevice {
|
|||||||
if let Err(e) = self.reset_evt.write(1) {
|
if let Err(e) = self.reset_evt.write(1) {
|
||||||
error!("Error triggering ACPI reset event: {}", e);
|
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
|
// The ACPI DSDT table specifies the S5 sleep state (shutdown) as value 5
|
||||||
const S5_SLEEP_VALUE: u8 = 5;
|
const S5_SLEEP_VALUE: u8 = 5;
|
||||||
@@ -68,13 +53,6 @@ impl BusDevice for AcpiShutdownDevice {
|
|||||||
if let Err(e) = self.exit_evt.write(1) {
|
if let Err(e) = self.exit_evt.write(1) {
|
||||||
error!("Error triggering ACPI shutdown event: {}", e);
|
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
|
None
|
||||||
}
|
}
|
||||||
@@ -134,11 +112,10 @@ impl Aml for AcpiGedDevice {
|
|||||||
&aml::Name::new(
|
&aml::Name::new(
|
||||||
"_CRS".into(),
|
"_CRS".into(),
|
||||||
&aml::ResourceTemplate::new(vec![&aml::AddressSpace::new_memory(
|
&aml::ResourceTemplate::new(vec![&aml::AddressSpace::new_memory(
|
||||||
aml::AddressSpaceCacheable::NotCacheable,
|
aml::AddressSpaceCachable::NotCacheable,
|
||||||
true,
|
true,
|
||||||
self.address.0,
|
self.address.0,
|
||||||
self.address.0 + GED_DEVICE_ACPI_SIZE as u64 - 1,
|
self.address.0 + GED_DEVICE_ACPI_SIZE as u64 - 1,
|
||||||
None,
|
|
||||||
)]),
|
)]),
|
||||||
),
|
),
|
||||||
&aml::OpRegion::new(
|
&aml::OpRegion::new(
|
||||||
|
|||||||
@@ -1,64 +0,0 @@
|
|||||||
// Copyright © 2023 Cyberus Technology
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
//
|
|
||||||
|
|
||||||
//! Module for [`DebugconState`].
|
|
||||||
|
|
||||||
use std::io;
|
|
||||||
use std::io::Write;
|
|
||||||
use std::sync::{Arc, Barrier};
|
|
||||||
use vm_device::BusDevice;
|
|
||||||
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
|
|
||||||
|
|
||||||
/// I/O-port.
|
|
||||||
pub const DEFAULT_PORT: u64 = 0xe9;
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct DebugconState {}
|
|
||||||
|
|
||||||
/// Emulates a debug console similar to the QEMU debugcon device. This device
|
|
||||||
/// is stateless and only prints the bytes (usually text) that are written to
|
|
||||||
/// it.
|
|
||||||
///
|
|
||||||
/// This device is only available on x86.
|
|
||||||
///
|
|
||||||
/// Reference:
|
|
||||||
/// - https://github.com/qemu/qemu/blob/master/hw/char/debugcon.c
|
|
||||||
/// - https://phip1611.de/blog/how-to-use-qemus-debugcon-feature-and-write-to-a-file/
|
|
||||||
pub struct DebugConsole {
|
|
||||||
id: String,
|
|
||||||
out: Box<dyn io::Write + Send>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DebugConsole {
|
|
||||||
pub fn new(id: String, out: Box<dyn io::Write + Send>) -> Self {
|
|
||||||
Self { id, out }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BusDevice for DebugConsole {
|
|
||||||
fn read(&mut self, _base: u64, _offset: u64, _data: &mut [u8]) {}
|
|
||||||
|
|
||||||
fn write(&mut self, _base: u64, _offset: u64, data: &[u8]) -> Option<Arc<Barrier>> {
|
|
||||||
if let Err(e) = self.out.write_all(data) {
|
|
||||||
// unlikely
|
|
||||||
error!("debug-console: failed writing data: {e:?}");
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Snapshottable for DebugConsole {
|
|
||||||
fn id(&self) -> String {
|
|
||||||
self.id.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn snapshot(&mut self) -> Result<Snapshot, MigratableError> {
|
|
||||||
Snapshot::new_from_state(&())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Pausable for DebugConsole {}
|
|
||||||
impl Transportable for DebugConsole {}
|
|
||||||
impl Migratable for DebugConsole {}
|
|
||||||
@@ -27,7 +27,7 @@ pub const IRQ_LEGACY_BASE: usize = layout::IRQ_BASE as usize;
|
|||||||
pub const IRQ_LEGACY_COUNT: usize = 32;
|
pub const IRQ_LEGACY_COUNT: usize = 32;
|
||||||
pub const GIC_SNAPSHOT_ID: &str = "gic-v3-its";
|
pub const GIC_SNAPSHOT_ID: &str = "gic-v3-its";
|
||||||
|
|
||||||
// Gic (Generic Interrupt Controller) struct provides all the functionality of a
|
// Gic (Generic Interupt Controller) struct provides all the functionality of a
|
||||||
// GIC device. It wraps a hypervisor-emulated GIC device (Vgic) provided by the
|
// GIC device. It wraps a hypervisor-emulated GIC device (Vgic) provided by the
|
||||||
// `hypervisor` crate.
|
// `hypervisor` crate.
|
||||||
// Gic struct also implements InterruptController to provide interrupt delivery
|
// Gic struct also implements InterruptController to provide interrupt delivery
|
||||||
@@ -98,14 +98,9 @@ impl Gic {
|
|||||||
i as InterruptIndex,
|
i as InterruptIndex,
|
||||||
InterruptSourceConfig::LegacyIrq(config),
|
InterruptSourceConfig::LegacyIrq(config),
|
||||||
false,
|
false,
|
||||||
false,
|
|
||||||
)
|
)
|
||||||
.map_err(Error::EnableInterrupt)?;
|
.map_err(Error::EnableInterrupt)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.interrupt_source_group
|
|
||||||
.set_gsi()
|
|
||||||
.map_err(Error::EnableInterrupt)?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,42 +4,31 @@
|
|||||||
|
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::result;
|
use std::result;
|
||||||
use thiserror::Error;
|
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
/// Invalid trigger mode.
|
/// Invalid trigger mode.
|
||||||
#[error("Invalid trigger mode")]
|
|
||||||
InvalidTriggerMode,
|
InvalidTriggerMode,
|
||||||
/// Invalid delivery mode.
|
/// Invalid delivery mode.
|
||||||
#[error("Invalid delivery mode")]
|
|
||||||
InvalidDeliveryMode,
|
InvalidDeliveryMode,
|
||||||
/// Failed creating the interrupt source group.
|
/// Failed creating the interrupt source group.
|
||||||
#[error("Failed creating the interrupt source group: {0}")]
|
|
||||||
CreateInterruptSourceGroup(io::Error),
|
CreateInterruptSourceGroup(io::Error),
|
||||||
/// Failed triggering the interrupt.
|
/// Failed triggering the interrupt.
|
||||||
#[error("Failed triggering the interrupt: {0}")]
|
|
||||||
TriggerInterrupt(io::Error),
|
TriggerInterrupt(io::Error),
|
||||||
/// Failed masking the interrupt.
|
/// Failed masking the interrupt.
|
||||||
#[error("Failed masking the interrupt: {0}")]
|
|
||||||
MaskInterrupt(io::Error),
|
MaskInterrupt(io::Error),
|
||||||
/// Failed unmasking the interrupt.
|
/// Failed unmasking the interrupt.
|
||||||
#[error("Failed unmasking the interrupt: {0}")]
|
|
||||||
UnmaskInterrupt(io::Error),
|
UnmaskInterrupt(io::Error),
|
||||||
/// Failed updating the interrupt.
|
/// Failed updating the interrupt.
|
||||||
#[error("Failed updating the interrupt: {0}")]
|
|
||||||
UpdateInterrupt(io::Error),
|
UpdateInterrupt(io::Error),
|
||||||
/// Failed enabling the interrupt.
|
/// Failed enabling the interrupt.
|
||||||
#[error("Failed enabling the interrupt: {0}")]
|
|
||||||
EnableInterrupt(io::Error),
|
EnableInterrupt(io::Error),
|
||||||
#[cfg(target_arch = "aarch64")]
|
#[cfg(target_arch = "aarch64")]
|
||||||
/// Failed creating GIC device.
|
/// Failed creating GIC device.
|
||||||
#[error("Failed creating GIC device: {0}")]
|
|
||||||
CreateGic(hypervisor::HypervisorVmError),
|
CreateGic(hypervisor::HypervisorVmError),
|
||||||
#[cfg(target_arch = "aarch64")]
|
#[cfg(target_arch = "aarch64")]
|
||||||
/// Failed restoring GIC device.
|
/// Failed restoring GIC device.
|
||||||
#[error("Failed restoring GIC device: {0}")]
|
|
||||||
RestoreGic(hypervisor::arch::aarch64::gic::Error),
|
RestoreGic(hypervisor::arch::aarch64::gic::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,16 +11,19 @@
|
|||||||
|
|
||||||
use super::interrupt_controller::{Error, InterruptController};
|
use super::interrupt_controller::{Error, InterruptController};
|
||||||
use byteorder::{ByteOrder, LittleEndian};
|
use byteorder::{ByteOrder, LittleEndian};
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use std::result;
|
use std::result;
|
||||||
use std::sync::{Arc, Barrier};
|
use std::sync::{Arc, Barrier};
|
||||||
|
use versionize::{VersionMap, Versionize, VersionizeResult};
|
||||||
|
use versionize_derive::Versionize;
|
||||||
use vm_device::interrupt::{
|
use vm_device::interrupt::{
|
||||||
InterruptIndex, InterruptManager, InterruptSourceConfig, InterruptSourceGroup,
|
InterruptIndex, InterruptManager, InterruptSourceConfig, InterruptSourceGroup,
|
||||||
MsiIrqGroupConfig, MsiIrqSourceConfig,
|
MsiIrqGroupConfig, MsiIrqSourceConfig,
|
||||||
};
|
};
|
||||||
use vm_device::BusDevice;
|
use vm_device::BusDevice;
|
||||||
use vm_memory::GuestAddress;
|
use vm_memory::GuestAddress;
|
||||||
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
|
use vm_migration::{
|
||||||
|
Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable, VersionMapped,
|
||||||
|
};
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
type Result<T> = result::Result<T, Error>;
|
type Result<T> = result::Result<T, Error>;
|
||||||
@@ -133,7 +136,7 @@ pub struct Ioapic {
|
|||||||
interrupt_source_group: Arc<dyn InterruptSourceGroup>,
|
interrupt_source_group: Arc<dyn InterruptSourceGroup>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Versionize)]
|
||||||
pub struct IoapicState {
|
pub struct IoapicState {
|
||||||
id_reg: u32,
|
id_reg: u32,
|
||||||
reg_sel: u32,
|
reg_sel: u32,
|
||||||
@@ -141,6 +144,7 @@ pub struct IoapicState {
|
|||||||
used_entries: [bool; NUM_IOAPIC_PINS],
|
used_entries: [bool; NUM_IOAPIC_PINS],
|
||||||
apic_address: u64,
|
apic_address: u64,
|
||||||
}
|
}
|
||||||
|
impl VersionMapped for IoapicState {}
|
||||||
|
|
||||||
impl BusDevice for Ioapic {
|
impl BusDevice for Ioapic {
|
||||||
fn read(&mut self, _base: u64, offset: u64, data: &mut [u8]) {
|
fn read(&mut self, _base: u64, offset: u64, data: &mut [u8]) {
|
||||||
@@ -233,14 +237,9 @@ impl Ioapic {
|
|||||||
if state.is_some() {
|
if state.is_some() {
|
||||||
for (irq, entry) in ioapic.used_entries.iter().enumerate() {
|
for (irq, entry) in ioapic.used_entries.iter().enumerate() {
|
||||||
if *entry {
|
if *entry {
|
||||||
ioapic.update_entry(irq, false)?;
|
ioapic.update_entry(irq)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ioapic
|
|
||||||
.interrupt_source_group
|
|
||||||
.set_gsi()
|
|
||||||
.map_err(Error::UpdateInterrupt)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(ioapic)
|
Ok(ioapic)
|
||||||
@@ -279,7 +278,7 @@ impl Ioapic {
|
|||||||
}
|
}
|
||||||
// The entry must be updated through the interrupt source
|
// The entry must be updated through the interrupt source
|
||||||
// group.
|
// group.
|
||||||
if let Err(e) = self.update_entry(index, true) {
|
if let Err(e) = self.update_entry(index) {
|
||||||
error!("Failed updating IOAPIC entry: {:?}", e);
|
error!("Failed updating IOAPIC entry: {:?}", e);
|
||||||
}
|
}
|
||||||
// Store the information this IRQ is now being used.
|
// Store the information this IRQ is now being used.
|
||||||
@@ -330,7 +329,7 @@ impl Ioapic {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_entry(&self, irq: usize, set_gsi: bool) -> Result<()> {
|
fn update_entry(&self, irq: usize) -> Result<()> {
|
||||||
let entry = self.reg_entries[irq];
|
let entry = self.reg_entries[irq];
|
||||||
|
|
||||||
// Validate Destination Mode value, and retrieve Destination ID
|
// Validate Destination Mode value, and retrieve Destination ID
|
||||||
@@ -387,7 +386,6 @@ impl Ioapic {
|
|||||||
irq as InterruptIndex,
|
irq as InterruptIndex,
|
||||||
InterruptSourceConfig::MsiIrq(config),
|
InterruptSourceConfig::MsiIrq(config),
|
||||||
interrupt_mask(entry) == 1,
|
interrupt_mask(entry) == 1,
|
||||||
set_gsi,
|
|
||||||
)
|
)
|
||||||
.map_err(Error::UpdateInterrupt)?;
|
.map_err(Error::UpdateInterrupt)?;
|
||||||
|
|
||||||
@@ -416,7 +414,7 @@ impl InterruptController for Ioapic {
|
|||||||
self.interrupt_source_group
|
self.interrupt_source_group
|
||||||
.trigger(irq as InterruptIndex)
|
.trigger(irq as InterruptIndex)
|
||||||
.map_err(Error::TriggerInterrupt)?;
|
.map_err(Error::TriggerInterrupt)?;
|
||||||
debug!("Interrupt {irq} successfully delivered");
|
debug!("Interrupt successfully delivered");
|
||||||
|
|
||||||
// If trigger mode is level sensitive, set the Remote IRR bit.
|
// If trigger mode is level sensitive, set the Remote IRR bit.
|
||||||
// It will be cleared when the EOI is received.
|
// It will be cleared when the EOI is received.
|
||||||
@@ -440,7 +438,7 @@ impl Snapshottable for Ioapic {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
|
fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
|
||||||
Snapshot::new_from_state(&self.state())
|
Snapshot::new_from_versioned_state(&self.state())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,11 @@
|
|||||||
// Copyright 2017 The Chromium OS Authors. All rights reserved.
|
// Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
|
||||||
|
|
||||||
use libc::{clock_gettime, gmtime_r, timespec, tm, CLOCK_REALTIME};
|
use libc::{clock_gettime, gmtime_r, timespec, tm, CLOCK_REALTIME};
|
||||||
use std::cmp::min;
|
use std::cmp::min;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
|
||||||
use std::sync::{Arc, Barrier};
|
use std::sync::{Arc, Barrier};
|
||||||
use std::thread;
|
|
||||||
use vm_device::BusDevice;
|
use vm_device::BusDevice;
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
@@ -27,19 +23,13 @@ pub struct Cmos {
|
|||||||
index: u8,
|
index: u8,
|
||||||
data: [u8; DATA_LEN],
|
data: [u8; DATA_LEN],
|
||||||
reset_evt: EventFd,
|
reset_evt: EventFd,
|
||||||
vcpus_kill_signalled: Option<Arc<AtomicBool>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Cmos {
|
impl Cmos {
|
||||||
/// Constructs a CMOS/RTC device with initial data.
|
/// Constructs a CMOS/RTC device with initial data.
|
||||||
/// `mem_below_4g` is the size of memory in bytes below the 32-bit gap.
|
/// `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.
|
/// `mem_above_4g` is the size of memory in bytes above the 32-bit gap.
|
||||||
pub fn new(
|
pub fn new(mem_below_4g: u64, mem_above_4g: u64, reset_evt: EventFd) -> Cmos {
|
||||||
mem_below_4g: u64,
|
|
||||||
mem_above_4g: u64,
|
|
||||||
reset_evt: EventFd,
|
|
||||||
vcpus_kill_signalled: Option<Arc<AtomicBool>>,
|
|
||||||
) -> Cmos {
|
|
||||||
let mut data = [0u8; DATA_LEN];
|
let mut data = [0u8; DATA_LEN];
|
||||||
|
|
||||||
// Extended memory from 16 MB to 4 GB in units of 64 KB
|
// Extended memory from 16 MB to 4 GB in units of 64 KB
|
||||||
@@ -60,7 +50,6 @@ impl Cmos {
|
|||||||
index: 0,
|
index: 0,
|
||||||
data,
|
data,
|
||||||
reset_evt,
|
reset_evt,
|
||||||
vcpus_kill_signalled,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -78,15 +67,6 @@ impl BusDevice for Cmos {
|
|||||||
if self.index == 0x8f && data[0] == 0 {
|
if self.index == 0x8f && data[0] == 0 {
|
||||||
info!("CMOS reset");
|
info!("CMOS reset");
|
||||||
self.reset_evt.write(1).unwrap();
|
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 {
|
} else {
|
||||||
self.data[(self.index & INDEX_MASK) as usize] = data[0]
|
self.data[(self.index & INDEX_MASK) as usize] = data[0]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,14 +8,16 @@
|
|||||||
//!
|
//!
|
||||||
|
|
||||||
use crate::{read_le_u32, write_le_u32};
|
use crate::{read_le_u32, write_le_u32};
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use std::io;
|
|
||||||
use std::result;
|
use std::result;
|
||||||
use std::sync::{Arc, Barrier};
|
use std::sync::{Arc, Barrier};
|
||||||
use thiserror::Error;
|
use std::{fmt, io};
|
||||||
|
use versionize::{VersionMap, Versionize, VersionizeResult};
|
||||||
|
use versionize_derive::Versionize;
|
||||||
use vm_device::interrupt::InterruptSourceGroup;
|
use vm_device::interrupt::InterruptSourceGroup;
|
||||||
use vm_device::BusDevice;
|
use vm_device::BusDevice;
|
||||||
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
|
use vm_migration::{
|
||||||
|
Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable, VersionMapped,
|
||||||
|
};
|
||||||
|
|
||||||
const OFS_DATA: u64 = 0x400; // Data Register
|
const OFS_DATA: u64 = 0x400; // Data Register
|
||||||
const GPIODIR: u64 = 0x400; // Direction Register
|
const GPIODIR: u64 = 0x400; // Direction Register
|
||||||
@@ -29,7 +31,7 @@ const GPIOIC: u64 = 0x41c; // Interrupt Clear Register
|
|||||||
const GPIOAFSEL: u64 = 0x420; // Mode Control Select Register
|
const GPIOAFSEL: u64 = 0x420; // Mode Control Select Register
|
||||||
// From 0x424 to 0xFDC => reserved space.
|
// From 0x424 to 0xFDC => reserved space.
|
||||||
// From 0xFE0 to 0xFFC => Peripheral and PrimeCell Identification Registers which are Read Only registers.
|
// From 0xFE0 to 0xFFC => Peripheral and PrimeCell Identification Registers which are Read Only registers.
|
||||||
// These registers can conceptually be treated as a 32-bit register, and PartNumber[11:0] is used to identify the peripheral.
|
// Thses registers can conceptually be treated as a 32-bit register, and PartNumber[11:0] is used to identify the peripheral.
|
||||||
// We are putting the expected values (look at 'Reset value' column from above mentioned document) in an array.
|
// We are putting the expected values (look at 'Reset value' column from above mentioned document) in an array.
|
||||||
const GPIO_ID: [u8; 8] = [0x61, 0x10, 0x14, 0x00, 0x0d, 0xf0, 0x05, 0xb1];
|
const GPIO_ID: [u8; 8] = [0x61, 0x10, 0x14, 0x00, 0x0d, 0xf0, 0x05, 0xb1];
|
||||||
// ID Margins
|
// ID Margins
|
||||||
@@ -38,18 +40,29 @@ const GPIO_ID_HIGH: u64 = 0x1000;
|
|||||||
|
|
||||||
const N_GPIOS: u32 = 8;
|
const N_GPIOS: u32 = 8;
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[error("Bad Write Offset: {0}")]
|
|
||||||
BadWriteOffset(u64),
|
BadWriteOffset(u64),
|
||||||
#[error("GPIO interrupt disabled by guest driver.")]
|
|
||||||
GpioInterruptDisabled,
|
GpioInterruptDisabled,
|
||||||
#[error("Could not trigger GPIO interrupt: {0}.")]
|
|
||||||
GpioInterruptFailure(io::Error),
|
GpioInterruptFailure(io::Error),
|
||||||
#[error("Invalid GPIO Input key triggered: {0}.")]
|
|
||||||
GpioTriggerKeyFailure(u32),
|
GpioTriggerKeyFailure(u32),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Error {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Error::BadWriteOffset(offset) => write!(f, "Bad Write Offset: {offset}"),
|
||||||
|
Error::GpioInterruptDisabled => write!(f, "GPIO interrupt disabled by guest driver.",),
|
||||||
|
Error::GpioInterruptFailure(ref e) => {
|
||||||
|
write!(f, "Could not trigger GPIO interrupt: {e}.")
|
||||||
|
}
|
||||||
|
Error::GpioTriggerKeyFailure(key) => {
|
||||||
|
write!(f, "Invalid GPIO Input key triggerd: {key}.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type Result<T> = result::Result<T, Error>;
|
type Result<T> = result::Result<T, Error>;
|
||||||
|
|
||||||
/// A GPIO device following the PL061 specification.
|
/// A GPIO device following the PL061 specification.
|
||||||
@@ -76,7 +89,7 @@ pub struct Gpio {
|
|||||||
interrupt: Arc<dyn InterruptSourceGroup>,
|
interrupt: Arc<dyn InterruptSourceGroup>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Versionize)]
|
||||||
pub struct GpioState {
|
pub struct GpioState {
|
||||||
data: u32,
|
data: u32,
|
||||||
old_in_data: u32,
|
old_in_data: u32,
|
||||||
@@ -89,6 +102,8 @@ pub struct GpioState {
|
|||||||
afsel: u32,
|
afsel: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl VersionMapped for GpioState {}
|
||||||
|
|
||||||
impl Gpio {
|
impl Gpio {
|
||||||
/// Constructs an PL061 GPIO device.
|
/// Constructs an PL061 GPIO device.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
@@ -313,7 +328,7 @@ impl Snapshottable for Gpio {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
|
fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
|
||||||
Snapshot::new_from_state(&self.state())
|
Snapshot::new_from_versioned_state(&self.state())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -324,6 +339,8 @@ impl Migratable for Gpio {}
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::{read_le_u32, write_le_u32};
|
||||||
|
use std::sync::Arc;
|
||||||
use vm_device::interrupt::{InterruptIndex, InterruptSourceConfig};
|
use vm_device::interrupt::{InterruptIndex, InterruptSourceConfig};
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
@@ -344,15 +361,10 @@ mod tests {
|
|||||||
_index: InterruptIndex,
|
_index: InterruptIndex,
|
||||||
_config: InterruptSourceConfig,
|
_config: InterruptSourceConfig,
|
||||||
_masked: bool,
|
_masked: bool,
|
||||||
_set_gsi: bool,
|
|
||||||
) -> result::Result<(), std::io::Error> {
|
) -> result::Result<(), std::io::Error> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_gsi(&self) -> result::Result<(), std::io::Error> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn notifier(&self, _index: InterruptIndex) -> Option<EventFd> {
|
fn notifier(&self, _index: InterruptIndex) -> Option<EventFd> {
|
||||||
Some(self.event_fd.try_clone().unwrap())
|
Some(self.event_fd.try_clone().unwrap())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +1,20 @@
|
|||||||
// Copyright 2017 The Chromium OS Authors. All rights reserved.
|
// Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE-BSD-3-Clause file.
|
// found in the LICENSE-BSD-3-Clause file.
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
|
||||||
|
|
||||||
use std::sync::{
|
use std::sync::{Arc, Barrier};
|
||||||
atomic::{AtomicBool, Ordering},
|
|
||||||
Arc, Barrier,
|
|
||||||
};
|
|
||||||
use std::thread;
|
|
||||||
use vm_device::BusDevice;
|
use vm_device::BusDevice;
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
/// A i8042 PS/2 controller that emulates just enough to shutdown the machine.
|
/// A i8042 PS/2 controller that emulates just enough to shutdown the machine.
|
||||||
pub struct I8042Device {
|
pub struct I8042Device {
|
||||||
reset_evt: EventFd,
|
reset_evt: EventFd,
|
||||||
vcpus_kill_signalled: Arc<AtomicBool>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl I8042Device {
|
impl I8042Device {
|
||||||
/// Constructs a i8042 device that will signal the given event when the guest requests it.
|
/// 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 {
|
pub fn new(reset_evt: EventFd) -> I8042Device {
|
||||||
I8042Device {
|
I8042Device { reset_evt }
|
||||||
reset_evt,
|
|
||||||
vcpus_kill_signalled,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,13 +38,6 @@ impl BusDevice for I8042Device {
|
|||||||
if let Err(e) = self.reset_evt.write(1) {
|
if let Err(e) = self.reset_evt.write(1) {
|
||||||
error!("Error triggering i8042 reset event: {}", e);
|
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
|
None
|
||||||
|
|||||||
@@ -9,10 +9,10 @@
|
|||||||
//! a real-time clock input.
|
//! a real-time clock input.
|
||||||
//!
|
//!
|
||||||
use crate::{read_le_u32, write_le_u32};
|
use crate::{read_le_u32, write_le_u32};
|
||||||
|
use std::fmt;
|
||||||
use std::sync::{Arc, Barrier};
|
use std::sync::{Arc, Barrier};
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
use std::{io, result};
|
use std::{io, result};
|
||||||
use thiserror::Error;
|
|
||||||
use vm_device::interrupt::InterruptSourceGroup;
|
use vm_device::interrupt::InterruptSourceGroup;
|
||||||
use vm_device::BusDevice;
|
use vm_device::BusDevice;
|
||||||
|
|
||||||
@@ -39,14 +39,21 @@ const AMBA_ID_HIGH: u64 = 0x1000;
|
|||||||
/// Constant to convert seconds to nanoseconds.
|
/// Constant to convert seconds to nanoseconds.
|
||||||
pub const NANOS_PER_SECOND: u64 = 1_000_000_000;
|
pub const NANOS_PER_SECOND: u64 = 1_000_000_000;
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[error("Bad Write Offset: {0}")]
|
|
||||||
BadWriteOffset(u64),
|
BadWriteOffset(u64),
|
||||||
#[error("Failed to trigger interrupt: {0}")]
|
|
||||||
InterruptFailure(io::Error),
|
InterruptFailure(io::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Error {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Error::BadWriteOffset(offset) => write!(f, "Bad Write Offset: {offset}"),
|
||||||
|
Error::InterruptFailure(e) => write!(f, "Failed to trigger interrupt: {e}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type Result<T> = result::Result<T, Error>;
|
type Result<T> = result::Result<T, Error>;
|
||||||
|
|
||||||
/// Wrapper over `libc::clockid_t` to specify Linux Kernel clock source.
|
/// Wrapper over `libc::clockid_t` to specify Linux Kernel clock source.
|
||||||
@@ -72,6 +79,98 @@ impl From<ClockType> for libc::clockid_t {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Structure representing the date in local time with nanosecond precision.
|
||||||
|
pub struct LocalTime {
|
||||||
|
/// Seconds in current minute.
|
||||||
|
sec: i32,
|
||||||
|
/// Minutes in current hour.
|
||||||
|
min: i32,
|
||||||
|
/// Hours in current day, 24H format.
|
||||||
|
hour: i32,
|
||||||
|
/// Days in current month.
|
||||||
|
mday: i32,
|
||||||
|
/// Months in current year.
|
||||||
|
mon: i32,
|
||||||
|
/// Years passed since 1900 BC.
|
||||||
|
year: i32,
|
||||||
|
/// Nanoseconds in current second.
|
||||||
|
nsec: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LocalTime {
|
||||||
|
/// Returns the [LocalTime](struct.LocalTime.html) structure for the calling moment.
|
||||||
|
#[cfg(test)]
|
||||||
|
pub fn now() -> LocalTime {
|
||||||
|
let mut timespec = libc::timespec {
|
||||||
|
tv_sec: 0,
|
||||||
|
tv_nsec: 0,
|
||||||
|
};
|
||||||
|
let mut tm: libc::tm = libc::tm {
|
||||||
|
tm_sec: 0,
|
||||||
|
tm_min: 0,
|
||||||
|
tm_hour: 0,
|
||||||
|
tm_mday: 0,
|
||||||
|
tm_mon: 0,
|
||||||
|
tm_year: 0,
|
||||||
|
tm_wday: 0,
|
||||||
|
tm_yday: 0,
|
||||||
|
tm_isdst: 0,
|
||||||
|
tm_gmtoff: 0,
|
||||||
|
tm_zone: std::ptr::null(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// SAFETY: the parameters are valid.
|
||||||
|
unsafe {
|
||||||
|
libc::clock_gettime(libc::CLOCK_REALTIME, &mut timespec);
|
||||||
|
libc::localtime_r(×pec.tv_sec, &mut tm);
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalTime {
|
||||||
|
sec: tm.tm_sec,
|
||||||
|
min: tm.tm_min,
|
||||||
|
hour: tm.tm_hour,
|
||||||
|
mday: tm.tm_mday,
|
||||||
|
mon: tm.tm_mon,
|
||||||
|
year: tm.tm_year,
|
||||||
|
nsec: timespec.tv_nsec,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for LocalTime {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}-{:02}-{:02}T{:02}:{:02}:{:02}.{:09}",
|
||||||
|
self.year + 1900,
|
||||||
|
self.mon + 1,
|
||||||
|
self.mday,
|
||||||
|
self.hour,
|
||||||
|
self.min,
|
||||||
|
self.sec,
|
||||||
|
self.nsec
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Holds a micro-second resolution timestamp with both the real time and cpu time.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct TimestampUs {
|
||||||
|
/// Real time in microseconds.
|
||||||
|
pub time_us: u64,
|
||||||
|
/// Cpu time in microseconds.
|
||||||
|
pub cputime_us: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for TimestampUs {
|
||||||
|
fn default() -> TimestampUs {
|
||||||
|
TimestampUs {
|
||||||
|
time_us: get_time(ClockType::Monotonic) / 1000,
|
||||||
|
cputime_us: get_time(ClockType::ProcessCpu) / 1000,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns a timestamp in nanoseconds based on the provided clock type.
|
/// Returns a timestamp in nanoseconds based on the provided clock type.
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
@@ -230,78 +329,15 @@ impl BusDevice for Rtc {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{
|
use crate::{
|
||||||
read_be_u16, read_be_u32, read_le_i32, read_le_u16, read_le_u64, write_be_u16,
|
read_be_u16, read_be_u32, read_le_i32, read_le_u16, read_le_u32, read_le_u64, write_be_u16,
|
||||||
write_be_u32, write_le_i32, write_le_u16, write_le_u64,
|
write_be_u32, write_le_i32, write_le_u16, write_le_u32, write_le_u64,
|
||||||
};
|
};
|
||||||
|
use std::sync::Arc;
|
||||||
use vm_device::interrupt::{InterruptIndex, InterruptSourceConfig};
|
use vm_device::interrupt::{InterruptIndex, InterruptSourceConfig};
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
const LEGACY_RTC_MAPPED_IO_START: u64 = 0x0901_0000;
|
const LEGACY_RTC_MAPPED_IO_START: u64 = 0x0901_0000;
|
||||||
|
|
||||||
struct LocalTime {
|
|
||||||
sec: i32,
|
|
||||||
min: i32,
|
|
||||||
hour: i32,
|
|
||||||
mday: i32,
|
|
||||||
mon: i32,
|
|
||||||
year: i32,
|
|
||||||
nsec: i64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LocalTime {
|
|
||||||
fn now() -> LocalTime {
|
|
||||||
let mut timespec = libc::timespec {
|
|
||||||
tv_sec: 0,
|
|
||||||
tv_nsec: 0,
|
|
||||||
};
|
|
||||||
let mut tm: libc::tm = libc::tm {
|
|
||||||
tm_sec: 0,
|
|
||||||
tm_min: 0,
|
|
||||||
tm_hour: 0,
|
|
||||||
tm_mday: 0,
|
|
||||||
tm_mon: 0,
|
|
||||||
tm_year: 0,
|
|
||||||
tm_wday: 0,
|
|
||||||
tm_yday: 0,
|
|
||||||
tm_isdst: 0,
|
|
||||||
tm_gmtoff: 0,
|
|
||||||
tm_zone: std::ptr::null(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// SAFETY: the parameters are valid.
|
|
||||||
unsafe {
|
|
||||||
libc::clock_gettime(libc::CLOCK_REALTIME, &mut timespec);
|
|
||||||
libc::localtime_r(×pec.tv_sec, &mut tm);
|
|
||||||
}
|
|
||||||
|
|
||||||
LocalTime {
|
|
||||||
sec: tm.tm_sec,
|
|
||||||
min: tm.tm_min,
|
|
||||||
hour: tm.tm_hour,
|
|
||||||
mday: tm.tm_mday,
|
|
||||||
mon: tm.tm_mon,
|
|
||||||
year: tm.tm_year,
|
|
||||||
nsec: timespec.tv_nsec,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Display for LocalTime {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"{}-{:02}-{:02}T{:02}:{:02}:{:02}.{:09}",
|
|
||||||
self.year + 1900,
|
|
||||||
self.mon + 1,
|
|
||||||
self.mday,
|
|
||||||
self.hour,
|
|
||||||
self.min,
|
|
||||||
self.sec,
|
|
||||||
self.nsec
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_get_time() {
|
fn test_get_time() {
|
||||||
for _ in 0..1000 {
|
for _ in 0..1000 {
|
||||||
@@ -377,15 +413,10 @@ mod tests {
|
|||||||
_index: InterruptIndex,
|
_index: InterruptIndex,
|
||||||
_config: InterruptSourceConfig,
|
_config: InterruptSourceConfig,
|
||||||
_masked: bool,
|
_masked: bool,
|
||||||
_set_gsi: bool,
|
|
||||||
) -> result::Result<(), std::io::Error> {
|
) -> result::Result<(), std::io::Error> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_gsi(&self) -> result::Result<(), std::io::Error> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn notifier(&self, _index: InterruptIndex) -> Option<EventFd> {
|
fn notifier(&self, _index: InterruptIndex) -> Option<EventFd> {
|
||||||
Some(self.event_fd.try_clone().unwrap())
|
Some(self.event_fd.try_clone().unwrap())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,13 +5,16 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE-BSD-3-Clause file.
|
// found in the LICENSE-BSD-3-Clause file.
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
use std::sync::{Arc, Barrier};
|
use std::sync::{Arc, Barrier};
|
||||||
use std::{io, result};
|
use std::{io, result};
|
||||||
|
use versionize::{VersionMap, Versionize, VersionizeResult};
|
||||||
|
use versionize_derive::Versionize;
|
||||||
use vm_device::interrupt::InterruptSourceGroup;
|
use vm_device::interrupt::InterruptSourceGroup;
|
||||||
use vm_device::BusDevice;
|
use vm_device::BusDevice;
|
||||||
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
|
use vm_migration::{
|
||||||
|
Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable, VersionMapped,
|
||||||
|
};
|
||||||
use vmm_sys_util::errno::Result;
|
use vmm_sys_util::errno::Result;
|
||||||
|
|
||||||
const LOOP_SIZE: usize = 0x40;
|
const LOOP_SIZE: usize = 0x40;
|
||||||
@@ -71,7 +74,7 @@ pub struct Serial {
|
|||||||
out: Option<Box<dyn io::Write + Send>>,
|
out: Option<Box<dyn io::Write + Send>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Versionize)]
|
||||||
pub struct SerialState {
|
pub struct SerialState {
|
||||||
interrupt_enable: u8,
|
interrupt_enable: u8,
|
||||||
interrupt_identification: u8,
|
interrupt_identification: u8,
|
||||||
@@ -83,6 +86,7 @@ pub struct SerialState {
|
|||||||
baud_divisor: u16,
|
baud_divisor: u16,
|
||||||
in_buffer: Vec<u8>,
|
in_buffer: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
impl VersionMapped for SerialState {}
|
||||||
|
|
||||||
impl Serial {
|
impl Serial {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
@@ -162,8 +166,8 @@ impl Serial {
|
|||||||
Self::new(id, interrupt, None, state)
|
Self::new(id, interrupt, None, state)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_out(&mut self, out: Option<Box<dyn io::Write + Send>>) {
|
pub fn set_out(&mut self, out: Box<dyn io::Write + Send>) {
|
||||||
self.out = out;
|
self.out = Some(out);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Queues raw bytes for the guest to read and signals the interrupt if the line status would
|
/// Queues raw bytes for the guest to read and signals the interrupt if the line status would
|
||||||
@@ -330,7 +334,7 @@ impl Snapshottable for Serial {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
|
fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
|
||||||
Snapshot::new_from_state(&self.state())
|
Snapshot::new_from_versioned_state(&self.state())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -341,7 +345,8 @@ impl Migratable for Serial {}
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use std::sync::Mutex;
|
use std::io;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
use vm_device::interrupt::{InterruptIndex, InterruptSourceConfig};
|
use vm_device::interrupt::{InterruptIndex, InterruptSourceConfig};
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
@@ -360,13 +365,9 @@ mod tests {
|
|||||||
_index: InterruptIndex,
|
_index: InterruptIndex,
|
||||||
_config: InterruptSourceConfig,
|
_config: InterruptSourceConfig,
|
||||||
_masked: bool,
|
_masked: bool,
|
||||||
_set_gsi: bool,
|
|
||||||
) -> result::Result<(), std::io::Error> {
|
) -> result::Result<(), std::io::Error> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
fn set_gsi(&self) -> result::Result<(), std::io::Error> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
fn notifier(&self, _index: InterruptIndex) -> Option<EventFd> {
|
fn notifier(&self, _index: InterruptIndex) -> Option<EventFd> {
|
||||||
Some(self.event_fd.try_clone().unwrap())
|
Some(self.event_fd.try_clone().unwrap())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,15 +7,18 @@
|
|||||||
//!
|
//!
|
||||||
|
|
||||||
use crate::{read_le_u32, write_le_u32};
|
use crate::{read_le_u32, write_le_u32};
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
|
use std::fmt;
|
||||||
use std::sync::{Arc, Barrier};
|
use std::sync::{Arc, Barrier};
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
use std::{io, result};
|
use std::{io, result};
|
||||||
use thiserror::Error;
|
use versionize::{VersionMap, Versionize, VersionizeResult};
|
||||||
|
use versionize_derive::Versionize;
|
||||||
use vm_device::interrupt::InterruptSourceGroup;
|
use vm_device::interrupt::InterruptSourceGroup;
|
||||||
use vm_device::BusDevice;
|
use vm_device::BusDevice;
|
||||||
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
|
use vm_migration::{
|
||||||
|
Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable, VersionMapped,
|
||||||
|
};
|
||||||
|
|
||||||
/* Registers */
|
/* Registers */
|
||||||
const UARTDR: u64 = 0;
|
const UARTDR: u64 = 0;
|
||||||
@@ -45,20 +48,27 @@ const PL011_ID: [u8; 8] = [0x11, 0x10, 0x14, 0x00, 0x0d, 0xf0, 0x05, 0xb1];
|
|||||||
const AMBA_ID_LOW: u64 = 0x3f8;
|
const AMBA_ID_LOW: u64 = 0x3f8;
|
||||||
const AMBA_ID_HIGH: u64 = 0x401;
|
const AMBA_ID_HIGH: u64 = 0x401;
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[error("pl011_write: Bad Write Offset: {0}")]
|
|
||||||
BadWriteOffset(u64),
|
BadWriteOffset(u64),
|
||||||
#[error("pl011: DMA not implemented.")]
|
|
||||||
DmaNotImplemented,
|
DmaNotImplemented,
|
||||||
#[error("Failed to trigger interrupt: {0}")]
|
|
||||||
InterruptFailure(io::Error),
|
InterruptFailure(io::Error),
|
||||||
#[error("Failed to write: {0}")]
|
|
||||||
WriteAllFailure(io::Error),
|
WriteAllFailure(io::Error),
|
||||||
#[error("Failed to flush: {0}")]
|
|
||||||
FlushFailure(io::Error),
|
FlushFailure(io::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Error {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Error::BadWriteOffset(offset) => write!(f, "pl011_write: Bad Write Offset: {offset}"),
|
||||||
|
Error::DmaNotImplemented => write!(f, "pl011: DMA not implemented."),
|
||||||
|
Error::InterruptFailure(e) => write!(f, "Failed to trigger interrupt: {e}"),
|
||||||
|
Error::WriteAllFailure(e) => write!(f, "Failed to write: {e}"),
|
||||||
|
Error::FlushFailure(e) => write!(f, "Failed to flush: {e}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type Result<T> = result::Result<T, Error>;
|
type Result<T> = result::Result<T, Error>;
|
||||||
|
|
||||||
/// A PL011 device following the PL011 specification.
|
/// A PL011 device following the PL011 specification.
|
||||||
@@ -84,7 +94,7 @@ pub struct Pl011 {
|
|||||||
timestamp: std::time::Instant,
|
timestamp: std::time::Instant,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Versionize)]
|
||||||
pub struct Pl011State {
|
pub struct Pl011State {
|
||||||
flags: u32,
|
flags: u32,
|
||||||
lcr: u32,
|
lcr: u32,
|
||||||
@@ -103,6 +113,8 @@ pub struct Pl011State {
|
|||||||
read_trigger: u32,
|
read_trigger: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl VersionMapped for Pl011State {}
|
||||||
|
|
||||||
impl Pl011 {
|
impl Pl011 {
|
||||||
/// Constructs an AMBA PL011 UART device.
|
/// Constructs an AMBA PL011 UART device.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
@@ -189,8 +201,8 @@ impl Pl011 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_out(&mut self, out: Option<Box<dyn io::Write + Send>>) {
|
pub fn set_out(&mut self, out: Box<dyn io::Write + Send>) {
|
||||||
self.out = out;
|
self.out = Some(out);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn state(&self) -> Pl011State {
|
fn state(&self) -> Pl011State {
|
||||||
@@ -442,7 +454,7 @@ impl Snapshottable for Pl011 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
|
fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
|
||||||
Snapshot::new_from_state(&self.state())
|
Snapshot::new_from_versioned_state(&self.state())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -453,7 +465,8 @@ impl Migratable for Pl011 {}
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use std::sync::Mutex;
|
use std::io;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
use vm_device::interrupt::{InterruptIndex, InterruptSourceConfig};
|
use vm_device::interrupt::{InterruptIndex, InterruptSourceConfig};
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
@@ -472,13 +485,9 @@ mod tests {
|
|||||||
_index: InterruptIndex,
|
_index: InterruptIndex,
|
||||||
_config: InterruptSourceConfig,
|
_config: InterruptSourceConfig,
|
||||||
_masked: bool,
|
_masked: bool,
|
||||||
_set_gsi: bool,
|
|
||||||
) -> result::Result<(), std::io::Error> {
|
) -> result::Result<(), std::io::Error> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
fn set_gsi(&self) -> result::Result<(), std::io::Error> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
fn notifier(&self, _index: InterruptIndex) -> Option<EventFd> {
|
fn notifier(&self, _index: InterruptIndex) -> Option<EventFd> {
|
||||||
Some(self.event_fd.try_clone().unwrap())
|
Some(self.event_fd.try_clone().unwrap())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,24 +10,18 @@
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate bitflags;
|
extern crate bitflags;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate event_monitor;
|
|
||||||
#[macro_use]
|
|
||||||
extern crate log;
|
extern crate log;
|
||||||
|
|
||||||
pub mod acpi;
|
pub mod acpi;
|
||||||
#[cfg(target_arch = "x86_64")]
|
|
||||||
pub mod debug_console;
|
|
||||||
#[cfg(target_arch = "aarch64")]
|
#[cfg(target_arch = "aarch64")]
|
||||||
pub mod gic;
|
pub mod gic;
|
||||||
pub mod interrupt_controller;
|
pub mod interrupt_controller;
|
||||||
#[cfg(target_arch = "x86_64")]
|
#[cfg(target_arch = "x86_64")]
|
||||||
pub mod ioapic;
|
pub mod ioapic;
|
||||||
pub mod legacy;
|
pub mod legacy;
|
||||||
pub mod pvpanic;
|
|
||||||
pub mod tpm;
|
pub mod tpm;
|
||||||
|
|
||||||
pub use self::acpi::{AcpiGedDevice, AcpiPmTimerDevice, AcpiShutdownDevice};
|
pub use self::acpi::{AcpiGedDevice, AcpiPmTimerDevice, AcpiShutdownDevice};
|
||||||
pub use self::pvpanic::{PvPanicDevice, PVPANIC_DEVICE_MMIO_SIZE};
|
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
pub struct AcpiNotificationFlags: u8 {
|
pub struct AcpiNotificationFlags: u8 {
|
||||||
|
|||||||
@@ -1,266 +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 serde::{Deserialize, Serialize};
|
|
||||||
use std::any::Any;
|
|
||||||
use std::result;
|
|
||||||
use std::sync::{Arc, Barrier, Mutex};
|
|
||||||
use thiserror::Error;
|
|
||||||
use vm_allocator::{AddressAllocator, SystemAllocator};
|
|
||||||
use vm_device::{BusDevice, Resource};
|
|
||||||
use vm_memory::{Address, GuestAddress};
|
|
||||||
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
|
|
||||||
|
|
||||||
const PVPANIC_VENDOR_ID: u16 = 0x1b36;
|
|
||||||
const PVPANIC_DEVICE_ID: u16 = 0x0011;
|
|
||||||
|
|
||||||
pub const PVPANIC_DEVICE_MMIO_SIZE: u64 = 0x2;
|
|
||||||
pub const PVPANIC_DEVICE_MMIO_ALIGNMENT: u64 = 0x10;
|
|
||||||
|
|
||||||
const PVPANIC_PANICKED: u8 = 1 << 0;
|
|
||||||
const PVPANIC_CRASH_LOADED: u8 = 1 << 1;
|
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
|
||||||
pub enum PvPanicError {
|
|
||||||
#[error("Failed creating PvPanicDevice: {0}")]
|
|
||||||
CreatePvPanicDevice(#[source] anyhow::Error),
|
|
||||||
#[error("Failed to retrieve PciConfigurationState: {0}")]
|
|
||||||
RetrievePciConfigurationState(#[source] anyhow::Error),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
|
||||||
enum PvPanicSubclass {
|
|
||||||
Other = 0x80,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PciSubclass for PvPanicSubclass {
|
|
||||||
fn get_register_value(&self) -> u8 {
|
|
||||||
*self as u8
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A device for handling guest panic event
|
|
||||||
pub struct PvPanicDevice {
|
|
||||||
id: String,
|
|
||||||
events: u8,
|
|
||||||
|
|
||||||
// PCI configuration registers.
|
|
||||||
configuration: PciConfiguration,
|
|
||||||
bar_regions: Vec<PciBarConfiguration>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
pub struct PvPanicDeviceState {
|
|
||||||
events: u8,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PvPanicDevice {
|
|
||||||
pub fn new(id: String, snapshot: Option<Snapshot>) -> Result<Self, PvPanicError> {
|
|
||||||
let pci_configuration_state =
|
|
||||||
vm_migration::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_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>>,
|
|
||||||
mmio32_allocator: &mut AddressAllocator,
|
|
||||||
_mmio64_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 = mmio32_allocator
|
|
||||||
.allocate(None, region_size, Some(PVPANIC_DEVICE_MMIO_ALIGNMENT))
|
|
||||||
.ok_or(PciDeviceError::IoAllocationFailed(region_size))?;
|
|
||||||
|
|
||||||
let bar = PciBarConfiguration::default()
|
|
||||||
.set_index(bar_id as usize)
|
|
||||||
.set_address(bar_addr.raw_value())
|
|
||||||
.set_size(region_size)
|
|
||||||
.set_region_type(region_type)
|
|
||||||
.set_prefetchable(PciBarPrefetchable::NotPrefetchable);
|
|
||||||
|
|
||||||
debug!("pvpanic bar address 0x{:x}", bar_addr.0);
|
|
||||||
if !restoring {
|
|
||||||
self.configuration
|
|
||||||
.add_pci_bar(&bar)
|
|
||||||
.map_err(|e| PciDeviceError::IoRegistrationFailed(bar_addr.raw_value(), e))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
bars.push(bar);
|
|
||||||
self.bar_regions.clone_from(&bars);
|
|
||||||
|
|
||||||
Ok(bars)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn free_bars(
|
|
||||||
&mut self,
|
|
||||||
_allocator: &mut SystemAllocator,
|
|
||||||
mmio32_allocator: &mut AddressAllocator,
|
|
||||||
_mmio64_allocator: &mut AddressAllocator,
|
|
||||||
) -> std::result::Result<(), PciDeviceError> {
|
|
||||||
for bar in self.bar_regions.drain(..) {
|
|
||||||
mmio32_allocator.free(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_state(&self.state())?;
|
|
||||||
|
|
||||||
// Snapshot PciConfiguration
|
|
||||||
snapshot.add_snapshot(self.configuration.id(), self.configuration.snapshot()?);
|
|
||||||
|
|
||||||
Ok(snapshot)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Transportable for PvPanicDevice {}
|
|
||||||
impl Migratable for PvPanicDevice {}
|
|
||||||
@@ -450,7 +450,7 @@ impl BusDevice for Tpm {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
error!("Invalid value passed to CTRL_REQ register");
|
error!("Invalid value passed to CRTL_REQ register");
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
All documentations (e.g. files with extension `.md`) in this repository is
|
The documentation in this directory is covered by the following license:
|
||||||
covered by the following license:
|
|
||||||
|
|
||||||
Attribution 4.0 International
|
Attribution 4.0 International
|
||||||
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
# AMD SEV-SNP
|
|
||||||
|
|
||||||
### WARNING
|
|
||||||
This feature is only currently supported on MSHV.
|
|
||||||
|
|
||||||
AMD Secure Encrypted Virtualization & Secure Nested Paging (SEV-SNP) is an AMD
|
|
||||||
technology designed to add strong memory integrity protection to help prevent
|
|
||||||
malicious hypervisor-based attacks like data replay, memory-remapping and more
|
|
||||||
in order to create an isolated execution environment. Here are some useful
|
|
||||||
links:
|
|
||||||
|
|
||||||
* [SNP Homepage] (https://www.amd.com/en/processors/amd-secure-encrypted-virtualization)
|
|
||||||
more information about SEV-SNP technical aspects, design and specification.
|
|
||||||
|
|
||||||
## Cloud Hypervisor support
|
|
||||||
|
|
||||||
It is required to use a machine which has enabled support for AMD SEV-SNP in
|
|
||||||
the BIOS.
|
|
||||||
|
|
||||||
On the Cloud Hypervisor side, all you need is to build the project with the
|
|
||||||
`sev_snp` feature enabled:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo build --no-default-features --features "sev_snp"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Note**
|
|
||||||
Please note that `sev_snp` cannot be enabled in conjunction with `tdx` feature flag.
|
|
||||||
|
|
||||||
You can run a SEV-SNP VM using the following command:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./cloud-hypervisor \
|
|
||||||
--platform sev_snp=on \
|
|
||||||
--cpus boot=1 \
|
|
||||||
--memory size=1G \
|
|
||||||
--disk path=ubuntu.img
|
|
||||||
```
|
|
||||||
|
|
||||||
For more information related to Microsoft Hypervisor please see [mshv.md](mshv.md)
|
|
||||||
195
docs/api.md
195
docs/api.md
@@ -1,21 +1,18 @@
|
|||||||
- [Cloud Hypervisor API](#cloud-hypervisor-api)
|
- [Cloud Hypervisor API](#cloud-hypervisor-api)
|
||||||
- [External API](#external-api)
|
- [External API](#external-api)
|
||||||
- [REST API](#rest-api)
|
- [REST API](#rest-api)
|
||||||
- [REST API Location and availability](#rest-api-location-and-availability)
|
- [Location and availability](#location-and-availability)
|
||||||
- [REST API Endpoints](#rest-api-endpoints)
|
- [Endpoints](#endpoints)
|
||||||
- [Virtual Machine Manager (VMM) Actions](#virtual-machine-manager-vmm-actions)
|
- [Virtual Machine Manager (VMM) Actions](#virtual-machine-manager-vmm-actions)
|
||||||
- [Virtual Machine (VM) Actions](#virtual-machine-vm-actions)
|
- [Virtual Machine (VM) Actions](#virtual-machine-vm-actions)
|
||||||
- [REST API Examples](#rest-api-examples)
|
- [REST API Examples](#rest-api-examples)
|
||||||
- [Create a Virtual Machine](#create-a-virtual-machine)
|
- [Create a Virtual Machine](#create-a-virtual-machine)
|
||||||
- [Boot a Virtual Machine](#boot-a-virtual-machine)
|
- [Boot a Virtual Machine](#boot-a-virtual-machine)
|
||||||
- [Dump a Virtual Machine Information](#dump-a-virtual-machine-information)
|
- [Dump a Virtual Machine Information](#dump-a-virtual-machine-information)
|
||||||
- [Reboot a Virtual Machine](#reboot-a-virtual-machine)
|
- [Reboot a Virtual Machine](#reboot-a-virtual-machine)
|
||||||
- [Shut a Virtual Machine Down](#shut-a-virtual-machine-down)
|
- [Shut a Virtual Machine Down](#shut-a-virtual-machine-down)
|
||||||
- [D-Bus API](#d-bus-api)
|
|
||||||
- [D-Bus API Location and availability](#d-bus-api-location-and-availability)
|
|
||||||
- [D-Bus API Interface](#d-bus-api-interface)
|
|
||||||
- [Command Line Interface](#command-line-interface)
|
- [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)
|
- [Internal API](#internal-api)
|
||||||
- [Goals and Design](#goals-and-design)
|
- [Goals and Design](#goals-and-design)
|
||||||
- [End to End Example](#end-to-end-example)
|
- [End to End Example](#end-to-end-example)
|
||||||
@@ -24,11 +21,9 @@
|
|||||||
|
|
||||||
The Cloud Hypervisor API is made of 2 distinct interfaces:
|
The Cloud Hypervisor API is made of 2 distinct interfaces:
|
||||||
|
|
||||||
1. **The External API** This is the user facing API. Users and operators
|
1. **The external API**. This is the user facing API. Users and operators can
|
||||||
can control and manage the Cloud Hypervisor through various options
|
control and manage Cloud Hypervisor through either a REST API or a Command
|
||||||
including a REST API, a Command Line Interface (CLI) or a D-Bus based API,
|
Line Interface (CLI).
|
||||||
which is not compiled into Cloud Hypervisor by default.
|
|
||||||
|
|
||||||
1. **The internal API**, based on [rust's Multi-Producer, Single-Consumer (MPSC)](https://doc.rust-lang.org/std/sync/mpsc/)
|
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
|
module. This API is used internally by the Cloud Hypervisor threads to
|
||||||
communicate between each others.
|
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.
|
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)
|
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)
|
compliant. Please consult the [Cloud Hypervisor API](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.
|
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,
|
The REST API is available as soon as the Cloud Hypervisor binary is started,
|
||||||
through a local UNIX socket.
|
through a local UNIX socket.
|
||||||
@@ -70,18 +65,18 @@ Cloud Hypervisor Guest
|
|||||||
Disk(s): None
|
Disk(s): None
|
||||||
```
|
```
|
||||||
|
|
||||||
#### REST API Endpoints
|
### Endpoints
|
||||||
|
|
||||||
The Cloud Hypervisor API exposes the following actions through its 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 |
|
| Action | Endpoint | Request Body | Response Body | Prerequisites |
|
||||||
| ----------------------------------- | --------------- | ------------ | -------------------------- | ------------------ |
|
| ----------------------------------- | --------------- | ------------ | -------------------------- | ------------------ |
|
||||||
| Check for the REST API availability | `/vmm.ping` | N/A | `/schemas/VmmPingResponse` | N/A |
|
| 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 |
|
| 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 |
|
| Action | Endpoint | Request Body | Response Body | Prerequisites |
|
||||||
| ---------------------------------- | ----------------------- | ------------------------------- | ------------------------ | ------------------------------------------------------ |
|
| ---------------------------------- | ----------------------- | ------------------------------- | ------------------------ | ------------------------------------------------------ |
|
||||||
@@ -110,16 +105,15 @@ The Cloud Hypervisor API exposes the following actions through its endpoints:
|
|||||||
| Add vsock device to the VM | `/vm.add-vsock` | `/schemas/VsockConfig` | `/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 |
|
| 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 |
|
| Dump the VM counters | `/vm.counters` | N/A | `/schemas/VmCounters` | The VM is booted |
|
||||||
| Inject an NMI | `/vm.nmi` | N/A | N/A | The VM is booted |
|
|
||||||
| Prepare to receive a migration | `/vm.receive-migration` | `/schemas/ReceiveMigrationData` | N/A | N/A |
|
| 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) |
|
| Start to send migration to target | `/vm.send-migration` | `/schemas/SendMigrationData` | N/A | The VM is booted and (shared mem or hugepages enabled) |
|
||||||
|
|
||||||
* The `vmcoredump` action is available exclusively for the `x86_64`
|
* The `vmcoredump` action is available exclusively for the `x86_64`
|
||||||
architecture and can be executed only when the `guest_debug` feature is
|
architecture and can be executed only when the `guest_debug` feature is
|
||||||
enabled. Without this feature, the corresponding [REST API](#rest-api) or
|
enabled. Without this feature, the corresponding REST API endpoint is not
|
||||||
[D-Bus API](#d-bus-api) endpoints are not available.
|
available.
|
||||||
|
|
||||||
#### REST API Examples
|
### REST API Examples
|
||||||
|
|
||||||
For the following set of examples, we assume Cloud Hypervisor is started with
|
For the following set of examples, we assume Cloud Hypervisor is started with
|
||||||
the REST API available at `/tmp/cloud-hypervisor.sock`:
|
the REST API available at `/tmp/cloud-hypervisor.sock`:
|
||||||
@@ -135,7 +129,7 @@ Cloud Hypervisor Guest
|
|||||||
Disk(s): None
|
Disk(s): None
|
||||||
```
|
```
|
||||||
|
|
||||||
##### Create a Virtual Machine
|
#### Create a Virtual Machine
|
||||||
|
|
||||||
We want to create a virtual machine with the following characteristics:
|
We want to create a virtual machine with the following characteristics:
|
||||||
|
|
||||||
@@ -148,7 +142,7 @@ We want to create a virtual machine with the following characteristics:
|
|||||||
`/opt/clh/images/focal-server-cloudimg-amd64.raw`
|
`/opt/clh/images/focal-server-cloudimg-amd64.raw`
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
#!/usr/bin/env bash
|
#!/bin/bash
|
||||||
|
|
||||||
curl --unix-socket /tmp/cloud-hypervisor.sock -i \
|
curl --unix-socket /tmp/cloud-hypervisor.sock -i \
|
||||||
-X PUT 'http://localhost/api/v1/vm.create' \
|
-X PUT 'http://localhost/api/v1/vm.create' \
|
||||||
@@ -163,140 +157,74 @@ 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:
|
Once the VM is created, we can boot it:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
#!/usr/bin/env bash
|
#!/bin/bash
|
||||||
|
|
||||||
curl --unix-socket /tmp/cloud-hypervisor.sock -i -X PUT 'http://localhost/api/v1/vm.boot'
|
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:
|
We can fetch information about any VM, as soon as it's created:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
#!/usr/bin/env bash
|
#!/bin/bash
|
||||||
|
|
||||||
curl --unix-socket /tmp/cloud-hypervisor.sock -i \
|
curl --unix-socket /tmp/cloud-hypervisor.sock -i \
|
||||||
-X GET 'http://localhost/api/v1/vm.info' \
|
-X GET 'http://localhost/api/v1/vm.info' \
|
||||||
-H 'Accept: application/json'
|
-H 'Accept: application/json'
|
||||||
```
|
```
|
||||||
|
|
||||||
##### Reboot a Virtual Machine
|
#### Reboot a Virtual Machine
|
||||||
|
|
||||||
We can reboot a VM that's already booted:
|
We can reboot a VM that's already booted:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
#!/usr/bin/env bash
|
#!/bin/bash
|
||||||
|
|
||||||
curl --unix-socket /tmp/cloud-hypervisor.sock -i -X PUT 'http://localhost/api/v1/vm.reboot'
|
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:
|
Once booted, we can shut a VM down from the REST API:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
#!/usr/bin/env bash
|
#!/bin/bash
|
||||||
|
|
||||||
curl --unix-socket /tmp/cloud-hypervisor.sock -i -X PUT 'http://localhost/api/v1/vm.shutdown'
|
curl --unix-socket /tmp/cloud-hypervisor.sock -i -X PUT 'http://localhost/api/v1/vm.shutdown'
|
||||||
```
|
```
|
||||||
|
|
||||||
### D-Bus API
|
|
||||||
|
|
||||||
Cloud Hypervisor offers a D-Bus API as an alternative to its REST API. This
|
|
||||||
D-Bus API fully reflects the functionality of the REST API, exposing the
|
|
||||||
same group of endpoints. It can be a drop-in replacement since it also
|
|
||||||
consumes/produces JSON.
|
|
||||||
|
|
||||||
In addition, the D-Bus API also exposes events from `event-monitor` in the
|
|
||||||
form of a D-Bus signal to which users can subscribe. For more information,
|
|
||||||
see [D-Bus API Interface](#d-bus-api-interface).
|
|
||||||
|
|
||||||
#### D-Bus API Location and availability
|
|
||||||
|
|
||||||
This feature is not compiled into Cloud Hypervisor by default. Users who
|
|
||||||
wish to use the D-Bus API, must explicitly enable it with the `dbus_api`
|
|
||||||
feature flag when compiling Cloud Hypervisor.
|
|
||||||
|
|
||||||
```sh
|
|
||||||
$ ./scripts/dev_cli.sh build --release --libc musl -- --features dbus_api
|
|
||||||
```
|
|
||||||
|
|
||||||
Once this feature is enabled, it can be configured with the following
|
|
||||||
CLI options:
|
|
||||||
|
|
||||||
```
|
|
||||||
--dbus-service-name
|
|
||||||
well known name of the service
|
|
||||||
--dbus-object-path
|
|
||||||
object path to serve the dbus interface
|
|
||||||
--dbus-system-bus use the system bus instead of a session bus
|
|
||||||
```
|
|
||||||
|
|
||||||
Example invocation:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
$ ./cloud-hypervisor --dbus-service-name "org.cloudhypervisor.DBusApi" \
|
|
||||||
--dbus-object-path "/org/cloudhypervisor/DBusApi"
|
|
||||||
```
|
|
||||||
|
|
||||||
This will start serving a service with the name `org.cloudhypervisor.DBusApi1`
|
|
||||||
which in turn can be used to control and manage Cloud Hypervisor.
|
|
||||||
|
|
||||||
#### D-Bus API Interface
|
|
||||||
|
|
||||||
Please refer to the [REST API](#rest-api) documentation for everything that
|
|
||||||
is in common with the REST API. As previously mentioned, the D-Bus API can
|
|
||||||
be used as a drop-in replacement for the [REST API](#rest-api).
|
|
||||||
|
|
||||||
The D-Bus interface also exposes a signal, named `Event`, which is emitted
|
|
||||||
whenever a new event is published from the `event-monitor` crate. Here is its
|
|
||||||
definition in XML format:
|
|
||||||
|
|
||||||
```xml
|
|
||||||
<node>
|
|
||||||
<interface name="org.cloudhypervisor.DBusApi1">
|
|
||||||
<signal name="Event">
|
|
||||||
<arg name="event" type="s"/>
|
|
||||||
</signal>
|
|
||||||
</interface>
|
|
||||||
</node>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Command Line Interface
|
### Command Line Interface
|
||||||
|
|
||||||
The Cloud Hypervisor Command Line Interface (CLI) can only be used for launching
|
The Cloud Hypervisor Command Line Interface (CLI) can only be used for launching
|
||||||
the Cloud Hypervisor binary, i.e. it cannot be used for controlling the VMM or
|
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.
|
the launched VM once they're up and running.
|
||||||
|
|
||||||
If you want to inspect the VMM, or control the VM after launching Cloud
|
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)
|
Hypervisor from the CLI, you must use the [REST API](#rest-api).
|
||||||
or the [D-Bus API](#d-bus-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
|
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
|
the VM config. Run `cloud-hypervisor --help` for a complete list of CLI
|
||||||
options. As soon as the `cloud-hypervisor` binary is launched, contrary
|
options. As soon as the `cloud-hypervisor` binary is launched, the
|
||||||
to the [D-Bus API](#d-bus-api), the [REST API](#rest-api) is available
|
[REST API](#rest-api) is available for controlling and managing the VM.
|
||||||
for controlling and managing the VM. The [D-Bus API](#d-bus-api) doesn't start
|
1. Start the [REST API](#rest-api) server only, by not passing any VM
|
||||||
automatically and needs to be explicitly configured in order to be run.
|
configuration options. The VM can then be asynchronously created and booted
|
||||||
1. Start either the REST API, D-Bus API or both simultaneously without passing
|
by sending HTTP commands to the [REST API](#rest-api). Check the
|
||||||
any VM configuration options. The VM can then be asynchronously created and
|
[REST API examples](#rest-api-examples) section for more details.
|
||||||
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.
|
|
||||||
|
|
||||||
### 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
|
The CLI options are parsed by the
|
||||||
[clap crate](https://docs.rs/clap/4.3.11/clap/) and then translated into
|
[argh crate](https://docs.rs/argh/latest/argh/) and then translated into
|
||||||
[internal API](#internal-api) commands.
|
[internal API](#internal-api) commands.
|
||||||
|
|
||||||
The REST API is processed by an HTTP thread using the
|
The REST API is processed by an HTTP thread using the
|
||||||
@@ -304,11 +232,7 @@ The REST API is processed by an HTTP thread using the
|
|||||||
crate. As with the CLI, the HTTP requests eventually get translated into
|
crate. As with the CLI, the HTTP requests eventually get translated into
|
||||||
[internal API](#internal-api) commands.
|
[internal API](#internal-api) commands.
|
||||||
|
|
||||||
The D-Bus API is implemented using the [zbus](https://github.com/dbus2/zbus)
|
As a summary, the REST API and the CLI are essentially frontends for the
|
||||||
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
|
|
||||||
[internal API](#internal-api):
|
[internal API](#internal-api):
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -319,16 +243,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
|
| +----------+ | VMM
|
||||||
| CLI | | |
|
| CLI | | |
|
||||||
+----------->+ clap +--------------+
|
+----------->+ argh +--------------+
|
||||||
| |
|
| |
|
||||||
+----------+
|
+----------+
|
||||||
|
|
||||||
@@ -338,23 +262,22 @@ As a summary, the REST API, the D-Bus API and the CLI are essentially frontends
|
|||||||
## Internal API
|
## Internal API
|
||||||
|
|
||||||
The Cloud Hypervisor internal API, as its name suggests, is used internally
|
The Cloud Hypervisor internal API, as its name suggests, is used internally
|
||||||
by the different Cloud Hypervisor threads (VMM, HTTP, D-Bus, control loop,
|
by the different Cloud Hypervisor threads (VMM, HTTP, control loop, etc) to
|
||||||
etc) to send commands and responses to each others.
|
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/),
|
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
|
and the single consumer (a.k.a. the API receiver) is the Cloud Hypervisor
|
||||||
control loop.
|
control loop.
|
||||||
|
|
||||||
API producers are the HTTP thread handling the [REST API](#rest-api), the
|
API producers are the HTTP thread handling the [REST API](#rest-api) and the
|
||||||
D-Bus thread handling the [D-Bus API](#d-bus-api) and the main thread that
|
main thread that initially parses the [CLI](#command-line-interface).
|
||||||
initially parses the [CLI](#command-line-interface).
|
|
||||||
|
|
||||||
### Goals and Design
|
### Goals and Design
|
||||||
|
|
||||||
The internal API is designed for controlling, managing and inspecting a Cloud
|
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
|
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)
|
visible requests through either the [REST API](#rest-api) or the
|
||||||
or the [CLI](#command-line-interface) interfaces.
|
[CLI](#command-line-interface) interfaces.
|
||||||
|
|
||||||
The API follows a command-response scheme that closely maps the [REST API](#rest-api).
|
The API follows a command-response scheme that closely maps the [REST API](#rest-api).
|
||||||
Any command must be replied to with a response.
|
Any command must be replied to with a response.
|
||||||
@@ -386,7 +309,7 @@ APIs work together, let's look at a complete VM creation flow, from the
|
|||||||
[REST API](#rest-api) in order to creates a virtual machine:
|
[REST API](#rest-api) in order to creates a virtual machine:
|
||||||
```
|
```
|
||||||
shell
|
shell
|
||||||
#!/usr/bin/env bash
|
#!/bin/bash
|
||||||
|
|
||||||
curl --unix-socket /tmp/cloud-hypervisor.sock -i \
|
curl --unix-socket /tmp/cloud-hypervisor.sock -i \
|
||||||
-X PUT 'http://localhost/api/v1/vm.create' \
|
-X PUT 'http://localhost/api/v1/vm.create' \
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ Value is a boolean set to `off` by default.
|
|||||||
_Example_
|
_Example_
|
||||||
|
|
||||||
```
|
```
|
||||||
--balloon size=2G,deflate_on_oom=on
|
--ballloon size=2G,deflate_on_oom=on
|
||||||
```
|
```
|
||||||
|
|
||||||
### `free_page_reporting`
|
### `free_page_reporting`
|
||||||
@@ -72,5 +72,5 @@ Value is a boolean set to `off` by default.
|
|||||||
_Example_
|
_Example_
|
||||||
|
|
||||||
```
|
```
|
||||||
--balloon size=0,free_page_reporting=on
|
--ballloon size=0,free_page_reporting=on
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1,81 +0,0 @@
|
|||||||
# Code coverage
|
|
||||||
|
|
||||||
LLVM provides a set of tools to collect code coverage data and present the data
|
|
||||||
in human-consumable forms.
|
|
||||||
|
|
||||||
## Building a suitable binary
|
|
||||||
|
|
||||||
The compiler flag to generate code coverage data has been stabilized since Rust
|
|
||||||
1.60.
|
|
||||||
|
|
||||||
An instrumented binary can be built with the following command:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
cargo clean && RUSTFLAGS='-C instrument-coverage' cargo build
|
|
||||||
```
|
|
||||||
|
|
||||||
Using either `debug` or `release` profile is fine. You will need to adjust
|
|
||||||
the path for some commands.
|
|
||||||
|
|
||||||
## Running the binary
|
|
||||||
|
|
||||||
Run the binary as you normally would. When the process exits, you will see
|
|
||||||
files with the prefix `profraw`.
|
|
||||||
|
|
||||||
Multiple runs of the same binary will produce multiple `profraw` files.
|
|
||||||
|
|
||||||
The more diverse the runs are, the better. Try to exercise different features
|
|
||||||
as much as possible.
|
|
||||||
|
|
||||||
## Combining raw data
|
|
||||||
|
|
||||||
Raw data files can be combined with `llvm-profdata`.
|
|
||||||
|
|
||||||
```shell
|
|
||||||
rustup component add llvm-tools-preview
|
|
||||||
# Assuming profraw files reside in the current directory and its children directories
|
|
||||||
find . -name '*.profraw' -exec llvm-profdata merge -sparse {} -o coverage.profdata \;
|
|
||||||
```
|
|
||||||
|
|
||||||
A file named `coverage.profdata` will be generated.
|
|
||||||
|
|
||||||
## Generating HTML files for human consumption
|
|
||||||
|
|
||||||
This can be done either with LLVM or `grcov`.
|
|
||||||
|
|
||||||
Here is an example using grcov.
|
|
||||||
|
|
||||||
```shell
|
|
||||||
cargo install grcov
|
|
||||||
# Assuming the profdata file is in the top level directory of the Cloud Hypervisor repository
|
|
||||||
grcov . --binary-path ./target/x86_64-unknown-linux-gnu/release -s . -t html --branch --ignore-not-existing -o coverage-html-output/
|
|
||||||
```
|
|
||||||
|
|
||||||
You can then open the `index.html` file under coverage-html-output to see the
|
|
||||||
results.
|
|
||||||
|
|
||||||
## Notes on running the in-tree integration tests and unit tests
|
|
||||||
|
|
||||||
Please set RUSTFLAGS the same way while invoking `dev_cli.sh`. The script will
|
|
||||||
pass RUSTFLAGS to the container.
|
|
||||||
|
|
||||||
Since the `profraw` files are generated from within the container, the file
|
|
||||||
paths embedded in the data files are going to be different. It is easier to do
|
|
||||||
the data processing from within the container if you don't want to fight the
|
|
||||||
tool chain.
|
|
||||||
|
|
||||||
```shell
|
|
||||||
# Get a shell
|
|
||||||
./scripts/dev_cli.sh shell
|
|
||||||
|
|
||||||
# Install llvm-tools-preview for llvm-profdata
|
|
||||||
rustup component add llvm-tools-preview
|
|
||||||
# Merge data files by using the following command
|
|
||||||
find . -name '*.profraw' -exec `rustc --print sysroot`/lib/rustlib/x86_64-unknown-linux-gnu/bin/llvm-profdata merge -sparse {} -o coverage.profdata \;
|
|
||||||
|
|
||||||
# As of writing, the container has Rust 1.67.1. It is too old for grcov.
|
|
||||||
rustup install stable
|
|
||||||
cargo +stable install grcov
|
|
||||||
# Run grcov as usual
|
|
||||||
grcov . --binary-path ./target/x86_64-unknown-linux-gnu/release -s . -t html --branch --ignore-not-existing -o coverage-html-output/
|
|
||||||
```
|
|
||||||
@@ -159,10 +159,10 @@ as we might need to update the direct kernel boot command line, replacing
|
|||||||
|
|
||||||
Update all references to the previous image name to the new one.
|
Update all references to the previous image name to the new one.
|
||||||
|
|
||||||
## NVIDIA image for VFIO bare-metal CI
|
## NVIDIA image for VFIO baremetal CI
|
||||||
|
|
||||||
Here we are going to describe how to create a cloud image that contains the
|
Here we are going to describe how to create a cloud image that contains the
|
||||||
necessary NVIDIA drivers for our VFIO bare-metal CI.
|
necessary NVIDIA drivers for our VFIO baremetal CI.
|
||||||
|
|
||||||
### Download base image
|
### Download base image
|
||||||
|
|
||||||
@@ -170,7 +170,7 @@ We usually start from one of the custom cloud image we have previously created
|
|||||||
but we can use a stock cloud image as well.
|
but we can use a stock cloud image as well.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
wget https://ch-images.azureedge.net/jammy-server-cloudimg-amd64-custom-20230119-0.raw
|
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
|
mv jammy-server-cloudimg-amd64-custom-20230119-0.raw jammy-server-cloudimg-amd64-nvidia.raw
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -225,7 +225,7 @@ Number Start End Size File system Name Flags
|
|||||||
|
|
||||||
### Create a macvtap interface
|
### Create a macvtap interface
|
||||||
|
|
||||||
Rely on the following [documentation](macvtap-bridge.md) to set up a
|
Rely on the following [documentation](docs/macvtap-bridge.md) to set up a
|
||||||
macvtap interface to provide your VM with proper connectivity.
|
macvtap interface to provide your VM with proper connectivity.
|
||||||
|
|
||||||
### Boot the image
|
### Boot the image
|
||||||
@@ -326,4 +326,4 @@ VM will be booted with this image.
|
|||||||
|
|
||||||
```
|
```
|
||||||
sudo cloud-init clean
|
sudo cloud-init clean
|
||||||
```
|
```
|
||||||
@@ -1,16 +1,7 @@
|
|||||||
# `cloud-hypervisor` debug IO ports
|
# `cloud-hypervisor` debug IO port
|
||||||
|
|
||||||
When running x86 guests, `cloud-hypervisor` provides different kinds of debug ports:
|
`cloud-hypervisor` uses the [`0x80`](https://www.intel.com/content/www/us/en/support/articles/000005500/boards-and-kits.html)
|
||||||
- [`0x80` debug port](https://www.intel.com/content/www/us/en/support/articles/000005500/boards-and-kits.html)
|
I/O port to trace user defined guest events.
|
||||||
- Debug console (by default at `0xe9`).
|
|
||||||
- Firmware debug port at `0x402`.
|
|
||||||
|
|
||||||
All of them can be used to trace user-defined guest events and all of them can
|
|
||||||
be used simultaneously.
|
|
||||||
|
|
||||||
## Debug Ports Overview
|
|
||||||
|
|
||||||
### `0x80` I/O port
|
|
||||||
|
|
||||||
Whenever the guest write one byte between `0x0` and `0xF` on this particular
|
Whenever the guest write one byte between `0x0` and `0xF` on this particular
|
||||||
I/O port, `cloud-hypervisor` will log and timestamp that event at the `debug`
|
I/O port, `cloud-hypervisor` will log and timestamp that event at the `debug`
|
||||||
@@ -39,13 +30,13 @@ guest will have `cloud-hypervisor` generate timestamped logs of all those steps.
|
|||||||
That provides a basic but convenient way of measuring not only the overall guest
|
That provides a basic but convenient way of measuring not only the overall guest
|
||||||
boot time but all intermediate steps as well.
|
boot time but all intermediate steps as well.
|
||||||
|
|
||||||
#### Logging
|
## Logging
|
||||||
|
|
||||||
Assuming parts of the guest software stack have been instrumented to use the
|
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.
|
`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
|
To do so we need to start `cloud-hypervisor` with the right debug level
|
||||||
(`-vvv`). It is also recommended to have it log into a dedicated file in order
|
(`-v -v -v`). It is also recommended to have it log into a dedicated file in order
|
||||||
to easily grep for the tracing logs (e.g.
|
to easily grep for the tracing logs (e.g.
|
||||||
`--log-file /tmp/cloud-hypervisor.log`):
|
`--log-file /tmp/cloud-hypervisor.log`):
|
||||||
|
|
||||||
@@ -57,7 +48,7 @@ to easily grep for the tracing logs (e.g.
|
|||||||
--memory size=1024M \
|
--memory size=1024M \
|
||||||
--rng \
|
--rng \
|
||||||
--log-file /tmp/ch-fw.log \
|
--log-file /tmp/ch-fw.log \
|
||||||
-vvv
|
-v -v -v
|
||||||
```
|
```
|
||||||
|
|
||||||
After booting the guest, we then have to grep for the debug I/O port traces in
|
After booting the guest, we then have to grep for the debug I/O port traces in
|
||||||
@@ -68,29 +59,3 @@ $ grep "Debug I/O port" /tmp/ch-fw.log
|
|||||||
cloud-hypervisor: 19.762449ms: DEBUG:vmm/src/vm.rs:510 -- [Debug I/O port: Firmware code 0x0] 0.019004 seconds
|
cloud-hypervisor: 19.762449ms: DEBUG:vmm/src/vm.rs:510 -- [Debug I/O port: Firmware code 0x0] 0.019004 seconds
|
||||||
cloud-hypervisor: 403.499628ms: DEBUG:vmm/src/vm.rs:510 -- [Debug I/O port: Firmware code 0x1] 0.402744 seconds
|
cloud-hypervisor: 403.499628ms: DEBUG:vmm/src/vm.rs:510 -- [Debug I/O port: Firmware code 0x1] 0.402744 seconds
|
||||||
```
|
```
|
||||||
|
|
||||||
### Debug console port
|
|
||||||
|
|
||||||
The debug console is inspired by QEMU and Bochs, which have a similar feature.
|
|
||||||
By default, the I/O port `0xe9` is used. This port can be configured like a
|
|
||||||
console. Thus, it can print to a tty, a file, or a pty, for example.
|
|
||||||
|
|
||||||
### Firmware debug port
|
|
||||||
|
|
||||||
The firmware debug port is also a simple port that prints all bytes written to
|
|
||||||
it. The firmware debug port only prints to stdout.
|
|
||||||
|
|
||||||
## When do I need these ports?
|
|
||||||
|
|
||||||
The ports are on the one hand interesting for firmware or kernel developers, as
|
|
||||||
they provide an easy way to print debug information from within a guest.
|
|
||||||
Furthermore, you can patch "normal" software to measure certain events, such as
|
|
||||||
the boot time of a guest.
|
|
||||||
|
|
||||||
## Which port should I choose?
|
|
||||||
|
|
||||||
The `0x80` debug port and the port of the firmware debug device are always
|
|
||||||
available. The debug console must be activated via the command line, but
|
|
||||||
provides more configuration options.
|
|
||||||
|
|
||||||
You can use different ports for different aspect of your logging messages.
|
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
Cloud Hypervisor uses [cargo-fuzz](https://github.com/rust-fuzz/cargo-fuzz) for fuzzing individual components.
|
Cloud Hypervisor uses [cargo-fuzz](https://github.com/rust-fuzz/cargo-fuzz) for fuzzing individual components.
|
||||||
|
|
||||||
The fuzzers are in the `fuzz/fuzz_targets` directory
|
The fuzzers are are in the `fuzz/fuzz_targets` directory
|
||||||
|
|
||||||
## Preparation
|
## Preparation
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ To enable debugging with GDB, build with the `guest_debug` feature enabled:
|
|||||||
cargo build --features guest_debug
|
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
|
```bash
|
||||||
./cloud-hypervisor \
|
./cloud-hypervisor \
|
||||||
|
|||||||
@@ -27,16 +27,16 @@ $ ./cloud-hypervisor/target/release/cloud-hypervisor \
|
|||||||
--memory size=1024M \
|
--memory size=1024M \
|
||||||
--net "tap=,mac=,ip=,mask=" \
|
--net "tap=,mac=,ip=,mask=" \
|
||||||
--rng \
|
--rng \
|
||||||
--api-socket=/tmp/ch-socket
|
--api-socket /tmp/ch-socket
|
||||||
$ popd
|
$ 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:
|
To ask the VMM to add additional vCPUs then use the resize API:
|
||||||
|
|
||||||
```shell
|
```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:
|
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:
|
||||||
@@ -53,10 +53,10 @@ On-line CPU(s) list: 0-7
|
|||||||
|
|
||||||
After a reboot the added CPUs will remain.
|
After a reboot the added CPUs will remain.
|
||||||
|
|
||||||
Removing CPUs works similarly by reducing the number in the "desired_vcpus" field of the resize API. The CPUs will be automatically offlined inside the guest so there is no need to run any commands inside the guest:
|
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
|
```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.
|
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 \
|
--memory size=1024M,hotplug_size=8192M \
|
||||||
--net "tap=,mac=,ip=,mask=" \
|
--net "tap=,mac=,ip=,mask=" \
|
||||||
--rng \
|
--rng \
|
||||||
--api-socket=/tmp/ch-socket
|
--api-socket /tmp/ch-socket
|
||||||
$ popd
|
$ 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:
|
To ask the VMM to expand the RAM for the VM:
|
||||||
|
|
||||||
```shell
|
```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:
|
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 \
|
--disk path=focal-server-cloudimg-amd64.raw \
|
||||||
--memory size=1024M,hotplug_size=8192M,hotplug_method=virtio-mem \
|
--memory size=1024M,hotplug_size=8192M,hotplug_method=virtio-mem \
|
||||||
--net "tap=,mac=,ip=,mask=" \
|
--net "tap=,mac=,ip=,mask=" \
|
||||||
--api-socket=/tmp/ch-socket
|
--api-socket /tmp/ch-socket
|
||||||
$ popd
|
$ popd
|
||||||
```
|
```
|
||||||
|
|
||||||
To ask the VMM to expand the RAM for the VM (request is in bytes):
|
To ask the VMM to expand the RAM for the VM (request is in bytes):
|
||||||
|
|
||||||
```shell
|
```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:
|
The new memory is now available to use inside the VM:
|
||||||
@@ -172,17 +172,17 @@ $ ./cloud-hypervisor/target/release/cloud-hypervisor \
|
|||||||
--cpus boot=4 \
|
--cpus boot=4 \
|
||||||
--memory size=1024M \
|
--memory size=1024M \
|
||||||
--net "tap=,mac=,ip=,mask=" \
|
--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
|
### Add VFIO Device
|
||||||
|
|
||||||
To ask the VMM to add additional VFIO device then use the `add-device` API.
|
To ask the VMM to add additional VFIO device then use the `add-device` API.
|
||||||
|
|
||||||
```shell
|
```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
|
### 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.
|
To ask the VMM to add additional disk device then use the `add-disk` API.
|
||||||
|
|
||||||
```shell
|
```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
|
### 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.
|
To ask the VMM to add additional fs device then use the `add-fs` API.
|
||||||
|
|
||||||
```shell
|
```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
|
### 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.
|
To ask the VMM to add additional network device then use the `add-net` API.
|
||||||
|
|
||||||
```shell
|
```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
|
### 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.
|
To ask the VMM to add additional PMEM device then use the `add-pmem` API.
|
||||||
|
|
||||||
```shell
|
```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
|
### 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.
|
To ask the VMM to add additional vsock device then use the `add-vsock` API.
|
||||||
|
|
||||||
```shell
|
```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
|
### 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.
|
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
|
```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.
|
As per adding a PCI device to the guest, after a reboot the VM will be running without the removed PCI device.
|
||||||
|
|||||||
@@ -47,15 +47,12 @@ The custom Linux kernel for the guest can be built with the [TDX Tools](https://
|
|||||||
The firmware can be built as follows:
|
The firmware can be built as follows:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install uuid-dev nasm iasl build-essential python3-distutils git
|
|
||||||
|
|
||||||
git clone https://github.com/tianocore/edk2.git
|
git clone https://github.com/tianocore/edk2.git
|
||||||
cd edk2
|
cd edk2
|
||||||
git checkout 13b97736c876919b9786055829caaa4fa46984b7
|
git checkout 13b97736c876919b9786055829caaa4fa46984b7
|
||||||
source ./edksetup.sh
|
|
||||||
git submodule update --init --recursive
|
git submodule update --init --recursive
|
||||||
make -C BaseTools -j `nproc`
|
make -C BaseTools
|
||||||
|
source ./edksetup.sh
|
||||||
build -p OvmfPkg/IntelTdx/IntelTdxX64.dsc -a X64 -t GCC5 -b RELEASE
|
build -p OvmfPkg/IntelTdx/IntelTdxX64.dsc -a X64 -t GCC5 -b RELEASE
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -103,7 +100,7 @@ firmware:
|
|||||||
### TDShim
|
### TDShim
|
||||||
|
|
||||||
> **Note**
|
> **Note**
|
||||||
> The latest version of TDShim being tested is [_v0.8.0_](https://github.com/confidential-containers/td-shim/releases/tag/v0.8.0).
|
> 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
|
This is a lightweight version of the TDVF, written in Rust and designed for
|
||||||
direct kernel boot, which is useful for containers use cases.
|
direct kernel boot, which is useful for containers use cases.
|
||||||
@@ -113,7 +110,7 @@ and `LLVM` first. The TDshim can be build as follows:
|
|||||||
```bash
|
```bash
|
||||||
git clone https://github.com/confidential-containers/td-shim
|
git clone https://github.com/confidential-containers/td-shim
|
||||||
cd td-shim
|
cd td-shim
|
||||||
git checkout v0.8.0
|
git checkout 66bb33451befbf1291abe3cfea7ee9e99d922b0d
|
||||||
cargo install cargo-xbuild
|
cargo install cargo-xbuild
|
||||||
export CC=clang
|
export CC=clang
|
||||||
export AR=llvm-ar
|
export AR=llvm-ar
|
||||||
@@ -121,13 +118,15 @@ export CC_x86_64_unknown_none=clang
|
|||||||
export AR_x86_64_unknown_none=llvm-ar
|
export AR_x86_64_unknown_none=llvm-ar
|
||||||
git submodule update --init --recursive
|
git submodule update --init --recursive
|
||||||
./sh_script/preparation.sh
|
./sh_script/preparation.sh
|
||||||
cargo image --release
|
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
|
If debug logs from the TDShim is needed, here are the alternative
|
||||||
commands:
|
commands:
|
||||||
```bash
|
```bash
|
||||||
cargo image
|
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
|
||||||
```
|
```
|
||||||
|
|
||||||
And run a TDX VM by providing the firmware previously built, along with a guest
|
And run a TDX VM by providing the firmware previously built, along with a guest
|
||||||
|
|||||||
@@ -42,14 +42,3 @@ actual rate limit users get can be as low as
|
|||||||
generally advisable to keep `bw/ops_refill_time` larger than `100 ms`
|
generally advisable to keep `bw/ops_refill_time` larger than `100 ms`
|
||||||
(`cool_down_time`) to make sure the actual rate limit is close to users'
|
(`cool_down_time`) to make sure the actual rate limit is close to users'
|
||||||
expectation ("refill-rate").
|
expectation ("refill-rate").
|
||||||
|
|
||||||
## Rate Limit Groups
|
|
||||||
It is possible to throttle the aggregate bandwidth or operations
|
|
||||||
of multiple virtio-blk devices using a `rate_limit_group`. virtio-blk devices may be
|
|
||||||
dynamically added and removed from a `rate_limit_group`. The following example
|
|
||||||
demonstrates how to throttle the aggregate bandwidth of two disks to 10 MiB/s.
|
|
||||||
```
|
|
||||||
--disk path=disk0.raw,rate_limit_group=group0 \
|
|
||||||
path=disk1.raw,rate_limit_group=group0 \
|
|
||||||
--rate-limit-group bw_size=1048576,bw_refill_time,bw_refill_time=100
|
|
||||||
```
|
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ Another reason for having a virtual IOMMU is to allow passing physical devices
|
|||||||
from the host through multiple layers of virtualization. Let's take as example
|
from the host through multiple layers of virtualization. Let's take as example
|
||||||
a system with a physical IOMMU running a VM with a virtual IOMMU. The
|
a system with a physical IOMMU running a VM with a virtual IOMMU. The
|
||||||
implementation of the virtual IOMMU is responsible for updating the physical
|
implementation of the virtual IOMMU is responsible for updating the physical
|
||||||
DMA Remapping table (DMAR) every time the DMA mapping changes. This must happen
|
DMA Remapping table (DMAR) everytime the DMA mapping changes. This must happen
|
||||||
through the VFIO framework on the host as this is the only userspace interface
|
through the VFIO framework on the host as this is the only userspace interface
|
||||||
to interact with a physical IOMMU.
|
to interact with a physical IOMMU.
|
||||||
|
|
||||||
@@ -60,7 +60,10 @@ implement a full emulation of a physical IOMMU.
|
|||||||
|
|
||||||
### Kernel
|
### Kernel
|
||||||
|
|
||||||
As of Kernel 5.14, virtio-iommu is available for both X86-64 and Aarch64.
|
Since virtio-iommu has landed partially into the version 5.3 of the Linux
|
||||||
|
kernel, a special branch is needed to get things working with Cloud Hypervisor.
|
||||||
|
By partially, we are talking about x86 specifically, as it is already fully
|
||||||
|
functional for ARM architectures.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
@@ -124,7 +127,7 @@ On AArch64 architecture, the virtual IOMMU can still be used even if ACPI is not
|
|||||||
enabled. But the effect is different with what the aforementioned test showed.
|
enabled. But the effect is different with what the aforementioned test showed.
|
||||||
|
|
||||||
When ACPI is disabled, virtual IOMMU is supported through Flattened Device Tree
|
When ACPI is disabled, virtual IOMMU is supported through Flattened Device Tree
|
||||||
(FDT). In this case, the guest kernel cannot tell which device should be
|
(FDT). In this case, the guest kernel can not tell which device should be
|
||||||
IOMMU-attached and which should not. No matter how many devices you attached to
|
IOMMU-attached and which should not. No matter how many devices you attached to
|
||||||
the virtual IOMMU by setting `iommu=on` option, all the devices on the PCI bus
|
the virtual IOMMU by setting `iommu=on` option, all the devices on the PCI bus
|
||||||
will be attached to the virtual IOMMU (except the IOMMU itself). Each of the
|
will be attached to the virtual IOMMU (except the IOMMU itself). Each of the
|
||||||
@@ -242,7 +245,7 @@ e.g.
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
./cloud-hypervisor \
|
./cloud-hypervisor \
|
||||||
--api-socket=/tmp/api \
|
--api-socket /tmp/api \
|
||||||
--cpus boot=1 \
|
--cpus boot=1 \
|
||||||
--memory size=4G,hugepages=on \
|
--memory size=4G,hugepages=on \
|
||||||
--disk path=focal-server-cloudimg-amd64.raw \
|
--disk path=focal-server-cloudimg-amd64.raw \
|
||||||
@@ -257,7 +260,7 @@ requiring the IOMMU then may be hotplugged:
|
|||||||
e.g.
|
e.g.
|
||||||
|
|
||||||
```bash
|
```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)
|
Devices that cannot be placed behind an IOMMU (e.g. lacking an `iommu=` option)
|
||||||
|
|||||||
@@ -16,22 +16,22 @@ $ target/release/cloud-hypervisor
|
|||||||
--disk path=~/workloads/focal.raw \
|
--disk path=~/workloads/focal.raw \
|
||||||
--cpus boot=1 --memory size=1G,shared=on \
|
--cpus boot=1 --memory size=1G,shared=on \
|
||||||
--cmdline "root=/dev/vda1 console=ttyS0" \
|
--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):
|
Launch the destination VM from the same directory (on the host machine):
|
||||||
```bash
|
```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):
|
Get ready for receiving migration for the destination VM (on the host machine):
|
||||||
```bash
|
```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):
|
Start to send migration for the source VM (on the host machine):
|
||||||
```bash
|
```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
|
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 \
|
--cpus boot=1 --memory size=512M \
|
||||||
--kernel vmlinux \
|
--kernel vmlinux \
|
||||||
--cmdline "root=/dev/vda1 console=ttyS0" \
|
--cmdline "root=/dev/vda1 console=ttyS0" \
|
||||||
--disk path=focal-1.raw path=focal-nested.raw path=tmp.img\
|
--disk path=focal-1.raw path=focal-nested.raw --disk path=tmp.img\
|
||||||
--net ip=192.168.101.1
|
--net ip=192.168.101.1
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -63,7 +63,7 @@ $ sudo /target/release/cloud-hypervisor \
|
|||||||
--cpus boot=1 --memory size=512M \
|
--cpus boot=1 --memory size=512M \
|
||||||
--kernel vmlinux \
|
--kernel vmlinux \
|
||||||
--cmdline "root=/dev/vda1 console=ttyS0" \
|
--cmdline "root=/dev/vda1 console=ttyS0" \
|
||||||
--disk path=focal-2.raw path=focal-nested.raw path=tmp.img\
|
--disk path=focal-2.raw path=focal-nested.raw --disk path=tmp.img\
|
||||||
--net ip=192.168.102.1
|
--net ip=192.168.102.1
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -74,8 +74,8 @@ vm-1:~$ sudo ./cloud-hypervisor \
|
|||||||
--memory size=128M \
|
--memory size=128M \
|
||||||
--kernel vmlinux \
|
--kernel vmlinux \
|
||||||
--cmdline "console=ttyS0 root=/dev/vda1" \
|
--cmdline "console=ttyS0 root=/dev/vda1" \
|
||||||
--disk path=/dev/vdb path=/dev/vdc \
|
--disk path=/dev/vdb --disk path=/dev/vdc \
|
||||||
--api-socket=/tmp/api1 \
|
--api-socket /tmp/api1 \
|
||||||
--net ip=192.168.100.1
|
--net ip=192.168.100.1
|
||||||
vm-1:~$ # setup the guest network if needed
|
vm-1:~$ # setup the guest network if needed
|
||||||
vm-1:~$ sudo ip addr add 192.168.101.2/24 dev ens4
|
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):
|
Launch the nested destination VM (inside the guest OS of the VM 2):
|
||||||
```bash
|
```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:~$ # 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 addr add 192.168.102.2/24 dev ens4
|
||||||
vm-2:~$ sudo ip link set up 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
|
Get ready for receiving migration for the nested destination VM (inside
|
||||||
the guest OS of the VM 2):
|
the guest OS of the VM 2):
|
||||||
```bash
|
```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
|
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):
|
the VM 1):
|
||||||
```bash
|
```bash
|
||||||
vm-1:~$ sudo socat UNIX-LISTEN:/tmp/sock1,reuseaddr TCP:192.168.102.2:6000
|
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
|
When the above commands completed, the source VM should be successfully
|
||||||
|
|||||||
@@ -7,13 +7,13 @@ The target audience of this document is both:
|
|||||||
|
|
||||||
## Control
|
## Control
|
||||||
|
|
||||||
The number of `-v` parameters passed to the `cloud-hypervisor` binary will determine the log level. Currently the default is log messages up to `WARN:` (`warn!`) are included by default. The `--log-file` allows the log to be sent to a location other than `stderr`.
|
The number of `-v` parameters passed to the `cloud-hypervisor` binary will determine the log level. Currenly the default is log messages up to `WARN:` (`warn!`) are included by default. The `--log-file` allows the log to be sent to a location other than `stderr`.
|
||||||
|
|
||||||
## Levels
|
## Levels
|
||||||
|
|
||||||
### `error!()`
|
### `error!()`
|
||||||
|
|
||||||
For immediate, unrecoverable errors where it does not make sense for the execution to continue as the behaviour of the VM is considerably impacted.
|
For immediate, unrecoverable errors where it does not make sense for the execution to continue as the behaviour of the VM is considerablely impacted.
|
||||||
|
|
||||||
Cloud Hypervisor should exit shortly after reporting this error (with a non-zero exit code). Generally this should be used during initial construction of the VM state before the virtual CPUs have begun running code.
|
Cloud Hypervisor should exit shortly after reporting this error (with a non-zero exit code). Generally this should be used during initial construction of the VM state before the virtual CPUs have begun running code.
|
||||||
|
|
||||||
@@ -23,7 +23,7 @@ Users should react to this error by checking their initial VM configuration.
|
|||||||
|
|
||||||
### `warn!()`
|
### `warn!()`
|
||||||
|
|
||||||
A serious problem has occurred but the execution of the VM can continue although some functionality might be impacted.
|
A serious problem has occured but the execution of the VM can continue although some functionality might be impacted.
|
||||||
|
|
||||||
A typical example of where this level of message should be generated is during an API call request that cannot be fulfilled.
|
A typical example of where this level of message should be generated is during an API call request that cannot be fulfilled.
|
||||||
|
|
||||||
@@ -38,6 +38,6 @@ This level is for the benefit of developers. It should be used for sporadic and
|
|||||||
|
|
||||||
### `debug!()`
|
### `debug!()`
|
||||||
|
|
||||||
Use `-vv` to enable.
|
Use `-v -v` 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`.
|
||||||
|
|||||||
@@ -222,7 +222,7 @@ struct MemoryZoneConfig {
|
|||||||
--memory-zone <memory-zone> User defined memory zone parameters "size=<guest_memory_region_size>,file=<backing_file>,shared=on|off,hugepages=on|off,hugepage_size=<hugepage_size>,host_numa_node=<node_id>,id=<zone_identifier>,hotplug_size=<hotpluggable_memory_size>,hotplugged_size=<hotplugged_memory_size>,prefault=on|off"
|
--memory-zone <memory-zone> User defined memory zone parameters "size=<guest_memory_region_size>,file=<backing_file>,shared=on|off,hugepages=on|off,hugepage_size=<hugepage_size>,host_numa_node=<node_id>,id=<zone_identifier>,hotplug_size=<hotpluggable_memory_size>,hotplugged_size=<hotplugged_memory_size>,prefault=on|off"
|
||||||
```
|
```
|
||||||
|
|
||||||
This parameter expects one or more occurrences, allowing for a list of memory
|
This parameter expects one or more occurences, allowing for a list of memory
|
||||||
zones to be defined. It must be used with `--memory size=0`, clearly indicating
|
zones to be defined. It must be used with `--memory size=0`, clearly indicating
|
||||||
that the memory will be described through advanced parameters.
|
that the memory will be described through advanced parameters.
|
||||||
|
|
||||||
@@ -466,7 +466,7 @@ List of virtual CPUs attached to the guest NUMA node identified by the
|
|||||||
`guest_numa_id` option. This allows for describing a list of CPUs which
|
`guest_numa_id` option. This allows for describing a list of CPUs which
|
||||||
must be seen by the guest as belonging to the NUMA node `guest_numa_id`.
|
must be seen by the guest as belonging to the NUMA node `guest_numa_id`.
|
||||||
|
|
||||||
One can use this option for a fine-grained description of the NUMA topology
|
One can use this option for a fine grained description of the NUMA topology
|
||||||
regarding the CPUs associated with it, which might help the guest run more
|
regarding the CPUs associated with it, which might help the guest run more
|
||||||
efficiently.
|
efficiently.
|
||||||
|
|
||||||
@@ -516,7 +516,7 @@ different distances, it can be described with the following example.
|
|||||||
_Example_
|
_Example_
|
||||||
|
|
||||||
```
|
```
|
||||||
--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]
|
--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]
|
||||||
```
|
```
|
||||||
|
|
||||||
### `memory_zones`
|
### `memory_zones`
|
||||||
@@ -540,14 +540,14 @@ demarcate the list.
|
|||||||
|
|
||||||
Note that a memory zone must belong to a single NUMA node. The following
|
Note that a memory zone must belong to a single NUMA node. The following
|
||||||
configuration is incorrect, therefore not allowed:
|
configuration is incorrect, therefore not allowed:
|
||||||
`--numa guest_numa_id=0,memory_zones=mem0 guest_numa_id=1,memory_zones=mem0`
|
`--numa guest_numa_id=0,memory_zones=mem0 --numa guest_numa_id=1,memory_zones=mem0`
|
||||||
|
|
||||||
_Example_
|
_Example_
|
||||||
|
|
||||||
```
|
```
|
||||||
--memory size=0
|
--memory size=0
|
||||||
--memory-zone id=mem0,size=1G id=mem1,size=1G id=mem2,size=1G
|
--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] guest_numa_id=1,memory_zones=mem1
|
--numa guest_numa_id=0,memory_zones=[mem0,mem2] --numa guest_numa_id=1,memory_zones=mem1
|
||||||
```
|
```
|
||||||
|
|
||||||
### `sgx_epc_sections`
|
### `sgx_epc_sections`
|
||||||
@@ -567,21 +567,12 @@ _Example_
|
|||||||
|
|
||||||
```
|
```
|
||||||
--sgx-epc id=epc0,size=32M id=epc1,size=64M id=epc2,size=32M
|
--sgx-epc id=epc0,size=32M id=epc1,size=64M id=epc2,size=32M
|
||||||
--numa guest_numa_id=0,sgx_epc_sections=epc1 guest_numa_id=1,sgx_epc_sections=[epc0,epc2]
|
--numa guest_numa_id=0,sgx_epc_sections=epc1 --numa guest_numa_id=1,sgx_epc_sections=[epc0,epc2]
|
||||||
```
|
```
|
||||||
|
|
||||||
### PCI bus
|
### PCI bus
|
||||||
|
|
||||||
Cloud Hypervisor supports guests with one or more PCI segments. The default PCI segment always
|
Cloud Hypervisor supports only one PCI bus, which is why it has been tied to
|
||||||
has affinity to NUMA node 0. Be default, all other PCI segments have affinity to NUMA node 0.
|
the NUMA node 0 by default. It is the user responsibility to organize the NUMA
|
||||||
The user may configure the NUMA affinity for any additional PCI segments.
|
nodes correctly so that vCPUs and guest RAM which should be located on the same
|
||||||
|
NUMA node as the PCI bus end up on the NUMA node 0.
|
||||||
_Example_
|
|
||||||
|
|
||||||
```
|
|
||||||
--platform num_pci_segments=2
|
|
||||||
--memory-zone size=16G,host_numa_node=0,id=mem0
|
|
||||||
--memory-zone size=16G,host_numa_node=1,id=mem1
|
|
||||||
--numa guest_numa_id=0,memory_zones=mem0,pci_segments=[0]
|
|
||||||
--numa guest_numa_id=1,memory_zones=mem1,pci_segments=[1]
|
|
||||||
```
|
|
||||||
|
|||||||
48
docs/mshv.md
48
docs/mshv.md
@@ -1,48 +0,0 @@
|
|||||||
# Microsoft Hypervisor
|
|
||||||
|
|
||||||
The Microsoft Hypervisor is a Type 1 hypervisor which runs on x64 and ARM64 architectures. As the foundation of the Hyper-V virtualization stack, it runs millions of Linux and Windows guests in Azure and on-premises deployments. It supports nested virtualization, and security features like AMD's SEV-SNP. It also supports various features in Windows such as [Device guard and confidential guard](https://techcommunity.microsoft.com/t5/iis-support-blog/windows-10-device-guard-and-credential-guard-demystified/ba-p/376419), and [WSL2](https://docs.microsoft.com/en-us/windows/wsl/wsl2-faq)
|
|
||||||
|
|
||||||
Since 2020, Microsoft has been releasing open-source components to support Linux running as root partition on the Microsoft Hypervisor.
|
|
||||||
|
|
||||||
1. Kernel patches to support Linux booting as root partition
|
|
||||||
2. A Linux kernel driver exposing an IOCTL interface for managing guest partitions, via a device node - /dev/mshv
|
|
||||||
3. Rust bindings and IOCTL wrappers
|
|
||||||
4. IGVM related crates
|
|
||||||
|
|
||||||
## Components
|
|
||||||
|
|
||||||
The following components are related to MSHV support with Cloud-Hypervisor:
|
|
||||||
|
|
||||||
* [igvm-crates](https://github.com/microsoft/igvm) : Parsing IGVM file
|
|
||||||
|
|
||||||
* [mshv-crates](https://github.com/rust-vmm/mshv) : Rust crates to interact with kernel module (/dev/mshv)
|
|
||||||
|
|
||||||
* [igvm-tooling](https://github.com/microsoft/igvm-tooling) : Tool to generate IGVM file
|
|
||||||
|
|
||||||
## IGVM
|
|
||||||
|
|
||||||
Independent Guest Virtual Machine (IGVM) file format.The format specification can be found in the igvm_defs crate, with a Rust implementation of the binary format in the igvm crate.
|
|
||||||
|
|
||||||
The IGVM file format is designed to encapsulate all information required to launch a virtual machine on any given virtualization stack, with support for different isolation technologies such as AMD SEV-SNP and Intel TDX.
|
|
||||||
|
|
||||||
At a conceptual level, this file format is a set of commands created by the tool that generated the file, used by the loader to construct the initial guest state. The file format also contains measurement information that the underlying platform will use to confirm that the file was loaded correctly and signed by the appropriate authorities.
|
|
||||||
|
|
||||||
Cloud Hypervisor can be built using igvm feature flag along with mshv and/or sev-snp. IGVM only works with MSHV.
|
|
||||||
|
|
||||||
## SEV-SNP
|
|
||||||
|
|
||||||
AMD's [Secure Encrypted Virtualization (SEV)](https://www.amd.com/en/developer/sev.html) and extensions such as Secure Nested Paging (SEV-SNP) encrypt memory and restrict access to a guest VM's memory and registers, securing it against a compromised hypervisor or VMM. They utilize the Platform Security Processor (PSP) to store keys and encrypt/decrypt the data. Microsoft has been continuously adding/improving support for SEV-SNP on Microsoft Hyper-V. Cloud-Hypervisor can be built with the sev_snp feature including mshv and igvm feature.
|
|
||||||
|
|
||||||
## Use Cases
|
|
||||||
|
|
||||||
Cloud Hypervisor can be built to run on an MSHV root partition by enabling the mshv feature, e.g.:
|
|
||||||
|
|
||||||
```cargo build --locked --all --all-targets --no-default-features --tests --examples --features mshv```
|
|
||||||
|
|
||||||
Cloud Hypervisor on MSHV can boot Linux guests using an IGVM file. IGVM feature depends on mshv for running legacy VMs.e.g.:
|
|
||||||
|
|
||||||
```cargo build --locked --all --all-targets --no-default-features --tests --examples --features igvm```
|
|
||||||
|
|
||||||
For running confidential VMs on mshv, you will only need to enable sev_snp, it requires and enables mshv and igvm automatically, eg.:
|
|
||||||
|
|
||||||
```cargo build --locked --all --all-targets --no-default-features --tests --examples --features sev_snp```
|
|
||||||
@@ -25,7 +25,7 @@ $ perf record -g target/profiling/cloud-hypervisor \
|
|||||||
--cpus boot=1 --memory size=1G \
|
--cpus boot=1 --memory size=1G \
|
||||||
--cmdline "root=/dev/pmem0p1 console=ttyS0" \
|
--cmdline "root=/dev/pmem0p1 console=ttyS0" \
|
||||||
--serial tty --console off \
|
--serial tty --console off \
|
||||||
--api-socket=/tmp/api1
|
--api-socket /tmp/api1
|
||||||
```
|
```
|
||||||
|
|
||||||
For analysing the samples:
|
For analysing the samples:
|
||||||
@@ -52,5 +52,5 @@ $ perf record --call-graph lbr --all-user --user-callchains -g target/release/cl
|
|||||||
--cpus boot=1 --memory size=1G \
|
--cpus boot=1 --memory size=1G \
|
||||||
--cmdline "root=/dev/pmem0p1 console=ttyS0" \
|
--cmdline "root=/dev/pmem0p1 console=ttyS0" \
|
||||||
--serial tty --console off \
|
--serial tty --console off \
|
||||||
--api-socket=/tmp/api1
|
--api-socket /tmp/api1
|
||||||
```
|
```
|
||||||
|
|||||||
2
docs/releases.md
Normal file → Executable file
2
docs/releases.md
Normal file → Executable file
@@ -95,7 +95,7 @@ E - EOL
|
|||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### LTS Stability Considerations
|
### LTS Stablity Considerations
|
||||||
|
|
||||||
An LTS release is just a `MAJOR` release for which point releases are made for
|
An LTS release is just a `MAJOR` release for which point releases are made for
|
||||||
longer following the same rules for what can be backported to a `POINT` release.
|
longer following the same rules for what can be backported to a `POINT` release.
|
||||||
|
|||||||
@@ -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:
|
At any point in time when the VM is running, one might choose to pause it:
|
||||||
|
|
||||||
```bash
|
```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
|
Once paused, the VM can be safely snapshot into the specified directory and
|
||||||
using the following command:
|
using the following command:
|
||||||
|
|
||||||
```bash
|
```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
|
Given the directory was present on the system, the snapshot will succeed and
|
||||||
@@ -63,7 +63,7 @@ component in the state it was left before the snapshot occurred.
|
|||||||
## Restore a Cloud Hypervisor VM
|
## Restore a Cloud Hypervisor VM
|
||||||
|
|
||||||
Given that one has access to an existing snapshot in `/home/foo/snapshot`,
|
Given that one has access to an existing snapshot in `/home/foo/snapshot`,
|
||||||
it is possible to create a new VM based on this snapshot with the following
|
it is possible to create a new VM based on this snapshot with the following
|
||||||
command:
|
command:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -79,7 +79,7 @@ Or using two different commands from two terminals:
|
|||||||
./cloud-hypervisor --api-socket /tmp/cloud-hypervisor.sock
|
./cloud-hypervisor --api-socket /tmp/cloud-hypervisor.sock
|
||||||
|
|
||||||
# Second terminal
|
# 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
|
Remember the VM is restored in a `paused` state, which was the VM's state when
|
||||||
@@ -87,27 +87,12 @@ it was snapshot. For this reason, one must explicitly `resume` the VM before to
|
|||||||
start using it.
|
start using it.
|
||||||
|
|
||||||
```bash
|
```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
|
At this point, the VM is fully restored and is identical to the VM which was
|
||||||
snapshot earlier.
|
snapshot earlier.
|
||||||
|
|
||||||
## Restore a VM with new Net FDs
|
|
||||||
For a VM created with FDs explicitly passed to NetConfig, a set of valid FDs
|
|
||||||
need to be provided along with the VM restore command in the following syntax:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# First terminal
|
|
||||||
./cloud-hypervisor --api-socket /tmp/cloud-hypervisor.sock
|
|
||||||
|
|
||||||
# Second terminal
|
|
||||||
./ch-remote --api-socket=/tmp/cloud-hypervisor.sock restore source_url=file:///home/foo/snapshot net_fds=[net1@[23,24],net2@[25,26]]
|
|
||||||
```
|
|
||||||
In the example above, the net device with id `net1` will be backed by FDs '23'
|
|
||||||
and '24', and the net device with id `net2` will be backed by FDs '25' and '26'
|
|
||||||
from the restored VM.
|
|
||||||
|
|
||||||
## Limitations
|
## Limitations
|
||||||
|
|
||||||
VFIO devices and Intel SGX are out of scope.
|
VFIO devices and Intel SGX are out of scope.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Tracing
|
# Tracing
|
||||||
|
|
||||||
Cloud Hypervisor has a basic tracing infrastructure, particularly focussed on
|
Cloud Hypervisor has a basic tracing infrastucture, particularly focussed on
|
||||||
the tracing of the initial VM setup.
|
the tracing of the initial VM setup.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
@@ -37,6 +37,6 @@ generating traces of the boot. These can be relocated for focus tracing on a
|
|||||||
narrow part of the code base.
|
narrow part of the code base.
|
||||||
|
|
||||||
A `tracer::trace_point!()` macro is also provided for an instantaneous trace
|
A `tracer::trace_point!()` macro is also provided for an instantaneous trace
|
||||||
point however this is neither in use in the code base currently nor is handled by
|
point however this is not in use in the code base currently nor is handled by
|
||||||
the visualisation script due to the difficulty in representation in the SVG.
|
the visualisation script due to the difficulty in representation in the SVG.
|
||||||
|
|
||||||
|
|||||||
62
docs/vfio.md
62
docs/vfio.md
@@ -122,64 +122,4 @@ $ ls /sys/kernel/iommu_groups/22/devices/
|
|||||||
|
|
||||||
This means these two devices are under the same IOMMU group 22. In such case,
|
This means these two devices are under the same IOMMU group 22. In such case,
|
||||||
it is important to bind both devices to VFIO and pass them both through the
|
it is important to bind both devices to VFIO and pass them both through the
|
||||||
VM, otherwise this could cause some functional and security issues.
|
VM, otherwise this could cause some functional and security issues.
|
||||||
|
|
||||||
### Advanced Configuration Options
|
|
||||||
|
|
||||||
When using NVIDIA GPUs in a VFIO passthrough configuration, advanced
|
|
||||||
configuration options are supported to enable GPUDirect P2P DMA over
|
|
||||||
PCIe. When enabled, loads and stores between GPUs use native PCIe
|
|
||||||
peer-to-peer transactions instead of a shared memory buffer. This drastically
|
|
||||||
decreases P2P latency between GPUs. This functionality is supported by
|
|
||||||
cloud-hypervisor on NVIDIA Turing, Ampere, Hopper, and Lovelace GPUs.
|
|
||||||
|
|
||||||
The NVIDIA driver does not enable GPUDirect P2P over PCIe within guests
|
|
||||||
by default because hardware support for routing P2P TLP between PCIe root
|
|
||||||
ports is optional. PCIe P2P should always be supported between devices
|
|
||||||
on the same PCIe switch. The `x_nv_gpudirect_clique` config argument may
|
|
||||||
be used to signal support for PCIe P2P traffic between NVIDIA VFIO endpoints.
|
|
||||||
The guest driver assumes that P2P traffic is supported between all endpoints
|
|
||||||
that are part of the same clique.
|
|
||||||
```
|
|
||||||
--device path=/sys/bus/pci/devices/0000:01:00.0/,x_nv_gpudirect_clique=0
|
|
||||||
```
|
|
||||||
|
|
||||||
The following command can be run on the guest to verify that GPUDirect P2P is
|
|
||||||
correctly enabled.
|
|
||||||
```
|
|
||||||
nvidia-smi topo -p2p r
|
|
||||||
GPU0 GPU1 GPU2 GPU3 GPU4 GPU5 GPU6 GPU7
|
|
||||||
GPU0 X OK OK OK OK OK OK OK
|
|
||||||
GPU1 OK X OK OK OK OK OK OK
|
|
||||||
GPU2 OK OK X OK OK OK OK OK
|
|
||||||
GPU3 OK OK OK X OK OK OK OK
|
|
||||||
GPU4 OK OK OK OK X OK OK OK
|
|
||||||
GPU5 OK OK OK OK OK X OK OK
|
|
||||||
GPU6 OK OK OK OK OK OK X OK
|
|
||||||
GPU7 OK OK OK OK OK OK OK X
|
|
||||||
```
|
|
||||||
|
|
||||||
Some VFIO devices have a 32-bit mmio BAR. When using many such devices, it is
|
|
||||||
possible to exhaust the 32-bit mmio space available on a PCI segment. The
|
|
||||||
following example demonstrates an example device with a 16 MiB 32-bit mmio BAR.
|
|
||||||
```
|
|
||||||
lspci -s 0000:01:00.0 -v
|
|
||||||
0000:01:00.0 3D controller: NVIDIA Corporation Device 26b9 (rev a1)
|
|
||||||
[...]
|
|
||||||
Memory at f9000000 (32-bit, non-prefetchable) [size=16M]
|
|
||||||
Memory at 46000000000 (64-bit, prefetchable) [size=64G]
|
|
||||||
Memory at 48040000000 (64-bit, prefetchable) [size=32M]
|
|
||||||
[...]
|
|
||||||
```
|
|
||||||
|
|
||||||
When using multiple PCI segments, the 32-bit mmio address space available to
|
|
||||||
be allocated to VFIO devices is equally split between all PCI segments by
|
|
||||||
default. This can be tuned with the `--pci-segment` flag. The following example
|
|
||||||
demonstrates a guest with two PCI segments. 2/3 of the 32-bit mmio address
|
|
||||||
space is available for use by devices on PCI segment 0 and 1/3 of the 32-bit
|
|
||||||
mmio address space is available for use by devices on PCI segment 1.
|
|
||||||
```
|
|
||||||
--platform num_pci_segments=2
|
|
||||||
--pci-segment pci_segment=0,mmio32_aperture_weight=2
|
|
||||||
--pci-segment pci_segment=1,mmio32_aperture_weight=1
|
|
||||||
```
|
|
||||||
@@ -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 \
|
--memory size=1024M,hugepages=on,shared=true \
|
||||||
--kernel linux/arch/x86/boot/compressed/vmlinux.bin \
|
--kernel linux/arch/x86/boot/compressed/vmlinux.bin \
|
||||||
--cmdline "console=ttyS0 root=/dev/vda1 rw iommu=off" \
|
--cmdline "console=ttyS0 root=/dev/vda1 rw iommu=off" \
|
||||||
--disk path=images/focal-server-cloudimg-amd64.raw vhost_user=true,socket=/var/tmp/vhost.1,num_queues=4,queue_size=128 \
|
--disk path=images/focal-server-cloudimg-amd64.raw --disk vhost_user=true,socket=/var/tmp/vhost.1,num_queues=4,queue_size=128 \
|
||||||
--console off \
|
--console off \
|
||||||
--serial tty \
|
--serial tty \
|
||||||
--rng
|
--rng
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
The purpose of this document is to illustrate how to test vhost-user-net
|
The purpose of this document is to illustrate how to test vhost-user-net
|
||||||
in cloud-hypervisor with OVS/DPDK as the backend. This document was
|
in cloud-hypervisor with OVS/DPDK as the backend. This document was
|
||||||
tested with Open vSwitch v2.17.8, DPDK v21.11.4, and Cloud Hypervisor
|
tested with Open vSwitch v2.13.1, DPDK v19.11.3, and Cloud Hypervisor
|
||||||
v37.0 on Ubuntu 22.04.3 (host kernel v5.15.0).
|
v15.0 on Ubuntu 20.04.1 (host kernel v5.4.0).
|
||||||
|
|
||||||
## Framework
|
## Framework
|
||||||
|
|
||||||
@@ -74,8 +74,8 @@ Here is an example how to create a bridge and add two DPDK ports to it
|
|||||||
# create a bridge
|
# create a bridge
|
||||||
ovs-vsctl add-br ovsbr0 -- set bridge ovsbr0 datapath_type=netdev
|
ovs-vsctl add-br ovsbr0 -- set bridge ovsbr0 datapath_type=netdev
|
||||||
# create two DPDK ports and add them to the bridge
|
# create two DPDK ports and add them to the bridge
|
||||||
ovs-vsctl add-port ovsbr0 vhost-user1 -- set Interface vhost-user1 type=dpdkvhostuserclient options:vhost-server-path=/tmp/vhost-user1
|
ovs-vsctl add-port ovsbr0 vhost-user1 -- set Interface vhost-user1 type=dpdkvhostuser
|
||||||
ovs-vsctl add-port ovsbr0 vhost-user2 -- set Interface vhost-user2 type=dpdkvhostuserclient options:vhost-server-path=/tmp/vhost-user2
|
ovs-vsctl add-port ovsbr0 vhost-user2 -- set Interface vhost-user2 type=dpdkvhostuser
|
||||||
# set the number of rx queues
|
# set the number of rx queues
|
||||||
ovs-vsctl set Interface vhost-user1 options:n_rxq=2
|
ovs-vsctl set Interface vhost-user1 options:n_rxq=2
|
||||||
ovs-vsctl set Interface vhost-user2 options:n_rxq=2
|
ovs-vsctl set Interface vhost-user2 options:n_rxq=2
|
||||||
@@ -92,7 +92,7 @@ VMs run in client mode. They connect to the socket created by the `dpdkvhostuser
|
|||||||
--kernel vmlinux \
|
--kernel vmlinux \
|
||||||
--cmdline "console=ttyS0 console=hvc0 root=/dev/vda1 rw" \
|
--cmdline "console=ttyS0 console=hvc0 root=/dev/vda1 rw" \
|
||||||
--disk path=focal-server-cloudimg-amd64.raw \
|
--disk path=focal-server-cloudimg-amd64.raw \
|
||||||
--net mac=52:54:00:02:d9:01,vhost_user=true,socket=/tmp/vhost-user1,num_queues=4,vhost_mode=server
|
--net mac=52:54:00:02:d9:01,vhost_user=true,socket=/var/run/openvswitch/vhost-user1,num_queues=4
|
||||||
|
|
||||||
# From another terminal. We need to give the cloud-hypervisor binary the NET_ADMIN capabilities for it to set TAP interfaces up on the host.
|
# From another terminal. We need to give the cloud-hypervisor binary the NET_ADMIN capabilities for it to set TAP interfaces up on the host.
|
||||||
./cloud-hypervisor \
|
./cloud-hypervisor \
|
||||||
@@ -101,21 +101,21 @@ VMs run in client mode. They connect to the socket created by the `dpdkvhostuser
|
|||||||
--kernel vmlinux \
|
--kernel vmlinux \
|
||||||
--cmdline "console=ttyS0 console=hvc0 root=/dev/vda1 rw" \
|
--cmdline "console=ttyS0 console=hvc0 root=/dev/vda1 rw" \
|
||||||
--disk path=focal-server-cloudimg-amd64.raw \
|
--disk path=focal-server-cloudimg-amd64.raw \
|
||||||
--net mac=52:54:20:11:C5:02,vhost_user=true,socket=/tmp/vhost-user2,num_queues=4,vhost_mode=server
|
--net mac=52:54:20:11:C5:02,vhost_user=true,socket=/var/run/openvswitch/vhost-user2,num_queues=4
|
||||||
```
|
```
|
||||||
|
|
||||||
_Setup VM1_
|
_Setup VM1_
|
||||||
```bash
|
```bash
|
||||||
# From inside the guest
|
# From inside the guest
|
||||||
sudo ip addr add 172.100.0.1/24 dev ens3
|
sudo ip addr add 172.100.0.1/24 dev ens2
|
||||||
sudo ip link set up dev ens3
|
sudo ip link set up dev ens2
|
||||||
```
|
```
|
||||||
|
|
||||||
_Setup VM2_
|
_Setup VM2_
|
||||||
```bash
|
```bash
|
||||||
# From inside the guest
|
# From inside the guest
|
||||||
sudo ip addr add 172.100.0.2/24 dev ens3
|
sudo ip addr add 172.100.0.2/24 dev ens2
|
||||||
sudo ip link set up dev ens3
|
sudo ip link set up dev ens2
|
||||||
```
|
```
|
||||||
|
|
||||||
_Ping VM1 from VM2_
|
_Ping VM1 from VM2_
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user