mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
Compare commits
20 Commits
v51.1
...
stable/v30
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
93d7e01b41 | ||
|
|
4fe593e12d | ||
|
|
4203a61947 | ||
|
|
e397e739bf | ||
|
|
2724edd1e5 | ||
|
|
269844da73 | ||
|
|
a299a10874 | ||
|
|
015941e294 | ||
|
|
ca6fe2a98e | ||
|
|
f4090b0196 | ||
|
|
3432c0ce5e | ||
|
|
ec70af1606 | ||
|
|
bb0d82c365 | ||
|
|
2d98a16d05 | ||
|
|
cd1a645421 | ||
|
|
4485210de5 | ||
|
|
0aa858c266 | ||
|
|
499e8433c3 | ||
|
|
ff27b00f5a | ||
|
|
d09af361bc |
@@ -1,3 +0,0 @@
|
||||
[profile.default]
|
||||
# Don't let one individual test run for more than 10 minutes
|
||||
slow-timeout = { period = "60s", terminate-after = 10 }
|
||||
76
.github/dependabot.yml
vendored
76
.github/dependabot.yml
vendored
@@ -1,70 +1,18 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: cargo
|
||||
directories:
|
||||
- "/"
|
||||
- "/fuzz"
|
||||
schedule:
|
||||
interval: weekly
|
||||
allow:
|
||||
- dependency-name: "acpi_tables"
|
||||
- dependency-name: "kvm-bindings"
|
||||
- dependency-name: "kvm-ioctls"
|
||||
- dependency-name: "linux-loader"
|
||||
- dependency-name: "micro_http"
|
||||
- dependency-name: "mshv-bindings"
|
||||
- dependency-name: "mshv-ioctls"
|
||||
- dependency-name: "seccompiler"
|
||||
- dependency-name: "vfio-bindings"
|
||||
- dependency-name: "vfio-ioctls"
|
||||
- dependency-name: "vfio_user"
|
||||
- dependency-name: "vhost"
|
||||
- dependency-name: "vhost-user-backend"
|
||||
- dependency-name: "virtio-bindings"
|
||||
- dependency-name: "virtio-queue"
|
||||
- dependency-name: "vm-fdt"
|
||||
- dependency-name: "vm-memory"
|
||||
- dependency-name: "vmm-sys-util"
|
||||
groups:
|
||||
rust-vmm:
|
||||
patterns:
|
||||
- "*"
|
||||
- package-ecosystem: cargo
|
||||
directories:
|
||||
- "/"
|
||||
- "/fuzz"
|
||||
schedule:
|
||||
interval: weekly
|
||||
allow:
|
||||
- dependency-type: all
|
||||
ignore:
|
||||
- dependency-name: "acpi_tables"
|
||||
- dependency-name: "kvm-bindings"
|
||||
- dependency-name: "kvm-ioctls"
|
||||
- dependency-name: "linux-loader"
|
||||
- dependency-name: "micro_http"
|
||||
- dependency-name: "mshv-bindings"
|
||||
- dependency-name: "mshv-ioctls"
|
||||
- dependency-name: "seccompiler"
|
||||
- dependency-name: "vfio-bindings"
|
||||
- dependency-name: "vfio-ioctls"
|
||||
- dependency-name: "vfio_user"
|
||||
- dependency-name: "vhost"
|
||||
- dependency-name: "vhost-user-backend"
|
||||
- dependency-name: "virtio-bindings"
|
||||
- dependency-name: "virtio-queue"
|
||||
- dependency-name: "vm-fdt"
|
||||
- dependency-name: "vm-memory"
|
||||
- dependency-name: "vmm-sys-util"
|
||||
groups:
|
||||
non-rust-vmm:
|
||||
patterns:
|
||||
- "*"
|
||||
# Makes it possible to have another config for the same directory.
|
||||
# https://github.com/dependabot/dependabot-core/issues/1778#issuecomment-1988140219
|
||||
target-branch: main
|
||||
- package-ecosystem: github-actions
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: daily
|
||||
open-pull-requests-limit: 1
|
||||
open-pull-requests-limit: 1
|
||||
allow:
|
||||
- dependency-type: direct
|
||||
- dependency-type: indirect
|
||||
- package-ecosystem: cargo
|
||||
directory: "/fuzz"
|
||||
schedule:
|
||||
interval: daily
|
||||
open-pull-requests-limit: 1
|
||||
allow:
|
||||
- dependency-type: direct
|
||||
- dependency-type: indirect
|
||||
|
||||
7
.github/workflows/audit.yaml
vendored
7
.github/workflows/audit.yaml
vendored
@@ -4,13 +4,12 @@ on:
|
||||
paths:
|
||||
- '**/Cargo.toml'
|
||||
- '**/Cargo.lock'
|
||||
|
||||
jobs:
|
||||
security_audit:
|
||||
name: Audit
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions-rust-lang/audit@v1
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions-rs/audit-check@v1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
43
.github/workflows/build.yaml
vendored
43
.github/workflows/build.yaml
vendored
@@ -1,11 +1,9 @@
|
||||
name: Cloud Hypervisor Build
|
||||
on: [pull_request, merge_group]
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ github.event_name }}
|
||||
cancel-in-progress: true
|
||||
on: [pull_request, create]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: github.event_name == 'pull_request'
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
@@ -15,13 +13,13 @@ jobs:
|
||||
- stable
|
||||
- beta
|
||||
- nightly
|
||||
- "1.89.0"
|
||||
- "1.62"
|
||||
target:
|
||||
- x86_64-unknown-linux-gnu
|
||||
- x86_64-unknown-linux-musl
|
||||
steps:
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -29,46 +27,29 @@ jobs:
|
||||
run: sudo apt install -y musl-tools
|
||||
|
||||
- name: Install Rust toolchain (${{ matrix.rust }})
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ matrix.rust }}
|
||||
target: ${{ matrix.target }}
|
||||
override: true
|
||||
|
||||
- name: Build (default features)
|
||||
run: cargo build --locked --bin cloud-hypervisor
|
||||
run: cargo rustc --locked --bin cloud-hypervisor -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||
|
||||
- name: Build (kvm)
|
||||
run: cargo build --locked --bin cloud-hypervisor --no-default-features --features "kvm"
|
||||
run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||
|
||||
- name: Build (default features + tdx)
|
||||
run: cargo build --locked --bin cloud-hypervisor --features "tdx"
|
||||
|
||||
- name: Build (default features + dbus_api)
|
||||
run: cargo build --locked --bin cloud-hypervisor --features "dbus_api"
|
||||
run: cargo rustc --locked --bin cloud-hypervisor --features "tdx" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||
|
||||
- name: Build (default features + guest_debug)
|
||||
run: cargo build --locked --bin cloud-hypervisor --features "guest_debug"
|
||||
|
||||
- name: Build (default features + pvmemcontrol)
|
||||
run: cargo build --locked --bin cloud-hypervisor --features "pvmemcontrol"
|
||||
|
||||
- name: Build (default features + fw_cfg)
|
||||
run: cargo build --locked --bin cloud-hypervisor --features "fw_cfg"
|
||||
|
||||
- name: Build (default features + ivshmem)
|
||||
run: cargo build --locked --bin cloud-hypervisor --features "ivshmem"
|
||||
run: cargo rustc --locked --bin cloud-hypervisor --features "guest_debug" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||
|
||||
- name: Build (mshv)
|
||||
run: cargo build --locked --bin cloud-hypervisor --no-default-features --features "mshv"
|
||||
|
||||
- name: Build (sev_snp)
|
||||
run: cargo build --locked --bin cloud-hypervisor --no-default-features --features "sev_snp"
|
||||
|
||||
- name: Build (igvm)
|
||||
run: cargo build --locked --bin cloud-hypervisor --no-default-features --features "igvm"
|
||||
run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "mshv" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||
|
||||
- name: Build (mshv + kvm)
|
||||
run: cargo build --locked --bin cloud-hypervisor --no-default-features --features "mshv,kvm"
|
||||
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)
|
||||
run: cargo build --locked --all --release --target=${{ matrix.target }}
|
||||
|
||||
10
.github/workflows/dco.yaml
vendored
10
.github/workflows/dco.yaml
vendored
@@ -1,18 +1,16 @@
|
||||
name: DCO
|
||||
on: [pull_request, merge_group]
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
jobs:
|
||||
check:
|
||||
name: DCO Check ("Signed-off-by")
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python 3.x
|
||||
uses: actions/setup-python@v6
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: '3.x'
|
||||
- name: Check DCO
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
|
||||
36
.github/workflows/docker-image.yaml
vendored
36
.github/workflows/docker-image.yaml
vendored
@@ -1,52 +1,46 @@
|
||||
name: Cloud Hypervisor's Docker image update
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: main
|
||||
paths: resources/Dockerfile
|
||||
pull_request:
|
||||
paths: resources/Dockerfile
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ github.event_name }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
|
||||
jobs:
|
||||
main:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
uses: docker/setup-qemu-action@v1
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
uses: docker/setup-buildx-action@v1
|
||||
|
||||
- name: Login to ghcr
|
||||
uses: docker/login-action@v3
|
||||
- name: Login to DockerHub
|
||||
if: ${{ github.event_name == 'push' }}
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
uses: docker/metadata-action@v3
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
# list of Docker images to use as base name for tags
|
||||
images: cloudhypervisor/dev
|
||||
# generate Docker tags based on the following events/attributes
|
||||
tags: |
|
||||
type=raw,value=20251114-0
|
||||
type=raw,value={{date 'YYYYMMDD'}}-0
|
||||
type=sha
|
||||
|
||||
- name: Build and push
|
||||
if: ${{ github.event_name == 'push' }}
|
||||
uses: docker/build-push-action@v6
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
file: ./resources/Dockerfile
|
||||
platforms: linux/amd64,linux/arm64
|
||||
@@ -55,7 +49,7 @@ jobs:
|
||||
|
||||
- name: Build only
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
uses: docker/build-push-action@v6
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
file: ./resources/Dockerfile
|
||||
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.event.pull_request.number || github.ref }}-${{ github.event_name }}
|
||||
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@v6
|
||||
- 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
|
||||
on: [pull_request, merge_group]
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ github.event_name }}
|
||||
cancel-in-progress: true
|
||||
on: [pull_request, create]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: github.event_name == 'pull_request'
|
||||
name: Cargo Fuzz Build
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
@@ -14,19 +12,18 @@ jobs:
|
||||
- nightly
|
||||
target:
|
||||
- x86_64-unknown-linux-gnu
|
||||
env:
|
||||
RUSTFLAGS: -D warnings
|
||||
steps:
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v2
|
||||
- name: Install Rust toolchain (${{ matrix.rust }})
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ matrix.rust }}
|
||||
target: ${{ matrix.target }}
|
||||
override: true
|
||||
- name: Install Cargo fuzz
|
||||
run: cargo install cargo-fuzz
|
||||
- name: Fuzz Build
|
||||
# Temporary fix for cargo-fuzz on latest nightly: https://github.com/rust-fuzz/cargo-fuzz/issues/276
|
||||
#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
|
||||
- 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@v6
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
fetch-depth: 0
|
||||
- name: Set up Python 3.10
|
||||
uses: actions/setup-python@v6
|
||||
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@v6
|
||||
|
||||
- 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.event.pull_request.number || github.ref }}-${{ github.event_name }}
|
||||
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@v6
|
||||
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@v6
|
||||
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.event.pull_request.number || github.ref }}-${{ github.event_name }}
|
||||
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@v6
|
||||
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.event.pull_request.number || github.ref }}-${{ github.event_name }}
|
||||
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@v6
|
||||
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.event.pull_request.number || github.ref }}-${{ github.event_name }}
|
||||
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@v6
|
||||
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.event.pull_request.number || github.ref }}-${{ github.event_name }}
|
||||
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@v6
|
||||
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"
|
||||
45
.github/workflows/lychee.yaml
vendored
45
.github/workflows/lychee.yaml
vendored
@@ -1,45 +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@v6
|
||||
with:
|
||||
# Fetch the entire history so git diff can compare against the base branch
|
||||
fetch-depth: 0
|
||||
- name: Get changed files in PR
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@v47 # Using a dedicated action for robustness
|
||||
with:
|
||||
# Compare the HEAD of the PR with the merge-base (where the PR branches off)
|
||||
base_sha: ${{ github.event.pull_request.base.sha }}
|
||||
|
||||
# NEW STEP: Print all changed-files outputs for verification
|
||||
- name: Verify Changed Files
|
||||
run: |
|
||||
echo "--- tj-actions/changed-files Outputs ---"
|
||||
echo "any_changed: ${{ steps.changed-files.outputs.any_changed }}"
|
||||
echo "all_changed_files: ${{ steps.changed-files.outputs.all_changed_files }}"
|
||||
echo "added_files: ${{ steps.changed-files.outputs.added_files }}"
|
||||
echo "modified_files: ${{ steps.changed-files.outputs.modified_files }}"
|
||||
echo "deleted_files: ${{ steps.changed-files.outputs.deleted_files }}"
|
||||
echo "renamed_files: ${{ steps.changed-files.outputs.renamed_files }}"
|
||||
echo "----------------------------------------"
|
||||
# This will also show if the all_changed_files string is empty or not
|
||||
if [ -n "${{ steps.changed-files.outputs.all_changed_files }}" ]; then
|
||||
echo "Detected changes: all_changed_files output is NOT empty."
|
||||
else
|
||||
echo "No changes detected: all_changed_files output IS empty."
|
||||
fi
|
||||
- name: Link Availability Check (Diff Only)
|
||||
# MODIFIED: Only run lychee if the 'all_changed_files' output is not an empty string
|
||||
if: ${{ steps.changed-files.outputs.all_changed_files != '' }}
|
||||
uses: lycheeverse/lychee-action@master
|
||||
with:
|
||||
# Pass the space-separated list of changed files to lychee
|
||||
args: --verbose --config .lychee.toml ${{ steps.changed-files.outputs.all_changed_files }}
|
||||
failIfEmpty: false
|
||||
fail: true
|
||||
248
.github/workflows/mshv-infra.yaml
vendored
248
.github/workflows/mshv-infra.yaml
vendored
@@ -1,248 +0,0 @@
|
||||
name: MSHV Infra Setup
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
ARCH:
|
||||
description: 'Architecture for the VM'
|
||||
required: true
|
||||
type: string
|
||||
KEY:
|
||||
description: 'SSH Key Name'
|
||||
required: true
|
||||
type: string
|
||||
OS_DISK_SIZE:
|
||||
description: 'OS Disk Size in GB'
|
||||
required: true
|
||||
type: string
|
||||
RG:
|
||||
description: 'Resource Group Name'
|
||||
required: true
|
||||
type: string
|
||||
VM_SKU:
|
||||
description: 'VM SKU'
|
||||
required: true
|
||||
type: string
|
||||
secrets:
|
||||
MI_CLIENT_ID:
|
||||
required: true
|
||||
RUNNER_RG:
|
||||
required: true
|
||||
STORAGE_ACCOUNT_PATHS:
|
||||
required: true
|
||||
ARCH_SOURCE_PATH:
|
||||
required: true
|
||||
USERNAME:
|
||||
required: true
|
||||
outputs:
|
||||
RG_NAME:
|
||||
description: 'Resource group of the VM'
|
||||
value: ${{ jobs.infra-setup.outputs.RG_NAME }}
|
||||
VM_NAME:
|
||||
description: 'Name of the VM'
|
||||
value: ${{ jobs.infra-setup.outputs.VM_NAME }}
|
||||
PRIVATE_IP:
|
||||
description: 'Private IP of the VM'
|
||||
value: ${{ jobs.infra-setup.outputs.PRIVATE_IP }}
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ github.event_name }}
|
||||
cancel-in-progress: true
|
||||
jobs:
|
||||
infra-setup:
|
||||
name: ${{ inputs.ARCH }} VM Provision
|
||||
runs-on: mshv
|
||||
outputs:
|
||||
RG_NAME: ${{ steps.rg-setup.outputs.RG_NAME }}
|
||||
VM_NAME: ${{ steps.vm-setup.outputs.VM_NAME }}
|
||||
PRIVATE_IP: ${{ steps.get-vm-ip.outputs.PRIVATE_IP }}
|
||||
steps:
|
||||
- name: Install & login to AZ CLI
|
||||
env:
|
||||
MI_CLIENT_ID: ${{ secrets.MI_CLIENT_ID }}
|
||||
run: |
|
||||
set -e
|
||||
echo "Installing Azure CLI if not already installed"
|
||||
if ! command -v az &>/dev/null; then
|
||||
curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash
|
||||
else
|
||||
echo "Azure CLI already installed"
|
||||
fi
|
||||
az --version
|
||||
echo "Logging into Azure CLI using Managed Identity"
|
||||
az login --identity --client-id ${MI_CLIENT_ID}
|
||||
|
||||
- name: Get Location
|
||||
id: get-location
|
||||
env:
|
||||
SKU: ${{ inputs.VM_SKU }}
|
||||
STORAGE_ACCOUNT_PATHS: ${{ secrets.STORAGE_ACCOUNT_PATHS }}
|
||||
run: |
|
||||
set -e
|
||||
# Extract vCPU count from SKU (e.g., "Standard_D2s_v3" => 2)
|
||||
vcpu=$(echo "$SKU" | sed -n 's/^Standard_[A-Za-z]\+\([0-9]\+\).*/\1/p')
|
||||
if [[ -z "$vcpu" ]]; then
|
||||
echo "Cannot extract vCPU count from SKU: $SKU"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SUPPORTED_LOCATIONS=$(echo "$STORAGE_ACCOUNT_PATHS" | jq -r 'to_entries[] | .key')
|
||||
|
||||
for location in $SUPPORTED_LOCATIONS; do
|
||||
family=$(az vm list-skus --size "$SKU" --location "$location" --resource-type "virtualMachines" --query '[0].family' -o tsv)
|
||||
if [[ -z "$family" ]]; then
|
||||
echo "Cannot determine VM family for SKU: $SKU in $location"
|
||||
continue
|
||||
fi
|
||||
|
||||
usage=$(az vm list-usage --location "$location" --query "[?name.value=='$family'] | [0]" -o json)
|
||||
current=$(echo "$usage" | jq -r '.currentValue')
|
||||
limit=$(echo "$usage" | jq -r '.limit')
|
||||
|
||||
if [[ $((limit - current)) -ge $vcpu ]]; then
|
||||
echo "Sufficient quota found in $location"
|
||||
echo "location=$location" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
done
|
||||
|
||||
echo "No location found with sufficient vCPU quota for SKU: $SKU"
|
||||
exit 1
|
||||
|
||||
- name: Create Resource Group
|
||||
id: rg-setup
|
||||
env:
|
||||
LOCATION: ${{ steps.get-location.outputs.location }}
|
||||
RG: ${{ inputs.RG }}
|
||||
STORAGE_ACCOUNT_PATHS: ${{ secrets.STORAGE_ACCOUNT_PATHS }}
|
||||
run: |
|
||||
set -e
|
||||
echo "Creating Resource Group: $RG"
|
||||
# Create the resource group
|
||||
echo "Creating resource group in location: ${LOCATION}"
|
||||
az group create --name ${RG} --location ${LOCATION}
|
||||
echo "RG_NAME=${RG}" >> $GITHUB_OUTPUT
|
||||
echo "Resource group created successfully."
|
||||
|
||||
- name: Generate SSH Key
|
||||
id: generate-ssh-key
|
||||
env:
|
||||
KEY: ${{ inputs.KEY }}
|
||||
run: |
|
||||
set -e
|
||||
echo "Generating SSH key: $KEY"
|
||||
mkdir -p ~/.ssh
|
||||
ssh-keygen -t rsa -b 4096 -f ~/.ssh/${KEY} -N ""
|
||||
|
||||
- name: Create VM
|
||||
id: vm-setup
|
||||
env:
|
||||
KEY: ${{ inputs.KEY }}
|
||||
LOCATION: ${{ steps.get-location.outputs.location }}
|
||||
OS_DISK_SIZE: ${{ inputs.OS_DISK_SIZE }}
|
||||
RG: ${{ inputs.RG }}
|
||||
RUNNER_RG: ${{ secrets.RUNNER_RG }}
|
||||
USERNAME: ${{ secrets.USERNAME }}
|
||||
VM_SKU: ${{ inputs.VM_SKU }}
|
||||
VM_IMAGE_NAME: ${{ inputs.ARCH }}_${{ steps.get-location.outputs.location }}_image
|
||||
VM_NAME: ${{ inputs.ARCH }}_${{ steps.get-location.outputs.location }}_${{ github.run_id }}
|
||||
run: |
|
||||
set -e
|
||||
echo "Creating $VM_SKU VM: $VM_NAME"
|
||||
|
||||
# Extract subnet ID from the runner VM
|
||||
echo "Retrieving subnet ID..."
|
||||
SUBNET_ID=$(az network vnet list --resource-group ${RUNNER_RG} --query "[?contains(location, '${LOCATION}')].{SUBNETS:subnets}" | jq -r ".[0].SUBNETS[0].id")
|
||||
if [[ -z "${SUBNET_ID}" ]]; then
|
||||
echo "ERROR: Failed to retrieve Subnet ID."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Extract image ID from the runner VM
|
||||
echo "Retrieving image ID..."
|
||||
IMAGE_ID=$(az image show --resource-group ${RUNNER_RG} --name ${VM_IMAGE_NAME} --query "id" -o tsv)
|
||||
if [[ -z "${IMAGE_ID}" ]]; then
|
||||
echo "ERROR: Failed to retrieve Image ID."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Create VM
|
||||
az vm create \
|
||||
--resource-group ${RG} \
|
||||
--name ${VM_NAME} \
|
||||
--subnet ${SUBNET_ID} \
|
||||
--size ${VM_SKU} \
|
||||
--location ${LOCATION} \
|
||||
--image ${IMAGE_ID} \
|
||||
--os-disk-size-gb ${OS_DISK_SIZE} \
|
||||
--public-ip-sku Standard \
|
||||
--storage-sku Premium_LRS \
|
||||
--public-ip-address "" \
|
||||
--admin-username ${USERNAME} \
|
||||
--ssh-key-value ~/.ssh/${KEY}.pub \
|
||||
--security-type Standard \
|
||||
--output json
|
||||
|
||||
az vm boot-diagnostics enable --name ${VM_NAME} --resource-group ${RG}
|
||||
|
||||
echo "VM_NAME=${VM_NAME}" >> $GITHUB_OUTPUT
|
||||
echo "VM creation process completed successfully."
|
||||
|
||||
- name: Get VM Private IP
|
||||
id: get-vm-ip
|
||||
env:
|
||||
RG: ${{ inputs.RG }}
|
||||
VM_NAME: ${{ inputs.ARCH }}_${{ steps.get-location.outputs.location }}_${{ github.run_id }}
|
||||
run: |
|
||||
set -e
|
||||
echo "Retrieving VM Private IP address..."
|
||||
# Retrieve VM Private IP address
|
||||
PRIVATE_IP=$(az vm show -g ${RG} -n ${VM_NAME} -d --query privateIps -o tsv)
|
||||
if [[ -z "$PRIVATE_IP" ]]; then
|
||||
echo "ERROR: Failed to retrieve private IP address."
|
||||
exit 1
|
||||
fi
|
||||
echo "PRIVATE_IP=$PRIVATE_IP" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Wait for SSH availability
|
||||
env:
|
||||
KEY: ${{ inputs.KEY }}
|
||||
PRIVATE_IP: ${{ steps.get-vm-ip.outputs.PRIVATE_IP }}
|
||||
USERNAME: ${{ secrets.USERNAME }}
|
||||
run: |
|
||||
echo "Waiting for SSH to be accessible..."
|
||||
timeout 120 bash -c 'until ssh -o StrictHostKeyChecking=no -i ~/.ssh/${KEY} ${USERNAME}@${PRIVATE_IP} "exit" 2>/dev/null; do sleep 5; done'
|
||||
echo "VM is accessible!"
|
||||
|
||||
- name: Remove Old Host Key
|
||||
env:
|
||||
PRIVATE_IP: ${{ steps.get-vm-ip.outputs.PRIVATE_IP }}
|
||||
run: |
|
||||
set -e
|
||||
echo "Removing the old host key"
|
||||
ssh-keygen -R $PRIVATE_IP
|
||||
|
||||
- name: SSH into VM and Install Dependencies
|
||||
env:
|
||||
KEY: ${{ inputs.KEY }}
|
||||
PRIVATE_IP: ${{ steps.get-vm-ip.outputs.PRIVATE_IP }}
|
||||
USERNAME: ${{ secrets.USERNAME }}
|
||||
run: |
|
||||
set -e
|
||||
ssh -i ~/.ssh/${KEY} -o StrictHostKeyChecking=no ${USERNAME}@${PRIVATE_IP} << EOF
|
||||
set -e
|
||||
echo "Logged in successfully."
|
||||
echo "Installing dependencies..."
|
||||
sudo tdnf install -y git moby-engine moby-cli clang llvm pkg-config make gcc glibc-devel
|
||||
echo "Installing Rust..."
|
||||
curl -sSf https://sh.rustup.rs | sh -s -- --default-toolchain stable --profile default -y
|
||||
export PATH="\$HOME/.cargo/bin:\$PATH"
|
||||
cargo --version
|
||||
sudo mkdir -p /etc/docker/
|
||||
echo '{"default-ulimits":{"nofile":{"Hard":65535,"Name":"nofile","Soft":65535}}}' | sudo tee /etc/docker/daemon.json
|
||||
sudo systemctl stop docker
|
||||
sudo systemctl enable docker.service
|
||||
sudo systemctl enable containerd.service
|
||||
sudo systemctl start docker
|
||||
sudo groupadd -f docker
|
||||
sudo usermod -a -G docker ${USERNAME}
|
||||
sudo systemctl restart docker
|
||||
EOF
|
||||
130
.github/workflows/mshv-integration.yaml
vendored
130
.github/workflows/mshv-integration.yaml
vendored
@@ -1,130 +0,0 @@
|
||||
name: Cloud Hypervisor Tests (MSHV) (x86_64)
|
||||
on: [pull_request_target, merge_group]
|
||||
|
||||
jobs:
|
||||
infra-setup:
|
||||
name: MSHV Infra Setup (x86_64)
|
||||
uses: ./.github/workflows/mshv-infra.yaml
|
||||
with:
|
||||
ARCH: x86_64
|
||||
KEY: azure_key_${{ github.run_id }}
|
||||
OS_DISK_SIZE: 512
|
||||
RG: MSHV-INTEGRATION-${{ github.run_id }}
|
||||
VM_SKU: Standard_D16s_v5
|
||||
secrets:
|
||||
MI_CLIENT_ID: ${{ secrets.MSHV_MI_CLIENT_ID }}
|
||||
RUNNER_RG: ${{ secrets.MSHV_RUNNER_RG }}
|
||||
STORAGE_ACCOUNT_PATHS: ${{ secrets.MSHV_STORAGE_ACCOUNT_PATHS }}
|
||||
ARCH_SOURCE_PATH: ${{ secrets.MSHV_X86_SOURCE_PATH }}
|
||||
USERNAME: ${{ secrets.MSHV_USERNAME }}
|
||||
|
||||
run-tests:
|
||||
name: Integration Tests (x86_64)
|
||||
needs: infra-setup
|
||||
if: ${{ always() && needs.infra-setup.result == 'success' }}
|
||||
runs-on: mshv
|
||||
steps:
|
||||
- name: Run integration tests
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
KEY: azure_key_${{ github.run_id }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
REPO_URL: https://github.com/cloud-hypervisor/cloud-hypervisor.git
|
||||
REPO_DIR: cloud-hypervisor
|
||||
PRIVATE_IP: ${{ needs.infra-setup.outputs.PRIVATE_IP }}
|
||||
RG: MSHV-${{ github.run_id }}
|
||||
USERNAME: ${{ secrets.MSHV_USERNAME }}
|
||||
run: |
|
||||
set -e
|
||||
echo "Connecting to the VM via SSH..."
|
||||
ssh -i ~/.ssh/${KEY} -o StrictHostKeyChecking=no ${USERNAME}@${PRIVATE_IP} << EOF
|
||||
set -e
|
||||
echo "Logged in successfully."
|
||||
export PATH="\$HOME/.cargo/bin:\$PATH"
|
||||
|
||||
if [[ "${{ github.event_name }}" == "pull_request_target" ]]; then
|
||||
git clone --depth 1 "$REPO_URL" "$REPO_DIR"
|
||||
cd "$REPO_DIR"
|
||||
git fetch origin pull/${{ github.event.pull_request.number }}/merge
|
||||
git checkout FETCH_HEAD
|
||||
else
|
||||
git clone --depth 1 --single-branch --branch "${{ github.ref_name }}" "$REPO_URL" "$REPO_DIR"
|
||||
cd "$REPO_DIR"
|
||||
fi
|
||||
|
||||
echo "Loading VDPA kernel modules..."
|
||||
sudo modprobe vdpa
|
||||
sudo modprobe vhost_vdpa
|
||||
sudo modprobe vdpa_sim
|
||||
sudo modprobe vdpa_sim_blk
|
||||
sudo modprobe vdpa_sim_net
|
||||
|
||||
echo "Creating VDPA devices..."
|
||||
sudo vdpa dev add name vdpa-blk0 mgmtdev vdpasim_blk
|
||||
sudo vdpa dev add name vdpa-blk1 mgmtdev vdpasim_blk
|
||||
sudo vdpa dev add name vdpa-blk2 mgmtdev vdpasim_net
|
||||
|
||||
echo "Setting permissions..."
|
||||
for i in 0 1 2; do
|
||||
dev="/dev/vhost-vdpa-$i"
|
||||
if [ -e "$dev" ]; then
|
||||
sudo chown $USER:$USER "$dev"
|
||||
sudo chmod 660 "$dev"
|
||||
else
|
||||
echo "Warning: Device $dev not found"
|
||||
fi
|
||||
done
|
||||
|
||||
sudo ./scripts/dev_cli.sh tests --hypervisor mshv --integration
|
||||
EOF
|
||||
|
||||
- name: Dump dmesg
|
||||
if: always()
|
||||
continue-on-error: true
|
||||
env:
|
||||
KEY: azure_key_${{ github.run_id }}
|
||||
PRIVATE_IP: ${{ needs.infra-setup.outputs.PRIVATE_IP }}
|
||||
USERNAME: ${{ secrets.MSHV_USERNAME }}
|
||||
run: |
|
||||
ssh -i ~/.ssh/${KEY} -o StrictHostKeyChecking=no ${USERNAME}@${PRIVATE_IP} << EOF
|
||||
sudo dmesg
|
||||
EOF
|
||||
|
||||
- name: Dump serial console logs
|
||||
if: always()
|
||||
continue-on-error: true
|
||||
env:
|
||||
RG_NAME: ${{ needs.infra-setup.outputs.RG_NAME }}
|
||||
VM_NAME: ${{ needs.infra-setup.outputs.VM_NAME }}
|
||||
run: |
|
||||
set -e
|
||||
az vm boot-diagnostics get-boot-log --name "${VM_NAME}" --resource-group "${RG_NAME}" | jq -r
|
||||
|
||||
cleanup:
|
||||
name: Cleanup
|
||||
needs: run-tests
|
||||
if: always()
|
||||
runs-on: mshv
|
||||
steps:
|
||||
- name: Delete RG
|
||||
env:
|
||||
RG: MSHV-INTEGRATION-${{ github.run_id }}
|
||||
run: |
|
||||
if az group exists --name ${RG}; then
|
||||
az group delete --name ${RG} --yes --no-wait
|
||||
else
|
||||
echo "Resource Group ${RG} does not exist. Skipping deletion."
|
||||
fi
|
||||
echo "Cleanup process completed."
|
||||
|
||||
- name: Delete SSH Key
|
||||
env:
|
||||
KEY: azure_key_${{ github.run_id }}
|
||||
run: |
|
||||
if [ -f ~/.ssh/${KEY} ]; then
|
||||
rm -f ~/.ssh/${KEY} ~/.ssh/${KEY}.pub
|
||||
echo "SSH key deleted successfully."
|
||||
else
|
||||
echo "SSH key does not exist. Skipping deletion."
|
||||
fi
|
||||
echo "Cleanup process completed."
|
||||
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@v6
|
||||
- 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.event.pull_request.number || github.ref }}-${{ github.event_name }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Rust VMM Consistency Check
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v6
|
||||
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
|
||||
30
.github/workflows/preview-riscv64-build.yaml
vendored
30
.github/workflows/preview-riscv64-build.yaml
vendored
@@ -1,30 +0,0 @@
|
||||
name: Cloud Hypervisor RISC-V 64-bit kvm build Preview
|
||||
on: [pull_request, merge_group]
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ github.event_name }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Cargo
|
||||
runs-on: riscv64-qemu-host
|
||||
strategy:
|
||||
fail-fast: false
|
||||
|
||||
steps:
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install Rust toolchain
|
||||
run: /opt/scripts/exec-in-qemu.sh rustup default 1.89.0
|
||||
|
||||
- name: Build test (kvm)
|
||||
run: /opt/scripts/exec-in-qemu.sh cargo build --locked --no-default-features --features "kvm" -p cloud-hypervisor
|
||||
|
||||
- name: Clippy test (kvm)
|
||||
run: /opt/scripts/exec-in-qemu.sh cargo clippy --locked --no-default-features --features "kvm" -p cloud-hypervisor
|
||||
|
||||
- name: Check no files were modified
|
||||
run: test -z "$(git status --porcelain)"
|
||||
39
.github/workflows/preview-riscv64-modules.yaml
vendored
39
.github/workflows/preview-riscv64-modules.yaml
vendored
@@ -1,39 +0,0 @@
|
||||
name: Cloud Hypervisor RISC-V 64-bit Preview
|
||||
on: [pull_request, merge_group]
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ github.event_name }}
|
||||
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@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install Rust toolchain
|
||||
run: /opt/scripts/exec-in-qemu.sh rustup default 1.89.0
|
||||
|
||||
- name: Build ${{ matrix.module }} Module (kvm)
|
||||
run: /opt/scripts/exec-in-qemu.sh cargo build --locked -p ${{ matrix.module }} --no-default-features --features "kvm"
|
||||
|
||||
- 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
|
||||
|
||||
- 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)"
|
||||
159
.github/workflows/quality.yaml
vendored
159
.github/workflows/quality.yaml
vendored
@@ -1,19 +1,16 @@
|
||||
name: Cloud Hypervisor Quality Checks
|
||||
on: [pull_request, merge_group]
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ github.event_name }}
|
||||
cancel-in-progress: true
|
||||
on: [pull_request, create]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Quality (clippy)
|
||||
if: github.event_name == 'pull_request'
|
||||
name: Quality (clippy, rustfmt)
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: ${{ matrix.experimental }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
rust:
|
||||
- beta
|
||||
- stable
|
||||
target:
|
||||
- aarch64-unknown-linux-gnu
|
||||
@@ -21,15 +18,23 @@ jobs:
|
||||
- x86_64-unknown-linux-gnu
|
||||
- x86_64-unknown-linux-musl
|
||||
|
||||
experimental: [false]
|
||||
include:
|
||||
- rust: beta
|
||||
target: aarch64-unknown-linux-gnu
|
||||
experimental: true
|
||||
- rust: beta
|
||||
target: aarch64-unknown-linux-musl
|
||||
experimental: true
|
||||
- rust: beta
|
||||
target: x86_64-unknown-linux-gnu
|
||||
experimental: true
|
||||
- rust: beta
|
||||
target: x86_64-unknown-linux-musl
|
||||
experimental: true
|
||||
- rust: stable
|
||||
experimental: false
|
||||
|
||||
steps:
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -39,132 +44,70 @@ jobs:
|
||||
toolchain: ${{ matrix.rust }}
|
||||
target: ${{ matrix.target }}
|
||||
override: true
|
||||
components: clippy
|
||||
components: rustfmt, clippy
|
||||
|
||||
- name: Bisectability Check (default features)
|
||||
if: ${{ github.event_name == 'pull_request' && matrix.target == 'x86_64-unknown-linux-gnu' }}
|
||||
- name: Debug Check (default features)
|
||||
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
|
||||
run: |
|
||||
set -e
|
||||
commits=$(git rev-list origin/${{ github.base_ref }}..${{ github.sha }})
|
||||
for commit in $commits; do git checkout $commit; cargo check --tests --examples --all --target=${{ matrix.target }}; done
|
||||
git checkout ${{ github.sha }}
|
||||
|
||||
- name: Formatting (rustfmt)
|
||||
run: cargo fmt -- --check
|
||||
|
||||
- name: Clippy (kvm)
|
||||
uses: houseabsolute/actions-rust-cross@v1
|
||||
with:
|
||||
command: clippy
|
||||
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
|
||||
toolchain: ${{ matrix.rust }}
|
||||
target: ${{ matrix.target }}
|
||||
args: --locked --all --all-targets --no-default-features --tests --examples --features "kvm" -- -D warnings
|
||||
|
||||
- name: Clippy (mshv)
|
||||
uses: houseabsolute/actions-rust-cross@v1
|
||||
with:
|
||||
command: clippy
|
||||
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
|
||||
toolchain: ${{ matrix.rust }}
|
||||
target: ${{ matrix.target }}
|
||||
args: --locked --all --all-targets --no-default-features --tests --examples --features "mshv" -- -D warnings
|
||||
|
||||
- name: Clippy (mshv + kvm)
|
||||
uses: houseabsolute/actions-rust-cross@v1
|
||||
with:
|
||||
command: clippy
|
||||
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
|
||||
toolchain: ${{ matrix.rust }}
|
||||
target: ${{ matrix.target }}
|
||||
args: --locked --all --all-targets --no-default-features --tests --examples --features "mshv,kvm" -- -D warnings
|
||||
|
||||
- name: Clippy (default features)
|
||||
uses: houseabsolute/actions-rust-cross@v1
|
||||
with:
|
||||
command: clippy
|
||||
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
|
||||
toolchain: ${{ matrix.rust }}
|
||||
target: ${{ matrix.target }}
|
||||
args: --locked --all --all-targets --tests --examples -- -D warnings
|
||||
|
||||
- name: Clippy (default features + guest_debug)
|
||||
uses: houseabsolute/actions-rust-cross@v1
|
||||
with:
|
||||
command: clippy
|
||||
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
|
||||
toolchain: ${{ matrix.rust }}
|
||||
target: ${{ matrix.target }}
|
||||
args: --locked --all --all-targets --tests --examples --features "guest_debug" -- -D warnings
|
||||
|
||||
- name: Clippy (default features + pvmemcontrol)
|
||||
uses: houseabsolute/actions-rust-cross@v1
|
||||
with:
|
||||
command: clippy
|
||||
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
|
||||
toolchain: ${{ matrix.rust }}
|
||||
target: ${{ matrix.target }}
|
||||
args: --locked --all --all-targets --tests --examples --features "pvmemcontrol" -- -D warnings
|
||||
|
||||
- name: Clippy (default features + tracing)
|
||||
uses: houseabsolute/actions-rust-cross@v1
|
||||
with:
|
||||
command: clippy
|
||||
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
|
||||
toolchain: ${{ matrix.rust }}
|
||||
target: ${{ matrix.target }}
|
||||
args: --locked --all --all-targets --tests --examples --features "tracing" -- -D warnings
|
||||
- name: Clippy (default features + fw_cfg)
|
||||
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 "fw_cfg" -- -D warnings
|
||||
args: --target=${{ matrix.target }} --locked --all --all-targets --no-default-features --tests --examples --features "kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||
|
||||
- name: Clippy (default features + ivshmem)
|
||||
uses: houseabsolute/actions-rust-cross@v1
|
||||
- name: Clippy (default features)
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
|
||||
command: clippy
|
||||
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
|
||||
toolchain: ${{ matrix.rust }}
|
||||
target: ${{ matrix.target }}
|
||||
args: --locked --all --all-targets --tests --examples --features "ivshmem" -- -D warnings
|
||||
args: --target=${{ matrix.target }} --locked --all --all-targets --tests --examples -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||
|
||||
- name: Clippy (sev_snp)
|
||||
- name: Clippy (default features + guest_debug)
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
|
||||
command: clippy
|
||||
args: --target=${{ matrix.target }} --locked --all --all-targets --tests --examples --features "guest_debug" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||
|
||||
- name: Clippy (default features + tracing)
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
|
||||
command: clippy
|
||||
args: --target=${{ matrix.target }} --locked --all --all-targets --tests --examples --features "tracing" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||
|
||||
- name: Clippy (mshv)
|
||||
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
|
||||
uses: houseabsolute/actions-rust-cross@v1
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
|
||||
command: clippy
|
||||
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
|
||||
toolchain: ${{ matrix.rust }}
|
||||
target: ${{ matrix.target }}
|
||||
args: --locked --all --all-targets --no-default-features --tests --examples --features "sev_snp" -- -D warnings
|
||||
args: --target=${{ matrix.target }} --locked --all --all-targets --no-default-features --tests --examples --features "mshv" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||
|
||||
- name: Clippy (igvm)
|
||||
- name: Clippy (mshv + kvm)
|
||||
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
|
||||
uses: houseabsolute/actions-rust-cross@v1
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
|
||||
command: clippy
|
||||
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
|
||||
toolchain: ${{ matrix.rust }}
|
||||
target: ${{ matrix.target }}
|
||||
args: --locked --all --all-targets --no-default-features --tests --examples --features "igvm" -- -D warnings
|
||||
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 (kvm + tdx)
|
||||
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
|
||||
uses: houseabsolute/actions-rust-cross@v1
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
|
||||
command: clippy
|
||||
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
|
||||
toolchain: ${{ matrix.rust }}
|
||||
target: ${{ matrix.target }}
|
||||
args: --locked --all --all-targets --no-default-features --tests --examples --features "tdx,kvm" -- -D warnings
|
||||
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
|
||||
run: test -z "$(git status --porcelain)"
|
||||
|
||||
typos:
|
||||
if: github.event_name == 'pull_request'
|
||||
name: Typos / Spellcheck
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
# Executes "typos ."
|
||||
- uses: crate-ci/typos@v1.43.5
|
||||
|
||||
196
.github/workflows/release.yaml
vendored
196
.github/workflows/release.yaml
vendored
@@ -1,69 +1,134 @@
|
||||
name: Cloud Hypervisor Release
|
||||
on: [create, merge_group]
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ github.event_name }}
|
||||
cancel-in-progress: true
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
on: [pull_request, create]
|
||||
|
||||
jobs:
|
||||
release:
|
||||
if: (github.event_name == 'create' && github.event.ref_type == 'tag') || github.event_name == 'merge_group'
|
||||
name: Release ${{ matrix.platform.target }}
|
||||
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
|
||||
if: (github.event_name == 'create' && github.event.ref_type == 'tag') || github.event_name == 'pull_request'
|
||||
name: Release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v2
|
||||
- name: Install musl-gcc
|
||||
if: contains(matrix.platform.target, 'musl')
|
||||
run: sudo apt install -y musl-tools
|
||||
- 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 }}
|
||||
- name: Build ${{ matrix.platform.target }}
|
||||
uses: houseabsolute/actions-rust-cross@v1
|
||||
- name: Install Rust toolchain (x86_64-unknown-linux-gnu)
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: "1.62"
|
||||
target: x86_64-unknown-linux-gnu
|
||||
- name: Install Rust toolchain (x86_64-unknown-linux-musl)
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: "1.62"
|
||||
target: x86_64-unknown-linux-musl
|
||||
- name: Build
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
toolchain: "1.62"
|
||||
command: build
|
||||
target: ${{ matrix.platform.target }}
|
||||
args: ${{ matrix.platform.args }}
|
||||
strip: true
|
||||
toolchain: "1.89.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@v6
|
||||
args: --all --release --no-default-features --features "kvm,mshv" --target=x86_64-unknown-linux-gnu
|
||||
- name: Static Build
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
name: Artifacts for ${{ matrix.platform.target }}
|
||||
path: |
|
||||
./${{ matrix.platform.name_ch }}
|
||||
./${{ matrix.platform.name_ch_remote }}
|
||||
toolchain: "1.62"
|
||||
command: build
|
||||
args: --all --release --no-default-features --features "kvm,mshv" --target=x86_64-unknown-linux-musl
|
||||
- name: Install Rust toolchain (aarch64-unknown-linux-musl)
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: "1.62"
|
||||
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
|
||||
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 }}
|
||||
run: |
|
||||
mkdir ../vendor-cargo-home
|
||||
@@ -71,25 +136,16 @@ jobs:
|
||||
mkdir .cargo
|
||||
cargo vendor > .cargo/config.toml
|
||||
- name: Create vendored source archive
|
||||
if: |
|
||||
github.event_name == 'create' && github.event.ref_type == 'tag' &&
|
||||
matrix.platform.target == 'x86_64-unknown-linux-gnu'
|
||||
run: tar cJf cloud-hypervisor-${{ github.event.ref }}.tar.xz ../cloud-hypervisor-${{ github.event.ref }}
|
||||
working-directory: ../
|
||||
run: tar cJf cloud-hypervisor-${{ github.event.ref }}.tar.xz cloud-hypervisor-${{ github.event.ref }}
|
||||
- name: Upload cloud-hypervisor vendored source archive
|
||||
if: |
|
||||
github.event_name == 'create' && github.event.ref_type == 'tag' &&
|
||||
matrix.platform.target == 'x86_64-unknown-linux-gnu'
|
||||
id: upload-release-cloud-hypervisor-vendored-sources
|
||||
uses: actions/upload-artifact@v6
|
||||
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'
|
||||
uses: softprops/action-gh-release@v2
|
||||
id: upload-release-cloud-hypervisor-vendored-sources
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
draft: true
|
||||
files: |
|
||||
./${{ matrix.platform.name_ch }}
|
||||
./${{ matrix.platform.name_ch_remote }}
|
||||
./cloud-hypervisor-${{ github.event.ref }}.tar.xz
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ../cloud-hypervisor-${{ github.event.ref }}.tar.xz
|
||||
asset_name: cloud-hypervisor-${{ github.event.ref }}.tar.xz
|
||||
asset_content_type: application/x-xz
|
||||
|
||||
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@v6
|
||||
- name: REUSE Compliance Check
|
||||
uses: fsfe/reuse-action@v6
|
||||
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@v6
|
||||
- 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@v6
|
||||
- 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
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -6,5 +6,3 @@
|
||||
**/rusty-tags.vi
|
||||
/rpm/SOURCES
|
||||
/.vscode
|
||||
/vendor
|
||||
__pycache__
|
||||
|
||||
13
.gitlint
13
.gitlint
@@ -1,13 +0,0 @@
|
||||
[general]
|
||||
extra-path=scripts/gitlint/rules
|
||||
regex-style-search=true
|
||||
ignore=body-max-line-length,body-hard-tab
|
||||
|
||||
[ignore-by-author-name]
|
||||
regex=dependabot
|
||||
ignore=all
|
||||
|
||||
# default 72
|
||||
[title-max-length]
|
||||
line-length=72
|
||||
|
||||
36
.lychee.toml
36
.lychee.toml
@@ -1,36 +0,0 @@
|
||||
verbose = "info"
|
||||
|
||||
exclude_path = [".lychee.toml"]
|
||||
|
||||
exclude = [
|
||||
# Availability of links below should be manually verified.
|
||||
# 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',
|
||||
# Exclude all pages with $ in the URL since $XXX is a variable
|
||||
"\\$.*",
|
||||
# Exclude local files
|
||||
"file://.*",
|
||||
|
||||
# ARM documentation returns 403 Forbidden for automated CI checks.
|
||||
'^http://infocenter\.arm\.com',
|
||||
'^https://developer\.arm\.com',
|
||||
|
||||
# Ignore internal/unsupported protocols seen in logs
|
||||
'^tcp://192\.168\.1\.10',
|
||||
]
|
||||
|
||||
# Exclude loopback addresses
|
||||
exclude_loopback = true
|
||||
|
||||
|
||||
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 = "2024"
|
||||
group_imports="StdExternalCrate"
|
||||
imports_granularity="Module"
|
||||
|
||||
edition = "2021"
|
||||
@@ -1,5 +0,0 @@
|
||||
include = ["**/Cargo.toml"]
|
||||
|
||||
[formatting]
|
||||
reorder_arrays = true
|
||||
reorder_keys = true
|
||||
28
.typos.toml
28
.typos.toml
@@ -1,28 +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]
|
||||
CLASSE = "CLASSE"
|
||||
Dake = "Dake"
|
||||
EXTINT = "EXTINT"
|
||||
INOUT = "INOUT"
|
||||
MSIS = "MSIS" # MSIs (Message Signaled Interrupt)
|
||||
SME = "SME" # Secure Memory Encryption
|
||||
THR = "THR" # Transmitter Holding Register
|
||||
TRANSLATER = "TRANSLATER"
|
||||
ba = "ba"
|
||||
conectix = "conectix"
|
||||
liness = "liness"
|
||||
outout = "outout"
|
||||
|
||||
[default.extend-identifiers]
|
||||
consts = "consts"
|
||||
fo = "fo"
|
||||
fpr = "fpr"
|
||||
# Public Linux API
|
||||
msg_controllen = "msg_controllen"
|
||||
@@ -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
|
||||
contain details of their licensing and changes to that file are under the same
|
||||
license unless the contribution changes the license of the file. When importing
|
||||
code from a third party project (e.g. Firecracker or crosvm) please respect the
|
||||
code from a third party project (e.g. Firecracker or CrosVM) please respect the
|
||||
license of those projects.
|
||||
|
||||
New code should be under the [Apache v2
|
||||
@@ -13,34 +13,12 @@ License](https://opensource.org/licenses/Apache-2.0).
|
||||
|
||||
## Coding Style
|
||||
|
||||
We follow the [Rust Style](https://github.com/rust-lang/rust/tree/HEAD/src/doc/style-guide/src)
|
||||
convention and enforce it through the Continuous Integration (CI) process calling into `rustfmt`,
|
||||
`clippy`, and other well-known code quality tool of the ecosystem for each submitted Pull Request (PR).
|
||||
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`
|
||||
for each submitted Pull Request (PR).
|
||||
|
||||
## Basic Checks
|
||||
|
||||
```sh
|
||||
# We currently rely on nightly-only formatting features
|
||||
cargo +nightly fmt --all
|
||||
cargo check --all-targets --tests
|
||||
cargo clippy --all-targets --tests
|
||||
# Please note that this will not execute integration tests.
|
||||
cargo test --all-targets --tests
|
||||
|
||||
# To lint your last three commits
|
||||
gitlint --commits "HEAD~3..HEAD"
|
||||
```
|
||||
|
||||
### \[Optional\] Run Integration Tests
|
||||
|
||||
_Caution: These tests are taking a long time to complete (40+ mins) and need special setup._
|
||||
|
||||
```sh
|
||||
bash ./scripts/dev_cli.sh tests --integration -- --test-filter '<optionally filter test by name pattern>'
|
||||
```
|
||||
|
||||
### Setup Commit Hook
|
||||
|
||||
Please consider creating the following hook as `.git/hooks/pre-commit` in order
|
||||
to ensure basic correctness of your code. You can extend this further if you
|
||||
have specific features that you regularly develop against.
|
||||
@@ -48,9 +26,9 @@ have specific features that you regularly develop against.
|
||||
```sh
|
||||
#!/bin/sh
|
||||
|
||||
cargo +nightly fmt --all -- --check || exit 1
|
||||
cargo check --locked --all-targets --tests || exit 1
|
||||
cargo clippy --locked --all-targets --tests -- -D warnings || exit 1
|
||||
cargo fmt -- --check || exit 1
|
||||
cargo check --locked --all --all-targets --tests || exit 1
|
||||
cargo clippy --locked --all --all-targets --tests -- -D warnings || exit 1
|
||||
```
|
||||
|
||||
You will need to `chmod +x .git/hooks/pre-commit` to have it run on every
|
||||
@@ -58,7 +36,7 @@ commit you make.
|
||||
|
||||
## 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.
|
||||
|
||||
## Patch format
|
||||
@@ -101,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
|
||||
into your github organization.
|
||||
1. 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/)
|
||||
2. Within your fork, create a branch for your contribution.
|
||||
3. [Create a pull request](https://help.github.com/articles/creating-a-pull-request-from-a-fork/)
|
||||
against the main branch of the Cloud Hypervisor repository.
|
||||
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.
|
||||
1. Once the pull request is approved it can be integrated.
|
||||
5. Once the pull request is approved it can be integrated.
|
||||
|
||||
## Issue tracking
|
||||
|
||||
@@ -134,15 +112,5 @@ Fixes #88
|
||||
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/).
|
||||
|
||||
## AI Generated Code
|
||||
|
||||
Our policy is to decline any contributions known to contain contents
|
||||
generated or derived from using Large Language Models (LLMs). This
|
||||
includes ChatGPT, Gemini, Claude, Copilot and similar tools.
|
||||
|
||||
The goal is to avoid ambiguity in license compliance and optimize the
|
||||
use of limited project resources, especially for code review and
|
||||
maintenance. This policy can be revisited as LLMs evolve and mature.
|
||||
|
||||
2350
Cargo.lock
generated
2350
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
197
Cargo.toml
197
Cargo.toml
@@ -1,121 +1,102 @@
|
||||
# Cloud Hypervisor Workspace
|
||||
#
|
||||
# The main crate producing the binaries is in `./cloud-hypervisor`.
|
||||
[package]
|
||||
name = "cloud-hypervisor"
|
||||
version = "30.1.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:
|
||||
# Keep in sync with version in .github/workflows/build.yaml
|
||||
# Policy on MSRV (see #4318):
|
||||
# Can only be bumped by:
|
||||
# a.) A dependency requires it,
|
||||
# b.) If we want to use a new feature and that MSRV is at least 6 months old,
|
||||
# c.) There is a security issue that is addressed by the toolchain update.
|
||||
rust-version = "1.62"
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
opt-level = "s"
|
||||
strip = true
|
||||
|
||||
[profile.profiling]
|
||||
debug = true
|
||||
inherits = "release"
|
||||
strip = false
|
||||
debug = true
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.69"
|
||||
api_client = { path = "api_client" }
|
||||
argh = "0.1.9"
|
||||
dhat = { version = "0.3.2", optional = true }
|
||||
epoll = "4.3.1"
|
||||
event_monitor = { path = "event_monitor" }
|
||||
hypervisor = { path = "hypervisor" }
|
||||
libc = "0.2.139"
|
||||
log = { version = "0.4.17", features = ["std"] }
|
||||
option_parser = { path = "option_parser" }
|
||||
seccompiler = "0.3.0"
|
||||
serde_json = "1.0.93"
|
||||
signal-hook = "0.3.14"
|
||||
thiserror = "1.0.38"
|
||||
tpm = { path = "tpm"}
|
||||
tracer = { path = "tracer" }
|
||||
vmm = { path = "vmm" }
|
||||
vmm-sys-util = "0.11.0"
|
||||
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]
|
||||
dirs = "4.0.0"
|
||||
net_util = { path = "net_util" }
|
||||
once_cell = "1.17.1"
|
||||
serde_json = "1.0.93"
|
||||
test_infra = { path = "test_infra" }
|
||||
wait-timeout = "0.2.0"
|
||||
|
||||
[features]
|
||||
default = ["kvm"]
|
||||
dhat-heap = ["dhat"] # For heap profiling
|
||||
guest_debug = ["vmm/guest_debug"]
|
||||
kvm = ["vmm/kvm"]
|
||||
mshv = ["vmm/mshv"]
|
||||
tdx = ["vmm/tdx"]
|
||||
tracing = ["vmm/tracing", "tracer/tracing"]
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
"api_client",
|
||||
"arch",
|
||||
"block",
|
||||
"cloud-hypervisor",
|
||||
"devices",
|
||||
"event_monitor",
|
||||
"hypervisor",
|
||||
"net_gen",
|
||||
"net_util",
|
||||
"option_parser",
|
||||
"pci",
|
||||
"performance-metrics",
|
||||
"rate_limiter",
|
||||
"serial_buffer",
|
||||
"test_infra",
|
||||
"tracer",
|
||||
"vhost_user_block",
|
||||
"vhost_user_net",
|
||||
"virtio-devices",
|
||||
"vm-allocator",
|
||||
"vm-device",
|
||||
"vm-migration",
|
||||
"vm-virtio",
|
||||
"vmm",
|
||||
"api_client",
|
||||
"arch",
|
||||
"block_util",
|
||||
"devices",
|
||||
"event_monitor",
|
||||
"hypervisor",
|
||||
"net_gen",
|
||||
"net_util",
|
||||
"option_parser",
|
||||
"pci",
|
||||
"performance-metrics",
|
||||
"qcow",
|
||||
"rate_limiter",
|
||||
"serial_buffer",
|
||||
"test_infra",
|
||||
"tracer",
|
||||
"vhdx",
|
||||
"vhost_user_block",
|
||||
"vhost_user_net",
|
||||
"virtio-devices",
|
||||
"vmm",
|
||||
"vm-allocator",
|
||||
"vm-device",
|
||||
"vm-migration",
|
||||
"vm-virtio"
|
||||
]
|
||||
package.edition = "2024"
|
||||
resolver = "3"
|
||||
|
||||
[workspace.dependencies]
|
||||
# rust-vmm crates
|
||||
acpi_tables = "0.2.0"
|
||||
kvm-bindings = "0.12.1"
|
||||
kvm-ioctls = "0.22.1"
|
||||
linux-loader = "0.13.1"
|
||||
mshv-bindings = "0.6.7"
|
||||
mshv-ioctls = "0.6.7"
|
||||
seccompiler = "0.5.0"
|
||||
vfio-bindings = { version = "0.6.0", default-features = false }
|
||||
vfio-ioctls = { version = "0.5.1", default-features = false }
|
||||
vfio_user = { version = "0.1.1", default-features = false }
|
||||
vhost = { version = "0.14.0", default-features = false }
|
||||
vhost-user-backend = { version = "0.20.0", default-features = false }
|
||||
virtio-bindings = "0.2.6"
|
||||
virtio-queue = "0.16.0"
|
||||
vm-fdt = "0.3.0"
|
||||
vm-memory = "0.16.1"
|
||||
vmm-sys-util = "0.14.0"
|
||||
|
||||
# igvm crates
|
||||
igvm = "0.4.0"
|
||||
igvm_defs = "0.4.0"
|
||||
|
||||
# serde crates
|
||||
serde = "1.0.228"
|
||||
serde_json = "1.0.149"
|
||||
serde_with = { version = "3.16.1", default-features = false }
|
||||
|
||||
# other crates
|
||||
anyhow = "1.0.101"
|
||||
bitflags = "2.11.0"
|
||||
byteorder = "1.5.0"
|
||||
cfg-if = "1.0.4"
|
||||
clap = "4.5.59"
|
||||
dhat = "0.3.3"
|
||||
dirs = "6.0.0"
|
||||
env_logger = "0.11.8"
|
||||
epoll = "4.4.0"
|
||||
flume = "0.12.0"
|
||||
itertools = "0.14.0"
|
||||
libc = "0.2.182"
|
||||
log = "0.4.29"
|
||||
signal-hook = "0.4.3"
|
||||
thiserror = "2.0.18"
|
||||
uuid = { version = "1.21.0" }
|
||||
wait-timeout = "0.2.1"
|
||||
zerocopy = { version = "0.8.39", default-features = false }
|
||||
|
||||
[workspace.lints.clippy]
|
||||
# Any clippy lint (group) in alphabetical order:
|
||||
# https://rust-lang.github.io/rust-clippy/master/index.html
|
||||
|
||||
# Groups
|
||||
all = "deny" # shorthand for the other groups but here for compleness
|
||||
complexity = "deny"
|
||||
correctness = "deny"
|
||||
perf = "deny"
|
||||
style = "deny"
|
||||
suspicious = "deny"
|
||||
|
||||
# Individual Lints
|
||||
assertions_on_result_states = "deny"
|
||||
if_not_else = "deny"
|
||||
manual_string_new = "deny"
|
||||
map_unwrap_or = "deny"
|
||||
needless_pass_by_value = "deny"
|
||||
redundant_else = "deny"
|
||||
semicolon_if_nothing_returned = "deny"
|
||||
undocumented_unsafe_blocks = "deny"
|
||||
uninlined_format_args = "deny"
|
||||
unnecessary_semicolon = "deny"
|
||||
|
||||
[workspace.lints.rust]
|
||||
# `level = warn` is irrelevant here but mandatory for rustc/cargo
|
||||
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(devcli_testenv)'] }
|
||||
|
||||
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
|
||||
}
|
||||
69
README.md
69
README.md
@@ -59,13 +59,9 @@ based on the [Rust VMM](https://github.com/rust-vmm) crates.
|
||||
|
||||
### Architectures
|
||||
|
||||
Cloud Hypervisor supports the `x86-64`, `AArch64` and `riscv64`
|
||||
architectures, with functionality varying across these platforms. The
|
||||
functionality differences between `x86-64` and `AArch64` are documented
|
||||
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).
|
||||
Cloud Hypervisor supports the `x86-64` and `AArch64` architectures. There are
|
||||
minor differences in functionality between the two architectures
|
||||
(see [#1125](https://github.com/cloud-hypervisor/cloud-hypervisor/issues/1125)).
|
||||
|
||||
### Guest OS
|
||||
|
||||
@@ -82,9 +78,9 @@ The following sections describe how to build and run Cloud Hypervisor.
|
||||
|
||||
## Host OS
|
||||
|
||||
For required KVM functionality and adequate performance the recommended host
|
||||
kernel version is 5.13. The majority of the CI currently tests with kernel
|
||||
version 5.15.
|
||||
For required KVM functionality the minimum host kernel version is 4.11. For
|
||||
adequate performance the minimum recommended host kernel version is 5.6. The
|
||||
majority of the CI currently tests with kernel version 5.15.
|
||||
|
||||
## Use Pre-built Binaries
|
||||
|
||||
@@ -112,12 +108,12 @@ do not wish to use the pre-built binaries.
|
||||
## Booting Linux
|
||||
|
||||
Cloud Hypervisor supports direct kernel boot (the x86-64 kernel requires the kernel
|
||||
built with PVH support or 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
|
||||
edk2 UEFI firmware called `CLOUDHV` / `CLOUDHV_EFI`.)
|
||||
|
||||
Binary builds of the firmware files are available for the latest release of
|
||||
[Rust Hypervisor
|
||||
[Rust Hyperivor
|
||||
Firmware](https://github.com/cloud-hypervisor/rust-hypervisor-firmware/releases/latest)
|
||||
and [our edk2
|
||||
repository](https://github.com/cloud-hypervisor/edk2/releases/latest)
|
||||
@@ -128,7 +124,7 @@ may be required.
|
||||
### Firmware Booting
|
||||
|
||||
Cloud Hypervisor supports booting disk images containing all needed components
|
||||
to run cloud workloads, a.k.a. cloud images.
|
||||
to run cloud workloads, a.k.a. cloud images.
|
||||
|
||||
The following sample commands will download an Ubuntu Cloud image, converting
|
||||
it into a format that Cloud Hypervisor can use and a firmware to boot the image
|
||||
@@ -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
|
||||
`cloud-init` image is generated by this [script](scripts/create-cloud-init.sh).
|
||||
This seeds the image with a default username/password of `cloud/cloud123`. It
|
||||
is only necessary to add this disk image on the first boot. Script also assigns
|
||||
default IP address using `test_data/cloud-init/ubuntu/local/network-config` details
|
||||
with `--net "mac=12:34:56:78:90:ab,tap="` option. Then the matching mac address
|
||||
interface will be enabled as per `network-config` details.
|
||||
is only necessary to add this disk image on the first boot.
|
||||
|
||||
```shell
|
||||
$ sudo setcap cap_net_admin+ep ./cloud-hypervisor
|
||||
$ ./create-cloud-init.sh
|
||||
$ ./cloud-hypervisor \
|
||||
--firmware ./hypervisor-fw \
|
||||
--disk path=focal-server-cloudimg-amd64.raw path=/tmp/ubuntu-cloudinit.img \
|
||||
--kernel ./hypervisor-fw \
|
||||
--disk path=focal-server-cloudimg-amd64.raw --disk path=/tmp/ubuntu-cloudinit.img \
|
||||
--cpus boot=4 \
|
||||
--memory size=1024M \
|
||||
--net "tap=,mac=,ip=,mask="
|
||||
@@ -167,7 +160,7 @@ GRUB) is required then it necessary to switch to the serial console instead of
|
||||
```shell
|
||||
$ ./cloud-hypervisor \
|
||||
--kernel ./hypervisor-fw \
|
||||
--disk path=focal-server-cloudimg-amd64.raw path=/tmp/ubuntu-cloudinit.img \
|
||||
--disk path=focal-server-cloudimg-amd64.raw --disk path=/tmp/ubuntu-cloudinit.img \
|
||||
--cpus boot=4 \
|
||||
--memory size=1024M \
|
||||
--net "tap=,mac=,ip=,mask=" \
|
||||
@@ -175,31 +168,24 @@ $ ./cloud-hypervisor \
|
||||
--console off
|
||||
```
|
||||
|
||||
## Booting: `--firmware` vs `--kernel`
|
||||
|
||||
The following scenarios are supported by Cloud Hypervisor to bootstrap a VM, i.e.,
|
||||
to load a payload/bootitem(s):
|
||||
|
||||
- Provide firmware
|
||||
- Provide kernel \[+ cmdline\]\ [+ initrd\]
|
||||
|
||||
Please note that our Cloud Hypervisor firmware (`hypervisor-fw`) has a Xen PVH
|
||||
boot entry, therefore it can also be booted via the `--kernel` parameter, as
|
||||
seen in some examples.
|
||||
|
||||
### Custom Kernel and Disk Image
|
||||
|
||||
#### 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:
|
||||
|
||||
```shell
|
||||
# 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.1.6 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
|
||||
$ KCFLAGS="-Wa,-mx86-used-note=no" make bzImage -j `nproc`
|
||||
# Do native build of the AArch64 kernel
|
||||
@@ -236,7 +222,7 @@ $ sudo setcap cap_net_admin+ep ./cloud-hypervisor
|
||||
$ ./create-cloud-init.sh
|
||||
$ ./cloud-hypervisor \
|
||||
--kernel ./linux-cloud-hypervisor/arch/x86/boot/compressed/vmlinux.bin \
|
||||
--disk path=focal-server-cloudimg-amd64.raw 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" \
|
||||
--cpus boot=4 \
|
||||
--memory size=1024M \
|
||||
@@ -250,7 +236,7 @@ $ sudo setcap cap_net_admin+ep ./cloud-hypervisor
|
||||
$ ./create-cloud-init.sh
|
||||
$ ./cloud-hypervisor \
|
||||
--kernel ./linux-cloud-hypervisor/arch/arm64/boot/Image \
|
||||
--disk path=focal-server-cloudimg-arm64.raw 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" \
|
||||
--cpus boot=4 \
|
||||
--memory size=1024M \
|
||||
@@ -313,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:
|
||||
|
||||
- [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 Noble](https://cloud-images.ubuntu.com/noble/current/) (noble-server-cloudimg-{amd64,arm64}.img)
|
||||
- [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/))
|
||||
- [Ubuntu Jammy](https://cloud-images.ubuntu.com/jammy/current/) (jammy-server-cloudimg-{amd64,arm64}.img )
|
||||
- [Fedora 36](https://fedora.mirrorservice.org/fedora/linux/releases/36/Cloud/) ([Fedora-Cloud-Base-36-1.5.x86_64.raw.xz](https://fedora.mirrorservice.org/fedora/linux/releases/36/Cloud/x86_64/images/) / [Fedora-Cloud-Base-36-1.5.aarch64.raw.xz](https://fedora.mirrorservice.org/fedora/linux/releases/36/Cloud/aarch64/images/))
|
||||
|
||||
Direct kernel boot to userspace should work with a rootfs from most
|
||||
distributions although you may need to enable exotic filesystem types in the
|
||||
@@ -387,8 +372,8 @@ are all equal and welcome means of contribution. See the
|
||||
|
||||
## Slack
|
||||
|
||||
Get an [invite to our Slack channel](https://join.slack.com/t/cloud-hypervisor/shared_invite/enQtNjY3MTE3MDkwNDQ4LWQ1MTA1ZDVmODkwMWQ1MTRhYzk4ZGNlN2UwNTI3ZmFlODU0OTcwOWZjMTkwZDExYWE3YjFmNzgzY2FmNDAyMjI),
|
||||
[join us on Slack](https://cloud-hypervisor.slack.com/), and [participate in our community activities](https://cloud-hypervisor.slack.com/archives/C04R5DUQVBN).
|
||||
Get an [invite to our Slack channel](https://join.slack.com/t/cloud-hypervisor/shared_invite/enQtNjY3MTE3MDkwNDQ4LWQ1MTA1ZDVmODkwMWQ1MTRhYzk4ZGNlN2UwNTI3ZmFlODU0OTcwOWZjMTkwZDExYWE3YjFmNzgzY2FmNDAyMjI)
|
||||
and [join us on Slack](https://cloud-hypervisor.slack.com/).
|
||||
|
||||
## Mailing list
|
||||
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
[package]
|
||||
authors = ["The Cloud Hypervisor Authors"]
|
||||
edition.workspace = true
|
||||
license = "Apache-2.0"
|
||||
name = "api_client"
|
||||
version = "0.1.0"
|
||||
authors = ["The Cloud Hypervisor Authors"]
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
thiserror = { workspace = true }
|
||||
vmm-sys-util = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
vmm-sys-util = "0.11.0"
|
||||
|
||||
@@ -3,32 +3,39 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
use std::fmt;
|
||||
use std::io::{Read, Write};
|
||||
use std::os::unix::io::RawFd;
|
||||
|
||||
use thiserror::Error;
|
||||
use vmm_sys_util::sock_ctrl_msg::ScmSocket;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
#[error("Error writing to or reading from HTTP socket")]
|
||||
Socket(#[source] std::io::Error),
|
||||
#[error("Error sending file descriptors")]
|
||||
SocketSendFds(#[source] vmm_sys_util::errno::Error),
|
||||
#[error("Error parsing HTTP status code")]
|
||||
StatusCodeParsing(#[source] std::num::ParseIntError),
|
||||
#[error("HTTP output is missing protocol statement")]
|
||||
Socket(std::io::Error),
|
||||
SocketSendFds(vmm_sys_util::errno::Error),
|
||||
StatusCodeParsing(std::num::ParseIntError),
|
||||
MissingProtocol,
|
||||
#[error("Error parsing HTTP Content-Length field")]
|
||||
ContentLengthParsing(#[source] std::num::ParseIntError),
|
||||
#[error("Server responded with error {0:?}: {1:?}")]
|
||||
ServerResponse(
|
||||
StatusCode,
|
||||
// TODO: Move `api` module from `vmm` to dedicated crate and use a common type definition
|
||||
Option<
|
||||
String, /* Untyped: Currently Vec<String> of error messages from top to root cause */
|
||||
>,
|
||||
),
|
||||
ContentLengthParsing(std::num::ParseIntError),
|
||||
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)]
|
||||
@@ -38,7 +45,6 @@ pub enum StatusCode {
|
||||
NoContent,
|
||||
BadRequest,
|
||||
NotFound,
|
||||
TooManyRequests,
|
||||
InternalServerError,
|
||||
NotImplemented,
|
||||
Unknown,
|
||||
@@ -52,7 +58,6 @@ impl StatusCode {
|
||||
204 => StatusCode::NoContent,
|
||||
400 => StatusCode::BadRequest,
|
||||
404 => StatusCode::NotFound,
|
||||
429 => StatusCode::TooManyRequests,
|
||||
500 => StatusCode::InternalServerError,
|
||||
501 => StatusCode::NotImplemented,
|
||||
_ => StatusCode::Unknown,
|
||||
@@ -118,11 +123,12 @@ fn parse_http_response(socket: &mut dyn Read) -> Result<Option<String>, Error> {
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(body_offset) = body_offset
|
||||
&& let Some(content_length) = content_length
|
||||
&& res.len() >= content_length + body_offset
|
||||
{
|
||||
break;
|
||||
if let Some(body_offset) = body_offset {
|
||||
if let Some(content_length) = content_length {
|
||||
if res.len() >= content_length + body_offset {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let body_string = content_length.and(body_offset.map(|o| String::from(&res[o..])));
|
||||
@@ -142,7 +148,7 @@ pub fn simple_api_full_command_with_fds_and_response<T: Read + Write + ScmSocket
|
||||
method: &str,
|
||||
full_command: &str,
|
||||
request_body: Option<&str>,
|
||||
request_fds: &[RawFd],
|
||||
request_fds: Vec<RawFd>,
|
||||
) -> Result<Option<String>, Error> {
|
||||
socket
|
||||
.send_with_fds(
|
||||
@@ -150,7 +156,7 @@ pub fn simple_api_full_command_with_fds_and_response<T: Read + Write + ScmSocket
|
||||
"{method} /api/v1/{full_command} HTTP/1.1\r\nHost: localhost\r\nAccept: */*\r\n"
|
||||
)
|
||||
.as_bytes()],
|
||||
request_fds,
|
||||
&request_fds,
|
||||
)
|
||||
.map_err(Error::SocketSendFds)?;
|
||||
|
||||
@@ -178,7 +184,7 @@ pub fn simple_api_full_command_with_fds<T: Read + Write + ScmSocket>(
|
||||
method: &str,
|
||||
full_command: &str,
|
||||
request_body: Option<&str>,
|
||||
request_fds: &[RawFd],
|
||||
request_fds: Vec<RawFd>,
|
||||
) -> Result<(), Error> {
|
||||
let response = simple_api_full_command_with_fds_and_response(
|
||||
socket,
|
||||
@@ -188,8 +194,8 @@ pub fn simple_api_full_command_with_fds<T: Read + Write + ScmSocket>(
|
||||
request_fds,
|
||||
)?;
|
||||
|
||||
if let Some(response) = response {
|
||||
println!("{response}");
|
||||
if response.is_some() {
|
||||
println!("{}", response.unwrap());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -201,7 +207,7 @@ pub fn simple_api_full_command<T: Read + Write + ScmSocket>(
|
||||
full_command: &str,
|
||||
request_body: Option<&str>,
|
||||
) -> Result<(), Error> {
|
||||
simple_api_full_command_with_fds(socket, method, full_command, request_body, &[])
|
||||
simple_api_full_command_with_fds(socket, method, full_command, request_body, Vec::new())
|
||||
}
|
||||
|
||||
pub fn simple_api_full_command_and_response<T: Read + Write + ScmSocket>(
|
||||
@@ -210,7 +216,13 @@ pub fn simple_api_full_command_and_response<T: Read + Write + ScmSocket>(
|
||||
full_command: &str,
|
||||
request_body: Option<&str>,
|
||||
) -> Result<Option<String>, Error> {
|
||||
simple_api_full_command_with_fds_and_response(socket, method, full_command, request_body, &[])
|
||||
simple_api_full_command_with_fds_and_response(
|
||||
socket,
|
||||
method,
|
||||
full_command,
|
||||
request_body,
|
||||
Vec::new(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn simple_api_command_with_fds<T: Read + Write + ScmSocket>(
|
||||
@@ -218,7 +230,7 @@ pub fn simple_api_command_with_fds<T: Read + Write + ScmSocket>(
|
||||
method: &str,
|
||||
c: &str,
|
||||
request_body: Option<&str>,
|
||||
request_fds: &[RawFd],
|
||||
request_fds: Vec<RawFd>,
|
||||
) -> Result<(), Error> {
|
||||
// Create the full VM command. For VMM commands, use
|
||||
// simple_api_full_command().
|
||||
@@ -233,5 +245,5 @@ pub fn simple_api_command<T: Read + Write + ScmSocket>(
|
||||
c: &str,
|
||||
request_body: Option<&str>,
|
||||
) -> Result<(), Error> {
|
||||
simple_api_command_with_fds(socket, method, c, request_body, &[])
|
||||
simple_api_command_with_fds(socket, method, c, request_body, Vec::new())
|
||||
}
|
||||
|
||||
@@ -1,32 +1,29 @@
|
||||
[package]
|
||||
authors = ["The Chromium OS Authors"]
|
||||
edition.workspace = true
|
||||
name = "arch"
|
||||
version = "0.1.0"
|
||||
authors = ["The Chromium OS Authors"]
|
||||
edition = "2021"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
fw_cfg = []
|
||||
kvm = ["hypervisor/kvm"]
|
||||
sev_snp = []
|
||||
tdx = []
|
||||
|
||||
[dependencies]
|
||||
anyhow = { workspace = true }
|
||||
byteorder = { workspace = true }
|
||||
anyhow = "1.0.69"
|
||||
byteorder = "1.4.3"
|
||||
hypervisor = { path = "../hypervisor" }
|
||||
libc = { workspace = true }
|
||||
linux-loader = { workspace = true, features = ["bzimage", "elf", "pe"] }
|
||||
log = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive", "rc"] }
|
||||
thiserror = { workspace = true }
|
||||
uuid = { workspace = true }
|
||||
vm-memory = { workspace = true, features = ["backend-bitmap", "backend-mmap"] }
|
||||
vmm-sys-util = { workspace = true, features = ["with-serde"] }
|
||||
libc = "0.2.139"
|
||||
linux-loader = { version = "0.8.1", features = ["elf", "bzimage", "pe"] }
|
||||
log = "0.4.17"
|
||||
serde = { version = "1.0.151", features = ["rc", "derive"] }
|
||||
thiserror = "1.0.38"
|
||||
uuid = "1.3.0"
|
||||
versionize = "0.1.9"
|
||||
versionize_derive = "0.1.4"
|
||||
vm-memory = { version = "0.10.0", features = ["backend-mmap", "backend-bitmap"] }
|
||||
vm-migration = { path = "../vm-migration" }
|
||||
vmm-sys-util = { version = "0.11.0", features = ["with-serde"] }
|
||||
|
||||
[target.'cfg(any(target_arch = "aarch64", target_arch = "riscv64"))'.dependencies]
|
||||
fdt_parser = { version = "0.1.5", package = "fdt" }
|
||||
vm-fdt = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
[target.'cfg(target_arch = "aarch64")'.dependencies]
|
||||
fdt_parser = { version = "0.1.4", package = "fdt" }
|
||||
vm-fdt = { git = "https://github.com/rust-vmm/vm-fdt", branch = "main" }
|
||||
|
||||
@@ -6,32 +6,27 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// 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::ffi::CStr;
|
||||
use std::fmt::Debug;
|
||||
use std::path::Path;
|
||||
use std::result;
|
||||
use std::str;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::{cmp, fs, result, str};
|
||||
|
||||
use byteorder::{BigEndian, ByteOrder};
|
||||
use hypervisor::arch::aarch64::gic::Vgic;
|
||||
use hypervisor::arch::aarch64::regs::{
|
||||
AARCH64_ARCH_TIMER_HYP_IRQ, AARCH64_ARCH_TIMER_PHYS_NONSECURE_IRQ,
|
||||
AARCH64_ARCH_TIMER_PHYS_SECURE_IRQ, AARCH64_ARCH_TIMER_VIRT_IRQ, AARCH64_PMU_IRQ,
|
||||
use super::super::DeviceType;
|
||||
use super::super::GuestMemoryMmap;
|
||||
use super::super::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 log::{debug, info, warn};
|
||||
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::{
|
||||
GIC_V2M_COMPATIBLE, GICV2M_SPI_BASE, GICV2M_SPI_NUM, 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::{NumaNodes, PciSpaceInfo};
|
||||
|
||||
// This is a value for uniquely identifying the FDT node declaring the interrupt controller.
|
||||
const GIC_PHANDLE: u32 = 1;
|
||||
// This is a value for uniquely identifying the FDT node declaring the MSI controller.
|
||||
@@ -45,12 +40,8 @@ const VIRTIO_IOMMU_PHANDLE: u32 = 5;
|
||||
// NOTE: Keep FIRST_VCPU_PHANDLE the last PHANDLE defined.
|
||||
// This is a value for uniquely identifying the FDT node containing the first vCPU.
|
||||
// The last number of vCPU phandle depends on the number of vCPUs.
|
||||
const FIRST_VCPU_PHANDLE: u32 = 8;
|
||||
const FIRST_VCPU_PHANDLE: u32 = 6;
|
||||
|
||||
// This is a value for uniquely identifying the FDT node containing the L2 cache info
|
||||
const L2_CACHE_PHANDLE: u32 = 6;
|
||||
// This is a value for uniquely identifying the FDT node containing the L3 cache info
|
||||
const L3_CACHE_PHANDLE: u32 = 7;
|
||||
// Read the documentation specified when appending the root node to the FDT.
|
||||
const ADDRESS_CELLS: u32 = 0x2;
|
||||
const SIZE_CELLS: u32 = 0x2;
|
||||
@@ -65,6 +56,9 @@ const GIC_FDT_IRQ_TYPE_PPI: u32 = 1;
|
||||
const IRQ_TYPE_EDGE_RISING: u32 = 1;
|
||||
const IRQ_TYPE_LEVEL_HI: u32 = 4;
|
||||
|
||||
// PMU PPI interrupt number
|
||||
pub const AARCH64_PMU_IRQ: u32 = 7;
|
||||
|
||||
// Keys and Buttons
|
||||
// System Power Down
|
||||
const KEY_POWER: u32 = 116;
|
||||
@@ -80,136 +74,20 @@ pub trait DeviceInfoForFdt {
|
||||
}
|
||||
|
||||
/// Errors thrown while configuring the Flattened Device Tree for aarch64.
|
||||
#[derive(Debug, Error)]
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Failure in writing FDT in memory.
|
||||
#[error("Failure in writing FDT in memory")]
|
||||
WriteFdtToMemory(#[source] GuestMemoryError),
|
||||
WriteFdtToMemory(GuestMemoryError),
|
||||
}
|
||||
type Result<T> = result::Result<T, Error>;
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
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() {
|
||||
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,
|
||||
}
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
/// 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() {
|
||||
let src = fs::read_to_string(file_directory).expect("File not exists or file corrupted.");
|
||||
src.trim().parse::<u32>().unwrap()
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
/// 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() {
|
||||
let src = fs::read_to_string(file_directory).expect("File not exists or file corrupted.");
|
||||
src.trim().parse::<u32>().unwrap()
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
/// 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() {
|
||||
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(',');
|
||||
}
|
||||
} else {
|
||||
result = false;
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// Creates the flattened device tree for this aarch64 VM.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn create_fdt<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHasher>(
|
||||
guest_mem: &GuestMemoryMmap,
|
||||
cmdline: &str,
|
||||
vcpu_mpidr: &[u64],
|
||||
vcpu_topology: Option<(u16, u16, u16, u16)>,
|
||||
vcpu_mpidr: Vec<u64>,
|
||||
vcpu_topology: Option<(u8, u8, u8)>,
|
||||
device_info: &HashMap<(DeviceType, String), T, S>,
|
||||
gic_device: &Arc<Mutex<dyn Vgic>>,
|
||||
initrd: &Option<InitramfsConfig>,
|
||||
@@ -222,8 +100,8 @@ pub fn create_fdt<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHash
|
||||
let mut fdt = FdtWriter::new().unwrap();
|
||||
|
||||
// For an explanation why these nodes were introduced in the blob take a look at
|
||||
// the "Device Node Requirements" chapter of the Devicetree Specification.
|
||||
// https://www.devicetree.org/specifications/
|
||||
// https://github.com/torvalds/linux/blob/master/Documentation/devicetree/booting-without-of.txt#L845
|
||||
// Look for "Required nodes and properties".
|
||||
|
||||
// Header or the root node as per above mentioned documentation.
|
||||
let root_node = fdt.begin_node("")?;
|
||||
@@ -235,7 +113,7 @@ pub fn create_fdt<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHash
|
||||
// This is not mandatory but we use it to point the root node to the node
|
||||
// containing description of the interrupt controller for this VM.
|
||||
fdt.property_u32("interrupt-parent", GIC_PHANDLE)?;
|
||||
create_cpu_nodes(&mut fdt, vcpu_mpidr, vcpu_topology, numa_nodes)?;
|
||||
create_cpu_nodes(&mut fdt, &vcpu_mpidr, vcpu_topology, numa_nodes)?;
|
||||
create_memory_node(&mut fdt, guest_mem, numa_nodes)?;
|
||||
create_chosen_node(&mut fdt, cmdline, initrd)?;
|
||||
create_gic_node(&mut fdt, gic_device)?;
|
||||
@@ -259,10 +137,10 @@ pub fn create_fdt<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHash
|
||||
Ok(fdt_final)
|
||||
}
|
||||
|
||||
pub fn write_fdt_to_memory(fdt_final: &[u8], guest_mem: &GuestMemoryMmap) -> Result<()> {
|
||||
pub fn write_fdt_to_memory(fdt_final: Vec<u8>, guest_mem: &GuestMemoryMmap) -> Result<()> {
|
||||
// Write FDT to memory.
|
||||
guest_mem
|
||||
.write_slice(fdt_final, super::layout::FDT_START)
|
||||
.write_slice(fdt_final.as_slice(), super::layout::FDT_START)
|
||||
.map_err(Error::WriteFdtToMemory)?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -271,7 +149,7 @@ pub fn write_fdt_to_memory(fdt_final: &[u8], guest_mem: &GuestMemoryMmap) -> Res
|
||||
fn create_cpu_nodes(
|
||||
fdt: &mut FdtWriter,
|
||||
vcpu_mpidr: &[u64],
|
||||
vcpu_topology: Option<(u16, u16, u16, u16)>,
|
||||
vcpu_topology: Option<(u8, u8, u8)>,
|
||||
numa_nodes: &NumaNodes,
|
||||
) -> FdtWriterResult<()> {
|
||||
// See https://github.com/torvalds/linux/blob/master/Documentation/devicetree/bindings/arm/cpus.yaml.
|
||||
@@ -280,81 +158,6 @@ fn create_cpu_nodes(
|
||||
fdt.property_u32("#size-cells", 0x0)?;
|
||||
|
||||
let num_cpus = vcpu_mpidr.len();
|
||||
let (threads_per_core, cores_per_die, dies_per_package, packages) =
|
||||
vcpu_topology.unwrap_or((1, 1, 1, 1));
|
||||
let cores_per_package = cores_per_die * dies_per_package;
|
||||
let max_cpus: u32 =
|
||||
threads_per_core as u32 * cores_per_die as u32 * dies_per_package as u32 * packages as u32;
|
||||
|
||||
// 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 {
|
||||
// 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);
|
||||
}
|
||||
} else {
|
||||
warn!("cache sysfs system does not exist.");
|
||||
}
|
||||
|
||||
// Arm boot protocol requires a minimal Device Tree
|
||||
// https://docs.kernel.org/arch/arm64/booting.html
|
||||
// As Generic initiators are supported only in ACPI
|
||||
// When a guest kernel does not boot under "acpi=force" mode it can
|
||||
// hang due to conflicting numa information present in FDT which
|
||||
// does not support Generic Initiators
|
||||
let has_generic_initiator = numa_nodes.values().any(|node| node.device_id.is_some());
|
||||
if has_generic_initiator {
|
||||
info!("Skipping NUMA CPU node encoding in FDT with Generic Initiator devices");
|
||||
}
|
||||
|
||||
for (cpu_id, mpidr) in vcpu_mpidr.iter().enumerate().take(num_cpus) {
|
||||
let cpu_name = format!("cpu@{cpu_id:x}");
|
||||
@@ -370,103 +173,21 @@ fn create_cpu_nodes(
|
||||
fdt.property_u32("reg", (mpidr & 0x7FFFFF) as u32)?;
|
||||
fdt.property_u32("phandle", cpu_id as u32 + FIRST_VCPU_PHANDLE)?;
|
||||
|
||||
// Skipping NUMA encoding in FDT when Generic Initiator devices
|
||||
// are present allowed such guest kernels to boot properly and
|
||||
// rely solely on ACPI tables to setup NUMA
|
||||
if numa_nodes.len() > 1 && !has_generic_initiator {
|
||||
// Add `numa-node-id` property if there is any numa config.
|
||||
if numa_nodes.len() > 1 {
|
||||
for numa_node_idx in 0..numa_nodes.len() {
|
||||
let numa_node = numa_nodes.get(&(numa_node_idx as u32));
|
||||
if numa_node.unwrap().cpus.contains(&(cpu_id as u32)) {
|
||||
if numa_node.unwrap().cpus.contains(&(cpu_id as u8)) {
|
||||
fdt.property_u32("numa-node-id", numa_node_idx as u32)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)?;
|
||||
}
|
||||
}
|
||||
|
||||
fdt.end_node(cpu_node)?;
|
||||
}
|
||||
|
||||
if cache_exist && l3_cache_size != 0 && !l2_cache_shared && l3_cache_shared {
|
||||
let mut i: u32 = 0;
|
||||
while i < packages.into() {
|
||||
let l3_cache_name = "l3-cache0";
|
||||
let l3_cache_node = fdt.begin_node(l3_cache_name)?;
|
||||
// ARM L3 cache is generally shared within the package (socket), so the
|
||||
// L3 cache node pointed to by the CPU in the package has the same L3
|
||||
// cache PHANDLE. The L3 cache phandle must start from the largest L2
|
||||
// cache PHANDLE plus 1 to avoid duplication.
|
||||
fdt.property_u32(
|
||||
"phandle",
|
||||
i + num_cpus as u32
|
||||
+ max_cpus
|
||||
+ FIRST_VCPU_PHANDLE
|
||||
+ L2_CACHE_PHANDLE
|
||||
+ L3_CACHE_PHANDLE,
|
||||
)?;
|
||||
|
||||
fdt.property_string("compatible", "cache")?;
|
||||
fdt.property_null("cache-unified")?;
|
||||
fdt.property_u32("cache-size", l3_cache_size)?;
|
||||
fdt.property_u32("cache-line-size", l3_cache_line_size)?;
|
||||
fdt.property_u32("cache-sets", l3_cache_sets)?;
|
||||
fdt.property_u32("cache-level", 3)?;
|
||||
fdt.end_node(l3_cache_node)?;
|
||||
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(topology) = vcpu_topology {
|
||||
let (threads_per_core, cores_per_die, dies_per_package, packages) = topology;
|
||||
let cores_per_package = cores_per_die * dies_per_package;
|
||||
let (threads_per_core, cores_per_package, packages) = topology;
|
||||
let cpu_map_node = fdt.begin_node("cpu-map")?;
|
||||
|
||||
// Create device tree nodes with regard of above mapping.
|
||||
@@ -514,14 +235,7 @@ fn create_memory_node(
|
||||
) -> FdtWriterResult<()> {
|
||||
// See https://github.com/torvalds/linux/blob/58ae0b51506802713aa0e9956d1853ba4c722c98/Documentation/devicetree/bindings/numa.txt
|
||||
// for NUMA setting in memory node.
|
||||
let has_generic_initiator = numa_nodes.values().any(|node| node.device_id.is_some());
|
||||
if has_generic_initiator {
|
||||
info!("Skipping NUMA memory node encoding in FDT with Generic Initiator devices");
|
||||
}
|
||||
// Skipping NUMA encoding in FDT when Generic Initiator devices
|
||||
// are present allowed guest kernels to boot and
|
||||
// rely solely on ACPI tables to setup NUMA
|
||||
if numa_nodes.len() > 1 && !has_generic_initiator {
|
||||
if numa_nodes.len() > 1 {
|
||||
for numa_node_idx in 0..numa_nodes.len() {
|
||||
let numa_node = numa_nodes.get(&(numa_node_idx as u32));
|
||||
let mut mem_reg_prop: Vec<u64> = Vec::new();
|
||||
@@ -533,100 +247,45 @@ fn create_memory_node(
|
||||
let memory_region_size: u64 = memory_region.size() as u64;
|
||||
mem_reg_prop.push(memory_region_start_addr);
|
||||
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 {
|
||||
node_memory_addr = memory_region_start_addr;
|
||||
}
|
||||
}
|
||||
// Only create a memory node if this NUMA node has memory regions
|
||||
if !mem_reg_prop.is_empty() {
|
||||
let memory_node_name = format!("memory@{node_memory_addr:x}");
|
||||
let memory_node = fdt.begin_node(&memory_node_name)?;
|
||||
fdt.property_string("device_type", "memory")?;
|
||||
fdt.property_array_u64("reg", &mem_reg_prop)?;
|
||||
fdt.property_u32("numa-node-id", numa_node_idx as u32)?;
|
||||
fdt.end_node(memory_node)?;
|
||||
}
|
||||
let memory_node_name = format!("memory@{node_memory_addr:x}");
|
||||
let memory_node = fdt.begin_node(&memory_node_name)?;
|
||||
fdt.property_string("device_type", "memory")?;
|
||||
fdt.property_array_u64("reg", &mem_reg_prop)?;
|
||||
fdt.property_u32("numa-node-id", numa_node_idx as u32)?;
|
||||
fdt.end_node(memory_node)?;
|
||||
}
|
||||
} else {
|
||||
// Note: memory regions from "GuestMemory" are sorted and non-zero sized.
|
||||
let ram_regions = {
|
||||
let mut ram_regions = Vec::new();
|
||||
let mut current_start = guest_mem
|
||||
.iter()
|
||||
.next()
|
||||
.map(GuestMemoryRegion::start_addr)
|
||||
.expect("GuestMemory must have one memory region at least")
|
||||
.raw_value();
|
||||
let mut current_end = current_start;
|
||||
|
||||
for (start, size) in guest_mem
|
||||
.iter()
|
||||
.map(|m| (m.start_addr().raw_value(), m.len()))
|
||||
{
|
||||
if current_end == start {
|
||||
// This zone is continuous with the previous one.
|
||||
current_end += size;
|
||||
} else {
|
||||
ram_regions.push((current_start, current_end));
|
||||
|
||||
current_start = start;
|
||||
current_end = start + size;
|
||||
}
|
||||
}
|
||||
|
||||
ram_regions.push((current_start, current_end));
|
||||
|
||||
ram_regions
|
||||
};
|
||||
|
||||
if ram_regions.len() > 2 {
|
||||
panic!(
|
||||
"There should be up to two non-continuous regions, divided by the
|
||||
gap at the end of 32bit address space."
|
||||
);
|
||||
}
|
||||
|
||||
// Create the memory node for memory region before the gap
|
||||
{
|
||||
let (first_region_start, first_region_end) = ram_regions
|
||||
.first()
|
||||
.expect("There should be at last one memory region");
|
||||
let ram_start = super::layout::RAM_START.raw_value();
|
||||
let mem_32bit_reserved_start = super::layout::MEM_32BIT_RESERVED_START.raw_value();
|
||||
|
||||
if !((first_region_start <= &ram_start)
|
||||
&& (first_region_end > &ram_start)
|
||||
&& (first_region_end <= &mem_32bit_reserved_start))
|
||||
{
|
||||
panic!(
|
||||
"Unexpected first memory region layout: (start: 0x{first_region_start:08x}, end: 0x{first_region_end:08x}).
|
||||
ram_start: 0x{ram_start:08x}, mem_32bit_reserved_start: 0x{mem_32bit_reserved_start:08x}"
|
||||
);
|
||||
}
|
||||
|
||||
let mem_size = first_region_end - ram_start;
|
||||
let mem_reg_prop = [ram_start, mem_size];
|
||||
let memory_node_name = format!("memory@{ram_start:x}");
|
||||
let last_addr = guest_mem.last_addr().raw_value();
|
||||
if last_addr < super::layout::MEM_32BIT_RESERVED_START.raw_value() {
|
||||
// Case 1: all RAM is under the hole
|
||||
let mem_size = last_addr - super::layout::RAM_START.raw_value() + 1;
|
||||
let mem_reg_prop = [super::layout::RAM_START.raw_value(), mem_size];
|
||||
let memory_node = fdt.begin_node("memory")?;
|
||||
fdt.property_string("device_type", "memory")?;
|
||||
fdt.property_array_u64("reg", &mem_reg_prop)?;
|
||||
fdt.end_node(memory_node)?;
|
||||
} else {
|
||||
// Case 2: RAM is split by the hole
|
||||
// Region 1: RAM before the hole
|
||||
let mem_size = super::layout::MEM_32BIT_RESERVED_START.raw_value()
|
||||
- super::layout::RAM_START.raw_value();
|
||||
let mem_reg_prop = [super::layout::RAM_START.raw_value(), mem_size];
|
||||
let memory_node_name = format!("memory@{:x}", super::layout::RAM_START.raw_value());
|
||||
let memory_node = fdt.begin_node(&memory_node_name)?;
|
||||
fdt.property_string("device_type", "memory")?;
|
||||
fdt.property_array_u64("reg", &mem_reg_prop)?;
|
||||
fdt.end_node(memory_node)?;
|
||||
}
|
||||
|
||||
// Create the memory map entry for memory region after the gap if any
|
||||
if let Some((second_region_start, second_region_end)) = ram_regions.get(1) {
|
||||
let ram_64bit_start = super::layout::RAM_64BIT_START.raw_value();
|
||||
|
||||
if second_region_start != &ram_64bit_start {
|
||||
panic!(
|
||||
"Unexpected second memory region layout: start: 0x{second_region_start:08x}, ram_64bit_start: 0x{ram_64bit_start:08x}"
|
||||
);
|
||||
}
|
||||
|
||||
let mem_size = second_region_end - ram_64bit_start;
|
||||
let mem_reg_prop = [ram_64bit_start, mem_size];
|
||||
let memory_node_name = format!("memory@{ram_64bit_start:x}");
|
||||
// Region 2: RAM after the hole
|
||||
let mem_size = last_addr - super::layout::RAM_64BIT_START.raw_value() + 1;
|
||||
let mem_reg_prop = [super::layout::RAM_64BIT_START.raw_value(), mem_size];
|
||||
let memory_node_name =
|
||||
format!("memory@{:x}", super::layout::RAM_64BIT_START.raw_value());
|
||||
let memory_node = fdt.begin_node(&memory_node_name)?;
|
||||
fdt.property_string("device_type", "memory")?;
|
||||
fdt.property_array_u64("reg", &mem_reg_prop)?;
|
||||
@@ -683,19 +342,11 @@ fn create_gic_node(fdt: &mut FdtWriter, gic_device: &Arc<Mutex<dyn Vgic>>) -> Fd
|
||||
|
||||
if gic_device.lock().unwrap().msi_compatible() {
|
||||
let msic_node = fdt.begin_node("msic")?;
|
||||
let msi_compatibility = gic_device.lock().unwrap().msi_compatibility().to_string();
|
||||
|
||||
fdt.property_string("compatible", msi_compatibility.as_str())?;
|
||||
fdt.property_string("compatible", gic_device.lock().unwrap().msi_compatibility())?;
|
||||
fdt.property_null("msi-controller")?;
|
||||
fdt.property_u32("phandle", MSI_PHANDLE)?;
|
||||
let msi_reg_prop = gic_device.lock().unwrap().msi_properties();
|
||||
fdt.property_array_u64("reg", &msi_reg_prop)?;
|
||||
|
||||
if msi_compatibility == GIC_V2M_COMPATIBLE {
|
||||
fdt.property_u32("arm,msi-base-spi", GICV2M_SPI_BASE)?;
|
||||
fdt.property_u32("arm,msi-num-spis", GICV2M_SPI_NUM)?;
|
||||
}
|
||||
|
||||
fdt.end_node(msic_node)?;
|
||||
}
|
||||
|
||||
@@ -722,14 +373,9 @@ fn create_clock_node(fdt: &mut FdtWriter) -> FdtWriterResult<()> {
|
||||
|
||||
fn create_timer_node(fdt: &mut FdtWriter) -> FdtWriterResult<()> {
|
||||
// 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.
|
||||
let irqs = [
|
||||
AARCH64_ARCH_TIMER_PHYS_SECURE_IRQ,
|
||||
AARCH64_ARCH_TIMER_PHYS_NONSECURE_IRQ,
|
||||
AARCH64_ARCH_TIMER_VIRT_IRQ,
|
||||
AARCH64_ARCH_TIMER_HYP_IRQ,
|
||||
];
|
||||
let irqs = [13, 14, 11, 10];
|
||||
let compatible = "arm,armv8-timer";
|
||||
|
||||
let mut timer_reg_cells: Vec<u32> = Vec::new();
|
||||
@@ -864,21 +510,6 @@ fn create_gpio_node<T: DeviceInfoForFdt + Clone + Debug>(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// https://www.kernel.org/doc/Documentation/devicetree/bindings/arm/fw-cfg.txt
|
||||
#[cfg(feature = "fw_cfg")]
|
||||
fn create_fw_cfg_node<T: DeviceInfoForFdt + Clone + Debug>(
|
||||
fdt: &mut FdtWriter,
|
||||
dev_info: &T,
|
||||
) -> FdtWriterResult<()> {
|
||||
// FwCfg node
|
||||
let fw_cfg_node = fdt.begin_node(&format!("fw-cfg@{:x}", dev_info.addr()))?;
|
||||
fdt.property("compatible", b"qemu,fw-cfg-mmio\0")?;
|
||||
fdt.property_array_u64("reg", &[dev_info.addr(), dev_info.length()])?;
|
||||
fdt.end_node(fw_cfg_node)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_devices_node<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHasher>(
|
||||
fdt: &mut FdtWriter,
|
||||
dev_info: &HashMap<(DeviceType, String), T, S>,
|
||||
@@ -894,8 +525,6 @@ fn create_devices_node<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::Buil
|
||||
DeviceType::Virtio(_) => {
|
||||
ordered_virtio_device.push(info);
|
||||
}
|
||||
#[cfg(feature = "fw_cfg")]
|
||||
DeviceType::FwCfg => create_fw_cfg_node(fdt, info)?,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -935,7 +564,7 @@ fn create_pci_nodes(
|
||||
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.
|
||||
// could fall bellow 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) =
|
||||
@@ -1025,39 +654,39 @@ fn create_pci_nodes(
|
||||
fdt.property_array_u32("msi-map", &msi_map)?;
|
||||
fdt.property_u32("msi-parent", MSI_PHANDLE)?;
|
||||
|
||||
if pci_device_info_elem.pci_segment_id == 0
|
||||
&& let Some(virtio_iommu_bdf) = virtio_iommu_bdf
|
||||
{
|
||||
// See kernel document Documentation/devicetree/bindings/pci/pci-iommu.txt
|
||||
// for 'iommu-map' attribute setting.
|
||||
let iommu_map = [
|
||||
0_u32,
|
||||
VIRTIO_IOMMU_PHANDLE,
|
||||
0_u32,
|
||||
virtio_iommu_bdf,
|
||||
virtio_iommu_bdf + 1,
|
||||
VIRTIO_IOMMU_PHANDLE,
|
||||
virtio_iommu_bdf + 1,
|
||||
0xffff - virtio_iommu_bdf,
|
||||
];
|
||||
fdt.property_array_u32("iommu-map", &iommu_map)?;
|
||||
if pci_device_info_elem.pci_segment_id == 0 {
|
||||
if let Some(virtio_iommu_bdf) = virtio_iommu_bdf {
|
||||
// See kernel document Documentation/devicetree/bindings/pci/pci-iommu.txt
|
||||
// for 'iommu-map' attribute setting.
|
||||
let iommu_map = [
|
||||
0_u32,
|
||||
VIRTIO_IOMMU_PHANDLE,
|
||||
0_u32,
|
||||
virtio_iommu_bdf,
|
||||
virtio_iommu_bdf + 1,
|
||||
VIRTIO_IOMMU_PHANDLE,
|
||||
virtio_iommu_bdf + 1,
|
||||
0xffff - virtio_iommu_bdf,
|
||||
];
|
||||
fdt.property_array_u32("iommu-map", &iommu_map)?;
|
||||
|
||||
// See kernel document Documentation/devicetree/bindings/virtio/iommu.txt
|
||||
// for virtio-iommu node settings.
|
||||
let virtio_iommu_node_name = format!("virtio_iommu@{virtio_iommu_bdf:x}");
|
||||
let virtio_iommu_node = fdt.begin_node(&virtio_iommu_node_name)?;
|
||||
fdt.property_u32("#iommu-cells", 1)?;
|
||||
fdt.property_string("compatible", "virtio,pci-iommu")?;
|
||||
// See kernel document Documentation/devicetree/bindings/virtio/iommu.txt
|
||||
// for virtio-iommu node settings.
|
||||
let virtio_iommu_node_name = format!("virtio_iommu@{virtio_iommu_bdf:x}");
|
||||
let virtio_iommu_node = fdt.begin_node(&virtio_iommu_node_name)?;
|
||||
fdt.property_u32("#iommu-cells", 1)?;
|
||||
fdt.property_string("compatible", "virtio,pci-iommu")?;
|
||||
|
||||
// 'reg' is a five-cell address encoded as
|
||||
// (phys.hi phys.mid phys.lo size.hi size.lo). phys.hi should contain the
|
||||
// device's BDF as 0b00000000 bbbbbbbb dddddfff 00000000. The other cells
|
||||
// should be zero.
|
||||
let reg = [virtio_iommu_bdf << 8, 0_u32, 0_u32, 0_u32, 0_u32];
|
||||
fdt.property_array_u32("reg", ®)?;
|
||||
fdt.property_u32("phandle", VIRTIO_IOMMU_PHANDLE)?;
|
||||
// 'reg' is a five-cell address encoded as
|
||||
// (phys.hi phys.mid phys.lo size.hi size.lo). phys.hi should contain the
|
||||
// device's BDF as 0b00000000 bbbbbbbb dddddfff 00000000. The other cells
|
||||
// should be zero.
|
||||
let reg = [virtio_iommu_bdf << 8, 0_u32, 0_u32, 0_u32, 0_u32];
|
||||
fdt.property_array_u32("reg", ®)?;
|
||||
fdt.property_u32("phandle", VIRTIO_IOMMU_PHANDLE)?;
|
||||
|
||||
fdt.end_node(virtio_iommu_node)?;
|
||||
fdt.end_node(virtio_iommu_node)?;
|
||||
}
|
||||
}
|
||||
|
||||
fdt.end_node(pci_node)?;
|
||||
@@ -1067,22 +696,6 @@ fn create_pci_nodes(
|
||||
}
|
||||
|
||||
fn create_distance_map_node(fdt: &mut FdtWriter, numa_nodes: &NumaNodes) -> FdtWriterResult<()> {
|
||||
// When Generic Initiator nodes are present, skip ALL FDT NUMA information.
|
||||
// Let ACPI (which supports Generic Initiator via SRAT Type 5) handle the entire NUMA topology.
|
||||
// FDT cannot represent Generic Initiator nodes, and mixing FDT + ACPI NUMA info causes conflicts.
|
||||
let has_generic_initiator = numa_nodes.values().any(|node| node.device_id.is_some());
|
||||
if has_generic_initiator {
|
||||
info!("Skipping NUMA distance map encoding in FDT with Generic Initiator devices");
|
||||
return Ok(());
|
||||
}
|
||||
// At this point, we know there are no Generic Initiator nodes
|
||||
let mut numa_ids: Vec<u32> = numa_nodes.keys().cloned().collect();
|
||||
|
||||
// If we only have one node, no distance map is needed
|
||||
if numa_ids.len() <= 1 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let distance_map_node = fdt.begin_node("distance-map")?;
|
||||
fdt.property_string("compatible", "numa-distance-map-v1")?;
|
||||
// Construct the distance matrix.
|
||||
@@ -1095,33 +708,26 @@ fn create_distance_map_node(fdt: &mut FdtWriter, numa_nodes: &NumaNodes) -> FdtW
|
||||
// a value greater than 10.
|
||||
// 4. distance-matrix should have entries in lexicographical ascending
|
||||
// order of nodes.
|
||||
numa_ids.sort_unstable(); // lexicographical order
|
||||
let mut distance_matrix = Vec::new();
|
||||
// Iterate over actual numa IDs instead of 0..len()
|
||||
for numa_id in numa_ids.iter() {
|
||||
let numa_node = &numa_nodes[numa_id];
|
||||
for dest_numa_id in numa_ids.iter() {
|
||||
if *numa_id == *dest_numa_id {
|
||||
distance_matrix.push(*numa_id);
|
||||
distance_matrix.push(*dest_numa_id);
|
||||
for numa_node_idx in 0..numa_nodes.len() {
|
||||
let numa_node = numa_nodes.get(&(numa_node_idx as u32));
|
||||
for dest_numa_node in 0..numa_node.unwrap().distances.len() + 1 {
|
||||
if numa_node_idx == dest_numa_node {
|
||||
distance_matrix.push(numa_node_idx as u32);
|
||||
distance_matrix.push(dest_numa_node as u32);
|
||||
distance_matrix.push(10_u32);
|
||||
continue;
|
||||
}
|
||||
|
||||
distance_matrix.push(*numa_id);
|
||||
distance_matrix.push(*dest_numa_id);
|
||||
// Use user-specified distance, checking both directions for symmetry
|
||||
let distance = if let Some(&dist) = numa_node.distances.get(dest_numa_id) {
|
||||
// Forward direction: current node -> dest node
|
||||
dist
|
||||
} else if let Some(dest_node) = numa_nodes.get(dest_numa_id) {
|
||||
// Reverse direction for symmetry: dest node -> current node
|
||||
dest_node.distances.get(numa_id).copied().unwrap_or(20)
|
||||
} else {
|
||||
// Default distance when neither direction is specified
|
||||
20
|
||||
};
|
||||
distance_matrix.push(distance as u32);
|
||||
distance_matrix.push(numa_node_idx as u32);
|
||||
distance_matrix.push(dest_numa_node as u32);
|
||||
distance_matrix.push(
|
||||
*numa_node
|
||||
.unwrap()
|
||||
.distances
|
||||
.get(&(dest_numa_node as u32))
|
||||
.unwrap() as u32,
|
||||
);
|
||||
}
|
||||
}
|
||||
fdt.property_array_u32("distance-matrix", distance_matrix.as_ref())?;
|
||||
@@ -1175,7 +781,10 @@ fn print_node(node: fdt_parser::node::FdtNode<'_, '_>, n_spaces: usize) {
|
||||
// - 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) => 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,
|
||||
};
|
||||
|
||||
@@ -1198,7 +807,7 @@ fn print_node(node: fdt_parser::node::FdtNode<'_, '_>, n_spaces: usize) {
|
||||
array,
|
||||
indent = (n_spaces + 2)
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Print children nodes if there is any
|
||||
@@ -1206,118 +815,3 @@ fn print_node(node: fdt_parser::node::FdtNode<'_, '_>, n_spaces: usize) {
|
||||
print_node(child, n_spaces + 2);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use super::*;
|
||||
use crate::NumaNode;
|
||||
|
||||
// Helper function to create a simple NumaNode for testing
|
||||
fn create_test_numa_node(cpus: Vec<u32>, device_id: Option<String>) -> NumaNode {
|
||||
NumaNode {
|
||||
memory_regions: Vec::new(),
|
||||
hotplug_regions: Vec::new(),
|
||||
cpus,
|
||||
pci_segments: Vec::new(),
|
||||
distances: BTreeMap::new(),
|
||||
memory_zones: Vec::new(),
|
||||
device_id,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fdt_generic_initiator_detection_and_skip() {
|
||||
// No Generic Initiator - should not skip FDT NUMA
|
||||
let mut numa_nodes = BTreeMap::new();
|
||||
numa_nodes.insert(0, create_test_numa_node(vec![0, 1], None));
|
||||
numa_nodes.insert(1, create_test_numa_node(vec![2, 3], None));
|
||||
|
||||
let has_gi = numa_nodes.values().any(|node| node.device_id.is_some());
|
||||
assert!(
|
||||
!has_gi,
|
||||
"Should not detect Generic Initiator when none present"
|
||||
);
|
||||
|
||||
// One Generic Initiator - should skip FDT NUMA
|
||||
let mut numa_nodes = BTreeMap::new();
|
||||
numa_nodes.insert(0, create_test_numa_node(vec![0, 1], None));
|
||||
numa_nodes.insert(1, create_test_numa_node(vec![], Some("vfio0".to_string())));
|
||||
|
||||
let has_gi = numa_nodes.values().any(|node| node.device_id.is_some());
|
||||
assert!(has_gi, "Should detect Generic Initiator when present");
|
||||
|
||||
let mut fdt = FdtWriter::new().unwrap();
|
||||
let result = create_distance_map_node(&mut fdt, &numa_nodes);
|
||||
assert!(result.is_ok(), "Should skip distance map when GI present");
|
||||
|
||||
// Multiple Generic Initiators - should skip FDT NUMA
|
||||
let mut numa_nodes = BTreeMap::new();
|
||||
numa_nodes.insert(0, create_test_numa_node(vec![0, 1], None));
|
||||
numa_nodes.insert(1, create_test_numa_node(vec![], Some("vfio0".to_string())));
|
||||
numa_nodes.insert(2, create_test_numa_node(vec![], Some("vfio1".to_string())));
|
||||
|
||||
let has_gi = numa_nodes.values().any(|node| node.device_id.is_some());
|
||||
assert!(has_gi, "Should detect multiple Generic Initiators");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fdt_distance_map() {
|
||||
// Single NUMA node - should skip distance map
|
||||
let mut numa_nodes = BTreeMap::new();
|
||||
numa_nodes.insert(0, create_test_numa_node(vec![0, 1], None));
|
||||
|
||||
let mut fdt = FdtWriter::new().unwrap();
|
||||
let result = create_distance_map_node(&mut fdt, &numa_nodes);
|
||||
assert!(result.is_ok(), "Should skip distance map for single node");
|
||||
|
||||
// Empty NUMA nodes - should handle gracefully
|
||||
let numa_nodes = BTreeMap::new();
|
||||
let mut fdt = FdtWriter::new().unwrap();
|
||||
let result = create_distance_map_node(&mut fdt, &numa_nodes);
|
||||
assert!(result.is_ok(), "Should handle empty NUMA nodes");
|
||||
|
||||
// Non-contiguous NUMA IDs (0, 2, 5) with distance symmetry
|
||||
let mut numa_nodes = BTreeMap::new();
|
||||
|
||||
let mut node0 = create_test_numa_node(vec![0], None);
|
||||
node0.distances.insert(2, 20);
|
||||
// node0 has no explicit distance to node5
|
||||
|
||||
let mut node2 = create_test_numa_node(vec![1], None);
|
||||
node2.distances.insert(0, 20);
|
||||
node2.distances.insert(5, 25);
|
||||
|
||||
let mut node5 = create_test_numa_node(vec![2], None);
|
||||
node5.distances.insert(0, 30);
|
||||
node5.distances.insert(2, 25);
|
||||
// node5->node0 (should be used for node0->node5)
|
||||
|
||||
numa_nodes.insert(0, node0);
|
||||
numa_nodes.insert(2, node2);
|
||||
numa_nodes.insert(5, node5);
|
||||
|
||||
// Verify IDs are sorted lexicographically
|
||||
let mut numa_ids: Vec<u32> = numa_nodes.keys().cloned().collect();
|
||||
numa_ids.sort_unstable();
|
||||
assert_eq!(numa_ids, vec![0, 2, 5]);
|
||||
|
||||
let mut fdt = FdtWriter::new().unwrap();
|
||||
let result = create_distance_map_node(&mut fdt, &numa_nodes);
|
||||
assert!(
|
||||
result.is_ok(),
|
||||
"Should handle non-contiguous IDs and symmetry"
|
||||
);
|
||||
|
||||
// Default distance (20) when no distance specified in either direction
|
||||
let mut numa_nodes = BTreeMap::new();
|
||||
numa_nodes.insert(0, create_test_numa_node(vec![0], None));
|
||||
numa_nodes.insert(1, create_test_numa_node(vec![1], None));
|
||||
// Neither node has distance to the other
|
||||
|
||||
let mut fdt = FdtWriter::new().unwrap();
|
||||
let result = create_distance_map_node(&mut fdt, &numa_nodes);
|
||||
assert!(result.is_ok(), "Should default to 20 for missing distances");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,9 +111,8 @@ pub const RAM_64BIT_START: GuestAddress = GuestAddress(0x1_0000_0000);
|
||||
pub const CMDLINE_MAX_SIZE: usize = 2048;
|
||||
|
||||
/// 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;
|
||||
/// 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;
|
||||
|
||||
/// Put ACPI table above dtb
|
||||
@@ -138,12 +137,3 @@ pub const IRQ_BASE: u32 = 32;
|
||||
|
||||
/// Number of supported interrupts
|
||||
pub const IRQ_NUM: u32 = 256;
|
||||
|
||||
/// Base SPI interrupt number for the GICv2M MSI frame
|
||||
pub const GICV2M_SPI_BASE: u32 = 128;
|
||||
|
||||
/// Total number of SPIs for the GICv2M MSI frame
|
||||
pub const GICV2M_SPI_NUM: u32 = 64;
|
||||
|
||||
/// GICv2M compatible string
|
||||
pub const GIC_V2M_COMPATIBLE: &str = "arm,gic-v2m-frame";
|
||||
|
||||
@@ -6,56 +6,52 @@
|
||||
pub mod fdt;
|
||||
/// Layout for this aarch64 system.
|
||||
pub mod layout;
|
||||
/// Module for system registers definition
|
||||
pub mod regs;
|
||||
/// Module for loading UEFI binary.
|
||||
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::{Level, log_enabled};
|
||||
use thiserror::Error;
|
||||
use vm_memory::{Address, GuestAddress, GuestMemory, GuestMemoryAtomic};
|
||||
|
||||
pub use self::fdt::DeviceInfoForFdt;
|
||||
use crate::{DeviceType, GuestMemoryMmap, NumaNodes, PciSpaceInfo, RegionType};
|
||||
|
||||
pub const _NSIG: i32 = 65;
|
||||
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, GuestUsize};
|
||||
|
||||
/// Errors thrown while configuring aarch64 system.
|
||||
#[derive(Debug, Error)]
|
||||
#[derive(Debug)]
|
||||
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")]
|
||||
WriteFdtToMemory(#[source] fdt::Error),
|
||||
WriteFdtToMemory(fdt::Error),
|
||||
|
||||
/// Failed to create a GIC.
|
||||
#[error("Failed to create a GIC")]
|
||||
SetupGic,
|
||||
|
||||
/// 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")]
|
||||
RegsConfiguration(#[source] hypervisor::HypervisorCpuError),
|
||||
RegsConfiguration(hypervisor::HypervisorCpuError),
|
||||
|
||||
/// Error configuring the MPIDR register
|
||||
#[error("Error configuring the MPIDR register")]
|
||||
VcpuRegMpidr(#[source] hypervisor::HypervisorCpuError),
|
||||
VcpuRegMpidr(hypervisor::HypervisorCpuError),
|
||||
|
||||
/// Error initializing PMU for vcpu
|
||||
#[error("Error initializing PMU for vcpu")]
|
||||
VcpuInitPmu,
|
||||
}
|
||||
|
||||
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.
|
||||
@@ -66,8 +62,8 @@ pub struct EntryPoint {
|
||||
|
||||
/// Configure the specified VCPU, and return its MPIDR.
|
||||
pub fn configure_vcpu(
|
||||
vcpu: &dyn hypervisor::Vcpu,
|
||||
id: u32,
|
||||
vcpu: &Arc<dyn hypervisor::Vcpu>,
|
||||
id: u8,
|
||||
boot_setup: Option<(EntryPoint, &GuestMemoryAtomic<GuestMemoryMmap>)>,
|
||||
) -> super::Result<u64> {
|
||||
if let Some((kernel_entry_point, _guest_memory)) = boot_setup {
|
||||
@@ -79,12 +75,14 @@ pub fn configure_vcpu(
|
||||
.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)
|
||||
}
|
||||
|
||||
pub fn arch_memory_regions() -> Vec<(GuestAddress, usize, RegionType)> {
|
||||
vec![
|
||||
pub fn arch_memory_regions(size: GuestUsize) -> Vec<(GuestAddress, usize, RegionType)> {
|
||||
let mut regions = vec![
|
||||
// 0 MiB ~ 256 MiB: UEFI, GIC and legacy devices
|
||||
(
|
||||
GuestAddress(0),
|
||||
@@ -103,21 +101,39 @@ pub fn arch_memory_regions() -> Vec<(GuestAddress, usize, RegionType)> {
|
||||
layout::PCI_MMCONFIG_SIZE as usize,
|
||||
RegionType::Reserved,
|
||||
),
|
||||
// 1GiB ~ 4032 MiB: RAM before the gap
|
||||
(
|
||||
];
|
||||
|
||||
let ram_32bit_space_size =
|
||||
layout::MEM_32BIT_RESERVED_START.unchecked_offset_from(layout::RAM_START);
|
||||
|
||||
// RAM space
|
||||
// Case1: guest memory fits before the gap
|
||||
if size <= ram_32bit_space_size {
|
||||
regions.push((layout::RAM_START, size as usize, RegionType::Ram));
|
||||
// Case2: guest memory extends beyond the gap
|
||||
} else {
|
||||
// Push memory before the gap
|
||||
regions.push((
|
||||
layout::RAM_START,
|
||||
layout::MEM_32BIT_RESERVED_START.unchecked_offset_from(layout::RAM_START) as usize,
|
||||
ram_32bit_space_size as usize,
|
||||
RegionType::Ram,
|
||||
),
|
||||
// 4GiB ~ inf: RAM after the gap
|
||||
(layout::RAM_64BIT_START, usize::MAX, RegionType::Ram),
|
||||
// Add the 32-bit reserved memory hole as a reserved region
|
||||
(
|
||||
layout::MEM_32BIT_RESERVED_START,
|
||||
layout::MEM_32BIT_RESERVED_SIZE as usize,
|
||||
RegionType::Reserved,
|
||||
),
|
||||
]
|
||||
));
|
||||
// Other memory is placed after 4GiB
|
||||
regions.push((
|
||||
layout::RAM_64BIT_START,
|
||||
(size - ram_32bit_space_size) as usize,
|
||||
RegionType::Ram,
|
||||
));
|
||||
}
|
||||
|
||||
// Add the 32-bit reserved memory hole as a reserved region
|
||||
regions.push((
|
||||
layout::MEM_32BIT_RESERVED_START,
|
||||
layout::MEM_32BIT_RESERVED_SIZE as usize,
|
||||
RegionType::Reserved,
|
||||
));
|
||||
|
||||
regions
|
||||
}
|
||||
|
||||
/// Configures the system and should be called once per vm before starting vcpu threads.
|
||||
@@ -125,8 +141,8 @@ pub fn arch_memory_regions() -> Vec<(GuestAddress, usize, RegionType)> {
|
||||
pub fn configure_system<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHasher>(
|
||||
guest_mem: &GuestMemoryMmap,
|
||||
cmdline: &str,
|
||||
vcpu_mpidr: &[u64],
|
||||
vcpu_topology: Option<(u16, u16, u16, u16)>,
|
||||
vcpu_mpidr: Vec<u64>,
|
||||
vcpu_topology: Option<(u8, u8, u8)>,
|
||||
device_info: &HashMap<(DeviceType, String), T, S>,
|
||||
initrd: &Option<super::InitramfsConfig>,
|
||||
pci_space_info: &[PciSpaceInfo],
|
||||
@@ -154,7 +170,7 @@ pub fn configure_system<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::Bui
|
||||
fdt::print_fdt(&fdt_final);
|
||||
}
|
||||
|
||||
fdt::write_fdt_to_memory(&fdt_final, guest_mem).map_err(Error::WriteFdtToMemory)?;
|
||||
fdt::write_fdt_to_memory(fdt_final, guest_mem).map_err(Error::WriteFdtToMemory)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -180,8 +196,11 @@ pub fn initramfs_load_addr(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_host_cpu_phys_bits(hypervisor: &dyn hypervisor::Hypervisor) -> u8 {
|
||||
let host_cpu_phys_bits = hypervisor.get_host_ipa_limit().try_into().unwrap();
|
||||
pub fn get_host_cpu_phys_bits() -> u8 {
|
||||
// A dummy hypervisor created only for querying the host IPA size and will
|
||||
// be freed after the query.
|
||||
let hv = hypervisor::new().unwrap();
|
||||
let host_cpu_phys_bits = hv.get_host_ipa_limit().try_into().unwrap();
|
||||
if host_cpu_phys_bits == 0 {
|
||||
// Host kernel does not support `get_host_ipa_limit`,
|
||||
// we return the default value 40 here.
|
||||
@@ -192,16 +211,30 @@ pub fn get_host_cpu_phys_bits(hypervisor: &dyn hypervisor::Hypervisor) -> u8 {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod unit_tests {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_arch_memory_regions_dram() {
|
||||
let regions = arch_memory_regions();
|
||||
fn test_arch_memory_regions_dram_2gb() {
|
||||
let regions = arch_memory_regions((1usize << 31) as u64); //2GB
|
||||
assert_eq!(5, regions.len());
|
||||
assert_eq!(layout::RAM_START, regions[3].0);
|
||||
assert_eq!((1usize << 31), regions[3].1);
|
||||
assert_eq!(RegionType::Ram, regions[3].2);
|
||||
assert_eq!(RegionType::Reserved, regions[4].2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_arch_memory_regions_dram_4gb() {
|
||||
let regions = arch_memory_regions((1usize << 32) as u64); //4GB
|
||||
let ram_32bit_space_size =
|
||||
layout::MEM_32BIT_RESERVED_START.unchecked_offset_from(layout::RAM_START) as usize;
|
||||
assert_eq!(6, regions.len());
|
||||
assert_eq!(layout::RAM_START, regions[3].0);
|
||||
assert_eq!(ram_32bit_space_size, regions[3].1);
|
||||
assert_eq!(RegionType::Ram, regions[3].2);
|
||||
assert_eq!(RegionType::Reserved, regions[5].2);
|
||||
assert_eq!(RegionType::Ram, regions[4].2);
|
||||
assert_eq!(((1usize << 32) - ram_32bit_space_size), regions[4].1);
|
||||
}
|
||||
}
|
||||
|
||||
44
arch/src/aarch64/regs.rs
Normal file
44
arch/src/aarch64/regs.rs
Normal file
@@ -0,0 +1,44 @@
|
||||
// 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.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
use std::os::fd::AsFd;
|
||||
use std::result;
|
||||
|
||||
use thiserror::Error;
|
||||
use vm_memory::{GuestAddress, GuestMemory};
|
||||
use vm_memory::{Bytes, GuestAddress, GuestMemory};
|
||||
|
||||
/// Errors thrown while loading UEFI binary
|
||||
#[derive(Debug, Error)]
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Unable to seek to UEFI image start.
|
||||
#[error("Unable to seek to UEFI image start")]
|
||||
SeekUefiStart,
|
||||
/// Unable to seek to UEFI image end.
|
||||
#[error("Unable to seek to UEFI image end")]
|
||||
SeekUefiEnd,
|
||||
/// UEFI image too big.
|
||||
#[error("UEFI image too big")]
|
||||
UefiTooBig,
|
||||
/// Unable to read UEFI image
|
||||
#[error("Unable to read UEFI image")]
|
||||
ReadUefiImage,
|
||||
}
|
||||
type Result<T> = result::Result<T, Error>;
|
||||
@@ -33,7 +24,7 @@ pub fn load_uefi<F, M: GuestMemory>(
|
||||
uefi_image: &mut F,
|
||||
) -> Result<()>
|
||||
where
|
||||
F: Read + Seek + AsFd,
|
||||
F: Read + Seek,
|
||||
{
|
||||
let uefi_size = uefi_image
|
||||
.seek(SeekFrom::End(0))
|
||||
@@ -45,6 +36,6 @@ where
|
||||
}
|
||||
uefi_image.rewind().map_err(|_| Error::SeekUefiStart)?;
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -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, Oracle and/or its affiliates.
|
||||
//
|
||||
@@ -6,14 +5,22 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//! Implements platform specific functionality.
|
||||
//! Supported platforms: x86_64, aarch64, riscv64.
|
||||
//! Supported platforms: x86_64, aarch64.
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::sync::Arc;
|
||||
use std::{fmt, result};
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use crate::x86_64::SgxEpcSection;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::BTreeMap;
|
||||
use std::fmt;
|
||||
use std::result;
|
||||
use std::sync::Arc;
|
||||
use thiserror::Error;
|
||||
use versionize::{VersionMap, Versionize, VersionizeError, VersionizeResult};
|
||||
use versionize_derive::Versionize;
|
||||
use vm_migration::VersionMapped;
|
||||
|
||||
type GuestMemoryMmap = vm_memory::GuestMemoryMmap<vm_memory::bitmap::AtomicBitmap>;
|
||||
type GuestRegionMmap = vm_memory::GuestRegionMmap<vm_memory::bitmap::AtomicBitmap>;
|
||||
@@ -22,14 +29,11 @@ type GuestRegionMmap = vm_memory::GuestRegionMmap<vm_memory::bitmap::AtomicBitma
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
#[error("Platform specific error (x86_64)")]
|
||||
PlatformSpecific(#[from] x86_64::Error),
|
||||
#[error("Platform specific error (x86_64): {0:?}")]
|
||||
PlatformSpecific(x86_64::Error),
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
#[error("Platform specific error (aarch64)")]
|
||||
PlatformSpecific(#[from] aarch64::Error),
|
||||
#[cfg(target_arch = "riscv64")]
|
||||
#[error("Platform specific error (riscv64)")]
|
||||
PlatformSpecific(#[from] riscv64::Error),
|
||||
#[error("Platform specific error (aarch64): {0:?}")]
|
||||
PlatformSpecific(aarch64::Error),
|
||||
#[error("The memory map table extends past the end of guest memory")]
|
||||
MemmapTablePastRamEnd,
|
||||
#[error("Error writing memory map table to guest memory")]
|
||||
@@ -40,21 +44,17 @@ pub enum Error {
|
||||
StartInfoSetup,
|
||||
#[error("Failed to compute initramfs address")]
|
||||
InitramfsAddress,
|
||||
#[error("Error writing module entry to guest memory")]
|
||||
#[error("Error writing module entry to guest memory: {0}")]
|
||||
ModlistSetup(#[source] vm_memory::GuestMemoryError),
|
||||
#[error("RSDP extends past the end of guest memory")]
|
||||
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.
|
||||
pub type Result<T> = result::Result<T, Error>;
|
||||
|
||||
/// 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 {
|
||||
/// RAM type
|
||||
Ram,
|
||||
@@ -72,26 +72,17 @@ pub enum RegionType {
|
||||
Reserved,
|
||||
}
|
||||
|
||||
impl VersionMapped for RegionType {}
|
||||
|
||||
/// Module for aarch64 related functionality.
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub mod aarch64;
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub use aarch64::{
|
||||
_NSIG, EntryPoint, arch_memory_regions, configure_system, configure_vcpu,
|
||||
fdt::DeviceInfoForFdt, get_host_cpu_phys_bits, initramfs_load_addr, layout,
|
||||
layout::CMDLINE_MAX_SIZE, layout::IRQ_BASE, uefi,
|
||||
};
|
||||
|
||||
/// Module for riscv64 related functionality.
|
||||
#[cfg(target_arch = "riscv64")]
|
||||
pub mod riscv64;
|
||||
|
||||
#[cfg(target_arch = "riscv64")]
|
||||
pub use riscv64::{
|
||||
_NSIG, EntryPoint, arch_memory_regions, configure_system, configure_vcpu,
|
||||
fdt::DeviceInfoForFdt, get_host_cpu_phys_bits, initramfs_load_addr, layout,
|
||||
layout::CMDLINE_MAX_SIZE, layout::IRQ_BASE, uefi,
|
||||
arch_memory_regions, configure_system, configure_vcpu, fdt::DeviceInfoForFdt,
|
||||
get_host_cpu_phys_bits, initramfs_load_addr, layout, layout::CMDLINE_MAX_SIZE,
|
||||
layout::IRQ_BASE, uefi, EntryPoint,
|
||||
};
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
@@ -99,9 +90,9 @@ pub mod x86_64;
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub use x86_64::{
|
||||
_NSIG, CpuidConfig, CpuidFeatureEntry, EntryPoint, arch_memory_regions, configure_system,
|
||||
configure_vcpu, generate_common_cpuid, generate_ram_ranges, get_host_cpu_phys_bits,
|
||||
initramfs_load_addr, layout, layout::CMDLINE_MAX_SIZE, layout::CMDLINE_START, regs,
|
||||
arch_memory_regions, configure_system, configure_vcpu, generate_common_cpuid,
|
||||
get_host_cpu_phys_bits, initramfs_load_addr, layout, layout::CMDLINE_MAX_SIZE,
|
||||
layout::CMDLINE_START, regs, CpuidFeatureEntry, EntryPoint,
|
||||
};
|
||||
|
||||
/// Safe wrapper for `sysconf(_SC_PAGESIZE)`.
|
||||
@@ -116,11 +107,11 @@ fn pagesize() -> usize {
|
||||
pub struct NumaNode {
|
||||
pub memory_regions: Vec<Arc<GuestRegionMmap>>,
|
||||
pub hotplug_regions: Vec<Arc<GuestRegionMmap>>,
|
||||
pub cpus: Vec<u32>,
|
||||
pub pci_segments: Vec<u16>,
|
||||
pub cpus: Vec<u8>,
|
||||
pub distances: BTreeMap<u32, u8>,
|
||||
pub memory_zones: Vec<String>,
|
||||
pub device_id: Option<String>,
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub sgx_epc_sections: Vec<SgxEpcSection>,
|
||||
}
|
||||
|
||||
pub type NumaNodes = BTreeMap<u32, NumaNode>;
|
||||
@@ -139,7 +130,7 @@ pub enum DeviceType {
|
||||
/// Device Type: Virtio.
|
||||
Virtio(u32),
|
||||
/// Device Type: Serial.
|
||||
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
Serial,
|
||||
/// Device Type: RTC.
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
@@ -147,9 +138,6 @@ pub enum DeviceType {
|
||||
/// Device Type: GPIO.
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
Gpio,
|
||||
/// Device Type: fw_cfg.
|
||||
#[cfg(feature = "fw_cfg")]
|
||||
FwCfg,
|
||||
}
|
||||
|
||||
/// Default (smallest) memory page size for the supported architectures.
|
||||
@@ -163,7 +151,7 @@ impl fmt::Display for DeviceType {
|
||||
|
||||
/// Structure to describe MMIO device information
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub struct MmioDeviceInfo {
|
||||
pub addr: u64,
|
||||
pub len: u64,
|
||||
@@ -172,7 +160,7 @@ pub struct MmioDeviceInfo {
|
||||
|
||||
/// Structure to describe PCI space information
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub struct PciSpaceInfo {
|
||||
pub pci_segment_id: u16,
|
||||
pub mmio_config_address: u64,
|
||||
@@ -180,7 +168,7 @@ pub struct PciSpaceInfo {
|
||||
pub pci_device_space_size: u64,
|
||||
}
|
||||
|
||||
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
impl DeviceInfoForFdt for MmioDeviceInfo {
|
||||
fn addr(&self) -> u64 {
|
||||
self.addr
|
||||
|
||||
@@ -1,483 +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 log::debug;
|
||||
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")]
|
||||
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,
|
||||
isa_string: &str,
|
||||
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, isa_string)?;
|
||||
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: &[u8], guest_mem: &GuestMemoryMmap) -> Result<()> {
|
||||
// Write FDT to memory.
|
||||
guest_mem
|
||||
.write_slice(fdt_final, 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, isa_string: &str) -> 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@{cpu_index:x}"))?;
|
||||
fdt.property_string("device_type", "cpu")?;
|
||||
fdt.property_string("compatible", "riscv")?;
|
||||
fdt.property_string("mmu-type", "sv48")?;
|
||||
fdt.property_string("riscv,isa", isa_string)?;
|
||||
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@{ram_start:x}");
|
||||
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) => value_cstr.to_str().ok(),
|
||||
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,117 +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 |
|
||||
// | |
|
||||
// 4 MB +---------------------------------------------------------------+
|
||||
// | UEFI flash |
|
||||
// 0 GB +---------------------------------------------------------------+
|
||||
//
|
||||
//
|
||||
|
||||
use vm_memory::GuestAddress;
|
||||
|
||||
/// 0x0 ~ 0x40_0000 (4 MiB) is reserved to UEFI
|
||||
/// UEFI binary size is required less than 3 MiB, reserving 4 MiB is enough.
|
||||
pub const UEFI_START: GuestAddress = GuestAddress(0);
|
||||
pub const UEFI_SIZE: u64 = 0x040_0000;
|
||||
|
||||
/// AIA related devices
|
||||
/// See https://elixir.bootlin.com/linux/v6.10/source/arch/riscv/include/uapi/asm/kvm.h
|
||||
/// 0x40_0000 ~ 0x0400_0000 (64 MiB) resides APLICs
|
||||
pub const APLIC_START: GuestAddress = GuestAddress(0x40_0000);
|
||||
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;
|
||||
|
||||
/// Put ACPI table above dtb
|
||||
pub const ACPI_START: GuestAddress = GuestAddress(RAM_START.0 + FDT_MAX_SIZE);
|
||||
pub const ACPI_MAX_SIZE: u64 = 0x20_0000;
|
||||
pub const RSDP_POINTER: GuestAddress = ACPI_START;
|
||||
|
||||
/// Kernel start after FDT and ACPI
|
||||
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,231 +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;
|
||||
/// Module for loading UEFI binary.
|
||||
pub mod uefi;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Debug;
|
||||
use std::fs::File;
|
||||
use std::io::{BufRead, BufReader};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use hypervisor::arch::riscv64::aia::Vaia;
|
||||
use log::{Level, log_enabled};
|
||||
use thiserror::Error;
|
||||
use vm_memory::{Address, GuestAddress, GuestMemory, GuestMemoryAtomic};
|
||||
|
||||
pub use self::fdt::DeviceInfoForFdt;
|
||||
use crate::{DeviceType, GuestMemoryMmap, PciSpaceInfo, RegionType};
|
||||
|
||||
pub const CLOUDHV_IRQCHIP_NUM_MSIS: u16 = 255;
|
||||
pub const CLOUDHV_IRQCHIP_NUM_SOURCES: u8 = 96;
|
||||
pub const CLOUDHV_IRQCHIP_NUM_PRIO_BITS: u8 = 3;
|
||||
pub const CLOUDHV_IRQCHIP_MAX_GUESTS_BITS: u8 = 3;
|
||||
pub const CLOUDHV_IRQCHIP_MAX_GUESTS: u8 = (1 << CLOUDHV_IRQCHIP_MAX_GUESTS_BITS) - 1;
|
||||
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")]
|
||||
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")]
|
||||
RegsConfiguration(#[source] hypervisor::HypervisorCpuError),
|
||||
|
||||
/// Error opening /proc/cpuinfo
|
||||
#[error("Error opening /proc/cpuinfo")]
|
||||
OpenCpuInfo(#[source] std::io::Error),
|
||||
|
||||
/// Error reading /proc/cpuinfo
|
||||
#[error("Error reading /proc/cpuinfo")]
|
||||
ReadCpuInfo(#[source] std::io::Error),
|
||||
|
||||
/// Invalid ISA string
|
||||
#[error("Invalid ISA string: {0}")]
|
||||
InvalidIsaString(String),
|
||||
|
||||
/// Error parsing /proc/cpuinfo
|
||||
#[error("Error parsing /proc/cpuinfo")]
|
||||
CpuInfoParsing,
|
||||
}
|
||||
|
||||
#[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: &dyn hypervisor::Vcpu,
|
||||
id: u32,
|
||||
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),
|
||||
]
|
||||
}
|
||||
|
||||
// Read the first "isa" string from /proc/cpuinfo and filter out the H extension,
|
||||
// while correctly preserving multi-letter extensions.
|
||||
fn isa_string_from_host() -> Result<String, Error> {
|
||||
let file = File::open("/proc/cpuinfo").map_err(Error::OpenCpuInfo)?;
|
||||
let reader = BufReader::new(file);
|
||||
|
||||
for line in reader.lines() {
|
||||
let line = line.map_err(Error::ReadCpuInfo)?;
|
||||
let trimmed_line = line.trim();
|
||||
|
||||
if trimmed_line.starts_with("isa") {
|
||||
let parts: Vec<&str> = trimmed_line.split(':').collect();
|
||||
if parts.len() == 2 {
|
||||
let isa_string = parts[1].trim();
|
||||
|
||||
// Split the string by underscores to separate single letter vs long-form
|
||||
// extensions
|
||||
let mut components: Vec<String> =
|
||||
isa_string.split('_').map(|s| s.to_string()).collect();
|
||||
|
||||
if components.is_empty() {
|
||||
return Err(Error::InvalidIsaString(isa_string.to_string()));
|
||||
}
|
||||
|
||||
// Remove H extension if present in single letter extensions
|
||||
let first_component = components[0].chars().filter(|&c| c != 'h').collect();
|
||||
|
||||
components[0] = first_component;
|
||||
|
||||
return Ok(components.join("_"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(Error::CpuInfoParsing)
|
||||
}
|
||||
|
||||
/// 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 isa_string = isa_string_from_host()?;
|
||||
let fdt_final = fdt::create_fdt(
|
||||
guest_mem,
|
||||
cmdline,
|
||||
num_vcpu,
|
||||
&isa_string,
|
||||
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: &dyn hypervisor::Hypervisor) -> u8 {
|
||||
40
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod unit_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);
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
// Copyright 2020 Arm Limited (or its affiliates). All rights reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
use std::os::fd::AsFd;
|
||||
use std::result;
|
||||
|
||||
use thiserror::Error;
|
||||
use vm_memory::{GuestAddress, GuestMemory};
|
||||
|
||||
/// Errors thrown while loading UEFI binary
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
/// Unable to seek to UEFI image start.
|
||||
#[error("Unable to seek to UEFI image start")]
|
||||
SeekUefiStart,
|
||||
/// Unable to seek to UEFI image end.
|
||||
#[error("Unable to seek to UEFI image end")]
|
||||
SeekUefiEnd,
|
||||
/// UEFI image too big.
|
||||
#[error("UEFI image too big")]
|
||||
UefiTooBig,
|
||||
/// Unable to read UEFI image
|
||||
#[error("Unable to read UEFI image")]
|
||||
ReadUefiImage,
|
||||
}
|
||||
type Result<T> = result::Result<T, Error>;
|
||||
|
||||
pub fn load_uefi<F, M: GuestMemory>(
|
||||
guest_mem: &M,
|
||||
guest_addr: GuestAddress,
|
||||
uefi_image: &mut F,
|
||||
) -> Result<()>
|
||||
where
|
||||
F: Read + Seek + AsFd,
|
||||
{
|
||||
let uefi_size = uefi_image
|
||||
.seek(SeekFrom::End(0))
|
||||
.map_err(|_| Error::SeekUefiEnd)? as usize;
|
||||
|
||||
// edk2 image on virtual platform is smaller than 3M
|
||||
if uefi_size > 0x300000 {
|
||||
return Err(Error::UefiTooBig);
|
||||
}
|
||||
uefi_image.rewind().map_err(|_| Error::SeekUefiStart)?;
|
||||
guest_mem
|
||||
.read_exact_volatile_from(guest_addr, &mut uefi_image.as_fd(), uefi_size)
|
||||
.map_err(|_| Error::ReadUefiImage)
|
||||
}
|
||||
@@ -6,6 +6,7 @@
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
|
||||
use std::result;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub type Result<T> = result::Result<T, hypervisor::HypervisorCpuError>;
|
||||
|
||||
@@ -23,7 +24,7 @@ pub fn set_apic_delivery_mode(reg: u32, mode: u32) -> u32 {
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `vcpu` - The VCPU object to configure.
|
||||
pub fn set_lint(vcpu: &dyn hypervisor::Vcpu) -> Result<()> {
|
||||
pub fn set_lint(vcpu: &Arc<dyn hypervisor::Vcpu>) -> Result<()> {
|
||||
let mut klapic = vcpu.get_lapic()?;
|
||||
|
||||
let lvt_lint0 = klapic.get_klapic_reg(APIC_LVT0);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,10 +1,6 @@
|
||||
// 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
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
#![allow(non_camel_case_types)]
|
||||
use vm_memory::ByteValued;
|
||||
|
||||
pub const MP_PROCESSOR: ::std::os::raw::c_uint = 0;
|
||||
pub const MP_BUS: ::std::os::raw::c_uint = 1;
|
||||
@@ -19,7 +15,7 @@ pub const MP_IRQDIR_DEFAULT: ::std::os::raw::c_uint = 0;
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone)]
|
||||
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 length: ::std::os::raw::c_uchar,
|
||||
pub specification: ::std::os::raw::c_uchar,
|
||||
@@ -31,22 +27,15 @@ pub struct mpf_intel {
|
||||
pub feature5: ::std::os::raw::c_uchar,
|
||||
}
|
||||
|
||||
const _: () = assert!(::core::mem::size_of::<mpf_intel>() == 16);
|
||||
// SAFETY: all members of this struct are plain integers
|
||||
// and the sum of their sizes is the size of the struct, so
|
||||
// padding and reserved values are not possible as there
|
||||
// would be nowhere for them to exist.
|
||||
unsafe impl ByteValued for mpf_intel {}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone)]
|
||||
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 spec: ::std::os::raw::c_uchar,
|
||||
pub checksum: ::std::os::raw::c_uchar,
|
||||
pub oem: [::std::os::raw::c_uchar; 8usize],
|
||||
pub productid: [::std::os::raw::c_uchar; 12usize],
|
||||
pub spec: ::std::os::raw::c_char,
|
||||
pub checksum: ::std::os::raw::c_char,
|
||||
pub oem: [::std::os::raw::c_char; 8usize],
|
||||
pub productid: [::std::os::raw::c_char; 12usize],
|
||||
pub oemptr: ::std::os::raw::c_uint,
|
||||
pub oemsize: ::std::os::raw::c_ushort,
|
||||
pub oemcount: ::std::os::raw::c_ushort,
|
||||
@@ -54,19 +43,6 @@ pub struct mpc_table {
|
||||
pub reserved: ::std::os::raw::c_uint,
|
||||
}
|
||||
|
||||
const _: () = {
|
||||
assert!(::core::mem::size_of::<mpc_table>() == 4 + 2 + 1 + 1 + 8 + 12 + 4 + 2 + 2 + 4 + 4);
|
||||
assert!(::core::mem::size_of::<::std::os::raw::c_uint>() == 4);
|
||||
assert!(::core::mem::size_of::<::std::os::raw::c_ushort>() == 2);
|
||||
assert!(::core::mem::size_of::<::std::os::raw::c_uchar>() == 1);
|
||||
};
|
||||
|
||||
// SAFETY: all members of this struct are plain integers
|
||||
// and the sum of their sizes is the size of the struct, so
|
||||
// padding and reserved values are not possible as there
|
||||
// would be nowhere for them to exist.
|
||||
unsafe impl ByteValued for mpc_table {}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone)]
|
||||
pub struct mpc_cpu {
|
||||
@@ -79,13 +55,6 @@ pub struct mpc_cpu {
|
||||
pub reserved: [::std::os::raw::c_uint; 2usize],
|
||||
}
|
||||
|
||||
const _: () = assert!(::core::mem::size_of::<mpc_cpu>() == 20);
|
||||
// SAFETY: all members of this struct are plain integers
|
||||
// and the sum of their sizes is the size of the struct, so
|
||||
// padding and reserved values are not possible as there
|
||||
// would be nowhere for them to exist.
|
||||
unsafe impl ByteValued for mpc_cpu {}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone)]
|
||||
pub struct mpc_bus {
|
||||
@@ -94,13 +63,6 @@ pub struct mpc_bus {
|
||||
pub bustype: [::std::os::raw::c_uchar; 6usize],
|
||||
}
|
||||
|
||||
const _: () = assert!(::core::mem::size_of::<mpc_bus>() == 8);
|
||||
// SAFETY: all members of this struct are plain integers
|
||||
// and the sum of their sizes is the size of the struct, so
|
||||
// padding and reserved values are not possible as there
|
||||
// would be nowhere for them to exist.
|
||||
unsafe impl ByteValued for mpc_bus {}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone)]
|
||||
pub struct mpc_ioapic {
|
||||
@@ -111,13 +73,6 @@ pub struct mpc_ioapic {
|
||||
pub apicaddr: ::std::os::raw::c_uint,
|
||||
}
|
||||
|
||||
const _: () = assert!(::core::mem::size_of::<mpc_ioapic>() == 8);
|
||||
// SAFETY: all members of this struct are plain integers
|
||||
// and the sum of their sizes is the size of the struct, so
|
||||
// padding and reserved values are not possible as there
|
||||
// would be nowhere for them to exist.
|
||||
unsafe impl ByteValued for mpc_ioapic {}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone)]
|
||||
pub struct mpc_intsrc {
|
||||
@@ -130,13 +85,6 @@ pub struct mpc_intsrc {
|
||||
pub dstirq: ::std::os::raw::c_uchar,
|
||||
}
|
||||
|
||||
const _: () = assert!(::core::mem::size_of::<mpc_intsrc>() == 8);
|
||||
// SAFETY: all members of this struct are plain integers
|
||||
// and the sum of their sizes is the size of the struct, so
|
||||
// padding and reserved values are not possible as there
|
||||
// would be nowhere for them to exist.
|
||||
unsafe impl ByteValued for mpc_intsrc {}
|
||||
|
||||
pub const MP_IRQ_SOURCE_TYPES_MP_INT: ::std::os::raw::c_uint = 0;
|
||||
pub const MP_IRQ_SOURCE_TYPES_MP_NMI: ::std::os::raw::c_uint = 1;
|
||||
pub const MP_IRQ_SOURCE_TYPES_MP_EXT_INT: ::std::os::raw::c_uint = 3;
|
||||
@@ -153,26 +101,12 @@ pub struct mpc_lintsrc {
|
||||
pub destapiclint: ::std::os::raw::c_uchar,
|
||||
}
|
||||
|
||||
const _: () = assert!(::core::mem::size_of::<mpc_lintsrc>() == 8);
|
||||
// SAFETY: all members of this struct are plain integers
|
||||
// and the sum of their sizes is the size of the struct, so
|
||||
// padding and reserved values are not possible as there
|
||||
// would be nowhere for them to exist.
|
||||
unsafe impl ByteValued for mpc_lintsrc {}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone)]
|
||||
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 rev: ::std::os::raw::c_uchar,
|
||||
pub checksum: ::std::os::raw::c_uchar,
|
||||
pub mpc: [::std::os::raw::c_uchar; 8usize],
|
||||
pub rev: ::std::os::raw::c_char,
|
||||
pub checksum: ::std::os::raw::c_char,
|
||||
pub mpc: [::std::os::raw::c_char; 8usize],
|
||||
}
|
||||
|
||||
const _: () = assert!(::core::mem::size_of::<mpc_oemtable>() == 16);
|
||||
// SAFETY: all members of this struct are plain integers
|
||||
// and the sum of their sizes is the size of the struct, so
|
||||
// padding and reserved values are not possible as there
|
||||
// would be nowhere for them to exist.
|
||||
unsafe impl ByteValued for mpc_oemtable {}
|
||||
|
||||
@@ -5,17 +5,15 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
|
||||
use std::{mem, result, slice};
|
||||
|
||||
use libc::c_uchar;
|
||||
use log::{info, warn};
|
||||
use thiserror::Error;
|
||||
use vm_memory::{Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryError};
|
||||
|
||||
use super::MAX_SUPPORTED_CPUS_LEGACY;
|
||||
use crate::GuestMemoryMmap;
|
||||
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 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
|
||||
// trait (in this case `ByteValued`) where:
|
||||
@@ -52,55 +50,57 @@ unsafe impl ByteValued for MpcLintsrcWrapper {}
|
||||
// SAFETY: see above
|
||||
unsafe impl ByteValued for MpfIntelWrapper {}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// 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,
|
||||
/// The MP table has too little address space to be stored.
|
||||
#[error("The MP table has too little address space to be stored")]
|
||||
AddressOverflow,
|
||||
/// Failure while zeroing out the memory for the MP table.
|
||||
#[error("Failure while zeroing out the memory for the MP table")]
|
||||
Clear(#[source] GuestMemoryError),
|
||||
Clear(GuestMemoryError),
|
||||
/// Number of CPUs exceeds the maximum supported CPUs
|
||||
TooManyCpus,
|
||||
/// Failure to write the MP floating pointer.
|
||||
#[error("Failure to write the MP floating pointer")]
|
||||
WriteMpfIntel(#[source] GuestMemoryError),
|
||||
WriteMpfIntel(GuestMemoryError),
|
||||
/// Failure to write MP CPU entry.
|
||||
#[error("Failure to write MP CPU entry")]
|
||||
WriteMpcCpu(#[source] GuestMemoryError),
|
||||
WriteMpcCpu(GuestMemoryError),
|
||||
/// Failure to write MP ioapic entry.
|
||||
#[error("Failure to write MP ioapic entry")]
|
||||
WriteMpcIoapic(#[source] GuestMemoryError),
|
||||
WriteMpcIoapic(GuestMemoryError),
|
||||
/// Failure to write MP bus entry.
|
||||
#[error("Failure to write MP bus entry")]
|
||||
WriteMpcBus(#[source] GuestMemoryError),
|
||||
WriteMpcBus(GuestMemoryError),
|
||||
/// Failure to write MP interrupt source entry.
|
||||
#[error("Failure to write MP interrupt source entry")]
|
||||
WriteMpcIntsrc(#[source] GuestMemoryError),
|
||||
WriteMpcIntsrc(GuestMemoryError),
|
||||
/// Failure to write MP local interrupt source entry.
|
||||
#[error("Failure to write MP local interrupt source entry")]
|
||||
WriteMpcLintsrc(#[source] GuestMemoryError),
|
||||
WriteMpcLintsrc(GuestMemoryError),
|
||||
/// Failure to write MP table header.
|
||||
#[error("Failure to write MP table header")]
|
||||
WriteMpcTable(#[source] GuestMemoryError),
|
||||
WriteMpcTable(GuestMemoryError),
|
||||
}
|
||||
|
||||
pub type Result<T> = result::Result<T, Error>;
|
||||
|
||||
// With APIC/xAPIC, there are only 255 APIC IDs available. And IOAPIC occupies
|
||||
// one APIC ID, so only 254 CPUs at maximum may be supported. Actually it's
|
||||
// a large number for FC usecases.
|
||||
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.
|
||||
const SMP_MAGIC_IDENT: &[c_uchar; 4] = b"_MP_";
|
||||
const MPC_SIGNATURE: &[c_uchar; 4] = b"PCMP";
|
||||
const MPC_SPEC: u8 = 4;
|
||||
const MPC_OEM: &[c_uchar; 8] = b"FC ";
|
||||
const MPC_PRODUCT_ID: &[c_uchar; 12] = &[b'0'; 12];
|
||||
const BUS_TYPE_ISA: &[c_uchar; 6] = b"ISA ";
|
||||
const SMP_MAGIC_IDENT: [c_char; 4] = char_array!(c_char; '_', 'M', 'P', '_');
|
||||
const MPC_SIGNATURE: [c_char; 4] = char_array!(c_char; 'P', 'C', 'M', 'P');
|
||||
const MPC_SPEC: i8 = 4;
|
||||
const MPC_OEM: [c_char; 8] = char_array!(c_char; 'F', 'C', ' ', ' ', ' ', ' ', ' ', ' ');
|
||||
const MPC_PRODUCT_ID: [c_char; 12] = ['0' as c_char; 12];
|
||||
const BUS_TYPE_ISA: [u8; 6] = char_array!(u8; 'I', 'S', 'A', ' ', ' ', ' ');
|
||||
const APIC_VERSION: u8 = 0x14;
|
||||
const CPU_STEPPING: u32 = 0x600;
|
||||
const CPU_FEATURE_APIC: u32 = 0x200;
|
||||
const CPU_FEATURE_FPU: u32 = 0x001;
|
||||
|
||||
fn compute_checksum<T: Copy + ByteValued>(v: &T) -> u8 {
|
||||
fn compute_checksum<T: Copy>(v: &T) -> u8 {
|
||||
// SAFETY: we are only reading the bytes within the size of the `T` reference `v`.
|
||||
let v_slice = unsafe { slice::from_raw_parts(v as *const T as *const u8, mem::size_of::<T>()) };
|
||||
let mut checksum: u8 = 0;
|
||||
@@ -115,7 +115,7 @@ fn mpf_intel_compute_checksum(v: &mpspec::mpf_intel) -> u8 {
|
||||
(!checksum).wrapping_add(1)
|
||||
}
|
||||
|
||||
fn compute_mp_size(num_cpus: u32) -> usize {
|
||||
fn compute_mp_size(num_cpus: u8) -> usize {
|
||||
mem::size_of::<MpfIntelWrapper>()
|
||||
+ mem::size_of::<MpcTableWrapper>()
|
||||
+ mem::size_of::<MpcCpuWrapper>() * (num_cpus as usize)
|
||||
@@ -126,19 +126,9 @@ fn compute_mp_size(num_cpus: u32) -> usize {
|
||||
}
|
||||
|
||||
/// Performs setup of the MP table for the given `num_cpus`.
|
||||
pub fn setup_mptable(
|
||||
offset: GuestAddress,
|
||||
mem: &GuestMemoryMmap,
|
||||
num_cpus: u32,
|
||||
topology: Option<(u16, u16, u16, u16)>,
|
||||
) -> Result<()> {
|
||||
if num_cpus > 0 {
|
||||
let cpu_id_max = num_cpus - 1;
|
||||
let x2apic_id_max = get_x2apic_id(cpu_id_max, topology);
|
||||
if x2apic_id_max >= MAX_SUPPORTED_CPUS_LEGACY {
|
||||
info!("Skipping mptable creation due to too many CPUs");
|
||||
return Ok(());
|
||||
}
|
||||
pub fn setup_mptable(offset: GuestAddress, mem: &GuestMemoryMmap, num_cpus: u8) -> Result<()> {
|
||||
if num_cpus as u32 > MAX_SUPPORTED_CPUS {
|
||||
return Err(Error::TooManyCpus);
|
||||
}
|
||||
|
||||
// Used to keep track of the next base pointer into the MP table.
|
||||
@@ -152,7 +142,7 @@ pub fn setup_mptable(
|
||||
}
|
||||
|
||||
let mut checksum: u8 = 0;
|
||||
let ioapicid: u8 = MAX_SUPPORTED_CPUS_LEGACY 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
|
||||
// overflow.
|
||||
@@ -164,13 +154,13 @@ pub fn setup_mptable(
|
||||
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)?;
|
||||
|
||||
{
|
||||
let mut mpf_intel = MpfIntelWrapper(mpspec::mpf_intel::default());
|
||||
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.specification = 4;
|
||||
mpf_intel.0.physptr = (base_mp.raw_value() + size) as u32;
|
||||
@@ -190,7 +180,7 @@ pub fn setup_mptable(
|
||||
for cpu_id in 0..num_cpus {
|
||||
let mut mpc_cpu = MpcCpuWrapper(mpspec::mpc_cpu::default());
|
||||
mpc_cpu.0.type_ = mpspec::MP_PROCESSOR as u8;
|
||||
mpc_cpu.0.apicid = get_x2apic_id(cpu_id, topology) as u8;
|
||||
mpc_cpu.0.apicid = cpu_id;
|
||||
mpc_cpu.0.apicver = APIC_VERSION;
|
||||
mpc_cpu.0.cpuflag = mpspec::CPU_ENABLED as u8
|
||||
| if cpu_id == 0 {
|
||||
@@ -211,7 +201,7 @@ pub fn setup_mptable(
|
||||
let mut mpc_bus = MpcBusWrapper(mpspec::mpc_bus::default());
|
||||
mpc_bus.0.type_ = mpspec::MP_BUS as u8;
|
||||
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)
|
||||
.map_err(Error::WriteMpcBus)?;
|
||||
base_mp = base_mp.unchecked_add(size as u64);
|
||||
@@ -282,14 +272,14 @@ pub fn setup_mptable(
|
||||
|
||||
{
|
||||
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.spec = MPC_SPEC;
|
||||
mpc_table.0.oem = *MPC_OEM;
|
||||
mpc_table.0.productid = *MPC_PRODUCT_ID;
|
||||
mpc_table.0.oem = MPC_OEM;
|
||||
mpc_table.0.productid = MPC_PRODUCT_ID;
|
||||
mpc_table.0.lapic = APIC_START.0 as u32;
|
||||
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)
|
||||
.map_err(Error::WriteMpcTable)?;
|
||||
}
|
||||
@@ -298,12 +288,10 @@ pub fn setup_mptable(
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod unit_tests {
|
||||
use vm_memory::bitmap::BitmapSlice;
|
||||
use vm_memory::{GuestUsize, VolatileMemoryError, VolatileSlice, WriteVolatile};
|
||||
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::layout::MPTABLE_START;
|
||||
use vm_memory::{GuestAddress, GuestUsize};
|
||||
|
||||
fn table_entry_size(type_: u8) -> usize {
|
||||
match type_ as u32 {
|
||||
@@ -322,7 +310,7 @@ mod unit_tests {
|
||||
let mem =
|
||||
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]
|
||||
@@ -331,7 +319,7 @@ mod unit_tests {
|
||||
let mem = GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(num_cpus) - 1)])
|
||||
.unwrap();
|
||||
|
||||
setup_mptable(MPTABLE_START, &mem, num_cpus, None).unwrap_err();
|
||||
assert!(setup_mptable(MPTABLE_START, &mem, num_cpus).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -340,7 +328,7 @@ mod unit_tests {
|
||||
let mem =
|
||||
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();
|
||||
|
||||
@@ -356,31 +344,27 @@ mod unit_tests {
|
||||
let mem =
|
||||
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 mpc_offset = GuestAddress(mpf_intel.0.physptr as GuestUsize);
|
||||
let mpc_table: MpcTableWrapper = mem.read_obj(mpc_offset).unwrap();
|
||||
|
||||
struct Sum(u8);
|
||||
impl WriteVolatile for Sum {
|
||||
fn write_volatile<B: BitmapSlice>(
|
||||
&mut self,
|
||||
buf: &VolatileSlice<B>,
|
||||
) -> result::Result<usize, VolatileMemoryError> {
|
||||
let mut tmp = vec![0u8; buf.len()];
|
||||
tmp.write_all_volatile(buf)?;
|
||||
|
||||
for v in tmp.iter() {
|
||||
impl io::Write for Sum {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
for v in buf.iter() {
|
||||
self.0 = self.0.wrapping_add(*v);
|
||||
}
|
||||
|
||||
Ok(buf.len())
|
||||
}
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
assert_eq!(sum.0, 0);
|
||||
}
|
||||
@@ -389,12 +373,12 @@ mod unit_tests {
|
||||
fn cpu_entry_count() {
|
||||
let mem = GuestMemoryMmap::from_ranges(&[(
|
||||
MPTABLE_START,
|
||||
compute_mp_size(MAX_SUPPORTED_CPUS_LEGACY),
|
||||
compute_mp_size(MAX_SUPPORTED_CPUS as u8),
|
||||
)])
|
||||
.unwrap();
|
||||
|
||||
for i in 0..MAX_SUPPORTED_CPUS_LEGACY {
|
||||
setup_mptable(MPTABLE_START, &mem, i, None).unwrap();
|
||||
for i in 0..MAX_SUPPORTED_CPUS as u8 {
|
||||
setup_mptable(MPTABLE_START, &mem, i).unwrap();
|
||||
|
||||
let mpf_intel: MpfIntelWrapper = mem.read_obj(MPTABLE_START).unwrap();
|
||||
let mpc_offset = GuestAddress(mpf_intel.0.physptr as GuestUsize);
|
||||
@@ -423,9 +407,11 @@ mod unit_tests {
|
||||
|
||||
#[test]
|
||||
fn cpu_entry_count_max() {
|
||||
let cpus = MAX_SUPPORTED_CPUS_LEGACY + 1;
|
||||
let mem = GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(cpus))]).unwrap();
|
||||
let cpus = MAX_SUPPORTED_CPUS + 1;
|
||||
let mem =
|
||||
GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(cpus as u8))]).unwrap();
|
||||
|
||||
setup_mptable(MPTABLE_START, &mem, cpus, None).unwrap();
|
||||
let result = setup_mptable(MPTABLE_START, &mem, cpus as u8);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,57 +6,41 @@
|
||||
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
use std::{mem, result};
|
||||
|
||||
use crate::layout::{BOOT_GDT_START, BOOT_IDT_START, PVH_INFO_START};
|
||||
use crate::GuestMemoryMmap;
|
||||
use hypervisor::arch::x86::gdt::{gdt_entry, segment_from_gdt};
|
||||
use hypervisor::arch::x86::regs::CR0_PE;
|
||||
use hypervisor::arch::x86::{FpuState, SpecialRegisters};
|
||||
use thiserror::Error;
|
||||
use hypervisor::arch::x86::{FpuState, SpecialRegisters, StandardRegisters};
|
||||
use std::sync::Arc;
|
||||
use std::{mem, result};
|
||||
use vm_memory::{Address, Bytes, GuestMemory, GuestMemoryError};
|
||||
|
||||
use crate::layout::{
|
||||
BOOT_GDT_START, BOOT_IDT_START, BOOT_STACK_POINTER, PVH_INFO_START, ZERO_PAGE_START,
|
||||
};
|
||||
use crate::{EntryPoint, GuestMemoryMmap};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Failed to get SREGs for this CPU.
|
||||
#[error("Failed to get SREGs for this CPU")]
|
||||
GetStatusRegisters(#[source] hypervisor::HypervisorCpuError),
|
||||
GetStatusRegisters(hypervisor::HypervisorCpuError),
|
||||
/// Failed to set base registers for this CPU.
|
||||
#[error("Failed to set base registers for this CPU")]
|
||||
SetBaseRegisters(#[source] hypervisor::HypervisorCpuError),
|
||||
SetBaseRegisters(hypervisor::HypervisorCpuError),
|
||||
/// Failed to configure the FPU.
|
||||
#[error("Failed to configure the FPU")]
|
||||
SetFpuRegisters(#[source] hypervisor::HypervisorCpuError),
|
||||
SetFpuRegisters(hypervisor::HypervisorCpuError),
|
||||
/// Setting up MSRs failed.
|
||||
#[error("Setting up MSRs failed")]
|
||||
SetModelSpecificRegisters(#[source] hypervisor::HypervisorCpuError),
|
||||
SetModelSpecificRegisters(hypervisor::HypervisorCpuError),
|
||||
/// Failed to set SREGs for this CPU.
|
||||
#[error("Failed to set SREGs for this CPU")]
|
||||
SetStatusRegisters(#[source] hypervisor::HypervisorCpuError),
|
||||
SetStatusRegisters(hypervisor::HypervisorCpuError),
|
||||
/// Checking the GDT address failed.
|
||||
#[error("Checking the GDT address failed")]
|
||||
CheckGdtAddr,
|
||||
/// Writing the GDT to RAM failed.
|
||||
#[error("Writing the GDT to RAM failed")]
|
||||
WriteGdt(#[source] GuestMemoryError),
|
||||
WriteGdt(GuestMemoryError),
|
||||
/// Writing the IDT to RAM failed.
|
||||
#[error("Writing the IDT to RAM failed")]
|
||||
WriteIdt(#[source] GuestMemoryError),
|
||||
WriteIdt(GuestMemoryError),
|
||||
/// Writing PDPTE to RAM failed.
|
||||
#[error("Writing PDPTE to RAM failed")]
|
||||
WritePdpteAddress(#[source] GuestMemoryError),
|
||||
WritePdpteAddress(GuestMemoryError),
|
||||
/// Writing PDE to RAM failed.
|
||||
#[error("Writing PDE to RAM failed")]
|
||||
WritePdeAddress(#[source] GuestMemoryError),
|
||||
WritePdeAddress(GuestMemoryError),
|
||||
/// Writing PML4 to RAM failed.
|
||||
#[error("Writing PML4 to RAM failed")]
|
||||
WritePml4Address(#[source] GuestMemoryError),
|
||||
WritePml4Address(GuestMemoryError),
|
||||
/// Writing PML5 to RAM failed.
|
||||
#[error("Writing PML5 to RAM failed")]
|
||||
WritePml5Address(#[source] GuestMemoryError),
|
||||
WritePml5Address(GuestMemoryError),
|
||||
}
|
||||
|
||||
pub type Result<T> = result::Result<T, Error>;
|
||||
@@ -66,7 +50,7 @@ pub type Result<T> = result::Result<T, Error>;
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `vcpu` - Structure for the VCPU that holds the VCPU's fd.
|
||||
pub fn setup_fpu(vcpu: &dyn hypervisor::Vcpu) -> Result<()> {
|
||||
pub fn setup_fpu(vcpu: &Arc<dyn hypervisor::Vcpu>) -> Result<()> {
|
||||
let fpu: FpuState = FpuState {
|
||||
fcw: 0x37f,
|
||||
mxcsr: 0x1f80,
|
||||
@@ -81,8 +65,8 @@ pub fn setup_fpu(vcpu: &dyn hypervisor::Vcpu) -> Result<()> {
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `vcpu` - Structure for the VCPU that holds the VCPU's fd.
|
||||
pub fn setup_msrs(vcpu: &dyn hypervisor::Vcpu) -> Result<()> {
|
||||
vcpu.set_msrs(vcpu.boot_msr_entries())
|
||||
pub fn setup_msrs(vcpu: &Arc<dyn hypervisor::Vcpu>) -> Result<()> {
|
||||
vcpu.set_msrs(&vcpu.boot_msr_entries())
|
||||
.map_err(Error::SetModelSpecificRegisters)?;
|
||||
|
||||
Ok(())
|
||||
@@ -93,22 +77,14 @@ pub fn setup_msrs(vcpu: &dyn hypervisor::Vcpu) -> Result<()> {
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `vcpu` - Structure for the VCPU that holds the VCPU's fd.
|
||||
/// * `entry_point` - Description of the boot entry to set up.
|
||||
pub fn setup_regs(vcpu: &dyn hypervisor::Vcpu, entry_point: EntryPoint) -> Result<()> {
|
||||
let mut regs = vcpu.create_standard_regs();
|
||||
match entry_point.setup_header {
|
||||
None => {
|
||||
regs.set_rflags(0x0000000000000002u64);
|
||||
regs.set_rip(entry_point.entry_addr.raw_value());
|
||||
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());
|
||||
}
|
||||
}
|
||||
/// * `boot_ip` - Starting instruction pointer.
|
||||
pub fn setup_regs(vcpu: &Arc<dyn hypervisor::Vcpu>, boot_ip: u64) -> Result<()> {
|
||||
let regs = StandardRegisters {
|
||||
rflags: 0x0000000000000002u64,
|
||||
rbx: PVH_INFO_START.raw_value(),
|
||||
rip: boot_ip,
|
||||
..Default::default()
|
||||
};
|
||||
vcpu.set_regs(®s).map_err(Error::SetBaseRegisters)
|
||||
}
|
||||
|
||||
@@ -118,13 +94,9 @@ pub fn setup_regs(vcpu: &dyn hypervisor::Vcpu, entry_point: EntryPoint) -> Resul
|
||||
///
|
||||
/// * `mem` - The memory that will be passed to the guest.
|
||||
/// * `vcpu` - Structure for the VCPU that holds the VCPU's fd.
|
||||
pub fn setup_sregs(
|
||||
mem: &GuestMemoryMmap,
|
||||
vcpu: &dyn hypervisor::Vcpu,
|
||||
enable_x2_apic_mode: bool,
|
||||
) -> Result<()> {
|
||||
pub fn setup_sregs(mem: &GuestMemoryMmap, vcpu: &Arc<dyn hypervisor::Vcpu>) -> Result<()> {
|
||||
let mut sregs: SpecialRegisters = vcpu.get_sregs().map_err(Error::GetStatusRegisters)?;
|
||||
configure_segments_and_sregs(mem, &mut sregs, enable_x2_apic_mode)?;
|
||||
configure_segments_and_sregs(mem, &mut sregs)?;
|
||||
vcpu.set_sregs(&sregs).map_err(Error::SetStatusRegisters)
|
||||
}
|
||||
|
||||
@@ -151,7 +123,6 @@ fn write_idt_value(val: u64, guest_mem: &GuestMemoryMmap) -> Result<()> {
|
||||
pub fn configure_segments_and_sregs(
|
||||
mem: &GuestMemoryMmap,
|
||||
sregs: &mut SpecialRegisters,
|
||||
enable_x2_apic_mode: bool,
|
||||
) -> Result<()> {
|
||||
let gdt_table: [u64; BOOT_GDT_MAX] = {
|
||||
// Configure GDT entries as specified by PVH boot protocol
|
||||
@@ -187,19 +158,14 @@ pub fn configure_segments_and_sregs(
|
||||
sregs.cr0 = CR0_PE;
|
||||
sregs.cr4 = 0;
|
||||
|
||||
if enable_x2_apic_mode {
|
||||
const X2APIC_ENABLE_BIT: u64 = 1 << 10;
|
||||
sregs.apic_base |= X2APIC_ENABLE_BIT;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod unit_tests {
|
||||
use vm_memory::GuestAddress;
|
||||
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::GuestMemoryMmap;
|
||||
use vm_memory::GuestAddress;
|
||||
|
||||
fn create_guest_mem() -> GuestMemoryMmap {
|
||||
GuestMemoryMmap::from_ranges(&[(GuestAddress(0), 0x10000)]).unwrap()
|
||||
@@ -213,7 +179,7 @@ mod unit_tests {
|
||||
fn segments_and_sregs() {
|
||||
let mut sregs: SpecialRegisters = Default::default();
|
||||
let gm = create_guest_mem();
|
||||
configure_segments_and_sregs(&gm, &mut sregs, false).unwrap();
|
||||
configure_segments_and_sregs(&gm, &mut sregs).unwrap();
|
||||
assert_eq!(0x0, read_u64(&gm, BOOT_GDT_START));
|
||||
assert_eq!(
|
||||
0xcf9b000000ffff,
|
||||
|
||||
@@ -6,35 +6,53 @@
|
||||
//
|
||||
// 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::GuestMemoryMmap;
|
||||
use crate::layout::SMBIOS_START;
|
||||
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 {
|
||||
/// 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,
|
||||
/// The SMBIOS table has too little address space to be stored.
|
||||
#[error("The SMBIOS table has too little address space to be stored")]
|
||||
AddressOverflow,
|
||||
/// Failure while zeroing out the memory for the SMBIOS table.
|
||||
#[error("Failure while zeroing out the memory for the SMBIOS table")]
|
||||
Clear,
|
||||
/// Failure to write SMBIOS entrypoint structure
|
||||
#[error("Failure to write SMBIOS entrypoint structure")]
|
||||
WriteSmbiosEp,
|
||||
/// Failure to write additional data to memory
|
||||
#[error("Failure to write additional data to memory")]
|
||||
WriteData,
|
||||
/// Failure to parse uuid, uuid format may be error
|
||||
#[error("Failure to parse uuid: {1}")]
|
||||
ParseUuid(#[source] uuid::Error, String),
|
||||
ParseUuid(uuid::Error),
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {}
|
||||
|
||||
impl Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
use self::Error::*;
|
||||
|
||||
let description = match self {
|
||||
NotEnoughMemory => {
|
||||
"There was too little guest memory to store the SMBIOS table".to_string()
|
||||
}
|
||||
AddressOverflow => {
|
||||
"The SMBIOS table has too little address space to be stored".to_string()
|
||||
}
|
||||
Clear => "Failure while zeroing out the memory for the SMBIOS table".to_string(),
|
||||
WriteSmbiosEp => "Failure to write SMBIOS entrypoint structure".to_string(),
|
||||
WriteData => "Failure to write additional data to memory".to_string(),
|
||||
ParseUuid(e) => format!("Failure to parse uuid: {e}"),
|
||||
};
|
||||
|
||||
write!(f, "SMBIOS error: {description}")
|
||||
}
|
||||
}
|
||||
|
||||
pub type Result<T> = result::Result<T, Error>;
|
||||
@@ -198,7 +216,7 @@ pub fn setup_smbios(
|
||||
let uuid_number = uuid
|
||||
.map(Uuid::parse_str)
|
||||
.transpose()
|
||||
.map_err(|e| Error::ParseUuid(e, uuid.unwrap().to_string()))?
|
||||
.map_err(Error::ParseUuid)?
|
||||
.unwrap_or(Uuid::nil());
|
||||
let smbios_sysinfo = SmbiosSysInfo {
|
||||
r#type: SYSTEM_INFORMATION,
|
||||
@@ -272,7 +290,7 @@ pub fn setup_smbios(
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod unit_tests {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -1,24 +1,21 @@
|
||||
// Copyright © 2021 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
use crate::GuestMemoryMmap;
|
||||
use std::fs::File;
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
use std::str::FromStr;
|
||||
|
||||
use log::{debug, info};
|
||||
use thiserror::Error;
|
||||
use uuid::Uuid;
|
||||
use vm_memory::{ByteValued, Bytes, GuestAddress, GuestMemoryError};
|
||||
|
||||
use crate::GuestMemoryMmap;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum TdvfError {
|
||||
#[error("Failed read TDVF descriptor")]
|
||||
#[error("Failed read TDVF descriptor: {0}")]
|
||||
ReadDescriptor(#[source] std::io::Error),
|
||||
#[error("Failed read TDVF descriptor offset")]
|
||||
#[error("Failed read TDVF descriptor offset: {0}")]
|
||||
ReadDescriptorOffset(#[source] std::io::Error),
|
||||
#[error("Failed read GUID table")]
|
||||
#[error("Failed read GUID table: {0}")]
|
||||
ReadGuidTable(#[source] std::io::Error),
|
||||
#[error("Invalid descriptor signature")]
|
||||
InvalidDescriptorSignature,
|
||||
@@ -26,9 +23,9 @@ pub enum TdvfError {
|
||||
InvalidDescriptorSize,
|
||||
#[error("Invalid descriptor version")]
|
||||
InvalidDescriptorVersion,
|
||||
#[error("Failed to write HOB details to guest memory")]
|
||||
#[error("Failed to write HOB details to guest memory: {0}")]
|
||||
GuestMemoryWriteHob(#[source] GuestMemoryError),
|
||||
#[error("Failed to create Uuid")]
|
||||
#[error("Failed to create Uuid: {0}")]
|
||||
UuidCreation(#[source] uuid::Error),
|
||||
}
|
||||
|
||||
@@ -36,7 +33,7 @@ const TABLE_FOOTER_GUID: &str = "96b582de-1fb2-45f7-baea-a366c55a082d";
|
||||
const TDVF_METADATA_OFFSET_GUID: &str = "e47a6535-984a-4798-865e-4685a7bf8ec2";
|
||||
|
||||
// TDVF_DESCRIPTOR
|
||||
#[repr(C, packed)]
|
||||
#[repr(packed)]
|
||||
#[derive(Default)]
|
||||
pub struct TdvfDescriptor {
|
||||
signature: [u8; 4],
|
||||
@@ -46,7 +43,7 @@ pub struct TdvfDescriptor {
|
||||
}
|
||||
|
||||
// TDVF_SECTION
|
||||
#[repr(C, packed)]
|
||||
#[repr(packed)]
|
||||
#[derive(Clone, Copy, Default, Debug)]
|
||||
pub struct TdvfSection {
|
||||
pub data_offset: u32,
|
||||
@@ -101,7 +98,7 @@ fn tdvf_descriptor_offset(file: &mut File) -> Result<(SeekFrom, bool), TdvfError
|
||||
// We start after the footer GUID and the table length.
|
||||
let mut offset = table_size - 18;
|
||||
|
||||
debug!("Parsing GUID structure");
|
||||
debug!("Parsing GUIDed structure");
|
||||
while offset >= 18 {
|
||||
let entry_uuid = Uuid::from_slice_le(&table[offset - 16..offset])
|
||||
.map_err(TdvfError::UuidCreation)?;
|
||||
@@ -109,7 +106,7 @@ fn tdvf_descriptor_offset(file: &mut File) -> Result<(SeekFrom, bool), TdvfError
|
||||
u16::from_le_bytes(table[offset - 18..offset - 16].try_into().unwrap()) as usize;
|
||||
debug!(
|
||||
"Entry GUID = {}, size = {}",
|
||||
entry_uuid.hyphenated(),
|
||||
entry_uuid.hyphenated().to_string(),
|
||||
entry_size
|
||||
);
|
||||
|
||||
@@ -210,7 +207,7 @@ enum HobType {
|
||||
EndOfHobList = 0xffff,
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Default, Debug)]
|
||||
struct HobHeader {
|
||||
r#type: HobType,
|
||||
@@ -218,7 +215,7 @@ struct HobHeader {
|
||||
reserved: u32,
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Default, Debug)]
|
||||
struct HobHandoffInfoTable {
|
||||
header: HobHeader,
|
||||
@@ -231,7 +228,7 @@ struct HobHandoffInfoTable {
|
||||
efi_end_of_hob_list: u64,
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Default, Debug)]
|
||||
struct EfiGuid {
|
||||
data1: u32,
|
||||
@@ -240,7 +237,7 @@ struct EfiGuid {
|
||||
data4: [u8; 8],
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Default, Debug)]
|
||||
struct HobResourceDescriptor {
|
||||
header: HobHeader,
|
||||
@@ -251,7 +248,7 @@ struct HobResourceDescriptor {
|
||||
resource_length: u64,
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Default, Debug)]
|
||||
struct HobGuidType {
|
||||
header: HobHeader,
|
||||
@@ -267,14 +264,14 @@ pub enum PayloadImageType {
|
||||
RawVmLinux,
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Default, Debug)]
|
||||
pub struct PayloadInfo {
|
||||
pub image_type: PayloadImageType,
|
||||
pub entry_point: u64,
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Default, Debug)]
|
||||
struct TdPayload {
|
||||
guid_type: HobGuidType,
|
||||
@@ -300,12 +297,12 @@ pub struct TdHob {
|
||||
}
|
||||
|
||||
fn align_hob(v: u64) -> u64 {
|
||||
v.div_ceil(8) * 8
|
||||
(v + 7) / 8 * 8
|
||||
}
|
||||
|
||||
impl TdHob {
|
||||
fn update_offset<T>(&mut self) {
|
||||
self.current_offset = align_hob(self.current_offset + std::mem::size_of::<T>() as u64);
|
||||
self.current_offset = align_hob(self.current_offset + std::mem::size_of::<T>() as u64)
|
||||
}
|
||||
|
||||
pub fn start(offset: u64) -> TdHob {
|
||||
@@ -520,7 +517,7 @@ impl TdHob {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod unit_tests {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
@@ -529,7 +526,7 @@ mod unit_tests {
|
||||
let mut f = std::fs::File::open("tdvf.fd").unwrap();
|
||||
let (sections, _) = parse_tdvf_sections(&mut f).unwrap();
|
||||
for section in sections {
|
||||
eprintln!("{section:x?}");
|
||||
eprintln!("{section:x?}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
[package]
|
||||
authors = ["The Chromium OS Authors", "The Cloud Hypervisor Authors"]
|
||||
edition.workspace = true
|
||||
name = "block"
|
||||
version = "0.1.0"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
io_uring = ["dep:io-uring"]
|
||||
|
||||
[dependencies]
|
||||
bitflags = { workspace = true }
|
||||
byteorder = { workspace = true }
|
||||
crc-any = "2.5.0"
|
||||
flate2 = "1.1"
|
||||
io-uring = { version = "0.7.11", optional = true }
|
||||
libc = { workspace = true }
|
||||
log = { workspace = true }
|
||||
remain = "0.2.15"
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
smallvec = "1.15.1"
|
||||
thiserror = { workspace = true }
|
||||
uuid = { workspace = true, features = ["v4"] }
|
||||
virtio-bindings = { workspace = true }
|
||||
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 }
|
||||
zstd = "0.13"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -1,148 +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::{BatchRequest, DiskTopology};
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum DiskFileError {
|
||||
/// Failed getting disk file size.
|
||||
#[error("Failed getting disk file size")]
|
||||
Size(#[source] std::io::Error),
|
||||
/// Failed creating a new AsyncIo.
|
||||
#[error("Failed creating a new AsyncIo")]
|
||||
NewAsyncIo(#[source] std::io::Error),
|
||||
/// Unsupported operation.
|
||||
#[error("Unsupported operation")]
|
||||
Unsupported,
|
||||
/// Resize failed
|
||||
#[error("Resize failed")]
|
||||
ResizeError(#[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 {
|
||||
/// Returns the logical disk size a guest will see.
|
||||
///
|
||||
/// For raw formats, this is equal to [`Self::physical_size`]. For file formats
|
||||
/// that wrap disk images in a container (e.g. QCOW2), this refers to the
|
||||
/// effective size that the guest will see.
|
||||
fn logical_size(&mut self) -> DiskFileResult<u64>;
|
||||
/// Returns the physical size of the underlying file.
|
||||
fn physical_size(&mut self) -> DiskFileResult<u64>;
|
||||
fn new_async_io(&self, ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>>;
|
||||
fn topology(&mut self) -> DiskTopology {
|
||||
DiskTopology::default()
|
||||
}
|
||||
fn resize(&mut self, _size: u64) -> DiskFileResult<()> {
|
||||
Err(DiskFileError::Unsupported)
|
||||
}
|
||||
|
||||
/// Indicates support for sparse operations (punch hole, write zeroes, discard).
|
||||
/// Override to return true when supported.
|
||||
fn supports_sparse_operations(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Indicates support for zero flag optimization in WRITE_ZEROES. Override
|
||||
/// to return true when supported.
|
||||
fn supports_zero_flag(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// 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")]
|
||||
ReadVectored(#[source] std::io::Error),
|
||||
/// Failed vectored writing to file.
|
||||
#[error("Failed vectored writing to file")]
|
||||
WriteVectored(#[source] std::io::Error),
|
||||
/// Failed synchronizing file.
|
||||
#[error("Failed synchronizing file")]
|
||||
Fsync(#[source] std::io::Error),
|
||||
/// Failed punching hole.
|
||||
#[error("Failed punching hole")]
|
||||
PunchHole(#[source] std::io::Error),
|
||||
/// Failed writing zeroes.
|
||||
#[error("Failed writing zeroes")]
|
||||
WriteZeroes(#[source] std::io::Error),
|
||||
/// Failed submitting batch requests.
|
||||
#[error("Failed submitting batch requests")]
|
||||
SubmitBatchRequests(#[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 punch_hole(&mut self, offset: u64, length: u64, user_data: u64) -> AsyncIoResult<()>;
|
||||
fn write_zeroes(&mut self, offset: u64, length: u64, user_data: u64) -> AsyncIoResult<()>;
|
||||
fn next_completed_request(&mut self) -> Option<(u64, i32)>;
|
||||
fn batch_requests_enabled(&self) -> bool {
|
||||
false
|
||||
}
|
||||
fn submit_batch_requests(&mut self, _batch_request: &[BatchRequest]) -> AsyncIoResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,222 +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")]
|
||||
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}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The granularity of the advisory lock.
|
||||
///
|
||||
/// The granularity has significant implications in typical cloud deployments
|
||||
/// with network storage. The Linux kernel will sync advisory locks to network
|
||||
/// file systems, but these backends may have different policies and handle
|
||||
/// locks differently. For example, Netapp speaks a NFS API but will treat
|
||||
/// advisory OFD locks for the whole file as mandatory locks, whereas byte-range
|
||||
/// locks for the whole file will remain advisory [0].
|
||||
///
|
||||
/// As it is a valid use case to prevent multiple CHV instances from accessing
|
||||
/// the same disk but disk management software (e.g., Cinder in OpenStack)
|
||||
/// should be able to snapshot disks while VMs are running, we need special
|
||||
/// control over the lock granularity. Therefore, it is a valid use case to lock
|
||||
/// the whole byte range of a disk image without technically locking the whole
|
||||
/// file - to get the best of both worlds.
|
||||
///
|
||||
/// [0] https://kb.netapp.com/on-prem/ontap/da/NAS/NAS-KBs/How_is_Mandatory_Locking_supported_for_NFSv4_on_ONTAP_9
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum LockGranularity {
|
||||
WholeFile,
|
||||
ByteRange(u64 /* from, inclusive */, u64 /* len */),
|
||||
}
|
||||
|
||||
impl LockGranularity {
|
||||
const fn l_start(self) -> u64 {
|
||||
match self {
|
||||
LockGranularity::WholeFile => 0,
|
||||
LockGranularity::ByteRange(start, _) => start,
|
||||
}
|
||||
}
|
||||
|
||||
const fn l_len(self) -> u64 {
|
||||
match self {
|
||||
LockGranularity::WholeFile => 0, /* EOF */
|
||||
LockGranularity::ByteRange(_, len) => len,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a [`struct@libc::flock`] structure for the whole file.
|
||||
const fn get_flock(lock_type: LockType, granularity: LockGranularity) -> 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: granularity.l_start() as libc::c_long,
|
||||
l_len: granularity.l_len() as libc::c_long,
|
||||
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`]
|
||||
/// - `granularity`: The [`LockGranularity`].
|
||||
pub fn try_acquire_lock<Fd: AsRawFd>(
|
||||
file: &Fd,
|
||||
lock_type: LockType,
|
||||
granularity: LockGranularity,
|
||||
) -> Result<(), LockError> {
|
||||
let flock = get_flock(lock_type, granularity);
|
||||
|
||||
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`].
|
||||
/// - `granularity`: The [`LockGranularity`].
|
||||
pub fn clear_lock<Fd: AsRawFd>(file: &Fd, granularity: LockGranularity) -> Result<(), LockError> {
|
||||
try_acquire_lock(file, LockType::Unlock, granularity)
|
||||
}
|
||||
|
||||
/// Returns the current lock state using [`fcntl`] with respect to the given
|
||||
/// parameters.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `file`: The file for which to get the lock state.
|
||||
/// - `granularity`: The [`LockGranularity`].
|
||||
pub fn get_lock_state<Fd: AsRawFd>(
|
||||
file: &Fd,
|
||||
granularity: LockGranularity,
|
||||
) -> Result<LockState, LockError> {
|
||||
let mut flock = get_flock(LockType::Write, granularity);
|
||||
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,99 +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::BlockBackend;
|
||||
use crate::vhd::VhdFooter;
|
||||
|
||||
#[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 logical_size(&self) -> Result<u64, crate::Error> {
|
||||
Ok(self.size)
|
||||
}
|
||||
|
||||
/// Returns the physical size of the underlying file.
|
||||
fn physical_size(&self) -> Result<u64, crate::Error> {
|
||||
self.file
|
||||
.metadata()
|
||||
.map(|m| m.len())
|
||||
.map_err(crate::Error::GetFileMetadata)
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for FixedVhd {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
file: self.file.try_clone().expect("FixedVhd cloning failed"),
|
||||
size: self.size,
|
||||
position: self.position,
|
||||
}
|
||||
}
|
||||
}
|
||||
1189
block/src/lib.rs
1189
block/src/lib.rs
File diff suppressed because it is too large
Load Diff
@@ -1,87 +0,0 @@
|
||||
// Copyright 2025 The Cloud Hypervisor Authors. All rights reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
#[error("Zlib decompress error")]
|
||||
ZlibDecompress(#[source] flate2::DecompressError),
|
||||
#[error("Zlib unexpected status: {0:?}")]
|
||||
ZlibUnexpectedStatus(flate2::Status),
|
||||
#[error("Zstd decompress error")]
|
||||
ZstdDecompress(#[source] std::io::Error),
|
||||
#[error("Zstd: failed to fill buffer")]
|
||||
ZstdFillBuffer(#[source] std::io::Error),
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
/// Generic trait for decoding zlib/zstd formats
|
||||
pub trait Decoder {
|
||||
fn decode(&self, input: &[u8], output: &mut [u8]) -> Result<usize>;
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ZlibDecoder {}
|
||||
|
||||
impl Decoder for ZlibDecoder {
|
||||
fn decode(&self, input: &[u8], output: &mut [u8]) -> Result<usize> {
|
||||
use flate2::{Decompress, FlushDecompress, Status};
|
||||
|
||||
let mut decompressor = Decompress::new(false);
|
||||
let status = decompressor
|
||||
.decompress(input, output, FlushDecompress::Finish)
|
||||
.map_err(Error::ZlibDecompress)?;
|
||||
if status == Status::StreamEnd {
|
||||
Ok(decompressor.total_out() as usize)
|
||||
} else {
|
||||
Err(Error::ZlibUnexpectedStatus(status))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ZstdDecoder {}
|
||||
|
||||
impl Decoder for ZstdDecoder {
|
||||
fn decode(&self, input: &[u8], output: &mut [u8]) -> Result<usize> {
|
||||
use std::io::Read;
|
||||
|
||||
let mut decoder = zstd::stream::read::Decoder::new(input).map_err(Error::ZstdDecompress)?;
|
||||
let decoded_size = decoder.read(output).map_err(Error::ZstdFillBuffer)?;
|
||||
Ok(decoded_size)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_zlib_decode() {
|
||||
let d = ZlibDecoder::default();
|
||||
let valid_input = vec![99, 96, 100, 98, 6, 0];
|
||||
let mut output1 = vec![0; 4];
|
||||
d.decode(&valid_input, &mut output1).unwrap();
|
||||
assert_eq!(&output1, b"\x00\x01\x02\x03");
|
||||
|
||||
let invalid_input = vec![1, 2, 3, 4];
|
||||
let mut output2 = vec![0; 1024];
|
||||
d.decode(&invalid_input, &mut output2).unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_zstd_decode() {
|
||||
let d = ZstdDecoder::default();
|
||||
let valid_input = vec![40, 181, 47, 253, 32, 2, 17, 0, 0, 1, 254];
|
||||
let mut output1 = vec![0; 2];
|
||||
d.decode(&valid_input, &mut output1).unwrap();
|
||||
assert_eq!(&output1, b"\x01\xfe");
|
||||
|
||||
let invalid_input = vec![1, 2, 3, 4];
|
||||
let mut output2 = vec![0; 1024];
|
||||
d.decode(&invalid_input, &mut output2).unwrap_err();
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,363 +0,0 @@
|
||||
// Copyright 2018 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||
|
||||
use std::fmt::Debug;
|
||||
use std::io::{self, BufWriter, Read, Seek, SeekFrom, Write};
|
||||
use std::mem::size_of;
|
||||
use std::os::fd::{AsRawFd, RawFd};
|
||||
|
||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use vmm_sys_util::write_zeroes::WriteZeroes;
|
||||
|
||||
use super::RawFile;
|
||||
|
||||
// Type aliases for the refcount read/write function pointers
|
||||
type RefcountReader = fn(&mut RawFile, usize) -> io::Result<Vec<u64>>;
|
||||
type RefcountWriter = fn(&mut RawFile, &[u64]) -> io::Result<()>;
|
||||
|
||||
/// Big-endian file access trait.
|
||||
pub(super) trait BeUint: Sized + Copy {
|
||||
fn from_be_slice(bytes: &[u8]) -> u64;
|
||||
fn read_be<R: Read>(r: &mut R) -> io::Result<Self>;
|
||||
fn write_be<W: Write>(w: &mut W, val: Self) -> io::Result<()>;
|
||||
}
|
||||
|
||||
impl BeUint for u8 {
|
||||
#[inline(always)]
|
||||
fn from_be_slice(bytes: &[u8]) -> u64 {
|
||||
bytes[0] as u64
|
||||
}
|
||||
#[inline(always)]
|
||||
fn read_be<R: Read>(r: &mut R) -> io::Result<Self> {
|
||||
r.read_u8()
|
||||
}
|
||||
#[inline(always)]
|
||||
fn write_be<W: Write>(w: &mut W, val: Self) -> io::Result<()> {
|
||||
w.write_u8(val)
|
||||
}
|
||||
}
|
||||
|
||||
impl BeUint for u16 {
|
||||
#[inline(always)]
|
||||
fn from_be_slice(bytes: &[u8]) -> u64 {
|
||||
u16::from_be_bytes([bytes[0], bytes[1]]) as u64
|
||||
}
|
||||
#[inline(always)]
|
||||
fn read_be<R: Read>(r: &mut R) -> io::Result<Self> {
|
||||
r.read_u16::<BigEndian>()
|
||||
}
|
||||
#[inline(always)]
|
||||
fn write_be<W: Write>(w: &mut W, val: Self) -> io::Result<()> {
|
||||
w.write_u16::<BigEndian>(val)
|
||||
}
|
||||
}
|
||||
|
||||
impl BeUint for u32 {
|
||||
#[inline(always)]
|
||||
fn from_be_slice(bytes: &[u8]) -> u64 {
|
||||
u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) as u64
|
||||
}
|
||||
#[inline(always)]
|
||||
fn read_be<R: Read>(r: &mut R) -> io::Result<Self> {
|
||||
r.read_u32::<BigEndian>()
|
||||
}
|
||||
#[inline(always)]
|
||||
fn write_be<W: Write>(w: &mut W, val: Self) -> io::Result<()> {
|
||||
w.write_u32::<BigEndian>(val)
|
||||
}
|
||||
}
|
||||
|
||||
impl BeUint for u64 {
|
||||
#[inline(always)]
|
||||
fn from_be_slice(bytes: &[u8]) -> u64 {
|
||||
u64::from_be_bytes([
|
||||
bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
|
||||
])
|
||||
}
|
||||
#[inline(always)]
|
||||
fn read_be<R: Read>(r: &mut R) -> io::Result<Self> {
|
||||
r.read_u64::<BigEndian>()
|
||||
}
|
||||
#[inline(always)]
|
||||
fn write_be<W: Write>(w: &mut W, val: Self) -> io::Result<()> {
|
||||
w.write_u64::<BigEndian>(val)
|
||||
}
|
||||
}
|
||||
|
||||
/// Read byte-aligned refcounts.
|
||||
fn read_refcount<T: BeUint>(file: &mut RawFile, count: usize) -> io::Result<Vec<u64>> {
|
||||
let bytes_per_entry = size_of::<T>();
|
||||
let mut data = vec![0u8; count * bytes_per_entry];
|
||||
file.read_exact(&mut data)?;
|
||||
Ok(data
|
||||
.chunks_exact(bytes_per_entry)
|
||||
.map(T::from_be_slice)
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// Write byte-aligned refcounts.
|
||||
fn write_refcount<T: BeUint + TryFrom<u64>>(file: &mut RawFile, table: &[u64]) -> io::Result<()>
|
||||
where
|
||||
<T as TryFrom<u64>>::Error: Debug,
|
||||
{
|
||||
let bytes_per_entry = size_of::<T>();
|
||||
let mut buffer = BufWriter::with_capacity(table.len() * bytes_per_entry, file);
|
||||
for &val in table {
|
||||
let converted = T::try_from(val).expect("refcount values are validated on increment");
|
||||
T::write_be(&mut buffer, converted)?;
|
||||
}
|
||||
buffer.flush()
|
||||
}
|
||||
|
||||
/// Read sub-byte refcounts. Bit 0 is the least significant bit.
|
||||
fn read_refcount_subbyte<const BITS: usize>(
|
||||
file: &mut RawFile,
|
||||
count: usize,
|
||||
) -> io::Result<Vec<u64>> {
|
||||
const { assert!(BITS == 1 || BITS == 2 || BITS == 4) };
|
||||
let entries_per_byte = 8 / BITS;
|
||||
let mask = (1u64 << BITS) - 1;
|
||||
let bytes_needed = count.div_ceil(entries_per_byte);
|
||||
let mut bytes = vec![0u8; bytes_needed];
|
||||
file.read_exact(&mut bytes)?;
|
||||
|
||||
let mut table = vec![0u64; count];
|
||||
for (i, val) in table.iter_mut().enumerate() {
|
||||
let byte_idx = i / entries_per_byte;
|
||||
let bit_offset = (i % entries_per_byte) * BITS;
|
||||
*val = (bytes[byte_idx] as u64 >> bit_offset) & mask;
|
||||
}
|
||||
Ok(table)
|
||||
}
|
||||
|
||||
/// Write sub-byte refcounts. Bit 0 is the least significant bit.
|
||||
fn write_refcount_subbyte<const BITS: usize>(file: &mut RawFile, table: &[u64]) -> io::Result<()> {
|
||||
const { assert!(BITS == 1 || BITS == 2 || BITS == 4) };
|
||||
let entries_per_byte = 8 / BITS;
|
||||
let mask = (1u64 << BITS) - 1;
|
||||
let mut buffer = BufWriter::with_capacity(table.len().div_ceil(entries_per_byte), file);
|
||||
|
||||
for chunk in table.chunks(entries_per_byte) {
|
||||
let mut byte = 0u8;
|
||||
for (i, &val) in chunk.iter().enumerate() {
|
||||
let bit_offset = i * BITS;
|
||||
byte |= ((val & mask) << bit_offset) as u8;
|
||||
}
|
||||
buffer.write_u8(byte)?;
|
||||
}
|
||||
buffer.flush()
|
||||
}
|
||||
|
||||
/// A qcow file. Allows reading/writing clusters and appending clusters.
|
||||
#[derive(Debug)]
|
||||
pub struct QcowRawFile {
|
||||
file: RawFile,
|
||||
cluster_size: u64,
|
||||
cluster_mask: u64,
|
||||
refcount_block_entries: u64,
|
||||
read_refcount_fn: RefcountReader,
|
||||
write_refcount_fn: RefcountWriter,
|
||||
}
|
||||
|
||||
impl QcowRawFile {
|
||||
/// Creates a `QcowRawFile` from the given `File`, `None` is returned if `cluster_size` is not
|
||||
/// a power of two or refcount_bits is invalid.
|
||||
pub fn from(file: RawFile, cluster_size: u64, refcount_bits: u64) -> Option<Self> {
|
||||
if !cluster_size.is_power_of_two() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let (read_refcount_fn, write_refcount_fn): (RefcountReader, RefcountWriter) =
|
||||
match refcount_bits {
|
||||
1 => (read_refcount_subbyte::<1>, write_refcount_subbyte::<1>),
|
||||
2 => (read_refcount_subbyte::<2>, write_refcount_subbyte::<2>),
|
||||
4 => (read_refcount_subbyte::<4>, write_refcount_subbyte::<4>),
|
||||
8 => (read_refcount::<u8>, write_refcount::<u8>),
|
||||
16 => (read_refcount::<u16>, write_refcount::<u16>),
|
||||
32 => (read_refcount::<u32>, write_refcount::<u32>),
|
||||
64 => (read_refcount::<u64>, write_refcount::<u64>),
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
// For sub-byte refcounts (1,2,4 bits), entries pack multiple per byte
|
||||
let refcount_block_entries = cluster_size * 8 / refcount_bits;
|
||||
|
||||
Some(QcowRawFile {
|
||||
file,
|
||||
cluster_size,
|
||||
cluster_mask: cluster_size - 1,
|
||||
refcount_block_entries,
|
||||
read_refcount_fn,
|
||||
write_refcount_fn,
|
||||
})
|
||||
}
|
||||
|
||||
/// Reads `count` 64 bit offsets and returns them as a vector.
|
||||
/// `mask` optionally `&`s out some of the bits on the file.
|
||||
pub fn read_pointer_table(
|
||||
&mut self,
|
||||
offset: u64,
|
||||
count: u64,
|
||||
mask: Option<u64>,
|
||||
) -> io::Result<Vec<u64>> {
|
||||
let mut table = vec![0; count as usize];
|
||||
self.file.seek(SeekFrom::Start(offset))?;
|
||||
self.file.read_u64_into::<BigEndian>(&mut table)?;
|
||||
if let Some(m) = mask {
|
||||
for ptr in &mut table {
|
||||
*ptr &= m;
|
||||
}
|
||||
}
|
||||
Ok(table)
|
||||
}
|
||||
|
||||
/// Reads a cluster's worth of 64 bit offsets and returns them as a vector.
|
||||
/// `mask` optionally `&`s out some of the bits on the file.
|
||||
pub fn read_pointer_cluster(&mut self, offset: u64, mask: Option<u64>) -> io::Result<Vec<u64>> {
|
||||
let count = self.cluster_size / size_of::<u64>() as u64;
|
||||
self.read_pointer_table(offset, count, mask)
|
||||
}
|
||||
|
||||
/// Internal helper for creating a buffered writer for pointer tables.
|
||||
#[inline]
|
||||
fn setup_pointer_table_writer<T>(
|
||||
&mut self,
|
||||
offset: u64,
|
||||
entries: &impl Iterator<Item = T>,
|
||||
) -> io::Result<BufWriter<RawFile>> {
|
||||
self.file.seek(SeekFrom::Start(offset))?;
|
||||
let my_file = self.file.try_clone()?;
|
||||
let capacity = entries.size_hint().0 * size_of::<u64>();
|
||||
Ok(BufWriter::with_capacity(capacity, my_file))
|
||||
}
|
||||
|
||||
/// Writes a pointer table to `offset` in the file.
|
||||
/// Entries are computed on-the-fly by the callback.
|
||||
pub fn write_pointer_table<'a, T: Copy + 'a>(
|
||||
&mut self,
|
||||
offset: u64,
|
||||
entries: impl Iterator<Item = &'a T>,
|
||||
mut f: impl FnMut(&mut QcowRawFile, T) -> io::Result<u64>,
|
||||
) -> io::Result<()> {
|
||||
let mut buffer = self.setup_pointer_table_writer(offset, &entries)?;
|
||||
|
||||
for addr in entries {
|
||||
let entry = f(self, *addr)?;
|
||||
u64::write_be(&mut buffer, entry)?;
|
||||
}
|
||||
buffer.flush()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Writes a pointer table directly without transforming values.
|
||||
pub fn write_pointer_table_direct<'a>(
|
||||
&mut self,
|
||||
offset: u64,
|
||||
entries: impl Iterator<Item = &'a u64>,
|
||||
) -> io::Result<()> {
|
||||
let mut buffer = self.setup_pointer_table_writer(offset, &entries)?;
|
||||
|
||||
for &entry in entries {
|
||||
u64::write_be(&mut buffer, entry)?;
|
||||
}
|
||||
buffer.flush()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Read a refcount block from the file and returns a Vec containing the block.
|
||||
/// Always returns a cluster's worth of data.
|
||||
#[inline]
|
||||
pub fn read_refcount_block(&mut self, offset: u64) -> io::Result<Vec<u64>> {
|
||||
self.file.seek(SeekFrom::Start(offset))?;
|
||||
(self.read_refcount_fn)(&mut self.file, self.refcount_block_entries as usize)
|
||||
}
|
||||
|
||||
/// Writes a refcount block to the file.
|
||||
#[inline]
|
||||
pub fn write_refcount_block(&mut self, offset: u64, table: &[u64]) -> io::Result<()> {
|
||||
self.file.seek(SeekFrom::Start(offset))?;
|
||||
(self.write_refcount_fn)(&mut self.file, table)
|
||||
}
|
||||
|
||||
/// Allocates a new cluster at the end of the current file, return the address.
|
||||
pub fn add_cluster_end(&mut self, max_valid_cluster_offset: u64) -> io::Result<Option<u64>> {
|
||||
// Determine where the new end of the file should be and set_len, which
|
||||
// translates to truncate(2).
|
||||
let file_end: u64 = self.file.seek(SeekFrom::End(0))?;
|
||||
let new_cluster_address: u64 = (file_end + self.cluster_size - 1) & !self.cluster_mask;
|
||||
|
||||
if new_cluster_address > max_valid_cluster_offset {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
self.file.set_len(new_cluster_address + self.cluster_size)?;
|
||||
|
||||
Ok(Some(new_cluster_address))
|
||||
}
|
||||
|
||||
/// Returns a reference to the underlying file.
|
||||
pub fn file(&self) -> &RawFile {
|
||||
&self.file
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the underlying file.
|
||||
pub fn file_mut(&mut self) -> &mut RawFile {
|
||||
&mut self.file
|
||||
}
|
||||
|
||||
/// Returns the size of the file's clusters.
|
||||
pub fn cluster_size(&self) -> u64 {
|
||||
self.cluster_size
|
||||
}
|
||||
|
||||
/// Returns the offset of `address` within a cluster.
|
||||
pub fn cluster_offset(&self, address: u64) -> u64 {
|
||||
address & self.cluster_mask
|
||||
}
|
||||
|
||||
/// Returns the base address of the cluster containing `address`.
|
||||
pub fn cluster_address(&self, address: u64) -> u64 {
|
||||
address & !self.cluster_mask
|
||||
}
|
||||
|
||||
/// Zeros out a cluster in the file.
|
||||
pub fn zero_cluster(&mut self, address: u64) -> io::Result<()> {
|
||||
let cluster_size = self.cluster_size as usize;
|
||||
self.file.seek(SeekFrom::Start(address))?;
|
||||
self.file.write_zeroes(cluster_size)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Writes
|
||||
pub fn write_cluster(&mut self, address: u64, data: &[u8]) -> io::Result<()> {
|
||||
let cluster_size = self.cluster_size as usize;
|
||||
self.file.seek(SeekFrom::Start(address))?;
|
||||
self.file.write_all(&data[0..cluster_size])
|
||||
}
|
||||
|
||||
pub fn physical_size(&self) -> Result<u64, std::io::Error> {
|
||||
self.file.metadata().map(|m| m.len())
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for QcowRawFile {
|
||||
fn clone(&self) -> Self {
|
||||
QcowRawFile {
|
||||
file: self.file.try_clone().expect("QcowRawFile cloning failed"),
|
||||
cluster_size: self.cluster_size,
|
||||
cluster_mask: self.cluster_mask,
|
||||
refcount_block_entries: self.refcount_block_entries,
|
||||
read_refcount_fn: self.read_refcount_fn,
|
||||
write_refcount_fn: self.write_refcount_fn,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRawFd for QcowRawFile {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.file.as_raw_fd()
|
||||
}
|
||||
}
|
||||
@@ -1,504 +0,0 @@
|
||||
// Copyright © 2021 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||
|
||||
use std::collections::VecDeque;
|
||||
use std::fs::File;
|
||||
use std::io::{self, Seek, SeekFrom};
|
||||
use std::os::fd::AsRawFd;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
use vmm_sys_util::write_zeroes::PunchHole;
|
||||
|
||||
use crate::async_io::{
|
||||
AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult,
|
||||
};
|
||||
use crate::qcow::{Error as QcowError, MAX_NESTING_DEPTH, QcowFile, RawFile, Result as QcowResult};
|
||||
use crate::{AsyncAdaptor, BlockBackend};
|
||||
|
||||
pub struct QcowDiskSync {
|
||||
// FIXME: The Mutex serializes all QCOW2 I/O operations across queues, which
|
||||
// is necessary for correctness but eliminates any parallelism benefit from
|
||||
// multiqueue. QcowFile has internal mutable state (L2 cache, refcounts, file
|
||||
// position) that is not safe to share across threads via Clone.
|
||||
//
|
||||
// A proper fix would require restructuring QcowFile to separate metadata
|
||||
// operations (which need synchronization) from data I/O (which could be
|
||||
// parallelized with per queue file descriptors). See #7560 for details.
|
||||
qcow_file: Arc<Mutex<QcowFile>>,
|
||||
}
|
||||
|
||||
impl QcowDiskSync {
|
||||
pub fn new(file: File, direct_io: bool, backing_files: bool, sparse: bool) -> QcowResult<Self> {
|
||||
let max_nesting_depth = if backing_files { MAX_NESTING_DEPTH } else { 0 };
|
||||
let qcow_file = QcowFile::from_with_nesting_depth(
|
||||
RawFile::new(file, direct_io),
|
||||
max_nesting_depth,
|
||||
sparse,
|
||||
)
|
||||
.map_err(|e| match e {
|
||||
QcowError::MaxNestingDepthExceeded if !backing_files => QcowError::BackingFilesDisabled,
|
||||
other => other,
|
||||
})?;
|
||||
Ok(QcowDiskSync {
|
||||
qcow_file: Arc::new(Mutex::new(qcow_file)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl DiskFile for QcowDiskSync {
|
||||
fn logical_size(&mut self) -> DiskFileResult<u64> {
|
||||
self.qcow_file
|
||||
.lock()
|
||||
.unwrap()
|
||||
.seek(SeekFrom::End(0))
|
||||
.map_err(DiskFileError::Size)
|
||||
}
|
||||
|
||||
fn physical_size(&mut self) -> DiskFileResult<u64> {
|
||||
self.qcow_file.lock().unwrap().physical_size().map_err(|e| {
|
||||
let io_inner = match e {
|
||||
crate::Error::GetFileMetadata(e) => e,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
DiskFileError::Size(io_inner)
|
||||
})
|
||||
}
|
||||
|
||||
fn new_async_io(&self, _ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
|
||||
Ok(Box::new(QcowSync::new(Arc::clone(&self.qcow_file))) as Box<dyn AsyncIo>)
|
||||
}
|
||||
|
||||
fn resize(&mut self, size: u64) -> DiskFileResult<()> {
|
||||
self.qcow_file
|
||||
.lock()
|
||||
.unwrap()
|
||||
.resize(size)
|
||||
.map_err(|e| DiskFileError::ResizeError(io::Error::other(e)))
|
||||
}
|
||||
|
||||
fn supports_sparse_operations(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn supports_zero_flag(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn fd(&mut self) -> BorrowedDiskFd<'_> {
|
||||
BorrowedDiskFd::new(self.qcow_file.lock().unwrap().as_raw_fd())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct QcowSync {
|
||||
qcow_file: Arc<Mutex<QcowFile>>,
|
||||
eventfd: EventFd,
|
||||
completion_list: VecDeque<(u64, i32)>,
|
||||
}
|
||||
|
||||
impl QcowSync {
|
||||
pub fn new(qcow_file: Arc<Mutex<QcowFile>>) -> Self {
|
||||
QcowSync {
|
||||
qcow_file,
|
||||
eventfd: EventFd::new(libc::EFD_NONBLOCK)
|
||||
.expect("Failed creating EventFd for QcowSync"),
|
||||
completion_list: VecDeque::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncAdaptor for QcowFile {}
|
||||
|
||||
impl AsyncIo for QcowSync {
|
||||
fn notifier(&self) -> &EventFd {
|
||||
&self.eventfd
|
||||
}
|
||||
|
||||
fn read_vectored(
|
||||
&mut self,
|
||||
offset: libc::off_t,
|
||||
iovecs: &[libc::iovec],
|
||||
user_data: u64,
|
||||
) -> AsyncIoResult<()> {
|
||||
self.qcow_file.lock().unwrap().read_vectored_sync(
|
||||
offset,
|
||||
iovecs,
|
||||
user_data,
|
||||
&self.eventfd,
|
||||
&mut self.completion_list,
|
||||
)
|
||||
}
|
||||
|
||||
fn write_vectored(
|
||||
&mut self,
|
||||
offset: libc::off_t,
|
||||
iovecs: &[libc::iovec],
|
||||
user_data: u64,
|
||||
) -> AsyncIoResult<()> {
|
||||
self.qcow_file.lock().unwrap().write_vectored_sync(
|
||||
offset,
|
||||
iovecs,
|
||||
user_data,
|
||||
&self.eventfd,
|
||||
&mut self.completion_list,
|
||||
)
|
||||
}
|
||||
|
||||
fn fsync(&mut self, user_data: Option<u64>) -> AsyncIoResult<()> {
|
||||
self.qcow_file.lock().unwrap().fsync_sync(
|
||||
user_data,
|
||||
&self.eventfd,
|
||||
&mut self.completion_list,
|
||||
)
|
||||
}
|
||||
|
||||
fn next_completed_request(&mut self) -> Option<(u64, i32)> {
|
||||
self.completion_list.pop_front()
|
||||
}
|
||||
|
||||
fn punch_hole(&mut self, offset: u64, length: u64, user_data: u64) -> AsyncIoResult<()> {
|
||||
// For QCOW2, punch_hole calls deallocate_cluster
|
||||
let result = self
|
||||
.qcow_file
|
||||
.lock()
|
||||
.unwrap()
|
||||
.punch_hole(offset, length)
|
||||
.map(|_| 0i32)
|
||||
.map_err(AsyncIoError::PunchHole);
|
||||
|
||||
match result {
|
||||
Ok(res) => {
|
||||
self.completion_list.push_back((user_data, res));
|
||||
self.eventfd.write(1).unwrap();
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
// CRITICAL: Always signal completion even on error to avoid hangs
|
||||
let errno = if let AsyncIoError::PunchHole(io_err) = &e {
|
||||
let err = io_err.raw_os_error().unwrap_or(libc::EIO);
|
||||
-err
|
||||
} else {
|
||||
-libc::EIO
|
||||
};
|
||||
self.completion_list.push_back((user_data, errno));
|
||||
self.eventfd.write(1).unwrap();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn write_zeroes(&mut self, offset: u64, length: u64, user_data: u64) -> AsyncIoResult<()> {
|
||||
// For QCOW2, write_zeroes is implemented by deallocating clusters via punch_hole.
|
||||
// This is more efficient than writing actual zeros and reduces disk usage.
|
||||
// Unallocated clusters inherently read as zero in the QCOW2 format.
|
||||
let result = self
|
||||
.qcow_file
|
||||
.lock()
|
||||
.unwrap()
|
||||
.punch_hole(offset, length)
|
||||
.map(|_| 0i32)
|
||||
.map_err(AsyncIoError::WriteZeroes);
|
||||
|
||||
match result {
|
||||
Ok(res) => {
|
||||
self.completion_list.push_back((user_data, res));
|
||||
self.eventfd.write(1).unwrap();
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
// Always signal completion even on error to avoid hangs
|
||||
let errno = if let AsyncIoError::WriteZeroes(io_err) = &e {
|
||||
let err = io_err.raw_os_error().unwrap_or(libc::EIO);
|
||||
-err
|
||||
} else {
|
||||
-libc::EIO
|
||||
};
|
||||
self.completion_list.push_back((user_data, errno));
|
||||
self.eventfd.write(1).unwrap();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod unit_tests {
|
||||
use std::io::{Read, Seek, SeekFrom, Write};
|
||||
|
||||
use vmm_sys_util::tempfile::TempFile;
|
||||
|
||||
use super::*;
|
||||
use crate::qcow::{QcowFile, QcowHeader, RawFile};
|
||||
|
||||
#[test]
|
||||
fn test_qcow_async_punch_hole_completion() {
|
||||
// Create a QCOW2 image with valid header
|
||||
let temp_file = TempFile::new().unwrap();
|
||||
let raw_file = RawFile::new(temp_file.into_file(), false);
|
||||
let file_size = 1024 * 1024 * 100; // 100MB
|
||||
let mut qcow_file = QcowFile::new(raw_file, 3, file_size, true).unwrap();
|
||||
|
||||
// Write some data
|
||||
let data = vec![0xDD; 128 * 1024]; // 128KB
|
||||
let offset = 0;
|
||||
qcow_file.seek(SeekFrom::Start(offset)).unwrap();
|
||||
qcow_file.write_all(&data).unwrap();
|
||||
qcow_file.flush().unwrap();
|
||||
|
||||
// Create async wrapper
|
||||
let qcow_file = Arc::new(Mutex::new(qcow_file));
|
||||
let mut async_qcow = QcowSync::new(qcow_file.clone());
|
||||
|
||||
// Punch hole
|
||||
async_qcow
|
||||
.punch_hole(offset, data.len() as u64, 100)
|
||||
.unwrap();
|
||||
|
||||
// Verify completion event was generated
|
||||
let (user_data, result) = async_qcow.next_completed_request().unwrap();
|
||||
assert_eq!(user_data, 100);
|
||||
assert_eq!(result, 0, "punch_hole should succeed");
|
||||
|
||||
// Verify data reads as zeros
|
||||
let mut read_buf = vec![0; data.len()];
|
||||
qcow_file
|
||||
.lock()
|
||||
.unwrap()
|
||||
.seek(SeekFrom::Start(offset))
|
||||
.unwrap();
|
||||
qcow_file.lock().unwrap().read_exact(&mut read_buf).unwrap();
|
||||
assert!(
|
||||
read_buf.iter().all(|&b| b == 0),
|
||||
"Punched hole should read as zeros"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_qcow_async_write_zeroes_completion() {
|
||||
// Create a QCOW2 image with valid header
|
||||
let temp_file = TempFile::new().unwrap();
|
||||
let raw_file = RawFile::new(temp_file.into_file(), false);
|
||||
let file_size = 1024 * 1024 * 100; // 100MB
|
||||
let mut qcow_file = QcowFile::new(raw_file, 3, file_size, true).unwrap();
|
||||
|
||||
// Write some data
|
||||
let data = vec![0xEE; 256 * 1024]; // 256KB
|
||||
let offset = 64 * 1024; // Start at 64KB offset
|
||||
qcow_file.seek(SeekFrom::Start(offset)).unwrap();
|
||||
qcow_file.write_all(&data).unwrap();
|
||||
qcow_file.flush().unwrap();
|
||||
|
||||
// Create async wrapper
|
||||
let qcow_file = Arc::new(Mutex::new(qcow_file));
|
||||
let mut async_qcow = QcowSync::new(qcow_file.clone());
|
||||
|
||||
// Write zeros
|
||||
async_qcow
|
||||
.write_zeroes(offset, data.len() as u64, 200)
|
||||
.unwrap();
|
||||
|
||||
// Verify completion event was generated
|
||||
let (user_data, result) = async_qcow.next_completed_request().unwrap();
|
||||
assert_eq!(user_data, 200);
|
||||
assert_eq!(result, 0, "write_zeroes should succeed");
|
||||
|
||||
// Verify data reads as zeros
|
||||
let mut read_buf = vec![0; data.len()];
|
||||
qcow_file
|
||||
.lock()
|
||||
.unwrap()
|
||||
.seek(SeekFrom::Start(offset))
|
||||
.unwrap();
|
||||
qcow_file.lock().unwrap().read_exact(&mut read_buf).unwrap();
|
||||
assert!(
|
||||
read_buf.iter().all(|&b| b == 0),
|
||||
"Zeroed region should read as zeros"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_qcow_async_multiple_operations() {
|
||||
// Create a QCOW2 image with valid header
|
||||
let temp_file = TempFile::new().unwrap();
|
||||
let raw_file = RawFile::new(temp_file.into_file(), false);
|
||||
let file_size = 1024 * 1024 * 100; // 100MB
|
||||
let mut qcow_file = QcowFile::new(raw_file, 3, file_size, true).unwrap();
|
||||
|
||||
// Write data at multiple offsets
|
||||
let data = vec![0xFF; 64 * 1024]; // 64KB chunks
|
||||
for i in 0..4 {
|
||||
let offset = i * 128 * 1024; // 128KB spacing
|
||||
qcow_file.seek(SeekFrom::Start(offset)).unwrap();
|
||||
qcow_file.write_all(&data).unwrap();
|
||||
}
|
||||
qcow_file.flush().unwrap();
|
||||
|
||||
// Create async wrapper
|
||||
let qcow_file = Arc::new(Mutex::new(qcow_file));
|
||||
let mut async_qcow = QcowSync::new(qcow_file.clone());
|
||||
|
||||
// Queue multiple punch_hole operations
|
||||
async_qcow.punch_hole(0, 64 * 1024, 1).unwrap();
|
||||
async_qcow.punch_hole(128 * 1024, 64 * 1024, 2).unwrap();
|
||||
async_qcow.punch_hole(256 * 1024, 64 * 1024, 3).unwrap();
|
||||
|
||||
// Verify all completions
|
||||
let (user_data, result) = async_qcow.next_completed_request().unwrap();
|
||||
assert_eq!(user_data, 1);
|
||||
assert_eq!(result, 0);
|
||||
|
||||
let (user_data, result) = async_qcow.next_completed_request().unwrap();
|
||||
assert_eq!(user_data, 2);
|
||||
assert_eq!(result, 0);
|
||||
|
||||
let (user_data, result) = async_qcow.next_completed_request().unwrap();
|
||||
assert_eq!(user_data, 3);
|
||||
assert_eq!(result, 0);
|
||||
|
||||
// Verify no more completions
|
||||
assert!(async_qcow.next_completed_request().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_qcow_punch_hole_with_shared_instance() {
|
||||
// This test verifies that with Arc<Mutex<>>, multiple async I/O operations
|
||||
// share the same QcowFile instance and see each other's changes.
|
||||
|
||||
// Create a QCOW2 image
|
||||
let temp_file = TempFile::new().unwrap();
|
||||
let raw_file = RawFile::new(temp_file.into_file(), false);
|
||||
let file_size = 1024 * 1024 * 100; // 100MB
|
||||
let mut qcow_file = QcowFile::new(raw_file, 3, file_size, true).unwrap();
|
||||
|
||||
// Write some data at offset 0
|
||||
let data = vec![0xAB; 128 * 1024]; // 128KB of 0xAB pattern
|
||||
let offset = 0;
|
||||
qcow_file.seek(SeekFrom::Start(offset)).unwrap();
|
||||
qcow_file.write_all(&data).unwrap();
|
||||
qcow_file.flush().unwrap();
|
||||
|
||||
let qcow_shared = Arc::new(Mutex::new(qcow_file));
|
||||
|
||||
// First async I/O: punch hole
|
||||
let mut async_qcow1 = QcowSync::new(qcow_shared.clone());
|
||||
async_qcow1
|
||||
.punch_hole(offset, data.len() as u64, 100)
|
||||
.unwrap();
|
||||
|
||||
// Verify punch_hole completed
|
||||
let (user_data, result) = async_qcow1.next_completed_request().unwrap();
|
||||
assert_eq!(user_data, 100);
|
||||
assert_eq!(result, 0, "punch_hole should succeed");
|
||||
|
||||
// Second async I/O: read from same shared instance
|
||||
// This should see the deallocated cluster because they share the same QcowFile
|
||||
let mut read_buf = vec![0xFF; data.len()];
|
||||
qcow_shared
|
||||
.lock()
|
||||
.unwrap()
|
||||
.seek(SeekFrom::Start(offset))
|
||||
.unwrap();
|
||||
qcow_shared
|
||||
.lock()
|
||||
.unwrap()
|
||||
.read_exact(&mut read_buf)
|
||||
.unwrap();
|
||||
|
||||
// The read should return zeros because the cluster was deallocated
|
||||
assert!(
|
||||
read_buf.iter().all(|&b| b == 0),
|
||||
"After punch_hole, shared QcowFile instance should read zeros from deallocated cluster"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_qcow_disk_sync_punch_hole_with_new_async_io() {
|
||||
// This test simulates the EXACT real usage pattern: QcowDiskSync.new_async_io()
|
||||
// creates a new QcowSync with a cloned QcowFile for each I/O operation.
|
||||
|
||||
use std::io::Write;
|
||||
|
||||
use crate::async_io::DiskFile;
|
||||
|
||||
// Create a QCOW2 image
|
||||
let temp_file = TempFile::new().unwrap();
|
||||
let file_size = 1024 * 1024 * 100; // 100MB
|
||||
|
||||
{
|
||||
let raw_file = RawFile::new(temp_file.as_file().try_clone().unwrap(), false);
|
||||
let mut qcow_file = QcowFile::new(raw_file, 3, file_size, true).unwrap();
|
||||
|
||||
// Write data at offset 1MB - use single cluster (64KB) to simplify test
|
||||
let data = vec![0xCD; 64 * 1024]; // 64KB (one cluster)
|
||||
let offset = 1024 * 1024u64;
|
||||
qcow_file.seek(SeekFrom::Start(offset)).unwrap();
|
||||
qcow_file.write_all(&data).unwrap();
|
||||
qcow_file.flush().unwrap();
|
||||
}
|
||||
|
||||
// Open with QcowDiskSync (like real code does)
|
||||
let disk =
|
||||
QcowDiskSync::new(temp_file.as_file().try_clone().unwrap(), false, true, true).unwrap();
|
||||
|
||||
// First async I/O: punch hole (simulates DISCARD command)
|
||||
let mut async_io1 = disk.new_async_io(1).unwrap();
|
||||
let offset = 1024 * 1024u64;
|
||||
let length = 64 * 1024u64; // Single cluster
|
||||
async_io1.punch_hole(offset, length, 1).unwrap();
|
||||
let (user_data, result) = async_io1.next_completed_request().unwrap();
|
||||
assert_eq!(user_data, 1);
|
||||
assert_eq!(result, 0, "punch_hole should succeed");
|
||||
drop(async_io1);
|
||||
|
||||
// Second async I/O: read from the same location (simulates READ command)
|
||||
let mut async_io2 = disk.new_async_io(1).unwrap();
|
||||
let mut read_buf = vec![0xFF; length as usize];
|
||||
let iovec = libc::iovec {
|
||||
iov_base: read_buf.as_mut_ptr() as *mut libc::c_void,
|
||||
iov_len: read_buf.len(),
|
||||
};
|
||||
|
||||
// These assertions are critical to prevent compiler optimization bugs
|
||||
// that can reorder operations. Without them, the test can fail even
|
||||
// though the QCOW2 implementation is correct.
|
||||
assert_eq!(iovec.iov_base as *const u8, read_buf.as_ptr());
|
||||
assert_eq!(iovec.iov_len, read_buf.len());
|
||||
|
||||
async_io2
|
||||
.read_vectored(offset as libc::off_t, &[iovec], 2)
|
||||
.unwrap();
|
||||
|
||||
let (user_data, result) = async_io2.next_completed_request().unwrap();
|
||||
assert_eq!(user_data, 2);
|
||||
assert_eq!(
|
||||
result as usize, length as usize,
|
||||
"read should complete successfully"
|
||||
);
|
||||
|
||||
// Verify the data is all zeros
|
||||
assert!(
|
||||
read_buf.iter().all(|&b| b == 0),
|
||||
"After punch_hole via new_async_io, read should return zeros"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn backing_files_disabled_error() {
|
||||
let header =
|
||||
QcowHeader::create_for_size_and_path(3, 0x10_0000, Some("/path/to/backing/file"))
|
||||
.expect("Failed to create header.");
|
||||
let temp_file = TempFile::new().unwrap();
|
||||
let mut raw_file = RawFile::new(temp_file.as_file().try_clone().unwrap(), false);
|
||||
header
|
||||
.write_to(&mut raw_file)
|
||||
.expect("Failed to write header.");
|
||||
|
||||
let file = temp_file.into_file();
|
||||
match QcowDiskSync::new(file, false, false, true) {
|
||||
Err(QcowError::BackingFilesDisabled) => {}
|
||||
Err(other) => panic!("Expected BackingFilesDisabled, got: {other:?}"),
|
||||
Ok(_) => panic!("Expected BackingFilesDisabled error, but succeeded"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,314 +0,0 @@
|
||||
// Copyright © 2021 Intel Corporation
|
||||
//
|
||||
// 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::{IoUring, opcode, types};
|
||||
use log::warn;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
use crate::async_io::{
|
||||
AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult,
|
||||
};
|
||||
use crate::{BatchRequest, DiskTopology, RequestType, probe_sparse_support};
|
||||
|
||||
pub struct RawFileDisk {
|
||||
file: File,
|
||||
}
|
||||
|
||||
impl RawFileDisk {
|
||||
pub fn new(file: File) -> Self {
|
||||
RawFileDisk { file }
|
||||
}
|
||||
}
|
||||
|
||||
impl DiskFile for RawFileDisk {
|
||||
fn logical_size(&mut self) -> DiskFileResult<u64> {
|
||||
self.file
|
||||
.seek(SeekFrom::End(0))
|
||||
.map_err(DiskFileError::Size)
|
||||
}
|
||||
|
||||
fn physical_size(&mut self) -> DiskFileResult<u64> {
|
||||
self.file
|
||||
.metadata()
|
||||
.map(|m| m.len())
|
||||
.map_err(DiskFileError::Size)
|
||||
}
|
||||
|
||||
fn new_async_io(&self, ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
|
||||
Ok(Box::new(
|
||||
RawFileAsync::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 resize(&mut self, size: u64) -> DiskFileResult<()> {
|
||||
self.file.set_len(size).map_err(DiskFileError::ResizeError)
|
||||
}
|
||||
|
||||
fn supports_sparse_operations(&self) -> bool {
|
||||
probe_sparse_support(&self.file)
|
||||
}
|
||||
|
||||
fn fd(&mut self) -> BorrowedDiskFd<'_> {
|
||||
BorrowedDiskFd::new(self.file.as_raw_fd())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RawFileAsync {
|
||||
fd: RawFd,
|
||||
io_uring: IoUring,
|
||||
eventfd: EventFd,
|
||||
}
|
||||
|
||||
impl RawFileAsync {
|
||||
pub fn new(fd: RawFd, ring_depth: u32) -> std::io::Result<Self> {
|
||||
let io_uring = IoUring::new(ring_depth)?;
|
||||
let eventfd = EventFd::new(libc::EFD_NONBLOCK)?;
|
||||
|
||||
// Register the io_uring eventfd that will notify when something in
|
||||
// the completion queue is ready.
|
||||
io_uring.submitter().register_eventfd(eventfd.as_raw_fd())?;
|
||||
|
||||
Ok(RawFileAsync {
|
||||
fd,
|
||||
io_uring,
|
||||
eventfd,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncIo for RawFileAsync {
|
||||
fn notifier(&self) -> &EventFd {
|
||||
&self.eventfd
|
||||
}
|
||||
|
||||
fn read_vectored(
|
||||
&mut self,
|
||||
offset: libc::off_t,
|
||||
iovecs: &[libc::iovec],
|
||||
user_data: u64,
|
||||
) -> AsyncIoResult<()> {
|
||||
let (submitter, mut sq, _) = self.io_uring.split();
|
||||
|
||||
// SAFETY: we know the file descriptor is valid and we
|
||||
// relied on vm-memory to provide the buffer address.
|
||||
unsafe {
|
||||
sq.push(
|
||||
&opcode::Readv::new(types::Fd(self.fd), iovecs.as_ptr(), iovecs.len() as u32)
|
||||
.offset(offset.try_into().unwrap())
|
||||
.build()
|
||||
.user_data(user_data),
|
||||
)
|
||||
.map_err(|_| AsyncIoError::ReadVectored(Error::other("Submission queue is full")))?;
|
||||
};
|
||||
|
||||
// Update the submission queue and submit new operations to the
|
||||
// io_uring instance.
|
||||
sq.sync();
|
||||
submitter.submit().map_err(AsyncIoError::ReadVectored)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_vectored(
|
||||
&mut self,
|
||||
offset: libc::off_t,
|
||||
iovecs: &[libc::iovec],
|
||||
user_data: u64,
|
||||
) -> AsyncIoResult<()> {
|
||||
let (submitter, mut sq, _) = self.io_uring.split();
|
||||
|
||||
// SAFETY: we know the file descriptor is valid and we
|
||||
// relied on vm-memory to provide the buffer address.
|
||||
unsafe {
|
||||
sq.push(
|
||||
&opcode::Writev::new(types::Fd(self.fd), iovecs.as_ptr(), iovecs.len() as u32)
|
||||
.offset(offset.try_into().unwrap())
|
||||
.build()
|
||||
.user_data(user_data),
|
||||
)
|
||||
.map_err(|_| AsyncIoError::WriteVectored(Error::other("Submission queue is full")))?;
|
||||
};
|
||||
|
||||
// Update the submission queue and submit new operations to the
|
||||
// io_uring instance.
|
||||
sq.sync();
|
||||
submitter.submit().map_err(AsyncIoError::WriteVectored)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn fsync(&mut self, user_data: Option<u64>) -> AsyncIoResult<()> {
|
||||
if let Some(user_data) = user_data {
|
||||
let (submitter, mut sq, _) = self.io_uring.split();
|
||||
|
||||
// SAFETY: we know the file descriptor is valid.
|
||||
unsafe {
|
||||
sq.push(
|
||||
&opcode::Fsync::new(types::Fd(self.fd))
|
||||
.build()
|
||||
.user_data(user_data),
|
||||
)
|
||||
.map_err(|_| AsyncIoError::Fsync(Error::other("Submission queue is full")))?;
|
||||
};
|
||||
|
||||
// Update the submission queue and submit new operations to the
|
||||
// io_uring instance.
|
||||
sq.sync();
|
||||
submitter.submit().map_err(AsyncIoError::Fsync)?;
|
||||
} else {
|
||||
// SAFETY: FFI call with a valid fd
|
||||
unsafe { libc::fsync(self.fd) };
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn next_completed_request(&mut self) -> Option<(u64, i32)> {
|
||||
self.io_uring
|
||||
.completion()
|
||||
.next()
|
||||
.map(|entry| (entry.user_data(), entry.result()))
|
||||
}
|
||||
|
||||
fn batch_requests_enabled(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn submit_batch_requests(&mut self, batch_request: &[BatchRequest]) -> AsyncIoResult<()> {
|
||||
if !self.batch_requests_enabled() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let (submitter, mut sq, _) = self.io_uring.split();
|
||||
let mut submitted = false;
|
||||
|
||||
for req in batch_request {
|
||||
match req.request_type {
|
||||
RequestType::In => {
|
||||
// SAFETY: we know the file descriptor is valid and we
|
||||
// relied on vm-memory to provide the buffer address.
|
||||
unsafe {
|
||||
sq.push(
|
||||
&opcode::Readv::new(
|
||||
types::Fd(self.fd),
|
||||
req.iovecs.as_ptr(),
|
||||
req.iovecs.len() as u32,
|
||||
)
|
||||
.offset(req.offset as u64)
|
||||
.build()
|
||||
.user_data(req.user_data),
|
||||
)
|
||||
.map_err(|_| {
|
||||
AsyncIoError::ReadVectored(Error::other("Submission queue is full"))
|
||||
})?;
|
||||
};
|
||||
submitted = true;
|
||||
}
|
||||
RequestType::Out => {
|
||||
// SAFETY: we know the file descriptor is valid and we
|
||||
// relied on vm-memory to provide the buffer address.
|
||||
unsafe {
|
||||
sq.push(
|
||||
&opcode::Writev::new(
|
||||
types::Fd(self.fd),
|
||||
req.iovecs.as_ptr(),
|
||||
req.iovecs.len() as u32,
|
||||
)
|
||||
.offset(req.offset as u64)
|
||||
.build()
|
||||
.user_data(req.user_data),
|
||||
)
|
||||
.map_err(|_| {
|
||||
AsyncIoError::WriteVectored(Error::other("Submission queue is full"))
|
||||
})?;
|
||||
};
|
||||
submitted = true;
|
||||
}
|
||||
_ => {
|
||||
unreachable!("Unexpected batch request type: {:?}", req.request_type)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Only submit if we actually queued something
|
||||
if submitted {
|
||||
// Update the submission queue and submit new operations to the
|
||||
// io_uring instance.
|
||||
sq.sync();
|
||||
submitter
|
||||
.submit()
|
||||
.map_err(AsyncIoError::SubmitBatchRequests)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn punch_hole(&mut self, offset: u64, length: u64, user_data: u64) -> AsyncIoResult<()> {
|
||||
let (submitter, mut sq, _) = self.io_uring.split();
|
||||
|
||||
const FALLOC_FL_PUNCH_HOLE: i32 = 0x02;
|
||||
const FALLOC_FL_KEEP_SIZE: i32 = 0x01;
|
||||
let mode = FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE;
|
||||
|
||||
// SAFETY: The file descriptor is known to be valid.
|
||||
unsafe {
|
||||
sq.push(
|
||||
&opcode::Fallocate::new(types::Fd(self.fd), length)
|
||||
.offset(offset)
|
||||
.mode(mode)
|
||||
.build()
|
||||
.user_data(user_data),
|
||||
)
|
||||
.map_err(|e| {
|
||||
AsyncIoError::PunchHole(Error::other(format!("Submission queue is full: {e:?}")))
|
||||
})?;
|
||||
};
|
||||
|
||||
sq.sync();
|
||||
submitter.submit().map_err(AsyncIoError::PunchHole)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_zeroes(&mut self, offset: u64, length: u64, user_data: u64) -> AsyncIoResult<()> {
|
||||
let (submitter, mut sq, _) = self.io_uring.split();
|
||||
|
||||
const FALLOC_FL_ZERO_RANGE: i32 = 0x10;
|
||||
const FALLOC_FL_KEEP_SIZE: i32 = 0x01;
|
||||
let mode = FALLOC_FL_ZERO_RANGE | FALLOC_FL_KEEP_SIZE;
|
||||
|
||||
// SAFETY: The file descriptor is known to be valid.
|
||||
unsafe {
|
||||
sq.push(
|
||||
&opcode::Fallocate::new(types::Fd(self.fd), length)
|
||||
.offset(offset)
|
||||
.mode(mode)
|
||||
.build()
|
||||
.user_data(user_data),
|
||||
)
|
||||
.map_err(|e| {
|
||||
AsyncIoError::WriteZeroes(Error::other(format!("Submission queue is full: {e:?}")))
|
||||
})?;
|
||||
};
|
||||
|
||||
sq.sync();
|
||||
submitter.submit().map_err(AsyncIoError::WriteZeroes)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,180 +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 log::warn;
|
||||
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, probe_sparse_support};
|
||||
|
||||
pub struct RawFileDiskAio {
|
||||
file: File,
|
||||
}
|
||||
|
||||
impl RawFileDiskAio {
|
||||
pub fn new(file: File) -> Self {
|
||||
RawFileDiskAio { file }
|
||||
}
|
||||
}
|
||||
|
||||
impl DiskFile for RawFileDiskAio {
|
||||
fn logical_size(&mut self) -> DiskFileResult<u64> {
|
||||
self.file
|
||||
.seek(SeekFrom::End(0))
|
||||
.map_err(DiskFileError::Size)
|
||||
}
|
||||
|
||||
fn physical_size(&mut self) -> DiskFileResult<u64> {
|
||||
self.file
|
||||
.metadata()
|
||||
.map(|m| m.len())
|
||||
.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 supports_sparse_operations(&self) -> bool {
|
||||
probe_sparse_support(&self.file)
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
fn punch_hole(&mut self, _offset: u64, _length: u64, _user_data: u64) -> AsyncIoResult<()> {
|
||||
Err(AsyncIoError::PunchHole(std::io::Error::other(
|
||||
"punch_hole not supported with AIO backend",
|
||||
)))
|
||||
}
|
||||
|
||||
fn write_zeroes(&mut self, _offset: u64, _length: u64, _user_data: u64) -> AsyncIoResult<()> {
|
||||
Err(AsyncIoError::WriteZeroes(std::io::Error::other(
|
||||
"write_zeroes not supported with AIO backend",
|
||||
)))
|
||||
}
|
||||
}
|
||||
@@ -1,375 +0,0 @@
|
||||
// Copyright © 2021 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||
|
||||
use std::collections::VecDeque;
|
||||
use std::fs::File;
|
||||
use std::io::{Seek, SeekFrom};
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
|
||||
use log::warn;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
use crate::async_io::{
|
||||
AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult,
|
||||
};
|
||||
use crate::{DiskTopology, probe_sparse_support};
|
||||
|
||||
pub struct RawFileDiskSync {
|
||||
file: File,
|
||||
}
|
||||
|
||||
impl RawFileDiskSync {
|
||||
pub fn new(file: File) -> Self {
|
||||
RawFileDiskSync { file }
|
||||
}
|
||||
}
|
||||
|
||||
impl DiskFile for RawFileDiskSync {
|
||||
fn logical_size(&mut self) -> DiskFileResult<u64> {
|
||||
self.file
|
||||
.seek(SeekFrom::End(0))
|
||||
.map_err(DiskFileError::Size)
|
||||
}
|
||||
|
||||
fn physical_size(&mut self) -> DiskFileResult<u64> {
|
||||
self.file
|
||||
.metadata()
|
||||
.map(|m| m.len())
|
||||
.map_err(DiskFileError::Size)
|
||||
}
|
||||
|
||||
fn new_async_io(&self, _ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
|
||||
Ok(Box::new(RawFileSync::new(self.file.as_raw_fd())) 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 supports_sparse_operations(&self) -> bool {
|
||||
probe_sparse_support(&self.file)
|
||||
}
|
||||
|
||||
fn fd(&mut self) -> BorrowedDiskFd<'_> {
|
||||
BorrowedDiskFd::new(self.file.as_raw_fd())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RawFileSync {
|
||||
fd: RawFd,
|
||||
eventfd: EventFd,
|
||||
completion_list: VecDeque<(u64, i32)>,
|
||||
}
|
||||
|
||||
impl RawFileSync {
|
||||
pub fn new(fd: RawFd) -> Self {
|
||||
RawFileSync {
|
||||
fd,
|
||||
eventfd: EventFd::new(libc::EFD_NONBLOCK).expect("Failed creating EventFd for RawFile"),
|
||||
completion_list: VecDeque::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncIo for RawFileSync {
|
||||
fn notifier(&self) -> &EventFd {
|
||||
&self.eventfd
|
||||
}
|
||||
|
||||
fn read_vectored(
|
||||
&mut self,
|
||||
offset: libc::off_t,
|
||||
iovecs: &[libc::iovec],
|
||||
user_data: u64,
|
||||
) -> AsyncIoResult<()> {
|
||||
// SAFETY: FFI call with valid arguments
|
||||
let result = unsafe {
|
||||
libc::preadv(
|
||||
self.fd as libc::c_int,
|
||||
iovecs.as_ptr(),
|
||||
iovecs.len() as libc::c_int,
|
||||
offset,
|
||||
)
|
||||
};
|
||||
if result < 0 {
|
||||
return Err(AsyncIoError::ReadVectored(std::io::Error::last_os_error()));
|
||||
}
|
||||
|
||||
self.completion_list.push_back((user_data, result as i32));
|
||||
self.eventfd.write(1).unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_vectored(
|
||||
&mut self,
|
||||
offset: libc::off_t,
|
||||
iovecs: &[libc::iovec],
|
||||
user_data: u64,
|
||||
) -> AsyncIoResult<()> {
|
||||
// SAFETY: FFI call with valid arguments
|
||||
let result = unsafe {
|
||||
libc::pwritev(
|
||||
self.fd as libc::c_int,
|
||||
iovecs.as_ptr(),
|
||||
iovecs.len() as libc::c_int,
|
||||
offset,
|
||||
)
|
||||
};
|
||||
if result < 0 {
|
||||
return Err(AsyncIoError::WriteVectored(std::io::Error::last_os_error()));
|
||||
}
|
||||
|
||||
self.completion_list.push_back((user_data, result as i32));
|
||||
self.eventfd.write(1).unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn fsync(&mut self, user_data: Option<u64>) -> AsyncIoResult<()> {
|
||||
// SAFETY: FFI call
|
||||
let result = unsafe { libc::fsync(self.fd as libc::c_int) };
|
||||
if result < 0 {
|
||||
return Err(AsyncIoError::Fsync(std::io::Error::last_os_error()));
|
||||
}
|
||||
|
||||
if let Some(user_data) = user_data {
|
||||
self.completion_list.push_back((user_data, result));
|
||||
self.eventfd.write(1).unwrap();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn next_completed_request(&mut self) -> Option<(u64, i32)> {
|
||||
self.completion_list.pop_front()
|
||||
}
|
||||
|
||||
fn punch_hole(&mut self, offset: u64, length: u64, user_data: u64) -> AsyncIoResult<()> {
|
||||
const FALLOC_FL_PUNCH_HOLE: i32 = 0x02;
|
||||
const FALLOC_FL_KEEP_SIZE: i32 = 0x01;
|
||||
let mode = FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE;
|
||||
|
||||
// SAFETY: FFI call with valid arguments
|
||||
let result = unsafe {
|
||||
libc::fallocate(
|
||||
self.fd as libc::c_int,
|
||||
mode,
|
||||
offset as libc::off_t,
|
||||
length as libc::off_t,
|
||||
)
|
||||
};
|
||||
if result < 0 {
|
||||
return Err(AsyncIoError::PunchHole(std::io::Error::last_os_error()));
|
||||
}
|
||||
|
||||
self.completion_list.push_back((user_data, result));
|
||||
self.eventfd.write(1).unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_zeroes(&mut self, offset: u64, length: u64, user_data: u64) -> AsyncIoResult<()> {
|
||||
const FALLOC_FL_ZERO_RANGE: i32 = 0x10;
|
||||
const FALLOC_FL_KEEP_SIZE: i32 = 0x01;
|
||||
let mode = FALLOC_FL_ZERO_RANGE | FALLOC_FL_KEEP_SIZE;
|
||||
|
||||
// SAFETY: FFI call with valid arguments
|
||||
let result = unsafe {
|
||||
libc::fallocate(
|
||||
self.fd as libc::c_int,
|
||||
mode,
|
||||
offset as libc::off_t,
|
||||
length as libc::off_t,
|
||||
)
|
||||
};
|
||||
if result < 0 {
|
||||
return Err(AsyncIoError::WriteZeroes(std::io::Error::last_os_error()));
|
||||
}
|
||||
|
||||
self.completion_list.push_back((user_data, result));
|
||||
self.eventfd.write(1).unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod unit_tests {
|
||||
use std::io::{Read, Seek, SeekFrom, Write};
|
||||
|
||||
use vmm_sys_util::tempfile::TempFile;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_punch_hole() {
|
||||
let temp_file = TempFile::new().unwrap();
|
||||
let mut file = temp_file.into_file();
|
||||
|
||||
// Write 4MB of data
|
||||
let data = vec![0xAA; 4 * 1024 * 1024];
|
||||
file.write_all(&data).unwrap();
|
||||
file.sync_all().unwrap();
|
||||
|
||||
// Create async IO instance
|
||||
let mut async_io = RawFileSync::new(file.as_raw_fd());
|
||||
|
||||
// Punch hole in the middle (1MB at offset 1MB)
|
||||
let offset = 1024 * 1024;
|
||||
let length = 1024 * 1024;
|
||||
async_io.punch_hole(offset, length, 1).unwrap();
|
||||
|
||||
// Check completion
|
||||
let (user_data, result) = async_io.next_completed_request().unwrap();
|
||||
assert_eq!(user_data, 1);
|
||||
assert_eq!(result, 0);
|
||||
|
||||
// Verify the hole reads as zeros
|
||||
file.seek(SeekFrom::Start(offset)).unwrap();
|
||||
let mut read_buf = vec![0; length as usize];
|
||||
file.read_exact(&mut read_buf).unwrap();
|
||||
assert!(
|
||||
read_buf.iter().all(|&b| b == 0),
|
||||
"Punched hole should read as zeros"
|
||||
);
|
||||
|
||||
// Verify data before hole is intact
|
||||
file.seek(SeekFrom::Start(0)).unwrap();
|
||||
let mut read_buf = vec![0; 1024];
|
||||
file.read_exact(&mut read_buf).unwrap();
|
||||
assert!(
|
||||
read_buf.iter().all(|&b| b == 0xAA),
|
||||
"Data before hole should be intact"
|
||||
);
|
||||
|
||||
// Verify data after hole is intact
|
||||
file.seek(SeekFrom::Start(offset + length)).unwrap();
|
||||
let mut read_buf = vec![0; 1024];
|
||||
file.read_exact(&mut read_buf).unwrap();
|
||||
assert!(
|
||||
read_buf.iter().all(|&b| b == 0xAA),
|
||||
"Data after hole should be intact"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_write_zeroes() {
|
||||
let temp_file = TempFile::new().unwrap();
|
||||
let mut file = temp_file.into_file();
|
||||
|
||||
// Write 4MB of data
|
||||
let data = vec![0xBB; 4 * 1024 * 1024];
|
||||
file.write_all(&data).unwrap();
|
||||
file.sync_all().unwrap();
|
||||
|
||||
// Create async IO instance
|
||||
let mut async_io = RawFileSync::new(file.as_raw_fd());
|
||||
|
||||
// Write zeros in the middle (512KB at offset 2MB)
|
||||
let offset = 2 * 1024 * 1024;
|
||||
let length = 512 * 1024;
|
||||
let write_zeroes_result = async_io.write_zeroes(offset, length, 2);
|
||||
|
||||
// FALLOC_FL_ZERO_RANGE might not be supported on all filesystems (e.g., tmpfs)
|
||||
// If it fails with ENOTSUP, skip the test
|
||||
if let Err(AsyncIoError::WriteZeroes(ref e)) = write_zeroes_result
|
||||
&& (e.raw_os_error() == Some(libc::EOPNOTSUPP)
|
||||
|| e.raw_os_error() == Some(libc::ENOTSUP))
|
||||
{
|
||||
eprintln!(
|
||||
"Skipping test_write_zeroes: filesystem doesn't support FALLOC_FL_ZERO_RANGE"
|
||||
);
|
||||
return;
|
||||
}
|
||||
write_zeroes_result.unwrap();
|
||||
|
||||
// Check completion
|
||||
let (user_data, result) = async_io.next_completed_request().unwrap();
|
||||
assert_eq!(user_data, 2);
|
||||
assert_eq!(result, 0);
|
||||
|
||||
// Verify the zeroed region reads as zeros
|
||||
file.seek(SeekFrom::Start(offset)).unwrap();
|
||||
let mut read_buf = vec![0; length as usize];
|
||||
file.read_exact(&mut read_buf).unwrap();
|
||||
assert!(
|
||||
read_buf.iter().all(|&b| b == 0),
|
||||
"Zeroed region should read as zeros"
|
||||
);
|
||||
|
||||
// Verify data before zeroed region is intact
|
||||
file.seek(SeekFrom::Start(offset - 1024)).unwrap();
|
||||
let mut read_buf = vec![0; 1024];
|
||||
file.read_exact(&mut read_buf).unwrap();
|
||||
assert!(
|
||||
read_buf.iter().all(|&b| b == 0xBB),
|
||||
"Data before zeroed region should be intact"
|
||||
);
|
||||
|
||||
// Verify data after zeroed region is intact
|
||||
file.seek(SeekFrom::Start(offset + length)).unwrap();
|
||||
let mut read_buf = vec![0; 1024];
|
||||
file.read_exact(&mut read_buf).unwrap();
|
||||
assert!(
|
||||
read_buf.iter().all(|&b| b == 0xBB),
|
||||
"Data after zeroed region should be intact"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_punch_hole_multiple_operations() {
|
||||
let temp_file = TempFile::new().unwrap();
|
||||
let mut file = temp_file.into_file();
|
||||
|
||||
// Write 8MB of data
|
||||
let data = vec![0xCC; 8 * 1024 * 1024];
|
||||
file.write_all(&data).unwrap();
|
||||
file.sync_all().unwrap();
|
||||
|
||||
// Create async IO instance
|
||||
let mut async_io = RawFileSync::new(file.as_raw_fd());
|
||||
|
||||
// Punch multiple holes
|
||||
async_io.punch_hole(1024 * 1024, 512 * 1024, 10).unwrap();
|
||||
async_io
|
||||
.punch_hole(3 * 1024 * 1024, 512 * 1024, 11)
|
||||
.unwrap();
|
||||
async_io
|
||||
.punch_hole(5 * 1024 * 1024, 512 * 1024, 12)
|
||||
.unwrap();
|
||||
|
||||
// Check all completions
|
||||
let (user_data, result) = async_io.next_completed_request().unwrap();
|
||||
assert_eq!(user_data, 10);
|
||||
assert_eq!(result, 0);
|
||||
|
||||
let (user_data, result) = async_io.next_completed_request().unwrap();
|
||||
assert_eq!(user_data, 11);
|
||||
assert_eq!(result, 0);
|
||||
|
||||
let (user_data, result) = async_io.next_completed_request().unwrap();
|
||||
assert_eq!(user_data, 12);
|
||||
assert_eq!(result, 0);
|
||||
|
||||
// Verify all holes read as zeros
|
||||
file.seek(SeekFrom::Start(1024 * 1024)).unwrap();
|
||||
let mut read_buf = vec![0; 512 * 1024];
|
||||
file.read_exact(&mut read_buf).unwrap();
|
||||
assert!(read_buf.iter().all(|&b| b == 0));
|
||||
|
||||
file.seek(SeekFrom::Start(3 * 1024 * 1024)).unwrap();
|
||||
file.read_exact(&mut read_buf).unwrap();
|
||||
assert!(read_buf.iter().all(|&b| b == 0));
|
||||
|
||||
file.seek(SeekFrom::Start(5 * 1024 * 1024)).unwrap();
|
||||
file.read_exact(&mut read_buf).unwrap();
|
||||
assert!(read_buf.iter().all(|&b| b == 0));
|
||||
}
|
||||
}
|
||||
@@ -1,219 +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.12"
|
||||
libc = "0.2.139"
|
||||
log = "0.4.17"
|
||||
qcow = { path = "../qcow" }
|
||||
smallvec = "1.10.0"
|
||||
thiserror = "1.0.38"
|
||||
versionize = "0.1.9"
|
||||
versionize_derive = "0.1.4"
|
||||
vhdx = { path = "../vhdx" }
|
||||
virtio-bindings = { version = "0.2.0", features = ["virtio-v5_0_0"] }
|
||||
virtio-queue = "0.7.0"
|
||||
vm-memory = { version = "0.10.0", features = ["backend-mmap", "backend-atomic", "backend-bitmap"] }
|
||||
vm-virtio = { path = "../vm-virtio" }
|
||||
vmm-sys-util = "0.11.0"
|
||||
|
||||
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,49 +2,42 @@
|
||||
//
|
||||
// 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::os::unix::io::{AsRawFd, RawFd};
|
||||
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
use crate::async_io::{
|
||||
AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult,
|
||||
};
|
||||
use crate::fixed_vhd::FixedVhd;
|
||||
use crate::raw_async::RawFileAsync;
|
||||
use crate::{BatchRequest, BlockBackend};
|
||||
|
||||
pub struct FixedVhdDiskAsync(FixedVhd);
|
||||
pub struct FixedVhdDiskAsync {
|
||||
file: File,
|
||||
size: u64,
|
||||
}
|
||||
|
||||
impl FixedVhdDiskAsync {
|
||||
pub fn new(file: File) -> std::io::Result<Self> {
|
||||
Ok(Self(FixedVhd::new(file)?))
|
||||
pub fn new(mut file: File) -> std::io::Result<Self> {
|
||||
let footer = VhdFooter::new(&mut file)?;
|
||||
|
||||
Ok(FixedVhdDiskAsync {
|
||||
file,
|
||||
size: footer.current_size(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl DiskFile for FixedVhdDiskAsync {
|
||||
fn logical_size(&mut self) -> DiskFileResult<u64> {
|
||||
Ok(self.0.logical_size().unwrap())
|
||||
}
|
||||
|
||||
fn physical_size(&mut self) -> DiskFileResult<u64> {
|
||||
Ok(self.0.physical_size().unwrap())
|
||||
fn size(&mut self) -> DiskFileResult<u64> {
|
||||
Ok(self.size)
|
||||
}
|
||||
|
||||
fn new_async_io(&self, ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
|
||||
Ok(Box::new(
|
||||
FixedVhdAsync::new(
|
||||
self.0.as_raw_fd(),
|
||||
ring_depth,
|
||||
self.0.logical_size().unwrap(),
|
||||
)
|
||||
.map_err(DiskFileError::NewAsyncIo)?,
|
||||
FixedVhdAsync::new(self.file.as_raw_fd(), ring_depth, self.size)
|
||||
.map_err(DiskFileError::NewAsyncIo)?,
|
||||
) as Box<dyn AsyncIo>)
|
||||
}
|
||||
|
||||
fn fd(&mut self) -> BorrowedDiskFd<'_> {
|
||||
BorrowedDiskFd::new(self.0.as_raw_fd())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FixedVhdAsync {
|
||||
@@ -114,24 +107,4 @@ impl AsyncIo for FixedVhdAsync {
|
||||
fn next_completed_request(&mut self) -> Option<(u64, i32)> {
|
||||
self.raw_file_async.next_completed_request()
|
||||
}
|
||||
|
||||
fn punch_hole(&mut self, _offset: u64, _length: u64, _user_data: u64) -> AsyncIoResult<()> {
|
||||
Err(AsyncIoError::PunchHole(std::io::Error::other(
|
||||
"punch_hole not supported for fixed VHD",
|
||||
)))
|
||||
}
|
||||
|
||||
fn write_zeroes(&mut self, _offset: u64, _length: u64, _user_data: u64) -> AsyncIoResult<()> {
|
||||
Err(AsyncIoError::WriteZeroes(std::io::Error::other(
|
||||
"write_zeroes not supported for fixed VHD",
|
||||
)))
|
||||
}
|
||||
|
||||
fn batch_requests_enabled(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn submit_batch_requests(&mut self, batch_request: &[BatchRequest]) -> AsyncIoResult<()> {
|
||||
self.raw_file_async.submit_batch_requests(batch_request)
|
||||
}
|
||||
}
|
||||
@@ -2,51 +2,42 @@
|
||||
//
|
||||
// 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::os::unix::io::{AsRawFd, RawFd};
|
||||
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
use crate::BlockBackend;
|
||||
use crate::async_io::{
|
||||
AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult,
|
||||
};
|
||||
use crate::fixed_vhd::FixedVhd;
|
||||
use crate::raw_sync::RawFileSync;
|
||||
|
||||
pub struct FixedVhdDiskSync(FixedVhd);
|
||||
pub struct FixedVhdDiskSync {
|
||||
file: File,
|
||||
size: u64,
|
||||
}
|
||||
|
||||
impl FixedVhdDiskSync {
|
||||
pub fn new(file: File) -> std::io::Result<Self> {
|
||||
Ok(Self(FixedVhd::new(file)?))
|
||||
pub fn new(mut file: File) -> std::io::Result<Self> {
|
||||
let footer = VhdFooter::new(&mut file)?;
|
||||
|
||||
Ok(FixedVhdDiskSync {
|
||||
file,
|
||||
size: footer.current_size(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl DiskFile for FixedVhdDiskSync {
|
||||
fn logical_size(&mut self) -> DiskFileResult<u64> {
|
||||
Ok(self.0.logical_size().unwrap())
|
||||
}
|
||||
|
||||
fn physical_size(&mut self) -> DiskFileResult<u64> {
|
||||
self.0.physical_size().map_err(|e| {
|
||||
let io_inner = match e {
|
||||
crate::Error::GetFileMetadata(e) => e,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
DiskFileError::Size(io_inner)
|
||||
})
|
||||
fn size(&mut self) -> DiskFileResult<u64> {
|
||||
Ok(self.size)
|
||||
}
|
||||
|
||||
fn new_async_io(&self, _ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
|
||||
Ok(Box::new(
|
||||
FixedVhdSync::new(self.0.as_raw_fd(), self.0.logical_size().unwrap())
|
||||
FixedVhdSync::new(self.file.as_raw_fd(), self.size)
|
||||
.map_err(DiskFileError::NewAsyncIo)?,
|
||||
) as Box<dyn AsyncIo>)
|
||||
}
|
||||
|
||||
fn fd(&mut self) -> BorrowedDiskFd<'_> {
|
||||
BorrowedDiskFd::new(self.0.as_raw_fd())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FixedVhdSync {
|
||||
@@ -113,16 +104,4 @@ impl AsyncIo for FixedVhdSync {
|
||||
fn next_completed_request(&mut self) -> Option<(u64, i32)> {
|
||||
self.raw_file_sync.next_completed_request()
|
||||
}
|
||||
|
||||
fn punch_hole(&mut self, _offset: u64, _length: u64, _user_data: u64) -> AsyncIoResult<()> {
|
||||
Err(AsyncIoError::PunchHole(std::io::Error::other(
|
||||
"punch_hole not supported for fixed VHD",
|
||||
)))
|
||||
}
|
||||
|
||||
fn write_zeroes(&mut self, _offset: u64, _length: u64, _user_data: u64) -> AsyncIoResult<()> {
|
||||
Err(AsyncIoError::WriteZeroes(std::io::Error::other(
|
||||
"write_zeroes not supported for fixed VHD",
|
||||
)))
|
||||
}
|
||||
}
|
||||
722
block_util/src/lib.rs
Normal file
722
block_util/src/lib.rs
Normal file
@@ -0,0 +1,722 @@
|
||||
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
//
|
||||
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
//
|
||||
// Copyright © 2020 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
pub mod async_io;
|
||||
pub mod fixed_vhd_async;
|
||||
pub mod fixed_vhd_sync;
|
||||
pub mod qcow_sync;
|
||||
pub mod raw_async;
|
||||
pub mod raw_sync;
|
||||
pub mod vhd;
|
||||
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::cmp;
|
||||
use std::collections::VecDeque;
|
||||
use std::convert::TryInto;
|
||||
use std::fs::File;
|
||||
use std::io::{self, IoSlice, IoSliceMut, Read, Seek, SeekFrom, Write};
|
||||
use std::os::linux::fs::MetadataExt;
|
||||
use std::path::Path;
|
||||
use std::result;
|
||||
use std::sync::Arc;
|
||||
use std::sync::MutexGuard;
|
||||
use std::time::Instant;
|
||||
use thiserror::Error;
|
||||
use versionize::{VersionMap, Versionize, VersionizeResult};
|
||||
use versionize_derive::Versionize;
|
||||
use virtio_bindings::virtio_blk::*;
|
||||
use virtio_queue::DescriptorChain;
|
||||
use vm_memory::{
|
||||
bitmap::AtomicBitmap, bitmap::Bitmap, ByteValued, Bytes, GuestAddress, GuestMemory,
|
||||
GuestMemoryError, GuestMemoryLoadGuard,
|
||||
};
|
||||
use vm_virtio::{AccessPlatform, Translatable};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
type GuestMemoryMmap = vm_memory::GuestMemoryMmap<AtomicBitmap>;
|
||||
|
||||
const SECTOR_SHIFT: u8 = 9;
|
||||
pub const SECTOR_SIZE: u64 = 0x01 << SECTOR_SHIFT;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Error {
|
||||
#[error("Guest gave us bad memory addresses")]
|
||||
GuestMemory(GuestMemoryError),
|
||||
#[error("Guest gave us offsets that would have overflowed a usize")]
|
||||
CheckedOffset(GuestAddress, usize),
|
||||
#[error("Guest gave us a write only descriptor that protocol says to read from")]
|
||||
UnexpectedWriteOnlyDescriptor,
|
||||
#[error("Guest gave us a read only descriptor that protocol says to write to")]
|
||||
UnexpectedReadOnlyDescriptor,
|
||||
#[error("Guest gave us too few descriptors in a descriptor chain")]
|
||||
DescriptorChainTooShort,
|
||||
#[error("Guest gave us a descriptor that was too short to use")]
|
||||
DescriptorLengthTooSmall,
|
||||
#[error("Getting a block's metadata fails for any reason")]
|
||||
GetFileMetadata,
|
||||
#[error("The requested operation would cause a seek beyond disk end")]
|
||||
InvalidOffset,
|
||||
#[error("The requested operation does not support multiple descriptors")]
|
||||
TooManyDescriptors,
|
||||
}
|
||||
|
||||
fn build_device_id(disk_path: &Path) -> result::Result<String, Error> {
|
||||
let blk_metadata = match disk_path.metadata() {
|
||||
Err(_) => return Err(Error::GetFileMetadata),
|
||||
Ok(m) => m,
|
||||
};
|
||||
// This is how kvmtool does it.
|
||||
let device_id = format!(
|
||||
"{}{}{}",
|
||||
blk_metadata.st_dev(),
|
||||
blk_metadata.st_rdev(),
|
||||
blk_metadata.st_ino()
|
||||
);
|
||||
Ok(device_id)
|
||||
}
|
||||
|
||||
pub fn build_disk_image_id(disk_path: &Path) -> Vec<u8> {
|
||||
let mut default_disk_image_id = vec![0; VIRTIO_BLK_ID_BYTES as usize];
|
||||
match build_device_id(disk_path) {
|
||||
Err(_) => {
|
||||
warn!("Could not generate device id. We'll use a default.");
|
||||
}
|
||||
Ok(m) => {
|
||||
// The kernel only knows to read a maximum of VIRTIO_BLK_ID_BYTES.
|
||||
// This will also zero out any leftover bytes.
|
||||
let disk_id = m.as_bytes();
|
||||
let bytes_to_copy = cmp::min(disk_id.len(), VIRTIO_BLK_ID_BYTES as usize);
|
||||
default_disk_image_id[..bytes_to_copy].clone_from_slice(&disk_id[..bytes_to_copy])
|
||||
}
|
||||
}
|
||||
default_disk_image_id
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum ExecuteError {
|
||||
#[error("Bad request: {0}")]
|
||||
BadRequest(Error),
|
||||
#[error("Falied to flush: {0}")]
|
||||
Flush(io::Error),
|
||||
#[error("Failed to read: {0}")]
|
||||
Read(GuestMemoryError),
|
||||
#[error("Failed to seek: {0}")]
|
||||
Seek(io::Error),
|
||||
#[error("Failed to write: {0}")]
|
||||
Write(GuestMemoryError),
|
||||
#[error("Unsupported request: {0}")]
|
||||
Unsupported(u32),
|
||||
#[error("Failed to submit io uring: {0}")]
|
||||
SubmitIoUring(io::Error),
|
||||
#[error("Failed to get guest address: {0}")]
|
||||
GetHostAddress(GuestMemoryError),
|
||||
#[error("Failed to async read: {0}")]
|
||||
AsyncRead(AsyncIoError),
|
||||
#[error("Failed to async write: {0}")]
|
||||
AsyncWrite(AsyncIoError),
|
||||
#[error("failed to async flush: {0}")]
|
||||
AsyncFlush(AsyncIoError),
|
||||
#[error("Failed allocating a temporary buffer: {0}")]
|
||||
TemporaryBufferAllocation(io::Error),
|
||||
}
|
||||
|
||||
impl ExecuteError {
|
||||
pub fn status(&self) -> u32 {
|
||||
match *self {
|
||||
ExecuteError::BadRequest(_) => VIRTIO_BLK_S_IOERR,
|
||||
ExecuteError::Flush(_) => VIRTIO_BLK_S_IOERR,
|
||||
ExecuteError::Read(_) => VIRTIO_BLK_S_IOERR,
|
||||
ExecuteError::Seek(_) => VIRTIO_BLK_S_IOERR,
|
||||
ExecuteError::Write(_) => VIRTIO_BLK_S_IOERR,
|
||||
ExecuteError::Unsupported(_) => VIRTIO_BLK_S_UNSUPP,
|
||||
ExecuteError::SubmitIoUring(_) => VIRTIO_BLK_S_IOERR,
|
||||
ExecuteError::GetHostAddress(_) => VIRTIO_BLK_S_IOERR,
|
||||
ExecuteError::AsyncRead(_) => VIRTIO_BLK_S_IOERR,
|
||||
ExecuteError::AsyncWrite(_) => VIRTIO_BLK_S_IOERR,
|
||||
ExecuteError::AsyncFlush(_) => VIRTIO_BLK_S_IOERR,
|
||||
ExecuteError::TemporaryBufferAllocation(_) => VIRTIO_BLK_S_IOERR,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum RequestType {
|
||||
In,
|
||||
Out,
|
||||
Flush,
|
||||
GetDeviceId,
|
||||
Unsupported(u32),
|
||||
}
|
||||
|
||||
pub fn request_type(
|
||||
mem: &GuestMemoryMmap,
|
||||
desc_addr: GuestAddress,
|
||||
) -> result::Result<RequestType, Error> {
|
||||
let type_ = mem.read_obj(desc_addr).map_err(Error::GuestMemory)?;
|
||||
match type_ {
|
||||
VIRTIO_BLK_T_IN => Ok(RequestType::In),
|
||||
VIRTIO_BLK_T_OUT => Ok(RequestType::Out),
|
||||
VIRTIO_BLK_T_FLUSH => Ok(RequestType::Flush),
|
||||
VIRTIO_BLK_T_GET_ID => Ok(RequestType::GetDeviceId),
|
||||
t => Ok(RequestType::Unsupported(t)),
|
||||
}
|
||||
}
|
||||
|
||||
fn sector(mem: &GuestMemoryMmap, desc_addr: GuestAddress) -> result::Result<u64, Error> {
|
||||
const SECTOR_OFFSET: usize = 8;
|
||||
let addr = match mem.checked_offset(desc_addr, SECTOR_OFFSET) {
|
||||
Some(v) => v,
|
||||
None => return Err(Error::CheckedOffset(desc_addr, SECTOR_OFFSET)),
|
||||
};
|
||||
|
||||
mem.read_obj(addr).map_err(Error::GuestMemory)
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AlignedOperation {
|
||||
origin_ptr: u64,
|
||||
aligned_ptr: u64,
|
||||
size: usize,
|
||||
layout: Layout,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Request {
|
||||
pub request_type: RequestType,
|
||||
pub sector: u64,
|
||||
pub data_descriptors: SmallVec<[(GuestAddress, u32); 1]>,
|
||||
pub status_addr: GuestAddress,
|
||||
pub writeback: bool,
|
||||
pub aligned_operations: SmallVec<[AlignedOperation; 1]>,
|
||||
pub start: Instant,
|
||||
}
|
||||
|
||||
impl Request {
|
||||
pub fn parse(
|
||||
desc_chain: &mut DescriptorChain<GuestMemoryLoadGuard<GuestMemoryMmap>>,
|
||||
access_platform: Option<&Arc<dyn AccessPlatform>>,
|
||||
) -> result::Result<Request, Error> {
|
||||
let hdr_desc = desc_chain
|
||||
.next()
|
||||
.ok_or(Error::DescriptorChainTooShort)
|
||||
.map_err(|e| {
|
||||
error!("Missing head descriptor");
|
||||
e
|
||||
})?;
|
||||
|
||||
// The head contains the request type which MUST be readable.
|
||||
if hdr_desc.is_write_only() {
|
||||
return Err(Error::UnexpectedWriteOnlyDescriptor);
|
||||
}
|
||||
|
||||
let hdr_desc_addr = hdr_desc
|
||||
.addr()
|
||||
.translate_gva(access_platform, hdr_desc.len() as usize);
|
||||
|
||||
let mut req = Request {
|
||||
request_type: request_type(desc_chain.memory(), hdr_desc_addr)?,
|
||||
sector: sector(desc_chain.memory(), hdr_desc_addr)?,
|
||||
data_descriptors: SmallVec::with_capacity(1),
|
||||
status_addr: GuestAddress(0),
|
||||
writeback: true,
|
||||
aligned_operations: SmallVec::with_capacity(1),
|
||||
start: Instant::now(),
|
||||
};
|
||||
|
||||
let status_desc;
|
||||
let mut desc = desc_chain
|
||||
.next()
|
||||
.ok_or(Error::DescriptorChainTooShort)
|
||||
.map_err(|e| {
|
||||
error!("Only head descriptor present: request = {:?}", req);
|
||||
e
|
||||
})?;
|
||||
|
||||
if !desc.has_next() {
|
||||
status_desc = desc;
|
||||
// Only flush requests are allowed to skip the data descriptor.
|
||||
if req.request_type != RequestType::Flush {
|
||||
error!("Need a data descriptor: request = {:?}", req);
|
||||
return Err(Error::DescriptorChainTooShort);
|
||||
}
|
||||
} else {
|
||||
req.data_descriptors.reserve_exact(1);
|
||||
while desc.has_next() {
|
||||
if desc.is_write_only() && req.request_type == RequestType::Out {
|
||||
return Err(Error::UnexpectedWriteOnlyDescriptor);
|
||||
}
|
||||
if !desc.is_write_only() && req.request_type == RequestType::In {
|
||||
return Err(Error::UnexpectedReadOnlyDescriptor);
|
||||
}
|
||||
if !desc.is_write_only() && req.request_type == RequestType::GetDeviceId {
|
||||
return Err(Error::UnexpectedReadOnlyDescriptor);
|
||||
}
|
||||
|
||||
req.data_descriptors.push((
|
||||
desc.addr()
|
||||
.translate_gva(access_platform, desc.len() as usize),
|
||||
desc.len(),
|
||||
));
|
||||
desc = desc_chain
|
||||
.next()
|
||||
.ok_or(Error::DescriptorChainTooShort)
|
||||
.map_err(|e| {
|
||||
error!("DescriptorChain corrupted: request = {:?}", req);
|
||||
e
|
||||
})?;
|
||||
}
|
||||
status_desc = desc;
|
||||
}
|
||||
|
||||
// The status MUST always be writable.
|
||||
if !status_desc.is_write_only() {
|
||||
return Err(Error::UnexpectedReadOnlyDescriptor);
|
||||
}
|
||||
|
||||
if status_desc.len() < 1 {
|
||||
return Err(Error::DescriptorLengthTooSmall);
|
||||
}
|
||||
|
||||
req.status_addr = status_desc
|
||||
.addr()
|
||||
.translate_gva(access_platform, status_desc.len() as usize);
|
||||
|
||||
Ok(req)
|
||||
}
|
||||
|
||||
pub fn execute<T: Seek + Read + Write>(
|
||||
&self,
|
||||
disk: &mut T,
|
||||
disk_nsectors: u64,
|
||||
mem: &GuestMemoryMmap,
|
||||
disk_id: &[u8],
|
||||
) -> result::Result<u32, ExecuteError> {
|
||||
disk.seek(SeekFrom::Start(self.sector << SECTOR_SHIFT))
|
||||
.map_err(ExecuteError::Seek)?;
|
||||
let mut len = 0;
|
||||
for (data_addr, data_len) in &self.data_descriptors {
|
||||
let mut top: u64 = u64::from(*data_len) / SECTOR_SIZE;
|
||||
if u64::from(*data_len) % SECTOR_SIZE != 0 {
|
||||
top += 1;
|
||||
}
|
||||
top = top
|
||||
.checked_add(self.sector)
|
||||
.ok_or(ExecuteError::BadRequest(Error::InvalidOffset))?;
|
||||
if top > disk_nsectors {
|
||||
return Err(ExecuteError::BadRequest(Error::InvalidOffset));
|
||||
}
|
||||
|
||||
match self.request_type {
|
||||
RequestType::In => {
|
||||
mem.read_exact_from(*data_addr, disk, *data_len as usize)
|
||||
.map_err(ExecuteError::Read)?;
|
||||
len += data_len;
|
||||
}
|
||||
RequestType::Out => {
|
||||
mem.write_all_to(*data_addr, disk, *data_len as usize)
|
||||
.map_err(ExecuteError::Write)?;
|
||||
if !self.writeback {
|
||||
disk.flush().map_err(ExecuteError::Flush)?;
|
||||
}
|
||||
}
|
||||
RequestType::Flush => disk.flush().map_err(ExecuteError::Flush)?,
|
||||
RequestType::GetDeviceId => {
|
||||
if (*data_len as usize) < disk_id.len() {
|
||||
return Err(ExecuteError::BadRequest(Error::InvalidOffset));
|
||||
}
|
||||
mem.write_slice(disk_id, *data_addr)
|
||||
.map_err(ExecuteError::Write)?;
|
||||
}
|
||||
RequestType::Unsupported(t) => return Err(ExecuteError::Unsupported(t)),
|
||||
};
|
||||
}
|
||||
Ok(len)
|
||||
}
|
||||
|
||||
pub fn execute_async(
|
||||
&mut self,
|
||||
mem: &GuestMemoryMmap,
|
||||
disk_nsectors: u64,
|
||||
disk_image: &mut dyn AsyncIo,
|
||||
disk_id: &[u8],
|
||||
user_data: u64,
|
||||
) -> result::Result<bool, ExecuteError> {
|
||||
let sector = self.sector;
|
||||
let request_type = self.request_type;
|
||||
let offset = (sector << SECTOR_SHIFT) as libc::off_t;
|
||||
|
||||
let mut iovecs: SmallVec<[libc::iovec; 1]> =
|
||||
SmallVec::with_capacity(self.data_descriptors.len());
|
||||
for (data_addr, data_len) in &self.data_descriptors {
|
||||
if *data_len == 0 {
|
||||
continue;
|
||||
}
|
||||
let mut top: u64 = u64::from(*data_len) / SECTOR_SIZE;
|
||||
if u64::from(*data_len) % SECTOR_SIZE != 0 {
|
||||
top += 1;
|
||||
}
|
||||
top = top
|
||||
.checked_add(sector)
|
||||
.ok_or(ExecuteError::BadRequest(Error::InvalidOffset))?;
|
||||
if top > disk_nsectors {
|
||||
return Err(ExecuteError::BadRequest(Error::InvalidOffset));
|
||||
}
|
||||
|
||||
let origin_ptr = mem
|
||||
.get_slice(*data_addr, *data_len as usize)
|
||||
.map_err(ExecuteError::GetHostAddress)?
|
||||
.as_ptr();
|
||||
|
||||
// Verify the buffer alignment.
|
||||
// In case it's not properly aligned, an intermediate buffer is
|
||||
// created with the correct alignment, and a copy from/to the
|
||||
// origin buffer is performed, depending on the type of operation.
|
||||
let iov_base = if (origin_ptr as u64) % SECTOR_SIZE != 0 {
|
||||
let layout =
|
||||
Layout::from_size_align(*data_len as usize, SECTOR_SIZE as usize).unwrap();
|
||||
// SAFETY: layout has non-zero size
|
||||
let aligned_ptr = unsafe { alloc_zeroed(layout) };
|
||||
if aligned_ptr.is_null() {
|
||||
return Err(ExecuteError::TemporaryBufferAllocation(
|
||||
io::Error::last_os_error(),
|
||||
));
|
||||
}
|
||||
|
||||
// We need to perform the copy beforehand in case we're writing
|
||||
// data out.
|
||||
if request_type == RequestType::Out {
|
||||
// SAFETY: destination buffer has been allocated with
|
||||
// the proper size.
|
||||
unsafe {
|
||||
std::ptr::copy(origin_ptr as *const u8, aligned_ptr, *data_len as usize)
|
||||
};
|
||||
}
|
||||
|
||||
// Store both origin and aligned pointers for complete_async()
|
||||
// to process them.
|
||||
self.aligned_operations.push(AlignedOperation {
|
||||
origin_ptr: origin_ptr as u64,
|
||||
aligned_ptr: aligned_ptr as u64,
|
||||
size: *data_len as usize,
|
||||
layout,
|
||||
});
|
||||
|
||||
aligned_ptr as *mut libc::c_void
|
||||
} else {
|
||||
origin_ptr as *mut libc::c_void
|
||||
};
|
||||
|
||||
let iovec = libc::iovec {
|
||||
iov_base,
|
||||
iov_len: *data_len as libc::size_t,
|
||||
};
|
||||
iovecs.push(iovec);
|
||||
}
|
||||
|
||||
// Queue operations expected to be submitted.
|
||||
match request_type {
|
||||
RequestType::In => {
|
||||
for (data_addr, data_len) in &self.data_descriptors {
|
||||
mem.get_slice(*data_addr, *data_len as usize)
|
||||
.map_err(ExecuteError::GetHostAddress)?
|
||||
.bitmap()
|
||||
.mark_dirty(0, *data_len as usize);
|
||||
}
|
||||
disk_image
|
||||
.read_vectored(offset, &iovecs, user_data)
|
||||
.map_err(ExecuteError::AsyncRead)?;
|
||||
}
|
||||
RequestType::Out => {
|
||||
disk_image
|
||||
.write_vectored(offset, &iovecs, user_data)
|
||||
.map_err(ExecuteError::AsyncWrite)?;
|
||||
}
|
||||
RequestType::Flush => {
|
||||
disk_image
|
||||
.fsync(Some(user_data))
|
||||
.map_err(ExecuteError::AsyncFlush)?;
|
||||
}
|
||||
RequestType::GetDeviceId => {
|
||||
let (data_addr, data_len) = if self.data_descriptors.len() == 1 {
|
||||
(self.data_descriptors[0].0, self.data_descriptors[0].1)
|
||||
} else {
|
||||
return Err(ExecuteError::BadRequest(Error::TooManyDescriptors));
|
||||
};
|
||||
if (data_len as usize) < disk_id.len() {
|
||||
return Err(ExecuteError::BadRequest(Error::InvalidOffset));
|
||||
}
|
||||
mem.write_slice(disk_id, data_addr)
|
||||
.map_err(ExecuteError::Write)?;
|
||||
return Ok(false);
|
||||
}
|
||||
RequestType::Unsupported(t) => return Err(ExecuteError::Unsupported(t)),
|
||||
}
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
pub fn complete_async(&mut self) -> result::Result<(), Error> {
|
||||
for aligned_operation in self.aligned_operations.drain(..) {
|
||||
// We need to perform the copy after the data has been read inside
|
||||
// the aligned buffer in case we're reading data in.
|
||||
if self.request_type == RequestType::In {
|
||||
// SAFETY: origin buffer has been allocated with the
|
||||
// proper size.
|
||||
unsafe {
|
||||
std::ptr::copy(
|
||||
aligned_operation.aligned_ptr as *const u8,
|
||||
aligned_operation.origin_ptr as *mut u8,
|
||||
aligned_operation.size,
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
// Free the temporary aligned buffer.
|
||||
// SAFETY: aligned_ptr was allocated by alloc_zeroed with the same
|
||||
// layout
|
||||
unsafe {
|
||||
dealloc(
|
||||
aligned_operation.aligned_ptr as *mut u8,
|
||||
aligned_operation.layout,
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_writeback(&mut self, writeback: bool) {
|
||||
self.writeback = writeback
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default, Versionize)]
|
||||
#[repr(C, packed)]
|
||||
pub struct VirtioBlockConfig {
|
||||
pub capacity: u64,
|
||||
pub size_max: u32,
|
||||
pub seg_max: u32,
|
||||
pub geometry: VirtioBlockGeometry,
|
||||
pub blk_size: u32,
|
||||
pub physical_block_exp: u8,
|
||||
pub alignment_offset: u8,
|
||||
pub min_io_size: u16,
|
||||
pub opt_io_size: u32,
|
||||
pub writeback: u8,
|
||||
pub unused: u8,
|
||||
pub num_queues: u16,
|
||||
pub max_discard_sectors: u32,
|
||||
pub max_discard_seg: u32,
|
||||
pub discard_sector_alignment: u32,
|
||||
pub max_write_zeroes_sectors: u32,
|
||||
pub max_write_zeroes_seg: u32,
|
||||
pub write_zeroes_may_unmap: u8,
|
||||
pub unused1: [u8; 3],
|
||||
}
|
||||
#[derive(Copy, Clone, Debug, Default, Versionize)]
|
||||
#[repr(C, packed)]
|
||||
pub struct VirtioBlockGeometry {
|
||||
pub cylinders: u16,
|
||||
pub heads: u8,
|
||||
pub sectors: u8,
|
||||
}
|
||||
|
||||
// SAFETY: data structure only contain a series of integers
|
||||
unsafe impl ByteValued for VirtioBlockConfig {}
|
||||
// SAFETY: data structure only contain a series of integers
|
||||
unsafe impl ByteValued for VirtioBlockGeometry {}
|
||||
|
||||
/// Check if io_uring for block device can be used on the current system, as
|
||||
/// it correctly supports the expected io_uring features.
|
||||
pub fn block_io_uring_is_supported() -> bool {
|
||||
let error_msg = "io_uring not supported:";
|
||||
|
||||
// Check we can create an io_uring instance, which effectively verifies
|
||||
// that io_uring_setup() syscall is supported.
|
||||
let io_uring = match IoUring::new(1) {
|
||||
Ok(io_uring) => io_uring,
|
||||
Err(e) => {
|
||||
info!("{} failed to create io_uring instance: {}", error_msg, e);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
let submitter = io_uring.submitter();
|
||||
|
||||
let mut probe = Probe::new();
|
||||
|
||||
// Check we can register a probe to validate supported operations.
|
||||
match submitter.register_probe(&mut probe) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
info!("{} failed to register a probe: {}", error_msg, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check IORING_OP_FSYNC is supported
|
||||
if !probe.is_supported(opcode::Fsync::CODE) {
|
||||
info!("{} IORING_OP_FSYNC operation not supported", error_msg);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check IORING_OP_READV is supported
|
||||
if !probe.is_supported(opcode::Readv::CODE) {
|
||||
info!("{} IORING_OP_READV operation not supported", error_msg);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check IORING_OP_WRITEV is supported
|
||||
if !probe.is_supported(opcode::Writev::CODE) {
|
||||
info!("{} IORING_OP_WRITEV operation not supported", error_msg);
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
pub trait AsyncAdaptor<F>
|
||||
where
|
||||
F: Read + Write + Seek,
|
||||
{
|
||||
fn read_vectored_sync(
|
||||
&mut self,
|
||||
offset: libc::off_t,
|
||||
iovecs: &[libc::iovec],
|
||||
user_data: u64,
|
||||
eventfd: &EventFd,
|
||||
completion_list: &mut VecDeque<(u64, i32)>,
|
||||
) -> AsyncIoResult<()> {
|
||||
// Convert libc::iovec into IoSliceMut
|
||||
let mut slices: SmallVec<[IoSliceMut; 1]> = SmallVec::with_capacity(iovecs.len());
|
||||
for iovec in iovecs.iter() {
|
||||
// SAFETY: on Linux IoSliceMut wraps around libc::iovec
|
||||
slices.push(IoSliceMut::new(unsafe { std::mem::transmute(*iovec) }));
|
||||
}
|
||||
|
||||
let result = {
|
||||
let mut file = self.file();
|
||||
|
||||
// Move the cursor to the right offset
|
||||
file.seek(SeekFrom::Start(offset as u64))
|
||||
.map_err(AsyncIoError::ReadVectored)?;
|
||||
|
||||
// Read vectored
|
||||
file.read_vectored(slices.as_mut_slice())
|
||||
.map_err(AsyncIoError::ReadVectored)?
|
||||
};
|
||||
|
||||
completion_list.push_back((user_data, result as i32));
|
||||
eventfd.write(1).unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_vectored_sync(
|
||||
&mut self,
|
||||
offset: libc::off_t,
|
||||
iovecs: &[libc::iovec],
|
||||
user_data: u64,
|
||||
eventfd: &EventFd,
|
||||
completion_list: &mut VecDeque<(u64, i32)>,
|
||||
) -> AsyncIoResult<()> {
|
||||
// Convert libc::iovec into IoSlice
|
||||
let mut slices: SmallVec<[IoSlice; 1]> = SmallVec::with_capacity(iovecs.len());
|
||||
for iovec in iovecs.iter() {
|
||||
// SAFETY: on Linux IoSlice wraps around libc::iovec
|
||||
slices.push(IoSlice::new(unsafe { std::mem::transmute(*iovec) }));
|
||||
}
|
||||
|
||||
let result = {
|
||||
let mut file = self.file();
|
||||
|
||||
// Move the cursor to the right offset
|
||||
file.seek(SeekFrom::Start(offset as u64))
|
||||
.map_err(AsyncIoError::WriteVectored)?;
|
||||
|
||||
// Write vectored
|
||||
file.write_vectored(slices.as_slice())
|
||||
.map_err(AsyncIoError::WriteVectored)?
|
||||
};
|
||||
|
||||
completion_list.push_back((user_data, result as i32));
|
||||
eventfd.write(1).unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn fsync_sync(
|
||||
&mut self,
|
||||
user_data: Option<u64>,
|
||||
eventfd: &EventFd,
|
||||
completion_list: &mut VecDeque<(u64, i32)>,
|
||||
) -> AsyncIoResult<()> {
|
||||
let result: i32 = {
|
||||
let mut file = self.file();
|
||||
|
||||
// Flush
|
||||
file.flush().map_err(AsyncIoError::Fsync)?;
|
||||
|
||||
0
|
||||
};
|
||||
|
||||
if let Some(user_data) = user_data {
|
||||
completion_list.push_back((user_data, result));
|
||||
eventfd.write(1).unwrap();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn file(&mut self) -> MutexGuard<F>;
|
||||
}
|
||||
|
||||
pub enum ImageType {
|
||||
FixedVhd,
|
||||
Qcow2,
|
||||
Raw,
|
||||
Vhdx,
|
||||
}
|
||||
|
||||
const QCOW_MAGIC: u32 = 0x5146_49fb;
|
||||
const VHDX_SIGN: u64 = 0x656C_6966_7864_6876;
|
||||
|
||||
/// Determine image type through file parsing.
|
||||
pub fn detect_image_type(f: &mut File) -> std::io::Result<ImageType> {
|
||||
// We must create a buffer aligned on 512 bytes with a size being a
|
||||
// multiple of 512 bytes as the file might be opened with O_DIRECT flag.
|
||||
#[repr(align(512))]
|
||||
struct Sector {
|
||||
data: [u8; 512],
|
||||
}
|
||||
let mut s = Sector { data: [0; 512] };
|
||||
|
||||
f.read_exact(&mut s.data)?;
|
||||
|
||||
// Check 4 first bytes to get the header value and determine the image type
|
||||
let image_type = if u32::from_be_bytes(s.data[0..4].try_into().unwrap()) == QCOW_MAGIC {
|
||||
ImageType::Qcow2
|
||||
} else if vhd::is_fixed_vhd(f)? {
|
||||
ImageType::FixedVhd
|
||||
} else if u64::from_le_bytes(s.data[0..8].try_into().unwrap()) == VHDX_SIGN {
|
||||
ImageType::Vhdx
|
||||
} else {
|
||||
ImageType::Raw
|
||||
};
|
||||
|
||||
Ok(image_type)
|
||||
}
|
||||
104
block_util/src/qcow_sync.rs
Normal file
104
block_util/src/qcow_sync.rs
Normal file
@@ -0,0 +1,104 @@
|
||||
// Copyright © 2021 Intel Corporation
|
||||
//
|
||||
// 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::fs::File;
|
||||
use std::io::{Seek, SeekFrom};
|
||||
use std::sync::{Arc, Mutex, MutexGuard};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
pub struct QcowDiskSync {
|
||||
qcow_file: Arc<Mutex<QcowFile>>,
|
||||
}
|
||||
|
||||
impl QcowDiskSync {
|
||||
pub fn new(file: File, direct_io: bool) -> QcowResult<Self> {
|
||||
Ok(QcowDiskSync {
|
||||
qcow_file: Arc::new(Mutex::new(QcowFile::from(RawFile::new(file, direct_io))?)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl DiskFile for QcowDiskSync {
|
||||
fn size(&mut self) -> DiskFileResult<u64> {
|
||||
let mut file = self.qcow_file.lock().unwrap();
|
||||
|
||||
file.seek(SeekFrom::End(0)).map_err(DiskFileError::Size)
|
||||
}
|
||||
|
||||
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>)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct QcowSync {
|
||||
qcow_file: Arc<Mutex<QcowFile>>,
|
||||
eventfd: EventFd,
|
||||
completion_list: VecDeque<(u64, i32)>,
|
||||
}
|
||||
|
||||
impl QcowSync {
|
||||
pub fn new(qcow_file: Arc<Mutex<QcowFile>>) -> Self {
|
||||
QcowSync {
|
||||
qcow_file,
|
||||
eventfd: EventFd::new(libc::EFD_NONBLOCK)
|
||||
.expect("Failed creating EventFd for QcowSync"),
|
||||
completion_list: VecDeque::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncAdaptor<QcowFile> for Arc<Mutex<QcowFile>> {
|
||||
fn file(&mut self) -> MutexGuard<QcowFile> {
|
||||
self.lock().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncIo for QcowSync {
|
||||
fn notifier(&self) -> &EventFd {
|
||||
&self.eventfd
|
||||
}
|
||||
|
||||
fn read_vectored(
|
||||
&mut self,
|
||||
offset: libc::off_t,
|
||||
iovecs: &[libc::iovec],
|
||||
user_data: u64,
|
||||
) -> AsyncIoResult<()> {
|
||||
self.qcow_file.read_vectored_sync(
|
||||
offset,
|
||||
iovecs,
|
||||
user_data,
|
||||
&self.eventfd,
|
||||
&mut self.completion_list,
|
||||
)
|
||||
}
|
||||
|
||||
fn write_vectored(
|
||||
&mut self,
|
||||
offset: libc::off_t,
|
||||
iovecs: &[libc::iovec],
|
||||
user_data: u64,
|
||||
) -> AsyncIoResult<()> {
|
||||
self.qcow_file.write_vectored_sync(
|
||||
offset,
|
||||
iovecs,
|
||||
user_data,
|
||||
&self.eventfd,
|
||||
&mut self.completion_list,
|
||||
)
|
||||
}
|
||||
|
||||
fn fsync(&mut self, user_data: Option<u64>) -> AsyncIoResult<()> {
|
||||
self.qcow_file
|
||||
.fsync_sync(user_data, &self.eventfd, &mut self.completion_list)
|
||||
}
|
||||
|
||||
fn next_completed_request(&mut self) -> Option<(u64, i32)> {
|
||||
self.completion_list.pop_front()
|
||||
}
|
||||
}
|
||||
164
block_util/src/raw_async.rs
Normal file
164
block_util/src/raw_async.rs
Normal file
@@ -0,0 +1,164 @@
|
||||
// Copyright © 2021 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||
|
||||
use crate::async_io::{
|
||||
AsyncIo, AsyncIoError, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult, 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 {
|
||||
file: File,
|
||||
}
|
||||
|
||||
impl RawFileDisk {
|
||||
pub fn new(file: File) -> Self {
|
||||
RawFileDisk { file }
|
||||
}
|
||||
}
|
||||
|
||||
impl DiskFile for RawFileDisk {
|
||||
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(
|
||||
RawFileAsync::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(&mut self.file) {
|
||||
topology
|
||||
} else {
|
||||
warn!("Unable to get device topology. Using default topology");
|
||||
DiskTopology::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RawFileAsync {
|
||||
fd: RawFd,
|
||||
io_uring: IoUring,
|
||||
eventfd: EventFd,
|
||||
}
|
||||
|
||||
impl RawFileAsync {
|
||||
pub fn new(fd: RawFd, ring_depth: u32) -> std::io::Result<Self> {
|
||||
let io_uring = IoUring::new(ring_depth)?;
|
||||
let eventfd = EventFd::new(libc::EFD_NONBLOCK)?;
|
||||
|
||||
// Register the io_uring eventfd that will notify when something in
|
||||
// the completion queue is ready.
|
||||
io_uring.submitter().register_eventfd(eventfd.as_raw_fd())?;
|
||||
|
||||
Ok(RawFileAsync {
|
||||
fd,
|
||||
io_uring,
|
||||
eventfd,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncIo for RawFileAsync {
|
||||
fn notifier(&self) -> &EventFd {
|
||||
&self.eventfd
|
||||
}
|
||||
|
||||
fn read_vectored(
|
||||
&mut self,
|
||||
offset: libc::off_t,
|
||||
iovecs: &[libc::iovec],
|
||||
user_data: u64,
|
||||
) -> AsyncIoResult<()> {
|
||||
let (submitter, mut sq, _) = self.io_uring.split();
|
||||
|
||||
// SAFETY: we know the file descriptor is valid and we
|
||||
// relied on vm-memory to provide the buffer address.
|
||||
let _ = unsafe {
|
||||
sq.push(
|
||||
&opcode::Readv::new(types::Fd(self.fd), iovecs.as_ptr(), iovecs.len() as u32)
|
||||
.offset(offset)
|
||||
.build()
|
||||
.flags(squeue::Flags::ASYNC)
|
||||
.user_data(user_data),
|
||||
)
|
||||
};
|
||||
|
||||
// Update the submission queue and submit new operations to the
|
||||
// io_uring instance.
|
||||
sq.sync();
|
||||
submitter.submit().map_err(AsyncIoError::ReadVectored)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_vectored(
|
||||
&mut self,
|
||||
offset: libc::off_t,
|
||||
iovecs: &[libc::iovec],
|
||||
user_data: u64,
|
||||
) -> AsyncIoResult<()> {
|
||||
let (submitter, mut sq, _) = self.io_uring.split();
|
||||
|
||||
// SAFETY: we know the file descriptor is valid and we
|
||||
// relied on vm-memory to provide the buffer address.
|
||||
let _ = unsafe {
|
||||
sq.push(
|
||||
&opcode::Writev::new(types::Fd(self.fd), iovecs.as_ptr(), iovecs.len() as u32)
|
||||
.offset(offset)
|
||||
.build()
|
||||
.flags(squeue::Flags::ASYNC)
|
||||
.user_data(user_data),
|
||||
)
|
||||
};
|
||||
|
||||
// Update the submission queue and submit new operations to the
|
||||
// io_uring instance.
|
||||
sq.sync();
|
||||
submitter.submit().map_err(AsyncIoError::WriteVectored)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn fsync(&mut self, user_data: Option<u64>) -> AsyncIoResult<()> {
|
||||
if let Some(user_data) = user_data {
|
||||
let (submitter, mut sq, _) = self.io_uring.split();
|
||||
|
||||
// SAFETY: we know the file descriptor is valid.
|
||||
let _ = unsafe {
|
||||
sq.push(
|
||||
&opcode::Fsync::new(types::Fd(self.fd))
|
||||
.build()
|
||||
.flags(squeue::Flags::ASYNC)
|
||||
.user_data(user_data),
|
||||
)
|
||||
};
|
||||
|
||||
// Update the submission queue and submit new operations to the
|
||||
// io_uring instance.
|
||||
sq.sync();
|
||||
submitter.submit().map_err(AsyncIoError::Fsync)?;
|
||||
} else {
|
||||
// SAFETY: FFI call with a valid fd
|
||||
unsafe { libc::fsync(self.fd) };
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn next_completed_request(&mut self) -> Option<(u64, i32)> {
|
||||
self.io_uring
|
||||
.completion()
|
||||
.next()
|
||||
.map(|entry| (entry.user_data(), entry.result()))
|
||||
}
|
||||
}
|
||||
134
block_util/src/raw_sync.rs
Normal file
134
block_util/src/raw_sync.rs
Normal file
@@ -0,0 +1,134 @@
|
||||
// Copyright © 2021 Intel Corporation
|
||||
//
|
||||
// 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::fs::File;
|
||||
use std::io::{Seek, SeekFrom};
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
pub struct RawFileDiskSync {
|
||||
file: File,
|
||||
}
|
||||
|
||||
impl RawFileDiskSync {
|
||||
pub fn new(file: File) -> Self {
|
||||
RawFileDiskSync { file }
|
||||
}
|
||||
}
|
||||
|
||||
impl DiskFile for RawFileDiskSync {
|
||||
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(RawFileSync::new(self.file.as_raw_fd())) as Box<dyn AsyncIo>)
|
||||
}
|
||||
|
||||
fn topology(&mut self) -> DiskTopology {
|
||||
if let Ok(topology) = DiskTopology::probe(&mut self.file) {
|
||||
topology
|
||||
} else {
|
||||
warn!("Unable to get device topology. Using default topology");
|
||||
DiskTopology::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RawFileSync {
|
||||
fd: RawFd,
|
||||
eventfd: EventFd,
|
||||
completion_list: VecDeque<(u64, i32)>,
|
||||
}
|
||||
|
||||
impl RawFileSync {
|
||||
pub fn new(fd: RawFd) -> Self {
|
||||
RawFileSync {
|
||||
fd,
|
||||
eventfd: EventFd::new(libc::EFD_NONBLOCK).expect("Failed creating EventFd for RawFile"),
|
||||
completion_list: VecDeque::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncIo for RawFileSync {
|
||||
fn notifier(&self) -> &EventFd {
|
||||
&self.eventfd
|
||||
}
|
||||
|
||||
fn read_vectored(
|
||||
&mut self,
|
||||
offset: libc::off_t,
|
||||
iovecs: &[libc::iovec],
|
||||
user_data: u64,
|
||||
) -> AsyncIoResult<()> {
|
||||
// SAFETY: FFI call with valid arguments
|
||||
let result = unsafe {
|
||||
libc::preadv(
|
||||
self.fd as libc::c_int,
|
||||
iovecs.as_ptr() as *const libc::iovec,
|
||||
iovecs.len() as libc::c_int,
|
||||
offset,
|
||||
)
|
||||
};
|
||||
if result < 0 {
|
||||
return Err(AsyncIoError::ReadVectored(std::io::Error::last_os_error()));
|
||||
}
|
||||
|
||||
self.completion_list.push_back((user_data, result as i32));
|
||||
self.eventfd.write(1).unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_vectored(
|
||||
&mut self,
|
||||
offset: libc::off_t,
|
||||
iovecs: &[libc::iovec],
|
||||
user_data: u64,
|
||||
) -> AsyncIoResult<()> {
|
||||
// SAFETY: FFI call with valid arguments
|
||||
let result = unsafe {
|
||||
libc::pwritev(
|
||||
self.fd as libc::c_int,
|
||||
iovecs.as_ptr() as *const libc::iovec,
|
||||
iovecs.len() as libc::c_int,
|
||||
offset,
|
||||
)
|
||||
};
|
||||
if result < 0 {
|
||||
return Err(AsyncIoError::WriteVectored(std::io::Error::last_os_error()));
|
||||
}
|
||||
|
||||
self.completion_list.push_back((user_data, result as i32));
|
||||
self.eventfd.write(1).unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn fsync(&mut self, user_data: Option<u64>) -> AsyncIoResult<()> {
|
||||
// SAFETY: FFI call
|
||||
let result = unsafe { libc::fsync(self.fd as libc::c_int) };
|
||||
if result < 0 {
|
||||
return Err(AsyncIoError::Fsync(std::io::Error::last_os_error()));
|
||||
}
|
||||
|
||||
if let Some(user_data) = user_data {
|
||||
self.completion_list.push_back((user_data, result));
|
||||
self.eventfd.write(1).unwrap();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn next_completed_request(&mut self) -> Option<(u64, i32)> {
|
||||
self.completion_list.pop_front()
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,9 @@
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::convert::TryInto;
|
||||
use std::fs::File;
|
||||
use std::io::{Seek, SeekFrom};
|
||||
|
||||
use crate::{DiskTopology, read_aligned_block_size};
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct VhdFooter {
|
||||
@@ -28,33 +27,37 @@ pub struct VhdFooter {
|
||||
|
||||
impl VhdFooter {
|
||||
pub fn new(file: &mut File) -> std::io::Result<VhdFooter> {
|
||||
let blocksize = DiskTopology::probe(file)?.logical_block_size as usize;
|
||||
// We must create a buffer aligned on 512 bytes with a size being a
|
||||
// multiple of 512 bytes as the file might be opened with O_DIRECT flag.
|
||||
#[repr(align(512))]
|
||||
struct Sector {
|
||||
data: [u8; 512],
|
||||
}
|
||||
let mut s = Sector { data: [0; 512] };
|
||||
|
||||
// Place the cursor in the last block of the file
|
||||
file.seek(SeekFrom::End(0 - (blocksize as i64)))?;
|
||||
// Read in the last block
|
||||
let data = read_aligned_block_size(file)?;
|
||||
// Place the cursor 512 bytes before the end of the file, as this is
|
||||
// where the footer starts.
|
||||
file.seek(SeekFrom::End(-512))?;
|
||||
|
||||
// We only care about the last sector
|
||||
let offset = blocksize - 512;
|
||||
let sector = &data[offset..];
|
||||
// Fill in the VhdFooter structure
|
||||
file.read_exact(&mut s.data)?;
|
||||
|
||||
Ok(VhdFooter {
|
||||
cookie: u64::from_be_bytes(sector[0..8].try_into().unwrap()),
|
||||
features: u32::from_be_bytes(sector[8..12].try_into().unwrap()),
|
||||
file_format_version: u32::from_be_bytes(sector[12..16].try_into().unwrap()),
|
||||
data_offset: u64::from_be_bytes(sector[16..24].try_into().unwrap()),
|
||||
time_stamp: u32::from_be_bytes(sector[24..28].try_into().unwrap()),
|
||||
creator_application: u32::from_be_bytes(sector[28..32].try_into().unwrap()),
|
||||
creator_version: u32::from_be_bytes(sector[32..36].try_into().unwrap()),
|
||||
creator_host_os: u32::from_be_bytes(sector[36..40].try_into().unwrap()),
|
||||
original_size: u64::from_be_bytes(sector[40..48].try_into().unwrap()),
|
||||
current_size: u64::from_be_bytes(sector[48..56].try_into().unwrap()),
|
||||
disk_geometry: u32::from_be_bytes(sector[56..60].try_into().unwrap()),
|
||||
disk_type: u32::from_be_bytes(sector[60..64].try_into().unwrap()),
|
||||
checksum: u32::from_be_bytes(sector[64..68].try_into().unwrap()),
|
||||
unique_id: u128::from_be_bytes(sector[68..84].try_into().unwrap()),
|
||||
saved_state: u8::from_be_bytes(sector[84..85].try_into().unwrap()),
|
||||
cookie: u64::from_be_bytes(s.data[0..8].try_into().unwrap()),
|
||||
features: u32::from_be_bytes(s.data[8..12].try_into().unwrap()),
|
||||
file_format_version: u32::from_be_bytes(s.data[12..16].try_into().unwrap()),
|
||||
data_offset: u64::from_be_bytes(s.data[16..24].try_into().unwrap()),
|
||||
time_stamp: u32::from_be_bytes(s.data[24..28].try_into().unwrap()),
|
||||
creator_application: u32::from_be_bytes(s.data[28..32].try_into().unwrap()),
|
||||
creator_version: u32::from_be_bytes(s.data[32..36].try_into().unwrap()),
|
||||
creator_host_os: u32::from_be_bytes(s.data[36..40].try_into().unwrap()),
|
||||
original_size: u64::from_be_bytes(s.data[40..48].try_into().unwrap()),
|
||||
current_size: u64::from_be_bytes(s.data[48..56].try_into().unwrap()),
|
||||
disk_geometry: u32::from_be_bytes(s.data[56..60].try_into().unwrap()),
|
||||
disk_type: u32::from_be_bytes(s.data[60..64].try_into().unwrap()),
|
||||
checksum: u32::from_be_bytes(s.data[64..68].try_into().unwrap()),
|
||||
unique_id: u128::from_be_bytes(s.data[68..84].try_into().unwrap()),
|
||||
saved_state: u8::from_be_bytes(s.data[84..85].try_into().unwrap()),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -117,14 +120,12 @@ pub fn is_fixed_vhd(f: &mut File) -> std::io::Result<bool> {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod unit_tests {
|
||||
mod tests {
|
||||
use super::{is_fixed_vhd, VhdFooter};
|
||||
use std::fs::File;
|
||||
use std::io::{Seek, SeekFrom, Write};
|
||||
|
||||
use vmm_sys_util::tempfile::TempFile;
|
||||
|
||||
use super::{VhdFooter, is_fixed_vhd};
|
||||
|
||||
fn valid_fixed_vhd_footer() -> Vec<u8> {
|
||||
vec![
|
||||
0x63, 0x6f, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x78, // cookie
|
||||
@@ -2,43 +2,29 @@
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::async_io::{AsyncIo, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult};
|
||||
use crate::AsyncAdaptor;
|
||||
use std::collections::VecDeque;
|
||||
use std::fs::File;
|
||||
use std::os::fd::AsRawFd;
|
||||
|
||||
use std::sync::{Arc, Mutex, MutexGuard};
|
||||
use vhdx::vhdx::{Result as VhdxResult, Vhdx};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
use crate::async_io::{
|
||||
AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult,
|
||||
};
|
||||
use crate::vhdx::{Result as VhdxResult, Vhdx};
|
||||
use crate::{AsyncAdaptor, BlockBackend, Error};
|
||||
|
||||
pub struct VhdxDiskSync {
|
||||
vhdx_file: Vhdx,
|
||||
vhdx_file: Arc<Mutex<Vhdx>>,
|
||||
}
|
||||
|
||||
impl VhdxDiskSync {
|
||||
pub fn new(f: File) -> VhdxResult<Self> {
|
||||
Ok(VhdxDiskSync {
|
||||
vhdx_file: Vhdx::new(f)?,
|
||||
vhdx_file: Arc::new(Mutex::new(Vhdx::new(f)?)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl DiskFile for VhdxDiskSync {
|
||||
fn logical_size(&mut self) -> DiskFileResult<u64> {
|
||||
Ok(self.vhdx_file.virtual_disk_size())
|
||||
}
|
||||
|
||||
fn physical_size(&mut self) -> DiskFileResult<u64> {
|
||||
self.vhdx_file.physical_size().map_err(|e| {
|
||||
let io_inner = match e {
|
||||
Error::GetFileMetadata(e) => e,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
DiskFileError::Size(io_inner)
|
||||
})
|
||||
fn size(&mut self) -> DiskFileResult<u64> {
|
||||
Ok(self.vhdx_file.lock().unwrap().virtual_disk_size())
|
||||
}
|
||||
|
||||
fn new_async_io(&self, _ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
|
||||
@@ -47,20 +33,16 @@ impl DiskFile for VhdxDiskSync {
|
||||
as Box<dyn AsyncIo>,
|
||||
)
|
||||
}
|
||||
|
||||
fn fd(&mut self) -> BorrowedDiskFd<'_> {
|
||||
BorrowedDiskFd::new(self.vhdx_file.as_raw_fd())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct VhdxSync {
|
||||
vhdx_file: Vhdx,
|
||||
vhdx_file: Arc<Mutex<Vhdx>>,
|
||||
eventfd: EventFd,
|
||||
completion_list: VecDeque<(u64, i32)>,
|
||||
}
|
||||
|
||||
impl VhdxSync {
|
||||
pub fn new(vhdx_file: Vhdx) -> std::io::Result<Self> {
|
||||
pub fn new(vhdx_file: Arc<Mutex<Vhdx>>) -> std::io::Result<Self> {
|
||||
Ok(VhdxSync {
|
||||
vhdx_file,
|
||||
eventfd: EventFd::new(libc::EFD_NONBLOCK)?,
|
||||
@@ -69,7 +51,11 @@ impl VhdxSync {
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncAdaptor for Vhdx {}
|
||||
impl AsyncAdaptor<Vhdx> for Arc<Mutex<Vhdx>> {
|
||||
fn file(&mut self) -> MutexGuard<Vhdx> {
|
||||
self.lock().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncIo for VhdxSync {
|
||||
fn notifier(&self) -> &EventFd {
|
||||
@@ -114,16 +100,4 @@ impl AsyncIo for VhdxSync {
|
||||
fn next_completed_request(&mut self) -> Option<(u64, i32)> {
|
||||
self.completion_list.pop_front()
|
||||
}
|
||||
|
||||
fn punch_hole(&mut self, _offset: u64, _length: u64, _user_data: u64) -> AsyncIoResult<()> {
|
||||
Err(AsyncIoError::PunchHole(std::io::Error::other(
|
||||
"punch_hole not supported for VHDX",
|
||||
)))
|
||||
}
|
||||
|
||||
fn write_zeroes(&mut self, _offset: u64, _length: u64, _user_data: u64) -> AsyncIoResult<()> {
|
||||
Err(AsyncIoError::WriteZeroes(std::io::Error::other(
|
||||
"write_zeroes not supported for VHDX",
|
||||
)))
|
||||
}
|
||||
}
|
||||
24
build.rs
Normal file
24
build.rs
Normal file
@@ -0,0 +1,24 @@
|
||||
// Copyright © 2020 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
use std::process::Command;
|
||||
|
||||
fn main() {
|
||||
let mut version = "v".to_owned() + env!("CARGO_PKG_VERSION");
|
||||
|
||||
if let Ok(git_out) = Command::new("git").args(["describe", "--dirty"]).output() {
|
||||
if git_out.status.success() {
|
||||
if let Ok(git_out_str) = String::from_utf8(git_out.stdout) {
|
||||
version = git_out_str;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This println!() has a special behavior, as it will set the environment
|
||||
// variable BUILT_VERSION, so that it can be reused from the binary.
|
||||
// Particularly, this is used from src/main.rs to display the exact
|
||||
// version.
|
||||
println!("cargo:rustc-env=BUILT_VERSION={version}");
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
[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 = "2024"
|
||||
homepage = "https://github.com/cloud-hypervisor/cloud-hypervisor"
|
||||
license = "Apache-2.0 AND BSD-3-Clause"
|
||||
name = "cloud-hypervisor"
|
||||
version = "51.1.0"
|
||||
# Minimum buildable version:
|
||||
# Keep in sync with version in .github/workflows/build.yaml
|
||||
# Policy on MSRV (see #4318):
|
||||
# Can only be bumped if satisfying any of the following:
|
||||
# a.) A dependency requires it,
|
||||
# b.) If we want to use a new feature and that MSRV is at least 6 months old,
|
||||
# c.) There is a security issue that is addressed by the toolchain update.
|
||||
rust-version = "1.89.0"
|
||||
|
||||
[dependencies]
|
||||
anyhow = { workspace = true }
|
||||
api_client = { path = "../api_client" }
|
||||
clap = { workspace = true, features = ["string"] }
|
||||
dhat = { workspace = true, optional = true }
|
||||
env_logger = { workspace = true }
|
||||
epoll = { workspace = true }
|
||||
event_monitor = { path = "../event_monitor" }
|
||||
hypervisor = { path = "../hypervisor" }
|
||||
libc = { workspace = true }
|
||||
log = { workspace = true, features = ["std"] }
|
||||
option_parser = { path = "../option_parser" }
|
||||
seccompiler = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
signal-hook = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tpm = { path = "../tpm" }
|
||||
tracer = { path = "../tracer" }
|
||||
vm-memory = { workspace = true }
|
||||
vmm = { path = "../vmm" }
|
||||
vmm-sys-util = { workspace = true }
|
||||
zbus = { version = "5.13.2", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
block = { path = "../block" }
|
||||
dirs = { workspace = true }
|
||||
net_util = { path = "../net_util" }
|
||||
serde_json = { workspace = true }
|
||||
test_infra = { path = "../test_infra" }
|
||||
wait-timeout = { workspace = true }
|
||||
|
||||
# Please adjust `vmm::feature_list()` accordingly when changing the
|
||||
# feature list below
|
||||
[features]
|
||||
dbus_api = ["vmm/dbus_api", "zbus"]
|
||||
default = ["io_uring", "kvm"]
|
||||
dhat-heap = ["dhat", "vmm/dhat-heap"] # For heap profiling
|
||||
fw_cfg = ["vmm/fw_cfg"]
|
||||
guest_debug = ["vmm/guest_debug"]
|
||||
igvm = ["mshv", "vmm/igvm"]
|
||||
io_uring = ["vmm/io_uring"]
|
||||
ivshmem = ["vmm/ivshmem"]
|
||||
kvm = ["vmm/kvm"]
|
||||
mshv = ["vmm/mshv"]
|
||||
pvmemcontrol = ["vmm/pvmemcontrol"]
|
||||
sev_snp = ["igvm", "mshv", "vmm/sev_snp"]
|
||||
tdx = ["vmm/tdx"]
|
||||
tracing = ["tracer/tracing", "vmm/tracing"]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -1,32 +0,0 @@
|
||||
// Copyright © 2020 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
use std::env;
|
||||
use std::process::Command;
|
||||
|
||||
fn main() {
|
||||
let mut version = "v".to_owned() + env!("CARGO_PKG_VERSION");
|
||||
|
||||
if let Ok(git_out) = Command::new("git").args(["describe", "--dirty"]).output()
|
||||
&& git_out.status.success()
|
||||
&& let Ok(git_out_str) = String::from_utf8(git_out.stdout)
|
||||
{
|
||||
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
|
||||
// variable BUILD_VERSION, so that it can be reused from the binary.
|
||||
// Particularly, this is used from src/main.rs to display the exact
|
||||
// version.
|
||||
println!("cargo:rustc-env=BUILD_VERSION={version}");
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,46 +0,0 @@
|
||||
// Copyright © 2025 Cyberus Technology GmbH
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::error::Error;
|
||||
|
||||
use log::error;
|
||||
|
||||
/// Prints a chain of errors to the user in a consistent manner.
|
||||
/// The user will see a clear chain of errors, followed by debug output
|
||||
/// for opening issues.
|
||||
pub fn cli_print_error_chain<'a>(
|
||||
top_error: &'a (dyn Error + 'static),
|
||||
component: &str,
|
||||
// Function optionally returning the display representation of an error.
|
||||
display_modifier: impl Fn(
|
||||
/* level */ usize,
|
||||
/*indention */ usize,
|
||||
&'a (dyn Error + 'static),
|
||||
) -> Option<String>,
|
||||
) {
|
||||
// Debug info.
|
||||
error!("Fatal error: {top_error:?}");
|
||||
|
||||
eprint!("Error: {component} exited with the following ");
|
||||
if top_error.source().is_none() {
|
||||
eprintln!("error:");
|
||||
eprintln!(" {top_error}");
|
||||
} else {
|
||||
eprintln!("chain of errors:");
|
||||
std::iter::successors(Some(top_error), |sub_error| {
|
||||
// Dereference necessary to mitigate rustc compiler bug.
|
||||
// See <https://github.com/rust-lang/rust/issues/141673>
|
||||
(*sub_error).source()
|
||||
})
|
||||
.enumerate()
|
||||
.for_each(|(level, error)| {
|
||||
// Special case: handling of HTTP Server responses in ch-remote
|
||||
if let Some(message) = display_modifier(level, 2, error) {
|
||||
eprintln!("{message}");
|
||||
} else {
|
||||
eprintln!(" {level}: {error}");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
// Copyright © 2025 Cyberus Technology GmbH
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
//! Test utilities.
|
||||
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use clap::Arg;
|
||||
|
||||
/// Ensures that all [`Arg`]s are sorted alphabetically.
|
||||
pub fn assert_args_sorted<'a, F: Fn() -> R, R: Iterator<Item = &'a Arg>>(get_base_iter: F) {
|
||||
let iter = get_base_iter().zip(get_base_iter().skip(1));
|
||||
for (arg, next) in iter {
|
||||
assert_ne!(
|
||||
arg.get_id().cmp(next.get_id()),
|
||||
Ordering::Greater,
|
||||
"args not alphabetically sorted: arg={}, next={}",
|
||||
arg.get_id(),
|
||||
next.get_id()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,53 +1,29 @@
|
||||
[package]
|
||||
authors = ["The Chromium OS Authors"]
|
||||
edition.workspace = true
|
||||
name = "devices"
|
||||
version = "0.1.0"
|
||||
authors = ["The Chromium OS Authors"]
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
acpi_tables = { workspace = true }
|
||||
anyhow = { workspace = true }
|
||||
acpi_tables = { git = "https://github.com/rust-vmm/acpi_tables", branch = "main" }
|
||||
anyhow = "1.0.69"
|
||||
arch = { path = "../arch" }
|
||||
bitfield-struct = { version = "0.12.0", optional = true }
|
||||
bitflags = { workspace = true }
|
||||
byteorder = { workspace = true }
|
||||
event_monitor = { path = "../event_monitor" }
|
||||
bitflags = "1.3.2"
|
||||
byteorder = "1.4.3"
|
||||
hypervisor = { path = "../hypervisor" }
|
||||
libc = { workspace = true }
|
||||
linux-loader = { workspace = true, features = [
|
||||
"bzimage",
|
||||
"elf",
|
||||
"pe",
|
||||
], optional = true }
|
||||
log = { workspace = true }
|
||||
num_enum = "0.7.5"
|
||||
pci = { path = "../pci" }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
thiserror = { workspace = true }
|
||||
libc = "0.2.139"
|
||||
log = "0.4.17"
|
||||
thiserror = "1.0.38"
|
||||
tpm = { path = "../tpm" }
|
||||
vm-allocator = { path = "../vm-allocator" }
|
||||
versionize = "0.1.9"
|
||||
versionize_derive = "0.1.4"
|
||||
vm-device = { path = "../vm-device" }
|
||||
vm-memory = { workspace = true, features = [
|
||||
"backend-atomic",
|
||||
"backend-bitmap",
|
||||
"backend-mmap",
|
||||
] }
|
||||
vm-memory = "0.10.0"
|
||||
vm-migration = { path = "../vm-migration" }
|
||||
vmm-sys-util = { workspace = true }
|
||||
zerocopy = { version = "0.8.39", features = [
|
||||
"alloc",
|
||||
"derive",
|
||||
], optional = 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" }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
fw_cfg = ["arch/fw_cfg", "bitfield-struct", "linux-loader", "zerocopy"]
|
||||
ivshmem = []
|
||||
kvm = ["arch/kvm"]
|
||||
pvmemcontrol = []
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -3,40 +3,29 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use super::AcpiNotificationFlags;
|
||||
use acpi_tables::{aml, aml::Aml};
|
||||
use std::sync::{Arc, Barrier};
|
||||
use std::thread;
|
||||
use std::time::Instant;
|
||||
|
||||
use acpi_tables::{Aml, AmlSink, aml};
|
||||
use log::{error, info, warn};
|
||||
use vm_device::BusDevice;
|
||||
use vm_device::interrupt::InterruptSourceGroup;
|
||||
use vm_device::BusDevice;
|
||||
use vm_memory::GuestAddress;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
use super::AcpiNotificationFlags;
|
||||
|
||||
pub const GED_DEVICE_ACPI_SIZE: usize = 0x1;
|
||||
|
||||
/// A device for handling ACPI shutdown and reboot
|
||||
pub struct AcpiShutdownDevice {
|
||||
exit_evt: EventFd,
|
||||
reset_evt: EventFd,
|
||||
vcpus_kill_signalled: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl AcpiShutdownDevice {
|
||||
/// Constructs a device that will signal the given event when the guest requests it.
|
||||
pub fn new(
|
||||
exit_evt: EventFd,
|
||||
reset_evt: EventFd,
|
||||
vcpus_kill_signalled: Arc<AtomicBool>,
|
||||
) -> AcpiShutdownDevice {
|
||||
pub fn new(exit_evt: EventFd, reset_evt: EventFd) -> AcpiShutdownDevice {
|
||||
AcpiShutdownDevice {
|
||||
exit_evt,
|
||||
reset_evt,
|
||||
vcpus_kill_signalled,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -45,21 +34,14 @@ impl AcpiShutdownDevice {
|
||||
impl BusDevice for AcpiShutdownDevice {
|
||||
// Spec has all fields as zero
|
||||
fn read(&mut self, _base: u64, _offset: u64, data: &mut [u8]) {
|
||||
data.fill(0);
|
||||
data.fill(0)
|
||||
}
|
||||
|
||||
fn write(&mut self, _base: u64, _offset: u64, data: &[u8]) -> Option<Arc<Barrier>> {
|
||||
if data[0] == 1 {
|
||||
info!("ACPI Reboot signalled");
|
||||
if let Err(e) = self.reset_evt.write(1) {
|
||||
error!("Error triggering ACPI reset event: {e}");
|
||||
}
|
||||
// Spin until we are sure the reset_evt has been handled and that when
|
||||
// we return from the KVM_RUN we will exit rather than re-enter the guest.
|
||||
while !self.vcpus_kill_signalled.load(Ordering::SeqCst) {
|
||||
// This is more effective than thread::yield_now() at
|
||||
// avoiding a priority inversion with the VMM thread
|
||||
thread::sleep(std::time::Duration::from_millis(1));
|
||||
error!("Error triggering ACPI reset event: {}", e);
|
||||
}
|
||||
}
|
||||
// The ACPI DSDT table specifies the S5 sleep state (shutdown) as value 5
|
||||
@@ -69,14 +51,7 @@ impl BusDevice for AcpiShutdownDevice {
|
||||
if data[0] == (S5_SLEEP_VALUE << SLEEP_VALUE_BIT) | (1 << SLEEP_STATUS_EN_BIT) {
|
||||
info!("ACPI Shutdown signalled");
|
||||
if let Err(e) = self.exit_evt.write(1) {
|
||||
error!("Error triggering ACPI shutdown event: {e}");
|
||||
}
|
||||
// Spin until we are sure the reset_evt has been handled and that when
|
||||
// we return from the KVM_RUN we will exit rather than re-enter the guest.
|
||||
while !self.vcpus_kill_signalled.load(Ordering::SeqCst) {
|
||||
// This is more effective than thread::yield_now() at
|
||||
// avoiding a priority inversion with the VMM thread
|
||||
thread::sleep(std::time::Duration::from_millis(1));
|
||||
error!("Error triggering ACPI shutdown event: {}", e);
|
||||
}
|
||||
}
|
||||
None
|
||||
@@ -128,32 +103,30 @@ impl BusDevice for AcpiGedDevice {
|
||||
}
|
||||
|
||||
impl Aml for AcpiGedDevice {
|
||||
fn to_aml_bytes(&self, sink: &mut dyn AmlSink) {
|
||||
fn append_aml_bytes(&self, bytes: &mut Vec<u8>) {
|
||||
aml::Device::new(
|
||||
"_SB_.GEC_".into(),
|
||||
vec![
|
||||
&aml::Name::new("_HID".into(), &aml::EISAName::new("PNP0A06")),
|
||||
&aml::Name::new("_HID".into(), &aml::EisaName::new("PNP0A06")),
|
||||
&aml::Name::new("_UID".into(), &"Generic Event Controller"),
|
||||
&aml::Name::new(
|
||||
"_CRS".into(),
|
||||
&aml::ResourceTemplate::new(vec![&aml::AddressSpace::new_memory(
|
||||
aml::AddressSpaceCacheable::NotCacheable,
|
||||
aml::AddressSpaceCachable::NotCacheable,
|
||||
true,
|
||||
self.address.0,
|
||||
self.address.0 + GED_DEVICE_ACPI_SIZE as u64 - 1,
|
||||
None,
|
||||
)]),
|
||||
),
|
||||
&aml::OpRegion::new(
|
||||
"GDST".into(),
|
||||
aml::OpRegionSpace::SystemMemory,
|
||||
&(self.address.0 as usize),
|
||||
&GED_DEVICE_ACPI_SIZE,
|
||||
self.address.0 as usize,
|
||||
GED_DEVICE_ACPI_SIZE,
|
||||
),
|
||||
&aml::Field::new(
|
||||
"GDST".into(),
|
||||
aml::FieldAccessType::Byte,
|
||||
aml::FieldLockRule::NoLock,
|
||||
aml::FieldUpdateRule::WriteAsZeroes,
|
||||
vec![aml::FieldEntry::Named(*b"GDAT", 8)],
|
||||
),
|
||||
@@ -190,7 +163,7 @@ impl Aml for AcpiGedDevice {
|
||||
),
|
||||
],
|
||||
)
|
||||
.to_aml_bytes(sink);
|
||||
.append_aml_bytes(bytes);
|
||||
aml::Device::new(
|
||||
"_SB_.GED_".into(),
|
||||
vec![
|
||||
@@ -214,7 +187,7 @@ impl Aml for AcpiGedDevice {
|
||||
),
|
||||
],
|
||||
)
|
||||
.to_aml_bytes(sink);
|
||||
.append_aml_bytes(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 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;
|
||||
|
||||
use super::interrupt_controller::{Error, InterruptController};
|
||||
|
||||
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 {
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub fn new(
|
||||
vcpu_count: u32,
|
||||
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 config = Aia::create_default_config(vcpu_count as u64);
|
||||
let vaia = vm.create_vaia(&config).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,66 +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 log::error;
|
||||
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 {}
|
||||
@@ -2,13 +2,16 @@
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||
|
||||
use std::result;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use super::interrupt_controller::{Error, InterruptController};
|
||||
extern crate arch;
|
||||
use anyhow::anyhow;
|
||||
use arch::layout;
|
||||
use hypervisor::CpuState;
|
||||
use hypervisor::arch::aarch64::gic::{GicState, Vgic, VgicConfig};
|
||||
use hypervisor::{
|
||||
arch::aarch64::gic::{Vgic, VgicConfig},
|
||||
CpuState, GicState,
|
||||
};
|
||||
use std::result;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use vm_device::interrupt::{
|
||||
InterruptIndex, InterruptManager, InterruptSourceConfig, InterruptSourceGroup,
|
||||
LegacyIrqSourceConfig, MsiIrqGroupConfig,
|
||||
@@ -17,8 +20,6 @@ use vm_memory::address::Address;
|
||||
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
use super::interrupt_controller::{Error, InterruptController};
|
||||
|
||||
type Result<T> = result::Result<T, Error>;
|
||||
|
||||
// Reserve 32 IRQs for legacy devices.
|
||||
@@ -26,7 +27,7 @@ pub const IRQ_LEGACY_BASE: usize = layout::IRQ_BASE as usize;
|
||||
pub const IRQ_LEGACY_COUNT: usize = 32;
|
||||
pub const GIC_SNAPSHOT_ID: &str = "gic-v3-its";
|
||||
|
||||
// Gic (Generic 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
|
||||
// `hypervisor` crate.
|
||||
// Gic struct also implements InterruptController to provide interrupt delivery
|
||||
@@ -38,9 +39,8 @@ pub struct Gic {
|
||||
}
|
||||
|
||||
impl Gic {
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub fn new(
|
||||
vcpu_count: u32,
|
||||
vcpu_count: u8,
|
||||
interrupt_manager: Arc<dyn InterruptManager<GroupConfig = MsiIrqGroupConfig>>,
|
||||
vm: Arc<dyn hypervisor::Vm>,
|
||||
) -> Result<Gic> {
|
||||
@@ -51,8 +51,9 @@ impl Gic {
|
||||
})
|
||||
.map_err(Error::CreateInterruptSourceGroup)?;
|
||||
|
||||
let config = Gic::create_default_config(vcpu_count as u64);
|
||||
let vgic = vm.create_vgic(&config).map_err(Error::CreateGic)?;
|
||||
let vgic = vm
|
||||
.create_vgic(Gic::create_default_config(vcpu_count as u64))
|
||||
.map_err(Error::CreateGic)?;
|
||||
|
||||
let gic = Gic {
|
||||
interrupt_source_group,
|
||||
@@ -97,14 +98,9 @@ impl Gic {
|
||||
i as InterruptIndex,
|
||||
InterruptSourceConfig::LegacyIrq(config),
|
||||
false,
|
||||
false,
|
||||
)
|
||||
.map_err(Error::EnableInterrupt)?;
|
||||
}
|
||||
|
||||
self.interrupt_source_group
|
||||
.set_gsi()
|
||||
.map_err(Error::EnableInterrupt)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -167,7 +163,10 @@ impl Pausable for Gic {
|
||||
// Flush tables to guest RAM
|
||||
let vgic = self.vgic.as_ref().unwrap().clone();
|
||||
vgic.lock().unwrap().save_data_tables().map_err(|e| {
|
||||
MigratableError::Pause(anyhow!("Could not save GICv3ITS GIC pending tables {e:?}",))
|
||||
MigratableError::Pause(anyhow!(
|
||||
"Could not save GICv3ITS GIC pending tables {:?}",
|
||||
e
|
||||
))
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,55 +1,35 @@
|
||||
// Copyright © 2024 Institute of Software, CAS. All rights reserved.
|
||||
// Copyright 2020, ARM Limited.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||
|
||||
use std::{io, result};
|
||||
|
||||
use thiserror::Error;
|
||||
use std::io;
|
||||
use std::result;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Invalid trigger mode.
|
||||
#[error("Invalid trigger mode")]
|
||||
InvalidTriggerMode,
|
||||
/// Invalid delivery mode.
|
||||
#[error("Invalid delivery mode")]
|
||||
InvalidDeliveryMode,
|
||||
/// Failed creating the interrupt source group.
|
||||
#[error("Failed creating the interrupt source group")]
|
||||
CreateInterruptSourceGroup(#[source] io::Error),
|
||||
CreateInterruptSourceGroup(io::Error),
|
||||
/// Failed triggering the interrupt.
|
||||
#[error("Failed triggering the interrupt")]
|
||||
TriggerInterrupt(#[source] io::Error),
|
||||
TriggerInterrupt(io::Error),
|
||||
/// Failed masking the interrupt.
|
||||
#[error("Failed masking the interrupt")]
|
||||
MaskInterrupt(#[source] io::Error),
|
||||
MaskInterrupt(io::Error),
|
||||
/// Failed unmasking the interrupt.
|
||||
#[error("Failed unmasking the interrupt")]
|
||||
UnmaskInterrupt(#[source] io::Error),
|
||||
UnmaskInterrupt(io::Error),
|
||||
/// Failed updating the interrupt.
|
||||
#[error("Failed updating the interrupt")]
|
||||
UpdateInterrupt(#[source] io::Error),
|
||||
UpdateInterrupt(io::Error),
|
||||
/// Failed enabling the interrupt.
|
||||
#[error("Failed enabling the interrupt")]
|
||||
EnableInterrupt(#[source] io::Error),
|
||||
EnableInterrupt(io::Error),
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
/// Failed creating GIC device.
|
||||
#[error("Failed creating GIC device")]
|
||||
CreateGic(#[source] hypervisor::HypervisorVmError),
|
||||
CreateGic(hypervisor::HypervisorVmError),
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
/// Failed restoring GIC device.
|
||||
#[error("Failed restoring GIC device")]
|
||||
RestoreGic(#[source] hypervisor::arch::aarch64::gic::Error),
|
||||
#[cfg(target_arch = "riscv64")]
|
||||
/// Failed creating AIA device.
|
||||
#[error("Failed creating AIA device")]
|
||||
CreateAia(#[source] hypervisor::HypervisorVmError),
|
||||
#[cfg(target_arch = "riscv64")]
|
||||
/// Failed restoring AIA device.
|
||||
#[error("Failed restoring AIA device")]
|
||||
RestoreAia(#[source] hypervisor::arch::riscv64::aia::Error),
|
||||
RestoreGic(hypervisor::arch::aarch64::gic::Error),
|
||||
}
|
||||
|
||||
type Result<T> = result::Result<T, Error>;
|
||||
@@ -76,7 +56,7 @@ pub struct MsiMessage {
|
||||
// Introduce trait InterruptController to uniform the interrupt
|
||||
// service provided for devices.
|
||||
// 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 {
|
||||
fn service_irq(&mut self, irq: usize) -> Result<()>;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
|
||||
@@ -9,23 +9,23 @@
|
||||
// Implementation of an intel 82093AA Input/Output Advanced Programmable Interrupt Controller
|
||||
// See https://pdos.csail.mit.edu/6.828/2016/readings/ia32/ioapic.pdf for a specification.
|
||||
|
||||
use super::interrupt_controller::{Error, InterruptController};
|
||||
use byteorder::{ByteOrder, LittleEndian};
|
||||
use std::result;
|
||||
use std::sync::{Arc, Barrier};
|
||||
|
||||
use byteorder::{ByteOrder, LittleEndian};
|
||||
use log::{debug, error, trace, warn};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use vm_device::BusDevice;
|
||||
use versionize::{VersionMap, Versionize, VersionizeResult};
|
||||
use versionize_derive::Versionize;
|
||||
use vm_device::interrupt::{
|
||||
InterruptIndex, InterruptManager, InterruptSourceConfig, InterruptSourceGroup,
|
||||
MsiIrqGroupConfig, MsiIrqSourceConfig,
|
||||
};
|
||||
use vm_device::BusDevice;
|
||||
use vm_memory::GuestAddress;
|
||||
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
|
||||
use vm_migration::{
|
||||
Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable, VersionMapped,
|
||||
};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
use super::interrupt_controller::{Error, InterruptController};
|
||||
|
||||
type Result<T> = result::Result<T, Error>;
|
||||
|
||||
// I/O REDIRECTION TABLE REGISTER
|
||||
@@ -136,7 +136,7 @@ pub struct Ioapic {
|
||||
interrupt_source_group: Arc<dyn InterruptSourceGroup>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Versionize)]
|
||||
pub struct IoapicState {
|
||||
id_reg: u32,
|
||||
reg_sel: u32,
|
||||
@@ -144,6 +144,7 @@ pub struct IoapicState {
|
||||
used_entries: [bool; NUM_IOAPIC_PINS],
|
||||
apic_address: u64,
|
||||
}
|
||||
impl VersionMapped for IoapicState {}
|
||||
|
||||
impl BusDevice for Ioapic {
|
||||
fn read(&mut self, _base: u64, offset: u64, data: &mut [u8]) {
|
||||
@@ -152,13 +153,13 @@ impl BusDevice for Ioapic {
|
||||
return;
|
||||
}
|
||||
|
||||
debug!("IOAPIC_R @ offset 0x{offset:x}");
|
||||
debug!("IOAPIC_R @ offset 0x{:x}", offset);
|
||||
|
||||
let value: u32 = match offset as u8 {
|
||||
IOREGSEL_OFF => self.reg_sel,
|
||||
IOWIN_OFF => self.ioapic_read(),
|
||||
_ => {
|
||||
error!("IOAPIC: failed reading at offset {offset}");
|
||||
error!("IOAPIC: failed reading at offset {}", offset);
|
||||
return;
|
||||
}
|
||||
};
|
||||
@@ -172,7 +173,7 @@ impl BusDevice for Ioapic {
|
||||
return None;
|
||||
}
|
||||
|
||||
debug!("IOAPIC_W @ offset 0x{offset:x}");
|
||||
debug!("IOAPIC_W @ offset 0x{:x}", offset);
|
||||
|
||||
let value = LittleEndian::read_u32(data);
|
||||
|
||||
@@ -180,7 +181,7 @@ impl BusDevice for Ioapic {
|
||||
IOREGSEL_OFF => self.reg_sel = value,
|
||||
IOWIN_OFF => self.ioapic_write(value),
|
||||
_ => {
|
||||
error!("IOAPIC: failed writing at offset {offset}");
|
||||
error!("IOAPIC: failed writing at offset {}", offset);
|
||||
}
|
||||
}
|
||||
None
|
||||
@@ -191,8 +192,8 @@ impl Ioapic {
|
||||
pub fn new(
|
||||
id: String,
|
||||
apic_address: GuestAddress,
|
||||
interrupt_manager: &dyn InterruptManager<GroupConfig = MsiIrqGroupConfig>,
|
||||
state: Option<&IoapicState>,
|
||||
interrupt_manager: Arc<dyn InterruptManager<GroupConfig = MsiIrqGroupConfig>>,
|
||||
state: Option<IoapicState>,
|
||||
) -> Result<Ioapic> {
|
||||
let interrupt_source_group = interrupt_manager
|
||||
.create_group(MsiIrqGroupConfig {
|
||||
@@ -236,14 +237,9 @@ impl Ioapic {
|
||||
if state.is_some() {
|
||||
for (irq, entry) in ioapic.used_entries.iter().enumerate() {
|
||||
if *entry {
|
||||
ioapic.update_entry(irq, false)?;
|
||||
ioapic.update_entry(irq)?;
|
||||
}
|
||||
}
|
||||
|
||||
ioapic
|
||||
.interrupt_source_group
|
||||
.set_gsi()
|
||||
.map_err(Error::UpdateInterrupt)?;
|
||||
}
|
||||
|
||||
Ok(ioapic)
|
||||
@@ -267,7 +263,7 @@ impl Ioapic {
|
||||
IOWIN_OFF..=REG_MAX_OFFSET => {
|
||||
let (index, is_high_bits) = decode_irq_from_selector(self.reg_sel as u8);
|
||||
if index > NUM_IOAPIC_PINS {
|
||||
warn!("IOAPIC index out of range: {index}");
|
||||
warn!("IOAPIC index out of range: {}", index);
|
||||
return;
|
||||
}
|
||||
if is_high_bits {
|
||||
@@ -282,8 +278,8 @@ impl Ioapic {
|
||||
}
|
||||
// The entry must be updated through the interrupt source
|
||||
// group.
|
||||
if let Err(e) = self.update_entry(index, true) {
|
||||
error!("Failed updating IOAPIC entry: {e:?}");
|
||||
if let Err(e) = self.update_entry(index) {
|
||||
error!("Failed updating IOAPIC entry: {:?}", e);
|
||||
}
|
||||
// Store the information this IRQ is now being used.
|
||||
self.used_entries[index] = true;
|
||||
@@ -304,7 +300,7 @@ impl Ioapic {
|
||||
IOWIN_OFF..=REG_MAX_OFFSET => {
|
||||
let (index, is_high_bits) = decode_irq_from_selector(self.reg_sel as u8);
|
||||
if index > NUM_IOAPIC_PINS {
|
||||
warn!("IOAPIC index out of range: {index}");
|
||||
warn!("IOAPIC index out of range: {}", index);
|
||||
return 0;
|
||||
}
|
||||
if is_high_bits {
|
||||
@@ -333,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];
|
||||
|
||||
// Validate Destination Mode value, and retrieve Destination ID
|
||||
@@ -347,9 +343,9 @@ impl Ioapic {
|
||||
|
||||
// Generate MSI message address
|
||||
let low_addr: u32 = self.apic_address.0 as u32
|
||||
| (u32::from(destination_id) << 12)
|
||||
| (u32::from(redirection_hint) << 3)
|
||||
| (u32::from(destination_mode) << 2);
|
||||
| u32::from(destination_id) << 12
|
||||
| u32::from(redirection_hint) << 3
|
||||
| u32::from(destination_mode) << 2;
|
||||
|
||||
// Validate Trigger Mode value
|
||||
let trigger_mode = trigger_mode(entry);
|
||||
@@ -373,9 +369,9 @@ impl Ioapic {
|
||||
}
|
||||
|
||||
// Generate MSI message data
|
||||
let data: u32 = (u32::from(trigger_mode) << 15)
|
||||
| (u32::from(remote_irr(entry)) << 14)
|
||||
| (u32::from(delivery_mode) << 8)
|
||||
let data: u32 = u32::from(trigger_mode) << 15
|
||||
| u32::from(remote_irr(entry)) << 14
|
||||
| u32::from(delivery_mode) << 8
|
||||
| u32::from(vector(entry));
|
||||
|
||||
let config = MsiIrqSourceConfig {
|
||||
@@ -390,7 +386,6 @@ impl Ioapic {
|
||||
irq as InterruptIndex,
|
||||
InterruptSourceConfig::MsiIrq(config),
|
||||
interrupt_mask(entry) == 1,
|
||||
set_gsi,
|
||||
)
|
||||
.map_err(Error::UpdateInterrupt)?;
|
||||
|
||||
@@ -419,7 +414,7 @@ impl InterruptController for Ioapic {
|
||||
self.interrupt_source_group
|
||||
.trigger(irq as InterruptIndex)
|
||||
.map_err(Error::TriggerInterrupt)?;
|
||||
trace!("Interrupt {irq} successfully delivered");
|
||||
debug!("Interrupt successfully delivered");
|
||||
|
||||
// If trigger mode is level sensitive, set the Remote IRR bit.
|
||||
// It will be cleared when the EOI is received.
|
||||
@@ -443,7 +438,7 @@ impl Snapshottable for Ioapic {
|
||||
}
|
||||
|
||||
fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
|
||||
Snapshot::new_from_state(&self.state())
|
||||
Snapshot::new_from_versioned_state(&self.state())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,415 +0,0 @@
|
||||
// Copyright © 2024 Tencent Corporation. All rights reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
use std::any::Any;
|
||||
use std::path::PathBuf;
|
||||
use std::result;
|
||||
use std::sync::atomic::{AtomicU32, Ordering};
|
||||
use std::sync::{Arc, Barrier, Mutex};
|
||||
|
||||
use anyhow::anyhow;
|
||||
use byteorder::{ByteOrder, LittleEndian};
|
||||
use log::{debug, error, warn};
|
||||
use pci::{
|
||||
BarReprogrammingParams, PCI_CONFIGURATION_ID, PciBarConfiguration, PciBarPrefetchable,
|
||||
PciBarRegionType, PciClassCode, PciConfiguration, PciDevice, PciDeviceError, PciHeaderType,
|
||||
PciSubclass,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
use vm_allocator::{AddressAllocator, SystemAllocator};
|
||||
use vm_device::{BusDevice, Resource, UserspaceMapping};
|
||||
use vm_memory::bitmap::AtomicBitmap;
|
||||
use vm_memory::{Address, GuestAddress};
|
||||
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
|
||||
|
||||
const IVSHMEM_BAR0_IDX: usize = 0;
|
||||
const IVSHMEM_BAR1_IDX: usize = 1;
|
||||
const IVSHMEM_BAR2_IDX: usize = 2;
|
||||
|
||||
const IVSHMEM_VENDOR_ID: u16 = 0x1af4;
|
||||
const IVSHMEM_DEVICE_ID: u16 = 0x1110;
|
||||
|
||||
const IVSHMEM_REG_BAR_SIZE: u64 = 0x100;
|
||||
|
||||
type MmapRegion = vm_memory::MmapRegion<AtomicBitmap>;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum IvshmemError {
|
||||
#[error("Failed to retrieve PciConfigurationState: {0}")]
|
||||
RetrievePciConfigurationState(#[source] anyhow::Error),
|
||||
#[error("Failed to retrieve IvshmemDeviceState: {0}")]
|
||||
RetrieveIvshmemDeviceStateState(#[source] anyhow::Error),
|
||||
#[error("Failed to remove user memory region")]
|
||||
RemoveUserMemoryRegion,
|
||||
#[error("Failed to create user memory region.")]
|
||||
CreateUserMemoryRegion,
|
||||
#[error("Failed to create userspace mapping.")]
|
||||
CreateUserspaceMapping,
|
||||
#[error("Failed to remove old userspace mapping.")]
|
||||
RemoveUserspaceMapping,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum IvshmemSubclass {
|
||||
Other = 0x00,
|
||||
}
|
||||
|
||||
impl PciSubclass for IvshmemSubclass {
|
||||
fn get_register_value(&self) -> u8 {
|
||||
*self as u8
|
||||
}
|
||||
}
|
||||
|
||||
pub trait IvshmemOps: Send + Sync {
|
||||
fn map_ram_region(
|
||||
&mut self,
|
||||
start_addr: u64,
|
||||
size: usize,
|
||||
backing_file: Option<PathBuf>,
|
||||
) -> Result<(Arc<MmapRegion>, UserspaceMapping), IvshmemError>;
|
||||
|
||||
fn unmap_ram_region(&mut self, mapping: UserspaceMapping) -> Result<(), IvshmemError>;
|
||||
}
|
||||
|
||||
/// Inner-Vm Shared Memory Device (Ivshmem device)
|
||||
///
|
||||
/// This device can share memory between host and guest(ivshmem-plain)
|
||||
/// and share memory between guests(ivshmem-doorbell).
|
||||
/// But only ivshmem-plain support now, ivshmem-doorbell doesn't support yet.
|
||||
pub struct IvshmemDevice {
|
||||
id: String,
|
||||
|
||||
// ivshmem device registers
|
||||
// (only used for ivshmem-doorbell, ivshmem-doorbell don't support yet)
|
||||
_interrupt_mask: u32,
|
||||
_interrupt_status: Arc<AtomicU32>,
|
||||
_iv_position: u32,
|
||||
_doorbell: u32,
|
||||
|
||||
// PCI configuration registers.
|
||||
configuration: PciConfiguration,
|
||||
bar_regions: Vec<PciBarConfiguration>,
|
||||
|
||||
region_size: u64,
|
||||
ivshmem_ops: Arc<Mutex<dyn IvshmemOps>>,
|
||||
backend_file: Option<PathBuf>,
|
||||
region: Option<Arc<MmapRegion>>,
|
||||
userspace_mapping: Option<UserspaceMapping>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Default, Clone)]
|
||||
pub struct IvshmemDeviceState {
|
||||
interrupt_mask: u32,
|
||||
interrupt_status: u32,
|
||||
iv_position: u32,
|
||||
doorbell: u32,
|
||||
}
|
||||
|
||||
impl IvshmemDevice {
|
||||
pub fn new(
|
||||
id: String,
|
||||
region_size: u64,
|
||||
backend_file: Option<PathBuf>,
|
||||
ivshmem_ops: Arc<Mutex<dyn IvshmemOps>>,
|
||||
snapshot: Option<&Snapshot>,
|
||||
) -> Result<Self, IvshmemError> {
|
||||
let pci_configuration_state = vm_migration::state_from_id(snapshot, PCI_CONFIGURATION_ID)
|
||||
.map_err(|e| {
|
||||
IvshmemError::RetrievePciConfigurationState(anyhow!(
|
||||
"Failed to get PciConfigurationState from Snapshot: {e}",
|
||||
))
|
||||
})?;
|
||||
|
||||
let state: Option<IvshmemDeviceState> = snapshot
|
||||
.as_ref()
|
||||
.map(|s| s.to_state())
|
||||
.transpose()
|
||||
.map_err(|e| {
|
||||
IvshmemError::RetrieveIvshmemDeviceStateState(anyhow!(
|
||||
"Failed to get IvshmemDeviceState from Snapshot: {e}",
|
||||
))
|
||||
})?;
|
||||
|
||||
let configuration = PciConfiguration::new(
|
||||
IVSHMEM_VENDOR_ID,
|
||||
IVSHMEM_DEVICE_ID,
|
||||
0x1,
|
||||
PciClassCode::MemoryController,
|
||||
&IvshmemSubclass::Other,
|
||||
None,
|
||||
PciHeaderType::Device,
|
||||
0,
|
||||
0,
|
||||
None,
|
||||
pci_configuration_state,
|
||||
);
|
||||
|
||||
let device = if let Some(s) = state {
|
||||
IvshmemDevice {
|
||||
id,
|
||||
configuration,
|
||||
bar_regions: vec![],
|
||||
_interrupt_mask: s.interrupt_mask,
|
||||
_interrupt_status: Arc::new(AtomicU32::new(s.interrupt_status)),
|
||||
_iv_position: s.iv_position,
|
||||
_doorbell: s.doorbell,
|
||||
region_size,
|
||||
ivshmem_ops,
|
||||
region: None,
|
||||
userspace_mapping: None,
|
||||
backend_file,
|
||||
}
|
||||
} else {
|
||||
IvshmemDevice {
|
||||
id,
|
||||
configuration,
|
||||
bar_regions: vec![],
|
||||
_interrupt_mask: 0,
|
||||
_interrupt_status: Arc::new(AtomicU32::new(0)),
|
||||
_iv_position: 0,
|
||||
_doorbell: 0,
|
||||
region_size,
|
||||
ivshmem_ops,
|
||||
region: None,
|
||||
userspace_mapping: None,
|
||||
backend_file,
|
||||
}
|
||||
};
|
||||
Ok(device)
|
||||
}
|
||||
|
||||
pub fn set_region(&mut self, region: Arc<MmapRegion>, userspace_mapping: UserspaceMapping) {
|
||||
self.region = Some(region);
|
||||
self.userspace_mapping = Some(userspace_mapping);
|
||||
}
|
||||
|
||||
pub fn config_bar_addr(&self) -> u64 {
|
||||
self.configuration.get_bar_addr(IVSHMEM_BAR0_IDX)
|
||||
}
|
||||
|
||||
pub fn data_bar_addr(&self) -> u64 {
|
||||
self.configuration.get_bar_addr(IVSHMEM_BAR2_IDX)
|
||||
}
|
||||
|
||||
fn state(&self) -> IvshmemDeviceState {
|
||||
IvshmemDeviceState {
|
||||
interrupt_mask: self._interrupt_mask,
|
||||
interrupt_status: self._interrupt_status.load(Ordering::SeqCst),
|
||||
iv_position: self._iv_position,
|
||||
doorbell: self._doorbell,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BusDevice for IvshmemDevice {
|
||||
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>> {
|
||||
self.write_bar(base, offset, data)
|
||||
}
|
||||
}
|
||||
|
||||
impl PciDevice for IvshmemDevice {
|
||||
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 mut bar0_addr = None;
|
||||
let mut bar2_addr = None;
|
||||
|
||||
let restoring = resources.is_some();
|
||||
if let Some(resources) = resources {
|
||||
for resource in resources {
|
||||
match resource {
|
||||
Resource::PciBar { index, base, .. } => match index {
|
||||
IVSHMEM_BAR0_IDX => {
|
||||
bar0_addr = Some(GuestAddress(base));
|
||||
}
|
||||
IVSHMEM_BAR1_IDX => {}
|
||||
IVSHMEM_BAR2_IDX => {
|
||||
bar2_addr = Some(GuestAddress(base));
|
||||
}
|
||||
_ => {
|
||||
error!("Unexpected pci bar index {index}");
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
error!("Unexpected resource {resource:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
if bar0_addr.is_none() || bar2_addr.is_none() {
|
||||
return Err(PciDeviceError::MissingResource);
|
||||
}
|
||||
}
|
||||
|
||||
// BAR0 holds device registers (256 Byte MMIO)
|
||||
let bar0_addr = mmio32_allocator
|
||||
.allocate(bar0_addr, IVSHMEM_REG_BAR_SIZE, None)
|
||||
.ok_or(PciDeviceError::IoAllocationFailed(IVSHMEM_REG_BAR_SIZE))?;
|
||||
debug!("ivshmem bar0 address 0x{:x}", bar0_addr.0);
|
||||
|
||||
let bar0 = PciBarConfiguration::default()
|
||||
.set_index(IVSHMEM_BAR0_IDX)
|
||||
.set_address(bar0_addr.raw_value())
|
||||
.set_size(IVSHMEM_REG_BAR_SIZE)
|
||||
.set_region_type(PciBarRegionType::Memory32BitRegion)
|
||||
.set_prefetchable(PciBarPrefetchable::NotPrefetchable);
|
||||
|
||||
// BAR1 holds MSI-X table and PBA (only ivshmem-doorbell).
|
||||
|
||||
// BAR2 maps the shared memory object
|
||||
let bar2_size = self.region_size;
|
||||
let bar2_addr = mmio64_allocator
|
||||
.allocate(bar2_addr, bar2_size, None)
|
||||
.ok_or(PciDeviceError::IoAllocationFailed(bar2_size))?;
|
||||
debug!("ivshmem bar2 address 0x{:x}", bar2_addr.0);
|
||||
|
||||
let bar2 = PciBarConfiguration::default()
|
||||
.set_index(IVSHMEM_BAR2_IDX)
|
||||
.set_address(bar2_addr.raw_value())
|
||||
.set_size(bar2_size)
|
||||
.set_region_type(PciBarRegionType::Memory64BitRegion)
|
||||
.set_prefetchable(PciBarPrefetchable::Prefetchable);
|
||||
|
||||
if !restoring {
|
||||
self.configuration
|
||||
.add_pci_bar(&bar0)
|
||||
.map_err(|e| PciDeviceError::IoRegistrationFailed(bar0_addr.raw_value(), e))?;
|
||||
self.configuration
|
||||
.add_pci_bar(&bar2)
|
||||
.map_err(|e| PciDeviceError::IoRegistrationFailed(bar2_addr.raw_value(), e))?;
|
||||
}
|
||||
|
||||
bars.push(bar0);
|
||||
bars.push(bar2);
|
||||
self.bar_regions = bars.clone();
|
||||
|
||||
Ok(bars)
|
||||
}
|
||||
|
||||
fn free_bars(
|
||||
&mut self,
|
||||
_allocator: &mut SystemAllocator,
|
||||
_mmio32_allocator: &mut AddressAllocator,
|
||||
_mmio64_allocator: &mut AddressAllocator,
|
||||
) -> std::result::Result<(), PciDeviceError> {
|
||||
unimplemented!("Device hotplug and remove are not supported for ivshmem");
|
||||
}
|
||||
|
||||
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 read_bar(&mut self, base: u64, offset: u64, data: &mut [u8]) {
|
||||
debug!("read base {base:x} offset {offset}");
|
||||
|
||||
let mut bar_idx = 0;
|
||||
for (idx, bar) in self.bar_regions.iter().enumerate() {
|
||||
if bar.addr() == base {
|
||||
bar_idx = idx;
|
||||
}
|
||||
}
|
||||
match bar_idx {
|
||||
// bar 0
|
||||
0 => {
|
||||
// ivshmem don't use interrupt, we return zero now.
|
||||
LittleEndian::write_u32(data, 0);
|
||||
}
|
||||
// bar 2
|
||||
1 => warn!("Unexpected read ivshmem memory idx: {offset}"),
|
||||
_ => {
|
||||
warn!("Invalid bar_idx: {bar_idx}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn write_bar(&mut self, base: u64, offset: u64, _data: &[u8]) -> Option<Arc<Barrier>> {
|
||||
debug!("write base {base:x} offset {offset}");
|
||||
warn!("Unexpected write ivshmem memory idx: {offset}");
|
||||
None
|
||||
}
|
||||
|
||||
fn move_bar(&mut self, old_base: u64, new_base: u64) -> result::Result<(), std::io::Error> {
|
||||
if new_base == self.data_bar_addr() {
|
||||
if let Some(old_mapping) = self.userspace_mapping.take() {
|
||||
self.ivshmem_ops
|
||||
.lock()
|
||||
.unwrap()
|
||||
.unmap_ram_region(old_mapping)
|
||||
.map_err(std::io::Error::other)?;
|
||||
}
|
||||
let (region, new_mapping) = self
|
||||
.ivshmem_ops
|
||||
.lock()
|
||||
.unwrap()
|
||||
.map_ram_region(
|
||||
new_base,
|
||||
self.region_size as usize,
|
||||
self.backend_file.clone(),
|
||||
)
|
||||
.map_err(std::io::Error::other)?;
|
||||
self.set_region(region, new_mapping);
|
||||
}
|
||||
for bar in self.bar_regions.iter_mut() {
|
||||
if bar.addr() == old_base {
|
||||
*bar = bar.set_address(new_base);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn id(&self) -> Option<String> {
|
||||
Some(self.id.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl Pausable for IvshmemDevice {}
|
||||
|
||||
impl Snapshottable for IvshmemDevice {
|
||||
fn id(&self) -> String {
|
||||
self.id.clone()
|
||||
}
|
||||
|
||||
// The snapshot/restore (also live migration) support only work for ivshmem-plain mode.
|
||||
// Additional work is needed for supporting ivshmem-doorbell.
|
||||
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 IvshmemDevice {}
|
||||
|
||||
impl Migratable for IvshmemDevice {}
|
||||
@@ -1,21 +1,17 @@
|
||||
// Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE 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::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::mem;
|
||||
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
|
||||
#[cfg_attr(target_env = "musl", allow(deprecated))]
|
||||
use libc::time_t;
|
||||
use libc::{CLOCK_REALTIME, clock_gettime, gmtime_r, timespec, tm};
|
||||
use log::{info, warn};
|
||||
use vm_device::BusDevice;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
const INDEX_MASK: u8 = 0x7f;
|
||||
const INDEX_OFFSET: u64 = 0x0;
|
||||
@@ -27,19 +23,13 @@ pub struct Cmos {
|
||||
index: u8,
|
||||
data: [u8; DATA_LEN],
|
||||
reset_evt: EventFd,
|
||||
vcpus_kill_signalled: Option<Arc<AtomicBool>>,
|
||||
}
|
||||
|
||||
impl Cmos {
|
||||
/// Constructs a CMOS/RTC device with initial data.
|
||||
/// `mem_below_4g` is the size of memory in bytes below the 32-bit gap.
|
||||
/// `mem_above_4g` is the size of memory in bytes above the 32-bit gap.
|
||||
pub fn new(
|
||||
mem_below_4g: u64,
|
||||
mem_above_4g: u64,
|
||||
reset_evt: EventFd,
|
||||
vcpus_kill_signalled: Option<Arc<AtomicBool>>,
|
||||
) -> Cmos {
|
||||
pub fn new(mem_below_4g: u64, mem_above_4g: u64, reset_evt: EventFd) -> Cmos {
|
||||
let mut data = [0u8; DATA_LEN];
|
||||
|
||||
// Extended memory from 16 MB to 4 GB in units of 64 KB
|
||||
@@ -60,7 +50,6 @@ impl Cmos {
|
||||
index: 0,
|
||||
data,
|
||||
reset_evt,
|
||||
vcpus_kill_signalled,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -78,21 +67,12 @@ impl BusDevice for Cmos {
|
||||
if self.index == 0x8f && data[0] == 0 {
|
||||
info!("CMOS reset");
|
||||
self.reset_evt.write(1).unwrap();
|
||||
if let Some(vcpus_kill_signalled) = self.vcpus_kill_signalled.take() {
|
||||
// Spin until we are sure the reset_evt has been handled and that when
|
||||
// we return from the KVM_RUN we will exit rather than re-enter the guest.
|
||||
while !vcpus_kill_signalled.load(Ordering::SeqCst) {
|
||||
// This is more effective than thread::yield_now() at
|
||||
// avoiding a priority inversion with the VMM thread
|
||||
thread::sleep(std::time::Duration::from_millis(1));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.data[(self.index & INDEX_MASK) as usize] = data[0];
|
||||
self.data[(self.index & INDEX_MASK) as usize] = data[0]
|
||||
}
|
||||
}
|
||||
o => warn!("bad write offset on CMOS device: {o}"),
|
||||
}
|
||||
o => warn!("bad write offset on CMOS device: {}", o),
|
||||
};
|
||||
None
|
||||
}
|
||||
|
||||
@@ -153,7 +133,7 @@ impl BusDevice for Cmos {
|
||||
0x08 => to_bcd(month as u8),
|
||||
0x09 => to_bcd((year % 100) as u8),
|
||||
// Bit 5 for 32kHz clock. Bit 7 for Update in Progress
|
||||
0x0a => (1 << 5) | ((update_in_progress as u8) << 7),
|
||||
0x0a => 1 << 5 | (update_in_progress as u8) << 7,
|
||||
// Bit 0-6 are reserved and must be 0.
|
||||
// Bit 7 must be 1 (CMOS has power)
|
||||
0x0d => 1 << 7,
|
||||
@@ -165,7 +145,7 @@ impl BusDevice for Cmos {
|
||||
}
|
||||
}
|
||||
o => {
|
||||
warn!("bad read offset on CMOS device: {o}");
|
||||
warn!("bad read offset on CMOS device: {}", o);
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user