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 }}
|
||||||
37
.github/workflows/build.yaml
vendored
37
.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.83.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,40 +27,29 @@ 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 -W clippy::assertions_on_result_states
|
run: cargo rustc --locked --bin cloud-hypervisor -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||||
|
|
||||||
- name: Build (kvm)
|
- name: Build (kvm)
|
||||||
run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||||
|
|
||||||
- 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 -W clippy::assertions_on_result_states
|
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 -W clippy::assertions_on_result_states
|
|
||||||
|
|
||||||
- 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 -W clippy::assertions_on_result_states
|
run: cargo rustc --locked --bin cloud-hypervisor --features "guest_debug" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||||
|
|
||||||
- name: Build (default features + pvmemcontrol)
|
|
||||||
run: cargo rustc --locked --bin cloud-hypervisor --features "pvmemcontrol" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
|
||||||
|
|
||||||
- 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 -W clippy::assertions_on_result_states
|
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 -W clippy::assertions_on_result_states
|
|
||||||
|
|
||||||
- name: Build (igvm)
|
|
||||||
run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "igvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
|
||||||
|
|
||||||
- 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 -W clippy::assertions_on_result_states
|
run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "mshv,kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||||
|
|
||||||
- name: Release Build (default features)
|
- name: Release Build (default features)
|
||||||
run: cargo build --locked --all --release --target=${{ matrix.target }}
|
run: cargo build --locked --all --release --target=${{ matrix.target }}
|
||||||
|
|||||||
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: |
|
||||||
|
|||||||
20
.github/workflows/docker-image.yaml
vendored
20
.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,16 +16,16 @@ 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@v3
|
uses: docker/setup-qemu-action@v1
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v1
|
||||||
|
|
||||||
- name: Login to ghcr
|
- name: Login to ghcr
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v2
|
||||||
with:
|
with:
|
||||||
registry: ${{ env.REGISTRY }}
|
registry: ${{ env.REGISTRY }}
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
@@ -36,17 +34,17 @@ jobs:
|
|||||||
|
|
||||||
- name: Docker meta
|
- name: Docker meta
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@v5
|
uses: docker/metadata-action@v4
|
||||||
with:
|
with:
|
||||||
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=20250412-0
|
type=raw,value={{date 'YYYYMMDD'}}-0
|
||||||
type=sha
|
type=sha
|
||||||
|
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
if: ${{ github.event_name == 'push' }}
|
if: ${{ github.event_name == 'push' }}
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v2
|
||||||
with:
|
with:
|
||||||
file: ./resources/Dockerfile
|
file: ./resources/Dockerfile
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
@@ -55,7 +53,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Build only
|
- name: Build only
|
||||||
if: ${{ github.event_name == 'pull_request' }}
|
if: ${{ github.event_name == 'pull_request' }}
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v2
|
||||||
with:
|
with:
|
||||||
file: ./resources/Dockerfile
|
file: ./resources/Dockerfile
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
|
|||||||
32
.github/workflows/formatting.yaml
vendored
32
.github/workflows/formatting.yaml
vendored
@@ -1,32 +0,0 @@
|
|||||||
name: Cloud Hypervisor Code Formatting
|
|
||||||
on: [pull_request, merge_group]
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
name: Code Formatting
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
rust:
|
|
||||||
- nightly
|
|
||||||
target:
|
|
||||||
- x86_64-unknown-linux-gnu
|
|
||||||
- aarch64-unknown-linux-musl
|
|
||||||
env:
|
|
||||||
RUSTFLAGS: -D warnings
|
|
||||||
steps:
|
|
||||||
- name: Code checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
- name: Install Rust toolchain (${{ matrix.rust }})
|
|
||||||
uses: dtolnay/rust-toolchain@stable
|
|
||||||
with:
|
|
||||||
toolchain: ${{ matrix.rust }}
|
|
||||||
target: ${{ matrix.target }}
|
|
||||||
components: rustfmt
|
|
||||||
- name: Formatting (rustfmt)
|
|
||||||
run: cargo fmt --all -- --check
|
|
||||||
- name: Formatting (fuzz) (rustfmt)
|
|
||||||
run: cargo fmt --all --manifest-path fuzz/Cargo.toml -- --check
|
|
||||||
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: 120
|
|
||||||
name: Tests (ARM64)
|
|
||||||
runs-on: bookworm-arm64
|
|
||||||
steps:
|
|
||||||
- name: Fix workspace permissions
|
|
||||||
run: sudo chown -R runner: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: 60
|
|
||||||
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/ bookworm 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: bare-metal-9950x
|
|
||||||
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'
|
|
||||||
25
.github/workflows/integration-rate-limiter.yaml
vendored
25
.github/workflows/integration-rate-limiter.yaml
vendored
@@ -1,25 +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' || 'bare-metal-9950x' }}
|
|
||||||
env:
|
|
||||||
AUTH_DOWNLOAD_TOKEN: ${{ secrets.AUTH_DOWNLOAD_TOKEN }}
|
|
||||||
steps:
|
|
||||||
- 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: 20
|
|
||||||
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"
|
|
||||||
33
.github/workflows/integration-vfio.yaml
vendored
33
.github/workflows/integration-vfio.yaml
vendored
@@ -1,33 +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' || 'vfio-nvidia' }}
|
|
||||||
env:
|
|
||||||
AUTH_DOWNLOAD_TOKEN: ${{ secrets.AUTH_DOWNLOAD_TOKEN }}
|
|
||||||
steps:
|
|
||||||
- name: Fix workspace permissions
|
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
|
||||||
run: sudo chown -R runner: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
|
|
||||||
# Most tests are failing with musl see #6790
|
|
||||||
# - 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"
|
|
||||||
16
.github/workflows/lychee.yaml
vendored
16
.github/workflows/lychee.yaml
vendored
@@ -1,16 +0,0 @@
|
|||||||
name: Link Check (lychee)
|
|
||||||
on:
|
|
||||||
pull_request
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
link_check:
|
|
||||||
name: Link Check
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Code checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Link Availability Check
|
|
||||||
uses: lycheeverse/lychee-action@master
|
|
||||||
with:
|
|
||||||
args: --verbose --config .lychee.toml .
|
|
||||||
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
|
|
||||||
32
.github/workflows/package-consistency.yaml
vendored
32
.github/workflows/package-consistency.yaml
vendored
@@ -1,32 +0,0 @@
|
|||||||
name: Cloud Hypervisor Consistency
|
|
||||||
on: [pull_request, merge_group]
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
name: Rust VMM Consistency Check
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Code checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: sudo apt install -y python3
|
|
||||||
|
|
||||||
- name: Install Rust toolchain stable
|
|
||||||
uses: dtolnay/rust-toolchain@stable
|
|
||||||
with:
|
|
||||||
toolchain: stable
|
|
||||||
|
|
||||||
- name: Check Rust VMM Package Consistency of root Workspace
|
|
||||||
run: python3 scripts/package-consistency-check.py github.com/rust-vmm
|
|
||||||
|
|
||||||
- name: Check Rust VMM Package Consistency of fuzz Workspace
|
|
||||||
run: |
|
|
||||||
pushd fuzz
|
|
||||||
python3 ../scripts/package-consistency-check.py github.com/rust-vmm
|
|
||||||
popd
|
|
||||||
39
.github/workflows/preview-riscv64.yaml
vendored
39
.github/workflows/preview-riscv64.yaml
vendored
@@ -1,39 +0,0 @@
|
|||||||
name: Cloud Hypervisor RISC-V 64-bit Preview
|
|
||||||
on: [pull_request, merge_group]
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
name: Cargo
|
|
||||||
runs-on: riscv64-qemu-host
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
module:
|
|
||||||
- hypervisor
|
|
||||||
- arch
|
|
||||||
- vm-allocator
|
|
||||||
- devices
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Code checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Install Rust toolchain
|
|
||||||
run: /opt/scripts/exec-in-qemu.sh rustup default 1.83.0
|
|
||||||
|
|
||||||
- name: Build ${{ matrix.module }} Module (kvm)
|
|
||||||
run: /opt/scripts/exec-in-qemu.sh cargo rustc --locked -p ${{ matrix.module }} --no-default-features --features "kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
|
||||||
|
|
||||||
- name: Clippy ${{ matrix.module }} Module (kvm)
|
|
||||||
run: /opt/scripts/exec-in-qemu.sh cargo clippy --locked -p ${{ matrix.module }} --no-default-features --features "kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
|
||||||
|
|
||||||
- name: Test ${{ matrix.module }} Module (kvm)
|
|
||||||
run: /opt/scripts/exec-in-qemu.sh cargo test --locked -p ${{ matrix.module }} --no-default-features --features "kvm"
|
|
||||||
|
|
||||||
- name: Check no files were modified
|
|
||||||
run: test -z "$(git status --porcelain)"
|
|
||||||
95
.github/workflows/quality.yaml
vendored
95
.github/workflows/quality.yaml
vendored
@@ -1,19 +1,16 @@
|
|||||||
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:
|
||||||
name: Quality (clippy)
|
if: github.event_name == 'pull_request'
|
||||||
|
name: Quality (clippy, rustfmt)
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
continue-on-error: ${{ matrix.experimental }}
|
continue-on-error: ${{ matrix.experimental }}
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
rust:
|
rust:
|
||||||
- beta
|
|
||||||
- stable
|
- stable
|
||||||
target:
|
target:
|
||||||
- aarch64-unknown-linux-gnu
|
- aarch64-unknown-linux-gnu
|
||||||
@@ -21,15 +18,23 @@ jobs:
|
|||||||
- x86_64-unknown-linux-gnu
|
- x86_64-unknown-linux-gnu
|
||||||
- x86_64-unknown-linux-musl
|
- x86_64-unknown-linux-musl
|
||||||
|
|
||||||
|
experimental: [false]
|
||||||
include:
|
include:
|
||||||
- rust: beta
|
- rust: beta
|
||||||
|
target: aarch64-unknown-linux-gnu
|
||||||
|
experimental: true
|
||||||
|
- rust: beta
|
||||||
|
target: aarch64-unknown-linux-musl
|
||||||
|
experimental: true
|
||||||
|
- rust: beta
|
||||||
|
target: x86_64-unknown-linux-gnu
|
||||||
|
experimental: true
|
||||||
|
- rust: beta
|
||||||
|
target: x86_64-unknown-linux-musl
|
||||||
experimental: true
|
experimental: true
|
||||||
- rust: stable
|
|
||||||
experimental: false
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Code checkout
|
- name: Code checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
@@ -39,108 +44,70 @@ jobs:
|
|||||||
toolchain: ${{ matrix.rust }}
|
toolchain: ${{ matrix.rust }}
|
||||||
target: ${{ matrix.target }}
|
target: ${{ matrix.target }}
|
||||||
override: true
|
override: true
|
||||||
components: 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 }})
|
||||||
for commit in $commits; do git checkout $commit; cargo check --tests --examples --all --target=${{ matrix.target }}; done
|
for commit in $commits; do git checkout $commit; cargo check --tests --examples --all --target=${{ matrix.target }}; done
|
||||||
git checkout ${{ github.sha }}
|
git checkout ${{ github.sha }}
|
||||||
|
|
||||||
|
- name: Formatting (rustfmt)
|
||||||
|
run: cargo fmt -- --check
|
||||||
|
|
||||||
- name: Clippy (kvm)
|
- name: Clippy (kvm)
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
|
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
|
||||||
command: clippy
|
command: clippy
|
||||||
args: --target=${{ matrix.target }} --locked --all --all-targets --no-default-features --tests --examples --features "kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
args: --target=${{ matrix.target }} --locked --all --all-targets --no-default-features --tests --examples --features "kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||||
|
|
||||||
- name: Clippy (mshv)
|
|
||||||
uses: actions-rs/cargo@v1
|
|
||||||
with:
|
|
||||||
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
|
|
||||||
command: clippy
|
|
||||||
args: --target=${{ matrix.target }} --locked --all --all-targets --no-default-features --tests --examples --features "mshv" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
|
||||||
|
|
||||||
- name: Clippy (mshv + kvm)
|
|
||||||
uses: actions-rs/cargo@v1
|
|
||||||
with:
|
|
||||||
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
|
|
||||||
command: clippy
|
|
||||||
args: --target=${{ matrix.target }} --locked --all --all-targets --no-default-features --tests --examples --features "mshv,kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
|
||||||
|
|
||||||
- name: Clippy (default features)
|
- name: Clippy (default features)
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
|
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
|
||||||
command: clippy
|
command: clippy
|
||||||
args: --target=${{ matrix.target }} --locked --all --all-targets --tests --examples -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
args: --target=${{ matrix.target }} --locked --all --all-targets --tests --examples -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||||
|
|
||||||
- name: Clippy (default features + guest_debug)
|
- name: Clippy (default features + guest_debug)
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
|
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
|
||||||
command: clippy
|
command: clippy
|
||||||
args: --target=${{ matrix.target }} --locked --all --all-targets --tests --examples --features "guest_debug" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
args: --target=${{ matrix.target }} --locked --all --all-targets --tests --examples --features "guest_debug" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||||
|
|
||||||
- name: Clippy (default features + pvmemcontrol)
|
|
||||||
uses: actions-rs/cargo@v1
|
|
||||||
with:
|
|
||||||
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
|
|
||||||
command: clippy
|
|
||||||
args: --target=${{ matrix.target }} --locked --all --all-targets --tests --examples --features "pvmemcontrol" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
|
||||||
|
|
||||||
- name: Clippy (default features + tracing)
|
- name: Clippy (default features + tracing)
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
|
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
|
||||||
command: clippy
|
command: clippy
|
||||||
args: --target=${{ matrix.target }} --locked --all --all-targets --tests --examples --features "tracing" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
args: --target=${{ matrix.target }} --locked --all --all-targets --tests --examples --features "tracing" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||||
|
|
||||||
- name: Clippy (mshv)
|
- name: Clippy (mshv)
|
||||||
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
|
||||||
with:
|
with:
|
||||||
|
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
|
||||||
command: clippy
|
command: clippy
|
||||||
args: --target=${{ matrix.target }} --locked --all --all-targets --no-default-features --tests --examples --features "mshv" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
args: --target=${{ matrix.target }} --locked --all --all-targets --no-default-features --tests --examples --features "mshv" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||||
|
|
||||||
- name: Clippy (mshv + kvm)
|
- name: Clippy (mshv + kvm)
|
||||||
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
|
||||||
with:
|
with:
|
||||||
|
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
|
||||||
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 -W clippy::assertions_on_result_states
|
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:
|
|
||||||
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 -W clippy::assertions_on_result_states
|
|
||||||
|
|
||||||
- name: Clippy (igvm)
|
|
||||||
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
|
|
||||||
uses: actions-rs/cargo@v1
|
|
||||||
with:
|
|
||||||
command: clippy
|
|
||||||
args: --target=${{ matrix.target }} --locked --all --all-targets --no-default-features --tests --examples --features "igvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
|
||||||
|
|
||||||
- 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
|
||||||
with:
|
with:
|
||||||
|
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
|
||||||
command: clippy
|
command: clippy
|
||||||
args: --target=${{ matrix.target }} --locked --all --all-targets --no-default-features --tests --examples --features "tdx,kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
args: --target=${{ matrix.target }} --locked --all --all-targets --no-default-features --tests --examples --features "tdx,kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||||
|
|
||||||
- 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.83.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@v4
|
|
||||||
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@v4
|
|
||||||
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
|
|
||||||
|
|||||||
13
.gitlint
13
.gitlint
@@ -1,13 +0,0 @@
|
|||||||
[general]
|
|
||||||
extra-path=scripts/gitlint/rules
|
|
||||||
regex-style-search=true
|
|
||||||
ignore=body-max-line-length
|
|
||||||
|
|
||||||
[ignore-by-author-name]
|
|
||||||
regex=dependabot
|
|
||||||
ignore=all
|
|
||||||
|
|
||||||
# default 72
|
|
||||||
[title-max-length]
|
|
||||||
line-length=72
|
|
||||||
|
|
||||||
21
.lychee.toml
21
.lychee.toml
@@ -1,21 +0,0 @@
|
|||||||
verbose = "info"
|
|
||||||
|
|
||||||
exclude = [
|
|
||||||
# Availability of links below should be manually verified.
|
|
||||||
# Page for intel SGX support, returns 403 while querying.
|
|
||||||
'^https://www.intel.com/content/www/us/en/developer/tools/software-guard-extensions/linux-overview.html',
|
|
||||||
# Page for intel TDX support, returns 403 while querying.
|
|
||||||
'^https://www.intel.com/content/www/us/en/developer/tools/trust-domain-extensions/overview.html',
|
|
||||||
# Page for TPM, returns 403 while querying.
|
|
||||||
'^https://trustedcomputinggroup.org/wp-content/uploads/PC-Client-Specific-Platform-TPM-Profile-for-TPM-2p0-v1p05p_r14_pub.pdf',
|
|
||||||
|
|
||||||
# GitHub user smibarber referenced in `CREDITS.md` no longer exist
|
|
||||||
'^https://github.com/smibarber',
|
|
||||||
|
|
||||||
# OSDev has added bot protection and accesses my result in 403 Forbidden.
|
|
||||||
'^https://wiki.osdev.org',
|
|
||||||
]
|
|
||||||
|
|
||||||
max_retries = 3
|
|
||||||
|
|
||||||
retry_wait_time = 5
|
|
||||||
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,4 +1 @@
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
group_imports="StdExternalCrate"
|
|
||||||
imports_granularity="Module"
|
|
||||||
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
include = ["**/Cargo.toml"]
|
|
||||||
|
|
||||||
[formatting]
|
|
||||||
reorder_arrays = true
|
|
||||||
reorder_keys = true
|
|
||||||
22
.typos.toml
22
.typos.toml
@@ -1,22 +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"
|
|
||||||
TRANSLATER = "TRANSLATER"
|
|
||||||
|
|
||||||
[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
|
||||||
@@ -13,7 +13,7 @@ License](https://opensource.org/licenses/Apache-2.0).
|
|||||||
|
|
||||||
## Coding Style
|
## Coding Style
|
||||||
|
|
||||||
We follow the [Rust Style](https://github.com/rust-lang/rust/tree/HEAD/src/doc/style-guide/src)
|
We follow the [Rust Style](https://github.com/rust-dev-tools/fmt-rfcs/blob/master/guide/guide.md)
|
||||||
convention and enforce it through the Continuous Integration (CI) process calling into `rustfmt`
|
convention and enforce it through the Continuous Integration (CI) process calling into `rustfmt`
|
||||||
for each submitted Pull Request (PR).
|
for each submitted Pull Request (PR).
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ commit you make.
|
|||||||
|
|
||||||
## Certificate of Origin
|
## Certificate of Origin
|
||||||
|
|
||||||
In order to get a clear contribution chain of trust we use the [signed-off-by language](https://web.archive.org/web/20230406041855/https://01.org/community/signed-process)
|
In order to get a clear contribution chain of trust we use the [signed-off-by language](https://01.org/community/signed-process)
|
||||||
used by the Linux kernel project.
|
used by the Linux kernel project.
|
||||||
|
|
||||||
## Patch format
|
## Patch format
|
||||||
@@ -79,12 +79,12 @@ you want to merge your changes to `cloud-hypervisor`:
|
|||||||
|
|
||||||
1. Fork the [cloud-hypervisor](https://github.com/cloud-hypervisor/cloud-hypervisor) project
|
1. Fork the [cloud-hypervisor](https://github.com/cloud-hypervisor/cloud-hypervisor) project
|
||||||
into your github organization.
|
into your github organization.
|
||||||
1. Within your fork, create a branch for your contribution.
|
2. Within your fork, create a branch for your contribution.
|
||||||
1. [Create a pull request](https://help.github.com/articles/creating-a-pull-request-from-a-fork/)
|
3. [Create a pull request](https://help.github.com/articles/creating-a-pull-request-from-a-fork/)
|
||||||
against the main branch of the Cloud Hypervisor repository.
|
against the main branch of the Cloud Hypervisor repository.
|
||||||
1. To update your pull request amend existing commits whenever applicable and
|
4. To update your pull request amend existing commits whenever applicable and
|
||||||
then push the new changes to your pull request branch.
|
then push the new changes to your pull request branch.
|
||||||
1. Once the pull request is approved it can be integrated.
|
5. Once the pull request is approved it can be integrated.
|
||||||
|
|
||||||
## Issue tracking
|
## Issue tracking
|
||||||
|
|
||||||
@@ -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/).
|
||||||
|
|||||||
2095
Cargo.lock
generated
2095
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
152
Cargo.toml
152
Cargo.toml
@@ -1,130 +1,102 @@
|
|||||||
[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 = "Apache-2.0 AND BSD-3-Clause"
|
|
||||||
name = "cloud-hypervisor"
|
name = "cloud-hypervisor"
|
||||||
version = "46.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):
|
||||||
# Can only be bumped if satisfying any of the following:
|
# Can only be bumped by:
|
||||||
# a.) A dependency requires it,
|
# a.) A dependency requires it,
|
||||||
# b.) If we want to use a new feature and that MSRV is at least 6 months old,
|
# 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.83.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.94"
|
anyhow = "1.0.69"
|
||||||
api_client = { path = "api_client" }
|
api_client = { path = "api_client" }
|
||||||
clap = { version = "4.5.13", 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.167"
|
libc = "0.2.139"
|
||||||
log = { version = "0.4.22", features = ["std"] }
|
log = { version = "0.4.17", features = ["std"] }
|
||||||
option_parser = { path = "option_parser" }
|
option_parser = { path = "option_parser" }
|
||||||
seccompiler = { workspace = true }
|
seccompiler = "0.3.0"
|
||||||
serde_json = { workspace = true }
|
serde_json = "1.0.95"
|
||||||
signal-hook = "0.3.18"
|
signal-hook = "0.3.15"
|
||||||
thiserror = "2.0.6"
|
thiserror = "1.0.39"
|
||||||
tpm = { path = "tpm" }
|
tpm = { path = "tpm"}
|
||||||
tracer = { path = "tracer" }
|
tracer = { path = "tracer" }
|
||||||
vm-memory = { workspace = true }
|
|
||||||
vmm = { path = "vmm" }
|
vmm = { path = "vmm" }
|
||||||
vmm-sys-util = { workspace = true }
|
vmm-sys-util = "0.11.0"
|
||||||
zbus = { version = "4.4.0", 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 = "6.0.0"
|
dirs = "4.0.0"
|
||||||
net_util = { path = "net_util" }
|
net_util = { path = "net_util" }
|
||||||
once_cell = "1.20.2"
|
once_cell = "1.17.1"
|
||||||
serde_json = { workspace = true }
|
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"]
|
||||||
pvmemcontrol = ["vmm/pvmemcontrol"]
|
|
||||||
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"
|
||||||
]
|
]
|
||||||
|
|
||||||
[workspace.dependencies]
|
|
||||||
# rust-vmm crates
|
|
||||||
acpi_tables = { git = "https://github.com/rust-vmm/acpi_tables", branch = "main" }
|
|
||||||
kvm-bindings = "0.10.0"
|
|
||||||
kvm-ioctls = "0.19.1"
|
|
||||||
linux-loader = "0.13.0"
|
|
||||||
mshv-bindings = "0.5.1"
|
|
||||||
mshv-ioctls = "0.5.1"
|
|
||||||
seccompiler = "0.5.0"
|
|
||||||
vfio-bindings = { git = "https://github.com/rust-vmm/vfio", branch = "main" }
|
|
||||||
vfio-ioctls = { git = "https://github.com/rust-vmm/vfio", branch = "main", default-features = false }
|
|
||||||
vfio_user = { git = "https://github.com/rust-vmm/vfio-user", branch = "main" }
|
|
||||||
vhost = { git = "https://github.com/rust-vmm/vhost", rev = "d983ae0" }
|
|
||||||
vhost-user-backend = { git = "https://github.com/rust-vmm/vhost", rev = "d983ae0" }
|
|
||||||
virtio-bindings = "0.2.4"
|
|
||||||
virtio-queue = "0.14.0"
|
|
||||||
vm-fdt = { git = "https://github.com/rust-vmm/vm-fdt", branch = "main" }
|
|
||||||
vm-memory = "0.16.1"
|
|
||||||
vmm-sys-util = "0.12.1"
|
|
||||||
|
|
||||||
# igvm crates
|
|
||||||
# TODO: bump to 0.3.5 release
|
|
||||||
igvm = { git = "https://github.com/microsoft/igvm", branch = "main" }
|
|
||||||
igvm_defs = { git = "https://github.com/microsoft/igvm", branch = "main" }
|
|
||||||
|
|
||||||
# serde crates
|
|
||||||
serde_json = "1.0.120"
|
|
||||||
|
|||||||
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
|
||||||
|
}
|
||||||
47
README.md
47
README.md
@@ -59,13 +59,9 @@ based on the [Rust VMM](https://github.com/rust-vmm) crates.
|
|||||||
|
|
||||||
### Architectures
|
### Architectures
|
||||||
|
|
||||||
Cloud Hypervisor supports the `x86-64`, `AArch64` and `riscv64`
|
Cloud Hypervisor supports the `x86-64` and `AArch64` architectures. There are
|
||||||
architectures, with functionality varying across these platforms. The
|
minor differences in functionality between the two architectures
|
||||||
functionality differences between `x86-64` and `AArch64` are documented
|
(see [#1125](https://github.com/cloud-hypervisor/cloud-hypervisor/issues/1125)).
|
||||||
in [#1125](https://github.com/cloud-hypervisor/cloud-hypervisor/issues/1125).
|
|
||||||
The `riscv64` architecture support is experimental and offers limited
|
|
||||||
functionality. For more details and instructions, please refer to [riscv
|
|
||||||
documentation](docs/riscv.md).
|
|
||||||
|
|
||||||
### Guest OS
|
### Guest OS
|
||||||
|
|
||||||
@@ -82,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
|
||||||
|
|
||||||
@@ -112,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`.)
|
||||||
|
|
||||||
@@ -144,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="
|
||||||
@@ -167,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=" \
|
||||||
@@ -179,15 +172,20 @@ $ ./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:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
# Clone the Cloud Hypervisor Linux branch
|
# Clone the Cloud Hypervisor Linux branch
|
||||||
$ git clone --depth 1 https://github.com/cloud-hypervisor/linux.git -b ch-6.12.8 linux-cloud-hypervisor
|
$ git clone --depth 1 https://github.com/cloud-hypervisor/linux.git -b ch-6.2 linux-cloud-hypervisor
|
||||||
$ pushd linux-cloud-hypervisor
|
$ pushd linux-cloud-hypervisor
|
||||||
$ make ch_defconfig
|
# Use the x86-64 cloud-hypervisor kernel config to build your kernel for x86-64
|
||||||
|
$ wget https://raw.githubusercontent.com/cloud-hypervisor/cloud-hypervisor/main/resources/linux-config-x86_64
|
||||||
|
# Use the AArch64 cloud-hypervisor kernel config to build your kernel for AArch64
|
||||||
|
$ wget https://raw.githubusercontent.com/cloud-hypervisor/cloud-hypervisor/main/resources/linux-config-aarch64
|
||||||
|
$ cp linux-config-x86_64 .config # x86-64
|
||||||
|
$ cp linux-config-aarch64 .config # AArch64
|
||||||
# Do native build of the x86-64 kernel
|
# Do native build of the x86-64 kernel
|
||||||
$ KCFLAGS="-Wa,-mx86-used-note=no" make bzImage -j `nproc`
|
$ KCFLAGS="-Wa,-mx86-used-note=no" make bzImage -j `nproc`
|
||||||
# Do native build of the AArch64 kernel
|
# Do native build of the AArch64 kernel
|
||||||
@@ -224,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 \
|
||||||
@@ -238,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 \
|
||||||
@@ -301,9 +299,8 @@ Further details can be found in the [release documentation](docs/releases.md).
|
|||||||
As of 2023-01-03, the following cloud images are supported:
|
As of 2023-01-03, the following cloud images are supported:
|
||||||
|
|
||||||
- [Ubuntu Focal](https://cloud-images.ubuntu.com/focal/current/) (focal-server-cloudimg-{amd64,arm64}.img)
|
- [Ubuntu Focal](https://cloud-images.ubuntu.com/focal/current/) (focal-server-cloudimg-{amd64,arm64}.img)
|
||||||
- [Ubuntu Jammy](https://cloud-images.ubuntu.com/jammy/current/) (jammy-server-cloudimg-{amd64,arm64}.img)
|
- [Ubuntu Jammy](https://cloud-images.ubuntu.com/jammy/current/) (jammy-server-cloudimg-{amd64,arm64}.img )
|
||||||
- [Ubuntu Noble](https://cloud-images.ubuntu.com/noble/current/) (noble-server-cloudimg-{amd64,arm64}.img)
|
- [Fedora 36](https://fedora.mirrorservice.org/fedora/linux/releases/36/Cloud/) ([Fedora-Cloud-Base-36-1.5.x86_64.raw.xz](https://fedora.mirrorservice.org/fedora/linux/releases/36/Cloud/x86_64/images/) / [Fedora-Cloud-Base-36-1.5.aarch64.raw.xz](https://fedora.mirrorservice.org/fedora/linux/releases/36/Cloud/aarch64/images/))
|
||||||
- [Fedora 36](https://archives.fedoraproject.org/pub/archive/fedora/linux/releases/36/Cloud/) ([Fedora-Cloud-Base-36-1.5.x86_64.raw.xz](https://archives.fedoraproject.org/pub/archive/fedora/linux/releases/36/Cloud/x86_64/images/) / [Fedora-Cloud-Base-36-1.5.aarch64.raw.xz](https://archives.fedoraproject.org/pub/archive/fedora/linux/releases/36/Cloud/aarch64/images/))
|
|
||||||
|
|
||||||
Direct kernel boot to userspace should work with a rootfs from most
|
Direct kernel boot to userspace should work with a rootfs from most
|
||||||
distributions although you may need to enable exotic filesystem types in the
|
distributions although you may need to enable exotic filesystem types in the
|
||||||
|
|||||||
@@ -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 = "2.0.6"
|
vmm-sys-util = "0.11.0"
|
||||||
vmm-sys-util = { workspace = true }
|
|
||||||
|
|||||||
@@ -3,28 +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(#[source] std::io::Error),
|
SocketSendFds(vmm_sys_util::errno::Error),
|
||||||
#[error("Error sending file descriptors: {0}")]
|
StatusCodeParsing(std::num::ParseIntError),
|
||||||
SocketSendFds(#[source] vmm_sys_util::errno::Error),
|
|
||||||
#[error("Error parsing HTTP status code: {0}")]
|
|
||||||
StatusCodeParsing(#[source] 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(#[source] std::num::ParseIntError),
|
|
||||||
#[error("Server responded with an error: {0:?}: {1:?}")]
|
|
||||||
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,
|
||||||
@@ -32,7 +45,6 @@ pub enum StatusCode {
|
|||||||
NoContent,
|
NoContent,
|
||||||
BadRequest,
|
BadRequest,
|
||||||
NotFound,
|
NotFound,
|
||||||
TooManyRequests,
|
|
||||||
InternalServerError,
|
InternalServerError,
|
||||||
NotImplemented,
|
NotImplemented,
|
||||||
Unknown,
|
Unknown,
|
||||||
@@ -46,7 +58,6 @@ impl StatusCode {
|
|||||||
204 => StatusCode::NoContent,
|
204 => StatusCode::NoContent,
|
||||||
400 => StatusCode::BadRequest,
|
400 => StatusCode::BadRequest,
|
||||||
404 => StatusCode::NotFound,
|
404 => StatusCode::NotFound,
|
||||||
429 => StatusCode::TooManyRequests,
|
|
||||||
500 => StatusCode::InternalServerError,
|
500 => StatusCode::InternalServerError,
|
||||||
501 => StatusCode::NotImplemented,
|
501 => StatusCode::NotImplemented,
|
||||||
_ => StatusCode::Unknown,
|
_ => StatusCode::Unknown,
|
||||||
|
|||||||
@@ -1,29 +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 = []
|
||||||
kvm = ["hypervisor/kvm"]
|
|
||||||
sev_snp = []
|
|
||||||
tdx = []
|
tdx = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.94"
|
anyhow = "1.0.69"
|
||||||
byteorder = "1.5.0"
|
byteorder = "1.4.3"
|
||||||
hypervisor = { path = "../hypervisor" }
|
hypervisor = { path = "../hypervisor" }
|
||||||
libc = "0.2.167"
|
libc = "0.2.139"
|
||||||
linux-loader = { workspace = true, features = ["bzimage", "elf", "pe"] }
|
linux-loader = { version = "0.8.1", features = ["elf", "bzimage", "pe"] }
|
||||||
log = "0.4.22"
|
log = "0.4.17"
|
||||||
serde = { version = "1.0.208", features = ["derive", "rc"] }
|
serde = { version = "1.0.151", features = ["rc", "derive"] }
|
||||||
thiserror = "2.0.6"
|
thiserror = "1.0.39"
|
||||||
uuid = "1.12.1"
|
uuid = "1.3.0"
|
||||||
vm-memory = { workspace = true, features = ["backend-bitmap", "backend-mmap"] }
|
versionize = "0.1.10"
|
||||||
|
versionize_derive = "0.1.4"
|
||||||
|
vm-memory = { version = "0.10.0", features = ["backend-mmap", "backend-bitmap"] }
|
||||||
vm-migration = { path = "../vm-migration" }
|
vm-migration = { path = "../vm-migration" }
|
||||||
vmm-sys-util = { workspace = true, features = ["with-serde"] }
|
vmm-sys-util = { version = "0.11.0", features = ["with-serde"] }
|
||||||
|
|
||||||
[target.'cfg(any(target_arch = "aarch64", target_arch = "riscv64"))'.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 = { workspace = true }
|
vm-fdt = { git = "https://github.com/rust-vmm/vm-fdt", branch = "main" }
|
||||||
|
|||||||
@@ -6,30 +6,27 @@
|
|||||||
// 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 THIRD-PARTY file.
|
// found in the THIRD-PARTY file.
|
||||||
|
|
||||||
|
use crate::{NumaNodes, PciSpaceInfo};
|
||||||
|
use byteorder::{BigEndian, ByteOrder};
|
||||||
|
use hypervisor::arch::aarch64::gic::Vgic;
|
||||||
|
use std::cmp;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::ffi::CStr;
|
use std::ffi::CStr;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::path::Path;
|
use std::result;
|
||||||
|
use std::str;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use std::{cmp, fs, result, str};
|
|
||||||
|
|
||||||
use byteorder::{BigEndian, ByteOrder};
|
use super::super::DeviceType;
|
||||||
use hypervisor::arch::aarch64::gic::Vgic;
|
use super::super::GuestMemoryMmap;
|
||||||
use hypervisor::arch::aarch64::regs::{
|
use super::super::InitramfsConfig;
|
||||||
AARCH64_ARCH_TIMER_HYP_IRQ, AARCH64_ARCH_TIMER_PHYS_NONSECURE_IRQ,
|
use super::layout::{
|
||||||
AARCH64_ARCH_TIMER_PHYS_SECURE_IRQ, AARCH64_ARCH_TIMER_VIRT_IRQ, AARCH64_PMU_IRQ,
|
IRQ_BASE, MEM_32BIT_DEVICES_SIZE, MEM_32BIT_DEVICES_START, MEM_PCI_IO_SIZE, MEM_PCI_IO_START,
|
||||||
|
PCI_HIGH_BASE, PCI_MMIO_CONFIG_SIZE_PER_SEGMENT,
|
||||||
};
|
};
|
||||||
use 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};
|
||||||
|
|
||||||
use super::super::{DeviceType, GuestMemoryMmap, InitramfsConfig};
|
|
||||||
use super::layout::{
|
|
||||||
GIC_V2M_COMPATIBLE, 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, SPI_BASE, SPI_NUM,
|
|
||||||
};
|
|
||||||
use crate::{NumaNodes, PciSpaceInfo};
|
|
||||||
|
|
||||||
// This is a value for uniquely identifying the FDT node declaring the interrupt controller.
|
// This is a value for uniquely identifying the FDT node declaring the interrupt controller.
|
||||||
const GIC_PHANDLE: u32 = 1;
|
const GIC_PHANDLE: u32 = 1;
|
||||||
// This is a value for uniquely identifying the FDT node declaring the MSI controller.
|
// This is a value for uniquely identifying the FDT node declaring the MSI controller.
|
||||||
@@ -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;
|
||||||
@@ -63,6 +56,9 @@ const GIC_FDT_IRQ_TYPE_PPI: u32 = 1;
|
|||||||
const IRQ_TYPE_EDGE_RISING: u32 = 1;
|
const IRQ_TYPE_EDGE_RISING: u32 = 1;
|
||||||
const IRQ_TYPE_LEVEL_HI: u32 = 4;
|
const IRQ_TYPE_LEVEL_HI: u32 = 4;
|
||||||
|
|
||||||
|
// PMU PPI interrupt number
|
||||||
|
pub const AARCH64_PMU_IRQ: u32 = 7;
|
||||||
|
|
||||||
// Keys and Buttons
|
// Keys and Buttons
|
||||||
// System Power Down
|
// System Power Down
|
||||||
const KEY_POWER: u32 = 116;
|
const KEY_POWER: u32 = 116;
|
||||||
@@ -78,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(#[source] 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>(
|
||||||
@@ -289,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}");
|
||||||
@@ -376,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")?;
|
||||||
@@ -522,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;
|
||||||
}
|
}
|
||||||
@@ -568,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."
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -586,14 +311,15 @@ fn create_memory_node(
|
|||||||
&& (first_region_end <= &mem_32bit_reserved_start))
|
&& (first_region_end <= &mem_32bit_reserved_start))
|
||||||
{
|
{
|
||||||
panic!(
|
panic!(
|
||||||
"Unexpected first memory region layout: (start: 0x{first_region_start:08x}, end: 0x{first_region_end:08x}).
|
"Unexpected first memory region layout: (start: 0x{:08x}, end: 0x{:08x}).
|
||||||
ram_start: 0x{ram_start:08x}, mem_32bit_reserved_start: 0x{mem_32bit_reserved_start:08x}"
|
ram_start: 0x{:08x}, mem_32bit_reserved_start: 0x{:08x}",
|
||||||
|
first_region_start, first_region_end, ram_start, mem_32bit_reserved_start
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mem_size = first_region_end - ram_start;
|
let mem_size = first_region_end - ram_start;
|
||||||
let mem_reg_prop = [ram_start, mem_size];
|
let mem_reg_prop = [ram_start, mem_size];
|
||||||
let memory_node_name = format!("memory@{ram_start:x}");
|
let memory_node_name = format!("memory@{:x}", ram_start);
|
||||||
let memory_node = fdt.begin_node(&memory_node_name)?;
|
let memory_node = fdt.begin_node(&memory_node_name)?;
|
||||||
fdt.property_string("device_type", "memory")?;
|
fdt.property_string("device_type", "memory")?;
|
||||||
fdt.property_array_u64("reg", &mem_reg_prop)?;
|
fdt.property_array_u64("reg", &mem_reg_prop)?;
|
||||||
@@ -606,13 +332,14 @@ fn create_memory_node(
|
|||||||
|
|
||||||
if second_region_start != &ram_64bit_start {
|
if second_region_start != &ram_64bit_start {
|
||||||
panic!(
|
panic!(
|
||||||
"Unexpected second memory region layout: start: 0x{second_region_start:08x}, ram_64bit_start: 0x{ram_64bit_start:08x}"
|
"Unexpected second memory region layout: start: 0x{:08x}, ram_64bit_start: 0x{:08x}",
|
||||||
|
second_region_start, ram_64bit_start
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mem_size = second_region_end - ram_64bit_start;
|
let mem_size = second_region_end - ram_64bit_start;
|
||||||
let mem_reg_prop = [ram_64bit_start, mem_size];
|
let mem_reg_prop = [ram_64bit_start, mem_size];
|
||||||
let memory_node_name = format!("memory@{ram_64bit_start:x}");
|
let memory_node_name = format!("memory@{:x}", ram_64bit_start);
|
||||||
let memory_node = fdt.begin_node(&memory_node_name)?;
|
let memory_node = fdt.begin_node(&memory_node_name)?;
|
||||||
fdt.property_string("device_type", "memory")?;
|
fdt.property_string("device_type", "memory")?;
|
||||||
fdt.property_array_u64("reg", &mem_reg_prop)?;
|
fdt.property_array_u64("reg", &mem_reg_prop)?;
|
||||||
@@ -669,19 +396,11 @@ fn create_gic_node(fdt: &mut FdtWriter, gic_device: &Arc<Mutex<dyn Vgic>>) -> Fd
|
|||||||
|
|
||||||
if gic_device.lock().unwrap().msi_compatible() {
|
if gic_device.lock().unwrap().msi_compatible() {
|
||||||
let msic_node = fdt.begin_node("msic")?;
|
let msic_node = fdt.begin_node("msic")?;
|
||||||
let msi_compatibility = gic_device.lock().unwrap().msi_compatibility().to_string();
|
fdt.property_string("compatible", gic_device.lock().unwrap().msi_compatibility())?;
|
||||||
|
|
||||||
fdt.property_string("compatible", msi_compatibility.as_str())?;
|
|
||||||
fdt.property_null("msi-controller")?;
|
fdt.property_null("msi-controller")?;
|
||||||
fdt.property_u32("phandle", MSI_PHANDLE)?;
|
fdt.property_u32("phandle", MSI_PHANDLE)?;
|
||||||
let msi_reg_prop = gic_device.lock().unwrap().msi_properties();
|
let msi_reg_prop = gic_device.lock().unwrap().msi_properties();
|
||||||
fdt.property_array_u64("reg", &msi_reg_prop)?;
|
fdt.property_array_u64("reg", &msi_reg_prop)?;
|
||||||
|
|
||||||
if msi_compatibility == GIC_V2M_COMPATIBLE {
|
|
||||||
fdt.property_u32("arm,msi-base-spi", SPI_BASE)?;
|
|
||||||
fdt.property_u32("arm,msi-num-spis", SPI_NUM)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
fdt.end_node(msic_node)?;
|
fdt.end_node(msic_node)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -708,14 +427,9 @@ fn create_clock_node(fdt: &mut FdtWriter) -> FdtWriterResult<()> {
|
|||||||
|
|
||||||
fn create_timer_node(fdt: &mut FdtWriter) -> FdtWriterResult<()> {
|
fn create_timer_node(fdt: &mut FdtWriter) -> FdtWriterResult<()> {
|
||||||
// See
|
// See
|
||||||
// https://github.com/torvalds/linux/blob/master/Documentation/devicetree/bindings/timer/arm%2Carch_timer.yaml
|
// https://github.com/torvalds/linux/blob/master/Documentation/devicetree/bindings/interrupt-controller/arch_timer.txt
|
||||||
// These are fixed interrupt numbers for the timer device.
|
// These are fixed interrupt numbers for the timer device.
|
||||||
let irqs = [
|
let irqs = [13, 14, 11, 10];
|
||||||
AARCH64_ARCH_TIMER_PHYS_SECURE_IRQ,
|
|
||||||
AARCH64_ARCH_TIMER_PHYS_NONSECURE_IRQ,
|
|
||||||
AARCH64_ARCH_TIMER_VIRT_IRQ,
|
|
||||||
AARCH64_ARCH_TIMER_HYP_IRQ,
|
|
||||||
];
|
|
||||||
let compatible = "arm,armv8-timer";
|
let compatible = "arm,armv8-timer";
|
||||||
|
|
||||||
let mut timer_reg_cells: Vec<u32> = Vec::new();
|
let mut timer_reg_cells: Vec<u32> = Vec::new();
|
||||||
@@ -904,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) =
|
||||||
@@ -1121,7 +835,10 @@ fn print_node(node: fdt_parser::node::FdtNode<'_, '_>, n_spaces: usize) {
|
|||||||
// - At first, try to convert it to CStr and print,
|
// - At first, try to convert it to CStr and print,
|
||||||
// - If failed, print it as u32 array.
|
// - If failed, print it as u32 array.
|
||||||
let value_result = match CStr::from_bytes_with_nul(value) {
|
let value_result = match CStr::from_bytes_with_nul(value) {
|
||||||
Ok(value_cstr) => value_cstr.to_str().ok(),
|
Ok(value_cstr) => match value_cstr.to_str() {
|
||||||
|
Ok(value_str) => Some(value_str),
|
||||||
|
Err(_e) => None,
|
||||||
|
},
|
||||||
Err(_e) => None,
|
Err(_e) => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -138,12 +137,3 @@ pub const IRQ_BASE: u32 = 32;
|
|||||||
|
|
||||||
/// Number of supported interrupts
|
/// Number of supported interrupts
|
||||||
pub const IRQ_NUM: u32 = 256;
|
pub const IRQ_NUM: u32 = 256;
|
||||||
|
|
||||||
/// Base SPI interrupt number
|
|
||||||
pub const SPI_BASE: u32 = 32;
|
|
||||||
|
|
||||||
/// Total number of SPIs
|
|
||||||
pub const SPI_NUM: u32 = 64;
|
|
||||||
|
|
||||||
/// GICv2M compatible string
|
|
||||||
pub const GIC_V2M_COMPATIBLE: &str = "arm,gic-v2m-frame";
|
|
||||||
|
|||||||
@@ -6,53 +6,45 @@
|
|||||||
pub mod fdt;
|
pub mod fdt;
|
||||||
/// Layout for this aarch64 system.
|
/// Layout for this aarch64 system.
|
||||||
pub mod layout;
|
pub mod layout;
|
||||||
|
/// Module for system registers definition
|
||||||
|
pub mod regs;
|
||||||
/// Module for loading UEFI binary.
|
/// Module for loading UEFI binary.
|
||||||
pub mod uefi;
|
pub mod uefi;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::fmt::Debug;
|
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
|
|
||||||
use hypervisor::arch::aarch64::gic::Vgic;
|
|
||||||
use hypervisor::arch::aarch64::regs::MPIDR_EL1;
|
|
||||||
use log::{log_enabled, Level};
|
|
||||||
use thiserror::Error;
|
|
||||||
use vm_memory::{Address, GuestAddress, GuestMemory, GuestMemoryAtomic};
|
|
||||||
|
|
||||||
pub use self::fdt::DeviceInfoForFdt;
|
pub use self::fdt::DeviceInfoForFdt;
|
||||||
use crate::{DeviceType, GuestMemoryMmap, NumaNodes, PciSpaceInfo, RegionType};
|
use crate::{DeviceType, GuestMemoryMmap, NumaNodes, PciSpaceInfo, RegionType};
|
||||||
|
use hypervisor::arch::aarch64::gic::Vgic;
|
||||||
|
use log::{log_enabled, Level};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::convert::TryInto;
|
||||||
|
use std::fmt::Debug;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
use vm_memory::{Address, GuestAddress, GuestMemory, GuestMemoryAtomic};
|
||||||
|
|
||||||
pub const _NSIG: i32 = 65;
|
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(#[source] 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(#[source] hypervisor::HypervisorCpuError),
|
|
||||||
|
|
||||||
/// Error configuring the MPIDR register
|
/// Error configuring the MPIDR register
|
||||||
#[error("Error configuring the MPIDR register: {0}")]
|
VcpuRegMpidr(hypervisor::HypervisorCpuError),
|
||||||
VcpuRegMpidr(#[source] hypervisor::HypervisorCpuError),
|
|
||||||
|
|
||||||
/// Error initializing PMU for vcpu
|
/// Error initializing PMU for vcpu
|
||||||
#[error("Error initializing PMU for vcpu")]
|
|
||||||
VcpuInitPmu,
|
VcpuInitPmu,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,7 +77,9 @@ pub fn configure_vcpu(
|
|||||||
.map_err(Error::RegsConfiguration)?;
|
.map_err(Error::RegsConfiguration)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mpidr = vcpu.get_sys_reg(MPIDR_EL1).map_err(Error::VcpuRegMpidr)?;
|
let mpidr = vcpu
|
||||||
|
.get_sys_reg(regs::MPIDR_EL1)
|
||||||
|
.map_err(Error::VcpuRegMpidr)?;
|
||||||
Ok(mpidr)
|
Ok(mpidr)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,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.
|
||||||
|
|||||||
43
arch/src/aarch64/regs.rs
Normal file
43
arch/src/aarch64/regs.rs
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
// Copyright 2022 Arm Limited (or its affiliates). All rights reserved.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
// AArch64 system register encoding:
|
||||||
|
// See https://developer.arm.com/documentation/ddi0487 (chapter D12)
|
||||||
|
//
|
||||||
|
// 31 22 21 20 19 18 16 15 12 11 8 7 5 4 0
|
||||||
|
// +----------+---+-----+-----+-----+-----+-----+----+
|
||||||
|
// |1101010100| L | op0 | op1 | CRn | CRm | op2 | Rt |
|
||||||
|
// +----------+---+-----+-----+-----+-----+-----+----+
|
||||||
|
//
|
||||||
|
// Notes:
|
||||||
|
// - L and Rt are reserved as implementation defined fields, ignored.
|
||||||
|
|
||||||
|
const SYSREG_HEAD: u32 = 0b1101010100u32 << 22;
|
||||||
|
const SYSREG_OP0_SHIFT: u32 = 19;
|
||||||
|
const SYSREG_OP0_MASK: u32 = 0b11u32 << 19;
|
||||||
|
const SYSREG_OP1_SHIFT: u32 = 16;
|
||||||
|
const SYSREG_OP1_MASK: u32 = 0b111u32 << 16;
|
||||||
|
const SYSREG_CRN_SHIFT: u32 = 12;
|
||||||
|
const SYSREG_CRN_MASK: u32 = 0b1111u32 << 12;
|
||||||
|
const SYSREG_CRM_SHIFT: u32 = 8;
|
||||||
|
const SYSREG_CRM_MASK: u32 = 0b1111u32 << 8;
|
||||||
|
const SYSREG_OP2_SHIFT: u32 = 5;
|
||||||
|
const SYSREG_OP2_MASK: u32 = 0b111u32 << 5;
|
||||||
|
|
||||||
|
/// Define the ID of system registers
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! arm64_sys_reg {
|
||||||
|
($name: tt, $op0: tt, $op1: tt, $crn: tt, $crm: tt, $op2: tt) => {
|
||||||
|
pub const $name: u32 = SYSREG_HEAD
|
||||||
|
| ((($op0 as u32) << SYSREG_OP0_SHIFT) & SYSREG_OP0_MASK as u32)
|
||||||
|
| ((($op1 as u32) << SYSREG_OP1_SHIFT) & SYSREG_OP1_MASK as u32)
|
||||||
|
| ((($crn as u32) << SYSREG_CRN_SHIFT) & SYSREG_CRN_MASK as u32)
|
||||||
|
| ((($crm as u32) << SYSREG_CRM_SHIFT) & SYSREG_CRM_MASK as u32)
|
||||||
|
| ((($op2 as u32) << SYSREG_OP2_SHIFT) & SYSREG_OP2_MASK as u32);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
arm64_sys_reg!(MPIDR_EL1, 3, 0, 0, 0, 5);
|
||||||
|
arm64_sys_reg!(ID_AA64MMFR0_EL1, 3, 0, 0, 7, 0);
|
||||||
|
arm64_sys_reg!(TTBR1_EL1, 3, 0, 2, 0, 1);
|
||||||
|
arm64_sys_reg!(TCR_EL1, 3, 0, 2, 0, 2);
|
||||||
@@ -1,28 +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 vm_memory::{Bytes, GuestAddress, GuestMemory};
|
||||||
use thiserror::Error;
|
|
||||||
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>;
|
||||||
@@ -33,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))
|
||||||
@@ -45,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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// Copyright © 2024 Institute of Software, CAS. All rights reserved.
|
|
||||||
// Copyright 2020 Arm Limited (or its affiliates). All rights reserved.
|
// Copyright 2020 Arm Limited (or its affiliates). All rights reserved.
|
||||||
// Copyright © 2020, Oracle and/or its affiliates.
|
// Copyright © 2020, Oracle and/or its affiliates.
|
||||||
//
|
//
|
||||||
@@ -6,20 +5,22 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
//! Implements platform specific functionality.
|
//! Implements platform specific functionality.
|
||||||
//! Supported platforms: x86_64, aarch64, riscv64.
|
//! Supported platforms: x86_64, aarch64.
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate log;
|
extern crate log;
|
||||||
|
|
||||||
use std::collections::BTreeMap;
|
|
||||||
use std::sync::Arc;
|
|
||||||
use std::{fmt, result};
|
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
#[cfg(target_arch = "x86_64")]
|
#[cfg(target_arch = "x86_64")]
|
||||||
use crate::x86_64::SgxEpcSection;
|
use crate::x86_64::SgxEpcSection;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
use std::fmt;
|
||||||
|
use std::result;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use thiserror::Error;
|
||||||
|
use versionize::{VersionMap, Versionize, VersionizeError, VersionizeResult};
|
||||||
|
use versionize_derive::Versionize;
|
||||||
|
use vm_migration::VersionMapped;
|
||||||
|
|
||||||
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>;
|
||||||
@@ -28,14 +29,11 @@ type GuestRegionMmap = vm_memory::GuestRegionMmap<vm_memory::bitmap::AtomicBitma
|
|||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[cfg(target_arch = "x86_64")]
|
#[cfg(target_arch = "x86_64")]
|
||||||
#[error("Platform specific error (x86_64): {0}")]
|
#[error("Platform specific error (x86_64): {0:?}")]
|
||||||
PlatformSpecific(#[source] x86_64::Error),
|
PlatformSpecific(x86_64::Error),
|
||||||
#[cfg(target_arch = "aarch64")]
|
#[cfg(target_arch = "aarch64")]
|
||||||
#[error("Platform specific error (aarch64): {0:?}")]
|
#[error("Platform specific error (aarch64): {0:?}")]
|
||||||
PlatformSpecific(#[source] aarch64::Error),
|
PlatformSpecific(aarch64::Error),
|
||||||
#[cfg(target_arch = "riscv64")]
|
|
||||||
#[error("Platform specific error (riscv64): {0:?}")]
|
|
||||||
PlatformSpecific(#[source] riscv64::Error),
|
|
||||||
#[error("The memory map table extends past the end of guest memory")]
|
#[error("The memory map table extends past the end of guest memory")]
|
||||||
MemmapTablePastRamEnd,
|
MemmapTablePastRamEnd,
|
||||||
#[error("Error writing memory map table to guest memory")]
|
#[error("Error writing memory map table to guest memory")]
|
||||||
@@ -50,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,
|
||||||
@@ -78,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;
|
||||||
@@ -89,26 +85,14 @@ pub use aarch64::{
|
|||||||
layout::IRQ_BASE, uefi, EntryPoint, _NSIG,
|
layout::IRQ_BASE, uefi, EntryPoint, _NSIG,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Module for riscv64 related functionality.
|
|
||||||
#[cfg(target_arch = "riscv64")]
|
|
||||||
pub mod riscv64;
|
|
||||||
|
|
||||||
#[cfg(target_arch = "riscv64")]
|
|
||||||
pub use riscv64::{
|
|
||||||
arch_memory_regions, configure_system, configure_vcpu, fdt::DeviceInfoForFdt,
|
|
||||||
get_host_cpu_phys_bits, initramfs_load_addr, layout, layout::CMDLINE_MAX_SIZE,
|
|
||||||
layout::IRQ_BASE, EntryPoint, _NSIG,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[cfg(target_arch = "x86_64")]
|
#[cfg(target_arch = "x86_64")]
|
||||||
pub mod x86_64;
|
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)`.
|
||||||
@@ -124,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")]
|
||||||
@@ -147,7 +130,7 @@ pub enum DeviceType {
|
|||||||
/// Device Type: Virtio.
|
/// Device Type: Virtio.
|
||||||
Virtio(u32),
|
Virtio(u32),
|
||||||
/// Device Type: Serial.
|
/// Device Type: Serial.
|
||||||
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
|
#[cfg(target_arch = "aarch64")]
|
||||||
Serial,
|
Serial,
|
||||||
/// Device Type: RTC.
|
/// Device Type: RTC.
|
||||||
#[cfg(target_arch = "aarch64")]
|
#[cfg(target_arch = "aarch64")]
|
||||||
@@ -168,7 +151,7 @@ impl fmt::Display for DeviceType {
|
|||||||
|
|
||||||
/// Structure to describe MMIO device information
|
/// Structure to describe MMIO device information
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
|
#[cfg(target_arch = "aarch64")]
|
||||||
pub struct MmioDeviceInfo {
|
pub struct MmioDeviceInfo {
|
||||||
pub addr: u64,
|
pub addr: u64,
|
||||||
pub len: u64,
|
pub len: u64,
|
||||||
@@ -177,7 +160,7 @@ pub struct MmioDeviceInfo {
|
|||||||
|
|
||||||
/// Structure to describe PCI space information
|
/// Structure to describe PCI space information
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
|
#[cfg(target_arch = "aarch64")]
|
||||||
pub struct PciSpaceInfo {
|
pub struct PciSpaceInfo {
|
||||||
pub pci_segment_id: u16,
|
pub pci_segment_id: u16,
|
||||||
pub mmio_config_address: u64,
|
pub mmio_config_address: u64,
|
||||||
@@ -185,7 +168,7 @@ pub struct PciSpaceInfo {
|
|||||||
pub pci_device_space_size: u64,
|
pub pci_device_space_size: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
|
#[cfg(target_arch = "aarch64")]
|
||||||
impl DeviceInfoForFdt for MmioDeviceInfo {
|
impl DeviceInfoForFdt for MmioDeviceInfo {
|
||||||
fn addr(&self) -> u64 {
|
fn addr(&self) -> u64 {
|
||||||
self.addr
|
self.addr
|
||||||
|
|||||||
@@ -1,484 +0,0 @@
|
|||||||
// Copyright © 2024 Institute of Software, CAS. All rights reserved.
|
|
||||||
// Copyright 2020 Arm Limited (or its affiliates). All rights reserved.
|
|
||||||
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
//
|
|
||||||
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
|
||||||
// found in the THIRD-PARTY file.
|
|
||||||
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::ffi::CStr;
|
|
||||||
use std::fmt::Debug;
|
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
use std::{cmp, result, str};
|
|
||||||
|
|
||||||
use byteorder::{BigEndian, ByteOrder};
|
|
||||||
use hypervisor::arch::riscv64::aia::Vaia;
|
|
||||||
use thiserror::Error;
|
|
||||||
use vm_fdt::{FdtWriter, FdtWriterResult};
|
|
||||||
use vm_memory::{Address, Bytes, GuestMemory, GuestMemoryError, GuestMemoryRegion};
|
|
||||||
|
|
||||||
use super::super::{DeviceType, GuestMemoryMmap, InitramfsConfig};
|
|
||||||
use super::layout::{
|
|
||||||
IRQ_BASE, MEM_32BIT_DEVICES_SIZE, MEM_32BIT_DEVICES_START, MEM_PCI_IO_SIZE, MEM_PCI_IO_START,
|
|
||||||
PCI_HIGH_BASE, PCI_MMIO_CONFIG_SIZE_PER_SEGMENT,
|
|
||||||
};
|
|
||||||
use crate::PciSpaceInfo;
|
|
||||||
|
|
||||||
const AIA_APLIC_PHANDLE: u32 = 1;
|
|
||||||
const AIA_IMSIC_PHANDLE: u32 = 2;
|
|
||||||
const CPU_INTC_BASE_PHANDLE: u32 = 3;
|
|
||||||
const CPU_BASE_PHANDLE: u32 = 256 + CPU_INTC_BASE_PHANDLE;
|
|
||||||
// Read the documentation specified when appending the root node to the FDT.
|
|
||||||
const ADDRESS_CELLS: u32 = 0x2;
|
|
||||||
const SIZE_CELLS: u32 = 0x2;
|
|
||||||
|
|
||||||
// From https://elixir.bootlin.com/linux/v6.10/source/include/dt-bindings/interrupt-controller/irq.h#L14
|
|
||||||
const _IRQ_TYPE_EDGE_RISING: u32 = 1;
|
|
||||||
const IRQ_TYPE_LEVEL_HI: u32 = 4;
|
|
||||||
|
|
||||||
const S_MODE_EXT_IRQ: u32 = 9;
|
|
||||||
|
|
||||||
/// Trait for devices to be added to the Flattened Device Tree.
|
|
||||||
pub trait DeviceInfoForFdt {
|
|
||||||
/// Returns the address where this device will be loaded.
|
|
||||||
fn addr(&self) -> u64;
|
|
||||||
/// Returns the associated interrupt for this device.
|
|
||||||
fn irq(&self) -> u32;
|
|
||||||
/// Returns the amount of memory that needs to be reserved for this device.
|
|
||||||
fn length(&self) -> u64;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Errors thrown while configuring the Flattened Device Tree for riscv64.
|
|
||||||
#[derive(Debug, Error)]
|
|
||||||
pub enum Error {
|
|
||||||
/// Failure in writing FDT in memory.
|
|
||||||
#[error("Failure in writing FDT in memory: {0}")]
|
|
||||||
WriteFdtToMemory(#[source] GuestMemoryError),
|
|
||||||
}
|
|
||||||
type Result<T> = result::Result<T, Error>;
|
|
||||||
|
|
||||||
/// Creates the flattened device tree for this riscv64 VM.
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
|
||||||
pub fn create_fdt<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHasher>(
|
|
||||||
guest_mem: &GuestMemoryMmap,
|
|
||||||
cmdline: &str,
|
|
||||||
num_vcpu: u32,
|
|
||||||
device_info: &HashMap<(DeviceType, String), T, S>,
|
|
||||||
aia_device: &Arc<Mutex<dyn Vaia>>,
|
|
||||||
initrd: &Option<InitramfsConfig>,
|
|
||||||
pci_space_info: &[PciSpaceInfo],
|
|
||||||
) -> FdtWriterResult<Vec<u8>> {
|
|
||||||
// Allocate stuff necessary for the holding the blob.
|
|
||||||
let mut fdt = FdtWriter::new()?;
|
|
||||||
|
|
||||||
// For an explanation why these nodes were introduced in the blob take a look at
|
|
||||||
// https://github.com/devicetree-org/devicetree-specification/releases/tag/v0.4
|
|
||||||
// In chapter 3.
|
|
||||||
|
|
||||||
// Header or the root node as per above mentioned documentation.
|
|
||||||
let root_node = fdt.begin_node("")?;
|
|
||||||
fdt.property_string("compatible", "linux,dummy-virt")?;
|
|
||||||
// For info on #address-cells and size-cells resort to Table 3.1 Root Node
|
|
||||||
// Properties
|
|
||||||
fdt.property_u32("#address-cells", ADDRESS_CELLS)?;
|
|
||||||
fdt.property_u32("#size-cells", SIZE_CELLS)?;
|
|
||||||
create_cpu_nodes(&mut fdt, num_vcpu)?;
|
|
||||||
create_memory_node(&mut fdt, guest_mem)?;
|
|
||||||
create_chosen_node(&mut fdt, cmdline, initrd)?;
|
|
||||||
create_aia_node(&mut fdt, aia_device)?;
|
|
||||||
create_devices_node(&mut fdt, device_info)?;
|
|
||||||
create_pci_nodes(&mut fdt, pci_space_info)?;
|
|
||||||
|
|
||||||
// End Header node.
|
|
||||||
fdt.end_node(root_node)?;
|
|
||||||
|
|
||||||
let fdt_final = fdt.finish()?;
|
|
||||||
|
|
||||||
Ok(fdt_final)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn write_fdt_to_memory(fdt_final: Vec<u8>, guest_mem: &GuestMemoryMmap) -> Result<()> {
|
|
||||||
// Write FDT to memory.
|
|
||||||
guest_mem
|
|
||||||
.write_slice(fdt_final.as_slice(), super::layout::FDT_START)
|
|
||||||
.map_err(Error::WriteFdtToMemory)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Following are the auxiliary function for creating the different nodes that we append to our FDT.
|
|
||||||
fn create_cpu_nodes(fdt: &mut FdtWriter, num_cpus: u32) -> FdtWriterResult<()> {
|
|
||||||
// See https://elixir.bootlin.com/linux/v6.10/source/Documentation/devicetree/bindings/riscv/cpus.yaml
|
|
||||||
let cpus = fdt.begin_node("cpus")?;
|
|
||||||
// As per documentation, on RISC-V 64-bit systems value should be set to 1.
|
|
||||||
fdt.property_u32("#address-cells", 0x01)?;
|
|
||||||
fdt.property_u32("#size-cells", 0x0)?;
|
|
||||||
// TODO: Retrieve CPU frequency from cpu timer regs
|
|
||||||
let timebase_frequency: u32 = 0x989680;
|
|
||||||
fdt.property_u32("timebase-frequency", timebase_frequency)?;
|
|
||||||
|
|
||||||
for cpu_index in 0..num_cpus {
|
|
||||||
let cpu = fdt.begin_node(&format!("cpu@{:x}", cpu_index))?;
|
|
||||||
fdt.property_string("device_type", "cpu")?;
|
|
||||||
fdt.property_string("compatible", "riscv")?;
|
|
||||||
fdt.property_string("mmu-type", "sv48")?;
|
|
||||||
fdt.property_string("riscv,isa", "rv64imafdc_smaia_ssaia")?;
|
|
||||||
fdt.property_string("status", "okay")?;
|
|
||||||
fdt.property_u32("reg", cpu_index)?;
|
|
||||||
fdt.property_u32("phandle", CPU_BASE_PHANDLE + cpu_index)?;
|
|
||||||
|
|
||||||
// interrupt controller node
|
|
||||||
let intc_node = fdt.begin_node("interrupt-controller")?;
|
|
||||||
fdt.property_string("compatible", "riscv,cpu-intc")?;
|
|
||||||
fdt.property_u32("#interrupt-cells", 1u32)?;
|
|
||||||
fdt.property_null("interrupt-controller")?;
|
|
||||||
fdt.property_u32("phandle", CPU_INTC_BASE_PHANDLE + cpu_index)?;
|
|
||||||
fdt.end_node(intc_node)?;
|
|
||||||
|
|
||||||
fdt.end_node(cpu)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
fdt.end_node(cpus)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_memory_node(fdt: &mut FdtWriter, guest_mem: &GuestMemoryMmap) -> FdtWriterResult<()> {
|
|
||||||
// Note: memory regions from "GuestMemory" are sorted and non-zero sized.
|
|
||||||
let ram_regions = {
|
|
||||||
let mut ram_regions = Vec::new();
|
|
||||||
let mut current_start = guest_mem
|
|
||||||
.iter()
|
|
||||||
.next()
|
|
||||||
.map(GuestMemoryRegion::start_addr)
|
|
||||||
.expect("GuestMemory must have one memory region at least")
|
|
||||||
.raw_value();
|
|
||||||
let mut current_end = current_start;
|
|
||||||
|
|
||||||
for (start, size) in guest_mem
|
|
||||||
.iter()
|
|
||||||
.map(|m| (m.start_addr().raw_value(), m.len()))
|
|
||||||
{
|
|
||||||
if current_end == start {
|
|
||||||
// This zone is continuous with the previous one.
|
|
||||||
current_end += size;
|
|
||||||
} else {
|
|
||||||
ram_regions.push((current_start, current_end));
|
|
||||||
|
|
||||||
current_start = start;
|
|
||||||
current_end = start + size;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ram_regions.push((current_start, current_end));
|
|
||||||
|
|
||||||
ram_regions
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut mem_reg_property = Vec::new();
|
|
||||||
for region in ram_regions {
|
|
||||||
let mem_size = region.1 - region.0;
|
|
||||||
mem_reg_property.push(region.0);
|
|
||||||
mem_reg_property.push(mem_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
let ram_start = super::layout::RAM_START.raw_value();
|
|
||||||
let memory_node_name = format!("memory@{:x}", ram_start);
|
|
||||||
let memory_node = fdt.begin_node(&memory_node_name)?;
|
|
||||||
fdt.property_string("device_type", "memory")?;
|
|
||||||
fdt.property_array_u64("reg", &mem_reg_property)?;
|
|
||||||
fdt.end_node(memory_node)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_chosen_node(
|
|
||||||
fdt: &mut FdtWriter,
|
|
||||||
cmdline: &str,
|
|
||||||
initrd: &Option<InitramfsConfig>,
|
|
||||||
) -> FdtWriterResult<()> {
|
|
||||||
let chosen_node = fdt.begin_node("chosen")?;
|
|
||||||
fdt.property_string("bootargs", cmdline)?;
|
|
||||||
|
|
||||||
if let Some(initrd_config) = initrd {
|
|
||||||
let initrd_start = initrd_config.address.raw_value();
|
|
||||||
let initrd_end = initrd_config.address.raw_value() + initrd_config.size as u64;
|
|
||||||
fdt.property_u64("linux,initrd-start", initrd_start)?;
|
|
||||||
fdt.property_u64("linux,initrd-end", initrd_end)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
fdt.end_node(chosen_node)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_aia_node(fdt: &mut FdtWriter, aia_device: &Arc<Mutex<dyn Vaia>>) -> FdtWriterResult<()> {
|
|
||||||
// IMSIC
|
|
||||||
if aia_device.lock().unwrap().msi_compatible() {
|
|
||||||
use super::layout::IMSIC_START;
|
|
||||||
let imsic_name = format!("imsics@{:x}", IMSIC_START.0);
|
|
||||||
let imsic_node = fdt.begin_node(&imsic_name)?;
|
|
||||||
|
|
||||||
fdt.property_string(
|
|
||||||
"compatible",
|
|
||||||
aia_device.lock().unwrap().imsic_compatibility(),
|
|
||||||
)?;
|
|
||||||
let imsic_reg_prop = aia_device.lock().unwrap().imsic_properties();
|
|
||||||
fdt.property_array_u32("reg", &imsic_reg_prop)?;
|
|
||||||
fdt.property_u32("#interrupt-cells", 0u32)?;
|
|
||||||
fdt.property_null("interrupt-controller")?;
|
|
||||||
fdt.property_null("msi-controller")?;
|
|
||||||
// TODO complete num-ids
|
|
||||||
fdt.property_u32("riscv,num-ids", 2047u32)?;
|
|
||||||
fdt.property_u32("phandle", AIA_IMSIC_PHANDLE)?;
|
|
||||||
|
|
||||||
let mut irq_cells = Vec::new();
|
|
||||||
let num_cpus = aia_device.lock().unwrap().vcpu_count();
|
|
||||||
for i in 0..num_cpus {
|
|
||||||
irq_cells.push(CPU_INTC_BASE_PHANDLE + i);
|
|
||||||
irq_cells.push(S_MODE_EXT_IRQ);
|
|
||||||
}
|
|
||||||
fdt.property_array_u32("interrupts-extended", &irq_cells)?;
|
|
||||||
|
|
||||||
fdt.end_node(imsic_node)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// APLIC
|
|
||||||
use super::layout::APLIC_START;
|
|
||||||
let aplic_name = format!("aplic@{:x}", APLIC_START.0);
|
|
||||||
let aplic_node = fdt.begin_node(&aplic_name)?;
|
|
||||||
|
|
||||||
fdt.property_string(
|
|
||||||
"compatible",
|
|
||||||
aia_device.lock().unwrap().aplic_compatibility(),
|
|
||||||
)?;
|
|
||||||
let reg_cells = aia_device.lock().unwrap().aplic_properties();
|
|
||||||
fdt.property_array_u32("reg", ®_cells)?;
|
|
||||||
fdt.property_u32("#interrupt-cells", 2u32)?;
|
|
||||||
fdt.property_null("interrupt-controller")?;
|
|
||||||
// TODO complete num-srcs
|
|
||||||
fdt.property_u32("riscv,num-sources", 96u32)?;
|
|
||||||
fdt.property_u32("phandle", AIA_APLIC_PHANDLE)?;
|
|
||||||
fdt.property_u32("msi-parent", AIA_IMSIC_PHANDLE)?;
|
|
||||||
|
|
||||||
fdt.end_node(aplic_node)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_serial_node<T: DeviceInfoForFdt + Clone + Debug>(
|
|
||||||
fdt: &mut FdtWriter,
|
|
||||||
dev_info: &T,
|
|
||||||
) -> FdtWriterResult<()> {
|
|
||||||
let serial_reg_prop = [dev_info.addr(), dev_info.length()];
|
|
||||||
let irq = [dev_info.irq() - IRQ_BASE, IRQ_TYPE_LEVEL_HI];
|
|
||||||
|
|
||||||
let serial_node = fdt.begin_node(&format!("serial@{:x}", dev_info.addr()))?;
|
|
||||||
fdt.property_string("compatible", "ns16550a")?;
|
|
||||||
fdt.property_array_u64("reg", &serial_reg_prop)?;
|
|
||||||
fdt.property_u32("clock-frequency", 3686400)?;
|
|
||||||
fdt.property_u32("interrupt-parent", AIA_APLIC_PHANDLE)?;
|
|
||||||
fdt.property_array_u32("interrupts", &irq)?;
|
|
||||||
fdt.end_node(serial_node)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_devices_node<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHasher>(
|
|
||||||
fdt: &mut FdtWriter,
|
|
||||||
dev_info: &HashMap<(DeviceType, String), T, S>,
|
|
||||||
) -> FdtWriterResult<()> {
|
|
||||||
for ((device_type, _device_id), info) in dev_info {
|
|
||||||
match device_type {
|
|
||||||
DeviceType::Serial => create_serial_node(fdt, info)?,
|
|
||||||
DeviceType::Virtio(_) => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_pci_nodes(fdt: &mut FdtWriter, pci_device_info: &[PciSpaceInfo]) -> FdtWriterResult<()> {
|
|
||||||
// Add node for PCIe controller.
|
|
||||||
// See Documentation/devicetree/bindings/pci/host-generic-pci.txt in the kernel
|
|
||||||
// and https://elinux.org/Device_Tree_Usage.
|
|
||||||
// In multiple PCI segments setup, each PCI segment needs a PCI node.
|
|
||||||
for pci_device_info_elem in pci_device_info.iter() {
|
|
||||||
// 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
|
|
||||||
// could fall below 4G.
|
|
||||||
// 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.
|
|
||||||
let (pci_device_base_64bit, pci_device_size_64bit) =
|
|
||||||
if pci_device_info_elem.pci_device_space_start < PCI_HIGH_BASE.raw_value() {
|
|
||||||
(
|
|
||||||
PCI_HIGH_BASE.raw_value(),
|
|
||||||
pci_device_info_elem.pci_device_space_size
|
|
||||||
- (PCI_HIGH_BASE.raw_value() - pci_device_info_elem.pci_device_space_start),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
(
|
|
||||||
pci_device_info_elem.pci_device_space_start,
|
|
||||||
pci_device_info_elem.pci_device_space_size,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
// There is no specific requirement of the 32bit MMIO range, and
|
|
||||||
// therefore at least we can make these ranges 4K aligned.
|
|
||||||
let pci_device_size_32bit: u64 =
|
|
||||||
MEM_32BIT_DEVICES_SIZE / ((1 << 12) * pci_device_info.len() as u64) * (1 << 12);
|
|
||||||
let pci_device_base_32bit: u64 = MEM_32BIT_DEVICES_START.0
|
|
||||||
+ pci_device_size_32bit * pci_device_info_elem.pci_segment_id as u64;
|
|
||||||
|
|
||||||
let ranges = [
|
|
||||||
// io addresses. Since AArch64 will not use IO address,
|
|
||||||
// we can set the same IO address range for every segment.
|
|
||||||
0x1000000,
|
|
||||||
0_u32,
|
|
||||||
0_u32,
|
|
||||||
(MEM_PCI_IO_START.0 >> 32) as u32,
|
|
||||||
MEM_PCI_IO_START.0 as u32,
|
|
||||||
(MEM_PCI_IO_SIZE >> 32) as u32,
|
|
||||||
MEM_PCI_IO_SIZE as u32,
|
|
||||||
// mmio addresses
|
|
||||||
0x2000000, // (ss = 10: 32-bit memory space)
|
|
||||||
(pci_device_base_32bit >> 32) as u32, // PCI address
|
|
||||||
pci_device_base_32bit as u32,
|
|
||||||
(pci_device_base_32bit >> 32) as u32, // CPU address
|
|
||||||
pci_device_base_32bit as u32,
|
|
||||||
(pci_device_size_32bit >> 32) as u32, // size
|
|
||||||
pci_device_size_32bit as u32,
|
|
||||||
// device addresses
|
|
||||||
0x3000000, // (ss = 11: 64-bit memory space)
|
|
||||||
(pci_device_base_64bit >> 32) as u32, // PCI address
|
|
||||||
pci_device_base_64bit as u32,
|
|
||||||
(pci_device_base_64bit >> 32) as u32, // CPU address
|
|
||||||
pci_device_base_64bit as u32,
|
|
||||||
(pci_device_size_64bit >> 32) as u32, // size
|
|
||||||
pci_device_size_64bit as u32,
|
|
||||||
];
|
|
||||||
let bus_range = [0, 0]; // Only bus 0
|
|
||||||
let reg = [
|
|
||||||
pci_device_info_elem.mmio_config_address,
|
|
||||||
PCI_MMIO_CONFIG_SIZE_PER_SEGMENT,
|
|
||||||
];
|
|
||||||
// See kernel document Documentation/devicetree/bindings/pci/pci-msi.txt
|
|
||||||
let msi_map = [
|
|
||||||
// rid-base: A single cell describing the first RID matched by the entry.
|
|
||||||
0x0,
|
|
||||||
// msi-controller: A single phandle to an MSI controller.
|
|
||||||
AIA_IMSIC_PHANDLE,
|
|
||||||
// msi-base: An msi-specifier describing the msi-specifier produced for the
|
|
||||||
// first RID matched by the entry.
|
|
||||||
(pci_device_info_elem.pci_segment_id as u32) << 8,
|
|
||||||
// length: A single cell describing how many consecutive RIDs are matched
|
|
||||||
// following the rid-base.
|
|
||||||
0x100,
|
|
||||||
];
|
|
||||||
|
|
||||||
let pci_node_name = format!("pci@{:x}", pci_device_info_elem.mmio_config_address);
|
|
||||||
let pci_node = fdt.begin_node(&pci_node_name)?;
|
|
||||||
|
|
||||||
fdt.property_string("compatible", "pci-host-ecam-generic")?;
|
|
||||||
fdt.property_string("device_type", "pci")?;
|
|
||||||
fdt.property_array_u32("ranges", &ranges)?;
|
|
||||||
fdt.property_array_u32("bus-range", &bus_range)?;
|
|
||||||
fdt.property_u32(
|
|
||||||
"linux,pci-domain",
|
|
||||||
pci_device_info_elem.pci_segment_id as u32,
|
|
||||||
)?;
|
|
||||||
fdt.property_u32("#address-cells", 3)?;
|
|
||||||
fdt.property_u32("#size-cells", 2)?;
|
|
||||||
fdt.property_array_u64("reg", ®)?;
|
|
||||||
fdt.property_u32("#interrupt-cells", 1)?;
|
|
||||||
fdt.property_null("interrupt-map")?;
|
|
||||||
fdt.property_null("interrupt-map-mask")?;
|
|
||||||
fdt.property_null("dma-coherent")?;
|
|
||||||
fdt.property_array_u32("msi-map", &msi_map)?;
|
|
||||||
fdt.property_u32("msi-parent", AIA_IMSIC_PHANDLE)?;
|
|
||||||
|
|
||||||
fdt.end_node(pci_node)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse the DTB binary and print for debugging
|
|
||||||
pub fn print_fdt(dtb: &[u8]) {
|
|
||||||
match fdt_parser::Fdt::new(dtb) {
|
|
||||||
Ok(fdt) => {
|
|
||||||
if let Some(root) = fdt.find_node("/") {
|
|
||||||
debug!("Printing the FDT:");
|
|
||||||
print_node(root, 0);
|
|
||||||
} else {
|
|
||||||
debug!("Failed to find root node in FDT for debugging.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(_) => debug!("Failed to parse FDT for debugging."),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn print_node(node: fdt_parser::node::FdtNode<'_, '_>, n_spaces: usize) {
|
|
||||||
debug!("{:indent$}{}/", "", node.name, indent = n_spaces);
|
|
||||||
for property in node.properties() {
|
|
||||||
let name = property.name;
|
|
||||||
|
|
||||||
// If the property is 'compatible', its value requires special handling.
|
|
||||||
// The u8 array could contain multiple null-terminated strings.
|
|
||||||
// We copy the original array and simply replace all 'null' characters with spaces.
|
|
||||||
let value = if name == "compatible" {
|
|
||||||
let mut compatible = vec![0u8; 256];
|
|
||||||
let handled_value = property
|
|
||||||
.value
|
|
||||||
.iter()
|
|
||||||
.map(|&c| if c == 0 { b' ' } else { c })
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
let len = cmp::min(255, handled_value.len());
|
|
||||||
compatible[..len].copy_from_slice(&handled_value[..len]);
|
|
||||||
compatible[..(len + 1)].to_vec()
|
|
||||||
} else {
|
|
||||||
property.value.to_vec()
|
|
||||||
};
|
|
||||||
let value = &value;
|
|
||||||
|
|
||||||
// Now the value can be either:
|
|
||||||
// - A null-terminated C string, or
|
|
||||||
// - Binary data
|
|
||||||
// We follow a very simple logic to present the value:
|
|
||||||
// - At first, try to convert it to CStr and print,
|
|
||||||
// - If failed, print it as u32 array.
|
|
||||||
let value_result = match CStr::from_bytes_with_nul(value) {
|
|
||||||
Ok(value_cstr) => match value_cstr.to_str() {
|
|
||||||
Ok(value_str) => Some(value_str),
|
|
||||||
Err(_e) => None,
|
|
||||||
},
|
|
||||||
Err(_e) => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(value_str) = value_result {
|
|
||||||
debug!(
|
|
||||||
"{:indent$}{} : {:#?}",
|
|
||||||
"",
|
|
||||||
name,
|
|
||||||
value_str,
|
|
||||||
indent = (n_spaces + 2)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
let mut array = Vec::with_capacity(256);
|
|
||||||
array.resize(value.len() / 4, 0u32);
|
|
||||||
BigEndian::read_u32_into(value, &mut array);
|
|
||||||
debug!(
|
|
||||||
"{:indent$}{} : {:X?}",
|
|
||||||
"",
|
|
||||||
name,
|
|
||||||
array,
|
|
||||||
indent = (n_spaces + 2)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print children nodes if there is any
|
|
||||||
for child in node.children() {
|
|
||||||
print_node(child, n_spaces + 2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
// Copyright © 2024 Institute of Software, CAS. All rights reserved.
|
|
||||||
// Copyright 2020 Arm Limited (or its affiliates). All rights reserved.
|
|
||||||
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
//
|
|
||||||
// Memory layout of RISC-V 64-bit guest:
|
|
||||||
//
|
|
||||||
// Physical +---------------------------------------------------------------+
|
|
||||||
// address | |
|
|
||||||
// end | |
|
|
||||||
// ~ ~ ~ ~
|
|
||||||
// | |
|
|
||||||
// | Highmem PCI MMIO space |
|
|
||||||
// | |
|
|
||||||
// RAM end +---------------------------------------------------------------+
|
|
||||||
// (dynamic, | |
|
|
||||||
// including | |
|
|
||||||
// hotplug ~ ~ ~ ~
|
|
||||||
// memory) | |
|
|
||||||
// | DRAM |
|
|
||||||
// | |
|
|
||||||
// | |
|
|
||||||
// | |
|
|
||||||
// | |
|
|
||||||
// 1 GB +---------------------------------------------------------------+
|
|
||||||
// | |
|
|
||||||
// | PCI MMCONFIG space |
|
|
||||||
// | |
|
|
||||||
// 768 MB +---------------------------------------------------------------+
|
|
||||||
// | |
|
|
||||||
// | |
|
|
||||||
// | PCI MMIO space |
|
|
||||||
// | |
|
|
||||||
// 256 MB +---------------------------------------------------------------|
|
|
||||||
// | |
|
|
||||||
// | Legacy devices space |
|
|
||||||
// | |
|
|
||||||
// 128 MB +---------------------------------------------------------------|
|
|
||||||
// | |
|
|
||||||
// | IMSICs |
|
|
||||||
// | |
|
|
||||||
// 64 MB +---------------------------------------------------------------+
|
|
||||||
// | |
|
|
||||||
// | APLICs |
|
|
||||||
// | |
|
|
||||||
// 0 GB +---------------------------------------------------------------+
|
|
||||||
//
|
|
||||||
//
|
|
||||||
|
|
||||||
use vm_memory::GuestAddress;
|
|
||||||
|
|
||||||
/// AIA related devices
|
|
||||||
/// See https://elixir.bootlin.com/linux/v6.10/source/arch/riscv/include/uapi/asm/kvm.h
|
|
||||||
/// 0x0 ~ 0x0400_0000 (64 MiB) resides APLICs
|
|
||||||
pub const APLIC_START: GuestAddress = GuestAddress(0);
|
|
||||||
pub const APLIC_SIZE: u64 = 0x4000;
|
|
||||||
|
|
||||||
/// 0x0400_0000 ~ 0x0800_0000 (64 MiB) resides IMSICs
|
|
||||||
pub const IMSIC_START: GuestAddress = GuestAddress(0x0400_0000);
|
|
||||||
pub const IMSIC_SIZE: u64 = 0x1000;
|
|
||||||
|
|
||||||
/// Below this address will reside the AIA, above this address will reside the MMIO devices.
|
|
||||||
const MAPPED_IO_START: GuestAddress = GuestAddress(0x0800_0000);
|
|
||||||
|
|
||||||
/// Space 0x0800_0000 ~ 0x1000_0000 is reserved for legacy devices.
|
|
||||||
pub const LEGACY_SERIAL_MAPPED_IO_START: GuestAddress = MAPPED_IO_START;
|
|
||||||
|
|
||||||
/// Space 0x0905_0000 ~ 0x0906_0000 is reserved for pcie io address
|
|
||||||
pub const MEM_PCI_IO_START: GuestAddress = GuestAddress(0x0905_0000);
|
|
||||||
pub const MEM_PCI_IO_SIZE: u64 = 0x1_0000;
|
|
||||||
|
|
||||||
/// Starting from 0x1000_0000 (256MiB) to 0x3000_0000 (768MiB) is used for PCIE MMIO
|
|
||||||
pub const MEM_32BIT_DEVICES_START: GuestAddress = GuestAddress(0x1000_0000);
|
|
||||||
pub const MEM_32BIT_DEVICES_SIZE: u64 = 0x2000_0000;
|
|
||||||
|
|
||||||
/// PCI MMCONFIG space (start: after the device space at 768MiB, length: 256MiB)
|
|
||||||
pub const PCI_MMCONFIG_START: GuestAddress = GuestAddress(0x3000_0000);
|
|
||||||
pub const PCI_MMCONFIG_SIZE: u64 = 256 << 20;
|
|
||||||
// One bus with potentially 256 devices (32 slots x 8 functions).
|
|
||||||
pub const PCI_MMIO_CONFIG_SIZE_PER_SEGMENT: u64 = 4096 * 256;
|
|
||||||
|
|
||||||
/// Start of RAM.
|
|
||||||
pub const RAM_START: GuestAddress = GuestAddress(0x4000_0000);
|
|
||||||
|
|
||||||
/// Kernel command line maximum size on RISC-V.
|
|
||||||
/// See https://elixir.bootlin.com/linux/v6.10/source/arch/riscv/include/uapi/asm/setup.h
|
|
||||||
pub const CMDLINE_MAX_SIZE: usize = 1024;
|
|
||||||
|
|
||||||
/// FDT is at the beginning of RAM.
|
|
||||||
pub const FDT_START: GuestAddress = RAM_START;
|
|
||||||
pub const FDT_MAX_SIZE: u64 = 0x1_0000;
|
|
||||||
|
|
||||||
/// Kernel start after FDT
|
|
||||||
pub const KERNEL_START: GuestAddress = GuestAddress(RAM_START.0 + FDT_MAX_SIZE);
|
|
||||||
|
|
||||||
/// Pci high memory base
|
|
||||||
pub const PCI_HIGH_BASE: GuestAddress = GuestAddress(0x2_0000_0000);
|
|
||||||
|
|
||||||
/// First usable interrupt on riscv64
|
|
||||||
pub const IRQ_BASE: u32 = 0;
|
|
||||||
|
|
||||||
// As per https://elixir.bootlin.com/linux/v6.10/source/arch/riscv/include/asm/kvm_host.h#L31
|
|
||||||
/// Number of supported interrupts
|
|
||||||
pub const IRQ_NUM: u32 = 1023;
|
|
||||||
@@ -1,173 +0,0 @@
|
|||||||
// Copyright © 2024 Institute of Software, CAS. All rights reserved.
|
|
||||||
// Copyright 2020 Arm Limited (or its affiliates). All rights reserved.
|
|
||||||
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
/// Module for the flattened device tree.
|
|
||||||
pub mod fdt;
|
|
||||||
/// Layout for this riscv64 system.
|
|
||||||
pub mod layout;
|
|
||||||
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::fmt::Debug;
|
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
|
|
||||||
use hypervisor::arch::riscv64::aia::Vaia;
|
|
||||||
use log::{log_enabled, Level};
|
|
||||||
use thiserror::Error;
|
|
||||||
use vm_memory::{Address, GuestAddress, GuestMemory, GuestMemoryAtomic};
|
|
||||||
|
|
||||||
pub use self::fdt::DeviceInfoForFdt;
|
|
||||||
use crate::{DeviceType, GuestMemoryMmap, PciSpaceInfo, RegionType};
|
|
||||||
|
|
||||||
pub const _NSIG: i32 = 65;
|
|
||||||
|
|
||||||
/// Errors thrown while configuring riscv64 system.
|
|
||||||
#[derive(Debug, Error)]
|
|
||||||
pub enum Error {
|
|
||||||
/// Failed to create a FDT.
|
|
||||||
#[error("Failed to create a FDT")]
|
|
||||||
SetupFdt,
|
|
||||||
|
|
||||||
/// Failed to write FDT to memory.
|
|
||||||
#[error("Failed to write FDT to memory: {0}")]
|
|
||||||
WriteFdtToMemory(#[source] fdt::Error),
|
|
||||||
|
|
||||||
/// Failed to create a AIA.
|
|
||||||
#[error("Failed to create a AIA")]
|
|
||||||
SetupAia,
|
|
||||||
|
|
||||||
/// Failed to compute the initramfs address.
|
|
||||||
#[error("Failed to compute the initramfs address")]
|
|
||||||
InitramfsAddress,
|
|
||||||
|
|
||||||
/// Error configuring the general purpose registers
|
|
||||||
#[error("Error configuring the general purpose registers: {0}")]
|
|
||||||
RegsConfiguration(#[source] hypervisor::HypervisorCpuError),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Error> for super::Error {
|
|
||||||
fn from(e: Error) -> super::Error {
|
|
||||||
super::Error::PlatformSpecific(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
|
||||||
/// Specifies the entry point address where the guest must start
|
|
||||||
/// executing code.
|
|
||||||
pub struct EntryPoint {
|
|
||||||
/// Address in guest memory where the guest must start execution
|
|
||||||
pub entry_addr: GuestAddress,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Configure the specified VCPU, and return its MPIDR.
|
|
||||||
pub fn configure_vcpu(
|
|
||||||
vcpu: &Arc<dyn hypervisor::Vcpu>,
|
|
||||||
id: u8,
|
|
||||||
boot_setup: Option<(EntryPoint, &GuestMemoryAtomic<GuestMemoryMmap>)>,
|
|
||||||
) -> super::Result<()> {
|
|
||||||
if let Some((kernel_entry_point, _guest_memory)) = boot_setup {
|
|
||||||
vcpu.setup_regs(
|
|
||||||
id,
|
|
||||||
kernel_entry_point.entry_addr.raw_value(),
|
|
||||||
layout::FDT_START.raw_value(),
|
|
||||||
)
|
|
||||||
.map_err(Error::RegsConfiguration)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn arch_memory_regions() -> Vec<(GuestAddress, usize, RegionType)> {
|
|
||||||
vec![
|
|
||||||
// 0 MiB ~ 256 MiB: AIA and legacy devices
|
|
||||||
(
|
|
||||||
GuestAddress(0),
|
|
||||||
layout::MEM_32BIT_DEVICES_START.0 as usize,
|
|
||||||
RegionType::Reserved,
|
|
||||||
),
|
|
||||||
// 256 MiB ~ 768 MiB: MMIO space
|
|
||||||
(
|
|
||||||
layout::MEM_32BIT_DEVICES_START,
|
|
||||||
layout::MEM_32BIT_DEVICES_SIZE as usize,
|
|
||||||
RegionType::SubRegion,
|
|
||||||
),
|
|
||||||
// 768 MiB ~ 1 GiB: reserved. The leading 256M for PCIe MMCONFIG space
|
|
||||||
(
|
|
||||||
layout::PCI_MMCONFIG_START,
|
|
||||||
layout::PCI_MMCONFIG_SIZE as usize,
|
|
||||||
RegionType::Reserved,
|
|
||||||
),
|
|
||||||
// 1GiB ~ inf: RAM
|
|
||||||
(layout::RAM_START, usize::MAX, RegionType::Ram),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Configures the system and should be called once per vm before starting vcpu threads.
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
|
||||||
pub fn configure_system<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHasher>(
|
|
||||||
guest_mem: &GuestMemoryMmap,
|
|
||||||
cmdline: &str,
|
|
||||||
num_vcpu: u32,
|
|
||||||
device_info: &HashMap<(DeviceType, String), T, S>,
|
|
||||||
initrd: &Option<super::InitramfsConfig>,
|
|
||||||
pci_space_info: &[PciSpaceInfo],
|
|
||||||
aia_device: &Arc<Mutex<dyn Vaia>>,
|
|
||||||
) -> super::Result<()> {
|
|
||||||
let fdt_final = fdt::create_fdt(
|
|
||||||
guest_mem,
|
|
||||||
cmdline,
|
|
||||||
num_vcpu,
|
|
||||||
device_info,
|
|
||||||
aia_device,
|
|
||||||
initrd,
|
|
||||||
pci_space_info,
|
|
||||||
)
|
|
||||||
.map_err(|_| Error::SetupFdt)?;
|
|
||||||
|
|
||||||
if log_enabled!(Level::Debug) {
|
|
||||||
fdt::print_fdt(&fdt_final);
|
|
||||||
}
|
|
||||||
|
|
||||||
fdt::write_fdt_to_memory(fdt_final, guest_mem).map_err(Error::WriteFdtToMemory)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the memory address where the initramfs could be loaded.
|
|
||||||
pub fn initramfs_load_addr(
|
|
||||||
guest_mem: &GuestMemoryMmap,
|
|
||||||
initramfs_size: usize,
|
|
||||||
) -> super::Result<u64> {
|
|
||||||
let round_to_pagesize = |size| (size + (super::PAGE_SIZE - 1)) & !(super::PAGE_SIZE - 1);
|
|
||||||
match guest_mem
|
|
||||||
.last_addr()
|
|
||||||
.checked_sub(round_to_pagesize(initramfs_size) as u64 - 1)
|
|
||||||
{
|
|
||||||
Some(offset) => {
|
|
||||||
if guest_mem.address_in_range(offset) {
|
|
||||||
Ok(offset.raw_value())
|
|
||||||
} else {
|
|
||||||
Err(super::Error::PlatformSpecific(Error::InitramfsAddress))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => Err(super::Error::PlatformSpecific(Error::InitramfsAddress)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_host_cpu_phys_bits(_hypervisor: &Arc<dyn hypervisor::Hypervisor>) -> u8 {
|
|
||||||
40
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_arch_memory_regions_dram() {
|
|
||||||
let regions = arch_memory_regions();
|
|
||||||
assert_eq!(4, regions.len());
|
|
||||||
assert_eq!(layout::RAM_START, regions[3].0);
|
|
||||||
assert_eq!(RegionType::Ram, regions[3].2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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],
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,15 +5,15 @@
|
|||||||
// 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 std::{mem, result, slice};
|
|
||||||
|
|
||||||
use libc::c_uchar;
|
|
||||||
use thiserror::Error;
|
|
||||||
use vm_memory::{Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryError};
|
|
||||||
|
|
||||||
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_char;
|
||||||
|
use std::io;
|
||||||
|
use std::mem;
|
||||||
|
use std::result;
|
||||||
|
use std::slice;
|
||||||
|
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
|
||||||
// trait (in this case `ByteValued`) where:
|
// trait (in this case `ByteValued`) where:
|
||||||
@@ -50,41 +50,30 @@ 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(#[source] 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(#[source] GuestMemoryError),
|
|
||||||
/// Failure to write MP CPU entry.
|
/// Failure to write MP CPU entry.
|
||||||
#[error("Failure to write MP CPU entry: {0}")]
|
WriteMpcCpu(GuestMemoryError),
|
||||||
WriteMpcCpu(#[source] GuestMemoryError),
|
|
||||||
/// Failure to write MP ioapic entry.
|
/// Failure to write MP ioapic entry.
|
||||||
#[error("Failure to write MP ioapic entry: {0}")]
|
WriteMpcIoapic(GuestMemoryError),
|
||||||
WriteMpcIoapic(#[source] GuestMemoryError),
|
|
||||||
/// Failure to write MP bus entry.
|
/// Failure to write MP bus entry.
|
||||||
#[error("Failure to write MP bus entry: {0}")]
|
WriteMpcBus(GuestMemoryError),
|
||||||
WriteMpcBus(#[source] 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(#[source] 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(#[source] GuestMemoryError),
|
|
||||||
/// Failure to write MP table header.
|
/// Failure to write MP table header.
|
||||||
#[error("Failure to write MP table header: {0}")]
|
WriteMpcTable(GuestMemoryError),
|
||||||
WriteMpcTable(#[source] GuestMemoryError),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Result<T> = result::Result<T, Error>;
|
pub type Result<T> = result::Result<T, Error>;
|
||||||
@@ -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)?;
|
||||||
}
|
}
|
||||||
@@ -304,11 +289,9 @@ pub fn setup_mptable(
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use vm_memory::bitmap::BitmapSlice;
|
|
||||||
use vm_memory::{GuestUsize, VolatileMemoryError, VolatileSlice, WriteVolatile};
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::layout::MPTABLE_START;
|
use crate::layout::MPTABLE_START;
|
||||||
|
use vm_memory::{GuestAddress, GuestUsize};
|
||||||
|
|
||||||
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();
|
||||||
|
|
||||||
setup_mptable(MPTABLE_START, &mem, num_cpus, None).unwrap_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);
|
||||||
result.unwrap_err();
|
assert!(result.is_err());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,58 +6,41 @@
|
|||||||
// 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 std::sync::Arc;
|
use crate::layout::{BOOT_GDT_START, BOOT_IDT_START, PVH_INFO_START};
|
||||||
use std::{mem, result};
|
use crate::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};
|
use hypervisor::arch::x86::{FpuState, SpecialRegisters, StandardRegisters};
|
||||||
use thiserror::Error;
|
use std::sync::Arc;
|
||||||
|
use std::{mem, result};
|
||||||
use vm_memory::{Address, Bytes, GuestMemory, GuestMemoryError};
|
use vm_memory::{Address, Bytes, GuestMemory, GuestMemoryError};
|
||||||
|
|
||||||
use crate::layout::{
|
#[derive(Debug)]
|
||||||
BOOT_GDT_START, BOOT_IDT_START, BOOT_STACK_POINTER, PVH_INFO_START, ZERO_PAGE_START,
|
|
||||||
};
|
|
||||||
use crate::{EntryPoint, GuestMemoryMmap};
|
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
|
||||||
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(#[source] 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(#[source] hypervisor::HypervisorCpuError),
|
|
||||||
/// Failed to configure the FPU.
|
/// Failed to configure the FPU.
|
||||||
#[error("Failed to configure the FPU: {0}")]
|
SetFpuRegisters(hypervisor::HypervisorCpuError),
|
||||||
SetFpuRegisters(#[source] hypervisor::HypervisorCpuError),
|
|
||||||
/// Setting up MSRs failed.
|
/// Setting up MSRs failed.
|
||||||
#[error("Setting up MSRs failed: {0}")]
|
SetModelSpecificRegisters(hypervisor::HypervisorCpuError),
|
||||||
SetModelSpecificRegisters(#[source] 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(#[source] 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(#[source] GuestMemoryError),
|
|
||||||
/// Writing the IDT to RAM failed.
|
/// Writing the IDT to RAM failed.
|
||||||
#[error("Writing the IDT to RAM failed: {0}")]
|
WriteIdt(GuestMemoryError),
|
||||||
WriteIdt(#[source] GuestMemoryError),
|
|
||||||
/// Writing PDPTE to RAM failed.
|
/// Writing PDPTE to RAM failed.
|
||||||
#[error("Writing PDPTE to RAM failed: {0}")]
|
WritePdpteAddress(GuestMemoryError),
|
||||||
WritePdpteAddress(#[source] GuestMemoryError),
|
|
||||||
/// Writing PDE to RAM failed.
|
/// Writing PDE to RAM failed.
|
||||||
#[error("Writing PDE to RAM failed: {0}")]
|
WritePdeAddress(GuestMemoryError),
|
||||||
WritePdeAddress(#[source] GuestMemoryError),
|
|
||||||
/// Writing PML4 to RAM failed.
|
/// Writing PML4 to RAM failed.
|
||||||
#[error("Writing PML4 to RAM failed: {0}")]
|
WritePml4Address(GuestMemoryError),
|
||||||
WritePml4Address(#[source] GuestMemoryError),
|
|
||||||
/// Writing PML5 to RAM failed.
|
/// Writing PML5 to RAM failed.
|
||||||
#[error("Writing PML5 to RAM failed: {0}")]
|
WritePml5Address(GuestMemoryError),
|
||||||
WritePml5Address(#[source] GuestMemoryError),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Result<T> = result::Result<T, Error>;
|
pub type Result<T> = result::Result<T, Error>;
|
||||||
@@ -94,21 +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 mut regs = vcpu.create_standard_regs();
|
let regs = StandardRegisters {
|
||||||
match entry_point.setup_header {
|
rflags: 0x0000000000000002u64,
|
||||||
None => {
|
rbx: PVH_INFO_START.raw_value(),
|
||||||
regs.set_rflags(0x0000000000000002u64);
|
rip: boot_ip,
|
||||||
regs.set_rip(entry_point.entry_addr.raw_value());
|
..Default::default()
|
||||||
regs.set_rbx(PVH_INFO_START.raw_value());
|
|
||||||
}
|
|
||||||
Some(_) => {
|
|
||||||
regs.set_rflags(0x0000000000000002u64);
|
|
||||||
regs.set_rip(entry_point.entry_addr.raw_value());
|
|
||||||
regs.set_rsp(BOOT_STACK_POINTER.raw_value());
|
|
||||||
regs.set_rsi(ZERO_PAGE_START.raw_value());
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
vcpu.set_regs(®s).map_err(Error::SetBaseRegisters)
|
vcpu.set_regs(®s).map_err(Error::SetBaseRegisters)
|
||||||
}
|
}
|
||||||
@@ -188,9 +163,9 @@ pub fn configure_segments_and_sregs(
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use vm_memory::GuestAddress;
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::GuestMemoryMmap;
|
||||||
|
use vm_memory::GuestAddress;
|
||||||
|
|
||||||
fn create_guest_mem() -> GuestMemoryMmap {
|
fn create_guest_mem() -> GuestMemoryMmap {
|
||||||
GuestMemoryMmap::from_ranges(&[(GuestAddress(0), 0x10000)]).unwrap()
|
GuestMemoryMmap::from_ranges(&[(GuestAddress(0), 0x10000)]).unwrap()
|
||||||
|
|||||||
@@ -6,35 +6,53 @@
|
|||||||
//
|
//
|
||||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||||
|
|
||||||
use std::{mem, result, slice};
|
|
||||||
|
|
||||||
use thiserror::Error;
|
|
||||||
use uuid::Uuid;
|
|
||||||
use vm_memory::{Address, ByteValued, Bytes, GuestAddress};
|
|
||||||
|
|
||||||
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::result;
|
||||||
|
use std::slice;
|
||||||
|
use uuid::Uuid;
|
||||||
|
use vm_memory::ByteValued;
|
||||||
|
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(#[source] 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>;
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
// Copyright © 2021 Intel Corporation
|
// Copyright © 2021 Intel Corporation
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
use crate::GuestMemoryMmap;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{Read, Seek, SeekFrom};
|
use std::io::{Read, Seek, SeekFrom};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use vm_memory::{ByteValued, Bytes, GuestAddress, GuestMemoryError};
|
use vm_memory::{ByteValued, Bytes, GuestAddress, GuestMemoryError};
|
||||||
|
|
||||||
use crate::GuestMemoryMmap;
|
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum TdvfError {
|
pub enum TdvfError {
|
||||||
#[error("Failed read TDVF descriptor: {0}")]
|
#[error("Failed read TDVF descriptor: {0}")]
|
||||||
@@ -35,7 +33,7 @@ const TABLE_FOOTER_GUID: &str = "96b582de-1fb2-45f7-baea-a366c55a082d";
|
|||||||
const TDVF_METADATA_OFFSET_GUID: &str = "e47a6535-984a-4798-865e-4685a7bf8ec2";
|
const TDVF_METADATA_OFFSET_GUID: &str = "e47a6535-984a-4798-865e-4685a7bf8ec2";
|
||||||
|
|
||||||
// TDVF_DESCRIPTOR
|
// TDVF_DESCRIPTOR
|
||||||
#[repr(C, packed)]
|
#[repr(packed)]
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct TdvfDescriptor {
|
pub struct TdvfDescriptor {
|
||||||
signature: [u8; 4],
|
signature: [u8; 4],
|
||||||
@@ -45,7 +43,7 @@ pub struct TdvfDescriptor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TDVF_SECTION
|
// TDVF_SECTION
|
||||||
#[repr(C, packed)]
|
#[repr(packed)]
|
||||||
#[derive(Clone, Copy, Default, Debug)]
|
#[derive(Clone, Copy, Default, Debug)]
|
||||||
pub struct TdvfSection {
|
pub struct TdvfSection {
|
||||||
pub data_offset: u32,
|
pub data_offset: u32,
|
||||||
@@ -100,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)?;
|
||||||
@@ -209,7 +207,7 @@ enum HobType {
|
|||||||
EndOfHobList = 0xffff,
|
EndOfHobList = 0xffff,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C, packed)]
|
#[repr(C)]
|
||||||
#[derive(Copy, Clone, Default, Debug)]
|
#[derive(Copy, Clone, Default, Debug)]
|
||||||
struct HobHeader {
|
struct HobHeader {
|
||||||
r#type: HobType,
|
r#type: HobType,
|
||||||
@@ -217,7 +215,7 @@ struct HobHeader {
|
|||||||
reserved: u32,
|
reserved: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C, packed)]
|
#[repr(C)]
|
||||||
#[derive(Copy, Clone, Default, Debug)]
|
#[derive(Copy, Clone, Default, Debug)]
|
||||||
struct HobHandoffInfoTable {
|
struct HobHandoffInfoTable {
|
||||||
header: HobHeader,
|
header: HobHeader,
|
||||||
@@ -230,7 +228,7 @@ struct HobHandoffInfoTable {
|
|||||||
efi_end_of_hob_list: u64,
|
efi_end_of_hob_list: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C, packed)]
|
#[repr(C)]
|
||||||
#[derive(Copy, Clone, Default, Debug)]
|
#[derive(Copy, Clone, Default, Debug)]
|
||||||
struct EfiGuid {
|
struct EfiGuid {
|
||||||
data1: u32,
|
data1: u32,
|
||||||
@@ -239,7 +237,7 @@ struct EfiGuid {
|
|||||||
data4: [u8; 8],
|
data4: [u8; 8],
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C, packed)]
|
#[repr(C)]
|
||||||
#[derive(Copy, Clone, Default, Debug)]
|
#[derive(Copy, Clone, Default, Debug)]
|
||||||
struct HobResourceDescriptor {
|
struct HobResourceDescriptor {
|
||||||
header: HobHeader,
|
header: HobHeader,
|
||||||
@@ -250,7 +248,7 @@ struct HobResourceDescriptor {
|
|||||||
resource_length: u64,
|
resource_length: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C, packed)]
|
#[repr(C)]
|
||||||
#[derive(Copy, Clone, Default, Debug)]
|
#[derive(Copy, Clone, Default, Debug)]
|
||||||
struct HobGuidType {
|
struct HobGuidType {
|
||||||
header: HobHeader,
|
header: HobHeader,
|
||||||
@@ -266,14 +264,14 @@ pub enum PayloadImageType {
|
|||||||
RawVmLinux,
|
RawVmLinux,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C, packed)]
|
#[repr(C)]
|
||||||
#[derive(Copy, Clone, Default, Debug)]
|
#[derive(Copy, Clone, Default, Debug)]
|
||||||
pub struct PayloadInfo {
|
pub struct PayloadInfo {
|
||||||
pub image_type: PayloadImageType,
|
pub image_type: PayloadImageType,
|
||||||
pub entry_point: u64,
|
pub entry_point: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C, packed)]
|
#[repr(C)]
|
||||||
#[derive(Copy, Clone, Default, Debug)]
|
#[derive(Copy, Clone, Default, Debug)]
|
||||||
struct TdPayload {
|
struct TdPayload {
|
||||||
guid_type: HobGuidType,
|
guid_type: HobGuidType,
|
||||||
@@ -299,7 +297,7 @@ pub struct TdHob {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn align_hob(v: u64) -> u64 {
|
fn align_hob(v: u64) -> u64 {
|
||||||
v.div_ceil(8) * 8
|
(v + 7) / 8 * 8
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TdHob {
|
impl TdHob {
|
||||||
|
|||||||
@@ -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.4", optional = true }
|
|
||||||
libc = "0.2.167"
|
|
||||||
log = "0.4.22"
|
|
||||||
remain = "0.2.14"
|
|
||||||
serde = { version = "1.0.208", features = ["derive"] }
|
|
||||||
smallvec = "1.13.2"
|
|
||||||
thiserror = "2.0.6"
|
|
||||||
uuid = { version = "1.12.1", features = ["v4"] }
|
|
||||||
virtio-bindings = { workspace = true, features = ["virtio-v5_0_0"] }
|
|
||||||
virtio-queue = { workspace = true }
|
|
||||||
vm-memory = { workspace = true, features = [
|
|
||||||
"backend-atomic",
|
|
||||||
"backend-bitmap",
|
|
||||||
"backend-mmap",
|
|
||||||
] }
|
|
||||||
vm-virtio = { path = "../vm-virtio" }
|
|
||||||
vmm-sys-util = { workspace = true }
|
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
// Copyright © 2021 Intel Corporation
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
|
||||||
|
|
||||||
use std::marker::PhantomData;
|
|
||||||
use std::os::fd::{AsRawFd, OwnedFd, RawFd};
|
|
||||||
|
|
||||||
use thiserror::Error;
|
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
|
||||||
|
|
||||||
use crate::DiskTopology;
|
|
||||||
|
|
||||||
#[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>;
|
|
||||||
|
|
||||||
/// A wrapper for [`RawFd`] capturing the lifetime of a corresponding [`DiskFile`].
|
|
||||||
///
|
|
||||||
/// This fulfills the same role as [`BorrowedFd`] but is tailored to the limitations
|
|
||||||
/// by some implementations of [`DiskFile`], which wrap the effective [`File`]
|
|
||||||
/// in an `Arc<Mutex<T>>`, making the use of [`BorrowedFd`] impossible.
|
|
||||||
///
|
|
||||||
/// [`BorrowedFd`]: std::os::fd::BorrowedFd
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
|
||||||
pub struct BorrowedDiskFd<'fd> {
|
|
||||||
raw_fd: RawFd,
|
|
||||||
_lifetime: PhantomData<&'fd OwnedFd>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BorrowedDiskFd<'_> {
|
|
||||||
pub(super) fn new(raw_fd: RawFd) -> Self {
|
|
||||||
Self {
|
|
||||||
raw_fd,
|
|
||||||
_lifetime: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsRawFd for BorrowedDiskFd<'_> {
|
|
||||||
fn as_raw_fd(&self) -> RawFd {
|
|
||||||
self.raw_fd
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Abstraction over the effective [`File`] backing up a block device,
|
|
||||||
/// with support for synchronous and asynchronous I/O.
|
|
||||||
///
|
|
||||||
/// This allows abstracting over raw image formats as well as structured
|
|
||||||
/// image formats.
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
/// Returns the file descriptor of the underlying disk image file.
|
|
||||||
///
|
|
||||||
/// The file descriptor is supposed to be used for `fcntl()` calls but no
|
|
||||||
/// other operation.
|
|
||||||
fn fd(&mut self) -> BorrowedDiskFd;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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,173 +0,0 @@
|
|||||||
// Copyright © 2025 Cyberus Technology GmbH
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
//
|
|
||||||
|
|
||||||
//! Helpers for advisory file locking.
|
|
||||||
//!
|
|
||||||
//! Under the hood, the implementation uses OFD locks for the entire file,
|
|
||||||
//! as described in [[0]]. The advantage over `F_SETLKW` (currently used by
|
|
||||||
//! Rust std: `File::try_lock()`) is that only the very last `close()` on a
|
|
||||||
//! file descriptor releases the lock. This prevents mistakes and unexpected
|
|
||||||
//! behavior.
|
|
||||||
//!
|
|
||||||
//! [0]: <https://apenwarr.ca/log/20101213>.
|
|
||||||
|
|
||||||
use std::fmt::Debug;
|
|
||||||
use std::io;
|
|
||||||
use std::os::fd::{AsRawFd, RawFd};
|
|
||||||
|
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
/// Errors that can happen when working with file locks.
|
|
||||||
#[derive(Error, Debug)]
|
|
||||||
pub enum LockError {
|
|
||||||
/// The file is already locked.
|
|
||||||
///
|
|
||||||
/// A call to [`get_lock_state`] can help to identify the reason.
|
|
||||||
#[error("The file is already locked")]
|
|
||||||
AlreadyLocked,
|
|
||||||
/// IO error.
|
|
||||||
#[error("The lock state could not be checked or set: {0}")]
|
|
||||||
Io(#[source] io::Error),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Commands for use with [`fcntl`].
|
|
||||||
#[allow(non_camel_case_types)]
|
|
||||||
enum FcntlArg<'a> {
|
|
||||||
/// Set an OFD lock from the given lock description.
|
|
||||||
F_OFD_SETLK(&'a libc::flock),
|
|
||||||
/// Get the first OFD lock for the given lock description.
|
|
||||||
F_OFD_GETLK(&'a mut libc::flock),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Wrapper for [`libc::fcntl`] that properly sets the function arguments.
|
|
||||||
fn fcntl(fd: RawFd, arg: FcntlArg) -> libc::c_int {
|
|
||||||
// SAFETY: We use a valid FD.
|
|
||||||
unsafe {
|
|
||||||
match arg {
|
|
||||||
FcntlArg::F_OFD_SETLK(flock) => libc::fcntl(fd, libc::F_OFD_SETLK, flock),
|
|
||||||
FcntlArg::F_OFD_GETLK(flock) => libc::fcntl(fd, libc::F_OFD_GETLK, flock),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Describes the type of lock you want to set.
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
|
||||||
pub enum LockType {
|
|
||||||
/// Clear a lock.
|
|
||||||
Unlock,
|
|
||||||
/// Set a write lock (exclusive).
|
|
||||||
Write,
|
|
||||||
/// Set a read lock (shared).
|
|
||||||
Read,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LockType {
|
|
||||||
pub const fn to_libc_val(self) -> libc::c_int {
|
|
||||||
match self {
|
|
||||||
Self::Unlock => libc::F_UNLCK as libc::c_int,
|
|
||||||
Self::Write => libc::F_WRLCK as libc::c_int,
|
|
||||||
Self::Read => libc::F_RDLCK as libc::c_int,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Describes the current state of a lock.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum LockState {
|
|
||||||
/// No lock set.
|
|
||||||
Unlocked,
|
|
||||||
/// Locked for reading (non-exclusive).
|
|
||||||
SharedRead,
|
|
||||||
/// Locked for writing (exclusive mode).
|
|
||||||
ExclusiveWrite,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LockState {
|
|
||||||
fn new(value: libc::c_int) -> Self {
|
|
||||||
const F_UNLCK: libc::c_int = libc::F_UNLCK as libc::c_int;
|
|
||||||
const F_WRLCK: libc::c_int = libc::F_WRLCK as libc::c_int;
|
|
||||||
const F_RDLCK: libc::c_int = libc::F_RDLCK as libc::c_int;
|
|
||||||
match value {
|
|
||||||
F_UNLCK => Self::Unlocked,
|
|
||||||
F_WRLCK => Self::ExclusiveWrite,
|
|
||||||
F_RDLCK => Self::SharedRead,
|
|
||||||
// This is so unlikely that we want to avoid the complexity of
|
|
||||||
// coping with this error case. Can only fail if either Linux
|
|
||||||
// is broken or memory is messed up.
|
|
||||||
other => panic!("Unexpected lock state: {other}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a [`struct@libc::flock`] structure for the whole file.
|
|
||||||
const fn get_flock(lock_type: LockType) -> libc::flock {
|
|
||||||
libc::flock {
|
|
||||||
l_type: lock_type.to_libc_val() as libc::c_short,
|
|
||||||
l_whence: libc::SEEK_SET as libc::c_short,
|
|
||||||
l_start: 0,
|
|
||||||
l_len: 0, /* EOF */
|
|
||||||
l_pid: 0, /* filled by callee */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Tries to acquire a lock using [`fcntl`] with respect to the given
|
|
||||||
/// parameters.
|
|
||||||
///
|
|
||||||
/// Please note that `fcntl()` OFD locks are **advisory locks**, which do not
|
|
||||||
/// prevent to `open()` a file if a lock is already placed.
|
|
||||||
///
|
|
||||||
/// # Parameters
|
|
||||||
/// - `file`: The file to acquire a lock for [`LockType`]. The file's state will
|
|
||||||
/// be logically mutated, but not technically.
|
|
||||||
/// - `lock_type`: The [`LockType`]
|
|
||||||
pub fn try_acquire_lock<Fd: AsRawFd>(file: Fd, lock_type: LockType) -> Result<(), LockError> {
|
|
||||||
let flock = get_flock(lock_type);
|
|
||||||
|
|
||||||
let res = fcntl(file.as_raw_fd(), FcntlArg::F_OFD_SETLK(&flock));
|
|
||||||
match res {
|
|
||||||
0 => Ok(()),
|
|
||||||
-1 => {
|
|
||||||
let io_error = io::Error::last_os_error();
|
|
||||||
let errno = io_error.raw_os_error().unwrap();
|
|
||||||
match errno {
|
|
||||||
// See man page for error code:
|
|
||||||
// <https://man7.org/linux/man-pages/man2/fcntl.2.html>
|
|
||||||
libc::EAGAIN | libc::EACCES => Err(LockError::AlreadyLocked),
|
|
||||||
_ => Err(LockError::Io(io_error)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val => panic!("Unexpected return value from fcntl(): {val}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Clears a lock.
|
|
||||||
///
|
|
||||||
/// # Parameters
|
|
||||||
/// - `file`: The file to clear all locks for [`LockType`].
|
|
||||||
pub fn clear_lock<Fd: AsRawFd>(file: Fd) -> Result<(), LockError> {
|
|
||||||
try_acquire_lock(file, LockType::Unlock)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the current lock state using [`fcntl`] with respect to the given
|
|
||||||
/// parameters.
|
|
||||||
///
|
|
||||||
/// # Parameters
|
|
||||||
/// - `file`: The file for which to get the lock state.
|
|
||||||
pub fn get_lock_state<Fd: AsRawFd>(file: Fd) -> Result<LockState, LockError> {
|
|
||||||
let mut flock = get_flock(LockType::Write);
|
|
||||||
let res = fcntl(file.as_raw_fd(), FcntlArg::F_OFD_GETLK(&mut flock));
|
|
||||||
match res {
|
|
||||||
0 => {
|
|
||||||
let state = flock.l_type as libc::c_int;
|
|
||||||
let state = LockState::new(state);
|
|
||||||
Ok(state)
|
|
||||||
}
|
|
||||||
-1 => {
|
|
||||||
let io_error = io::Error::last_os_error();
|
|
||||||
Err(LockError::Io(io_error))
|
|
||||||
}
|
|
||||||
val => panic!("Unexpected return value from fcntl(): {val}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
// Copyright © 2021 Intel Corporation
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
use std::fs::File;
|
|
||||||
use std::io::{Read, Seek, SeekFrom, Write};
|
|
||||||
use std::os::unix::io::{AsRawFd, RawFd};
|
|
||||||
|
|
||||||
use crate::vhd::VhdFooter;
|
|
||||||
use crate::BlockBackend;
|
|
||||||
|
|
||||||
#[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,156 +0,0 @@
|
|||||||
// Copyright © 2023 Intel Corporation
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
|
||||||
//
|
|
||||||
// Copyright © 2023 Crusoe Energy Systems LLC
|
|
||||||
//
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
use crate::async_io::{
|
|
||||||
AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult,
|
|
||||||
};
|
|
||||||
use crate::DiskTopology;
|
|
||||||
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fd(&mut self) -> BorrowedDiskFd {
|
|
||||||
BorrowedDiskFd::new(self.file.as_raw_fd())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,221 +0,0 @@
|
|||||||
// Copyright © 2021 Intel Corporation
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
use std::fs::File;
|
|
||||||
use std::io::{self, Read, Seek, SeekFrom, Write};
|
|
||||||
|
|
||||||
use remain::sorted;
|
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
use crate::vhdx::vhdx_bat::{self, BatEntry, VhdxBatError};
|
|
||||||
use crate::vhdx::vhdx_metadata::{self, DiskSpec};
|
|
||||||
|
|
||||||
const SECTOR_SIZE: u64 = 512;
|
|
||||||
|
|
||||||
#[sorted]
|
|
||||||
#[derive(Error, Debug)]
|
|
||||||
pub enum VhdxIoError {
|
|
||||||
#[error("Invalid BAT entry state")]
|
|
||||||
InvalidBatEntryState,
|
|
||||||
#[error("Invalid BAT entry count")]
|
|
||||||
InvalidBatIndex,
|
|
||||||
#[error("Invalid disk size")]
|
|
||||||
InvalidDiskSize,
|
|
||||||
#[error("Failed reading sector blocks from file {0}")]
|
|
||||||
ReadSectorBlock(#[source] io::Error),
|
|
||||||
#[error("Failed changing file length {0}")]
|
|
||||||
ResizeFile(#[source] io::Error),
|
|
||||||
#[error("Differencing mode is not supported yet")]
|
|
||||||
UnsupportedMode,
|
|
||||||
#[error("Failed writing BAT to file {0}")]
|
|
||||||
WriteBat(#[source] VhdxBatError),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, VhdxIoError>;
|
|
||||||
|
|
||||||
macro_rules! align {
|
|
||||||
($n:expr, $align:expr) => {{
|
|
||||||
$n.div_ceil($align) * $align
|
|
||||||
}};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
struct Sector {
|
|
||||||
bat_index: u64,
|
|
||||||
free_sectors: u64,
|
|
||||||
free_bytes: u64,
|
|
||||||
file_offset: u64,
|
|
||||||
block_offset: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Sector {
|
|
||||||
/// Translate sector index and count of data in file to actual offsets and
|
|
||||||
/// BAT index.
|
|
||||||
pub fn new(
|
|
||||||
disk_spec: &DiskSpec,
|
|
||||||
bat: &[BatEntry],
|
|
||||||
sector_index: u64,
|
|
||||||
sector_count: u64,
|
|
||||||
) -> Result<Sector> {
|
|
||||||
let mut sector = Sector::default();
|
|
||||||
|
|
||||||
sector.bat_index = sector_index / disk_spec.sectors_per_block as u64;
|
|
||||||
sector.block_offset = sector_index % disk_spec.sectors_per_block as u64;
|
|
||||||
sector.free_sectors = disk_spec.sectors_per_block as u64 - sector.block_offset;
|
|
||||||
if sector.free_sectors > sector_count {
|
|
||||||
sector.free_sectors = sector_count;
|
|
||||||
}
|
|
||||||
|
|
||||||
sector.free_bytes = sector.free_sectors * disk_spec.logical_sector_size as u64;
|
|
||||||
sector.block_offset *= disk_spec.logical_sector_size as u64;
|
|
||||||
|
|
||||||
let bat_entry = match bat.get(sector.bat_index as usize) {
|
|
||||||
Some(entry) => entry.0,
|
|
||||||
None => {
|
|
||||||
return Err(VhdxIoError::InvalidBatIndex);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
sector.file_offset = bat_entry & vhdx_bat::BAT_FILE_OFF_MASK;
|
|
||||||
if sector.file_offset != 0 {
|
|
||||||
sector.file_offset += sector.block_offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(sector)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// VHDx IO read routine: requires relative sector index and count for the
|
|
||||||
/// requested data.
|
|
||||||
pub fn read(
|
|
||||||
f: &mut File,
|
|
||||||
buf: &mut [u8],
|
|
||||||
disk_spec: &DiskSpec,
|
|
||||||
bat: &[BatEntry],
|
|
||||||
mut sector_index: u64,
|
|
||||||
mut sector_count: u64,
|
|
||||||
) -> Result<usize> {
|
|
||||||
if disk_spec.has_parent {
|
|
||||||
return Err(VhdxIoError::UnsupportedMode);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut read_count: usize = 0;
|
|
||||||
while sector_count > 0 {
|
|
||||||
let sector = Sector::new(disk_spec, bat, sector_index, sector_count)?;
|
|
||||||
|
|
||||||
let bat_entry = match bat.get(sector.bat_index as usize) {
|
|
||||||
Some(entry) => entry.0,
|
|
||||||
None => {
|
|
||||||
return Err(VhdxIoError::InvalidBatIndex);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
match bat_entry & vhdx_bat::BAT_STATE_BIT_MASK {
|
|
||||||
vhdx_bat::PAYLOAD_BLOCK_NOT_PRESENT
|
|
||||||
| vhdx_bat::PAYLOAD_BLOCK_UNDEFINED
|
|
||||||
| vhdx_bat::PAYLOAD_BLOCK_UNMAPPED
|
|
||||||
| vhdx_bat::PAYLOAD_BLOCK_ZERO => {}
|
|
||||||
vhdx_bat::PAYLOAD_BLOCK_FULLY_PRESENT => {
|
|
||||||
f.seek(SeekFrom::Start(sector.file_offset))
|
|
||||||
.map_err(VhdxIoError::ReadSectorBlock)?;
|
|
||||||
f.read_exact(
|
|
||||||
&mut buf
|
|
||||||
[read_count..(read_count + (sector.free_sectors * SECTOR_SIZE) as usize)],
|
|
||||||
)
|
|
||||||
.map_err(VhdxIoError::ReadSectorBlock)?;
|
|
||||||
}
|
|
||||||
vhdx_bat::PAYLOAD_BLOCK_PARTIALLY_PRESENT => {
|
|
||||||
return Err(VhdxIoError::UnsupportedMode);
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
return Err(VhdxIoError::InvalidBatEntryState);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
sector_count -= sector.free_sectors;
|
|
||||||
sector_index += sector.free_sectors;
|
|
||||||
read_count += sector.free_bytes as usize;
|
|
||||||
}
|
|
||||||
Ok(read_count)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// VHDx IO write routine: requires relative sector index and count for the
|
|
||||||
/// requested data.
|
|
||||||
pub fn write(
|
|
||||||
f: &mut File,
|
|
||||||
buf: &[u8],
|
|
||||||
disk_spec: &mut DiskSpec,
|
|
||||||
bat_offset: u64,
|
|
||||||
bat: &mut [BatEntry],
|
|
||||||
mut sector_index: u64,
|
|
||||||
mut sector_count: u64,
|
|
||||||
) -> Result<usize> {
|
|
||||||
if disk_spec.has_parent {
|
|
||||||
return Err(VhdxIoError::UnsupportedMode);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut write_count: usize = 0;
|
|
||||||
while sector_count > 0 {
|
|
||||||
let sector = Sector::new(disk_spec, bat, sector_index, sector_count)?;
|
|
||||||
|
|
||||||
let bat_entry = match bat.get(sector.bat_index as usize) {
|
|
||||||
Some(entry) => entry.0,
|
|
||||||
None => {
|
|
||||||
return Err(VhdxIoError::InvalidBatIndex);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
match bat_entry & vhdx_bat::BAT_STATE_BIT_MASK {
|
|
||||||
vhdx_bat::PAYLOAD_BLOCK_NOT_PRESENT
|
|
||||||
| vhdx_bat::PAYLOAD_BLOCK_UNDEFINED
|
|
||||||
| vhdx_bat::PAYLOAD_BLOCK_UNMAPPED
|
|
||||||
| vhdx_bat::PAYLOAD_BLOCK_ZERO => {
|
|
||||||
let file_offset =
|
|
||||||
align!(disk_spec.image_size, vhdx_metadata::BLOCK_SIZE_MIN as u64);
|
|
||||||
let new_size = file_offset
|
|
||||||
.checked_add(disk_spec.block_size as u64)
|
|
||||||
.ok_or(VhdxIoError::InvalidDiskSize)?;
|
|
||||||
|
|
||||||
f.set_len(new_size).map_err(VhdxIoError::ResizeFile)?;
|
|
||||||
disk_spec.image_size = new_size;
|
|
||||||
|
|
||||||
let new_bat_entry = file_offset
|
|
||||||
| (vhdx_bat::PAYLOAD_BLOCK_FULLY_PRESENT & vhdx_bat::BAT_STATE_BIT_MASK);
|
|
||||||
bat[sector.bat_index as usize] = BatEntry(new_bat_entry);
|
|
||||||
BatEntry::write_bat_entries(f, bat_offset, bat).map_err(VhdxIoError::WriteBat)?;
|
|
||||||
|
|
||||||
if file_offset < vhdx_metadata::BLOCK_SIZE_MIN as u64 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
f.seek(SeekFrom::Start(file_offset))
|
|
||||||
.map_err(VhdxIoError::ReadSectorBlock)?;
|
|
||||||
f.write_all(
|
|
||||||
&buf[write_count..(write_count + (sector.free_sectors * SECTOR_SIZE) as usize)],
|
|
||||||
)
|
|
||||||
.map_err(VhdxIoError::ReadSectorBlock)?;
|
|
||||||
}
|
|
||||||
vhdx_bat::PAYLOAD_BLOCK_FULLY_PRESENT => {
|
|
||||||
if sector.file_offset < vhdx_metadata::BLOCK_SIZE_MIN as u64 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
f.seek(SeekFrom::Start(sector.file_offset))
|
|
||||||
.map_err(VhdxIoError::ReadSectorBlock)?;
|
|
||||||
f.write_all(
|
|
||||||
&buf[write_count..(write_count + (sector.free_sectors * SECTOR_SIZE) as usize)],
|
|
||||||
)
|
|
||||||
.map_err(VhdxIoError::ReadSectorBlock)?;
|
|
||||||
}
|
|
||||||
vhdx_bat::PAYLOAD_BLOCK_PARTIALLY_PRESENT => {
|
|
||||||
return Err(VhdxIoError::UnsupportedMode);
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
return Err(VhdxIoError::InvalidBatEntryState);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
sector_count -= sector.free_sectors;
|
|
||||||
sector_index += sector.free_sectors;
|
|
||||||
write_count += sector.free_bytes as usize;
|
|
||||||
}
|
|
||||||
Ok(write_count)
|
|
||||||
}
|
|
||||||
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)>;
|
||||||
|
}
|
||||||
@@ -2,41 +2,42 @@
|
|||||||
//
|
//
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use crate::async_io::{
|
||||||
|
AsyncIo, AsyncIoError, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult,
|
||||||
|
};
|
||||||
|
use crate::raw_async::RawFileAsync;
|
||||||
|
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;
|
||||||
|
|
||||||
use crate::async_io::{
|
pub struct FixedVhdDiskAsync {
|
||||||
AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult,
|
file: File,
|
||||||
};
|
size: u64,
|
||||||
use crate::fixed_vhd::FixedVhd;
|
}
|
||||||
use crate::raw_async::RawFileAsync;
|
|
||||||
use crate::BlockBackend;
|
|
||||||
|
|
||||||
pub struct FixedVhdDiskAsync(FixedVhd);
|
|
||||||
|
|
||||||
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>)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fd(&mut self) -> BorrowedDiskFd {
|
|
||||||
BorrowedDiskFd::new(self.0.as_raw_fd())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct FixedVhdAsync {
|
pub struct FixedVhdAsync {
|
||||||
@@ -2,41 +2,42 @@
|
|||||||
//
|
//
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use crate::async_io::{
|
||||||
|
AsyncIo, AsyncIoError, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult,
|
||||||
|
};
|
||||||
|
use crate::raw_sync::RawFileSync;
|
||||||
|
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;
|
||||||
|
|
||||||
use crate::async_io::{
|
pub struct FixedVhdDiskSync {
|
||||||
AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult,
|
file: File,
|
||||||
};
|
size: u64,
|
||||||
use crate::fixed_vhd::FixedVhd;
|
}
|
||||||
use crate::raw_sync::RawFileSync;
|
|
||||||
use crate::BlockBackend;
|
|
||||||
|
|
||||||
pub struct FixedVhdDiskSync(FixedVhd);
|
|
||||||
|
|
||||||
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>)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fd(&mut self) -> BorrowedDiskFd {
|
|
||||||
BorrowedDiskFd::new(self.0.as_raw_fd())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct FixedVhdSync {
|
pub struct FixedVhdSync {
|
||||||
@@ -12,55 +12,42 @@
|
|||||||
extern crate log;
|
extern crate log;
|
||||||
|
|
||||||
pub mod async_io;
|
pub mod async_io;
|
||||||
pub mod fcntl;
|
|
||||||
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 io_uring::{opcode, IoUring, Probe};
|
||||||
|
use smallvec::SmallVec;
|
||||||
use std::alloc::{alloc_zeroed, dealloc, Layout};
|
use std::alloc::{alloc_zeroed, dealloc, Layout};
|
||||||
|
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::sync::{Arc, MutexGuard};
|
use std::result;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::sync::MutexGuard;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
use std::{cmp, result};
|
|
||||||
|
|
||||||
#[cfg(feature = "io_uring")]
|
|
||||||
use io_uring::{opcode, IoUring, Probe};
|
|
||||||
use libc::{ioctl, S_IFBLK, S_IFMT};
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use smallvec::SmallVec;
|
|
||||||
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::bitmap::Bitmap;
|
|
||||||
use vm_memory::{
|
use vm_memory::{
|
||||||
ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryError, GuestMemoryLoadGuard,
|
bitmap::AtomicBitmap, bitmap::Bitmap, ByteValued, Bytes, GuestAddress, GuestMemory,
|
||||||
|
GuestMemoryError, GuestMemoryLoadGuard,
|
||||||
};
|
};
|
||||||
use vm_virtio::{AccessPlatform, Translatable};
|
use vm_virtio::{AccessPlatform, Translatable};
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
use vmm_sys_util::{aio, ioctl_io_nr, ioctl_ioc_nr};
|
|
||||||
|
|
||||||
use crate::async_io::{AsyncIo, AsyncIoError, AsyncIoResult};
|
type GuestMemoryMmap = vm_memory::GuestMemoryMmap<AtomicBitmap>;
|
||||||
use crate::vhdx::VhdxError;
|
|
||||||
|
|
||||||
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;
|
||||||
@@ -68,9 +55,9 @@ pub const SECTOR_SIZE: u64 = 0x01 << SECTOR_SHIFT;
|
|||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[error("Guest gave us bad memory addresses")]
|
#[error("Guest gave us bad memory addresses")]
|
||||||
GuestMemory(#[source] GuestMemoryError),
|
GuestMemory(GuestMemoryError),
|
||||||
#[error("Guest gave us offsets that would have overflowed a usize")]
|
#[error("Guest gave us offsets that would have overflowed a usize")]
|
||||||
CheckedOffset(GuestAddress, usize /* sector offset */),
|
CheckedOffset(GuestAddress, usize),
|
||||||
#[error("Guest gave us a write only descriptor that protocol says to read from")]
|
#[error("Guest gave us a write only descriptor that protocol says to read from")]
|
||||||
UnexpectedWriteOnlyDescriptor,
|
UnexpectedWriteOnlyDescriptor,
|
||||||
#[error("Guest gave us a read only descriptor that protocol says to write to")]
|
#[error("Guest gave us a read only descriptor that protocol says to write to")]
|
||||||
@@ -79,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(#[source] std::io::Error),
|
|
||||||
#[error("Failure in fixed vhd: {0}")]
|
|
||||||
FixedVhdError(#[source] 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(#[source] qcow::Error),
|
|
||||||
#[error("Failure in raw file: {0}")]
|
|
||||||
RawFileError(#[source] 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(#[source] VhdxError),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_device_id(disk_path: &Path) -> result::Result<String, Error> {
|
fn build_device_id(disk_path: &Path) -> result::Result<String, Error> {
|
||||||
@@ -112,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.");
|
||||||
@@ -123,54 +100,48 @@ 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(#[source] Error),
|
BadRequest(Error),
|
||||||
#[error("Failed to flush: {0}")]
|
#[error("Falied to flush: {0}")]
|
||||||
Flush(#[source] io::Error),
|
Flush(io::Error),
|
||||||
#[error("Failed to read: {0}")]
|
#[error("Failed to read: {0}")]
|
||||||
Read(#[source] GuestMemoryError),
|
Read(GuestMemoryError),
|
||||||
#[error("Failed to read_exact: {0}")]
|
|
||||||
ReadExact(#[source] io::Error),
|
|
||||||
#[error("Failed to seek: {0}")]
|
#[error("Failed to seek: {0}")]
|
||||||
Seek(#[source] io::Error),
|
Seek(io::Error),
|
||||||
#[error("Failed to write: {0}")]
|
#[error("Failed to write: {0}")]
|
||||||
Write(#[source] GuestMemoryError),
|
Write(GuestMemoryError),
|
||||||
#[error("Failed to write_all: {0}")]
|
|
||||||
WriteAll(#[source] 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}")]
|
||||||
SubmitIoUring(#[source] io::Error),
|
SubmitIoUring(io::Error),
|
||||||
#[error("Failed to get guest address: {0}")]
|
#[error("Failed to get guest address: {0}")]
|
||||||
GetHostAddress(#[source] GuestMemoryError),
|
GetHostAddress(GuestMemoryError),
|
||||||
#[error("Failed to async read: {0}")]
|
#[error("Failed to async read: {0}")]
|
||||||
AsyncRead(#[source] AsyncIoError),
|
AsyncRead(AsyncIoError),
|
||||||
#[error("Failed to async write: {0}")]
|
#[error("Failed to async write: {0}")]
|
||||||
AsyncWrite(#[source] AsyncIoError),
|
AsyncWrite(AsyncIoError),
|
||||||
#[error("failed to async flush: {0}")]
|
#[error("failed to async flush: {0}")]
|
||||||
AsyncFlush(#[source] AsyncIoError),
|
AsyncFlush(AsyncIoError),
|
||||||
#[error("Failed allocating a temporary buffer: {0}")]
|
#[error("Failed allocating a temporary buffer: {0}")]
|
||||||
TemporaryBufferAllocation(#[source] io::Error),
|
TemporaryBufferAllocation(io::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ExecuteError {
|
impl ExecuteError {
|
||||||
pub fn status(&self) -> u8 {
|
pub fn status(&self) -> u32 {
|
||||||
let status = match *self {
|
match *self {
|
||||||
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,
|
||||||
@@ -178,8 +149,7 @@ impl ExecuteError {
|
|||||||
ExecuteError::AsyncWrite(_) => VIRTIO_BLK_S_IOERR,
|
ExecuteError::AsyncWrite(_) => VIRTIO_BLK_S_IOERR,
|
||||||
ExecuteError::AsyncFlush(_) => VIRTIO_BLK_S_IOERR,
|
ExecuteError::AsyncFlush(_) => VIRTIO_BLK_S_IOERR,
|
||||||
ExecuteError::TemporaryBufferAllocation(_) => VIRTIO_BLK_S_IOERR,
|
ExecuteError::TemporaryBufferAllocation(_) => VIRTIO_BLK_S_IOERR,
|
||||||
};
|
}
|
||||||
status as u8
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,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)?;
|
||||||
@@ -206,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,
|
||||||
@@ -219,8 +186,6 @@ fn sector<B: Bitmap + 'static>(
|
|||||||
mem.read_obj(addr).map_err(Error::GuestMemory)
|
mem.read_obj(addr).map_err(Error::GuestMemory)
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_DESCRIPTOR_VEC_SIZE: usize = 32;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct AlignedOperation {
|
pub struct AlignedOperation {
|
||||||
origin_ptr: u64,
|
origin_ptr: u64,
|
||||||
@@ -233,23 +198,24 @@ pub struct AlignedOperation {
|
|||||||
pub struct Request {
|
pub struct Request {
|
||||||
pub request_type: RequestType,
|
pub request_type: RequestType,
|
||||||
pub sector: u64,
|
pub sector: u64,
|
||||||
pub data_descriptors: SmallVec<[(GuestAddress, u32); DEFAULT_DESCRIPTOR_VEC_SIZE]>,
|
pub data_descriptors: SmallVec<[(GuestAddress, u32); 1]>,
|
||||||
pub status_addr: GuestAddress,
|
pub status_addr: GuestAddress,
|
||||||
pub writeback: bool,
|
pub writeback: bool,
|
||||||
pub aligned_operations: SmallVec<[AlignedOperation; DEFAULT_DESCRIPTOR_VEC_SIZE]>,
|
pub aligned_operations: SmallVec<[AlignedOperation; 1]>,
|
||||||
pub start: Instant,
|
pub start: Instant,
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
.next()
|
.next()
|
||||||
.ok_or(Error::DescriptorChainTooShort)
|
.ok_or(Error::DescriptorChainTooShort)
|
||||||
.inspect_err(|_| {
|
.map_err(|e| {
|
||||||
error!("Missing head descriptor");
|
error!("Missing head descriptor");
|
||||||
|
e
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
// The head contains the request type which MUST be readable.
|
// The head contains the request type which MUST be readable.
|
||||||
@@ -264,10 +230,10 @@ impl Request {
|
|||||||
let mut req = Request {
|
let mut req = Request {
|
||||||
request_type: request_type(desc_chain.memory(), hdr_desc_addr)?,
|
request_type: request_type(desc_chain.memory(), hdr_desc_addr)?,
|
||||||
sector: sector(desc_chain.memory(), hdr_desc_addr)?,
|
sector: sector(desc_chain.memory(), hdr_desc_addr)?,
|
||||||
data_descriptors: SmallVec::with_capacity(DEFAULT_DESCRIPTOR_VEC_SIZE),
|
data_descriptors: SmallVec::with_capacity(1),
|
||||||
status_addr: GuestAddress(0),
|
status_addr: GuestAddress(0),
|
||||||
writeback: true,
|
writeback: true,
|
||||||
aligned_operations: SmallVec::with_capacity(DEFAULT_DESCRIPTOR_VEC_SIZE),
|
aligned_operations: SmallVec::with_capacity(1),
|
||||||
start: Instant::now(),
|
start: Instant::now(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -275,8 +241,9 @@ impl Request {
|
|||||||
let mut desc = desc_chain
|
let mut desc = desc_chain
|
||||||
.next()
|
.next()
|
||||||
.ok_or(Error::DescriptorChainTooShort)
|
.ok_or(Error::DescriptorChainTooShort)
|
||||||
.inspect_err(|_| {
|
.map_err(|e| {
|
||||||
error!("Only head descriptor present: request = {:?}", req);
|
error!("Only head descriptor present: request = {:?}", req);
|
||||||
|
e
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
if !desc.has_next() {
|
if !desc.has_next() {
|
||||||
@@ -307,8 +274,9 @@ impl Request {
|
|||||||
desc = desc_chain
|
desc = desc_chain
|
||||||
.next()
|
.next()
|
||||||
.ok_or(Error::DescriptorChainTooShort)
|
.ok_or(Error::DescriptorChainTooShort)
|
||||||
.inspect_err(|_| {
|
.map_err(|e| {
|
||||||
error!("DescriptorChain corrupted: request = {:?}", req);
|
error!("DescriptorChain corrupted: request = {:?}", req);
|
||||||
|
e
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
status_desc = desc;
|
status_desc = desc;
|
||||||
@@ -330,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)?;
|
||||||
@@ -354,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)),
|
||||||
@@ -387,19 +347,19 @@ 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;
|
||||||
let request_type = self.request_type;
|
let request_type = self.request_type;
|
||||||
let offset = (sector << SECTOR_SHIFT) as libc::off_t;
|
let offset = (sector << SECTOR_SHIFT) as libc::off_t;
|
||||||
|
|
||||||
let mut iovecs: SmallVec<[libc::iovec; DEFAULT_DESCRIPTOR_VEC_SIZE]> =
|
let mut iovecs: SmallVec<[libc::iovec; 1]> =
|
||||||
SmallVec::with_capacity(self.data_descriptors.len());
|
SmallVec::with_capacity(self.data_descriptors.len());
|
||||||
for (data_addr, data_len) in &self.data_descriptors {
|
for (data_addr, data_len) in &self.data_descriptors {
|
||||||
if *data_len == 0 {
|
if *data_len == 0 {
|
||||||
@@ -419,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
|
||||||
@@ -441,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,
|
||||||
@@ -455,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 {
|
||||||
@@ -494,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);
|
||||||
}
|
}
|
||||||
@@ -542,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,
|
||||||
@@ -565,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,
|
||||||
@@ -578,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>
|
||||||
@@ -654,13 +602,10 @@ where
|
|||||||
completion_list: &mut VecDeque<(u64, i32)>,
|
completion_list: &mut VecDeque<(u64, i32)>,
|
||||||
) -> AsyncIoResult<()> {
|
) -> AsyncIoResult<()> {
|
||||||
// Convert libc::iovec into IoSliceMut
|
// Convert libc::iovec into IoSliceMut
|
||||||
let mut slices: SmallVec<[IoSliceMut; DEFAULT_DESCRIPTOR_VEC_SIZE]> =
|
let mut slices: SmallVec<[IoSliceMut; 1]> = SmallVec::with_capacity(iovecs.len());
|
||||||
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 = {
|
||||||
@@ -670,11 +615,9 @@ where
|
|||||||
file.seek(SeekFrom::Start(offset as u64))
|
file.seek(SeekFrom::Start(offset as u64))
|
||||||
.map_err(AsyncIoError::ReadVectored)?;
|
.map_err(AsyncIoError::ReadVectored)?;
|
||||||
|
|
||||||
let mut r = 0;
|
// Read vectored
|
||||||
for b in slices.iter_mut() {
|
file.read_vectored(slices.as_mut_slice())
|
||||||
r += file.read(b).map_err(AsyncIoError::ReadVectored)?;
|
.map_err(AsyncIoError::ReadVectored)?
|
||||||
}
|
|
||||||
r
|
|
||||||
};
|
};
|
||||||
|
|
||||||
completion_list.push_back((user_data, result as i32));
|
completion_list.push_back((user_data, result as i32));
|
||||||
@@ -692,13 +635,10 @@ where
|
|||||||
completion_list: &mut VecDeque<(u64, i32)>,
|
completion_list: &mut VecDeque<(u64, i32)>,
|
||||||
) -> AsyncIoResult<()> {
|
) -> AsyncIoResult<()> {
|
||||||
// Convert libc::iovec into IoSlice
|
// Convert libc::iovec into IoSlice
|
||||||
let mut slices: SmallVec<[IoSlice; DEFAULT_DESCRIPTOR_VEC_SIZE]> =
|
let mut slices: SmallVec<[IoSlice; 1]> = SmallVec::with_capacity(iovecs.len());
|
||||||
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 = {
|
||||||
@@ -708,11 +648,9 @@ where
|
|||||||
file.seek(SeekFrom::Start(offset as u64))
|
file.seek(SeekFrom::Start(offset as u64))
|
||||||
.map_err(AsyncIoError::WriteVectored)?;
|
.map_err(AsyncIoError::WriteVectored)?;
|
||||||
|
|
||||||
let mut r = 0;
|
// Write vectored
|
||||||
for b in slices.iter() {
|
file.write_vectored(slices.as_slice())
|
||||||
r += file.write(b).map_err(AsyncIoError::WriteVectored)?;
|
.map_err(AsyncIoError::WriteVectored)?
|
||||||
}
|
|
||||||
r
|
|
||||||
};
|
};
|
||||||
|
|
||||||
completion_list.push_back((user_data, result as i32));
|
completion_list.push_back((user_data, result as i32));
|
||||||
@@ -757,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
|
||||||
@@ -791,89 +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>;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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)?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,20 +2,15 @@
|
|||||||
//
|
//
|
||||||
// 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::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};
|
||||||
use std::os::fd::AsRawFd;
|
|
||||||
use std::sync::{Arc, Mutex, MutexGuard};
|
use std::sync::{Arc, Mutex, MutexGuard};
|
||||||
|
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
use crate::async_io::{
|
|
||||||
AsyncIo, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult,
|
|
||||||
};
|
|
||||||
use crate::qcow::{QcowFile, RawFile, Result as QcowResult};
|
|
||||||
use crate::AsyncAdaptor;
|
|
||||||
|
|
||||||
pub struct QcowDiskSync {
|
pub struct QcowDiskSync {
|
||||||
qcow_file: Arc<Mutex<QcowFile>>,
|
qcow_file: Arc<Mutex<QcowFile>>,
|
||||||
}
|
}
|
||||||
@@ -38,11 +33,6 @@ impl DiskFile for QcowDiskSync {
|
|||||||
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(QcowSync::new(self.qcow_file.clone())) as Box<dyn AsyncIo>)
|
Ok(Box::new(QcowSync::new(self.qcow_file.clone())) as Box<dyn AsyncIo>)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fd(&mut self) -> BorrowedDiskFd {
|
|
||||||
let lock = self.qcow_file.lock().unwrap();
|
|
||||||
BorrowedDiskFd::new(lock.as_raw_fd())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct QcowSync {
|
pub struct QcowSync {
|
||||||
@@ -2,17 +2,14 @@
|
|||||||
//
|
//
|
||||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||||
|
|
||||||
use std::fs::File;
|
|
||||||
use std::io::{Error, Seek, SeekFrom};
|
|
||||||
use std::os::unix::io::{AsRawFd, RawFd};
|
|
||||||
|
|
||||||
use io_uring::{opcode, types, IoUring};
|
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
|
||||||
|
|
||||||
use crate::async_io::{
|
use crate::async_io::{
|
||||||
AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult,
|
AsyncIo, AsyncIoError, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult, DiskTopology,
|
||||||
};
|
};
|
||||||
use crate::DiskTopology;
|
use io_uring::{opcode, squeue, types, IoUring};
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::{Seek, SeekFrom};
|
||||||
|
use std::os::unix::io::{AsRawFd, RawFd};
|
||||||
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
pub struct RawFileDisk {
|
pub struct RawFileDisk {
|
||||||
file: File,
|
file: File,
|
||||||
@@ -39,17 +36,13 @@ 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");
|
||||||
DiskTopology::default()
|
DiskTopology::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fd(&mut self) -> BorrowedDiskFd {
|
|
||||||
BorrowedDiskFd::new(self.file.as_raw_fd())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RawFileAsync {
|
pub struct RawFileAsync {
|
||||||
@@ -90,14 +83,14 @@ impl AsyncIo for RawFileAsync {
|
|||||||
|
|
||||||
// SAFETY: we know the file descriptor is valid and we
|
// SAFETY: we know the file descriptor is valid and we
|
||||||
// relied on vm-memory to provide the buffer address.
|
// relied on vm-memory to provide the buffer address.
|
||||||
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)
|
||||||
.user_data(user_data),
|
.user_data(user_data),
|
||||||
)
|
)
|
||||||
.map_err(|_| AsyncIoError::ReadVectored(Error::other("Submission queue is full")))?
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Update the submission queue and submit new operations to the
|
// Update the submission queue and submit new operations to the
|
||||||
@@ -118,14 +111,14 @@ impl AsyncIo for RawFileAsync {
|
|||||||
|
|
||||||
// SAFETY: we know the file descriptor is valid and we
|
// SAFETY: we know the file descriptor is valid and we
|
||||||
// relied on vm-memory to provide the buffer address.
|
// relied on vm-memory to provide the buffer address.
|
||||||
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)
|
||||||
.user_data(user_data),
|
.user_data(user_data),
|
||||||
)
|
)
|
||||||
.map_err(|_| AsyncIoError::WriteVectored(Error::other("Submission queue is full")))?
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Update the submission queue and submit new operations to the
|
// Update the submission queue and submit new operations to the
|
||||||
@@ -141,13 +134,13 @@ impl AsyncIo for RawFileAsync {
|
|||||||
let (submitter, mut sq, _) = self.io_uring.split();
|
let (submitter, mut sq, _) = self.io_uring.split();
|
||||||
|
|
||||||
// SAFETY: we know the file descriptor is valid.
|
// SAFETY: we know the file descriptor is valid.
|
||||||
unsafe {
|
let _ = unsafe {
|
||||||
sq.push(
|
sq.push(
|
||||||
&opcode::Fsync::new(types::Fd(self.fd))
|
&opcode::Fsync::new(types::Fd(self.fd))
|
||||||
.build()
|
.build()
|
||||||
|
.flags(squeue::Flags::ASYNC)
|
||||||
.user_data(user_data),
|
.user_data(user_data),
|
||||||
)
|
)
|
||||||
.map_err(|_| AsyncIoError::Fsync(Error::other("Submission queue is full")))?
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Update the submission queue and submit new operations to the
|
// Update the submission queue and submit new operations to the
|
||||||
@@ -2,18 +2,15 @@
|
|||||||
//
|
//
|
||||||
// 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, AsyncIoError, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult, 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};
|
||||||
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;
|
||||||
|
|
||||||
use crate::async_io::{
|
|
||||||
AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult,
|
|
||||||
};
|
|
||||||
use crate::DiskTopology;
|
|
||||||
|
|
||||||
pub struct RawFileDiskSync {
|
pub struct RawFileDiskSync {
|
||||||
file: File,
|
file: File,
|
||||||
}
|
}
|
||||||
@@ -36,17 +33,13 @@ 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");
|
||||||
DiskTopology::default()
|
DiskTopology::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fd(&mut self) -> BorrowedDiskFd {
|
|
||||||
BorrowedDiskFd::new(self.file.as_raw_fd())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RawFileSync {
|
pub struct RawFileSync {
|
||||||
@@ -80,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,
|
||||||
)
|
)
|
||||||
@@ -105,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,10 +2,9 @@
|
|||||||
//
|
//
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use std::convert::TryInto;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{Seek, SeekFrom};
|
use std::io::{Read, Seek, SeekFrom};
|
||||||
|
|
||||||
use crate::{read_aligned_block_size, DiskTopology};
|
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub struct VhdFooter {
|
pub struct VhdFooter {
|
||||||
@@ -28,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()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,13 +121,11 @@ pub fn is_fixed_vhd(f: &mut File) -> std::io::Result<bool> {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use super::{is_fixed_vhd, VhdFooter};
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{Seek, SeekFrom, Write};
|
use std::io::{Seek, SeekFrom, Write};
|
||||||
|
|
||||||
use vmm_sys_util::tempfile::TempFile;
|
use vmm_sys_util::tempfile::TempFile;
|
||||||
|
|
||||||
use super::{is_fixed_vhd, VhdFooter};
|
|
||||||
|
|
||||||
fn valid_fixed_vhd_footer() -> Vec<u8> {
|
fn valid_fixed_vhd_footer() -> Vec<u8> {
|
||||||
vec![
|
vec![
|
||||||
0x63, 0x6f, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x78, // cookie
|
0x63, 0x6f, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x78, // cookie
|
||||||
@@ -2,19 +2,14 @@
|
|||||||
//
|
//
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use crate::async_io::{AsyncIo, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult};
|
||||||
|
use crate::AsyncAdaptor;
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::os::fd::AsRawFd;
|
|
||||||
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;
|
||||||
|
|
||||||
use crate::async_io::{
|
|
||||||
AsyncIo, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult,
|
|
||||||
};
|
|
||||||
use crate::vhdx::{Result as VhdxResult, Vhdx};
|
|
||||||
use crate::AsyncAdaptor;
|
|
||||||
|
|
||||||
pub struct VhdxDiskSync {
|
pub struct VhdxDiskSync {
|
||||||
vhdx_file: Arc<Mutex<Vhdx>>,
|
vhdx_file: Arc<Mutex<Vhdx>>,
|
||||||
}
|
}
|
||||||
@@ -38,11 +33,6 @@ impl DiskFile for VhdxDiskSync {
|
|||||||
as Box<dyn AsyncIo>,
|
as Box<dyn AsyncIo>,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fd(&mut self) -> BorrowedDiskFd {
|
|
||||||
let lock = self.vhdx_file.lock().unwrap();
|
|
||||||
BorrowedDiskFd::new(lock.as_raw_fd())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct VhdxSync {
|
pub struct VhdxSync {
|
||||||
13
build.rs
13
build.rs
@@ -3,7 +3,6 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
//
|
//
|
||||||
|
|
||||||
use std::env;
|
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
@@ -13,21 +12,13 @@ fn main() {
|
|||||||
if git_out.status.success() {
|
if git_out.status.success() {
|
||||||
if let Ok(git_out_str) = String::from_utf8(git_out.stdout) {
|
if let Ok(git_out_str) = String::from_utf8(git_out.stdout) {
|
||||||
version = git_out_str;
|
version = git_out_str;
|
||||||
// Pop the trailing newline.
|
|
||||||
version.pop();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Append CH_EXTRA_VERSION to version if it is set.
|
|
||||||
if let Ok(extra_version) = env::var("CH_EXTRA_VERSION") {
|
|
||||||
println!("cargo:rerun-if-env-changed=CH_EXTRA_VERSION");
|
|
||||||
version.push_str(&format!("-{extra_version}"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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,38 +1,29 @@
|
|||||||
[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 = { workspace = true }
|
acpi_tables = { git = "https://github.com/rust-vmm/acpi_tables", branch = "main" }
|
||||||
anyhow = "1.0.94"
|
anyhow = "1.0.69"
|
||||||
arch = { path = "../arch" }
|
arch = { path = "../arch" }
|
||||||
bitflags = "2.9.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.167"
|
libc = "0.2.139"
|
||||||
log = "0.4.22"
|
log = "0.4.17"
|
||||||
num_enum = "0.7.2"
|
thiserror = "1.0.39"
|
||||||
pci = { path = "../pci" }
|
|
||||||
serde = { version = "1.0.208", features = ["derive"] }
|
|
||||||
thiserror = "2.0.6"
|
|
||||||
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 = { workspace = true, features = [
|
vm-memory = "0.10.0"
|
||||||
"backend-atomic",
|
|
||||||
"backend-bitmap",
|
|
||||||
"backend-mmap",
|
|
||||||
] }
|
|
||||||
vm-migration = { path = "../vm-migration" }
|
vm-migration = { path = "../vm-migration" }
|
||||||
vmm-sys-util = { workspace = true }
|
vmm-sys-util = "0.11.0"
|
||||||
|
|
||||||
[target.'cfg(any(target_arch = "aarch64", target_arch = "riscv64"))'.dependencies]
|
[target.'cfg(target_arch = "aarch64")'.dependencies]
|
||||||
arch = { path = "../arch" }
|
arch = { path = "../arch" }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
kvm = ["arch/kvm"]
|
|
||||||
pvmemcontrol = []
|
|
||||||
|
|||||||
@@ -3,39 +3,29 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
//
|
//
|
||||||
|
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use super::AcpiNotificationFlags;
|
||||||
use std::sync::{Arc, Barrier};
|
|
||||||
use std::thread;
|
|
||||||
use std::time::Instant;
|
|
||||||
|
|
||||||
use acpi_tables::{aml, Aml, AmlSink};
|
use acpi_tables::{aml, Aml, AmlSink};
|
||||||
|
use std::sync::{Arc, Barrier};
|
||||||
|
use std::time::Instant;
|
||||||
use vm_device::interrupt::InterruptSourceGroup;
|
use vm_device::interrupt::InterruptSourceGroup;
|
||||||
use vm_device::BusDevice;
|
use vm_device::BusDevice;
|
||||||
use vm_memory::GuestAddress;
|
use vm_memory::GuestAddress;
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
use super::AcpiNotificationFlags;
|
|
||||||
|
|
||||||
pub const GED_DEVICE_ACPI_SIZE: usize = 0x1;
|
pub const GED_DEVICE_ACPI_SIZE: usize = 0x1;
|
||||||
|
|
||||||
/// A device for handling ACPI shutdown and reboot
|
/// A device for handling ACPI shutdown and reboot
|
||||||
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,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -53,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;
|
||||||
@@ -70,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
|
||||||
}
|
}
|
||||||
@@ -136,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,144 +0,0 @@
|
|||||||
// Copyright © 2024 Institute of Software, CAS. All rights reserved.
|
|
||||||
// Copyright 2020, ARM Limited.
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
|
||||||
|
|
||||||
use super::interrupt_controller::{Error, InterruptController};
|
|
||||||
extern crate arch;
|
|
||||||
use std::result;
|
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
|
|
||||||
use arch::layout;
|
|
||||||
use hypervisor::arch::riscv64::aia::{Vaia, VaiaConfig};
|
|
||||||
use hypervisor::{AiaState, CpuState};
|
|
||||||
use vm_device::interrupt::{
|
|
||||||
InterruptIndex, InterruptManager, InterruptSourceConfig, InterruptSourceGroup,
|
|
||||||
LegacyIrqSourceConfig, MsiIrqGroupConfig,
|
|
||||||
};
|
|
||||||
use vm_memory::address::Address;
|
|
||||||
use vm_migration::{Migratable, Pausable, Snapshottable, Transportable};
|
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
|
||||||
|
|
||||||
type Result<T> = result::Result<T, Error>;
|
|
||||||
|
|
||||||
// Reserve 32 IRQs for legacy devices.
|
|
||||||
pub const IRQ_LEGACY_BASE: usize = layout::IRQ_BASE as usize;
|
|
||||||
pub const IRQ_LEGACY_COUNT: usize = 32;
|
|
||||||
// TODO: AIA snapshotting is not yet completed.
|
|
||||||
pub const _AIA_SNAPSHOT_ID: &str = "";
|
|
||||||
|
|
||||||
// Aia (Advance Interrupt Architecture) struct provides all the functionality of a
|
|
||||||
// AIA device. It wraps a hypervisor-emulated AIA device (Vaia) provided by the
|
|
||||||
// `hypervisor` crate.
|
|
||||||
// Aia struct also implements InterruptController to provide interrupt delivery
|
|
||||||
// service.
|
|
||||||
pub struct Aia {
|
|
||||||
interrupt_source_group: Arc<dyn InterruptSourceGroup>,
|
|
||||||
// The hypervisor agnostic virtual AIA
|
|
||||||
vaia: Arc<Mutex<dyn Vaia>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Aia {
|
|
||||||
pub fn new(
|
|
||||||
vcpu_count: u8,
|
|
||||||
interrupt_manager: Arc<dyn InterruptManager<GroupConfig = MsiIrqGroupConfig>>,
|
|
||||||
vm: Arc<dyn hypervisor::Vm>,
|
|
||||||
) -> Result<Aia> {
|
|
||||||
let interrupt_source_group = interrupt_manager
|
|
||||||
.create_group(MsiIrqGroupConfig {
|
|
||||||
base: IRQ_LEGACY_BASE as InterruptIndex,
|
|
||||||
count: IRQ_LEGACY_COUNT as InterruptIndex,
|
|
||||||
})
|
|
||||||
.map_err(Error::CreateInterruptSourceGroup)?;
|
|
||||||
|
|
||||||
let vaia = vm
|
|
||||||
.create_vaia(Aia::create_default_config(vcpu_count as u64))
|
|
||||||
.map_err(Error::CreateAia)?;
|
|
||||||
|
|
||||||
let aia = Aia {
|
|
||||||
interrupt_source_group,
|
|
||||||
vaia,
|
|
||||||
};
|
|
||||||
aia.enable()?;
|
|
||||||
|
|
||||||
Ok(aia)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn restore_vaia(
|
|
||||||
&mut self,
|
|
||||||
state: Option<AiaState>,
|
|
||||||
_saved_vcpu_states: &[CpuState],
|
|
||||||
) -> Result<()> {
|
|
||||||
self.vaia
|
|
||||||
.clone()
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.set_state(&state.unwrap())
|
|
||||||
.map_err(Error::RestoreAia)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn enable(&self) -> Result<()> {
|
|
||||||
// Set irqfd for legacy interrupts
|
|
||||||
self.interrupt_source_group
|
|
||||||
.enable()
|
|
||||||
.map_err(Error::EnableInterrupt)?;
|
|
||||||
|
|
||||||
// Set irq_routing for legacy interrupts.
|
|
||||||
// irqchip: Hardcode to 0 as we support only 1 APLIC
|
|
||||||
// pin: Use irq number as pin
|
|
||||||
for i in IRQ_LEGACY_BASE..(IRQ_LEGACY_BASE + IRQ_LEGACY_COUNT) {
|
|
||||||
let config = LegacyIrqSourceConfig {
|
|
||||||
irqchip: 0,
|
|
||||||
pin: (i - IRQ_LEGACY_BASE) as u32,
|
|
||||||
};
|
|
||||||
self.interrupt_source_group
|
|
||||||
.update(
|
|
||||||
i as InterruptIndex,
|
|
||||||
InterruptSourceConfig::LegacyIrq(config),
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
.map_err(Error::EnableInterrupt)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.interrupt_source_group
|
|
||||||
.set_gsi()
|
|
||||||
.map_err(Error::EnableInterrupt)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Default config implied by arch::layout
|
|
||||||
pub fn create_default_config(vcpu_count: u64) -> VaiaConfig {
|
|
||||||
VaiaConfig {
|
|
||||||
vcpu_count: vcpu_count as u32,
|
|
||||||
aplic_addr: layout::APLIC_START.raw_value(),
|
|
||||||
imsic_addr: layout::IMSIC_START.raw_value(),
|
|
||||||
nr_irqs: layout::IRQ_NUM,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_vaia(&mut self) -> Result<Arc<Mutex<dyn Vaia>>> {
|
|
||||||
Ok(self.vaia.clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InterruptController for Aia {
|
|
||||||
// This should be called anytime an interrupt needs to be injected into the
|
|
||||||
// running guest.
|
|
||||||
fn service_irq(&mut self, irq: usize) -> Result<()> {
|
|
||||||
self.interrupt_source_group
|
|
||||||
.trigger(irq as InterruptIndex)
|
|
||||||
.map_err(Error::TriggerInterrupt)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn notifier(&self, irq: usize) -> Option<EventFd> {
|
|
||||||
self.interrupt_source_group.notifier(irq as InterruptIndex)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Snapshottable for Aia {}
|
|
||||||
impl Pausable for Aia {}
|
|
||||||
impl Transportable for Aia {}
|
|
||||||
impl Migratable for Aia {}
|
|
||||||
@@ -1,65 +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 {}
|
|
||||||
@@ -4,13 +4,14 @@
|
|||||||
|
|
||||||
use super::interrupt_controller::{Error, InterruptController};
|
use super::interrupt_controller::{Error, InterruptController};
|
||||||
extern crate arch;
|
extern crate arch;
|
||||||
use std::result;
|
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
|
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use arch::layout;
|
use arch::layout;
|
||||||
use hypervisor::arch::aarch64::gic::{GicState, Vgic, VgicConfig};
|
use hypervisor::{
|
||||||
use hypervisor::CpuState;
|
arch::aarch64::gic::{Vgic, VgicConfig},
|
||||||
|
CpuState, GicState,
|
||||||
|
};
|
||||||
|
use std::result;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
use vm_device::interrupt::{
|
use vm_device::interrupt::{
|
||||||
InterruptIndex, InterruptManager, InterruptSourceConfig, InterruptSourceGroup,
|
InterruptIndex, InterruptManager, InterruptSourceConfig, InterruptSourceGroup,
|
||||||
LegacyIrqSourceConfig, MsiIrqGroupConfig,
|
LegacyIrqSourceConfig, MsiIrqGroupConfig,
|
||||||
@@ -26,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
|
||||||
@@ -97,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(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,55 +1,35 @@
|
|||||||
// Copyright © 2024 Institute of Software, CAS. All rights reserved.
|
|
||||||
// Copyright 2020, ARM Limited.
|
// Copyright 2020, ARM Limited.
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||||
|
|
||||||
use std::{io, result};
|
use std::io;
|
||||||
|
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(#[source] io::Error),
|
|
||||||
/// Failed triggering the interrupt.
|
/// Failed triggering the interrupt.
|
||||||
#[error("Failed triggering the interrupt: {0}")]
|
TriggerInterrupt(io::Error),
|
||||||
TriggerInterrupt(#[source] io::Error),
|
|
||||||
/// Failed masking the interrupt.
|
/// Failed masking the interrupt.
|
||||||
#[error("Failed masking the interrupt: {0}")]
|
MaskInterrupt(io::Error),
|
||||||
MaskInterrupt(#[source] io::Error),
|
|
||||||
/// Failed unmasking the interrupt.
|
/// Failed unmasking the interrupt.
|
||||||
#[error("Failed unmasking the interrupt: {0}")]
|
UnmaskInterrupt(io::Error),
|
||||||
UnmaskInterrupt(#[source] io::Error),
|
|
||||||
/// Failed updating the interrupt.
|
/// Failed updating the interrupt.
|
||||||
#[error("Failed updating the interrupt: {0}")]
|
UpdateInterrupt(io::Error),
|
||||||
UpdateInterrupt(#[source] io::Error),
|
|
||||||
/// Failed enabling the interrupt.
|
/// Failed enabling the interrupt.
|
||||||
#[error("Failed enabling the interrupt: {0}")]
|
EnableInterrupt(io::Error),
|
||||||
EnableInterrupt(#[source] 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(#[source] 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(#[source] hypervisor::arch::aarch64::gic::Error),
|
|
||||||
#[cfg(target_arch = "riscv64")]
|
|
||||||
/// Failed creating AIA device.
|
|
||||||
#[error("Failed creating AIA device: {0}")]
|
|
||||||
CreateAia(#[source] hypervisor::HypervisorVmError),
|
|
||||||
#[cfg(target_arch = "riscv64")]
|
|
||||||
/// Failed restoring AIA device.
|
|
||||||
#[error("Failed restoring AIA device: {0}")]
|
|
||||||
RestoreAia(#[source] hypervisor::arch::riscv64::aia::Error),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Result<T> = result::Result<T, Error>;
|
type Result<T> = result::Result<T, Error>;
|
||||||
@@ -76,7 +56,7 @@ pub struct MsiMessage {
|
|||||||
// Introduce trait InterruptController to uniform the interrupt
|
// Introduce trait InterruptController to uniform the interrupt
|
||||||
// service provided for devices.
|
// service provided for devices.
|
||||||
// Device manager uses this trait without caring whether it is a
|
// Device manager uses this trait without caring whether it is a
|
||||||
// IOAPIC (X86), GIC (Arm) or AIA (RISC-V).
|
// IOAPIC (X86) or GIC (Arm).
|
||||||
pub trait InterruptController: Send {
|
pub trait InterruptController: Send {
|
||||||
fn service_irq(&mut self, irq: usize) -> Result<()>;
|
fn service_irq(&mut self, irq: usize) -> Result<()>;
|
||||||
#[cfg(target_arch = "x86_64")]
|
#[cfg(target_arch = "x86_64")]
|
||||||
|
|||||||
@@ -9,22 +9,23 @@
|
|||||||
// Implementation of an intel 82093AA Input/Output Advanced Programmable Interrupt Controller
|
// Implementation of an intel 82093AA Input/Output Advanced Programmable Interrupt Controller
|
||||||
// See https://pdos.csail.mit.edu/6.828/2016/readings/ia32/ioapic.pdf for a specification.
|
// See https://pdos.csail.mit.edu/6.828/2016/readings/ia32/ioapic.pdf for a specification.
|
||||||
|
|
||||||
|
use super::interrupt_controller::{Error, InterruptController};
|
||||||
|
use byteorder::{ByteOrder, LittleEndian};
|
||||||
use std::result;
|
use std::result;
|
||||||
use std::sync::{Arc, Barrier};
|
use std::sync::{Arc, Barrier};
|
||||||
|
use versionize::{VersionMap, Versionize, VersionizeResult};
|
||||||
use byteorder::{ByteOrder, LittleEndian};
|
use versionize_derive::Versionize;
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
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;
|
||||||
|
|
||||||
use super::interrupt_controller::{Error, InterruptController};
|
|
||||||
|
|
||||||
type Result<T> = result::Result<T, Error>;
|
type Result<T> = result::Result<T, Error>;
|
||||||
|
|
||||||
// I/O REDIRECTION TABLE REGISTER
|
// I/O REDIRECTION TABLE REGISTER
|
||||||
@@ -135,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,
|
||||||
@@ -143,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]) {
|
||||||
@@ -235,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)
|
||||||
@@ -281,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.
|
||||||
@@ -332,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
|
||||||
@@ -346,9 +343,9 @@ impl Ioapic {
|
|||||||
|
|
||||||
// Generate MSI message address
|
// Generate MSI message address
|
||||||
let low_addr: u32 = self.apic_address.0 as u32
|
let low_addr: u32 = self.apic_address.0 as u32
|
||||||
| (u32::from(destination_id) << 12)
|
| u32::from(destination_id) << 12
|
||||||
| (u32::from(redirection_hint) << 3)
|
| u32::from(redirection_hint) << 3
|
||||||
| (u32::from(destination_mode) << 2);
|
| u32::from(destination_mode) << 2;
|
||||||
|
|
||||||
// Validate Trigger Mode value
|
// Validate Trigger Mode value
|
||||||
let trigger_mode = trigger_mode(entry);
|
let trigger_mode = trigger_mode(entry);
|
||||||
@@ -372,9 +369,9 @@ impl Ioapic {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Generate MSI message data
|
// Generate MSI message data
|
||||||
let data: u32 = (u32::from(trigger_mode) << 15)
|
let data: u32 = u32::from(trigger_mode) << 15
|
||||||
| (u32::from(remote_irr(entry)) << 14)
|
| u32::from(remote_irr(entry)) << 14
|
||||||
| (u32::from(delivery_mode) << 8)
|
| u32::from(delivery_mode) << 8
|
||||||
| u32::from(vector(entry));
|
| u32::from(vector(entry));
|
||||||
|
|
||||||
let config = MsiIrqSourceConfig {
|
let config = MsiIrqSourceConfig {
|
||||||
@@ -389,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)?;
|
||||||
|
|
||||||
@@ -418,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)?;
|
||||||
trace!("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.
|
||||||
@@ -442,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,20 +1,17 @@
|
|||||||
// 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 std::cmp::min;
|
use std::cmp::min;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::mem;
|
||||||
use std::sync::{Arc, Barrier};
|
use std::sync::{Arc, Barrier};
|
||||||
use std::{mem, thread};
|
use vm_device::BusDevice;
|
||||||
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
// https://github.com/rust-lang/libc/issues/1848
|
// https://github.com/rust-lang/libc/issues/1848
|
||||||
#[cfg_attr(target_env = "musl", allow(deprecated))]
|
#[cfg_attr(target_env = "musl", allow(deprecated))]
|
||||||
use libc::time_t;
|
use libc::time_t;
|
||||||
use libc::{clock_gettime, gmtime_r, timespec, tm, CLOCK_REALTIME};
|
|
||||||
use vm_device::BusDevice;
|
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
|
||||||
|
|
||||||
const INDEX_MASK: u8 = 0x7f;
|
const INDEX_MASK: u8 = 0x7f;
|
||||||
const INDEX_OFFSET: u64 = 0x0;
|
const INDEX_OFFSET: u64 = 0x0;
|
||||||
@@ -26,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
|
||||||
@@ -59,7 +50,6 @@ impl Cmos {
|
|||||||
index: 0,
|
index: 0,
|
||||||
data,
|
data,
|
||||||
reset_evt,
|
reset_evt,
|
||||||
vcpus_kill_signalled,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -77,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]
|
||||||
}
|
}
|
||||||
@@ -152,7 +133,7 @@ impl BusDevice for Cmos {
|
|||||||
0x08 => to_bcd(month as u8),
|
0x08 => to_bcd(month as u8),
|
||||||
0x09 => to_bcd((year % 100) as u8),
|
0x09 => to_bcd((year % 100) as u8),
|
||||||
// Bit 5 for 32kHz clock. Bit 7 for Update in Progress
|
// Bit 5 for 32kHz clock. Bit 7 for Update in Progress
|
||||||
0x0a => (1 << 5) | ((update_in_progress as u8) << 7),
|
0x0a => 1 << 5 | (update_in_progress as u8) << 7,
|
||||||
// Bit 0-6 are reserved and must be 0.
|
// Bit 0-6 are reserved and must be 0.
|
||||||
// Bit 7 must be 1 (CMOS has power)
|
// Bit 7 must be 1 (CMOS has power)
|
||||||
0x0d => 1 << 7,
|
0x0d => 1 << 7,
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
use vm_device::BusDevice;
|
use vm_device::BusDevice;
|
||||||
|
|
||||||
/// Debug I/O port, see:
|
/// Debug I/O port, see:
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
use std::sync::{Arc, Barrier};
|
use std::sync::{Arc, Barrier};
|
||||||
|
|
||||||
use vm_device::BusDevice;
|
use vm_device::BusDevice;
|
||||||
|
|
||||||
/// Provides firmware debug output via I/O port controls
|
/// Provides firmware debug output via I/O port controls
|
||||||
|
|||||||
@@ -7,16 +7,17 @@
|
|||||||
//! This module implements an ARM PrimeCell General Purpose Input/Output(PL061) to support gracefully poweroff microvm from external.
|
//! This module implements an ARM PrimeCell General Purpose Input/Output(PL061) to support gracefully poweroff microvm from external.
|
||||||
//!
|
//!
|
||||||
|
|
||||||
|
use crate::{read_le_u32, write_le_u32};
|
||||||
|
use std::result;
|
||||||
use std::sync::{Arc, Barrier};
|
use std::sync::{Arc, Barrier};
|
||||||
use std::{io, result};
|
use std::{fmt, io};
|
||||||
|
use versionize::{VersionMap, Versionize, VersionizeResult};
|
||||||
use serde::{Deserialize, Serialize};
|
use versionize_derive::Versionize;
|
||||||
use thiserror::Error;
|
|
||||||
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 crate::{read_le_u32, write_le_u32};
|
};
|
||||||
|
|
||||||
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
|
||||||
@@ -30,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
|
||||||
@@ -39,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(#[source] 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.
|
||||||
@@ -77,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,
|
||||||
@@ -90,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(
|
||||||
@@ -314,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,11 +338,12 @@ impl Migratable for Gpio {}
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
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;
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
const GPIO_NAME: &str = "gpio";
|
const GPIO_NAME: &str = "gpio";
|
||||||
const LEGACY_GPIO_MAPPED_IO_START: u64 = 0x0902_0000;
|
const LEGACY_GPIO_MAPPED_IO_START: u64 = 0x0902_0000;
|
||||||
|
|
||||||
@@ -346,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,29 +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::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;
|
||||||
|
|
||||||
/// 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,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,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
|
||||||
|
|||||||
@@ -24,13 +24,14 @@ pub use self::cmos::Cmos;
|
|||||||
pub use self::debug_port::DebugPort;
|
pub use self::debug_port::DebugPort;
|
||||||
#[cfg(target_arch = "x86_64")]
|
#[cfg(target_arch = "x86_64")]
|
||||||
pub use self::fwdebug::FwDebugDevice;
|
pub use self::fwdebug::FwDebugDevice;
|
||||||
|
pub use self::i8042::I8042Device;
|
||||||
|
pub use self::serial::Serial;
|
||||||
|
|
||||||
#[cfg(target_arch = "aarch64")]
|
#[cfg(target_arch = "aarch64")]
|
||||||
pub use self::gpio_pl061::Error as GpioDeviceError;
|
pub use self::gpio_pl061::Error as GpioDeviceError;
|
||||||
#[cfg(target_arch = "aarch64")]
|
#[cfg(target_arch = "aarch64")]
|
||||||
pub use self::gpio_pl061::Gpio;
|
pub use self::gpio_pl061::Gpio;
|
||||||
pub use self::i8042::I8042Device;
|
|
||||||
#[cfg(target_arch = "aarch64")]
|
#[cfg(target_arch = "aarch64")]
|
||||||
pub use self::rtc_pl031::Rtc;
|
pub use self::rtc_pl031::Rtc;
|
||||||
pub use self::serial::Serial;
|
|
||||||
#[cfg(target_arch = "aarch64")]
|
#[cfg(target_arch = "aarch64")]
|
||||||
pub use self::uart_pl011::Pl011;
|
pub use self::uart_pl011::Pl011;
|
||||||
|
|||||||
@@ -8,16 +8,14 @@
|
|||||||
//! This is achieved by generating an interrupt signal after counting for a programmed number of cycles of
|
//! This is achieved by generating an interrupt signal after counting for a programmed number of cycles of
|
||||||
//! a real-time clock input.
|
//! a real-time clock input.
|
||||||
//!
|
//!
|
||||||
|
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;
|
||||||
|
|
||||||
use crate::{read_le_u32, write_le_u32};
|
|
||||||
|
|
||||||
// As you can see in https://static.docs.arm.com/ddi0224/c/real_time_clock_pl031_r1p3_technical_reference_manual_DDI0224C.pdf
|
// As you can see in https://static.docs.arm.com/ddi0224/c/real_time_clock_pl031_r1p3_technical_reference_manual_DDI0224C.pdf
|
||||||
// at section 3.2 Summary of RTC registers, the total size occupied by this device is 0x000 -> 0xFFC + 4 = 0x1000.
|
// at section 3.2 Summary of RTC registers, the total size occupied by this device is 0x000 -> 0xFFC + 4 = 0x1000.
|
||||||
// From 0x0 to 0x1C we have following registers:
|
// From 0x0 to 0x1C we have following registers:
|
||||||
@@ -41,12 +39,19 @@ 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(#[source] 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>;
|
||||||
@@ -74,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,81 +327,17 @@ impl BusDevice for Rtc {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::{
|
||||||
|
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_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;
|
||||||
|
|
||||||
use super::*;
|
|
||||||
use crate::{
|
|
||||||
read_be_u16, read_be_u32, read_le_i32, read_le_u16, read_le_u64, write_be_u16,
|
|
||||||
write_be_u32, write_le_i32, write_le_u16, write_le_u64,
|
|
||||||
};
|
|
||||||
|
|
||||||
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 {
|
||||||
@@ -380,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())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,11 +8,13 @@
|
|||||||
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 serde::{Deserialize, Serialize};
|
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;
|
||||||
@@ -72,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,
|
||||||
@@ -84,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(
|
||||||
@@ -163,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
|
||||||
@@ -331,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,13 +344,12 @@ impl Migratable for Serial {}
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::sync::Mutex;
|
use super::*;
|
||||||
|
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;
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
const SERIAL_NAME: &str = "serial";
|
const SERIAL_NAME: &str = "serial";
|
||||||
|
|
||||||
struct TestInterrupt {
|
struct TestInterrupt {
|
||||||
@@ -363,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())
|
||||||
}
|
}
|
||||||
@@ -414,11 +412,14 @@ mod tests {
|
|||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
|
|
||||||
serial.write(0, DATA as u64, b"xy");
|
serial.write(0, DATA as u64, &[b'x', b'y']);
|
||||||
serial.write(0, DATA as u64, b"a");
|
serial.write(0, DATA as u64, &[b'a']);
|
||||||
serial.write(0, DATA as u64, b"b");
|
serial.write(0, DATA as u64, &[b'b']);
|
||||||
serial.write(0, DATA as u64, b"c");
|
serial.write(0, DATA as u64, &[b'c']);
|
||||||
assert_eq!(serial_out.buf.lock().unwrap().as_slice(), b"abc");
|
assert_eq!(
|
||||||
|
serial_out.buf.lock().unwrap().as_slice(),
|
||||||
|
&[b'a', b'b', b'c']
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -434,9 +435,9 @@ mod tests {
|
|||||||
|
|
||||||
// write 1 to the interrupt event fd, so that read doesn't block in case the event fd
|
// write 1 to the interrupt event fd, so that read doesn't block in case the event fd
|
||||||
// counter doesn't change (for 0 it blocks)
|
// counter doesn't change (for 0 it blocks)
|
||||||
intr_evt.write(1).unwrap();
|
assert!(intr_evt.write(1).is_ok());
|
||||||
serial.write(0, IER as u64, &[IER_RECV_BIT]);
|
serial.write(0, IER as u64, &[IER_RECV_BIT]);
|
||||||
serial.queue_input_bytes(b"abc").unwrap();
|
serial.queue_input_bytes(&[b'a', b'b', b'c']).unwrap();
|
||||||
|
|
||||||
assert_eq!(intr_evt.read().unwrap(), 2);
|
assert_eq!(intr_evt.read().unwrap(), 2);
|
||||||
|
|
||||||
@@ -471,9 +472,9 @@ mod tests {
|
|||||||
|
|
||||||
// write 1 to the interrupt event fd, so that read doesn't block in case the event fd
|
// write 1 to the interrupt event fd, so that read doesn't block in case the event fd
|
||||||
// counter doesn't change (for 0 it blocks)
|
// counter doesn't change (for 0 it blocks)
|
||||||
intr_evt.write(1).unwrap();
|
assert!(intr_evt.write(1).is_ok());
|
||||||
serial.write(0, IER as u64, &[IER_THR_BIT]);
|
serial.write(0, IER as u64, &[IER_THR_BIT]);
|
||||||
serial.write(0, DATA as u64, b"a");
|
serial.write(0, DATA as u64, &[b'a']);
|
||||||
|
|
||||||
assert_eq!(intr_evt.read().unwrap(), 2);
|
assert_eq!(intr_evt.read().unwrap(), 2);
|
||||||
let mut data = [0u8];
|
let mut data = [0u8];
|
||||||
@@ -515,9 +516,9 @@ mod tests {
|
|||||||
);
|
);
|
||||||
|
|
||||||
serial.write(0, MCR as u64, &[MCR_LOOP_BIT]);
|
serial.write(0, MCR as u64, &[MCR_LOOP_BIT]);
|
||||||
serial.write(0, DATA as u64, b"a");
|
serial.write(0, DATA as u64, &[b'a']);
|
||||||
serial.write(0, DATA as u64, b"b");
|
serial.write(0, DATA as u64, &[b'b']);
|
||||||
serial.write(0, DATA as u64, b"c");
|
serial.write(0, DATA as u64, &[b'c']);
|
||||||
|
|
||||||
let mut data = [0u8];
|
let mut data = [0u8];
|
||||||
serial.read(0, MSR as u64, &mut data[..]);
|
serial.read(0, MSR as u64, &mut data[..]);
|
||||||
|
|||||||
@@ -6,18 +6,19 @@
|
|||||||
//! This module implements an ARM PrimeCell UART(PL011).
|
//! This module implements an ARM PrimeCell UART(PL011).
|
||||||
//!
|
//!
|
||||||
|
|
||||||
|
use crate::{read_le_u32, write_le_u32};
|
||||||
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 versionize::{VersionMap, Versionize, VersionizeResult};
|
||||||
use serde::{Deserialize, Serialize};
|
use versionize_derive::Versionize;
|
||||||
use thiserror::Error;
|
|
||||||
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 crate::{read_le_u32, write_le_u32};
|
};
|
||||||
|
|
||||||
/* Registers */
|
/* Registers */
|
||||||
const UARTDR: u64 = 0;
|
const UARTDR: u64 = 0;
|
||||||
@@ -47,18 +48,25 @@ 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(#[source] io::Error),
|
WriteAllFailure(io::Error),
|
||||||
#[error("Failed to write: {0}")]
|
FlushFailure(io::Error),
|
||||||
WriteAllFailure(#[source] io::Error),
|
}
|
||||||
#[error("Failed to flush: {0}")]
|
|
||||||
FlushFailure(#[source] 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>;
|
||||||
@@ -86,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,
|
||||||
@@ -105,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(
|
||||||
@@ -191,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 {
|
||||||
@@ -444,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())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -454,13 +464,12 @@ impl Migratable for Pl011 {}
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::sync::Mutex;
|
use super::*;
|
||||||
|
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;
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
const SERIAL_NAME: &str = "serial";
|
const SERIAL_NAME: &str = "serial";
|
||||||
|
|
||||||
struct TestInterrupt {
|
struct TestInterrupt {
|
||||||
@@ -476,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())
|
||||||
}
|
}
|
||||||
@@ -528,11 +533,14 @@ mod tests {
|
|||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
|
|
||||||
pl011.write(0, UARTDR, b"xy");
|
pl011.write(0, UARTDR, &[b'x', b'y']);
|
||||||
pl011.write(0, UARTDR, b"a");
|
pl011.write(0, UARTDR, &[b'a']);
|
||||||
pl011.write(0, UARTDR, b"b");
|
pl011.write(0, UARTDR, &[b'b']);
|
||||||
pl011.write(0, UARTDR, b"c");
|
pl011.write(0, UARTDR, &[b'c']);
|
||||||
assert_eq!(pl011_out.buf.lock().unwrap().as_slice(), b"xabc");
|
assert_eq!(
|
||||||
|
pl011_out.buf.lock().unwrap().as_slice(),
|
||||||
|
&[b'x', b'a', b'b', b'c']
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -549,8 +557,8 @@ mod tests {
|
|||||||
|
|
||||||
// write 1 to the interrupt event fd, so that read doesn't block in case the event fd
|
// write 1 to the interrupt event fd, so that read doesn't block in case the event fd
|
||||||
// counter doesn't change (for 0 it blocks)
|
// counter doesn't change (for 0 it blocks)
|
||||||
intr_evt.write(1).unwrap();
|
assert!(intr_evt.write(1).is_ok());
|
||||||
pl011.queue_input_bytes(b"abc").unwrap();
|
pl011.queue_input_bytes(&[b'a', b'b', b'c']).unwrap();
|
||||||
|
|
||||||
assert_eq!(intr_evt.read().unwrap(), 2);
|
assert_eq!(intr_evt.read().unwrap(), 2);
|
||||||
|
|
||||||
|
|||||||
@@ -10,30 +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 = "riscv64")]
|
|
||||||
pub mod aia;
|
|
||||||
#[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;
|
||||||
#[cfg(feature = "pvmemcontrol")]
|
|
||||||
pub mod pvmemcontrol;
|
|
||||||
pub mod pvpanic;
|
|
||||||
// TODO: TPM is not yet supported
|
|
||||||
#[cfg(not(target_arch = "riscv64"))]
|
|
||||||
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,814 +0,0 @@
|
|||||||
// Copyright © 2024 Google LLC
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
//
|
|
||||||
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::ffi::CString;
|
|
||||||
use std::sync::{Arc, Barrier, Mutex, RwLock};
|
|
||||||
use std::{io, result};
|
|
||||||
|
|
||||||
use num_enum::TryFromPrimitive;
|
|
||||||
use pci::{
|
|
||||||
BarReprogrammingParams, PciBarConfiguration, PciBarPrefetchable, PciBarRegionType,
|
|
||||||
PciClassCode, PciConfiguration, PciDevice, PciDeviceError, PciHeaderType, PciSubclass,
|
|
||||||
};
|
|
||||||
use thiserror::Error;
|
|
||||||
use vm_allocator::page_size::get_page_size;
|
|
||||||
use vm_allocator::{AddressAllocator, SystemAllocator};
|
|
||||||
use vm_device::{BusDeviceSync, Resource};
|
|
||||||
use vm_memory::bitmap::AtomicBitmap;
|
|
||||||
use vm_memory::{
|
|
||||||
Address, ByteValued, Bytes, GuestAddress, GuestAddressSpace, GuestMemory, GuestMemoryAtomic,
|
|
||||||
GuestMemoryError, GuestMemoryMmap, Le32, Le64,
|
|
||||||
};
|
|
||||||
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
|
|
||||||
|
|
||||||
const PVMEMCONTROL_VENDOR_ID: u16 = 0x1ae0;
|
|
||||||
const PVMEMCONTROL_DEVICE_ID: u16 = 0x0087;
|
|
||||||
|
|
||||||
const PVMEMCONTROL_SUBSYSTEM_VENDOR_ID: u16 = 0x1ae0;
|
|
||||||
const PVMEMCONTROL_SUBSYSTEM_ID: u16 = 0x011F;
|
|
||||||
|
|
||||||
const MAJOR_VERSION: u64 = 1;
|
|
||||||
const MINOR_VERSION: u64 = 0;
|
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
|
||||||
pub enum Error {
|
|
||||||
// device errors
|
|
||||||
#[error("Guest gave us bad memory addresses: {0}")]
|
|
||||||
GuestMemory(#[source] GuestMemoryError),
|
|
||||||
#[error("Guest sent us invalid request")]
|
|
||||||
InvalidRequest,
|
|
||||||
|
|
||||||
#[error("Guest sent us invalid command: {0}")]
|
|
||||||
InvalidCommand(u32),
|
|
||||||
#[error("Guest sent us invalid connection: {0}")]
|
|
||||||
InvalidConnection(u32),
|
|
||||||
|
|
||||||
// pvmemcontrol errors
|
|
||||||
#[error("Request contains invalid arguments: {0}")]
|
|
||||||
InvalidArgument(u64),
|
|
||||||
#[error("Unknown function code: {0}")]
|
|
||||||
UnknownFunctionCode(u64),
|
|
||||||
#[error("Libc call fail: {0}")]
|
|
||||||
LibcFail(#[source] std::io::Error),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
|
||||||
enum PvmemcontrolSubclass {
|
|
||||||
Other = 0x80,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PciSubclass for PvmemcontrolSubclass {
|
|
||||||
fn get_register_value(&self) -> u8 {
|
|
||||||
*self as u8
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// commands have 0 as the most significant byte
|
|
||||||
#[repr(u32)]
|
|
||||||
#[derive(PartialEq, Eq, Copy, Clone, TryFromPrimitive)]
|
|
||||||
enum PvmemcontrolTransportCommand {
|
|
||||||
Reset = 0x060f_e6d2,
|
|
||||||
Register = 0x0e35_9539,
|
|
||||||
Ready = 0x0ca8_d227,
|
|
||||||
Disconnect = 0x030f_5da0,
|
|
||||||
Ack = 0x03cf_5196,
|
|
||||||
Error = 0x01fb_a249,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Copy, Clone)]
|
|
||||||
struct PvmemcontrolTransportRegister {
|
|
||||||
buf_phys_addr: Le64,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Copy, Clone)]
|
|
||||||
struct PvmemcontrolTransportRegisterResponse {
|
|
||||||
command: Le32,
|
|
||||||
_padding: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Copy, Clone)]
|
|
||||||
union PvmemcontrolTransportUnion {
|
|
||||||
register: PvmemcontrolTransportRegister,
|
|
||||||
register_response: PvmemcontrolTransportRegisterResponse,
|
|
||||||
unit: (),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Copy, Clone)]
|
|
||||||
struct PvmemcontrolTransport {
|
|
||||||
payload: PvmemcontrolTransportUnion,
|
|
||||||
command: PvmemcontrolTransportCommand,
|
|
||||||
}
|
|
||||||
|
|
||||||
const PVMEMCONTROL_DEVICE_MMIO_SIZE: u64 = std::mem::size_of::<PvmemcontrolTransport>() as u64;
|
|
||||||
const PVMEMCONTROL_DEVICE_MMIO_ALIGN: u64 = std::mem::align_of::<PvmemcontrolTransport>() as u64;
|
|
||||||
|
|
||||||
impl PvmemcontrolTransport {
|
|
||||||
fn ack() -> Self {
|
|
||||||
PvmemcontrolTransport {
|
|
||||||
payload: PvmemcontrolTransportUnion { unit: () },
|
|
||||||
command: PvmemcontrolTransportCommand::Ack,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn error() -> Self {
|
|
||||||
PvmemcontrolTransport {
|
|
||||||
payload: PvmemcontrolTransportUnion { unit: () },
|
|
||||||
command: PvmemcontrolTransportCommand::Error,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn register_response(command: u32) -> Self {
|
|
||||||
PvmemcontrolTransport {
|
|
||||||
payload: PvmemcontrolTransportUnion {
|
|
||||||
register_response: PvmemcontrolTransportRegisterResponse {
|
|
||||||
command: command.into(),
|
|
||||||
_padding: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
command: PvmemcontrolTransportCommand::Ack,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn as_register(self) -> PvmemcontrolTransportRegister {
|
|
||||||
self.payload.register
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SAFETY: Contains no references and does not have compiler-inserted padding
|
|
||||||
unsafe impl ByteValued for PvmemcontrolTransportUnion {}
|
|
||||||
// SAFETY: Contains no references and does not have compiler-inserted padding
|
|
||||||
unsafe impl ByteValued for PvmemcontrolTransport {}
|
|
||||||
|
|
||||||
#[repr(u64)]
|
|
||||||
#[derive(Copy, Clone, TryFromPrimitive, Debug)]
|
|
||||||
enum FunctionCode {
|
|
||||||
Info = 0,
|
|
||||||
Dontneed = 1,
|
|
||||||
Remove = 2,
|
|
||||||
Free = 3,
|
|
||||||
Pageout = 4,
|
|
||||||
Dontdump = 5,
|
|
||||||
SetVMAAnonName = 6,
|
|
||||||
Mlock = 7,
|
|
||||||
Munlock = 8,
|
|
||||||
MprotectNone = 9,
|
|
||||||
MprotectR = 10,
|
|
||||||
MprotectW = 11,
|
|
||||||
MprotectRW = 12,
|
|
||||||
Mergeable = 13,
|
|
||||||
Unmergeable = 14,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Copy, Clone, Debug, Default)]
|
|
||||||
struct PvmemcontrolReq {
|
|
||||||
func_code: Le64,
|
|
||||||
addr: Le64,
|
|
||||||
length: Le64,
|
|
||||||
arg: Le64,
|
|
||||||
}
|
|
||||||
|
|
||||||
// SAFETY: it only has data and has no implicit padding.
|
|
||||||
unsafe impl ByteValued for PvmemcontrolReq {}
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Copy, Clone, Default)]
|
|
||||||
struct PvmemcontrolResp {
|
|
||||||
ret_errno: Le32,
|
|
||||||
ret_code: Le32,
|
|
||||||
ret_value: Le64,
|
|
||||||
arg0: Le64,
|
|
||||||
arg1: Le64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Debug for PvmemcontrolResp {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
let PvmemcontrolResp {
|
|
||||||
ret_errno,
|
|
||||||
ret_code,
|
|
||||||
..
|
|
||||||
} = self;
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"PvmemcontrolResp {{ ret_errno: {}, ret_code: {}, .. }}",
|
|
||||||
ret_errno.to_native(),
|
|
||||||
ret_code.to_native()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SAFETY: it only has data and has no implicit padding.
|
|
||||||
unsafe impl ByteValued for PvmemcontrolResp {}
|
|
||||||
|
|
||||||
/// The guest connections start at 0x8000_0000, which has a leading 1 in
|
|
||||||
/// the most significant byte, this ensures it does not conflict with
|
|
||||||
/// any of the transport commands
|
|
||||||
#[derive(Hash, Clone, Copy, PartialEq, Eq, Debug)]
|
|
||||||
pub struct GuestConnection {
|
|
||||||
command: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for GuestConnection {
|
|
||||||
fn default() -> Self {
|
|
||||||
GuestConnection::new(0x8000_0000)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GuestConnection {
|
|
||||||
fn new(command: u32) -> Self {
|
|
||||||
Self { command }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn next(&self) -> Self {
|
|
||||||
let GuestConnection { command } = *self;
|
|
||||||
|
|
||||||
if command == u32::MAX {
|
|
||||||
GuestConnection::default()
|
|
||||||
} else {
|
|
||||||
GuestConnection::new(command + 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<u32> for GuestConnection {
|
|
||||||
type Error = Error;
|
|
||||||
|
|
||||||
fn try_from(value: u32) -> Result<Self, Self::Error> {
|
|
||||||
if (value & 0x8000_0000) != 0 {
|
|
||||||
Ok(GuestConnection::new(value))
|
|
||||||
} else {
|
|
||||||
Err(Error::InvalidConnection(value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct PercpuInitState {
|
|
||||||
port_buf_map: HashMap<GuestConnection, GuestAddress>,
|
|
||||||
next_conn: GuestConnection,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PercpuInitState {
|
|
||||||
fn new() -> Self {
|
|
||||||
PercpuInitState {
|
|
||||||
port_buf_map: HashMap::new(),
|
|
||||||
next_conn: GuestConnection::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum PvmemcontrolState {
|
|
||||||
PercpuInit(PercpuInitState),
|
|
||||||
Ready(HashMap<GuestConnection, GuestAddress>),
|
|
||||||
Broken,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct PvmemcontrolDevice {
|
|
||||||
transport: PvmemcontrolTransport,
|
|
||||||
state: PvmemcontrolState,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PvmemcontrolDevice {
|
|
||||||
fn new(transport: PvmemcontrolTransport, state: PvmemcontrolState) -> Self {
|
|
||||||
PvmemcontrolDevice { transport, state }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PvmemcontrolDevice {
|
|
||||||
fn register_percpu_buf(
|
|
||||||
guest_memory: &GuestMemoryAtomic<GuestMemoryMmap<AtomicBitmap>>,
|
|
||||||
mut state: PercpuInitState,
|
|
||||||
PvmemcontrolTransportRegister { buf_phys_addr }: PvmemcontrolTransportRegister,
|
|
||||||
) -> Self {
|
|
||||||
// access to this address is checked
|
|
||||||
let buf_phys_addr = GuestAddress(buf_phys_addr.into());
|
|
||||||
if !guest_memory.memory().check_range(
|
|
||||||
buf_phys_addr,
|
|
||||||
std::mem::size_of::<PvmemcontrolResp>().max(std::mem::size_of::<PvmemcontrolReq>()),
|
|
||||||
) {
|
|
||||||
warn!("guest sent invalid phys addr {:#x}", buf_phys_addr.0);
|
|
||||||
return PvmemcontrolDevice::new(
|
|
||||||
PvmemcontrolTransport::error(),
|
|
||||||
PvmemcontrolState::Broken,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let conn = {
|
|
||||||
// find an available port+byte combination, and fail if full
|
|
||||||
let mut next_conn = state.next_conn;
|
|
||||||
while state.port_buf_map.contains_key(&next_conn) {
|
|
||||||
next_conn = next_conn.next();
|
|
||||||
if next_conn == state.next_conn {
|
|
||||||
warn!("connections exhausted");
|
|
||||||
return PvmemcontrolDevice::new(
|
|
||||||
PvmemcontrolTransport::error(),
|
|
||||||
PvmemcontrolState::Broken,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
next_conn
|
|
||||||
};
|
|
||||||
state.next_conn = conn.next();
|
|
||||||
state.port_buf_map.insert(conn, buf_phys_addr);
|
|
||||||
|
|
||||||
// inform guest of the connection
|
|
||||||
let response = PvmemcontrolTransport::register_response(conn.command);
|
|
||||||
|
|
||||||
PvmemcontrolDevice::new(response, PvmemcontrolState::PercpuInit(state))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn reset() -> Self {
|
|
||||||
PvmemcontrolDevice::new(
|
|
||||||
PvmemcontrolTransport::ack(),
|
|
||||||
PvmemcontrolState::PercpuInit(PercpuInitState::new()),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn error() -> Self {
|
|
||||||
PvmemcontrolDevice::new(PvmemcontrolTransport::error(), PvmemcontrolState::Broken)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ready(PercpuInitState { port_buf_map, .. }: PercpuInitState) -> Self {
|
|
||||||
PvmemcontrolDevice::new(
|
|
||||||
PvmemcontrolTransport::ack(),
|
|
||||||
PvmemcontrolState::Ready(port_buf_map),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run_command(
|
|
||||||
&mut self,
|
|
||||||
guest_memory: &GuestMemoryAtomic<GuestMemoryMmap<AtomicBitmap>>,
|
|
||||||
command: PvmemcontrolTransportCommand,
|
|
||||||
) {
|
|
||||||
let state = std::mem::replace(&mut self.state, PvmemcontrolState::Broken);
|
|
||||||
|
|
||||||
*self = match command {
|
|
||||||
PvmemcontrolTransportCommand::Reset => Self::reset(),
|
|
||||||
PvmemcontrolTransportCommand::Register => {
|
|
||||||
if let PvmemcontrolState::PercpuInit(state) = state {
|
|
||||||
// SAFETY: By device protocol. If driver is wrong the device
|
|
||||||
// can enter a Broken state, but the behavior is still sound.
|
|
||||||
Self::register_percpu_buf(guest_memory, state, unsafe {
|
|
||||||
self.transport.as_register()
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
debug!("received register without reset");
|
|
||||||
Self::error()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
PvmemcontrolTransportCommand::Ready => {
|
|
||||||
if let PvmemcontrolState::PercpuInit(state) = state {
|
|
||||||
Self::ready(state)
|
|
||||||
} else {
|
|
||||||
debug!("received ready without reset");
|
|
||||||
Self::error()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
PvmemcontrolTransportCommand::Disconnect => Self::error(),
|
|
||||||
PvmemcontrolTransportCommand::Ack => {
|
|
||||||
debug!("received ack as command");
|
|
||||||
Self::error()
|
|
||||||
}
|
|
||||||
PvmemcontrolTransportCommand::Error => {
|
|
||||||
debug!("received error as command");
|
|
||||||
Self::error()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// read from the transport
|
|
||||||
fn read_transport(&self, offset: u64, data: &mut [u8]) {
|
|
||||||
self.transport
|
|
||||||
.as_slice()
|
|
||||||
.iter()
|
|
||||||
.skip(offset as usize)
|
|
||||||
.zip(data.iter_mut())
|
|
||||||
.for_each(|(src, dest)| *dest = *src)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// can only write to transport payload
|
|
||||||
/// command is a special register that needs separate dispatching
|
|
||||||
fn write_transport(&mut self, offset: u64, data: &[u8]) {
|
|
||||||
self.transport
|
|
||||||
.payload
|
|
||||||
.as_mut_slice()
|
|
||||||
.iter_mut()
|
|
||||||
.skip(offset as usize)
|
|
||||||
.zip(data.iter())
|
|
||||||
.for_each(|(dest, src)| *dest = *src)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn find_connection(&self, conn: GuestConnection) -> Option<GuestAddress> {
|
|
||||||
match &self.state {
|
|
||||||
PvmemcontrolState::Ready(map) => map.get(&conn).copied(),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct PvmemcontrolBusDevice {
|
|
||||||
mem: GuestMemoryAtomic<GuestMemoryMmap<AtomicBitmap>>,
|
|
||||||
dev: RwLock<PvmemcontrolDevice>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct PvmemcontrolPciDevice {
|
|
||||||
id: String,
|
|
||||||
configuration: PciConfiguration,
|
|
||||||
bar_regions: Vec<PciBarConfiguration>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PvmemcontrolBusDevice {
|
|
||||||
/// f is called with the host address of `range_base` and only when
|
|
||||||
/// [`range_base`, `range_base` + `range_len`) is present in the guest
|
|
||||||
fn operate_on_memory_range<F>(&self, addr: u64, length: u64, f: F) -> result::Result<(), Error>
|
|
||||||
where
|
|
||||||
F: FnOnce(*mut libc::c_void, libc::size_t) -> libc::c_int,
|
|
||||||
{
|
|
||||||
let memory = self.mem.memory();
|
|
||||||
let range_base = GuestAddress(addr);
|
|
||||||
let range_len = usize::try_from(length).map_err(|_| Error::InvalidRequest)?;
|
|
||||||
|
|
||||||
// assume guest memory is not interleaved with vmm memory on the host.
|
|
||||||
if !memory.check_range(range_base, range_len) {
|
|
||||||
return Err(Error::GuestMemory(GuestMemoryError::InvalidGuestAddress(
|
|
||||||
range_base,
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
let hva = memory
|
|
||||||
.get_host_address(range_base)
|
|
||||||
.map_err(Error::GuestMemory)?;
|
|
||||||
let res = f(hva as *mut libc::c_void, range_len as libc::size_t);
|
|
||||||
if res != 0 {
|
|
||||||
return Err(Error::LibcFail(io::Error::last_os_error()));
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn madvise(&self, addr: u64, length: u64, advice: libc::c_int) -> result::Result<(), Error> {
|
|
||||||
// SAFETY: [`base`, `base` + `len`) is guest memory
|
|
||||||
self.operate_on_memory_range(addr, length, |base, len| unsafe {
|
|
||||||
libc::madvise(base, len, advice)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn mlock(&self, addr: u64, length: u64, on_default: bool) -> result::Result<(), Error> {
|
|
||||||
// SAFETY: [`base`, `base` + `len`) is guest memory
|
|
||||||
self.operate_on_memory_range(addr, length, |base, len| unsafe {
|
|
||||||
libc::mlock2(base, len, if on_default { libc::MLOCK_ONFAULT } else { 0 })
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn munlock(&self, addr: u64, length: u64) -> result::Result<(), Error> {
|
|
||||||
// SAFETY: [`base`, `base` + `len`) is guest memory
|
|
||||||
self.operate_on_memory_range(addr, length, |base, len| unsafe {
|
|
||||||
libc::munlock(base, len)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn mprotect(
|
|
||||||
&self,
|
|
||||||
addr: u64,
|
|
||||||
length: u64,
|
|
||||||
protection: libc::c_int,
|
|
||||||
) -> result::Result<(), Error> {
|
|
||||||
// SAFETY: [`base`, `base` + `len`) is guest memory
|
|
||||||
self.operate_on_memory_range(addr, length, |base, len| unsafe {
|
|
||||||
libc::mprotect(base, len, protection)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_vma_anon_name(&self, addr: u64, length: u64, name: u64) -> result::Result<(), Error> {
|
|
||||||
let name = (name != 0).then(|| CString::new(format!("pvmemcontrol-{name}")).unwrap());
|
|
||||||
let name_ptr = if let Some(name) = &name {
|
|
||||||
name.as_ptr()
|
|
||||||
} else {
|
|
||||||
std::ptr::null()
|
|
||||||
};
|
|
||||||
debug!("addr {:X} length {} name {:?}", addr, length, name);
|
|
||||||
|
|
||||||
// SAFETY: [`base`, `base` + `len`) is guest memory
|
|
||||||
self.operate_on_memory_range(addr, length, |base, len| unsafe {
|
|
||||||
libc::prctl(
|
|
||||||
libc::PR_SET_VMA,
|
|
||||||
libc::PR_SET_VMA_ANON_NAME,
|
|
||||||
base,
|
|
||||||
len,
|
|
||||||
name_ptr,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn process_request(
|
|
||||||
&self,
|
|
||||||
func_code: FunctionCode,
|
|
||||||
addr: u64,
|
|
||||||
length: u64,
|
|
||||||
arg: u64,
|
|
||||||
) -> Result<PvmemcontrolResp, Error> {
|
|
||||||
let result = match func_code {
|
|
||||||
FunctionCode::Info => {
|
|
||||||
return Ok(PvmemcontrolResp {
|
|
||||||
ret_errno: 0.into(),
|
|
||||||
ret_code: 0.into(),
|
|
||||||
ret_value: get_page_size().into(),
|
|
||||||
arg0: MAJOR_VERSION.into(),
|
|
||||||
arg1: MINOR_VERSION.into(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
FunctionCode::Dontneed => self.madvise(addr, length, libc::MADV_DONTNEED),
|
|
||||||
FunctionCode::Remove => self.madvise(addr, length, libc::MADV_REMOVE),
|
|
||||||
FunctionCode::Free => self.madvise(addr, length, libc::MADV_FREE),
|
|
||||||
FunctionCode::Pageout => self.madvise(addr, length, libc::MADV_PAGEOUT),
|
|
||||||
FunctionCode::Dontdump => self.madvise(addr, length, libc::MADV_DONTDUMP),
|
|
||||||
FunctionCode::SetVMAAnonName => self.set_vma_anon_name(addr, length, arg),
|
|
||||||
FunctionCode::Mlock => self.mlock(addr, length, false),
|
|
||||||
FunctionCode::Munlock => self.munlock(addr, length),
|
|
||||||
FunctionCode::MprotectNone => self.mprotect(addr, length, libc::PROT_NONE),
|
|
||||||
FunctionCode::MprotectR => self.mprotect(addr, length, libc::PROT_READ),
|
|
||||||
FunctionCode::MprotectW => self.mprotect(addr, length, libc::PROT_WRITE),
|
|
||||||
FunctionCode::MprotectRW => {
|
|
||||||
self.mprotect(addr, length, libc::PROT_READ | libc::PROT_WRITE)
|
|
||||||
}
|
|
||||||
FunctionCode::Mergeable => self.madvise(addr, length, libc::MADV_MERGEABLE),
|
|
||||||
FunctionCode::Unmergeable => self.madvise(addr, length, libc::MADV_UNMERGEABLE),
|
|
||||||
};
|
|
||||||
result.map(|_| PvmemcontrolResp::default())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_request(
|
|
||||||
&self,
|
|
||||||
PvmemcontrolReq {
|
|
||||||
func_code,
|
|
||||||
addr,
|
|
||||||
length,
|
|
||||||
arg,
|
|
||||||
}: PvmemcontrolReq,
|
|
||||||
) -> Result<PvmemcontrolResp, Error> {
|
|
||||||
let (func_code, addr, length, arg) = (
|
|
||||||
func_code.to_native(),
|
|
||||||
addr.to_native(),
|
|
||||||
length.to_native(),
|
|
||||||
arg.to_native(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let resp_or_err = FunctionCode::try_from(func_code)
|
|
||||||
.map_err(|_| Error::UnknownFunctionCode(func_code))
|
|
||||||
.and_then(|func_code| self.process_request(func_code, addr, length, arg));
|
|
||||||
|
|
||||||
let resp = match resp_or_err {
|
|
||||||
Ok(resp) => resp,
|
|
||||||
Err(e) => match e {
|
|
||||||
Error::InvalidArgument(arg) => PvmemcontrolResp {
|
|
||||||
ret_errno: (libc::EINVAL as u32).into(),
|
|
||||||
ret_code: (arg as u32).into(),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
Error::LibcFail(err) => PvmemcontrolResp {
|
|
||||||
ret_errno: (err.raw_os_error().unwrap_or(libc::EFAULT) as u32).into(),
|
|
||||||
ret_code: 0u32.into(),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
Error::UnknownFunctionCode(func_code) => PvmemcontrolResp {
|
|
||||||
ret_errno: (libc::EOPNOTSUPP as u32).into(),
|
|
||||||
ret_code: (func_code as u32).into(),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
Error::GuestMemory(err) => {
|
|
||||||
warn!("{}", err);
|
|
||||||
PvmemcontrolResp {
|
|
||||||
ret_errno: (libc::EINVAL as u32).into(),
|
|
||||||
ret_code: (func_code as u32).into(),
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// device error, stop responding
|
|
||||||
other => return Err(other),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
Ok(resp)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_pvmemcontrol_request(&self, guest_addr: GuestAddress) {
|
|
||||||
let request: PvmemcontrolReq = if let Ok(x) = self.mem.memory().read_obj(guest_addr) {
|
|
||||||
x
|
|
||||||
} else {
|
|
||||||
warn!("cannot read from guest address {:#x}", guest_addr.0);
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let response: PvmemcontrolResp = match self.handle_request(request) {
|
|
||||||
Ok(x) => x,
|
|
||||||
Err(e) => {
|
|
||||||
warn!("cannot process request {:?} with error {}", request, e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if self.mem.memory().write_obj(response, guest_addr).is_err() {
|
|
||||||
warn!("cannot write to guest address {:#x}", guest_addr.0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_guest_write(&self, offset: u64, data: &[u8]) {
|
|
||||||
if offset as usize != std::mem::offset_of!(PvmemcontrolTransport, command) {
|
|
||||||
if data.len() != 4 && data.len() != 8 {
|
|
||||||
warn!("guest write is not 4 or 8 bytes long");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
self.dev.write().unwrap().write_transport(offset, data);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let data = if data.len() == 4 {
|
|
||||||
let mut d = [0u8; 4];
|
|
||||||
d.iter_mut()
|
|
||||||
.zip(data.iter())
|
|
||||||
.for_each(|(d, data)| *d = *data);
|
|
||||||
d
|
|
||||||
} else {
|
|
||||||
warn!("guest write with non u32 at command register");
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let data_cmd = u32::from_le_bytes(data);
|
|
||||||
let command = PvmemcontrolTransportCommand::try_from(data_cmd);
|
|
||||||
|
|
||||||
match command {
|
|
||||||
Ok(command) => self.dev.write().unwrap().run_command(&self.mem, command),
|
|
||||||
Err(_) => {
|
|
||||||
GuestConnection::try_from(data_cmd)
|
|
||||||
.and_then(|conn| {
|
|
||||||
self.dev
|
|
||||||
.read()
|
|
||||||
.unwrap()
|
|
||||||
.find_connection(conn)
|
|
||||||
.ok_or(Error::InvalidConnection(conn.command))
|
|
||||||
})
|
|
||||||
.map(|gpa| self.handle_pvmemcontrol_request(gpa))
|
|
||||||
.unwrap_or_else(|err| warn!("{:?}", err));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_guest_read(&self, offset: u64, data: &mut [u8]) {
|
|
||||||
self.dev.read().unwrap().read_transport(offset, data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PvmemcontrolDevice {
|
|
||||||
pub fn make_device(
|
|
||||||
id: String,
|
|
||||||
mem: GuestMemoryAtomic<GuestMemoryMmap<AtomicBitmap>>,
|
|
||||||
) -> (PvmemcontrolPciDevice, PvmemcontrolBusDevice) {
|
|
||||||
let dev = RwLock::new(PvmemcontrolDevice::error());
|
|
||||||
let mut configuration = PciConfiguration::new(
|
|
||||||
PVMEMCONTROL_VENDOR_ID,
|
|
||||||
PVMEMCONTROL_DEVICE_ID,
|
|
||||||
0x1,
|
|
||||||
PciClassCode::BaseSystemPeripheral,
|
|
||||||
&PvmemcontrolSubclass::Other,
|
|
||||||
None,
|
|
||||||
PciHeaderType::Device,
|
|
||||||
PVMEMCONTROL_SUBSYSTEM_VENDOR_ID,
|
|
||||||
PVMEMCONTROL_SUBSYSTEM_ID,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
let command: [u8; 2] = [0x03, 0x01]; // memory, io, SERR#
|
|
||||||
|
|
||||||
configuration.write_config_register(1, 0, &command);
|
|
||||||
(
|
|
||||||
PvmemcontrolPciDevice {
|
|
||||||
id,
|
|
||||||
configuration,
|
|
||||||
bar_regions: Vec::new(),
|
|
||||||
},
|
|
||||||
PvmemcontrolBusDevice { mem, dev },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PciDevice for PvmemcontrolPciDevice {
|
|
||||||
fn write_config_register(
|
|
||||||
&mut self,
|
|
||||||
reg_idx: usize,
|
|
||||||
offset: u64,
|
|
||||||
data: &[u8],
|
|
||||||
) -> (Vec<BarReprogrammingParams>, 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_config_register(reg_idx)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
fn id(&self) -> Option<String> {
|
|
||||||
Some(self.id.clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn allocate_bars(
|
|
||||||
&mut self,
|
|
||||||
_allocator: &Arc<Mutex<SystemAllocator>>,
|
|
||||||
mmio32_allocator: &mut AddressAllocator,
|
|
||||||
_mmio64_allocator: &mut AddressAllocator,
|
|
||||||
resources: Option<Vec<Resource>>,
|
|
||||||
) -> Result<Vec<PciBarConfiguration>, PciDeviceError> {
|
|
||||||
let mut bars = Vec::new();
|
|
||||||
let region_type = PciBarRegionType::Memory32BitRegion;
|
|
||||||
let bar_id = 0;
|
|
||||||
let region_size = PVMEMCONTROL_DEVICE_MMIO_SIZE;
|
|
||||||
let restoring = resources.is_some();
|
|
||||||
let bar_addr = mmio32_allocator
|
|
||||||
.allocate(None, region_size, Some(PVMEMCONTROL_DEVICE_MMIO_ALIGN))
|
|
||||||
.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);
|
|
||||||
|
|
||||||
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,
|
|
||||||
) -> 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<(), io::Error> {
|
|
||||||
for bar in self.bar_regions.iter_mut() {
|
|
||||||
if bar.addr() == old_base {
|
|
||||||
*bar = bar.set_address(new_base);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Pausable for PvmemcontrolPciDevice {
|
|
||||||
fn pause(&mut self) -> std::result::Result<(), MigratableError> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn resume(&mut self) -> std::result::Result<(), MigratableError> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Snapshottable for PvmemcontrolPciDevice {
|
|
||||||
fn id(&self) -> String {
|
|
||||||
self.id.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
|
|
||||||
let mut snapshot = Snapshot::new_from_state(&())?;
|
|
||||||
|
|
||||||
// Snapshot PciConfiguration
|
|
||||||
snapshot.add_snapshot(self.configuration.id(), self.configuration.snapshot()?);
|
|
||||||
|
|
||||||
Ok(snapshot)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Transportable for PvmemcontrolPciDevice {}
|
|
||||||
impl Migratable for PvmemcontrolPciDevice {}
|
|
||||||
|
|
||||||
impl BusDeviceSync for PvmemcontrolBusDevice {
|
|
||||||
fn read(&self, _base: u64, offset: u64, data: &mut [u8]) {
|
|
||||||
self.handle_guest_read(offset, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write(&self, _base: u64, offset: u64, data: &[u8]) -> Option<Arc<Barrier>> {
|
|
||||||
self.handle_guest_write(offset, data);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,265 +0,0 @@
|
|||||||
// Copyright © 2023 Tencent Corporation
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
//
|
|
||||||
|
|
||||||
use std::any::Any;
|
|
||||||
use std::result;
|
|
||||||
use std::sync::{Arc, Barrier, Mutex};
|
|
||||||
|
|
||||||
use anyhow::anyhow;
|
|
||||||
use pci::{
|
|
||||||
BarReprogrammingParams, PciBarConfiguration, PciBarPrefetchable, PciBarRegionType,
|
|
||||||
PciClassCode, PciConfiguration, PciDevice, PciDeviceError, PciHeaderType, PciSubclass,
|
|
||||||
PCI_CONFIGURATION_ID,
|
|
||||||
};
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
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];
|
|
||||||
let bar_reprogram = configuration.write_config_register(1, 0, &command);
|
|
||||||
assert!(
|
|
||||||
bar_reprogram.is_empty(),
|
|
||||||
"No bar reprogrammig is expected from writing to the COMMAND register"
|
|
||||||
);
|
|
||||||
|
|
||||||
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],
|
|
||||||
) -> (Vec<BarReprogrammingParams>, 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 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(&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 {}
|
|
||||||
@@ -3,14 +3,13 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
//
|
//
|
||||||
|
|
||||||
use std::cmp;
|
|
||||||
use std::sync::{Arc, Barrier};
|
|
||||||
|
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
#[cfg(target_arch = "aarch64")]
|
#[cfg(target_arch = "aarch64")]
|
||||||
use arch::aarch64::layout::{TPM_SIZE, TPM_START};
|
use arch::aarch64::layout::{TPM_SIZE, TPM_START};
|
||||||
#[cfg(target_arch = "x86_64")]
|
#[cfg(target_arch = "x86_64")]
|
||||||
use arch::x86_64::layout::{TPM_SIZE, TPM_START};
|
use arch::x86_64::layout::{TPM_SIZE, TPM_START};
|
||||||
|
use std::cmp;
|
||||||
|
use std::sync::{Arc, Barrier};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use tpm::emulator::{BackendCmd, Emulator};
|
use tpm::emulator::{BackendCmd, Emulator};
|
||||||
use tpm::TPM_CRB_BUFFER_MAX;
|
use tpm::TPM_CRB_BUFFER_MAX;
|
||||||
@@ -451,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,41 +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/content/dam/amd/en/documents/epyc-business-docs/solution-briefs/amd-secure-encrypted-virtualization-solution-brief.pdf):
|
|
||||||
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)
|
|
||||||
208
docs/api.md
208
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,17 +40,22 @@ 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, if enabled, 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 either a local UNIX socket as given in the Cloud Hypervisor option `--api-socket path=...`
|
through a local UNIX socket.
|
||||||
or a fd with `--api-socket fd=...`.
|
By default, it is located at `/run/user/{user ID}/cloud-hypervisor.{Cloud Hypervisor PID}`.
|
||||||
|
For example, if you launched Cloud Hypervisor as user ID 1000 and its PID is
|
||||||
|
123456, the Cloud Hypervisor REST API will be available at `/run/user/1000/cloud-hypervisor.123456`.
|
||||||
|
|
||||||
|
The REST API default URL can be overridden through the Cloud Hypervisor
|
||||||
|
option `--api-socket`:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ ./target/debug/cloud-hypervisor --api-socket path=/tmp/cloud-hypervisor.sock
|
$ ./target/debug/cloud-hypervisor --api-socket /tmp/cloud-hypervisor.sock
|
||||||
Cloud Hypervisor Guest
|
Cloud Hypervisor Guest
|
||||||
API server: /tmp/cloud-hypervisor.sock
|
API server: /tmp/cloud-hypervisor.sock
|
||||||
vCPUs: 1
|
vCPUs: 1
|
||||||
@@ -65,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 |
|
||||||
| ---------------------------------- | ----------------------- | ------------------------------- | ------------------------ | ------------------------------------------------------ |
|
| ---------------------------------- | ----------------------- | ------------------------------- | ------------------------ | ------------------------------------------------------ |
|
||||||
@@ -105,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`:
|
||||||
@@ -130,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:
|
||||||
|
|
||||||
@@ -143,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' \
|
||||||
@@ -158,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
|
||||||
@@ -299,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):
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -314,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 +--------------+
|
||||||
| |
|
| |
|
||||||
+----------+
|
+----------+
|
||||||
|
|
||||||
@@ -333,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.
|
||||||
@@ -381,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
|
|
||||||
# Set env to enable code coverage
|
|
||||||
export RUSTFLAGS="-Cinstrument-coverage"
|
|
||||||
export LLVM_PROFILE_FILE="ch-%p-%m.profraw"
|
|
||||||
|
|
||||||
# Run unit tests
|
|
||||||
scripts/dev_cli.sh tests --unit --libc gnu
|
|
||||||
|
|
||||||
# Run integration tests
|
|
||||||
scripts/dev_cli.sh tests --integration --libc gnu
|
|
||||||
scripts/dev_cli.sh tests --integration-live-migration --libc gnu
|
|
||||||
|
|
||||||
# Export code coverage report
|
|
||||||
scripts/dev_cli.sh tests --coverage -- -- html
|
|
||||||
```
|
|
||||||
@@ -4,17 +4,151 @@ In the context of adding more utilities to the Ubuntu cloud image being used
|
|||||||
for integration testing, this quick guide details how to achieve the proper
|
for integration testing, this quick guide details how to achieve the proper
|
||||||
modification of an official Ubuntu cloud image.
|
modification of an official Ubuntu cloud image.
|
||||||
|
|
||||||
## Image generation script
|
## Create the image
|
||||||
|
|
||||||
This [script](../scripts/build-custom-image.sh) can be used to generate a custom image (needs to be modified per architecture/distribution image)
|
Let's go through the steps on how to extend an official Ubuntu image. These
|
||||||
|
steps can be applied to other distributions (with a few changes regarding
|
||||||
|
package management).
|
||||||
|
|
||||||
|
### Get latest Ubuntu cloud image
|
||||||
|
|
||||||
|
```bash
|
||||||
|
wget https://cloud-images.ubuntu.com/focal/current/focal-server-cloudimg-amd64.img
|
||||||
|
```
|
||||||
|
|
||||||
|
### Check the file format is QCOW2
|
||||||
|
|
||||||
|
```bash
|
||||||
|
file focal-server-cloudimg-amd64.img
|
||||||
|
focal-server-cloudimg-amd64.img: QEMU QCOW2 Image (v2), 2361393152 bytes
|
||||||
|
```
|
||||||
|
|
||||||
|
### Convert QCOW2 into RAW
|
||||||
|
|
||||||
|
```bash
|
||||||
|
qemu-img convert -p -f qcow2 -O raw focal-server-cloudimg-amd64.img focal-server-cloudimg-amd64.raw
|
||||||
|
```
|
||||||
|
|
||||||
|
### Identify the Linux partition
|
||||||
|
|
||||||
|
The goal is to mount the image rootfs so that it can be modified as needed.
|
||||||
|
That's why we need to identify where the Linux filesystem partition is located
|
||||||
|
in the image.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo fdisk -l focal-server-cloudimg-amd64.raw
|
||||||
|
Disk focal-server-cloudimg-amd64.raw: 2.2 GiB, 2361393152 bytes, 4612096 sectors
|
||||||
|
Units: sectors of 1 * 512 = 512 bytes
|
||||||
|
Sector size (logical/physical): 512 bytes / 512 bytes
|
||||||
|
I/O size (minimum/optimal): 512 bytes / 512 bytes
|
||||||
|
Disklabel type: gpt
|
||||||
|
Disk identifier: A1171ABA-2BEA-4218-A467-1B2B607E5953
|
||||||
|
|
||||||
|
Device Start End Sectors Size Type
|
||||||
|
focal-server-cloudimg-amd64.raw1 227328 4612062 4384735 2.1G Linux filesystem
|
||||||
|
focal-server-cloudimg-amd64.raw14 2048 10239 8192 4M BIOS boot
|
||||||
|
focal-server-cloudimg-amd64.raw15 10240 227327 217088 106M EFI System
|
||||||
|
|
||||||
|
Partition table entries are not in disk order.
|
||||||
|
```
|
||||||
|
|
||||||
|
### Mount the Linux partition
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p /mnt
|
||||||
|
sudo mount -o loop,offset=$((227328 * 512)) focal-server-cloudimg-amd64.raw /mnt
|
||||||
|
```
|
||||||
|
|
||||||
|
### Set up DNS
|
||||||
|
|
||||||
|
The next step describes changing the root directory to the rootfs contained by
|
||||||
|
the cloud image. For DNS to work in the root directory, you will need to first bind-mount
|
||||||
|
the host `/etc/resolv.conf` onto the mounted linux partition of the cloud image.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo mount -o bind /etc/resolv.conf /mnt/etc/resolv.conf
|
||||||
|
```
|
||||||
|
|
||||||
|
### Change root directory
|
||||||
|
|
||||||
|
Changing the root directory will allow us to install new packages to the rootfs
|
||||||
|
contained by the cloud image.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo chroot /mnt
|
||||||
|
mount -t proc proc /proc
|
||||||
|
mount -t devpts devpts /dev/pts
|
||||||
|
```
|
||||||
|
|
||||||
|
### Install needed packages
|
||||||
|
|
||||||
|
In the context Cloud Hypervisor's integration tests, we need several utilities.
|
||||||
|
Here is the way to install them for a Ubuntu image. This step is specific to
|
||||||
|
Ubuntu distributions.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
apt update
|
||||||
|
apt install fio iperf iperf3 socat stress cpuid tpm2-tools
|
||||||
|
```
|
||||||
|
|
||||||
|
### Remove counterproductive packages
|
||||||
|
|
||||||
|
* snapd:
|
||||||
|
|
||||||
|
This prevents snapd from trying to mount squashfs filesystem when the kernel
|
||||||
|
might not support it. This might be the case when the image is used with direct
|
||||||
|
kernel boot. This step is specific to Ubuntu distributions.
|
||||||
|
|
||||||
|
* pollinate:
|
||||||
|
|
||||||
|
Remove this package which can fail and lead to the SSH daemon failing to start.
|
||||||
|
See #2113 for details.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
apt remove --purge snapd pollinate
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Cleanup the image
|
||||||
|
|
||||||
|
Leave no trace in the image before unmounting its content.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
umount /dev/pts
|
||||||
|
umount /proc
|
||||||
|
history -c
|
||||||
|
exit
|
||||||
|
umount /mnt/etc/resolv.conf
|
||||||
|
umount /mnt
|
||||||
|
```
|
||||||
|
|
||||||
|
### Rename the image
|
||||||
|
|
||||||
|
Renaming is important to identify this is a modified image.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mv focal-server-cloudimg-amd64.raw focal-server-cloudimg-amd64-custom-$(date "+%Y%m%d")-0.raw
|
||||||
|
```
|
||||||
|
|
||||||
|
The `-0` is the revision and is only necessary to change if multiple images are
|
||||||
|
updated on the same day.
|
||||||
|
|
||||||
|
### Create QCOW2 from RAW
|
||||||
|
|
||||||
|
Last step is to create the QCOW2 image back from the modified image.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
qemu-img convert -p -f raw -O qcow2 focal-server-cloudimg-amd64-custom-$(date "+%Y%m%d")-0.raw focal-server-cloudimg-amd64-custom-$(date "+%Y%m%d")-0.qcow2
|
||||||
|
```
|
||||||
|
|
||||||
## Switch CI to use the new image
|
## Switch CI to use the new image
|
||||||
|
|
||||||
### Upload to Azure storage
|
### Upload to Azure storage
|
||||||
|
|
||||||
A command like the following can be used to upload the image:
|
The next step is to update both images (QCOW2 and RAW) stored as part of the
|
||||||
|
Azure storage account, replacing them with the newly created ones. This will
|
||||||
`az storage blob upload --account-name cloudhypervisorstorages --container-name '$web' --name jammy-server-cloudimg-amd64-custom-20241017-0.qcow2 --file jammy-server-cloudimg-amd64-custom-20241017-0.qcow2 --sas-token <redacted>`
|
make these new images available from the integration tests. This is usually
|
||||||
|
achieved through the web interface.
|
||||||
|
|
||||||
### Update integration tests
|
### Update integration tests
|
||||||
|
|
||||||
@@ -25,6 +159,171 @@ 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
|
||||||
|
|
||||||
Uncomment "VFIO_CUSTOM_IMAGE" in the script listed above to generate the custom image used for the VFIO worker.
|
Here we are going to describe how to create a cloud image that contains the
|
||||||
|
necessary NVIDIA drivers for our VFIO baremetal CI.
|
||||||
|
|
||||||
|
### Download base image
|
||||||
|
|
||||||
|
We usually start from one of the custom cloud image we have previously created
|
||||||
|
but we can use a stock cloud image as well.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
wget https://cloud-hypervisor.azureedge.net/jammy-server-cloudimg-amd64-custom-20230119-0.raw
|
||||||
|
mv jammy-server-cloudimg-amd64-custom-20230119-0.raw jammy-server-cloudimg-amd64-nvidia.raw
|
||||||
|
```
|
||||||
|
|
||||||
|
### Extend the image size
|
||||||
|
|
||||||
|
The NVIDIA drivers consume lots of space, which is why we must resize the image
|
||||||
|
before we proceed any further.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
qemu-img resize jammy-server-cloudimg-amd64-nvidia.raw 5G
|
||||||
|
```
|
||||||
|
|
||||||
|
### Resize the partition
|
||||||
|
|
||||||
|
We use `parted` for fixing the GPT after the image was resized, as well as for
|
||||||
|
resizing the `Linux` partition.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo parted jammy-server-cloudimg-amd64-nvidia.raw
|
||||||
|
|
||||||
|
(parted) print
|
||||||
|
Warning: Not all of the space available to jammy-server-cloudimg-amd64-nvidia.raw
|
||||||
|
appears to be used, you can fix the GPT to use all of the space (an extra 5873664
|
||||||
|
blocks) or continue with the current setting?
|
||||||
|
Fix/Ignore? Fix
|
||||||
|
Model: (file)
|
||||||
|
Disk jammy-server-cloudimg-amd64-nvidia.raw: 5369MB
|
||||||
|
Sector size (logical/physical): 512B/512B
|
||||||
|
Partition Table: gpt
|
||||||
|
Disk Flags:
|
||||||
|
|
||||||
|
Number Start End Size File system Name Flags
|
||||||
|
14 1049kB 5243kB 4194kB bios_grub
|
||||||
|
15 5243kB 116MB 111MB fat32 boot, esp
|
||||||
|
1 116MB 2361MB 2245MB ext4
|
||||||
|
|
||||||
|
(parted) resizepart 1 5369MB
|
||||||
|
(parted) print
|
||||||
|
Model: (file)
|
||||||
|
Disk jammy-server-cloudimg-amd64-nvidia.raw: 5369MB
|
||||||
|
Sector size (logical/physical): 512B/512B
|
||||||
|
Partition Table: gpt
|
||||||
|
Disk Flags:
|
||||||
|
|
||||||
|
Number Start End Size File system Name Flags
|
||||||
|
14 1049kB 5243kB 4194kB bios_grub
|
||||||
|
15 5243kB 116MB 111MB fat32 boot, esp
|
||||||
|
1 116MB 5369MB 5252MB ext4
|
||||||
|
|
||||||
|
(parted) quit
|
||||||
|
```
|
||||||
|
|
||||||
|
### Create a macvtap interface
|
||||||
|
|
||||||
|
Rely on the following [documentation](docs/macvtap-bridge.md) to set up a
|
||||||
|
macvtap interface to provide your VM with proper connectivity.
|
||||||
|
|
||||||
|
### Boot the image
|
||||||
|
|
||||||
|
It is particularly important to boot with a `cloud-init` disk attached to the
|
||||||
|
VM as it will automatically resize the Linux `ext4` filesystem based on the
|
||||||
|
partition that we have previously resized.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./cloud-hypervisor \
|
||||||
|
--kernel hypervisor-fw \
|
||||||
|
--disk path=focal-server-cloudimg-amd64-nvidia.raw path=/tmp/ubuntu-cloudinit.img \
|
||||||
|
--cpus boot=4 \
|
||||||
|
--memory size=4G \
|
||||||
|
--net fd=3,mac=$mac 3<>$"$tapdevice"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Bring up connectivity
|
||||||
|
|
||||||
|
If your network has a DHCP server, run the following from your VM
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo dhclient
|
||||||
|
```
|
||||||
|
|
||||||
|
But if that's not the case, let's give it an IP manually (the IP addresses
|
||||||
|
depend on your actual network) and set the DNS server IP address as well.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo ip addr add 192.168.2.10/24 dev ens4
|
||||||
|
sudo ip link set up dev ens4
|
||||||
|
sudo ip route add default via 192.168.2.1
|
||||||
|
sudo resolvectl dns ens4 8.8.8.8
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Check connectivity and update the image
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo apt update
|
||||||
|
sudo apt upgrade
|
||||||
|
```
|
||||||
|
|
||||||
|
### Install NVIDIA drivers
|
||||||
|
|
||||||
|
The following steps and commands are referenced from the
|
||||||
|
[NVIDIA official documentation](https://docs.nvidia.com/datacenter/tesla/tesla-installation-notes/index.html#ubuntu-lts)
|
||||||
|
about Tesla compute cards.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
distribution=$(. /etc/os-release;echo $ID$VERSION_ID | sed -e 's/\.//g')
|
||||||
|
wget https://developer.download.nvidia.com/compute/cuda/repos/$distribution/x86_64/cuda-keyring_1.0-1_all.deb
|
||||||
|
sudo dpkg -i cuda-keyring_1.0-1_all.deb
|
||||||
|
sudo apt-key del 7fa2af80
|
||||||
|
sudo apt update
|
||||||
|
sudo apt -y install cuda-drivers
|
||||||
|
```
|
||||||
|
|
||||||
|
### Check the `nvidia-smi` tool
|
||||||
|
|
||||||
|
Quickly validate that you can find and run the `nvidia-smi` command from your
|
||||||
|
VM. At this point it should fail given no NVIDIA card has been passed through
|
||||||
|
the VM, therefore no NVIDIA driver is loaded.
|
||||||
|
|
||||||
|
### Workaround LA57 reboot issue
|
||||||
|
|
||||||
|
Add `reboot=a` to `GRUB_CMDLINE_LINUX` in `etc/default/grub` so that the VM
|
||||||
|
will be booted with the ACPI reboot type. This resolves a reboot issue when
|
||||||
|
running on 5-level paging systems.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo vim /etc/default/grub
|
||||||
|
sudo update-grub
|
||||||
|
sudo reboot
|
||||||
|
```
|
||||||
|
|
||||||
|
### Remove previous logins
|
||||||
|
|
||||||
|
Since our integration tests rely on past logins to count the number of reboots,
|
||||||
|
we must ensure to clear the list.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
>/var/log/lastlog
|
||||||
|
>/var/log/wtmp
|
||||||
|
>/var/log/btmp
|
||||||
|
```
|
||||||
|
|
||||||
|
### Clear history
|
||||||
|
|
||||||
|
```
|
||||||
|
history -c
|
||||||
|
rm /home/cloud/.bash_history
|
||||||
|
```
|
||||||
|
|
||||||
|
### Reset cloud-init
|
||||||
|
|
||||||
|
This is mandatory as we want `cloud-init` provisioning to work again when a new
|
||||||
|
VM will be booted with this image.
|
||||||
|
|
||||||
|
```
|
||||||
|
sudo cloud-init clean
|
||||||
|
```
|
||||||
@@ -1,17 +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)
|
||||||
|
I/O port to trace user defined guest events.
|
||||||
- [`0x80` debug port](https://web.archive.org/web/20211028033025/https://www.intel.com/content/www/us/en/support/articles/000005500/boards-and-kits.html)
|
|
||||||
- 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`
|
||||||
@@ -26,13 +16,13 @@ for debugging specific components of the guest software stack. When logging a
|
|||||||
write of one of those codes to the debug port, `cloud-hypervisor` adds a
|
write of one of those codes to the debug port, `cloud-hypervisor` adds a
|
||||||
pre-defined string to the logs.
|
pre-defined string to the logs.
|
||||||
|
|
||||||
| Code Range | Component | Log string |
|
| Code Range | Component | Log string |
|
||||||
| ---------------- | ---------- | ------------ |
|
| ---------------- | ----------- | ------------ |
|
||||||
| `0x00` to `0x1f` | Firmware | `Firmware` |
|
| `0x00` to `0x1f` | Firmware | `Firmware` |
|
||||||
| `0x20` to `0x3f` | Bootloader | `Bootloader` |
|
| `0x20` to `0x3f` | Bootloader | `Bootloader` |
|
||||||
| `0x40` to `0x5f` | Kernel | `Kernel` |
|
| `0x40` to `0x5f` | Kernel | `Kernel` |
|
||||||
| `0x60` to `0x7f` | Userspace | `Userspace` |
|
| `0x60` to `0x7f` | Userspace | `Userspace` |
|
||||||
| `0x80` to `0xff` | Custom | `Custom` |
|
| `0x80` to `0xff` | Custom | `Custom` |
|
||||||
|
|
||||||
One typical use case is guest boot time measurement and tracing. By writing
|
One typical use case is guest boot time measurement and tracing. By writing
|
||||||
different values to the debug I/O port at different boot process steps, the
|
different values to the debug I/O port at different boot process steps, the
|
||||||
@@ -40,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`):
|
||||||
|
|
||||||
@@ -58,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
|
||||||
@@ -69,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
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user