Compare commits

..

5 Commits

Author SHA1 Message Date
Rob Bradford
bb02c28b62 build: Update for v0.14.1 bug fix release
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2021-03-31 16:56:05 +01:00
Sebastien Boeuf
b2c96dea24 vmm: Add missing syscall for vCPU unplug
clock_nanosleep() is triggered when hot-unplugging a vCPU.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
(cherry picked from commit 46f96f27a4)
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2021-03-31 16:56:05 +01:00
Rob Bradford
2c81312433 vm-virtio: queue: Fix descriptor chain validation
DescriptorChain::is_valid() wrongly used .checked_offset() to attempt to
validate that the descriptor's data is in valid memory. This works in
all cases except where the guest has placed the data at the very end of
the guest memory as the offset + offset will be outside the range (as
the combined offset will be the next byte and as such out of the guest
memory). Instead use the function .check_range() takes an offset and a
length to validate

This fixes issues see with error messages featuring the
DescriptorChainTooShort error.

Fixes: #2424

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
(cherry picked from commit 1eb4ebe3d1)
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2021-03-31 16:56:05 +01:00
Anatol Belski
e8ee29b4fe CpuManager: Fix MMIO read handling
There are two parts:

- Unconditionally zero the output area. The length of the incoming
  vector has been seen from 1 to 4 bytes, even though just the first
  byte might need to be handled. But also, this ensures any possibly
  unhandled offset will return zeroed result to the caller. The former
  implementation used an I/O port which seems to behave differently from
  MMIO and wouldn't require explicit output zeroing.
- An access with zero offset still takes place and needs to be handled.

Fixes #2437.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
(cherry picked from commit 9e9aba7c0b)
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2021-03-31 16:56:05 +01:00
Anatol Belski
498939c297 hyperv: Fix CPU hotadd
The following is from the Hyper-V specification v6.0b.

Cpuid leaf 0x40000003 EDX:

Bit 3: Support for physical CPU dynamic partitioning events is
available.

When Windows determines to be running under a hypervisor, it will
require this cpuid bit to be set to support dynamic CPU operations.

Cpuid leaf 0x40000004 EAX:

Bit 5: Recommend using relaxed timing for this partition. If
used, the VM should disable any watchdog timeouts that
rely on the timely delivery of external interrupts.

This bit has been figured out as required after seeing guest BSOD
when CPU hotplug bit is enabled. Race conditions seem to arise after a
hotplug operation, when a system watchdog has expired.

Closes #1799.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
(cherry picked from commit 5b168f54a6)
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2021-03-31 16:56:05 +01:00
419 changed files with 48559 additions and 94457 deletions

View File

@@ -1,34 +0,0 @@
---
name: Bug report
about: File a bug report
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behaviour:
**Version**
Output of `cloud-hypervisor --version`:
Did you build from source, if so build command line (e.g. features):
**VM configuration**
What command line did you run (or JSON config data):
Guest OS version details:
Host OS version details:
**Logs**
Output of `cloud-hypervisor -v` from either standard error or via `--log-file`:
Linux kernel output:

View File

@@ -1,23 +0,0 @@
version: 2
updates:
- package-ecosystem: cargo
directory: "/"
schedule:
interval: daily
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
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: daily
open-pull-requests-limit: 1

View File

@@ -4,13 +4,12 @@ on:
paths:
- '**/Cargo.toml'
- '**/Cargo.lock'
jobs:
security_audit:
name: Audit
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- 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 }}

View File

@@ -1,11 +1,9 @@
name: Cloud Hypervisor Build
on: [pull_request, merge_group]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
on: [pull_request, create]
jobs:
build:
if: github.event_name == 'pull_request'
name: Build
runs-on: ubuntu-latest
strategy:
@@ -15,63 +13,32 @@ jobs:
- stable
- beta
- nightly
- "1.88.0"
target:
- x86_64-unknown-linux-gnu
- x86_64-unknown-linux-musl
steps:
- name: Code checkout
uses: actions/checkout@v5
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Install musl-gcc
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 }}
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
override: true
- name: Build (default features)
run: cargo rustc --locked --bin cloud-hypervisor -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
- name: Debug Check (default features)
run: |
git rev-list origin/master..$GITHUB_SHA | xargs -t -I % sh -c 'git checkout %; cargo check --all --target=${{ matrix.target }}'
git checkout $GITHUB_SHA
- name: Build (acpi,kvm)
run: cargo rustc --bin cloud-hypervisor --no-default-features --features "acpi,kvm" -- -D warnings
- name: Build (kvm)
run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
- name: Build (default features + tdx)
run: cargo rustc --locked --bin cloud-hypervisor --features "tdx" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
- name: Build (default features + dbus_api)
run: cargo rustc --locked --bin cloud-hypervisor --features "dbus_api" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
- name: Build (default features + guest_debug)
run: cargo rustc --locked --bin cloud-hypervisor --features "guest_debug" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
- name: Build (default features + pvmemcontrol)
run: cargo rustc --locked --bin cloud-hypervisor --features "pvmemcontrol" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
- name: Build (default features + fw_cfg)
run: cargo rustc --locked --bin cloud-hypervisor --features "fw_cfg" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
- name: Build (default features + ivshmem)
run: cargo rustc --locked --bin cloud-hypervisor --features "ivshmem" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
- name: Build (mshv)
run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "mshv" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
- name: Build (sev_snp)
run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "sev_snp" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
- name: Build (igvm)
run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "igvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
- name: Build (mshv + kvm)
run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "mshv,kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
run: cargo rustc --bin cloud-hypervisor --no-default-features --features "kvm" -- -D warnings
- name: Release Build (default features)
run: cargo build --locked --all --release --target=${{ matrix.target }}
- name: Check build did not modify any files
run: test -z "$(git status --porcelain)"
run: cargo build --all --release --target=${{ matrix.target }}

View File

@@ -1,20 +0,0 @@
name: DCO
on: [pull_request, merge_group]
jobs:
check:
name: DCO Check ("Signed-Off-By")
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Set up Python 3.x
uses: actions/setup-python@v6
with:
python-version: '3.x'
- name: Check DCO
if: ${{ github.event_name == 'pull_request' }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
pip3 install -U dco-check
dco-check -e "49699333+dependabot[bot]@users.noreply.github.com"

View File

@@ -1,65 +1,36 @@
name: Cloud Hypervisor's Docker image update
name: Cloud-Hypervisor's Docker image update
on:
push:
branches: main
branches: master
paths: resources/Dockerfile
pull_request:
paths: resources/Dockerfile
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
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@v5
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
uses: docker/login-action@v1
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
# generate Docker tags based on the following events/attributes
tags: |
type=raw,value=20250815-0
type=sha
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- 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
push: true
tags: ${{ steps.meta.outputs.tags }}
- name: Build only
if: ${{ github.event_name == 'pull_request' }}
uses: docker/build-push-action@v6
with:
file: ./resources/Dockerfile
platforms: linux/amd64,linux/arm64
tags: ${{ steps.meta.outputs.tags }}
tags: cloudhypervisor/dev:latest
- name: Image digest
run: echo ${{ steps.docker_build.outputs.digest }}

View File

@@ -1,32 +0,0 @@
name: Cloud Hypervisor Code Formatting
on: [pull_request, merge_group]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
name: Code Formatting
runs-on: ubuntu-latest
strategy:
matrix:
rust:
- nightly
target:
- x86_64-unknown-linux-gnu
- aarch64-unknown-linux-musl
env:
RUSTFLAGS: -D warnings
steps:
- name: Code checkout
uses: actions/checkout@v5
- 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

View File

@@ -1,11 +1,9 @@
name: Cloud Hypervisor Cargo Fuzz Build
on: [pull_request, merge_group]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
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,16 @@ jobs:
- nightly
target:
- x86_64-unknown-linux-gnu
env:
RUSTFLAGS: -D warnings
steps:
- name: Code checkout
uses: actions/checkout@v5
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
run: cargo install -f cargo-fuzz
- name: Cargo Fuzz Build
run: cargo fuzz build
- name: Fuzz Check
run: cargo fuzz check

View File

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

View File

@@ -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@v5
- name: Lint Dockerfile
uses: hadolint/hadolint-action@master
with:
dockerfile: ./resources/Dockerfile
format: tty
no-fail: false
verbose: true
failure-threshold: info

View File

@@ -1,54 +0,0 @@
name: Cloud Hypervisor Tests (ARM64)
on: [pull_request, merge_group]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
timeout-minutes: 120
name: Tests (ARM64)
runs-on: bookworm-arm64
steps:
- name: Fix workspace permissions
run: sudo chown -R runner:runner ${GITHUB_WORKSPACE}
- name: Code checkout
uses: actions/checkout@v5
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

View File

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

View File

@@ -1,25 +0,0 @@
name: Cloud Hypervisor Tests (Rate-Limiter)
on: [merge_group, pull_request]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
name: Tests (Rate-Limiter)
runs-on: ${{ github.event_name == 'pull_request' && 'ubuntu-latest' || 'bare-metal-9950x' }}
env:
AUTH_DOWNLOAD_TOKEN: ${{ secrets.AUTH_DOWNLOAD_TOKEN }}
steps:
- name: Code checkout
if: ${{ github.event_name != 'pull_request' }}
uses: actions/checkout@v5
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"

View File

@@ -1,33 +0,0 @@
name: Cloud Hypervisor Tests (VFIO)
on: [merge_group, pull_request]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
name: Tests (VFIO)
runs-on: ${{ github.event_name == 'pull_request' && 'ubuntu-latest' || 'vfio-nvidia' }}
env:
AUTH_DOWNLOAD_TOKEN: ${{ secrets.AUTH_DOWNLOAD_TOKEN }}
steps:
- name: Fix workspace permissions
if: ${{ github.event_name != 'pull_request' }}
run: sudo chown -R runner:runner ${GITHUB_WORKSPACE}
- name: Code checkout
if: ${{ github.event_name != 'pull_request' }}
uses: actions/checkout@v5
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"

View File

@@ -1,50 +0,0 @@
name: Cloud Hypervisor Tests (Windows Guest)
on: [merge_group, pull_request]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
name: Tests (Windows Guest)
runs-on: ${{ github.event_name == 'pull_request' && 'ubuntu-latest' || 'garm-jammy-16' }}
steps:
- name: Code checkout
if: ${{ github.event_name != 'pull_request' }}
uses: actions/checkout@v5
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"

View File

@@ -1,52 +0,0 @@
name: Cloud Hypervisor Tests (x86-64)
on: [pull_request, merge_group]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
runner: ['garm-jammy', "garm-jammy-amd"]
libc: ["musl", 'gnu']
name: Tests (x86-64)
runs-on: ${{ github.event_name == 'pull_request' && !(matrix.runner == 'garm-jammy' && matrix.libc == 'gnu') && 'ubuntu-latest' || format('{0}-16', matrix.runner) }}
steps:
- name: Code checkout
if: ${{ github.event_name != 'pull_request' || (matrix.runner == 'garm-jammy' && matrix.libc == 'gnu') }}
uses: actions/checkout@v5
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"

View File

@@ -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@v5
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@v46 # 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

View File

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

View File

@@ -1,32 +0,0 @@
name: Cloud Hypervisor Consistency
on: [pull_request, merge_group]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
name: Rust VMM Consistency Check
runs-on: ubuntu-latest
steps:
- name: Code checkout
uses: actions/checkout@v5
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

View File

@@ -1,30 +0,0 @@
name: Cloud Hypervisor RISC-V 64-bit kvm build Preview
on: [pull_request, merge_group]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
name: Cargo
runs-on: riscv64-qemu-host
strategy:
fail-fast: false
steps:
- name: Code checkout
uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Install Rust toolchain
run: /opt/scripts/exec-in-qemu.sh rustup default 1.88.0
- name: Build test (kvm)
run: /opt/scripts/exec-in-qemu.sh cargo rustc --locked --no-default-features --features "kvm"
- name: Clippy test (kvm)
run: /opt/scripts/exec-in-qemu.sh cargo clippy --locked --no-default-features --features "kvm"
- name: Check no files were modified
run: test -z "$(git status --porcelain)"

View File

@@ -1,39 +0,0 @@
name: Cloud Hypervisor RISC-V 64-bit Preview
on: [pull_request, merge_group]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
name: Cargo
runs-on: riscv64-qemu-host
strategy:
fail-fast: false
matrix:
module:
- hypervisor
- arch
- vm-allocator
- devices
steps:
- name: Code checkout
uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Install Rust toolchain
run: /opt/scripts/exec-in-qemu.sh rustup default 1.88.0
- name: Build ${{ matrix.module }} Module (kvm)
run: /opt/scripts/exec-in-qemu.sh cargo rustc --locked -p ${{ matrix.module }} --no-default-features --features "kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
- name: Clippy ${{ matrix.module }} Module (kvm)
run: /opt/scripts/exec-in-qemu.sh cargo clippy --locked -p ${{ matrix.module }} --no-default-features --features "kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
- name: Test ${{ matrix.module }} Module (kvm)
run: /opt/scripts/exec-in-qemu.sh cargo test --locked -p ${{ matrix.module }} --no-default-features --features "kvm"
- name: Check no files were modified
run: test -z "$(git status --porcelain)"

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

@@ -0,0 +1,34 @@
name: Cloud Hypervisor Quality Checks
on: [pull_request, create]
jobs:
build:
if: github.event_name == 'pull_request'
name: Quality (clippy, rustfmt)
runs-on: ubuntu-latest
strategy:
matrix:
rust:
- stable
target:
- aarch64-unknown-linux-gnu
steps:
- name: Code checkout
uses: actions/checkout@v2
- name: Install Rust toolchain (${{ matrix.rust }})
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
override: true
components: rustfmt, clippy
- name: Install arm64 libfdt
run: wget http://ftp.us.debian.org/debian/pool/main/d/device-tree-compiler/libfdt-dev_1.6.0-1_arm64.deb && dpkg-deb -xv libfdt-dev_1.6.0-1_arm64.deb ./tlibfdtdev && mkdir -p target/debug/deps && sudo cp ./tlibfdtdev/usr/lib/aarch64-linux-gnu/libfdt.a target/debug/deps/libfdt.a && echo "libfdt installed"
- name: Formatting (rustfmt)
run: cargo fmt -- --check
- name: Clippy (kvm)
uses: actions-rs/cargo@v1
with:
use-cross: true
command: clippy
args: --target=${{ matrix.target }} --no-default-features --features "kvm" -- -D warnings

View File

@@ -1,170 +1,51 @@
name: Cloud Hypervisor Quality Checks
on: [pull_request, merge_group]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
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
- aarch64-unknown-linux-musl
- x86_64-unknown-linux-gnu
- x86_64-unknown-linux-musl
include:
- rust: beta
experimental: true
- rust: stable
experimental: false
steps:
- name: Code checkout
uses: actions/checkout@v5
with:
fetch-depth: 0
uses: actions/checkout@v2
- name: Install Rust toolchain (${{ matrix.rust }})
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
override: true
components: clippy
components: rustfmt, clippy
- name: Bisectability Check (default features)
if: ${{ github.event_name == 'pull_request' && 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 (all features,kvm)
run: cargo clippy --all --all-targets --no-default-features --tests --features "common,kvm" -- -D warnings
- name: Clippy (all features,mshv)
run: cargo clippy --all --all-targets --no-default-features --tests --features "common,mshv" -- -D warnings
- name: Clippy (acpi,kvm)
run: cargo clippy --all --all-targets --no-default-features --tests --features "acpi,kvm" -- -D warnings
- name: Clippy (acpi,kvm,tdx)
run: cargo clippy --all --all-targets --no-default-features --tests --features "acpi,kvm,tdx" -- -D warnings
- 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 -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
run: cargo clippy --all --all-targets --no-default-features --tests --features "kvm" -- -D warnings
- name: Clippy (acpi,mshv)
run: cargo clippy --all --all-targets --no-default-features --tests --features "acpi,mshv" -- -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 -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
run: cargo clippy --all --all-targets --no-default-features --tests --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 -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
- 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 -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
- 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 -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
- 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 -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
- 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 -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
- 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 -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
- name: Clippy (default features + ivshmem)
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 "ivshmem" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
- name: Clippy (sev_snp)
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
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 "sev_snp" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
- name: Clippy (igvm)
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
uses: 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 "igvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
- name: Clippy (kvm + tdx)
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
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 "tdx,kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
- 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@v5
# Executes "typos ."
- uses: crate-ci/typos@v1.36.2
- name: Clippy (integration tests)
run: cargo clippy --all --all-targets --tests --features "integration_tests" -- -D warnings

View File

@@ -1,95 +1,67 @@
name: Cloud Hypervisor Release
on: [create, merge_group]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}
cancel-in-progress: true
env:
GITHUB_TOKEN: ${{ github.token }}
on: [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'
name: Release
runs-on: ubuntu-latest
steps:
- name: Code checkout
uses: actions/checkout@v5
- 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
uses: actions/checkout@v2
- name: Install Rust toolchain (x86_64-unknown-linux-gnu)
uses: actions-rs/toolchain@v1
with:
command: build
target: ${{ matrix.platform.target }}
args: ${{ matrix.platform.args }}
strip: true
toolchain: "1.88.0"
- name: Copy Release Binaries
if: github.event_name == 'create' && github.event.ref_type == 'tag'
shell: bash
run: |
cp target/${{ matrix.platform.target }}/release/cloud-hypervisor ./${{ matrix.platform.name_ch }}
cp target/${{ matrix.platform.target }}/release/ch-remote ./${{ matrix.platform.name_ch_remote }}
- name: Upload Release Artifacts
if: github.event_name == 'create' && github.event.ref_type == 'tag'
uses: actions/upload-artifact@v4
toolchain: stable
target: x86_64-unknown-linux-gnu
- name: Install Rust toolchain (x86_64-unknown-linux-musl)
uses: actions-rs/toolchain@v1
with:
name: Artifacts for ${{ matrix.platform.target }}
path: |
./${{ matrix.platform.name_ch }}
./${{ matrix.platform.name_ch_remote }}
- 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
export CARGO_HOME=$(realpath ../vendor-cargo-home)
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 }}
- name: Upload cloud-hypervisor vendored source archive
if: |
github.event_name == 'create' && github.event.ref_type == 'tag' &&
matrix.platform.target == 'x86_64-unknown-linux-gnu'
id: upload-release-cloud-hypervisor-vendored-sources
uses: actions/upload-artifact@v4
with:
path: cloud-hypervisor-${{ github.event.ref }}.tar.xz
name: cloud-hypervisor-${{ github.event.ref }}.tar.xz
- name: Create GitHub Release
if: github.event_name == 'create' && github.event.ref_type == 'tag'
uses: softprops/action-gh-release@v2
toolchain: stable
target: x86_64-unknown-linux-musl
- name: Build
run: cargo build --all --release --target=x86_64-unknown-linux-gnu
- name: Static Build
run: cargo build --all --release --target=x86_64-unknown-linux-musl
- name: Strip cloud-hypervisor binaries
run: strip target/*/release/cloud-hypervisor
- name: Create Release
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
files: |
./${{ matrix.platform.name_ch }}
./${{ matrix.platform.name_ch_remote }}
./cloud-hypervisor-${{ github.event.ref }}.tar.xz
prerelease: true
- name: Upload cloud-hypervisor
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
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
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

View File

@@ -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@v5
- name: REUSE Compliance Check
uses: fsfe/reuse-action@v5

View File

@@ -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@v5
- name: Run the shell script checkers
uses: luizm/action-sh-checker@master
env:
SHFMT_OPTS: -i 4 -d
SHELLCHECK_OPTS: -x --source-path scripts

View File

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

5
.gitignore vendored
View File

@@ -1,10 +1,5 @@
/build
/.cargo
/target
**/*.rs.bk
**/Cargo.lock
**/rusty-tags.vi
/rpm/SOURCES
/.vscode
/vendor
__pycache__

View File

@@ -1,13 +0,0 @@
[general]
extra-path=scripts/gitlint/rules
regex-style-search=true
ignore=body-max-line-length
[ignore-by-author-name]
regex=dependabot
ignore=all
# default 72
[title-max-length]
line-length=72

View File

@@ -1,27 +0,0 @@
verbose = "info"
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://.*",
]
# Exclude loopback addresses
exclude_loopback = true
max_retries = 3
retry_wait_time = 5

View File

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

View File

@@ -1,4 +1 @@
edition = "2024"
group_imports="StdExternalCrate"
imports_granularity="Module"
edition = "2018"

View File

@@ -1,5 +0,0 @@
include = ["**/Cargo.toml"]
[formatting]
reorder_arrays = true
reorder_keys = true

View File

@@ -1,24 +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"
SME = "SME" # Secure Memory Encryption
THR = "THR" # Transmitter Holding Register
TRANSLATER = "TRANSLATER"
ba = "ba"
conectix = "conectix"
liness = "liness"
outout = "outout"
[default.extend-identifiers]
fo = "fo"
fpr = "fpr"

View File

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

View File

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

2535
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,155 +1,89 @@
[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 = "48.0.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.88.0"
version = "0.14.1"
authors = ["The Cloud Hypervisor Authors"]
edition = "2018"
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"
[profile.release]
codegen-units = 1
lto = true
opt-level = "s"
strip = true
[profile.profiling]
debug = true
inherits = "release"
strip = false
[dependencies]
anyhow = { workspace = true }
anyhow = "1.0.39"
api_client = { path = "api_client" }
clap = { workspace = true, features = ["string"] }
dhat = { workspace = true, optional = true }
env_logger = { workspace = true }
epoll = { workspace = true }
clap = { version = "2.33.3", features = ["wrap_help"] }
epoll = "4.3.1"
event_monitor = { path = "event_monitor" }
hypervisor = { path = "hypervisor" }
libc = { workspace = true }
log = { workspace = true, features = ["std"] }
libc = "0.2.91"
log = { version = "0.4.14", 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 }
seccomp = { git = "https://github.com/firecracker-microvm/firecracker", tag = "v0.22.0" }
serde_json = "1.0.64"
signal-hook = "0.3.7"
thiserror = "1.0.24"
vmm = { path = "vmm" }
vmm-sys-util = { workspace = true }
zbus = { version = "5.7.1", optional = true }
vmm-sys-util = "0.8.0"
vm-memory = "0.5.0"
[build-dependencies]
clap = { version = "2.33.3", features = ["wrap_help"] }
# List of patched crates
[patch.crates-io]
kvm-bindings = { git = "https://github.com/cloud-hypervisor/kvm-bindings", branch = "ch-v0.4.0", features = ["with-serde", "fam-wrappers"] }
[dev-dependencies]
dirs = { workspace = true }
credibility = "0.1.3"
dirs = "3.0.1"
lazy_static= "1.4.0"
net_util = { path = "net_util" }
serde_json = { workspace = true }
serde_json = "1.0.64"
test_infra = { path = "test_infra" }
wait-timeout = { workspace = true }
wait-timeout = "0.2.0"
# 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"]
default = ["acpi", "cmos", "io_uring", "kvm"]
# Common features for all hypervisors
common = ["acpi", "cmos", "fwdebug", "io_uring"]
acpi = ["vmm/acpi"]
cmos = ["vmm/cmos"]
fwdebug = ["vmm/fwdebug"]
kvm = ["vmm/kvm"]
mshv = ["vmm/mshv"]
pvmemcontrol = ["vmm/pvmemcontrol"]
sev_snp = ["igvm", "mshv", "vmm/sev_snp"]
io_uring = ["vmm/io_uring"]
tdx = ["vmm/tdx"]
tracing = ["tracer/tracing", "vmm/tracing"]
# Integration tests require a special environment to run in
integration_tests = []
[workspace]
members = [
"api_client",
"arch",
"block",
"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",
"acpi_tables",
"api_client",
"arch",
"arch_gen",
"block_util",
"devices",
"event_monitor",
"hypervisor",
"net_gen",
"net_util",
"option_parser",
"pci",
"qcow",
"vhost_user_backend",
"vhost_user_block",
"vhost_user_net",
"virtio-devices",
"vmm",
"vm-allocator",
"vm-device",
"vm-migration",
"vm-virtio"
]
package.edition = "2024"
[workspace.dependencies]
# rust-vmm crates
acpi_tables = { git = "https://github.com/rust-vmm/acpi_tables", branch = "main" }
kvm-bindings = "0.12.0"
kvm-ioctls = "0.22.0"
# TODO: update to 0.13.1+
linux-loader = { git = "https://github.com/rust-vmm/linux-loader", branch = "main" }
mshv-bindings = "0.6.0"
mshv-ioctls = "0.6.0"
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 = { git = "https://github.com/rust-vmm/vm-fdt", branch = "main" }
vm-memory = "0.16.1"
vmm-sys-util = "0.14.0"
# igvm crates
# TODO: bump to 0.3.5 release
igvm = { git = "https://github.com/microsoft/igvm", branch = "main" }
igvm_defs = { git = "https://github.com/microsoft/igvm", branch = "main" }
# serde crates
serde = "1.0.208"
serde_json = "1.0.143"
serde_with = { version = "3.14.0", default-features = false }
# other crates
anyhow = "1.0.98"
bitflags = "2.9.4"
byteorder = "1.5.0"
cfg-if = "1.0.0"
clap = "4.5.47"
dhat = "0.3.3"
dirs = "6.0.0"
env_logger = "0.11.8"
epoll = "4.3.3"
flume = "0.11.1"
libc = "0.2.167"
log = "0.4.22"
signal-hook = "0.3.18"
thiserror = "2.0.12"
uuid = { version = "1.18.1" }
wait-timeout = "0.2.1"
zerocopy = { version = "0.8.26", default-features = false }
exclude = ["test_infra"]

247
Jenkinsfile vendored Normal file
View File

@@ -0,0 +1,247 @@
pipeline{
agent none
stages {
stage ('Early checks') {
agent { node { label 'master' } }
stages {
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 'master' } }
steps {
cancelPreviousBuilds()
}
}
}
}
stage ('Build') {
parallel {
stage ('Worker build') {
agent { node { label 'groovy' } }
stages {
stage ('Checkout') {
steps {
checkout scm
}
}
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 "scripts/dev_cli.sh tests --integration"
}
}
}
}
stage ('AArch64 worker build') {
agent { node { label 'bionic-arm64' } }
stages {
stage ('Checkout') {
steps {
checkout scm
}
}
stage ('Run unit tests') {
steps {
sh "scripts/dev_cli.sh tests --unit"
}
}
stage ('Run integration tests') {
options {
timeout(time: 1, unit: 'HOURS')
}
steps {
sh "scripts/dev_cli.sh tests --integration"
}
}
}
post {
always {
sh "sudo chown -R jenkins.jenkins ${WORKSPACE}"
deleteDir()
}
}
}
stage ('Worker build (musl)') {
agent { node { label 'groovy' } }
stages {
stage ('Checkout') {
steps {
checkout scm
}
}
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 "scripts/dev_cli.sh tests --integration --libc musl"
}
}
}
}
stage ('Worker build SGX') {
agent { node { label 'bionic-sgx' } }
when { branch 'master' }
stages {
stage ('Checkout') {
steps {
checkout scm
}
}
stage ('Run SGX integration tests') {
options {
timeout(time: 1, unit: 'HOURS')
}
steps {
sh "scripts/dev_cli.sh tests --integration-sgx"
}
}
stage ('Run SGX integration tests for musl') {
options {
timeout(time: 1, unit: 'HOURS')
}
steps {
sh "scripts/dev_cli.sh tests --integration-sgx --libc musl"
}
}
}
post {
always {
sh "sudo chown -R jenkins.jenkins ${WORKSPACE}"
deleteDir()
}
}
}
stage ('Worker build VFIO') {
agent { node { label 'bionic-vfio' } }
when { branch 'master' }
stages {
stage ('Checkout') {
steps {
checkout scm
}
}
stage ('Run VFIO integration tests') {
options {
timeout(time: 1, unit: 'HOURS')
}
steps {
sh "scripts/dev_cli.sh tests --integration-vfio"
}
}
stage ('Run VFIO integration tests for musl') {
options {
timeout(time: 1, unit: 'HOURS')
}
steps {
sh "scripts/dev_cli.sh tests --integration-vfio --libc musl"
}
}
}
post {
always {
sh "sudo chown -R jenkins.jenkins ${WORKSPACE}"
deleteDir()
}
}
}
stage ('Worker build - Windows guest') {
agent { node { label 'groovy-win' } }
stages {
stage ('Checkout') {
steps {
checkout scm
}
}
stage ('Download assets') {
steps {
sh "mkdir ${env.HOME}/workloads"
azureDownload(storageCredentialId: 'ch-image-store',
containerName: 'private-images',
includeFilesPattern: 'OVMF-4b47d0c6c8.fd',
downloadType: 'container',
downloadDirLoc: "${env.HOME}/workloads")
azureDownload(storageCredentialId: 'ch-image-store',
containerName: 'private-images',
includeFilesPattern: 'windows-server-2019.raw',
downloadType: 'container',
downloadDirLoc: "${env.HOME}/workloads")
}
}
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"
}
}
}
}
}
}
}
post {
regression {
script {
if (env.BRANCH_NAME == 'master') {
slackSend (color: '#ff0000', message: '"master" branch build is now failing')
}
}
}
fixed {
script {
if (env.BRANCH_NAME == 'master') {
slackSend (color: '#00ff00', message: '"master" branch build is now fixed')
}
}
}
}
}
def cancelPreviousBuilds() {
// Check for other instances of this particular build, cancel any that are older than the current one
def jobName = env.JOB_NAME
def currentBuildNumber = env.BUILD_NUMBER.toInteger()
def currentJob = Jenkins.instance.getItemByFullName(jobName)
// 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()
}
}
}

View File

@@ -1,400 +0,0 @@
All documentations (e.g. files with extension `.md`) in this repository is
covered by the following license:
Attribution 4.0 International
=======================================================================
Creative Commons Corporation ("Creative Commons") is not a law firm and
does not provide legal services or legal advice. Distribution of
Creative Commons public licenses does not create a lawyer-client or
other relationship. Creative Commons makes its licenses and related
information available on an "as-is" basis. Creative Commons gives no
warranties regarding its licenses, any material licensed under their
terms and conditions, or any related information. Creative Commons
disclaims all liability for damages resulting from their use to the
fullest extent possible.
Using Creative Commons Public Licenses
Creative Commons public licenses provide a standard set of terms and
conditions that creators and other rights holders may use to share
original works of authorship and other material subject to copyright
and certain other rights specified in the public license below. The
following considerations are for informational purposes only, are not
exhaustive, and do not form part of our licenses.
Considerations for licensors: Our public licenses are
intended for use by those authorized to give the public
permission to use material in ways otherwise restricted by
copyright and certain other rights. Our licenses are
irrevocable. Licensors should read and understand the terms
and conditions of the license they choose before applying it.
Licensors should also secure all rights necessary before
applying our licenses so that the public can reuse the
material as expected. Licensors should clearly mark any
material not subject to the license. This includes other CC-
licensed material, or material used under an exception or
limitation to copyright. More considerations for licensors:
wiki.creativecommons.org/Considerations_for_licensors
Considerations for the public: By using one of our public
licenses, a licensor grants the public permission to use the
licensed material under specified terms and conditions. If
the licensor's permission is not necessary for any reason--for
example, because of any applicable exception or limitation to
copyright--then that use is not regulated by the license. Our
licenses grant only permissions under copyright and certain
other rights that a licensor has authority to grant. Use of
the licensed material may still be restricted for other
reasons, including because others have copyright or other
rights in the material. A licensor may make special requests,
such as asking that all changes be marked or described.
Although not required by our licenses, you are encouraged to
respect those requests where reasonable. More considerations
for the public:
wiki.creativecommons.org/Considerations_for_licensees
=======================================================================
Creative Commons Attribution 4.0 International Public License
By exercising the Licensed Rights (defined below), You accept and agree
to be bound by the terms and conditions of this Creative Commons
Attribution 4.0 International Public License ("Public License"). To the
extent this Public License may be interpreted as a contract, You are
granted the Licensed Rights in consideration of Your acceptance of
these terms and conditions, and the Licensor grants You such rights in
consideration of benefits the Licensor receives from making the
Licensed Material available under these terms and conditions.
Section 1 -- Definitions.
a. Adapted Material means material subject to Copyright and Similar
Rights that is derived from or based upon the Licensed Material
and in which the Licensed Material is translated, altered,
arranged, transformed, or otherwise modified in a manner requiring
permission under the Copyright and Similar Rights held by the
Licensor. For purposes of this Public License, where the Licensed
Material is a musical work, performance, or sound recording,
Adapted Material is always produced where the Licensed Material is
synched in timed relation with a moving image.
b. Adapter's License means the license You apply to Your Copyright
and Similar Rights in Your contributions to Adapted Material in
accordance with the terms and conditions of this Public License.
c. Copyright and Similar Rights means copyright and/or similar rights
closely related to copyright including, without limitation,
performance, broadcast, sound recording, and Sui Generis Database
Rights, without regard to how the rights are labeled or
categorized. For purposes of this Public License, the rights
specified in Section 2(b)(1)-(2) are not Copyright and Similar
Rights.
d. Effective Technological Measures means those measures that, in the
absence of proper authority, may not be circumvented under laws
fulfilling obligations under Article 11 of the WIPO Copyright
Treaty adopted on December 20, 1996, and/or similar international
agreements.
e. Exceptions and Limitations means fair use, fair dealing, and/or
any other exception or limitation to Copyright and Similar Rights
that applies to Your use of the Licensed Material.
f. Licensed Material means the artistic or literary work, database,
or other material to which the Licensor applied this Public
License.
g. Licensed Rights means the rights granted to You subject to the
terms and conditions of this Public License, which are limited to
all Copyright and Similar Rights that apply to Your use of the
Licensed Material and that the Licensor has authority to license.
h. Licensor means the individual(s) or entity(ies) granting rights
under this Public License.
i. Share means to provide material to the public by any means or
process that requires permission under the Licensed Rights, such
as reproduction, public display, public performance, distribution,
dissemination, communication, or importation, and to make material
available to the public including in ways that members of the
public may access the material from a place and at a time
individually chosen by them.
j. Sui Generis Database Rights means rights other than copyright
resulting from Directive 96/9/EC of the European Parliament and of
the Council of 11 March 1996 on the legal protection of databases,
as amended and/or succeeded, as well as other essentially
equivalent rights anywhere in the world.
k. You means the individual or entity exercising the Licensed Rights
under this Public License. Your has a corresponding meaning.
Section 2 -- Scope.
a. License grant.
1. Subject to the terms and conditions of this Public License,
the Licensor hereby grants You a worldwide, royalty-free,
non-sublicensable, non-exclusive, irrevocable license to
exercise the Licensed Rights in the Licensed Material to:
a. reproduce and Share the Licensed Material, in whole or
in part; and
b. produce, reproduce, and Share Adapted Material.
2. Exceptions and Limitations. For the avoidance of doubt, where
Exceptions and Limitations apply to Your use, this Public
License does not apply, and You do not need to comply with
its terms and conditions.
3. Term. The term of this Public License is specified in Section
6(a).
4. Media and formats; technical modifications allowed. The
Licensor authorizes You to exercise the Licensed Rights in
all media and formats whether now known or hereafter created,
and to make technical modifications necessary to do so. The
Licensor waives and/or agrees not to assert any right or
authority to forbid You from making technical modifications
necessary to exercise the Licensed Rights, including
technical modifications necessary to circumvent Effective
Technological Measures. For purposes of this Public License,
simply making modifications authorized by this Section 2(a)
(4) never produces Adapted Material.
5. Downstream recipients.
a. Offer from the Licensor -- Licensed Material. Every
recipient of the Licensed Material automatically
receives an offer from the Licensor to exercise the
Licensed Rights under the terms and conditions of this
Public License.
b. No downstream restrictions. You may not offer or impose
any additional or different terms or conditions on, or
apply any Effective Technological Measures to, the
Licensed Material if doing so restricts exercise of the
Licensed Rights by any recipient of the Licensed
Material.
6. No endorsement. Nothing in this Public License constitutes or
may be construed as permission to assert or imply that You
are, or that Your use of the Licensed Material is, connected
with, or sponsored, endorsed, or granted official status by,
the Licensor or others designated to receive attribution as
provided in Section 3(a)(1)(A)(i).
b. Other rights.
1. Moral rights, such as the right of integrity, are not
licensed under this Public License, nor are publicity,
privacy, and/or other similar personality rights; however, to
the extent possible, the Licensor waives and/or agrees not to
assert any such rights held by the Licensor to the limited
extent necessary to allow You to exercise the Licensed
Rights, but not otherwise.
2. Patent and trademark rights are not licensed under this
Public License.
3. To the extent possible, the Licensor waives any right to
collect royalties from You for the exercise of the Licensed
Rights, whether directly or through a collecting society
under any voluntary or waivable statutory or compulsory
licensing scheme. In all other cases the Licensor expressly
reserves any right to collect such royalties.
Section 3 -- License Conditions.
Your exercise of the Licensed Rights is expressly made subject to the
following conditions.
a. Attribution.
1. If You Share the Licensed Material (including in modified
form), You must:
a. retain the following if it is supplied by the Licensor
with the Licensed Material:
i. identification of the creator(s) of the Licensed
Material and any others designated to receive
attribution, in any reasonable manner requested by
the Licensor (including by pseudonym if
designated);
ii. a copyright notice;
iii. a notice that refers to this Public License;
iv. a notice that refers to the disclaimer of
warranties;
v. a URI or hyperlink to the Licensed Material to the
extent reasonably practicable;
b. indicate if You modified the Licensed Material and
retain an indication of any previous modifications; and
c. indicate the Licensed Material is licensed under this
Public License, and include the text of, or the URI or
hyperlink to, this Public License.
2. You may satisfy the conditions in Section 3(a)(1) in any
reasonable manner based on the medium, means, and context in
which You Share the Licensed Material. For example, it may be
reasonable to satisfy the conditions by providing a URI or
hyperlink to a resource that includes the required
information.
3. If requested by the Licensor, You must remove any of the
information required by Section 3(a)(1)(A) to the extent
reasonably practicable.
4. If You Share Adapted Material You produce, the Adapter's
License You apply must not prevent recipients of the Adapted
Material from complying with this Public License.
Section 4 -- Sui Generis Database Rights.
Where the Licensed Rights include Sui Generis Database Rights that
apply to Your use of the Licensed Material:
a. for the avoidance of doubt, Section 2(a)(1) grants You the right
to extract, reuse, reproduce, and Share all or a substantial
portion of the contents of the database;
b. if You include all or a substantial portion of the database
contents in a database in which You have Sui Generis Database
Rights, then the database in which You have Sui Generis Database
Rights (but not its individual contents) is Adapted Material; and
c. You must comply with the conditions in Section 3(a) if You Share
all or a substantial portion of the contents of the database.
For the avoidance of doubt, this Section 4 supplements and does not
replace Your obligations under this Public License where the Licensed
Rights include other Copyright and Similar Rights.
Section 5 -- Disclaimer of Warranties and Limitation of Liability.
a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
c. The disclaimer of warranties and limitation of liability provided
above shall be interpreted in a manner that, to the extent
possible, most closely approximates an absolute disclaimer and
waiver of all liability.
Section 6 -- Term and Termination.
a. This Public License applies for the term of the Copyright and
Similar Rights licensed here. However, if You fail to comply with
this Public License, then Your rights under this Public License
terminate automatically.
b. Where Your right to use the Licensed Material has terminated under
Section 6(a), it reinstates:
1. automatically as of the date the violation is cured, provided
it is cured within 30 days of Your discovery of the
violation; or
2. upon express reinstatement by the Licensor.
For the avoidance of doubt, this Section 6(b) does not affect any
right the Licensor may have to seek remedies for Your violations
of this Public License.
c. For the avoidance of doubt, the Licensor may also offer the
Licensed Material under separate terms or conditions or stop
distributing the Licensed Material at any time; however, doing so
will not terminate this Public License.
d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
License.
Section 7 -- Other Terms and Conditions.
a. The Licensor shall not be bound by any additional or different
terms or conditions communicated by You unless expressly agreed.
b. Any arrangements, understandings, or agreements regarding the
Licensed Material not stated herein are separate from and
independent of the terms and conditions of this Public License.
Section 8 -- Interpretation.
a. For the avoidance of doubt, this Public License does not, and
shall not be interpreted to, reduce, limit, restrict, or impose
conditions on any use of the Licensed Material that could lawfully
be made without permission under this Public License.
b. To the extent possible, if any provision of this Public License is
deemed unenforceable, it shall be automatically reformed to the
minimum extent necessary to make it enforceable. If the provision
cannot be reformed, it shall be severed from this Public License
without affecting the enforceability of the remaining terms and
conditions.
c. No term or condition of this Public License will be waived and no
failure to comply consented to unless expressly agreed to by the
Licensor.
d. Nothing in this Public License constitutes or may be interpreted
as a limitation upon, or waiver of, any privileges and immunities
that apply to the Licensor or You, including from the legal
processes of any jurisdiction or authority.
=======================================================================
Creative Commons is not a party to its public
licenses. Notwithstanding, Creative Commons may elect to apply one of
its public licenses to material it publishes and in those instances
will be considered the “Licensor.” The text of the Creative Commons
public licenses is dedicated to the public domain under the CC0 Public
Domain Dedication. Except for the limited purpose of indicating that
material is shared under a Creative Commons public license or as
otherwise permitted by the Creative Commons policies published at
creativecommons.org/policies, Creative Commons does not authorize the
use of the trademark "Creative Commons" or any other trademark or logo
of Creative Commons without its prior written consent including,
without limitation, in connection with any unauthorized modifications
to any of its public licenses or any other arrangements,
understandings, or agreements concerning use of licensed material. For
the avoidance of doubt, this paragraph does not form part of the
public licenses.
Creative Commons may be contacted at creativecommons.org.

View File

@@ -1,8 +1,6 @@
# Maintainers
- Sebastien Boeuf - @sboeuf
- Robert Bradford - @rbradford
- Bo Chen - @likebreath
- Samuel Ortiz - @sameo
- Wei Liu - @liuw
- Michael Zhao - @michael2012z
- Sebastien Boeuf <sebastien.boeuf@intel.com> @sboeuf
- Robert Bradford <robert.bradford@intel.com> @rbradford
- Samuel Ortiz <sameo@linux.intel.com> @sameo
- Chao P Peng <chao.p.peng@linux.intel.com> @chao-p

475
README.md
View File

@@ -1,52 +1,42 @@
[![Build Status](https://travis-ci.com/cloud-hypervisor/cloud-hypervisor.svg?branch=master)](https://travis-ci.com/cloud-hypervisor/cloud-hypervisor)
- [1. What is Cloud Hypervisor?](#1-what-is-cloud-hypervisor)
- [Objectives](#objectives)
- [High Level](#high-level)
- [Architectures](#architectures)
- [Guest OS](#guest-os)
- [2. Getting Started](#2-getting-started)
- [Host OS](#host-os)
- [Use Pre-built Binaries](#use-pre-built-binaries)
- [Packages](#packages)
- [Building from Source](#building-from-source)
- [Booting Linux](#booting-linux)
- [Firmware Booting](#firmware-booting)
- [Custom Kernel and Disk Image](#custom-kernel-and-disk-image)
- [Building your Kernel](#building-your-kernel)
- [Clone and build](#clone-and-build)
- [Containerized builds and tests](#containerized-builds-and-tests)
- [Run](#run)
- [Cloud image](#cloud-image)
- [Custom kernel and disk image](#custom-kernel-and-disk-image)
- [Building your kernel](#building-your-kernel)
- [Disk image](#disk-image)
- [Booting the guest VM](#booting-the-guest-vm)
- [3. Status](#3-status)
- [Hot Plug](#hot-plug)
- [Device Model](#device-model)
- [Roadmap](#roadmap)
- [4. Relationship with _Rust VMM_ Project](#4-relationship-with-rust-vmm-project)
- [Differences with Firecracker and crosvm](#differences-with-firecracker-and-crosvm)
- [TODO](#todo)
- [4. `rust-vmm` project dependency](#4-rust-vmm-project-dependency)
- [Firecracker and crosvm](#firecracker-and-crosvm)
- [5. Community](#5-community)
- [Contribute](#contribute)
- [Slack](#slack)
- [Mailing list](#mailing-list)
- [Security issues](#security-issues)
- [Join us](#join-us)
# 1. What is Cloud Hypervisor?
Cloud Hypervisor is an open source Virtual Machine Monitor (VMM) that runs on
top of the [KVM](https://www.kernel.org/doc/Documentation/virtual/kvm/api.txt)
hypervisor and the Microsoft Hypervisor (MSHV).
Cloud Hypervisor is an open source Virtual Machine Monitor (VMM) that runs on top of [KVM](https://www.kernel.org/doc/Documentation/virtual/kvm/api.txt).
The project focuses on exclusively running modern, cloud workloads, on top of a limited set of hardware architectures and platforms.
Cloud workloads refers to those that are usually run by customers inside a cloud provider. For our purposes this means modern operating systems with most I/O handled by paravirtualised devices (i.e. virtio), no requirement for legacy devices, and 64-bit CPUs.
The project focuses on running modern, _Cloud Workloads_, on specific, common,
hardware architectures. In this case _Cloud Workloads_ refers to those that are
run by customers inside a Cloud Service Provider. This means modern operating
systems with most I/O handled by
paravirtualised devices (e.g. _virtio_), no requirement for legacy devices, and
64-bit CPUs.
Cloud Hypervisor is implemented in [Rust](https://www.rust-lang.org/) and is
based on the [Rust VMM](https://github.com/rust-vmm) crates.
Cloud Hypervisor is implemented in [Rust](https://www.rust-lang.org/) and is based on the [rust-vmm](https://github.com/rust-vmm) crates.
## Objectives
### High Level
- Runs on KVM or MSHV
- KVM based
- Minimal emulation
- Low latency
- Low memory footprint
@@ -59,210 +49,185 @@ 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 some small differences in functionality between the two architectures (see [#1125](https://github.com/cloud-hypervisor/cloud-hypervisor/issues/1125)).
### Guest OS
Cloud Hypervisor supports `64-bit Linux` and Windows 10/Windows Server 2019.
Cloud Hypervisor supports `64-bit Linux` with support for _modern_ 64-bit Windows guests currently under development.
# 2. Getting Started
The following sections describe how to build and run Cloud Hypervisor.
## Prerequisites for AArch64
- AArch64 servers (recommended) or development boards equipped with the GICv3
interrupt controller.
## Host OS
For required KVM functionality and adequate performance the recommended host
kernel version is 5.13. The majority of the CI currently tests with kernel
version 5.15.
## Use Pre-built Binaries
The recommended approach to getting started with Cloud Hypervisor is by using a
pre-built binary. Binaries are available for the [latest
release](https://github.com/cloud-hypervisor/cloud-hypervisor/releases/latest).
Use `cloud-hypervisor-static` for `x86-64` or `cloud-hypervisor-static-aarch64`
for `AArch64` platform.
## Packages
For convenience, packages are also available targeting some popular Linux
distributions. This is thanks to the [Open Build
Service](https://build.opensuse.org). The [OBS
README](https://github.com/cloud-hypervisor/obs-packaging) explains how to
enable the repository in a supported Linux distribution and install Cloud Hypervisor
and accompanying packages. Please report any packaging issues in the
[obs-packaging](https://github.com/cloud-hypervisor/obs-packaging) repository.
## Building from Source
Please see the [instructions for building from source](docs/building.md) if you
do not wish to use the pre-built binaries.
## Booting Linux
Cloud Hypervisor supports direct kernel boot (the x86-64 kernel requires the kernel
built with PVH support or a bzImage) or booting via a firmware (either [Rust Hypervisor
Firmware](https://github.com/cloud-hypervisor/rust-hypervisor-firmware) or an
edk2 UEFI firmware called `CLOUDHV` / `CLOUDHV_EFI`.)
Binary builds of the firmware files are available for the latest release of
[Rust Hypervisor
Firmware](https://github.com/cloud-hypervisor/rust-hypervisor-firmware/releases/latest)
and [our edk2
repository](https://github.com/cloud-hypervisor/edk2/releases/latest)
The choice of firmware depends on your guest OS choice; some experimentation
may be required.
### Firmware Booting
Cloud Hypervisor supports booting disk images containing all needed components
to run cloud workloads, a.k.a. cloud images.
The following sample commands will download an Ubuntu Cloud image, converting
it into a format that Cloud Hypervisor can use and a firmware to boot the image
with.
We create a folder to build and run `cloud-hypervisor` at `$HOME/cloud-hypervisor`
```shell
$ export CLOUDH=$HOME/cloud-hypervisor
$ mkdir $CLOUDH
```
## Install prerequisites
You need to install some prerequisite packages in order to build and test Cloud Hypervisor.
Here, all the steps are based on Ubuntu, for other Linux distributions please replace the
package manager and package name.
```shell
# Install git
$ sudo apt install git
# Install rust tool chain
$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Install build-essential
$ sudo apt install build-essential
# If you want to build statically linked binary please add musl target
$ rustup target add x86_64-unknown-linux-musl
```
## Clone and build
First you need to clone and build the cloud-hypervisor repo:
```shell
$ pushd $CLOUDH
$ git clone https://github.com/cloud-hypervisor/cloud-hypervisor.git
$ cd cloud-hypervisor
$ cargo build --release
# We need to give the cloud-hypervisor binary the NET_ADMIN capabilities for it to set TAP interfaces up on the host.
$ sudo setcap cap_net_admin+ep ./target/release/cloud-hypervisor
# If you want to build statically linked binary
$ cargo build --release --target=x86_64-unknown-linux-musl --all
$ popd
```
This will build a `cloud-hypervisor` binary under `$CLOUDH/cloud-hypervisor/target/release/cloud-hypervisor`.
### Containerized builds and tests
If you want to build and test Cloud Hypervisor without having to install all the
required dependencies (The rust toolchain, cargo tools, etc), you can also use
Cloud Hypervisor's development script: `dev_cli.sh`. Please note that upon its
first invocation, this script will pull a fairly large container image.
For example, to build the Cloud Hypervisor release binary:
```shell
$ pushd $CLOUDH
$ cd cloud-hypervisor
$ ./scripts/dev_cli.sh build --release
```
With `dev_cli.sh`, one can also run the Cloud Hypervisor CI locally. This can be
very convenient for debugging CI errors without having to fully rely on the
Cloud Hypervisor CI infrastructure.
For example, to run the Cloud Hypervisor unit tests:
```shell
$ ./scripts/dev_cli.sh tests --unit
```
Run the `./scripts/dev_cli.sh --help` command to view all the supported
development script commands and their related options.
## Run
You can run a guest VM by either using an existing cloud image or booting into your own kernel and disk image.
### Cloud image
Cloud Hypervisor supports booting disk images containing all needed
components to run cloud workloads, a.k.a. cloud images. To do that we rely on
the [Rust Hypervisor
Firmware](https://github.com/cloud-hypervisor/rust-hypervisor-firmware) project to provide
an ELF
formatted KVM firmware for `cloud-hypervisor` to directly boot into.
We need to get the latest `rust-hypervisor-firmware` release and also a working cloud image. Here we will use a Ubuntu image:
```shell
$ pushd $CLOUDH
$ wget https://cloud-images.ubuntu.com/focal/current/focal-server-cloudimg-amd64.img
$ qemu-img convert -p -f qcow2 -O raw focal-server-cloudimg-amd64.img focal-server-cloudimg-amd64.raw
$ wget https://github.com/cloud-hypervisor/rust-hypervisor-firmware/releases/download/0.4.2/hypervisor-fw
$ wget https://github.com/cloud-hypervisor/rust-hypervisor-firmware/releases/download/0.3.0/hypervisor-fw
$ popd
```
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.
```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 \
--cpus boot=4 \
--memory size=1024M \
--net "tap=,mac=,ip=,mask="
```
If access to the firmware messages or interaction with the boot loader (e.g.
GRUB) is required then it necessary to switch to the serial console instead of
`virtio-console`.
```shell
$ ./cloud-hypervisor \
$ pushd $CLOUDH
$ sudo setcap cap_net_admin+ep ./cloud-hypervisor/target/release/cloud-hypervisor
$ ./cloud-hypervisor/target/release/cloud-hypervisor \
--kernel ./hypervisor-fw \
--disk path=focal-server-cloudimg-amd64.raw path=/tmp/ubuntu-cloudinit.img \
--disk path=focal-server-cloudimg-amd64.raw \
--cpus boot=4 \
--memory size=1024M \
--net "tap=,mac=,ip=,mask=" \
--serial tty \
--console off
--rng
$ popd
```
## Booting: `--firmware` vs `--kernel`
Multiple arguments can be given to the `--disk` parameter.
The following scenarios are supported by Cloud Hypervisor to bootstrap a VM, i.e.,
to load a payload/bootitem(s):
### Custom kernel and disk image
- Provide firmware
- Provide kernel \[+ cmdline\]\ [+ initrd\]
#### Building your kernel
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 into a `vmlinux` ELF kernel or `bzImage`. In order to support virtio-fs and virtio-iommu we have our own development branch. You are of course able to use your own kernel but these instructions will continue with the version that we develop and test against.
To build the kernel:
```shell
# Clone the Cloud Hypervisor Linux branch
$ git clone --depth 1 https://github.com/cloud-hypervisor/linux.git -b ch-6.12.8 linux-cloud-hypervisor
$ pushd $CLOUDH
$ git clone --depth 1 https://github.com/cloud-hypervisor/linux.git -b virtio-fs-virtio-iommu-virtio-mem-5.6-rc4 linux-cloud-hypervisor
$ pushd linux-cloud-hypervisor
$ make ch_defconfig
# 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
$ make -j `nproc`
# Use the cloud-hypervisor kernel config to build your kernel
$ cp $CLOUDH/cloud-hypervisor/resources/linux-config-x86_64 .config
$ make bzImage -j `nproc`
$ popd
```
For x86-64, the `vmlinux` kernel image will then be located at
`linux-cloud-hypervisor/arch/x86/boot/compressed/vmlinux.bin`.
For AArch64, the `Image` kernel image will then be located at
`linux-cloud-hypervisor/arch/arm64/boot/Image`.
The `vmlinux` kernel image will then be located at `linux-cloud-hypervisor/arch/x86/boot/compressed/vmlinux.bin`.
#### Disk image
For the disk image the same Ubuntu image as before can be used. This contains
an `ext4` root filesystem.
For the disk image, we will use a Ubuntu cloud image that contains a root partition:
```shell
$ wget https://cloud-images.ubuntu.com/focal/current/focal-server-cloudimg-amd64.img # x86-64
$ wget https://cloud-images.ubuntu.com/focal/current/focal-server-cloudimg-arm64.img # AArch64
$ qemu-img convert -p -f qcow2 -O raw focal-server-cloudimg-amd64.img focal-server-cloudimg-amd64.raw # x86-64
$ qemu-img convert -p -f qcow2 -O raw focal-server-cloudimg-arm64.img focal-server-cloudimg-arm64.raw # AArch64
$ pushd $CLOUDH
$ wget https://cloud-images.ubuntu.com/focal/current/focal-server-cloudimg-amd64.img
$ qemu-img convert -p -f qcow2 -O raw focal-server-cloudimg-amd64.img focal-server-cloudimg-amd64.raw
$ popd
```
#### Booting the guest VM
These sample commands boot the disk image using the custom kernel whilst also
supplying the desired kernel command line.
- x86-64
Now we can directly boot into our custom kernel and make it use the Ubuntu root partition.
If we want to have 4 vCPUs and 512 MBytes of memory:
```shell
$ sudo setcap cap_net_admin+ep ./cloud-hypervisor
$ ./create-cloud-init.sh
$ ./cloud-hypervisor \
$ pushd $CLOUDH
$ sudo setcap cap_net_admin+ep ./cloud-hypervisor/target/release/cloud-hypervisor
$ ./cloud-hypervisor/target/release/cloud-hypervisor \
--kernel ./linux-cloud-hypervisor/arch/x86/boot/compressed/vmlinux.bin \
--disk path=focal-server-cloudimg-amd64.raw path=/tmp/ubuntu-cloudinit.img \
--disk path=focal-server-cloudimg-amd64.raw \
--cmdline "console=hvc0 root=/dev/vda1 rw" \
--cpus boot=4 \
--memory size=1024M \
--net "tap=,mac=,ip=,mask="
--net "tap=,mac=,ip=,mask=" \
--rng
```
- AArch64
The above example use the `virtio-console` device as the guest console, and this
device may not be enabled soon enough by the guest kernel to get early kernel
debug messages.
When in need for earlier debug messages, using the legacy serial device based
console is preferred:
```shell
$ sudo setcap cap_net_admin+ep ./cloud-hypervisor
$ ./create-cloud-init.sh
$ ./cloud-hypervisor \
--kernel ./linux-cloud-hypervisor/arch/arm64/boot/Image \
--disk path=focal-server-cloudimg-arm64.raw path=/tmp/ubuntu-cloudinit.img \
--cmdline "console=hvc0 root=/dev/vda1 rw" \
--cpus boot=4 \
--memory size=1024M \
--net "tap=,mac=,ip=,mask="
```
If earlier kernel messages are required the serial console should be used instead of `virtio-console`.
- x86-64
```shell
$ ./cloud-hypervisor \
$ ./cloud-hypervisor/target/release/cloud-hypervisor \
--kernel ./linux-cloud-hypervisor/arch/x86/boot/compressed/vmlinux.bin \
--console off \
--serial tty \
@@ -270,133 +235,85 @@ $ ./cloud-hypervisor \
--cmdline "console=ttyS0 root=/dev/vda1 rw" \
--cpus boot=4 \
--memory size=1024M \
--net "tap=,mac=,ip=,mask="
```
- AArch64
```shell
$ ./cloud-hypervisor \
--kernel ./linux-cloud-hypervisor/arch/arm64/boot/Image \
--console off \
--serial tty \
--disk path=focal-server-cloudimg-arm64.raw \
--cmdline "console=ttyAMA0 root=/dev/vda1 rw" \
--cpus boot=4 \
--memory size=1024M \
--net "tap=,mac=,ip=,mask="
--net "tap=,mac=,ip=,mask=" \
--rng
```
# 3. Status
Cloud Hypervisor is under active development. The following stability
guarantees are currently made:
Cloud Hypervisor is under active development. No API or feature stability is guaranteed.
* The API (including command line options) will not be removed or changed in a
breaking way without a minimum of 2 major releases notice. Where possible
warnings will be given about the use of deprecated functionality and the
deprecations will be documented in the release notes.
As of 2020-07-02, the following cloud images are supported:
* Point releases will be made between individual releases where there are
substantial bug fixes or security issues that need to be fixed. These point
releases will only include bug fixes.
- [Ubuntu Bionic](https://cloud-images.ubuntu.com/bionic/current/) (cloudimg)
- [Ubuntu Focal](https://cloud-images.ubuntu.com/focal/current/) (cloudimg)
Currently the following items are **not** guaranteed across updates:
* Snapshot/restore is not supported across different versions
* Live migration is not supported across different versions
* The following features are considered experimental and may change
substantially between releases: TDX, vfio-user, vDPA.
Further details can be found in the [release documentation](docs/releases.md).
As of 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/))
Direct kernel boot to userspace should work with a rootfs from most
distributions although you may need to enable exotic filesystem types in the
reference kernel configuration (e.g. XFS or btrfs.)
Direct kernel boot to userspace should work with a rootfs from most distributions.
## Hot Plug
Cloud Hypervisor supports hotplug of CPUs, passthrough devices (VFIO),
`virtio-{net,block,pmem,fs,vsock}` and memory resizing. This
[document](docs/hotplug.md) details how to add devices to a running VM.
Cloud Hypervisor supports hotplug of CPUs, passthrough devices (VFIO), `virtio-{net,block,pmem,fs,vsock}` and memory resizing. This [document](https://github.com/cloud-hypervisor/cloud-hypervisor/blob/master/docs/hotplug.md) details how to add devices to
a running VM.
## Device Model
Details of the device model can be found in this
[documentation](docs/device_model.md).
Details of the device model can be found in this [documentation](https://github.com/cloud-hypervisor/cloud-hypervisor/blob/master/docs/device_model.md).
## Roadmap
## TODO
The project roadmap is tracked through a [GitHub
project](https://github.com/orgs/cloud-hypervisor/projects/6).
We are not tracking the Cloud Hypervisor TODO list from a specific git tracked file but through
[github issues](https://github.com/cloud-hypervisor/cloud-hypervisor/issues/new) instead.
# 4. Relationship with _Rust VMM_ Project
# 4. `rust-vmm` project dependency
In order to satisfy the design goal of having a high-performance,
security-focused hypervisor the decision was made to use the
[Rust](https://www.rust-lang.org/) programming language. The language's strong
focus on memory and thread safety makes it an ideal candidate for implementing
VMMs.
In order to satisfy the design goal of having a high-performance, security-focused hypervisor the decision
was made to use the [Rust](https://www.rust-lang.org/) programming language.
The language's strong focus on memory and thread safety makes it an ideal candidate for implementing VMMs.
Instead of implementing the VMM components from scratch, Cloud Hypervisor is
importing the [Rust VMM](https://github.com/rust-vmm) crates, and sharing code
and architecture together with other VMMs like e.g. Amazon's
[Firecracker](https://firecracker-microvm.github.io/) and Google's
[crosvm](https://chromium.googlesource.com/chromiumos/platform/crosvm/).
Instead of implementing the VMM components from scratch, Cloud Hypervisor is importing the [rust-vmm](https://github.com/rust-vmm)
crates, and sharing code and architecture together with other VMMs like e.g. Amazon's [Firecracker](https://firecracker-microvm.github.io/)
and Google's [crosvm](https://chromium.googlesource.com/chromiumos/platform/crosvm/).
Cloud Hypervisor embraces the _Rust VMM_ project's goals, which is to be able
to share and re-use as many virtualization crates as possible.
Cloud Hypervisor embraces the rust-vmm project goals, which is to be able to share and re-use
as many virtualization crates as possible. As such, the Cloud Hypervisor relationship with the rust-vmm
project is twofold:
## Differences with Firecracker and crosvm
1. It will use as much of the rust-vmm code as possible. Any new rust-vmm crate that's relevant to the project
goals will be integrated as soon as possible.
2. As it is likely that the rust-vmm project will lack some of the features that Cloud Hypervisor needs (e.g. ACPI,
VFIO, vhost-user, etc), we will be using the Cloud Hypervisor VMM to implement and test them, and contribute them
back to the rust-vmm project.
A large part of the Cloud Hypervisor code is based on either the Firecracker or
the crosvm project's implementations. Both of these are VMMs written in Rust
with a focus on safety and security, like Cloud Hypervisor.
## Firecracker and crosvm
The goal of the Cloud Hypervisor project differs from the aforementioned
projects in that it aims to be a general purpose VMM for _Cloud Workloads_ and
not limited to container/serverless or client workloads.
A large part of the Cloud Hypervisor code is based on either the Firecracker or the crosvm projects implementations.
Both of these are VMMs written in Rust with a focus on safety and security, like Cloud Hypervisor.
The Cloud Hypervisor community thanks the communities of both the Firecracker
and crosvm projects for their excellent work.
However we want to emphasize that the Cloud Hypervisor project is neither a fork nor a reimplementation of any of those
projects. The goals and use cases we're trying to meet are different.
We're aiming at supporting cloud workloads, i.e. those modern, full Linux distribution images currently being run by
Cloud Service Provider (CSP) tenants.
Our primary target is not to support client or serverless use cases, and as such our code base already diverges
from the crosvm and Firecracker ones. As we add more features to support our use cases, we believe that the divergence
will increase while at the same time sharing as much of the fundamental virtualization code through the rust-vmm project
crates as possible.
# 5. Community
The Cloud Hypervisor project follows the governance, and community guidelines
described in the [Community](https://github.com/cloud-hypervisor/community)
repository.
The Cloud Hypervisor project follows the governance, and community guidelines described in
the [Community](https://github.com/cloud-hypervisor/community) repository.
## Contribute
The project strongly believes in building a global, diverse and collaborative
community around the Cloud Hypervisor project. Anyone who is interested in
[contributing](CONTRIBUTING.md) to the project is welcome to participate.
We are working on building a global, diverse and collaborative community around the Cloud Hypervisor project.
Anyone who is interested in [contributing](CONTRIBUTING.md) to the project is welcome to participate.
Contributing to a open source project like Cloud Hypervisor covers a lot more
than just sending code. Testing, documentation, pull request
reviews, bug reports, feature requests, project improvement suggestions, etc,
are all equal and welcome means of contribution. See the
[CONTRIBUTING](CONTRIBUTING.md) document for more details.
We believe that contributing to a open source project like Cloud Hypervisor covers a lot more than just sending
code. Testing, documentation, pull request reviews, bug reports, feature requests, project improvement suggestions,
etc, are all equal and welcome means of contribution. See the [CONTRIBUTING](CONTRIBUTING.md) document for more details.
## Slack
## Join us
Get an [invite to our Slack channel](https://join.slack.com/t/cloud-hypervisor/shared_invite/enQtNjY3MTE3MDkwNDQ4LWQ1MTA1ZDVmODkwMWQ1MTRhYzk4ZGNlN2UwNTI3ZmFlODU0OTcwOWZjMTkwZDExYWE3YjFmNzgzY2FmNDAyMjI),
[join us on Slack](https://cloud-hypervisor.slack.com/), and [participate in our community activities](https://cloud-hypervisor.slack.com/archives/C04R5DUQVBN).
## Mailing list
Please report bugs using the [GitHub issue
tracker](https://github.com/cloud-hypervisor/cloud-hypervisor/issues) but for
broader community discussions you may use our [mailing
list](https://lists.cloudhypervisor.org/g/dev/).
## Security issues
Please contact the maintainers listed in the MAINTAINERS.md file with security issues.
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/).

9
acpi_tables/Cargo.toml Normal file
View File

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

1965
acpi_tables/src/aml.rs Normal file

File diff suppressed because it is too large Load Diff

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

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

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

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

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

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

View File

@@ -1,9 +1,5 @@
[package]
authors = ["The Cloud Hypervisor Authors"]
edition.workspace = true
name = "api_client"
version = "0.1.0"
[dependencies]
thiserror = { workspace = true }
vmm-sys-util = { workspace = true }
authors = ["The Cloud Hypervisor Authors"]
edition = "2018"

View File

@@ -3,32 +3,35 @@
// 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),
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),
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 +41,6 @@ pub enum StatusCode {
NoContent,
BadRequest,
NotFound,
TooManyRequests,
InternalServerError,
NotImplemented,
Unknown,
@@ -52,7 +54,6 @@ impl StatusCode {
204 => StatusCode::NoContent,
400 => StatusCode::BadRequest,
404 => StatusCode::NotFound,
429 => StatusCode::TooManyRequests,
500 => StatusCode::InternalServerError,
501 => StatusCode::NotImplemented,
_ => StatusCode::Unknown,
@@ -74,9 +75,12 @@ impl StatusCode {
}
fn get_header<'a>(res: &'a str, header: &'a str) -> Option<&'a str> {
let header_str = format!("{header}: ");
res.find(&header_str)
.map(|o| &res[o + header_str.len()..o + res[o..].find('\r').unwrap()])
let header_str = format!("{}: ", header);
if let Some(o) = res.find(&header_str) {
Some(&res[o + header_str.len()..o + res[o..].find('\r').unwrap()])
} else {
None
}
}
fn get_status_code(res: &str) -> Result<StatusCode, Error> {
@@ -96,10 +100,6 @@ fn parse_http_response(socket: &mut dyn Read) -> Result<Option<String>, Error> {
loop {
let mut bytes = vec![0; 256];
let count = socket.read(&mut bytes).map_err(Error::Socket)?;
// If the return value is 0, the peer has performed an orderly shutdown.
if count == 0 {
break;
}
res.push_str(std::str::from_utf8(&bytes[0..count]).unwrap());
// End of headers
@@ -118,14 +118,15 @@ 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..])));
let body_string = content_length.and(Some(String::from(&res[body_offset.unwrap()..])));
let status_code = get_status_code(&res)?;
if status_code.is_server_error() {
@@ -135,24 +136,21 @@ fn parse_http_response(socket: &mut dyn Read) -> Result<Option<String>, Error> {
}
}
/// Make an API request using the fully qualified command name.
/// For example, full_command could be "vm.create" or "vmm.ping".
pub fn simple_api_full_command_with_fds_and_response<T: Read + Write + ScmSocket>(
pub fn simple_api_command<T: Read + Write>(
socket: &mut T,
method: &str,
full_command: &str,
c: &str,
request_body: Option<&str>,
request_fds: Vec<RawFd>,
) -> Result<Option<String>, Error> {
) -> Result<(), Error> {
socket
.send_with_fds(
&[format!(
"{method} /api/v1/{full_command} HTTP/1.1\r\nHost: localhost\r\nAccept: */*\r\n"
.write_all(
format!(
"{} /api/v1/vm.{} HTTP/1.1\r\nHost: localhost\r\nAccept: */*\r\n",
method, c
)
.as_bytes()],
&request_fds,
.as_bytes(),
)
.map_err(Error::SocketSendFds)?;
.map_err(Error::Socket)?;
if let Some(request_body) = request_body {
socket
@@ -170,74 +168,8 @@ pub fn simple_api_full_command_with_fds_and_response<T: Read + Write + ScmSocket
socket.flush().map_err(Error::Socket)?;
parse_http_response(socket)
}
pub fn simple_api_full_command_with_fds<T: Read + Write + ScmSocket>(
socket: &mut T,
method: &str,
full_command: &str,
request_body: Option<&str>,
request_fds: Vec<RawFd>,
) -> Result<(), Error> {
let response = simple_api_full_command_with_fds_and_response(
socket,
method,
full_command,
request_body,
request_fds,
)?;
if response.is_some() {
println!("{}", response.unwrap());
if let Some(body) = parse_http_response(socket)? {
println!("{}", body);
}
Ok(())
}
pub fn simple_api_full_command<T: Read + Write + ScmSocket>(
socket: &mut T,
method: &str,
full_command: &str,
request_body: Option<&str>,
) -> Result<(), Error> {
simple_api_full_command_with_fds(socket, method, full_command, request_body, Vec::new())
}
pub fn simple_api_full_command_and_response<T: Read + Write + ScmSocket>(
socket: &mut T,
method: &str,
full_command: &str,
request_body: Option<&str>,
) -> Result<Option<String>, Error> {
simple_api_full_command_with_fds_and_response(
socket,
method,
full_command,
request_body,
Vec::new(),
)
}
pub fn simple_api_command_with_fds<T: Read + Write + ScmSocket>(
socket: &mut T,
method: &str,
c: &str,
request_body: Option<&str>,
request_fds: Vec<RawFd>,
) -> Result<(), Error> {
// Create the full VM command. For VMM commands, use
// simple_api_full_command().
let full_command = format!("vm.{c}");
simple_api_full_command_with_fds(socket, method, &full_command, request_body, request_fds)
}
pub fn simple_api_command<T: Read + Write + ScmSocket>(
socket: &mut T,
method: &str,
c: &str,
request_body: Option<&str>,
) -> Result<(), Error> {
simple_api_command_with_fds(socket, method, c, request_body, Vec::new())
}

View File

@@ -1,29 +1,26 @@
[package]
authors = ["The Chromium OS Authors"]
edition.workspace = true
name = "arch"
version = "0.1.0"
authors = ["The Chromium OS Authors"]
[features]
default = []
fw_cfg = []
kvm = ["hypervisor/kvm"]
sev_snp = []
acpi = ["acpi_tables"]
tdx = []
[dependencies]
anyhow = { workspace = true }
byteorder = { workspace = true }
acpi_tables = { path = "../acpi_tables", optional = true }
anyhow = "1.0"
arch_gen = { path = "../arch_gen" }
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.91"
linux-loader = { version = "0.3.0", features = ["elf", "bzimage", "pe"] }
log = "0.4.14"
serde = {version = ">=1.0.27", features = ["rc"] }
serde_derive = ">=1.0.27"
serde_json = ">=1.0.9"
thiserror = "1.0"
vm-memory = { version = "0.5.0", features = ["backend-mmap"] }
vm-migration = { path = "../vm-migration" }
[target.'cfg(any(target_arch = "aarch64", target_arch = "riscv64"))'.dependencies]
fdt_parser = { version = "0.1.5", package = "fdt" }
vm-fdt = { workspace = true }

File diff suppressed because it is too large Load Diff

View File

@@ -1,17 +1,16 @@
// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
use kvm_ioctls::DeviceFd;
use crate::arch::aarch64::gic::{Error, Result};
use crate::device::HypervisorDeviceError;
use crate::kvm::kvm_bindings::{
KVM_DEV_ARM_VGIC_GRP_DIST_REGS, KVM_DEV_ARM_VGIC_GRP_NR_IRQS, kvm_device_attr,
use super::{Error, Result};
use crate::layout::IRQ_BASE;
use hypervisor::kvm::kvm_bindings::{
kvm_device_attr, KVM_DEV_ARM_VGIC_GRP_DIST_REGS, KVM_DEV_ARM_VGIC_GRP_NR_IRQS,
};
use std::sync::Arc;
/*
Distributor registers as detailed at page 456 from
https://developer.arm.com/documentation/ihi0069/c/?lang=en.
https://static.docs.arm.com/ihi0069/c/IHI0069C_gic_architecture_specification.pdf.
Address offsets are relative to the Distributor base address defined
by the system memory map. Unless otherwise stated in the register description,
all GIC registers are 32-bits wide.
@@ -64,7 +63,7 @@ macro_rules! VGIC_DIST_REG {
// List with relevant distributor registers that we will be restoring.
// Order is taken from qemu.
static VGIC_DIST_REGS: &[DistReg] = &[
static VGIC_DIST_REGS: &'static [DistReg] = &[
VGIC_DIST_REG!(GICD_STATUSR, 0, 4),
VGIC_DIST_REG!(GICD_ICENABLER, 1, 0),
VGIC_DIST_REG!(GICD_ISENABLER, 1, 0),
@@ -78,71 +77,55 @@ static VGIC_DIST_REGS: &[DistReg] = &[
VGIC_DIST_REG!(GICD_IPRIORITYR, 8, 0),
];
fn dist_attr_set(gic: &DeviceFd, offset: u32, val: u32) -> Result<()> {
let gic_dist_attr = kvm_device_attr {
group: KVM_DEV_ARM_VGIC_GRP_DIST_REGS,
attr: offset as u64,
addr: &val as *const u32 as u64,
flags: 0,
};
gic.set_device_attr(&gic_dist_attr).map_err(|e| {
Error::SetDeviceAttribute(HypervisorDeviceError::SetDeviceAttribute(e.into()))
})?;
Ok(())
}
fn dist_attr_get(gic: &DeviceFd, offset: u32) -> Result<u32> {
let mut val = 0;
fn dist_attr_access(
gic: &Arc<dyn hypervisor::Device>,
offset: u32,
val: &u32,
set: bool,
) -> Result<()> {
let mut gic_dist_attr = kvm_device_attr {
group: KVM_DEV_ARM_VGIC_GRP_DIST_REGS,
attr: offset as u64,
addr: &mut val as *mut u32 as u64,
addr: val as *const u32 as u64,
flags: 0,
};
// SAFETY: gic_dist_attr.addr is safe to write to.
unsafe { gic.get_device_attr(&mut gic_dist_attr) }.map_err(|e| {
Error::GetDeviceAttribute(HypervisorDeviceError::GetDeviceAttribute(e.into()))
})?;
Ok(val)
if set {
gic.set_device_attr(&gic_dist_attr)
.map_err(Error::SetDeviceAttribute)?;
} else {
gic.get_device_attr(&mut gic_dist_attr)
.map_err(Error::GetDeviceAttribute)?;
}
Ok(())
}
/// Get the distributor control register.
pub fn read_ctlr(gic: &DeviceFd) -> Result<u32> {
dist_attr_get(gic, GICD_CTLR)
pub fn read_ctlr(gic: &Arc<dyn hypervisor::Device>) -> Result<u32> {
let val: u32 = 0;
dist_attr_access(gic, GICD_CTLR, &val, false)?;
Ok(val)
}
/// Set the distributor control register.
pub fn write_ctlr(gic: &DeviceFd, val: u32) -> Result<()> {
dist_attr_set(gic, GICD_CTLR, val)
pub fn write_ctlr(gic: &Arc<dyn hypervisor::Device>, val: u32) -> Result<()> {
dist_attr_access(gic, GICD_CTLR, &val, true)
}
fn get_interrupts_num(gic: &DeviceFd) -> Result<u32> {
let mut num_irq = 0;
fn get_interrupts_num(gic: &Arc<dyn hypervisor::Device>) -> Result<u32> {
let num_irq = 0;
let mut nr_irqs_attr = kvm_device_attr {
group: KVM_DEV_ARM_VGIC_GRP_NR_IRQS,
attr: 0,
addr: &mut num_irq as *mut u32 as u64,
addr: &num_irq as *const u32 as u64,
flags: 0,
};
// SAFETY: nr_irqs_attr.addr is safe to write to.
unsafe { gic.get_device_attr(&mut nr_irqs_attr) }.map_err(|e| {
Error::GetDeviceAttribute(HypervisorDeviceError::GetDeviceAttribute(e.into()))
})?;
gic.get_device_attr(&mut nr_irqs_attr)
.map_err(Error::GetDeviceAttribute)?;
Ok(num_irq)
}
fn compute_reg_len(gic: &DeviceFd, reg: &DistReg, base: u32) -> Result<u32> {
// FIXME:
// Redefine some GIC constants to avoid the dependency on `layout` crate.
// This is temporary solution, will be fixed in future refactoring.
const LAYOUT_IRQ_BASE: u32 = 32;
fn compute_reg_len(gic: &Arc<dyn hypervisor::Device>, reg: &DistReg, base: u32) -> Result<u32> {
let mut end = base;
let num_irq = get_interrupts_num(gic)?;
if reg.length > 0 {
@@ -155,8 +138,8 @@ fn compute_reg_len(gic: &DeviceFd, reg: &DistReg, base: u32) -> Result<u32> {
// This is the type of register that takes into account the number of interrupts
// that the model has. It is also the type of register where
// a register relates to multiple interrupts.
end = base + (reg.bpi as u32 * (num_irq - LAYOUT_IRQ_BASE) / 8);
if !(reg.bpi as u32 * (num_irq - LAYOUT_IRQ_BASE)).is_multiple_of(8) {
end = base + (reg.bpi as u32 * (num_irq - IRQ_BASE) / 8);
if reg.bpi as u32 * (num_irq - IRQ_BASE) % 8 > 0 {
end += REG_SIZE as u32;
}
}
@@ -164,15 +147,16 @@ fn compute_reg_len(gic: &DeviceFd, reg: &DistReg, base: u32) -> Result<u32> {
}
/// Set distributor registers of the GIC.
pub fn set_dist_regs(gic: &DeviceFd, state: &[u32]) -> Result<()> {
pub fn set_dist_regs(gic: &Arc<dyn hypervisor::Device>, state: &[u32]) -> Result<()> {
let mut idx = 0;
for dreg in VGIC_DIST_REGS {
let mut base = dreg.base + REG_SIZE as u32 * dreg.bpi as u32;
let end = compute_reg_len(gic, dreg, base)?;
let end = compute_reg_len(gic, &dreg, base)?;
while base < end {
dist_attr_set(gic, base, state[idx])?;
let val = state[idx];
dist_attr_access(gic, base, &val, true)?;
idx += 1;
base += REG_SIZE as u32;
}
@@ -180,15 +164,17 @@ pub fn set_dist_regs(gic: &DeviceFd, state: &[u32]) -> Result<()> {
Ok(())
}
/// Get distributor registers of the GIC.
pub fn get_dist_regs(gic: &DeviceFd) -> Result<Vec<u32>> {
pub fn get_dist_regs(gic: &Arc<dyn hypervisor::Device>) -> Result<Vec<u32>> {
let mut state = Vec::new();
for dreg in VGIC_DIST_REGS {
let mut base = dreg.base + REG_SIZE as u32 * dreg.bpi as u32;
let end = compute_reg_len(gic, dreg, base)?;
let end = compute_reg_len(gic, &dreg, base)?;
while base < end {
state.push(dist_attr_get(gic, base)?);
let val: u32 = 0;
dist_attr_access(gic, base, &val, false)?;
state.push(val);
base += REG_SIZE as u32;
}
}

View File

@@ -0,0 +1,275 @@
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
pub mod kvm {
use crate::aarch64::gic::dist_regs::{get_dist_regs, read_ctlr, set_dist_regs, write_ctlr};
use crate::aarch64::gic::icc_regs::{get_icc_regs, set_icc_regs};
use crate::aarch64::gic::kvm::{save_pending_tables, KvmGicDevice};
use crate::aarch64::gic::redist_regs::{get_redist_regs, set_redist_regs};
use crate::aarch64::gic::GicDevice;
use crate::layout;
use anyhow::anyhow;
use hypervisor::kvm::kvm_bindings;
use std::any::Any;
use std::convert::TryInto;
use std::sync::Arc;
use std::{boxed::Box, result};
use vm_migration::{
Migratable, MigratableError, Pausable, Snapshot, SnapshotDataSection, Snapshottable,
Transportable,
};
/// Errors thrown while saving/restoring the GICv3.
#[derive(Debug)]
pub enum Error {
/// Error in saving RDIST pending tables into guest RAM.
SavePendingTables(crate::aarch64::gic::Error),
/// Error in saving GIC distributor registers.
SaveDistributorRegisters(crate::aarch64::gic::Error),
/// Error in restoring GIC distributor registers.
RestoreDistributorRegisters(crate::aarch64::gic::Error),
/// Error in saving GIC distributor control registers.
SaveDistributorCtrlRegisters(crate::aarch64::gic::Error),
/// Error in restoring GIC distributor control registers.
RestoreDistributorCtrlRegisters(crate::aarch64::gic::Error),
/// Error in saving GIC redistributor registers.
SaveRedistributorRegisters(crate::aarch64::gic::Error),
/// Error in restoring GIC redistributor registers.
RestoreRedistributorRegisters(crate::aarch64::gic::Error),
/// Error in saving GIC CPU interface registers.
SaveIccRegisters(crate::aarch64::gic::Error),
/// Error in restoring GIC CPU interface registers.
RestoreIccRegisters(crate::aarch64::gic::Error),
}
type Result<T> = result::Result<T, Error>;
pub struct KvmGicV3 {
/// The hypervisor agnostic device
device: Arc<dyn hypervisor::Device>,
/// Vector holding values of GICR_TYPER for each vCPU
gicr_typers: Vec<u64>,
/// GIC device properties, to be used for setting up the fdt entry
properties: [u64; 4],
/// Number of CPUs handled by the device
vcpu_count: u64,
}
#[derive(Serialize, Deserialize)]
pub struct Gicv3State {
dist: Vec<u32>,
rdist: Vec<u32>,
icc: Vec<u32>,
// special register that enables interrupts and affinity routing
gicd_ctlr: u32,
}
impl KvmGicV3 {
// Unfortunately bindgen omits defines that are based on other defines.
// See arch/arm64/include/uapi/asm/kvm.h file from the linux kernel.
pub const SZ_64K: u64 = 0x0001_0000;
const KVM_VGIC_V3_DIST_SIZE: u64 = KvmGicV3::SZ_64K;
const KVM_VGIC_V3_REDIST_SIZE: u64 = (2 * KvmGicV3::SZ_64K);
// Device trees specific constants
pub const ARCH_GIC_V3_MAINT_IRQ: u32 = 9;
/// Get the address of the GIC distributor.
pub fn get_dist_addr() -> u64 {
layout::MAPPED_IO_START - KvmGicV3::KVM_VGIC_V3_DIST_SIZE
}
/// Get the size of the GIC distributor.
pub fn get_dist_size() -> u64 {
KvmGicV3::KVM_VGIC_V3_DIST_SIZE
}
/// Get the address of the GIC redistributors.
pub fn get_redists_addr(vcpu_count: u64) -> u64 {
KvmGicV3::get_dist_addr() - KvmGicV3::get_redists_size(vcpu_count)
}
/// Get the size of the GIC redistributors.
pub fn get_redists_size(vcpu_count: u64) -> u64 {
vcpu_count * KvmGicV3::KVM_VGIC_V3_REDIST_SIZE
}
/// Save the state of GIC.
fn state(&self, gicr_typers: &[u64]) -> Result<Gicv3State> {
// Flush redistributors pending tables to guest RAM.
save_pending_tables(&self.device()).map_err(Error::SavePendingTables)?;
let gicd_ctlr =
read_ctlr(&self.device()).map_err(Error::SaveDistributorCtrlRegisters)?;
let dist_state =
get_dist_regs(&self.device()).map_err(Error::SaveDistributorRegisters)?;
let rdist_state = get_redist_regs(&self.device(), &gicr_typers)
.map_err(Error::SaveRedistributorRegisters)?;
let icc_state =
get_icc_regs(&self.device(), &gicr_typers).map_err(Error::SaveIccRegisters)?;
Ok(Gicv3State {
dist: dist_state,
rdist: rdist_state,
icc: icc_state,
gicd_ctlr,
})
}
/// Restore the state of GIC.
fn set_state(&mut self, gicr_typers: &[u64], state: &Gicv3State) -> Result<()> {
write_ctlr(&self.device(), state.gicd_ctlr)
.map_err(Error::RestoreDistributorCtrlRegisters)?;
set_dist_regs(&self.device(), &state.dist)
.map_err(Error::RestoreDistributorRegisters)?;
set_redist_regs(&self.device(), gicr_typers, &state.rdist)
.map_err(Error::RestoreRedistributorRegisters)?;
set_icc_regs(&self.device(), &gicr_typers, &state.icc)
.map_err(Error::RestoreIccRegisters)?;
Ok(())
}
}
impl GicDevice for KvmGicV3 {
fn device(&self) -> &Arc<dyn hypervisor::Device> {
&self.device
}
fn fdt_compatibility(&self) -> &str {
"arm,gic-v3"
}
fn fdt_maint_irq(&self) -> u32 {
KvmGicV3::ARCH_GIC_V3_MAINT_IRQ
}
fn device_properties(&self) -> &[u64] {
&self.properties
}
fn vcpu_count(&self) -> u64 {
self.vcpu_count
}
fn set_gicr_typers(&mut self, gicr_typers: Vec<u64>) {
self.gicr_typers = gicr_typers;
}
fn as_any_concrete_mut(&mut self) -> &mut dyn Any {
self
}
}
impl KvmGicDevice for KvmGicV3 {
fn version() -> u32 {
kvm_bindings::kvm_device_type_KVM_DEV_TYPE_ARM_VGIC_V3
}
fn create_device(
device: Arc<dyn hypervisor::Device>,
vcpu_count: u64,
) -> Box<dyn GicDevice> {
Box::new(KvmGicV3 {
device,
gicr_typers: vec![0; vcpu_count.try_into().unwrap()],
properties: [
KvmGicV3::get_dist_addr(),
KvmGicV3::get_dist_size(),
KvmGicV3::get_redists_addr(vcpu_count),
KvmGicV3::get_redists_size(vcpu_count),
],
vcpu_count,
})
}
fn init_device_attributes(
_vm: &Arc<dyn hypervisor::Vm>,
gic_device: &dyn GicDevice,
) -> crate::aarch64::gic::Result<()> {
/* Setting up the distributor attribute.
We are placing the GIC below 1GB so we need to substract the size of the distributor.
*/
Self::set_device_attribute(
gic_device.device(),
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ADDR,
u64::from(kvm_bindings::KVM_VGIC_V3_ADDR_TYPE_DIST),
&KvmGicV3::get_dist_addr() as *const u64 as u64,
0,
)?;
/* Setting up the redistributors' attribute.
We are calculating here the start of the redistributors address. We have one per CPU.
*/
Self::set_device_attribute(
gic_device.device(),
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ADDR,
u64::from(kvm_bindings::KVM_VGIC_V3_ADDR_TYPE_REDIST),
&KvmGicV3::get_redists_addr(gic_device.vcpu_count()) as *const u64 as u64,
0,
)?;
Ok(())
}
}
pub const GIC_V3_SNAPSHOT_ID: &str = "gic-v3";
impl Snapshottable for KvmGicV3 {
fn id(&self) -> String {
GIC_V3_SNAPSHOT_ID.to_string()
}
fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
let gicr_typers = self.gicr_typers.clone();
let snapshot = serde_json::to_vec(&self.state(&gicr_typers).unwrap())
.map_err(|e| MigratableError::Snapshot(e.into()))?;
let mut gic_v3_snapshot = Snapshot::new(self.id().as_str());
gic_v3_snapshot.add_data_section(SnapshotDataSection {
id: format!("{}-section", self.id()),
snapshot,
});
Ok(gic_v3_snapshot)
}
fn restore(&mut self, snapshot: Snapshot) -> std::result::Result<(), MigratableError> {
if let Some(gic_v3_section) = snapshot
.snapshot_data
.get(&format!("{}-section", self.id()))
{
let gic_v3_state = match serde_json::from_slice(&gic_v3_section.snapshot) {
Ok(state) => state,
Err(error) => {
return Err(MigratableError::Restore(anyhow!(
"Could not deserialize GICv3 {}",
error
)))
}
};
let gicr_typers = self.gicr_typers.clone();
return self.set_state(&gicr_typers, &gic_v3_state).map_err(|e| {
MigratableError::Restore(anyhow!("Could not restore GICv3 state {:?}", e))
});
}
Err(MigratableError::Restore(anyhow!(
"Could not find GICv3 snapshot section"
)))
}
}
impl Pausable for KvmGicV3 {}
impl Transportable for KvmGicV3 {}
impl Migratable for KvmGicV3 {}
}

View File

@@ -0,0 +1,147 @@
// Copyright 2020 ARM Limited
// SPDX-License-Identifier: Apache-2.0
pub mod kvm {
use std::any::Any;
use std::convert::TryInto;
use std::sync::Arc;
use std::{boxed::Box, result};
type Result<T> = result::Result<T, Error>;
use crate::aarch64::gic::gicv3::kvm::KvmGicV3;
use crate::aarch64::gic::kvm::KvmGicDevice;
use crate::aarch64::gic::{Error, GicDevice};
use hypervisor::kvm::kvm_bindings;
pub struct KvmGicV3Its {
/// The hypervisor agnostic device
device: Arc<dyn hypervisor::Device>,
/// Vector holding values of GICR_TYPER for each vCPU
gicr_typers: Vec<u64>,
/// GIC device properties, to be used for setting up the fdt entry
gic_properties: [u64; 4],
/// MSI device properties, to be used for setting up the fdt entry
msi_properties: [u64; 2],
/// Number of CPUs handled by the device
vcpu_count: u64,
}
impl KvmGicV3Its {
const KVM_VGIC_V3_ITS_SIZE: u64 = (2 * KvmGicV3::SZ_64K);
fn get_msi_size() -> u64 {
KvmGicV3Its::KVM_VGIC_V3_ITS_SIZE
}
fn get_msi_addr(vcpu_count: u64) -> u64 {
KvmGicV3::get_redists_addr(vcpu_count) - KvmGicV3Its::get_msi_size()
}
}
impl GicDevice for KvmGicV3Its {
fn device(&self) -> &Arc<dyn hypervisor::Device> {
&self.device
}
fn fdt_compatibility(&self) -> &str {
"arm,gic-v3"
}
fn msi_compatible(&self) -> bool {
true
}
fn msi_compatibility(&self) -> &str {
"arm,gic-v3-its"
}
fn fdt_maint_irq(&self) -> u32 {
KvmGicV3::ARCH_GIC_V3_MAINT_IRQ
}
fn msi_properties(&self) -> &[u64] {
&self.msi_properties
}
fn device_properties(&self) -> &[u64] {
&self.gic_properties
}
fn vcpu_count(&self) -> u64 {
self.vcpu_count
}
fn set_gicr_typers(&mut self, gicr_typers: Vec<u64>) {
self.gicr_typers = gicr_typers;
}
fn as_any_concrete_mut(&mut self) -> &mut dyn Any {
self
}
}
impl KvmGicDevice for KvmGicV3Its {
fn version() -> u32 {
KvmGicV3::version()
}
fn create_device(
device: Arc<dyn hypervisor::Device>,
vcpu_count: u64,
) -> Box<dyn GicDevice> {
Box::new(KvmGicV3Its {
device,
gicr_typers: vec![0; vcpu_count.try_into().unwrap()],
gic_properties: [
KvmGicV3::get_dist_addr(),
KvmGicV3::get_dist_size(),
KvmGicV3::get_redists_addr(vcpu_count),
KvmGicV3::get_redists_size(vcpu_count),
],
msi_properties: [
KvmGicV3Its::get_msi_addr(vcpu_count),
KvmGicV3Its::get_msi_size(),
],
vcpu_count,
})
}
fn init_device_attributes(
vm: &Arc<dyn hypervisor::Vm>,
gic_device: &dyn GicDevice,
) -> Result<()> {
KvmGicV3::init_device_attributes(vm, gic_device)?;
let mut its_device = kvm_bindings::kvm_create_device {
type_: kvm_bindings::kvm_device_type_KVM_DEV_TYPE_ARM_VGIC_ITS,
fd: 0,
flags: 0,
};
let its_fd = vm
.create_device(&mut its_device)
.map_err(Error::CreateGic)?;
Self::set_device_attribute(
&its_fd,
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ADDR,
u64::from(kvm_bindings::KVM_VGIC_ITS_ADDR_TYPE),
&KvmGicV3Its::get_msi_addr(gic_device.vcpu_count()) as *const u64 as u64,
0,
)?;
Self::set_device_attribute(
&its_fd,
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_CTRL,
u64::from(kvm_bindings::KVM_DEV_ARM_VGIC_CTRL_INIT),
0,
0,
)?;
Ok(())
}
}
}

View File

@@ -1,18 +1,14 @@
// Copyright 2022 Arm Limited (or its affiliates). All rights reserved.
// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
use kvm_ioctls::DeviceFd;
use crate::arch::aarch64::gic::{Error, Result};
use crate::device::HypervisorDeviceError;
use crate::kvm::kvm_bindings::{
KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS, KVM_REG_ARM64_SYSREG_CRM_MASK,
use super::{Error, Result};
use hypervisor::kvm::kvm_bindings::{
kvm_device_attr, KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS, KVM_REG_ARM64_SYSREG_CRM_MASK,
KVM_REG_ARM64_SYSREG_CRM_SHIFT, KVM_REG_ARM64_SYSREG_CRN_MASK, KVM_REG_ARM64_SYSREG_CRN_SHIFT,
KVM_REG_ARM64_SYSREG_OP0_MASK, KVM_REG_ARM64_SYSREG_OP0_SHIFT, KVM_REG_ARM64_SYSREG_OP1_MASK,
KVM_REG_ARM64_SYSREG_OP1_SHIFT, KVM_REG_ARM64_SYSREG_OP2_MASK, KVM_REG_ARM64_SYSREG_OP2_SHIFT,
kvm_device_attr,
};
use std::sync::Arc;
const KVM_DEV_ARM_VGIC_V3_MPIDR_SHIFT: u32 = 32;
const KVM_DEV_ARM_VGIC_V3_MPIDR_MASK: u64 = 0xffffffff << KVM_DEV_ARM_VGIC_V3_MPIDR_SHIFT as u64;
@@ -63,7 +59,7 @@ SYS_ICC_AP1Rn_EL1!(SYS_ICC_AP1R1_EL1, 1);
SYS_ICC_AP1Rn_EL1!(SYS_ICC_AP1R2_EL1, 2);
SYS_ICC_AP1Rn_EL1!(SYS_ICC_AP1R3_EL1, 3);
static VGIC_ICC_REGS: &[u64] = &[
static VGIC_ICC_REGS: &'static [u64] = &[
SYS_ICC_SRE_EL1,
SYS_ICC_CTLR_EL1,
SYS_ICC_IGRPEN0_EL1,
@@ -81,41 +77,32 @@ static VGIC_ICC_REGS: &[u64] = &[
SYS_ICC_AP1R3_EL1,
];
fn icc_attr_set(gic: &DeviceFd, offset: u64, typer: u64, val: u32) -> Result<()> {
let gic_icc_attr = kvm_device_attr {
group: KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS,
attr: ((typer & KVM_DEV_ARM_VGIC_V3_MPIDR_MASK) | offset), // this needs the mpidr
addr: &val as *const u32 as u64,
flags: 0,
};
gic.set_device_attr(&gic_icc_attr).map_err(|e| {
Error::SetDeviceAttribute(HypervisorDeviceError::SetDeviceAttribute(e.into()))
})?;
Ok(())
}
fn icc_attr_get(gic: &DeviceFd, offset: u64, typer: u64) -> Result<u32> {
let mut val = 0;
fn icc_attr_access(
gic: &Arc<dyn hypervisor::Device>,
offset: u64,
typer: u64,
val: &u32,
set: bool,
) -> Result<()> {
let mut gic_icc_attr = kvm_device_attr {
group: KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS,
attr: ((typer & KVM_DEV_ARM_VGIC_V3_MPIDR_MASK) | offset), // this needs the mpidr
addr: &mut val as *mut u32 as u64,
addr: val as *const u32 as u64,
flags: 0,
};
// SAFETY: gic_icc_attr.addr is safe to write to.
unsafe { gic.get_device_attr(&mut gic_icc_attr) }.map_err(|e| {
Error::GetDeviceAttribute(HypervisorDeviceError::GetDeviceAttribute(e.into()))
})?;
Ok(val)
if set {
#[allow(clippy::unnecessary_mut_passed)]
gic.set_device_attr(&mut gic_icc_attr)
.map_err(Error::SetDeviceAttribute)?;
} else {
gic.get_device_attr(&mut gic_icc_attr)
.map_err(Error::GetDeviceAttribute)?;
}
Ok(())
}
/// Get ICC registers.
pub fn get_icc_regs(gic: &DeviceFd, gicr_typer: &[u64]) -> Result<Vec<u32>> {
pub fn get_icc_regs(gic: &Arc<dyn hypervisor::Device>, gicr_typer: &[u64]) -> Result<Vec<u32>> {
let mut state: Vec<u32> = Vec::new();
// We need this for the ICC_AP<m>R<n>_EL1 registers.
let mut num_priority_bits = 0;
@@ -123,9 +110,10 @@ pub fn get_icc_regs(gic: &DeviceFd, gicr_typer: &[u64]) -> Result<Vec<u32>> {
for ix in gicr_typer {
let i = *ix;
for icc_offset in VGIC_ICC_REGS {
let val = 0;
if *icc_offset == SYS_ICC_CTLR_EL1 {
// calculate priority bits by reading the ctrl_el1 register.
let val = icc_attr_get(gic, *icc_offset, i)?;
icc_attr_access(gic, *icc_offset, i, &val, false)?;
// The priority bits are found in the ICC_CTLR_EL1 register (bits from 10:8).
// See page 194 from https://static.docs.arm.com/ihi0069/c/IHI0069C_gic_
// architecture_specification.pdf.
@@ -145,7 +133,8 @@ pub fn get_icc_regs(gic: &DeviceFd, gicr_typer: &[u64]) -> Result<Vec<u32>> {
// 7 bits of priority.
else if *icc_offset == SYS_ICC_AP0R1_EL1 || *icc_offset == SYS_ICC_AP1R1_EL1 {
if num_priority_bits >= 6 {
state.push(icc_attr_get(gic, *icc_offset, i)?);
icc_attr_access(gic, *icc_offset, i, &val, false)?;
state.push(val);
}
} else if *icc_offset == SYS_ICC_AP0R2_EL1
|| *icc_offset == SYS_ICC_AP0R3_EL1
@@ -153,10 +142,12 @@ pub fn get_icc_regs(gic: &DeviceFd, gicr_typer: &[u64]) -> Result<Vec<u32>> {
|| *icc_offset == SYS_ICC_AP1R3_EL1
{
if num_priority_bits == 7 {
state.push(icc_attr_get(gic, *icc_offset, i)?);
icc_attr_access(gic, *icc_offset, i, &val, false)?;
state.push(val);
}
} else {
state.push(icc_attr_get(gic, *icc_offset, i)?);
icc_attr_access(gic, *icc_offset, i, &val, false)?;
state.push(val);
}
}
}
@@ -164,7 +155,11 @@ pub fn get_icc_regs(gic: &DeviceFd, gicr_typer: &[u64]) -> Result<Vec<u32>> {
}
/// Set ICC registers.
pub fn set_icc_regs(gic: &DeviceFd, gicr_typer: &[u64], state: &[u32]) -> Result<()> {
pub fn set_icc_regs(
gic: &Arc<dyn hypervisor::Device>,
gicr_typer: &[u64],
state: &[u32],
) -> Result<()> {
let mut num_priority_bits = 0;
let mut idx = 0;
for ix in gicr_typer {
@@ -177,7 +172,7 @@ pub fn set_icc_regs(gic: &DeviceFd, gicr_typer: &[u64], state: &[u32]) -> Result
}
if *icc_offset == SYS_ICC_AP0R1_EL1 || *icc_offset == SYS_ICC_AP1R1_EL1 {
if num_priority_bits >= 6 {
icc_attr_set(gic, *icc_offset, i, state[idx])?;
icc_attr_access(gic, *icc_offset, i, &state[idx], true)?;
idx += 1;
}
continue;
@@ -188,12 +183,12 @@ pub fn set_icc_regs(gic: &DeviceFd, gicr_typer: &[u64], state: &[u32]) -> Result
|| *icc_offset == SYS_ICC_AP1R3_EL1
{
if num_priority_bits == 7 {
icc_attr_set(gic, *icc_offset, i, state[idx])?;
icc_attr_access(gic, *icc_offset, i, &state[idx], true)?;
idx += 1;
}
continue;
}
icc_attr_set(gic, *icc_offset, i, state[idx])?;
icc_attr_access(gic, *icc_offset, i, &state[idx], true)?;
idx += 1;
}
}

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

@@ -0,0 +1,211 @@
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
pub mod dist_regs;
pub mod gicv3;
pub mod gicv3_its;
pub mod icc_regs;
pub mod redist_regs;
pub use self::dist_regs::{get_dist_regs, read_ctlr, set_dist_regs, write_ctlr};
pub use self::icc_regs::{get_icc_regs, set_icc_regs};
pub use self::redist_regs::{get_redist_regs, set_redist_regs};
use std::any::Any;
use std::result;
use std::sync::Arc;
/// Errors thrown while setting up the GIC.
#[derive(Debug)]
pub enum Error {
/// Error while calling KVM ioctl for setting up the global interrupt controller.
CreateGic(hypervisor::HypervisorVmError),
/// Error while setting device attributes for the GIC.
SetDeviceAttribute(hypervisor::HypervisorDeviceError),
/// Error while getting device attributes for the GIC.
GetDeviceAttribute(hypervisor::HypervisorDeviceError),
}
type Result<T> = result::Result<T, Error>;
pub trait GicDevice: Send {
/// Returns the hypervisor agnostic Device of the GIC device
fn device(&self) -> &Arc<dyn hypervisor::Device>;
/// Returns the fdt compatibility property of the device
fn fdt_compatibility(&self) -> &str;
/// Returns the maint_irq fdt property of the device
fn fdt_maint_irq(&self) -> u32;
/// Returns an array with GIC device properties
fn device_properties(&self) -> &[u64];
/// Returns the number of vCPUs this GIC handles
fn vcpu_count(&self) -> u64;
/// Returns whether the GIC device is MSI compatible or not
fn msi_compatible(&self) -> bool {
false
}
/// Returns the MSI compatibility property of the device
fn msi_compatibility(&self) -> &str {
""
}
/// Returns the MSI reg property of the device
fn msi_properties(&self) -> &[u64] {
&[]
}
/// Get the values of GICR_TYPER for each vCPU.
fn set_gicr_typers(&mut self, gicr_typers: Vec<u64>);
/// Downcast the trait object to its concrete type.
fn as_any_concrete_mut(&mut self) -> &mut dyn Any;
}
pub mod kvm {
use super::GicDevice;
use super::Result;
use crate::aarch64::gic::gicv3_its::kvm::KvmGicV3Its;
use crate::layout;
use hypervisor::kvm::kvm_bindings;
use std::boxed::Box;
use std::sync::Arc;
/// Trait for GIC devices.
pub trait KvmGicDevice: Send + Sync + GicDevice {
/// Returns the GIC version of the device
fn version() -> u32;
/// Create the GIC device object
fn create_device(
device: Arc<dyn hypervisor::Device>,
vcpu_count: u64,
) -> Box<dyn GicDevice>;
/// Setup the device-specific attributes
fn init_device_attributes(
vm: &Arc<dyn hypervisor::Vm>,
gic_device: &dyn GicDevice,
) -> Result<()>;
/// Initialize a GIC device
fn init_device(vm: &Arc<dyn hypervisor::Vm>) -> Result<Arc<dyn hypervisor::Device>> {
let mut gic_device = kvm_bindings::kvm_create_device {
type_: Self::version(),
fd: 0,
flags: 0,
};
vm.create_device(&mut gic_device)
.map_err(super::Error::CreateGic)
}
/// Set a GIC device attribute
fn set_device_attribute(
device: &Arc<dyn hypervisor::Device>,
group: u32,
attr: u64,
addr: u64,
flags: u32,
) -> Result<()> {
let attr = kvm_bindings::kvm_device_attr {
group,
attr,
addr,
flags,
};
device
.set_device_attr(&attr)
.map_err(super::Error::SetDeviceAttribute)?;
Ok(())
}
/// Get a GIC device attribute
fn get_device_attribute(
device: &Arc<dyn hypervisor::Device>,
group: u32,
attr: u64,
addr: u64,
flags: u32,
) -> Result<()> {
let mut attr = kvm_bindings::kvm_device_attr {
group,
attr,
addr,
flags,
};
device
.get_device_attr(&mut attr)
.map_err(super::Error::GetDeviceAttribute)?;
Ok(())
}
/// Finalize the setup of a GIC device
fn finalize_device(gic_device: &dyn GicDevice) -> Result<()> {
/* We need to tell the kernel how many irqs to support with this vgic.
* See the `layout` module for details.
*/
let nr_irqs: u32 = layout::IRQ_MAX - layout::IRQ_BASE + 1;
let nr_irqs_ptr = &nr_irqs as *const u32;
Self::set_device_attribute(
gic_device.device(),
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_NR_IRQS,
0,
nr_irqs_ptr as u64,
0,
)?;
/* Finalize the GIC.
* See https://code.woboq.org/linux/linux/virt/kvm/arm/vgic/vgic-kvm-device.c.html#211.
*/
Self::set_device_attribute(
gic_device.device(),
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_CTRL,
u64::from(kvm_bindings::KVM_DEV_ARM_VGIC_CTRL_INIT),
0,
0,
)?;
Ok(())
}
/// Method to initialize the GIC device
#[allow(clippy::new_ret_no_self)]
fn new(vm: &Arc<dyn hypervisor::Vm>, vcpu_count: u64) -> Result<Box<dyn GicDevice>> {
let vgic_fd = Self::init_device(vm)?;
let device = Self::create_device(vgic_fd, vcpu_count);
Self::init_device_attributes(vm, &*device)?;
Self::finalize_device(&*device)?;
Ok(device)
}
}
/// Create a GICv3-ITS device.
///
pub fn create_gic(vm: &Arc<dyn hypervisor::Vm>, vcpu_count: u64) -> Result<Box<dyn GicDevice>> {
debug!("creating a GICv3-ITS");
KvmGicV3Its::new(vm, vcpu_count)
}
/// Function that saves RDIST pending tables into guest RAM.
///
/// The tables get flushed to guest RAM whenever the VM gets stopped.
pub fn save_pending_tables(gic: &Arc<dyn hypervisor::Device>) -> Result<()> {
let init_gic_attr = kvm_bindings::kvm_device_attr {
group: kvm_bindings::KVM_DEV_ARM_VGIC_GRP_CTRL,
attr: u64::from(kvm_bindings::KVM_DEV_ARM_VGIC_SAVE_PENDING_TABLES),
addr: 0,
flags: 0,
};
gic.set_device_attr(&init_gic_attr)
.map_err(super::Error::SetDeviceAttribute)
}
}

View File

@@ -1,18 +1,9 @@
// Copyright 2022 Arm Limited (or its affiliates). All rights reserved.
// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
use kvm_ioctls::DeviceFd;
use crate::CpuState;
use crate::arch::aarch64::gic::{Error, Result};
use crate::device::HypervisorDeviceError;
use crate::kvm::VcpuKvmState;
use crate::kvm::kvm_bindings::{
KVM_DEV_ARM_VGIC_GRP_REDIST_REGS, KVM_REG_ARM64, KVM_REG_ARM64_SYSREG,
KVM_REG_ARM64_SYSREG_OP0_MASK, KVM_REG_ARM64_SYSREG_OP0_SHIFT, KVM_REG_ARM64_SYSREG_OP2_MASK,
KVM_REG_ARM64_SYSREG_OP2_SHIFT, KVM_REG_SIZE_U64, kvm_device_attr, kvm_one_reg,
};
use super::{Error, Result};
use hypervisor::kvm::kvm_bindings::{kvm_device_attr, KVM_DEV_ARM_VGIC_GRP_REDIST_REGS};
use std::sync::Arc;
// Relevant redistributor registers that we want to save/restore.
const GICR_CTLR: u32 = 0x0000;
@@ -39,14 +30,8 @@ const GICR_ICFGR0: u32 = GICR_SGI_OFFSET + 0x0C00;
const KVM_DEV_ARM_VGIC_V3_MPIDR_SHIFT: u32 = 32;
const KVM_DEV_ARM_VGIC_V3_MPIDR_MASK: u64 = 0xffffffff << KVM_DEV_ARM_VGIC_V3_MPIDR_SHIFT as u64;
const KVM_ARM64_SYSREG_MPIDR_EL1: u64 = KVM_REG_ARM64
| KVM_REG_SIZE_U64
| KVM_REG_ARM64_SYSREG as u64
| (((3_u64) << KVM_REG_ARM64_SYSREG_OP0_SHIFT) & KVM_REG_ARM64_SYSREG_OP0_MASK as u64)
| (((5_u64) << KVM_REG_ARM64_SYSREG_OP2_SHIFT) & KVM_REG_ARM64_SYSREG_OP2_MASK as u64);
/// This is how we represent the registers of a distributor.
/// It is relevant their offset from the base address of the
/// It is relrvant their offset from the base address of the
/// distributor.
/// Each register has a different number
/// of bits_per_irq and is therefore variable length.
@@ -75,16 +60,16 @@ macro_rules! VGIC_RDIST_REG {
}
// List with relevant distributor registers that we will be restoring.
static VGIC_RDIST_REGS: &[RdistReg] = &[
static VGIC_RDIST_REGS: &'static [RdistReg] = &[
VGIC_RDIST_REG!(GICR_CTLR, 4),
VGIC_RDIST_REG!(GICR_STATUSR, 4),
VGIC_RDIST_REG!(GICR_WAKER, 4),
VGIC_RDIST_REG!(GICR_PROPBASER, 8),
VGIC_RDIST_REG!(GICR_PENDBASER, 8),
VGIC_RDIST_REG!(GICR_CTLR, 4),
];
// List with relevant distributor registers that we will be restoring.
static VGIC_SGI_REGS: &[RdistReg] = &[
static VGIC_SGI_REGS: &'static [RdistReg] = &[
VGIC_RDIST_REG!(GICR_IGROUPR0, 4),
VGIC_RDIST_REG!(GICR_ICENABLER0, 4),
VGIC_RDIST_REG!(GICR_ISENABLER0, 4),
@@ -96,44 +81,35 @@ static VGIC_SGI_REGS: &[RdistReg] = &[
VGIC_RDIST_REG!(GICR_IPRIORITYR0, 32),
];
fn redist_attr_set(gic: &DeviceFd, offset: u32, typer: u64, val: u32) -> Result<()> {
let gic_redist_attr = kvm_device_attr {
fn redist_attr_access(
gic: &Arc<dyn hypervisor::Device>,
offset: u32,
typer: u64,
val: &u32,
set: bool,
) -> Result<()> {
let mut gic_dist_attr = kvm_device_attr {
group: KVM_DEV_ARM_VGIC_GRP_REDIST_REGS,
attr: (typer & KVM_DEV_ARM_VGIC_V3_MPIDR_MASK) | (offset as u64), // this needs the mpidr
addr: &val as *const u32 as u64,
addr: val as *const u32 as u64,
flags: 0,
};
gic.set_device_attr(&gic_redist_attr).map_err(|e| {
Error::SetDeviceAttribute(HypervisorDeviceError::SetDeviceAttribute(e.into()))
})?;
if set {
#[allow(clippy::unnecessary_mut_passed)]
gic.set_device_attr(&mut gic_dist_attr)
.map_err(Error::SetDeviceAttribute)?;
} else {
gic.get_device_attr(&mut gic_dist_attr)
.map_err(Error::GetDeviceAttribute)?;
}
Ok(())
}
fn redist_attr_get(gic: &DeviceFd, offset: u32, typer: u64) -> Result<u32> {
let mut val = 0;
let mut gic_redist_attr = kvm_device_attr {
group: KVM_DEV_ARM_VGIC_GRP_REDIST_REGS,
attr: (typer & KVM_DEV_ARM_VGIC_V3_MPIDR_MASK) | (offset as u64), // this needs the mpidr
addr: &mut val as *mut u32 as u64,
flags: 0,
};
// SAFETY: gic_redist_attr.addr is safe to write to.
unsafe { gic.get_device_attr(&mut gic_redist_attr) }.map_err(|e| {
Error::GetDeviceAttribute(HypervisorDeviceError::GetDeviceAttribute(e.into()))
})?;
Ok(val)
}
fn access_redists_aux(
gic: &DeviceFd,
gic: &Arc<dyn hypervisor::Device>,
gicr_typer: &[u64],
state: &mut Vec<u32>,
reg_list: &[RdistReg],
reg_list: &'static [RdistReg],
idx: &mut usize,
set: bool,
) -> Result<()> {
@@ -143,11 +119,14 @@ fn access_redists_aux(
let end = base + rdreg.length as u32;
while base < end {
let mut val = 0;
if set {
redist_attr_set(gic, base, *i, state[*idx])?;
val = state[*idx];
redist_attr_access(gic, base, *i, &val, true)?;
*idx += 1;
} else {
state.push(redist_attr_get(gic, base, *i)?);
redist_attr_access(gic, base, *i, &val, false)?;
state.push(val);
}
base += REG_SIZE as u32;
}
@@ -157,24 +136,28 @@ fn access_redists_aux(
}
/// Get redistributor registers.
pub fn get_redist_regs(gic: &DeviceFd, gicr_typer: &[u64]) -> Result<Vec<u32>> {
pub fn get_redist_regs(gic: &Arc<dyn hypervisor::Device>, gicr_typer: &[u64]) -> Result<Vec<u32>> {
let mut state = Vec::new();
let mut idx: usize = 0;
access_redists_aux(
gic,
gicr_typer,
&gicr_typer,
&mut state,
VGIC_RDIST_REGS,
&mut idx,
false,
)?;
access_redists_aux(gic, gicr_typer, &mut state, VGIC_SGI_REGS, &mut idx, false)?;
access_redists_aux(gic, &gicr_typer, &mut state, VGIC_SGI_REGS, &mut idx, false)?;
Ok(state)
}
/// Set redistributor registers.
pub fn set_redist_regs(gic: &DeviceFd, gicr_typer: &[u64], state: &[u32]) -> Result<()> {
pub fn set_redist_regs(
gic: &Arc<dyn hypervisor::Device>,
gicr_typer: &[u64],
state: &[u32],
) -> Result<()> {
let mut idx: usize = 0;
let mut mut_state = state.to_owned();
access_redists_aux(
@@ -194,35 +177,3 @@ pub fn set_redist_regs(gic: &DeviceFd, gicr_typer: &[u64], state: &[u32]) -> Res
true,
)
}
pub fn construct_gicr_typers(vcpu_states: &[CpuState]) -> Vec<u64> {
/* Pre-construct the GICR_TYPER:
* For our implementation:
* Top 32 bits are the affinity value of the associated CPU
* CommonLPIAff == 01 (redistributors with same Aff3 share LPI table)
* Processor_Number == CPU index starting from 0
* DPGS == 0 (GICR_CTLR.DPG* not supported)
* Last == 1 if this is the last redistributor in a series of
* contiguous redistributor pages
* DirectLPI == 0 (direct injection of LPIs not supported)
* VLPIS == 0 (virtual LPIs not supported)
* PLPIS == 0 (physical LPIs not supported)
*/
let mut gicr_typers: Vec<u64> = Vec::new();
for (index, state) in vcpu_states.iter().enumerate() {
let state: VcpuKvmState = state.clone().into();
let last = (index == vcpu_states.len() - 1) as u64;
// state.sys_regs is a big collection of system registers, including MIPDR_EL1
let mpidr: Vec<kvm_one_reg> = state
.sys_regs
.into_iter()
.filter(|reg| reg.id == KVM_ARM64_SYSREG_MPIDR_EL1)
.collect();
//calculate affinity
let mut cpu_affid = mpidr[0].addr & 1095233437695;
cpu_affid = ((cpu_affid & 0xFF00000000) >> 8) | (cpu_affid & 0xFFFFFF);
gicr_typers.push((cpu_affid << 32) | (1 << 24) | ((index as u64) << 8) | (last << 4));
}
gicr_typers
}

View File

@@ -3,7 +3,7 @@
// SPDX-License-Identifier: Apache-2.0
//
// Memory layout of AArch64 guest:
// Memory layout of Aarch64 guest:
//
// Physical +---------------------------------------------------------------+
// address | |
@@ -19,21 +19,15 @@
// memory) | |
// | DRAM |
// | |
// 2GB +---------------------------------------------------------------+
// | |
// 4GB +---------------------------------------------------------------+
// | 32-bit devices hole |
// 4GB-64M +---------------------------------------------------------------+
// | Reserved |
// | |
// | |
// | DRAM |
// | |
// | |
// 1GB +---------------------------------------------------------------+
// 1G+256M +---------------------------------------------------------------+
// | |
// | PCI MMCONFIG space |
// | |
// 768 M +---------------------------------------------------------------+
// | |
// 1GB +---------------------------------------------------------------+
// | |
// | PCI MMIO space |
// | |
@@ -45,87 +39,40 @@
// | |
// | Reserved (now GIC is here) |
// | |
// 4 M +---------------------------------------------------------------+
// | UEFI flash |
// 0GB +---------------------------------------------------------------+
//
//
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;
/// Below this address will reside the GIC, above this address will reside the MMIO devices.
const MAPPED_IO_START: GuestAddress = GuestAddress(0x0900_0000);
pub const MAPPED_IO_START: u64 = 0x0900_0000;
/// See kernel file arch/arm64/include/uapi/asm/kvm.h for the GIC related definitions.
/// 0x08ff_0000 ~ 0x0900_0000 is reserved for GICv3 Distributor
pub const GIC_V3_DIST_SIZE: u64 = 0x01_0000;
pub const GIC_V3_DIST_START: GuestAddress = GuestAddress(MAPPED_IO_START.0 - GIC_V3_DIST_SIZE);
/// Below 0x08ff_0000 is reserved for GICv3 Redistributor.
/// The size defined here is for each vcpu.
/// The total size is 'number_of_vcpu * GIC_V3_REDIST_SIZE'
pub const GIC_V3_REDIST_SIZE: u64 = 0x02_0000;
/// Below Redistributor area is GICv3 ITS
pub const GIC_V3_ITS_SIZE: u64 = 0x02_0000;
/// Space 0x0900_0000 ~ 0x1000_0000 is reserved for legacy devices.
pub const LEGACY_SERIAL_MAPPED_IO_START: u64 = 0x0900_0000;
pub const LEGACY_RTC_MAPPED_IO_START: u64 = 0x0901_0000;
pub const LEGACY_GPIO_MAPPED_IO_START: u64 = 0x0902_0000;
/// Space 0x0900_0000 ~ 0x0905_0000 is reserved for legacy devices.
pub const LEGACY_SERIAL_MAPPED_IO_START: GuestAddress = MAPPED_IO_START;
pub const LEGACY_RTC_MAPPED_IO_START: GuestAddress = GuestAddress(0x0901_0000);
pub const LEGACY_GPIO_MAPPED_IO_START: GuestAddress = GuestAddress(0x0902_0000);
/// Legacy space will be allocated at once whiling setting up legacy devices.
pub const LEGACY_DEVICES_MAPPED_IO_SIZE: u64 = 0x0700_0000;
/// 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 = 0x10000;
/// Starting from 0x1000_0000 (256MiB) to 0x3000_0000 (768MiB) is used for PCIE MMIO
/// Starting from 0x1000_0000 (256MiB), the 768MiB (ends at 1 GiB) is used for PCIE MMIO
pub const MEM_32BIT_DEVICES_START: GuestAddress = GuestAddress(0x1000_0000);
pub const MEM_32BIT_DEVICES_SIZE: u64 = 0x2000_0000;
pub const MEM_32BIT_DEVICES_SIZE: u64 = 0x3000_0000;
/// PCI MMCONFIG space (start: after the device space at 1 GiB, length: 256MiB)
pub const PCI_MMCONFIG_START: GuestAddress = GuestAddress(0x3000_0000);
pub const PCI_MMCONFIG_START: GuestAddress = GuestAddress(0x4000_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);
/// 32-bit reserved area: 64MiB before 4GiB
pub const MEM_32BIT_RESERVED_START: GuestAddress = GuestAddress(0xfc00_0000);
pub const MEM_32BIT_RESERVED_SIZE: u64 = 0x0400_0000;
/// TPM Address Range
/// This Address range is specific to CRB Interface
pub const TPM_START: GuestAddress = GuestAddress(0xfed4_0000);
pub const TPM_SIZE: u64 = 0x1000;
/// Start of 64-bit RAM.
pub const RAM_64BIT_START: GuestAddress = GuestAddress(0x1_0000_0000);
/// Start of RAM on 64 bit ARM.
pub const RAM_64BIT_START: u64 = 0x8000_0000;
/// Kernel command line maximum size.
/// As per `arch/arm64/include/uapi/asm/setup.h`.
pub const CMDLINE_MAX_SIZE: usize = 2048;
/// FDT is at the beginning of RAM.
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
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(ACPI_START.0 + ACPI_MAX_SIZE);
/// Pci high memory base
pub const PCI_HIGH_BASE: GuestAddress = GuestAddress(0x2_0000_0000);
/// Maximum size of the device tree blob as specified in https://www.kernel.org/doc/Documentation/arm64/booting.txt.
pub const FDT_MAX_SIZE: usize = 0x20_0000;
// As per virt/kvm/arm/vgic/vgic-kvm-device.c we need
// the number of interrupts our GIC will support to be:
@@ -133,17 +80,8 @@ pub const PCI_HIGH_BASE: GuestAddress = GuestAddress(0x2_0000_0000);
// * less than 1023 and
// * a multiple of 32.
// We are setting up our interrupt controller to support a maximum of 256 interrupts.
/// First usable interrupt on aarch64
pub const IRQ_BASE: u32 = 32;
/// First usable interrupt on aarch64.
pub const IRQ_BASE: u32 = 0;
/// Number of supported interrupts
pub const IRQ_NUM: u32 = 256;
/// Base SPI interrupt number
pub const SPI_BASE: u32 = 32;
/// Total number of SPIs
pub const SPI_NUM: u32 = 64;
/// GICv2M compatible string
pub const GIC_V2M_COMPATIBLE: &str = "arm,gic-v2m-frame";
/// Last usable interrupt on aarch64.
pub const IRQ_MAX: u32 = 255;

View File

@@ -4,56 +4,49 @@
/// Module for the flattened device tree.
pub mod fdt;
/// Module for the global interrupt controller configuration.
pub mod gic;
/// Layout for this aarch64 system.
pub mod layout;
/// Module for 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};
/// Logic for configuring aarch64 registers.
pub mod regs;
pub use self::fdt::DeviceInfoForFdt;
use crate::{DeviceType, GuestMemoryMmap, NumaNodes, PciSpaceInfo, RegionType};
pub const _NSIG: i32 = 65;
use crate::DeviceType;
use crate::RegionType;
use aarch64::gic::GicDevice;
use std::collections::HashMap;
use std::ffi::CStr;
use std::fmt::Debug;
use std::sync::Arc;
use vm_memory::{
Address, GuestAddress, GuestAddressSpace, GuestMemory, GuestMemoryAtomic, GuestMemoryMmap,
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),
SetupFdt(fdt::Error),
/// Failed to create a GIC.
#[error("Failed to create a GIC")]
SetupGic,
SetupGic(gic::Error),
/// 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(regs::Error),
/// 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::AArch64Setup(e)
}
}
#[derive(Debug, Copy, Clone)]
@@ -66,97 +59,84 @@ pub struct EntryPoint {
/// Configure the specified VCPU, and return its MPIDR.
pub fn configure_vcpu(
vcpu: &Arc<dyn hypervisor::Vcpu>,
id: u32,
boot_setup: Option<(EntryPoint, &GuestMemoryAtomic<GuestMemoryMmap>)>,
fd: &Arc<dyn hypervisor::Vcpu>,
id: u8,
kernel_entry_point: Option<EntryPoint>,
vm_memory: &GuestMemoryAtomic<GuestMemoryMmap>,
) -> super::Result<u64> {
if let Some((kernel_entry_point, _guest_memory)) = boot_setup {
vcpu.setup_regs(
if let Some(kernel_entry_point) = kernel_entry_point {
regs::setup_regs(
fd,
id,
kernel_entry_point.entry_addr.raw_value(),
super::layout::FDT_START.raw_value(),
&vm_memory.memory(),
)
.map_err(Error::RegsConfiguration)?;
}
let mpidr = vcpu.get_sys_reg(MPIDR_EL1).map_err(Error::VcpuRegMpidr)?;
let mpidr = fd.read_mpidr().map_err(Error::VcpuRegMpidr)?;
Ok(mpidr)
}
pub fn arch_memory_regions() -> Vec<(GuestAddress, usize, RegionType)> {
pub fn arch_memory_regions(size: GuestUsize) -> Vec<(GuestAddress, usize, RegionType)> {
vec![
// 0 MiB ~ 256 MiB: UEFI, GIC and legacy devices
// 0 ~ 256 MiB: Reserved
(
GuestAddress(0),
layout::MEM_32BIT_DEVICES_START.0 as usize,
RegionType::Reserved,
),
// 256 MiB ~ 768 MiB: MMIO space
// 256 MiB ~ 1 G: 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
// 1G ~ 2G: reserved. The leading 256M for PCIe MMCONFIG space
(
layout::PCI_MMCONFIG_START,
layout::PCI_MMCONFIG_SIZE as usize,
(layout::RAM_64BIT_START - layout::PCI_MMCONFIG_START.0) as usize,
RegionType::Reserved,
),
// 1GiB ~ 4032 MiB: RAM before the gap
(
layout::RAM_START,
layout::MEM_32BIT_RESERVED_START.unchecked_offset_from(layout::RAM_START) as usize,
GuestAddress(layout::RAM_64BIT_START),
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,
),
]
}
/// Configures the system and should be called once per vm before starting vcpu threads.
///
/// # Arguments
///
/// * `guest_mem` - The memory to be used by the guest.
/// * `num_cpus` - Number of virtual CPUs the guest will have.
#[allow(clippy::too_many_arguments)]
pub fn configure_system<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHasher>(
vm: &Arc<dyn hypervisor::Vm>,
guest_mem: &GuestMemoryMmap,
cmdline: &str,
cmdline_cstring: &CStr,
vcpu_count: u64,
vcpu_mpidr: Vec<u64>,
vcpu_topology: Option<(u16, u16, u16, u16)>,
device_info: &HashMap<(DeviceType, String), T, S>,
initrd: &Option<super::InitramfsConfig>,
pci_space_info: &[PciSpaceInfo],
virtio_iommu_bdf: Option<u32>,
gic_device: &Arc<Mutex<dyn Vgic>>,
numa_nodes: &NumaNodes,
pmu_supported: bool,
) -> super::Result<()> {
let fdt_final = fdt::create_fdt(
pci_space_address: &(u64, u64),
) -> super::Result<Box<dyn GicDevice>> {
let gic_device = gic::kvm::create_gic(vm, vcpu_count).map_err(Error::SetupGic)?;
fdt::create_fdt(
guest_mem,
cmdline,
cmdline_cstring,
vcpu_mpidr,
vcpu_topology,
device_info,
gic_device,
&*gic_device,
initrd,
pci_space_info,
numa_nodes,
virtio_iommu_bdf,
pmu_supported,
pci_space_address,
)
.map_err(|_| Error::SetupFdt)?;
.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(())
Ok(gic_device)
}
/// Returns the memory address where the initramfs could be loaded.
@@ -165,30 +145,57 @@ pub fn initramfs_load_addr(
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)
match GuestAddress(get_fdt_addr(&guest_mem))
.checked_sub(round_to_pagesize(initramfs_size) as u64)
{
Some(offset) => {
if guest_mem.address_in_range(offset) {
Ok(offset.raw_value())
} else {
Err(super::Error::PlatformSpecific(Error::InitramfsAddress))
Err(super::Error::AArch64Setup(Error::InitramfsAddress))
}
}
None => Err(super::Error::PlatformSpecific(Error::InitramfsAddress)),
None => Err(super::Error::AArch64Setup(Error::InitramfsAddress)),
}
}
pub fn get_host_cpu_phys_bits(hypervisor: &Arc<dyn hypervisor::Hypervisor>) -> u8 {
let host_cpu_phys_bits = hypervisor.get_host_ipa_limit().try_into().unwrap();
if host_cpu_phys_bits == 0 {
// Host kernel does not support `get_host_ipa_limit`,
// we return the default value 40 here.
40
} else {
host_cpu_phys_bits
/// Returns the memory address where the kernel could be loaded.
pub fn get_kernel_start() -> u64 {
layout::RAM_64BIT_START
}
// Auxiliary function to get the address where the device tree blob is loaded.
fn get_fdt_addr(mem: &GuestMemoryMmap) -> u64 {
// If the memory allocated is smaller than the size allocated for the FDT,
// we return the start of the DRAM so that
// we allow the code to try and load the FDT.
if let Some(addr) = mem.last_addr().checked_sub(layout::FDT_MAX_SIZE as u64 - 1) {
if mem.address_in_range(addr) {
return addr.raw_value();
}
}
layout::RAM_64BIT_START
}
pub fn get_host_cpu_phys_bits() -> u8 {
// The value returned here is used to determine the physical address space size
// for a VM (IPA size).
// In recent kernel versions, the maximum IPA size supported by the host can be
// known by querying cap KVM_CAP_ARM_VM_IPA_SIZE. And the IPA size for a
// guest can be configured smaller.
// But in Cloud-Hypervisor we simply use the maximum value for the VM.
// Reference https://lwn.net/Articles/766767/.
//
// The correct way to query KVM_CAP_ARM_VM_IPA_SIZE is via rust-vmm/kvm-ioctls,
// which wraps all IOCTL's and provides easy interface to user hypervisors.
// For now the cap hasn't been supported. A separate patch will be submitted to
// rust-vmm to add it.
// So a hardcoded value is used here as a temporary solution.
// It will be replace once rust-vmm/kvm-ioctls is ready.
//
40
}
#[cfg(test)]
@@ -197,11 +204,39 @@ mod tests {
#[test]
fn test_arch_memory_regions_dram() {
let regions = arch_memory_regions();
assert_eq!(6, regions.len());
assert_eq!(layout::RAM_START, regions[3].0);
let regions = arch_memory_regions((1usize << 32) as u64); //4GB
assert_eq!(4, regions.len());
assert_eq!(GuestAddress(layout::RAM_64BIT_START), regions[3].0);
assert_eq!(1usize << 32, regions[3].1);
assert_eq!(RegionType::Ram, regions[3].2);
assert_eq!(RegionType::Reserved, regions[5].2);
assert_eq!(RegionType::Ram, regions[4].2);
}
#[test]
fn test_get_fdt_addr() {
let mut regions = Vec::new();
regions.push((
GuestAddress(layout::RAM_64BIT_START),
(layout::FDT_MAX_SIZE - 0x1000) as usize,
));
let mem = GuestMemoryMmap::from_ranges(&regions).expect("Cannot initialize memory");
assert_eq!(get_fdt_addr(&mem), layout::RAM_64BIT_START);
regions.clear();
regions.push((
GuestAddress(layout::RAM_64BIT_START),
(layout::FDT_MAX_SIZE) as usize,
));
let mem = GuestMemoryMmap::from_ranges(&regions).expect("Cannot initialize memory");
assert_eq!(get_fdt_addr(&mem), layout::RAM_64BIT_START);
regions.clear();
regions.push((
GuestAddress(layout::RAM_64BIT_START),
(layout::FDT_MAX_SIZE + 0x1000) as usize,
));
let mem = GuestMemoryMmap::from_ranges(&regions).expect("Cannot initialize memory");
assert_eq!(get_fdt_addr(&mem), 0x1000 + layout::RAM_64BIT_START);
regions.clear();
}
}

81
arch/src/aarch64/regs.rs Normal file
View File

@@ -0,0 +1,81 @@
// 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 super::get_fdt_addr;
use hypervisor::kvm::kvm_bindings::{
kvm_regs, user_pt_regs, KVM_REG_ARM64, KVM_REG_ARM_CORE, KVM_REG_SIZE_U64,
};
use hypervisor::{arm64_core_reg_id, offset__of};
use std::sync::Arc;
use std::{mem, result};
use vm_memory::GuestMemoryMmap;
/// Errors thrown while setting aarch64 registers.
#[derive(Debug)]
pub enum Error {
/// Failed to set core register (PC, PSTATE or general purpose ones).
SetCoreRegister(hypervisor::HypervisorCpuError),
/// Failed to get a system register.
GetSysRegister(hypervisor::HypervisorCpuError),
}
type Result<T> = result::Result<T, Error>;
#[allow(non_upper_case_globals)]
// PSR (Processor State Register) bits.
// Taken from arch/arm64/include/uapi/asm/ptrace.h.
const PSR_MODE_EL1h: u64 = 0x0000_0005;
const PSR_F_BIT: u64 = 0x0000_0040;
const PSR_I_BIT: u64 = 0x0000_0080;
const PSR_A_BIT: u64 = 0x0000_0100;
const PSR_D_BIT: u64 = 0x0000_0200;
// Taken from arch/arm64/kvm/inject_fault.c.
const PSTATE_FAULT_BITS_64: u64 = PSR_MODE_EL1h | PSR_A_BIT | PSR_F_BIT | PSR_I_BIT | PSR_D_BIT;
/// Configure core registers for a given CPU.
///
/// # Arguments
///
/// * `vcpu` - Structure for the VCPU that holds the VCPU's fd.
/// * `cpu_id` - Index of current vcpu.
/// * `boot_ip` - Starting instruction pointer.
/// * `mem` - Reserved DRAM for current VM.
pub fn setup_regs(
vcpu: &Arc<dyn hypervisor::Vcpu>,
cpu_id: u8,
boot_ip: u64,
mem: &GuestMemoryMmap,
) -> Result<()> {
let kreg_off = offset__of!(kvm_regs, regs);
// Get the register index of the PSTATE (Processor State) register.
let pstate = offset__of!(user_pt_regs, pstate) + kreg_off;
vcpu.set_reg(
arm64_core_reg_id!(KVM_REG_SIZE_U64, pstate),
PSTATE_FAULT_BITS_64,
)
.map_err(Error::SetCoreRegister)?;
// Other vCPUs are powered off initially awaiting PSCI wakeup.
if cpu_id == 0 {
// Setting the PC (Processor Counter) to the current program address (kernel address).
let pc = offset__of!(user_pt_regs, pc) + kreg_off;
vcpu.set_reg(arm64_core_reg_id!(KVM_REG_SIZE_U64, pc), boot_ip as u64)
.map_err(Error::SetCoreRegister)?;
// Last mandatory thing to set -> the address pointing to the FDT (also called DTB).
// "The device tree blob (dtb) must be placed on an 8-byte boundary and must
// not exceed 2 megabytes in size." -> https://www.kernel.org/doc/Documentation/arm64/booting.txt.
// We are choosing to place it the end of DRAM. See `get_fdt_addr`.
let regs0 = offset__of!(user_pt_regs, regs) + kreg_off;
vcpu.set_reg(
arm64_core_reg_id!(KVM_REG_SIZE_U64, regs0),
get_fdt_addr(mem) as u64,
)
.map_err(Error::SetCoreRegister)?;
}
Ok(())
}

View File

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

View File

@@ -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,58 +5,65 @@
// SPDX-License-Identifier: Apache-2.0
//! Implements platform specific functionality.
//! Supported platforms: x86_64, aarch64, riscv64.
//! Supported platforms: x86_64, aarch64.
#![allow(clippy::transmute_ptr_to_ptr, clippy::redundant_static_lifetimes)]
extern crate anyhow;
extern crate byteorder;
extern crate hypervisor;
extern crate libc;
#[macro_use]
extern crate log;
#[cfg(feature = "acpi")]
extern crate acpi_tables;
extern crate arch_gen;
extern crate linux_loader;
extern crate serde;
extern crate vm_memory;
extern crate vm_migration;
#[cfg(target_arch = "aarch64")]
#[macro_use]
extern crate serde_derive;
extern crate serde_json;
extern crate thiserror;
use std::collections::BTreeMap;
use std::sync::Arc;
use std::{fmt, result};
use serde::{Deserialize, Serialize};
use thiserror::Error;
type GuestMemoryMmap = vm_memory::GuestMemoryMmap<vm_memory::bitmap::AtomicBitmap>;
type GuestRegionMmap = vm_memory::GuestRegionMmap<vm_memory::bitmap::AtomicBitmap>;
use std::fmt;
use std::result;
/// Type for returning error code.
#[derive(Debug, Error)]
#[derive(Debug)]
pub enum Error {
#[cfg(target_arch = "x86_64")]
#[error("Platform specific error (x86_64)")]
PlatformSpecific(#[from] x86_64::Error),
/// X86_64 specific error triggered during system configuration.
X86_64Setup(x86_64::Error),
#[cfg(target_arch = "aarch64")]
#[error("Platform specific error (aarch64)")]
PlatformSpecific(#[from] aarch64::Error),
#[cfg(target_arch = "riscv64")]
#[error("Platform specific error (riscv64)")]
PlatformSpecific(#[from] riscv64::Error),
#[error("The memory map table extends past the end of guest memory")]
MemmapTablePastRamEnd,
#[error("Error writing memory map table to guest memory")]
MemmapTableSetup,
#[error("The hvm_start_info structure extends past the end of guest memory")]
StartInfoPastRamEnd,
#[error("Error writing hvm_start_info to guest memory")]
StartInfoSetup,
#[error("Failed to compute initramfs address")]
InitramfsAddress,
#[error("Error writing module entry to guest memory")]
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")]
/// AArch64 specific error triggered during system configuration.
AArch64Setup(aarch64::Error),
/// The zero page extends past the end of guest_mem.
ZeroPagePastRamEnd,
/// Error writing the zero page of guest memory.
ZeroPageSetup(vm_memory::GuestMemoryError),
/// The memory map table extends past the end of guest memory.
MemmapTablePastRamEnd,
/// Error writing memory map table to guest memory.
MemmapTableSetup,
/// The hvm_start_info structure extends past the end of guest memory.
StartInfoPastRamEnd,
/// Error writing hvm_start_info to guest memory.
StartInfoSetup,
/// Failed to compute initramfs address.
InitramfsAddress,
/// Error writing module entry to guest memory.
ModlistSetup(vm_memory::GuestMemoryError),
/// RSDP Beyond Guest Memory
RsdpPastRamEnd,
}
/// 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(PartialEq, Debug)]
pub enum RegionType {
/// RAM type
Ram,
@@ -81,20 +87,9 @@ 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, get_kernel_start, initramfs_load_addr, layout,
layout::CMDLINE_MAX_SIZE, layout::IRQ_BASE, layout::IRQ_MAX, EntryPoint,
};
#[cfg(target_arch = "x86_64")]
@@ -102,31 +97,19 @@ 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,
arch_memory_regions, configure_system, configure_vcpu, get_host_cpu_phys_bits,
initramfs_load_addr, layout, layout::CMDLINE_MAX_SIZE, layout::CMDLINE_START, regs,
BootProtocol, CpuidPatch, CpuidReg, EntryPoint,
};
/// Safe wrapper for `sysconf(_SC_PAGESIZE)`.
#[cfg(target_arch = "x86_64")]
#[inline(always)]
fn pagesize() -> usize {
// SAFETY: Trivially safe
// Trivially safe
unsafe { libc::sysconf(libc::_SC_PAGESIZE) as usize }
}
#[derive(Clone, Default)]
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 distances: BTreeMap<u32, u8>,
pub memory_zones: Vec<String>,
}
pub type NumaNodes = BTreeMap<u32, NumaNode>;
/// Type for passing information about the initramfs in the guest memory.
pub struct InitramfsConfig {
/// Load address of initramfs in guest memory
@@ -141,7 +124,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")]
@@ -149,9 +132,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.
@@ -159,30 +139,19 @@ pub const PAGE_SIZE: usize = 4096;
impl fmt::Display for DeviceType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{self:?}")
write!(f, "{:?}", self)
}
}
/// 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,
pub irq: u32,
}
/// Structure to describe PCI space information
#[derive(Clone, Debug)]
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
pub struct PciSpaceInfo {
pub pci_segment_id: u16,
pub mmio_config_address: u64,
pub pci_device_space_start: u64,
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
@@ -191,6 +160,6 @@ impl DeviceInfoForFdt for MmioDeviceInfo {
self.irq
}
fn length(&self) -> u64 {
self.len
4096
}
}

View File

@@ -1,481 +0,0 @@
// Copyright © 2024 Institute of Software, CAS. All rights reserved.
// Copyright 2020 Arm Limited (or its affiliates). All rights reserved.
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
//
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the THIRD-PARTY file.
use std::collections::HashMap;
use std::ffi::CStr;
use std::fmt::Debug;
use std::sync::{Arc, Mutex};
use std::{cmp, result, str};
use byteorder::{BigEndian, ByteOrder};
use hypervisor::arch::riscv64::aia::Vaia;
use thiserror::Error;
use vm_fdt::{FdtWriter, FdtWriterResult};
use vm_memory::{Address, Bytes, GuestMemory, GuestMemoryError, GuestMemoryRegion};
use super::super::{DeviceType, GuestMemoryMmap, InitramfsConfig};
use super::layout::{
IRQ_BASE, MEM_32BIT_DEVICES_SIZE, MEM_32BIT_DEVICES_START, MEM_PCI_IO_SIZE, MEM_PCI_IO_START,
PCI_HIGH_BASE, PCI_MMIO_CONFIG_SIZE_PER_SEGMENT,
};
use crate::PciSpaceInfo;
const AIA_APLIC_PHANDLE: u32 = 1;
const AIA_IMSIC_PHANDLE: u32 = 2;
const CPU_INTC_BASE_PHANDLE: u32 = 3;
const CPU_BASE_PHANDLE: u32 = 256 + CPU_INTC_BASE_PHANDLE;
// Read the documentation specified when appending the root node to the FDT.
const ADDRESS_CELLS: u32 = 0x2;
const SIZE_CELLS: u32 = 0x2;
// From https://elixir.bootlin.com/linux/v6.10/source/include/dt-bindings/interrupt-controller/irq.h#L14
const _IRQ_TYPE_EDGE_RISING: u32 = 1;
const IRQ_TYPE_LEVEL_HI: u32 = 4;
const S_MODE_EXT_IRQ: u32 = 9;
/// Trait for devices to be added to the Flattened Device Tree.
pub trait DeviceInfoForFdt {
/// Returns the address where this device will be loaded.
fn addr(&self) -> u64;
/// Returns the associated interrupt for this device.
fn irq(&self) -> u32;
/// Returns the amount of memory that needs to be reserved for this device.
fn length(&self) -> u64;
}
/// Errors thrown while configuring the Flattened Device Tree for riscv64.
#[derive(Debug, Error)]
pub enum Error {
/// Failure in writing FDT in memory.
#[error("Failure in writing FDT in memory")]
WriteFdtToMemory(#[source] GuestMemoryError),
}
type Result<T> = result::Result<T, Error>;
/// Creates the flattened device tree for this riscv64 VM.
#[allow(clippy::too_many_arguments)]
pub fn create_fdt<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHasher>(
guest_mem: &GuestMemoryMmap,
cmdline: &str,
num_vcpu: u32,
device_info: &HashMap<(DeviceType, String), T, S>,
aia_device: &Arc<Mutex<dyn Vaia>>,
initrd: &Option<InitramfsConfig>,
pci_space_info: &[PciSpaceInfo],
) -> FdtWriterResult<Vec<u8>> {
// Allocate stuff necessary for the holding the blob.
let mut fdt = FdtWriter::new()?;
// For an explanation why these nodes were introduced in the blob take a look at
// https://github.com/devicetree-org/devicetree-specification/releases/tag/v0.4
// In chapter 3.
// Header or the root node as per above mentioned documentation.
let root_node = fdt.begin_node("")?;
fdt.property_string("compatible", "linux,dummy-virt")?;
// For info on #address-cells and size-cells resort to Table 3.1 Root Node
// Properties
fdt.property_u32("#address-cells", ADDRESS_CELLS)?;
fdt.property_u32("#size-cells", SIZE_CELLS)?;
create_cpu_nodes(&mut fdt, num_vcpu)?;
create_memory_node(&mut fdt, guest_mem)?;
create_chosen_node(&mut fdt, cmdline, initrd)?;
create_aia_node(&mut fdt, aia_device)?;
create_devices_node(&mut fdt, device_info)?;
create_pci_nodes(&mut fdt, pci_space_info)?;
// End Header node.
fdt.end_node(root_node)?;
let fdt_final = fdt.finish()?;
Ok(fdt_final)
}
pub fn write_fdt_to_memory(fdt_final: Vec<u8>, guest_mem: &GuestMemoryMmap) -> Result<()> {
// Write FDT to memory.
guest_mem
.write_slice(fdt_final.as_slice(), super::layout::FDT_START)
.map_err(Error::WriteFdtToMemory)?;
Ok(())
}
// Following are the auxiliary function for creating the different nodes that we append to our FDT.
fn create_cpu_nodes(fdt: &mut FdtWriter, num_cpus: u32) -> FdtWriterResult<()> {
// See https://elixir.bootlin.com/linux/v6.10/source/Documentation/devicetree/bindings/riscv/cpus.yaml
let cpus = fdt.begin_node("cpus")?;
// As per documentation, on RISC-V 64-bit systems value should be set to 1.
fdt.property_u32("#address-cells", 0x01)?;
fdt.property_u32("#size-cells", 0x0)?;
// TODO: Retrieve CPU frequency from cpu timer regs
let timebase_frequency: u32 = 0x989680;
fdt.property_u32("timebase-frequency", timebase_frequency)?;
for cpu_index in 0..num_cpus {
let cpu = fdt.begin_node(&format!("cpu@{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", "rv64imafdc_smaia_ssaia")?;
fdt.property_string("status", "okay")?;
fdt.property_u32("reg", cpu_index)?;
fdt.property_u32("phandle", CPU_BASE_PHANDLE + cpu_index)?;
// interrupt controller node
let intc_node = fdt.begin_node("interrupt-controller")?;
fdt.property_string("compatible", "riscv,cpu-intc")?;
fdt.property_u32("#interrupt-cells", 1u32)?;
fdt.property_null("interrupt-controller")?;
fdt.property_u32("phandle", CPU_INTC_BASE_PHANDLE + cpu_index)?;
fdt.end_node(intc_node)?;
fdt.end_node(cpu)?;
}
fdt.end_node(cpus)?;
Ok(())
}
fn create_memory_node(fdt: &mut FdtWriter, guest_mem: &GuestMemoryMmap) -> FdtWriterResult<()> {
// Note: memory regions from "GuestMemory" are sorted and non-zero sized.
let ram_regions = {
let mut ram_regions = Vec::new();
let mut current_start = guest_mem
.iter()
.next()
.map(GuestMemoryRegion::start_addr)
.expect("GuestMemory must have one memory region at least")
.raw_value();
let mut current_end = current_start;
for (start, size) in guest_mem
.iter()
.map(|m| (m.start_addr().raw_value(), m.len()))
{
if current_end == start {
// This zone is continuous with the previous one.
current_end += size;
} else {
ram_regions.push((current_start, current_end));
current_start = start;
current_end = start + size;
}
}
ram_regions.push((current_start, current_end));
ram_regions
};
let mut mem_reg_property = Vec::new();
for region in ram_regions {
let mem_size = region.1 - region.0;
mem_reg_property.push(region.0);
mem_reg_property.push(mem_size);
}
let ram_start = super::layout::RAM_START.raw_value();
let memory_node_name = format!("memory@{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", &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", &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);
}
}

View File

@@ -1,112 +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;
/// Kernel start after FDT
pub const KERNEL_START: GuestAddress = GuestAddress(RAM_START.0 + FDT_MAX_SIZE);
/// Pci high memory base
pub const PCI_HIGH_BASE: GuestAddress = GuestAddress(0x2_0000_0000);
/// First usable interrupt on riscv64
pub const IRQ_BASE: u32 = 0;
// As per https://elixir.bootlin.com/linux/v6.10/source/arch/riscv/include/asm/kvm_host.h#L31
/// Number of supported interrupts
pub const IRQ_NUM: u32 = 1023;

View File

@@ -1,169 +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::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 _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),
}
#[derive(Debug, Copy, Clone)]
/// Specifies the entry point address where the guest must start
/// executing code.
pub struct EntryPoint {
/// Address in guest memory where the guest must start execution
pub entry_addr: GuestAddress,
}
/// Configure the specified VCPU, and return its MPIDR.
pub fn configure_vcpu(
vcpu: &Arc<dyn hypervisor::Vcpu>,
id: 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),
]
}
/// Configures the system and should be called once per vm before starting vcpu threads.
#[allow(clippy::too_many_arguments)]
pub fn configure_system<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHasher>(
guest_mem: &GuestMemoryMmap,
cmdline: &str,
num_vcpu: u32,
device_info: &HashMap<(DeviceType, String), T, S>,
initrd: &Option<super::InitramfsConfig>,
pci_space_info: &[PciSpaceInfo],
aia_device: &Arc<Mutex<dyn Vaia>>,
) -> super::Result<()> {
let fdt_final = fdt::create_fdt(
guest_mem,
cmdline,
num_vcpu,
device_info,
aia_device,
initrd,
pci_space_info,
)
.map_err(|_| Error::SetupFdt)?;
if log_enabled!(Level::Debug) {
fdt::print_fdt(&fdt_final);
}
fdt::write_fdt_to_memory(fdt_final, guest_mem).map_err(Error::WriteFdtToMemory)?;
Ok(())
}
/// Returns the memory address where the initramfs could be loaded.
pub fn initramfs_load_addr(
guest_mem: &GuestMemoryMmap,
initramfs_size: usize,
) -> super::Result<u64> {
let round_to_pagesize = |size| (size + (super::PAGE_SIZE - 1)) & !(super::PAGE_SIZE - 1);
match guest_mem
.last_addr()
.checked_sub(round_to_pagesize(initramfs_size) as u64 - 1)
{
Some(offset) => {
if guest_mem.address_in_range(offset) {
Ok(offset.raw_value())
} else {
Err(super::Error::PlatformSpecific(Error::InitramfsAddress))
}
}
None => Err(super::Error::PlatformSpecific(Error::InitramfsAddress)),
}
}
pub fn get_host_cpu_phys_bits(_hypervisor: &Arc<dyn hypervisor::Hypervisor>) -> u8 {
40
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_arch_memory_regions_dram() {
let regions = arch_memory_regions();
assert_eq!(4, regions.len());
assert_eq!(layout::RAM_START, regions[3].0);
assert_eq!(RegionType::Ram, regions[3].2);
}
}

View File

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

View File

@@ -5,9 +5,19 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE-BSD-3-Clause file.
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use hypervisor::x86_64::LapicState;
use std::io::Cursor;
use std::mem;
use std::result;
use std::sync::Arc;
#[derive(Debug)]
pub enum Error {
GetLapic(anyhow::Error),
SetLapic(anyhow::Error),
}
pub type Result<T> = result::Result<T, hypervisor::HypervisorCpuError>;
// Defines poached from apicdef.h kernel header.
@@ -16,6 +26,32 @@ pub const APIC_LVT1: usize = 0x360;
pub const APIC_MODE_NMI: u32 = 0x4;
pub const APIC_MODE_EXTINT: u32 = 0x7;
pub fn get_klapic_reg(klapic: &LapicState, reg_offset: usize) -> u32 {
let sliceu8 = unsafe {
// This array is only accessed as parts of a u32 word, so interpret it as a u8 array.
// Cursors are only readable on arrays of u8, not i8(c_char).
mem::transmute::<&[i8], &[u8]>(&klapic.regs[reg_offset..])
};
let mut reader = Cursor::new(sliceu8);
// Following call can't fail if the offsets defined above are correct.
reader
.read_u32::<LittleEndian>()
.expect("Failed to read klapic register")
}
pub fn set_klapic_reg(klapic: &mut LapicState, reg_offset: usize, value: u32) {
let sliceu8 = unsafe {
// This array is only accessed as parts of a u32 word, so interpret it as a u8 array.
// Cursors are only readable on arrays of u8, not i8(c_char).
mem::transmute::<&mut [i8], &mut [u8]>(&mut klapic.regs[reg_offset..])
};
let mut writer = Cursor::new(sliceu8);
// Following call can't fail if the offsets defined above are correct.
writer
.write_u32::<LittleEndian>(value)
.expect("Failed to write klapic register")
}
pub fn set_apic_delivery_mode(reg: u32, mode: u32) -> u32 {
((reg) & !0x700) | ((mode) << 8)
}
@@ -27,13 +63,60 @@ pub fn set_apic_delivery_mode(reg: u32, mode: u32) -> u32 {
pub fn set_lint(vcpu: &Arc<dyn hypervisor::Vcpu>) -> Result<()> {
let mut klapic = vcpu.get_lapic()?;
let lvt_lint0 = klapic.get_klapic_reg(APIC_LVT0);
klapic.set_klapic_reg(
let lvt_lint0 = get_klapic_reg(&klapic, APIC_LVT0);
set_klapic_reg(
&mut klapic,
APIC_LVT0,
set_apic_delivery_mode(lvt_lint0, APIC_MODE_EXTINT),
);
let lvt_lint1 = klapic.get_klapic_reg(APIC_LVT1);
klapic.set_klapic_reg(APIC_LVT1, set_apic_delivery_mode(lvt_lint1, APIC_MODE_NMI));
let lvt_lint1 = get_klapic_reg(&klapic, APIC_LVT1);
set_klapic_reg(
&mut klapic,
APIC_LVT1,
set_apic_delivery_mode(lvt_lint1, APIC_MODE_NMI),
);
vcpu.set_lapic(&klapic)
}
#[cfg(test)]
mod tests {
use super::*;
const KVM_APIC_REG_SIZE: usize = 0x400;
#[test]
fn test_set_and_get_klapic_reg() {
let reg_offset = 0x340;
let mut klapic = LapicState::default();
set_klapic_reg(&mut klapic, reg_offset, 3);
let value = get_klapic_reg(&klapic, reg_offset);
assert_eq!(value, 3);
}
#[test]
#[should_panic]
fn test_set_and_get_klapic_out_of_bounds() {
let reg_offset = KVM_APIC_REG_SIZE + 10;
let mut klapic = LapicState::default();
set_klapic_reg(&mut klapic, reg_offset, 3);
}
#[test]
fn test_apic_delivery_mode() {
let mut v: Vec<u32> = Vec::new();
v.resize(20, 0);
unsafe {
assert_eq!(
libc::getrandom(v.as_mut_ptr() as *mut _ as *mut libc::c_void, 80, 0),
80
);
}
v.iter_mut()
.for_each(|x| *x = set_apic_delivery_mode(*x, 2));
let after: Vec<u32> = v.iter().map(|x| ((*x & !0x700) | ((2) << 8))).collect();
assert_eq!(v, after);
}
}

View File

@@ -24,9 +24,6 @@ pub const LOW_RAM_START: GuestAddress = GuestAddress(0x0);
// == Fixed addresses within the "Low RAM" range: ==
// Location of EBDA address
pub const EBDA_POINTER: GuestAddress = GuestAddress(0x40e);
// Initial GDT/IDT needed to boot kernel
pub const BOOT_GDT_START: GuestAddress = GuestAddress(0x500);
pub const BOOT_IDT_START: GuestAddress = GuestAddress(0x520);
@@ -82,9 +79,9 @@ pub const HIGH_RAM_START: GuestAddress = GuestAddress(0x100000);
// == No fixed addresses in the "High RAM" range ==
// ** 32-bit reserved area (start: 3GiB, length: 896MiB) **
// ** 32-bit reserved area (start: 3GiB, length: 1GiB) **
pub const MEM_32BIT_RESERVED_START: GuestAddress = GuestAddress(0xc000_0000);
pub const MEM_32BIT_RESERVED_SIZE: u64 = PCI_MMCONFIG_SIZE + MEM_32BIT_DEVICES_SIZE;
pub const MEM_32BIT_RESERVED_SIZE: u64 = 1024 << 20;
// == Fixed constants within the "32-bit reserved" range ==
@@ -96,21 +93,6 @@ pub const MEM_32BIT_DEVICES_SIZE: u64 = 640 << 20;
pub const PCI_MMCONFIG_START: GuestAddress =
GuestAddress(MEM_32BIT_DEVICES_START.0 + MEM_32BIT_DEVICES_SIZE);
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;
// TSS is 3 pages after the PCI MMCONFIG space
pub const KVM_TSS_START: GuestAddress = GuestAddress(PCI_MMCONFIG_START.0 + PCI_MMCONFIG_SIZE);
pub const KVM_TSS_SIZE: u64 = (3 * 4) << 10;
// Identity map is a one page region after the TSS
pub const KVM_IDENTITY_MAP_START: GuestAddress = GuestAddress(KVM_TSS_START.0 + KVM_TSS_SIZE);
pub const KVM_IDENTITY_MAP_SIZE: u64 = 4 << 10;
/// TPM Address Range
/// This Address range is specific to CRB Interface
pub const TPM_START: GuestAddress = GuestAddress(0xfed4_0000);
pub const TPM_SIZE: u64 = 0x1000;
// IOAPIC
pub const IOAPIC_START: GuestAddress = GuestAddress(0xfec0_0000);
@@ -119,6 +101,9 @@ pub const IOAPIC_SIZE: u64 = 0x20;
// APIC
pub const APIC_START: GuestAddress = GuestAddress(0xfee0_0000);
/// Address for the TSS setup.
pub const KVM_TSS_ADDRESS: GuestAddress = GuestAddress(0xfffb_d000);
// == End of "32-bit reserved" range. ==
// ** 64-bit RAM start (start: 4GiB, length: varies) **

File diff suppressed because it is too large Load Diff

View File

@@ -1,178 +0,0 @@
// 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;
pub const MP_IOAPIC: ::std::os::raw::c_uint = 2;
pub const MP_INTSRC: ::std::os::raw::c_uint = 3;
pub const MP_LINTSRC: ::std::os::raw::c_uint = 4;
pub const CPU_ENABLED: ::std::os::raw::c_uint = 1;
pub const CPU_BOOTPROCESSOR: ::std::os::raw::c_uint = 2;
pub const MPC_APIC_USABLE: ::std::os::raw::c_uint = 1;
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 physptr: ::std::os::raw::c_uint,
pub length: ::std::os::raw::c_uchar,
pub specification: ::std::os::raw::c_uchar,
pub checksum: ::std::os::raw::c_uchar,
pub feature1: ::std::os::raw::c_uchar,
pub feature2: ::std::os::raw::c_uchar,
pub feature3: ::std::os::raw::c_uchar,
pub feature4: ::std::os::raw::c_uchar,
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 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 oemptr: ::std::os::raw::c_uint,
pub oemsize: ::std::os::raw::c_ushort,
pub oemcount: ::std::os::raw::c_ushort,
pub lapic: ::std::os::raw::c_uint,
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 {
pub type_: ::std::os::raw::c_uchar,
pub apicid: ::std::os::raw::c_uchar,
pub apicver: ::std::os::raw::c_uchar,
pub cpuflag: ::std::os::raw::c_uchar,
pub cpufeature: ::std::os::raw::c_uint,
pub featureflag: ::std::os::raw::c_uint,
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 {
pub type_: ::std::os::raw::c_uchar,
pub busid: ::std::os::raw::c_uchar,
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 {
pub type_: ::std::os::raw::c_uchar,
pub apicid: ::std::os::raw::c_uchar,
pub apicver: ::std::os::raw::c_uchar,
pub flags: ::std::os::raw::c_uchar,
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 {
pub type_: ::std::os::raw::c_uchar,
pub irqtype: ::std::os::raw::c_uchar,
pub irqflag: ::std::os::raw::c_ushort,
pub srcbus: ::std::os::raw::c_uchar,
pub srcbusirq: ::std::os::raw::c_uchar,
pub dstapic: ::std::os::raw::c_uchar,
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;
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct mpc_lintsrc {
pub type_: ::std::os::raw::c_uchar,
pub irqtype: ::std::os::raw::c_uchar,
pub irqflag: ::std::os::raw::c_ushort,
pub srcbusid: ::std::os::raw::c_uchar,
pub srcbusirq: ::std::os::raw::c_uchar,
pub destapic: ::std::os::raw::c_uchar,
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 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],
}
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 {}

View File

@@ -5,16 +5,18 @@
// 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 std::io;
use std::mem;
use std::result;
use std::slice;
use libc::c_uchar;
use thiserror::Error;
use vm_memory::{Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryError};
use libc::c_char;
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 arch_gen::x86::mpspec;
use layout::{APIC_START, HIGH_RAM_START, IOAPIC_START};
use vm_memory::{
Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryError, GuestMemoryMmap,
};
// This is a workaround to the Rust enforcement specifying that any implementation of a foreign
// trait (in this case `ByteValued`) where:
@@ -36,71 +38,67 @@ struct MpcLintsrcWrapper(mpspec::mpc_lintsrc);
#[derive(Copy, Clone, Default)]
struct MpfIntelWrapper(mpspec::mpf_intel);
// SAFETY: These `mpspec` wrapper types are only data, reading them from data is a safe initialization.
// These `mpspec` wrapper types are only data, reading them from data is a safe initialization.
unsafe impl ByteValued for MpcBusWrapper {}
// SAFETY: see above
unsafe impl ByteValued for MpcCpuWrapper {}
// SAFETY: see above
unsafe impl ByteValued for MpcIntsrcWrapper {}
// SAFETY: see above
unsafe impl ByteValued for MpcIoapicWrapper {}
// SAFETY: see above
unsafe impl ByteValued for MpcTableWrapper {}
// SAFETY: see above
unsafe impl ByteValued for MpcLintsrcWrapper {}
// SAFETY: see above
unsafe impl ByteValued for MpfIntelWrapper {}
#[derive(Debug, 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 {
// SAFETY: we are only reading the bytes within the size of the `T` reference `v`.
fn compute_checksum<T: Copy>(v: &T) -> u8 {
// Safe because we are only reading the bytes within the size of the `T` reference `v`.
let v_slice = unsafe { slice::from_raw_parts(v as *const T as *const u8, mem::size_of::<T>()) };
let mut checksum: u8 = 0;
for i in v_slice.iter() {
@@ -114,7 +112,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)
@@ -125,19 +123,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.
@@ -151,7 +139,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.
@@ -163,13 +151,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;
@@ -189,7 +177,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 {
@@ -210,7 +198,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);
@@ -234,7 +222,7 @@ pub fn setup_mptable(
let size = mem::size_of::<MpcIntsrcWrapper>();
let mut mpc_intsrc = MpcIntsrcWrapper(mpspec::mpc_intsrc::default());
mpc_intsrc.0.type_ = mpspec::MP_INTSRC as u8;
mpc_intsrc.0.irqtype = mpspec::MP_IRQ_SOURCE_TYPES_MP_INT as u8;
mpc_intsrc.0.irqtype = mpspec::mp_irq_source_types_mp_INT as u8;
mpc_intsrc.0.irqflag = mpspec::MP_IRQDIR_DEFAULT as u16;
mpc_intsrc.0.srcbus = 0;
mpc_intsrc.0.srcbusirq = i;
@@ -249,7 +237,7 @@ pub fn setup_mptable(
let size = mem::size_of::<MpcLintsrcWrapper>();
let mut mpc_lintsrc = MpcLintsrcWrapper(mpspec::mpc_lintsrc::default());
mpc_lintsrc.0.type_ = mpspec::MP_LINTSRC as u8;
mpc_lintsrc.0.irqtype = mpspec::MP_IRQ_SOURCE_TYPES_MP_EXT_INT as u8;
mpc_lintsrc.0.irqtype = mpspec::mp_irq_source_types_mp_ExtINT as u8;
mpc_lintsrc.0.irqflag = mpspec::MP_IRQDIR_DEFAULT as u16;
mpc_lintsrc.0.srcbusid = 0;
mpc_lintsrc.0.srcbusirq = 0;
@@ -264,7 +252,7 @@ pub fn setup_mptable(
let size = mem::size_of::<MpcLintsrcWrapper>();
let mut mpc_lintsrc = MpcLintsrcWrapper(mpspec::mpc_lintsrc::default());
mpc_lintsrc.0.type_ = mpspec::MP_LINTSRC as u8;
mpc_lintsrc.0.irqtype = mpspec::MP_IRQ_SOURCE_TYPES_MP_NMI as u8;
mpc_lintsrc.0.irqtype = mpspec::mp_irq_source_types_mp_NMI as u8;
mpc_lintsrc.0.irqflag = mpspec::MP_IRQDIR_DEFAULT as u16;
mpc_lintsrc.0.srcbusid = 0;
mpc_lintsrc.0.srcbusirq = 0;
@@ -281,14 +269,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,11 +286,9 @@ pub fn setup_mptable(
#[cfg(test)]
mod tests {
use vm_memory::bitmap::BitmapSlice;
use vm_memory::{GuestUsize, VolatileMemoryError, VolatileSlice, WriteVolatile};
use super::*;
use crate::layout::MPTABLE_START;
use layout::MPTABLE_START;
use vm_memory::{GuestAddress, GuestUsize};
fn table_entry_size(type_: u8) -> usize {
match type_ as u32 {
@@ -311,7 +297,7 @@ mod tests {
mpspec::MP_IOAPIC => mem::size_of::<MpcIoapicWrapper>(),
mpspec::MP_INTSRC => mem::size_of::<MpcIntsrcWrapper>(),
mpspec::MP_LINTSRC => mem::size_of::<MpcLintsrcWrapper>(),
_ => panic!("unrecognized mpc table entry type: {type_}"),
_ => panic!("unrecognized mpc table entry type: {}", type_),
}
}
@@ -321,7 +307,7 @@ mod 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]
@@ -330,7 +316,7 @@ mod 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]
@@ -339,7 +325,7 @@ mod 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();
@@ -355,31 +341,27 @@ mod 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);
}
@@ -388,12 +370,12 @@ mod 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);
@@ -422,9 +404,11 @@ mod 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());
}
}

View File

@@ -9,55 +9,41 @@
use std::sync::Arc;
use std::{mem, result};
use super::BootProtocol;
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 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 hypervisor::arch::x86::regs::*;
use hypervisor::x86_64::{FpuState, SpecialRegisters, StandardRegisters};
use layout::{
BOOT_GDT_START, BOOT_IDT_START, PDE_START, PDPTE_START, PML4_START, PML5_START, PVH_INFO_START,
};
use crate::{EntryPoint, GuestMemoryMmap};
use vm_memory::{Address, Bytes, GuestMemory, GuestMemoryError, 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>;
@@ -83,7 +69,7 @@ pub fn setup_fpu(vcpu: &Arc<dyn hypervisor::Vcpu>) -> Result<()> {
///
/// * `vcpu` - Structure for the VCPU that holds the VCPU's fd.
pub fn setup_msrs(vcpu: &Arc<dyn hypervisor::Vcpu>) -> Result<()> {
vcpu.set_msrs(&vcpu.boot_msr_entries())
vcpu.set_msrs(&hypervisor::x86_64::boot_msr_entries())
.map_err(Error::SetModelSpecificRegisters)?;
Ok(())
@@ -94,21 +80,33 @@ pub fn setup_msrs(vcpu: &Arc<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: &Arc<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.
/// * `boot_sp` - Starting stack pointer.
/// * `boot_si` - Must point to zero page address per Linux ABI.
pub fn setup_regs(
vcpu: &Arc<dyn hypervisor::Vcpu>,
boot_ip: u64,
boot_sp: u64,
boot_si: u64,
boot_prot: BootProtocol,
) -> Result<()> {
let regs: StandardRegisters = match boot_prot {
// Configure regs as required by PVH boot protocol.
BootProtocol::PvhBoot => StandardRegisters {
rflags: 0x0000000000000002u64,
rbx: PVH_INFO_START.raw_value(),
rip: boot_ip,
..Default::default()
},
// Configure regs as required by Linux 64-bit boot protocol.
BootProtocol::LinuxBoot => StandardRegisters {
rflags: 0x0000000000000002u64,
rip: boot_ip,
rsp: boot_sp,
rbp: boot_sp,
rsi: boot_si,
..Default::default()
},
};
vcpu.set_regs(&regs).map_err(Error::SetBaseRegisters)
}
@@ -122,10 +120,16 @@ pub fn setup_regs(vcpu: &Arc<dyn hypervisor::Vcpu>, entry_point: EntryPoint) ->
pub fn setup_sregs(
mem: &GuestMemoryMmap,
vcpu: &Arc<dyn hypervisor::Vcpu>,
enable_x2_apic_mode: bool,
boot_prot: BootProtocol,
) -> 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, boot_prot)?;
if let BootProtocol::LinuxBoot = boot_prot {
setup_page_tables(mem, &mut sregs)?; // TODO(dgreid) - Can this be done once per system instead?
}
vcpu.set_sregs(&sregs).map_err(Error::SetStatusRegisters)
}
@@ -152,16 +156,27 @@ 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,
boot_prot: BootProtocol,
) -> Result<()> {
let gdt_table: [u64; BOOT_GDT_MAX] = {
// Configure GDT entries as specified by PVH boot protocol
[
gdt_entry(0, 0, 0), // NULL
gdt_entry(0xc09b, 0, 0xffffffff), // CODE
gdt_entry(0xc093, 0, 0xffffffff), // DATA
gdt_entry(0x008b, 0, 0x67), // TSS
]
let gdt_table: [u64; BOOT_GDT_MAX as usize] = match boot_prot {
BootProtocol::PvhBoot => {
// Configure GDT entries as specified by PVH boot protocol
[
gdt_entry(0, 0, 0), // NULL
gdt_entry(0xc09b, 0, 0xffffffff), // CODE
gdt_entry(0xc093, 0, 0xffffffff), // DATA
gdt_entry(0x008b, 0, 0x67), // TSS
]
}
BootProtocol::LinuxBoot => {
// Configure GDT entries as specified by Linux 64bit boot protocol
[
gdt_entry(0, 0, 0), // NULL
gdt_entry(0xa09b, 0, 0xfffff), // CODE
gdt_entry(0xc093, 0, 0xfffff), // DATA
gdt_entry(0x808b, 0, 0xfffff), // TSS
]
}
};
let code_seg = segment_from_gdt(gdt_table[1], 1);
@@ -185,22 +200,61 @@ pub fn configure_segments_and_sregs(
sregs.ss = data_seg;
sregs.tr = tss_seg;
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;
match boot_prot {
BootProtocol::PvhBoot => {
sregs.cr0 = CR0_PE;
sregs.cr4 = 0;
}
BootProtocol::LinuxBoot => {
/* 64-bit protected mode */
sregs.cr0 |= CR0_PE;
sregs.efer |= EFER_LME | EFER_LMA;
}
}
Ok(())
}
pub fn setup_page_tables(mem: &GuestMemoryMmap, sregs: &mut SpecialRegisters) -> Result<()> {
// Puts PML5 or PML4 right after zero page but aligned to 4k.
if unsafe { std::arch::x86_64::__cpuid(7).ecx } & (1 << 16) != 0 {
// Entry covering VA [0..256TB)
mem.write_obj(PML4_START.raw_value() | 0x03, PML5_START)
.map_err(Error::WritePml5Address)?;
sregs.cr3 = PML5_START.raw_value();
sregs.cr4 |= CR4_LA57;
} else {
sregs.cr3 = PML4_START.raw_value();
}
// Entry covering VA [0..512GB)
mem.write_obj(PDPTE_START.raw_value() | 0x03, PML4_START)
.map_err(Error::WritePml4Address)?;
// Entry covering VA [0..1GB)
mem.write_obj(PDE_START.raw_value() | 0x03, PDPTE_START)
.map_err(Error::WritePdpteAddress)?;
// 512 2MB entries together covering VA [0..1GB). Note we are assuming
// CPU supports 2MB pages (/proc/cpuinfo has 'pse'). All modern CPUs do.
for i in 0..512 {
mem.write_obj((i << 21) + 0x83u64, PDE_START.unchecked_add(i * 8))
.map_err(Error::WritePdeAddress)?;
}
sregs.cr4 |= CR4_PAE;
sregs.cr0 |= CR0_PG;
Ok(())
}
#[cfg(test)]
mod tests {
use vm_memory::GuestAddress;
extern crate vm_memory;
use super::*;
use vm_memory::{GuestAddress, GuestMemoryMmap};
fn create_guest_mem() -> GuestMemoryMmap {
GuestMemoryMmap::from_ranges(&[(GuestAddress(0), 0x10000)]).unwrap()
@@ -214,7 +268,36 @@ mod 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, BootProtocol::LinuxBoot).unwrap();
assert_eq!(0x0, read_u64(&gm, BOOT_GDT_START));
assert_eq!(
0xaf9b000000ffff,
read_u64(&gm, BOOT_GDT_START.unchecked_add(8))
);
assert_eq!(
0xcf93000000ffff,
read_u64(&gm, BOOT_GDT_START.unchecked_add(16))
);
assert_eq!(
0x8f8b000000ffff,
read_u64(&gm, BOOT_GDT_START.unchecked_add(24))
);
assert_eq!(0x0, read_u64(&gm, BOOT_IDT_START));
assert_eq!(0, sregs.cs.base);
assert_eq!(0xffffffff, sregs.ds.limit);
assert_eq!(0x10, sregs.es.selector);
assert_eq!(1, sregs.fs.present);
assert_eq!(1, sregs.gs.g);
assert_eq!(0, sregs.ss.avl);
assert_eq!(0, sregs.tr.base);
assert_eq!(0xffffffff, sregs.tr.limit);
assert_eq!(0, sregs.tr.avl);
assert_eq!(CR0_PE, sregs.cr0);
assert_eq!(EFER_LME | EFER_LMA, sregs.efer);
configure_segments_and_sregs(&gm, &mut sregs, BootProtocol::PvhBoot).unwrap();
assert_eq!(0x0, read_u64(&gm, BOOT_GDT_START));
assert_eq!(
0xcf9b000000ffff,
@@ -244,4 +327,31 @@ mod tests {
assert_eq!(CR0_PE, sregs.cr0);
assert_eq!(0, sregs.cr4);
}
#[test]
fn page_tables() {
let mut sregs: SpecialRegisters = Default::default();
let gm = create_guest_mem();
setup_page_tables(&gm, &mut sregs).unwrap();
if unsafe { std::arch::x86_64::__cpuid(7).ecx } & (1 << 16) != 0 {
assert_eq!(0xa003, read_u64(&gm, PML5_START));
}
assert_eq!(0xb003, read_u64(&gm, PML4_START));
assert_eq!(0xc003, read_u64(&gm, PDPTE_START));
for i in 0..512 {
assert_eq!(
(i << 21) + 0x83u64,
read_u64(&gm, PDE_START.unchecked_add(i * 8))
);
}
if unsafe { std::arch::x86_64::__cpuid(7).ecx } & (1 << 16) != 0 {
assert_eq!(PML5_START.raw_value(), sregs.cr3);
} else {
assert_eq!(PML4_START.raw_value(), sregs.cr3);
}
assert_eq!(CR4_PAE, sregs.cr4);
assert_eq!(CR0_PG, sregs.cr0);
}
}

View File

@@ -6,35 +6,45 @@
//
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
use std::{mem, result, slice};
use layout::SMBIOS_START;
use std::fmt::{self, Display};
use std::mem;
use std::result;
use std::slice;
use vm_memory::ByteValued;
use vm_memory::{Address, Bytes, GuestAddress, GuestMemoryMmap};
use thiserror::Error;
use uuid::Uuid;
use vm_memory::{Address, ByteValued, Bytes, GuestAddress};
use crate::GuestMemoryMmap;
use crate::layout::SMBIOS_START;
#[derive(Debug, Error)]
#[allow(unused_variables)]
#[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")]
ParseUuid(#[source] uuid::Error),
}
impl std::error::Error for Error {}
impl Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::Error::*;
let description = match self {
NotEnoughMemory => "There was too little guest memory to store the SMBIOS table",
AddressOverflow => "The SMBIOS table has too little address space to be stored",
Clear => "Failure while zeroing out the memory for the SMBIOS table",
WriteSmbiosEp => "Failure to write SMBIOS entrypoint structure",
WriteData => "Failure to write additional data to memory",
};
write!(f, "SMBIOS error: {}", description)
}
}
pub type Result<T> = result::Result<T, Error>;
@@ -43,13 +53,12 @@ pub type Result<T> = result::Result<T, Error>;
const SM3_MAGIC_IDENT: &[u8; 5usize] = b"_SM3_";
const BIOS_INFORMATION: u8 = 0;
const SYSTEM_INFORMATION: u8 = 1;
const OEM_STRINGS: u8 = 11;
const END_OF_TABLE: u8 = 127;
const PCI_SUPPORTED: u64 = 1 << 7;
const IS_VIRTUAL_MACHINE: u8 = 1 << 4;
fn compute_checksum<T: Copy>(v: &T) -> u8 {
// SAFETY: we are only reading the bytes within the size of the `T` reference `v`.
// Safe because we are only reading the bytes within the size of the `T` reference `v`.
let v_slice = unsafe { slice::from_raw_parts(v as *const T as *const u8, mem::size_of::<T>()) };
let mut checksum: u8 = 0;
for i in v_slice.iter() {
@@ -58,85 +67,75 @@ fn compute_checksum<T: Copy>(v: &T) -> u8 {
(!checksum).wrapping_add(1)
}
#[repr(C)]
#[repr(packed)]
#[derive(Default, Copy, Clone)]
struct Smbios30Entrypoint {
signature: [u8; 5usize],
checksum: u8,
length: u8,
majorver: u8,
minorver: u8,
docrev: u8,
revision: u8,
reserved: u8,
max_size: u32,
physptr: u64,
#[derive(Default, Copy)]
pub struct Smbios30Entrypoint {
pub signature: [u8; 5usize],
pub checksum: u8,
pub length: u8,
pub majorver: u8,
pub minorver: u8,
pub docrev: u8,
pub revision: u8,
pub reserved: u8,
pub max_size: u32,
pub physptr: u64,
}
#[repr(C)]
#[repr(packed)]
#[derive(Default, Copy, Clone)]
struct SmbiosBiosInfo {
r#type: u8,
length: u8,
handle: u16,
vendor: u8,
version: u8,
start_addr: u16,
release_date: u8,
rom_size: u8,
characteristics: u64,
characteristics_ext1: u8,
characteristics_ext2: u8,
}
#[repr(C)]
#[repr(packed)]
#[derive(Default, Copy, Clone)]
struct SmbiosSysInfo {
r#type: u8,
length: u8,
handle: u16,
manufacturer: u8,
product_name: u8,
version: u8,
serial_number: u8,
uuid: [u8; 16usize],
wake_up_type: u8,
sku: u8,
family: u8,
}
#[repr(C)]
#[repr(packed)]
#[derive(Default, Copy, Clone)]
struct SmbiosOemStrings {
r#type: u8,
length: u8,
handle: u16,
count: u8,
}
#[repr(C)]
#[repr(packed)]
#[derive(Default, Copy, Clone)]
struct SmbiosEndOfTable {
r#type: u8,
length: u8,
handle: u16,
}
// SAFETY: data structure only contain a series of integers
unsafe impl ByteValued for Smbios30Entrypoint {}
// SAFETY: data structure only contain a series of integers
impl Clone for Smbios30Entrypoint {
fn clone(&self) -> Self {
*self
}
}
#[repr(packed)]
#[derive(Default, Copy)]
pub struct SmbiosBiosInfo {
pub typ: u8,
pub length: u8,
pub handle: u16,
pub vendor: u8,
pub version: u8,
pub start_addr: u16,
pub release_date: u8,
pub rom_size: u8,
pub characteristics: u64,
pub characteristics_ext1: u8,
pub characteristics_ext2: u8,
}
impl Clone for SmbiosBiosInfo {
fn clone(&self) -> Self {
*self
}
}
unsafe impl ByteValued for SmbiosBiosInfo {}
// SAFETY: data structure only contain a series of integers
#[repr(packed)]
#[derive(Default, Copy)]
pub struct SmbiosSysInfo {
pub typ: u8,
pub length: u8,
pub handle: u16,
pub manufacturer: u8,
pub product_name: u8,
pub version: u8,
pub serial_number: u8,
pub uuid: [u8; 16usize],
pub wake_up_type: u8,
pub sku: u8,
pub family: u8,
}
impl Clone for SmbiosSysInfo {
fn clone(&self) -> Self {
*self
}
}
unsafe impl ByteValued for SmbiosSysInfo {}
// SAFETY: data structure only contain a series of integers
unsafe impl ByteValued for SmbiosOemStrings {}
// SAFETY: data structure only contain a series of integers
unsafe impl ByteValued for SmbiosEndOfTable {}
fn write_and_incr<T: ByteValued>(
mem: &GuestMemoryMmap,
@@ -162,12 +161,7 @@ fn write_string(
Ok(curptr)
}
pub fn setup_smbios(
mem: &GuestMemoryMmap,
serial_number: Option<&str>,
uuid: Option<&str>,
oem_strings: Option<&[&str]>,
) -> Result<u64> {
pub fn setup_smbios(mem: &GuestMemoryMmap) -> Result<u64> {
let physptr = GuestAddress(SMBIOS_START)
.checked_add(mem::size_of::<Smbios30Entrypoint>() as u64)
.ok_or(Error::NotEnoughMemory)?;
@@ -177,7 +171,7 @@ pub fn setup_smbios(
{
handle += 1;
let smbios_biosinfo = SmbiosBiosInfo {
r#type: BIOS_INFORMATION,
typ: BIOS_INFORMATION,
length: mem::size_of::<SmbiosBiosInfo>() as u8,
handle,
vendor: 1, // First string written in this section
@@ -194,59 +188,29 @@ pub fn setup_smbios(
{
handle += 1;
let uuid_number = uuid
.map(Uuid::parse_str)
.transpose()
.map_err(Error::ParseUuid)?
.unwrap_or(Uuid::nil());
let smbios_sysinfo = SmbiosSysInfo {
r#type: SYSTEM_INFORMATION,
typ: SYSTEM_INFORMATION,
length: mem::size_of::<SmbiosSysInfo>() as u8,
handle,
manufacturer: 1, // First string written in this section
product_name: 2, // Second string written in this section
serial_number: serial_number.map(|_| 3).unwrap_or_default(), // 3rd string
uuid: uuid_number.to_bytes_le(), // set uuid
..Default::default()
};
curptr = write_and_incr(mem, smbios_sysinfo, curptr)?;
curptr = write_string(mem, "Cloud Hypervisor", curptr)?;
curptr = write_string(mem, "cloud-hypervisor", curptr)?;
if let Some(serial_number) = serial_number {
curptr = write_string(mem, serial_number, curptr)?;
}
curptr = write_and_incr(mem, 0u8, curptr)?;
}
if let Some(oem_strings) = oem_strings {
handle += 1;
let smbios_oemstrings = SmbiosOemStrings {
r#type: OEM_STRINGS,
length: mem::size_of::<SmbiosOemStrings>() as u8,
handle,
count: oem_strings.len() as u8,
};
curptr = write_and_incr(mem, smbios_oemstrings, curptr)?;
for s in oem_strings {
curptr = write_string(mem, s, curptr)?;
}
curptr = write_and_incr(mem, 0u8, curptr)?;
}
{
handle += 1;
let smbios_end = SmbiosEndOfTable {
r#type: END_OF_TABLE,
length: mem::size_of::<SmbiosEndOfTable>() as u8,
let smbios_sysinfo = SmbiosSysInfo {
typ: END_OF_TABLE,
length: mem::size_of::<SmbiosSysInfo>() as u8,
handle,
..Default::default()
};
curptr = write_and_incr(mem, smbios_end, curptr)?;
curptr = write_and_incr(mem, 0u8, curptr)?;
curptr = write_and_incr(mem, smbios_sysinfo, curptr)?;
curptr = write_and_incr(mem, 0u8, curptr)?;
}
@@ -268,7 +232,7 @@ pub fn setup_smbios(
.map_err(|_| Error::WriteSmbiosEp)?;
}
Ok(curptr.unchecked_offset_from(physptr) + std::mem::size_of::<Smbios30Entrypoint>() as u64)
Ok(curptr.unchecked_offset_from(physptr))
}
#[cfg(test)]
@@ -298,7 +262,7 @@ mod tests {
fn entrypoint_checksum() {
let mem = GuestMemoryMmap::from_ranges(&[(GuestAddress(SMBIOS_START), 4096)]).unwrap();
setup_smbios(&mem, None, None, None).unwrap();
setup_smbios(&mem).unwrap();
let smbios_ep: Smbios30Entrypoint = mem.read_obj(GuestAddress(SMBIOS_START)).unwrap();

View File

@@ -3,40 +3,27 @@
// SPDX-License-Identifier: Apache-2.0
use std::fs::File;
use std::io::{Read, Seek, SeekFrom};
use std::str::FromStr;
use thiserror::Error;
use uuid::Uuid;
use vm_memory::{ByteValued, Bytes, GuestAddress, GuestMemoryError};
use crate::GuestMemoryMmap;
use vm_memory::{ByteValued, Bytes, GuestAddress, GuestMemoryError, 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")]
ReadGuidTable(#[source] std::io::Error),
#[error("Invalid descriptor signature")]
InvalidDescriptorSignature,
#[error("Invalid descriptor size")]
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")]
UuidCreation(#[source] uuid::Error),
}
const TABLE_FOOTER_GUID: &str = "96b582de-1fb2-45f7-baea-a366c55a082d";
const TDVF_METADATA_OFFSET_GUID: &str = "e47a6535-984a-4798-865e-4685a7bf8ec2";
// TDVF_DESCRIPTOR
#[repr(C, packed)]
#[derive(Default)]
#[repr(packed)]
pub struct TdvfDescriptor {
signature: [u8; 4],
length: u32,
@@ -45,7 +32,7 @@ pub struct TdvfDescriptor {
}
// TDVF_SECTION
#[repr(C, packed)]
#[repr(packed)]
#[derive(Clone, Copy, Default, Debug)]
pub struct TdvfSection {
pub data_offset: u32,
@@ -57,85 +44,22 @@ pub struct TdvfSection {
}
#[repr(u32)]
#[derive(Clone, Copy, Debug, Default)]
#[derive(Clone, Copy, Debug)]
pub enum TdvfSectionType {
Bfv,
Cfv,
TdHob,
TempMem,
PermMem,
Payload,
PayloadParam,
#[default]
Reserved = 0xffffffff,
}
fn tdvf_descriptor_offset(file: &mut File) -> Result<(SeekFrom, bool), TdvfError> {
// Let's first try to identify the presence of the table footer GUID
file.seek(SeekFrom::End(-0x30))
.map_err(TdvfError::ReadGuidTable)?;
let mut table_footer_guid: [u8; 16] = [0; 16];
file.read_exact(&mut table_footer_guid)
.map_err(TdvfError::ReadGuidTable)?;
let uuid =
Uuid::from_slice_le(table_footer_guid.as_slice()).map_err(TdvfError::UuidCreation)?;
let expected_uuid = Uuid::from_str(TABLE_FOOTER_GUID).map_err(TdvfError::UuidCreation)?;
if uuid == expected_uuid {
// Retrieve the table size
file.seek(SeekFrom::End(-0x32))
.map_err(TdvfError::ReadGuidTable)?;
let mut table_size: [u8; 2] = [0; 2];
file.read_exact(&mut table_size)
.map_err(TdvfError::ReadGuidTable)?;
let table_size = u16::from_le_bytes(table_size) as usize;
let mut table: Vec<u8> = vec![0; table_size];
// Read the entire table
file.seek(SeekFrom::End(-(table_size as i64 + 0x20)))
.map_err(TdvfError::ReadGuidTable)?;
file.read_exact(table.as_mut_slice())
.map_err(TdvfError::ReadGuidTable)?;
// Let's start from the top and go backward down the table.
// We start after the footer GUID and the table length.
let mut offset = table_size - 18;
debug!("Parsing GUID structure");
while offset >= 18 {
let entry_uuid = Uuid::from_slice_le(&table[offset - 16..offset])
.map_err(TdvfError::UuidCreation)?;
let entry_size =
u16::from_le_bytes(table[offset - 18..offset - 16].try_into().unwrap()) as usize;
debug!(
"Entry GUID = {}, size = {}",
entry_uuid.hyphenated().to_string(),
entry_size
);
// Avoid going through an infinite loop if the entry size is 0
if entry_size == 0 {
break;
}
offset -= entry_size;
let expected_uuid =
Uuid::from_str(TDVF_METADATA_OFFSET_GUID).map_err(TdvfError::UuidCreation)?;
if entry_uuid == expected_uuid && entry_size == 22 {
return Ok((
SeekFrom::End(
-(u32::from_le_bytes(table[offset..offset + 4].try_into().unwrap()) as i64),
),
true,
));
}
}
impl Default for TdvfSectionType {
fn default() -> Self {
TdvfSectionType::Reserved
}
}
// If we end up here, this means the firmware doesn't support the new way
// of exposing the TDVF descriptor offset through the table of GUIDs.
// That's why we fallback onto the deprecated method.
pub fn parse_tdvf_sections(file: &mut File) -> Result<Vec<TdvfSection>, TdvfError> {
// The 32-bit offset to the TDVF metadata is located 32 bytes from
// the end of the file.
// See "TDVF Metadata Pointer" in "TDX Virtual Firmware Design Guide
@@ -145,21 +69,13 @@ fn tdvf_descriptor_offset(file: &mut File) -> Result<(SeekFrom, bool), TdvfError
let mut descriptor_offset: [u8; 4] = [0; 4];
file.read_exact(&mut descriptor_offset)
.map_err(TdvfError::ReadDescriptorOffset)?;
let descriptor_offset = u32::from_le_bytes(descriptor_offset) as u64;
Ok((
SeekFrom::Start(u32::from_le_bytes(descriptor_offset) as u64),
false,
))
}
pub fn parse_tdvf_sections(file: &mut File) -> Result<(Vec<TdvfSection>, bool), TdvfError> {
let (descriptor_offset, guid_found) = tdvf_descriptor_offset(file)?;
file.seek(descriptor_offset)
file.seek(SeekFrom::Start(descriptor_offset))
.map_err(TdvfError::ReadDescriptor)?;
let mut descriptor: TdvfDescriptor = Default::default();
// SAFETY: we read exactly the size of the descriptor header
let mut descriptor: TdvfDescriptor = unsafe { std::mem::zeroed() };
// Safe as we read exactly the size of the descriptor header
file.read_exact(unsafe {
std::slice::from_raw_parts_mut(
&mut descriptor as *mut _ as *mut u8,
@@ -186,7 +102,7 @@ pub fn parse_tdvf_sections(file: &mut File) -> Result<(Vec<TdvfSection>, bool),
let mut sections = Vec::new();
sections.resize_with(descriptor.num_sections as usize, TdvfSection::default);
// SAFETY: we read exactly the advertised sections
// Safe as we read exactly the advertised sections
file.read_exact(unsafe {
std::slice::from_raw_parts_mut(
sections.as_mut_ptr() as *mut u8,
@@ -195,42 +111,47 @@ pub fn parse_tdvf_sections(file: &mut File) -> Result<(Vec<TdvfSection>, bool),
})
.map_err(TdvfError::ReadDescriptor)?;
Ok((sections, guid_found))
Ok(sections)
}
#[repr(u16)]
#[derive(Copy, Clone, Debug, Default)]
#[derive(Copy, Clone, Debug)]
enum HobType {
Handoff = 0x1,
ResourceDescriptor = 0x3,
GuidExtension = 0x4,
#[default]
Unused = 0xfffe,
EndOfHobList = 0xffff,
}
#[repr(C, packed)]
impl Default for HobType {
fn default() -> Self {
HobType::Unused
}
}
#[repr(C)]
#[derive(Copy, Clone, Default, Debug)]
struct HobHeader {
r#type: HobType,
length: u16,
reserved: u32,
}
unsafe impl ByteValued for HobHeader {}
#[repr(C, packed)]
#[repr(C)]
#[derive(Copy, Clone, Default, Debug)]
struct HobHandoffInfoTable {
header: HobHeader,
version: u32,
boot_mode: u32,
efi_memory_top: u64,
efi_memory_bottom: u64,
efi_free_memory_top: u64,
efi_free_memory_bottom: u64,
efi_end_of_hob_list: u64,
}
unsafe impl ByteValued for HobHandoffInfoTable {}
#[repr(C, packed)]
#[repr(C)]
#[derive(Copy, Clone, Default, Debug)]
struct EfiGuid {
data1: u32,
@@ -239,7 +160,7 @@ struct EfiGuid {
data4: [u8; 8],
}
#[repr(C, packed)]
#[repr(C)]
#[derive(Copy, Clone, Default, Debug)]
struct HobResourceDescriptor {
header: HobHeader,
@@ -249,49 +170,7 @@ struct HobResourceDescriptor {
physical_start: u64,
resource_length: u64,
}
#[repr(C, packed)]
#[derive(Copy, Clone, Default, Debug)]
struct HobGuidType {
header: HobHeader,
name: EfiGuid,
}
#[repr(u32)]
#[derive(Clone, Copy, Debug, Default)]
pub enum PayloadImageType {
#[default]
ExecutablePayload,
BzImage,
RawVmLinux,
}
#[repr(C, packed)]
#[derive(Copy, Clone, Default, Debug)]
pub struct PayloadInfo {
pub image_type: PayloadImageType,
pub entry_point: u64,
}
#[repr(C, packed)]
#[derive(Copy, Clone, Default, Debug)]
struct TdPayload {
guid_type: HobGuidType,
payload_info: PayloadInfo,
}
// SAFETY: data structure only contain a series of integers
unsafe impl ByteValued for HobHeader {}
// SAFETY: data structure only contain a series of integers
unsafe impl ByteValued for HobHandoffInfoTable {}
// SAFETY: data structure only contain a series of integers
unsafe impl ByteValued for HobResourceDescriptor {}
// SAFETY: data structure only contain a series of integers
unsafe impl ByteValued for HobGuidType {}
// SAFETY: data structure only contain a series of integers
unsafe impl ByteValued for PayloadInfo {}
// SAFETY: data structure only contain a series of integers
unsafe impl ByteValued for TdPayload {}
pub struct TdHob {
start_offset: u64,
@@ -299,7 +178,7 @@ pub struct TdHob {
}
fn align_hob(v: u64) -> u64 {
v.div_ceil(8) * 8
(v + 7) / 8 * 8
}
impl TdHob {
@@ -338,7 +217,6 @@ impl TdHob {
reserved: 0,
},
version: 0x9,
boot_mode: 0,
efi_memory_top: 0,
efi_memory_bottom: 0,
efi_free_memory_top: 0,
@@ -386,19 +264,12 @@ impl TdHob {
physical_start: u64,
resource_length: u64,
ram: bool,
guid_found: bool,
) -> Result<(), TdvfError> {
self.add_resource(
mem,
physical_start,
resource_length,
if ram {
if guid_found {
0x7 /* EFI_RESOURCE_MEMORY_UNACCEPTED */
} else {
0 /* EFI_RESOURCE_SYSTEM_MEMORY */
}
} else if guid_found {
0 /* EFI_RESOURCE_SYSTEM_MEMORY */
} else {
0x5 /*EFI_RESOURCE_MEMORY_RESERVED */
@@ -429,93 +300,6 @@ impl TdHob {
0x403,
)
}
pub fn add_acpi_table(
&mut self,
mem: &GuestMemoryMmap,
table_content: &[u8],
) -> Result<(), TdvfError> {
// We already know the HobGuidType size is 8 bytes multiple, but we
// need the total size to be 8 bytes multiple. That is why the ACPI
// table size must be 8 bytes multiple as well.
let length = std::mem::size_of::<HobGuidType>() as u16
+ align_hob(table_content.len() as u64) as u16;
let hob_guid_type = HobGuidType {
header: HobHeader {
r#type: HobType::GuidExtension,
length,
reserved: 0,
},
// ACPI_TABLE_HOB_GUID
// 0x6a0c5870, 0xd4ed, 0x44f4, {0xa1, 0x35, 0xdd, 0x23, 0x8b, 0x6f, 0xc, 0x8d }
name: EfiGuid {
data1: 0x6a0c_5870,
data2: 0xd4ed,
data3: 0x44f4,
data4: [0xa1, 0x35, 0xdd, 0x23, 0x8b, 0x6f, 0xc, 0x8d],
},
};
info!(
"Writing HOB ACPI table {:x} {:x?} {:x?}",
self.current_offset, hob_guid_type, table_content
);
mem.write_obj(hob_guid_type, GuestAddress(self.current_offset))
.map_err(TdvfError::GuestMemoryWriteHob)?;
let current_offset = self.current_offset + std::mem::size_of::<HobGuidType>() as u64;
// In case the table is quite large, let's make sure we can handle
// retrying until everything has been correctly copied.
let mut offset: usize = 0;
loop {
let bytes_written = mem
.write(
&table_content[offset..],
GuestAddress(current_offset + offset as u64),
)
.map_err(TdvfError::GuestMemoryWriteHob)?;
offset += bytes_written;
if offset >= table_content.len() {
break;
}
}
self.current_offset += length as u64;
Ok(())
}
pub fn add_payload(
&mut self,
mem: &GuestMemoryMmap,
payload_info: PayloadInfo,
) -> Result<(), TdvfError> {
let payload = TdPayload {
guid_type: HobGuidType {
header: HobHeader {
r#type: HobType::GuidExtension,
length: std::mem::size_of::<TdPayload>() as u16,
reserved: 0,
},
// HOB_PAYLOAD_INFO_GUID
// 0xb96fa412, 0x461f, 0x4be3, {0x8c, 0xd, 0xad, 0x80, 0x5a, 0x49, 0x7a, 0xc0
name: EfiGuid {
data1: 0xb96f_a412,
data2: 0x461f,
data3: 0x4be3,
data4: [0x8c, 0xd, 0xad, 0x80, 0x5a, 0x49, 0x7a, 0xc0],
},
},
payload_info,
};
info!(
"Writing HOB TD_PAYLOAD {:x} {:x?}",
self.current_offset, payload
);
mem.write_obj(payload, GuestAddress(self.current_offset))
.map_err(TdvfError::GuestMemoryWriteHob)?;
self.update_offset::<TdPayload>();
Ok(())
}
}
#[cfg(test)]
@@ -526,9 +310,9 @@ mod tests {
#[ignore]
fn test_parse_tdvf_sections() {
let mut f = std::fs::File::open("tdvf.fd").unwrap();
let (sections, _) = parse_tdvf_sections(&mut f).unwrap();
let sections = parse_tdvf_sections(&mut f).unwrap();
for section in sections {
eprintln!("{section:x?}")
eprintln!("{:x?}", section)
}
}
}

6
arch_gen/Cargo.lock generated Normal file
View File

@@ -0,0 +1,6 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "arch_gen"
version = "0.1.0"

7
arch_gen/Cargo.toml Normal file
View File

@@ -0,0 +1,7 @@
[package]
name = "arch_gen"
version = "0.1.0"
authors = ["Amazon firecracker team <firecracker-devel@amazon.com>"]
[dependencies]

5
arch_gen/src/lib.rs Normal file
View File

@@ -0,0 +1,5 @@
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
pub mod x86;

14
arch_gen/src/x86/mod.rs Normal file
View File

@@ -0,0 +1,14 @@
// Copyright 2018 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 LICENSE-BSD-3-Clause file.
#[allow(non_upper_case_globals)]
#[allow(non_camel_case_types)]
#[allow(non_snake_case)]
#[allow(non_camel_case_types)]
#[allow(non_upper_case_globals)]
#[allow(clippy::unreadable_literal, clippy::redundant_static_lifetimes)]
pub mod mpspec;

832
arch_gen/src/x86/mpspec.rs Normal file
View File

@@ -0,0 +1,832 @@
// 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.
/* automatically generated by rust-bindgen */
pub const MPC_SIGNATURE: &'static [u8; 5usize] = b"PCMP\x00";
pub const MP_PROCESSOR: ::std::os::raw::c_uint = 0;
pub const MP_BUS: ::std::os::raw::c_uint = 1;
pub const MP_IOAPIC: ::std::os::raw::c_uint = 2;
pub const MP_INTSRC: ::std::os::raw::c_uint = 3;
pub const MP_LINTSRC: ::std::os::raw::c_uint = 4;
pub const MP_TRANSLATION: ::std::os::raw::c_uint = 192;
pub const CPU_ENABLED: ::std::os::raw::c_uint = 1;
pub const CPU_BOOTPROCESSOR: ::std::os::raw::c_uint = 2;
pub const CPU_STEPPING_MASK: ::std::os::raw::c_uint = 15;
pub const CPU_MODEL_MASK: ::std::os::raw::c_uint = 240;
pub const CPU_FAMILY_MASK: ::std::os::raw::c_uint = 3840;
pub const BUSTYPE_EISA: &'static [u8; 5usize] = b"EISA\x00";
pub const BUSTYPE_ISA: &'static [u8; 4usize] = b"ISA\x00";
pub const BUSTYPE_INTERN: &'static [u8; 7usize] = b"INTERN\x00";
pub const BUSTYPE_MCA: &'static [u8; 4usize] = b"MCA\x00";
pub const BUSTYPE_VL: &'static [u8; 3usize] = b"VL\x00";
pub const BUSTYPE_PCI: &'static [u8; 4usize] = b"PCI\x00";
pub const BUSTYPE_PCMCIA: &'static [u8; 7usize] = b"PCMCIA\x00";
pub const BUSTYPE_CBUS: &'static [u8; 5usize] = b"CBUS\x00";
pub const BUSTYPE_CBUSII: &'static [u8; 7usize] = b"CBUSII\x00";
pub const BUSTYPE_FUTURE: &'static [u8; 7usize] = b"FUTURE\x00";
pub const BUSTYPE_MBI: &'static [u8; 4usize] = b"MBI\x00";
pub const BUSTYPE_MBII: &'static [u8; 5usize] = b"MBII\x00";
pub const BUSTYPE_MPI: &'static [u8; 4usize] = b"MPI\x00";
pub const BUSTYPE_MPSA: &'static [u8; 5usize] = b"MPSA\x00";
pub const BUSTYPE_NUBUS: &'static [u8; 6usize] = b"NUBUS\x00";
pub const BUSTYPE_TC: &'static [u8; 3usize] = b"TC\x00";
pub const BUSTYPE_VME: &'static [u8; 4usize] = b"VME\x00";
pub const BUSTYPE_XPRESS: &'static [u8; 7usize] = b"XPRESS\x00";
pub const MPC_APIC_USABLE: ::std::os::raw::c_uint = 1;
pub const MP_IRQDIR_DEFAULT: ::std::os::raw::c_uint = 0;
pub const MP_IRQDIR_HIGH: ::std::os::raw::c_uint = 1;
pub const MP_IRQDIR_LOW: ::std::os::raw::c_uint = 3;
pub const MP_APIC_ALL: ::std::os::raw::c_uint = 255;
pub const MPC_OEM_SIGNATURE: &'static [u8; 5usize] = b"_OEM\x00";
#[repr(C)]
#[derive(Debug, Default, Copy)]
pub struct mpf_intel {
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,
pub checksum: ::std::os::raw::c_uchar,
pub feature1: ::std::os::raw::c_uchar,
pub feature2: ::std::os::raw::c_uchar,
pub feature3: ::std::os::raw::c_uchar,
pub feature4: ::std::os::raw::c_uchar,
pub feature5: ::std::os::raw::c_uchar,
}
#[test]
fn bindgen_test_layout_mpf_intel() {
assert_eq!(
::std::mem::size_of::<mpf_intel>(),
16usize,
concat!("Size of: ", stringify!(mpf_intel))
);
assert_eq!(
::std::mem::align_of::<mpf_intel>(),
4usize,
concat!("Alignment of ", stringify!(mpf_intel))
);
assert_eq!(
unsafe { &(*std::ptr::null::<mpf_intel>()).signature as *const _ as usize },
0usize,
concat!(
"Alignment of field: ",
stringify!(mpf_intel),
"::",
stringify!(signature)
)
);
assert_eq!(
unsafe { &(*std::ptr::null::<mpf_intel>()).physptr as *const _ as usize },
4usize,
concat!(
"Alignment of field: ",
stringify!(mpf_intel),
"::",
stringify!(physptr)
)
);
assert_eq!(
unsafe { &(*std::ptr::null::<mpf_intel>()).length as *const _ as usize },
8usize,
concat!(
"Alignment of field: ",
stringify!(mpf_intel),
"::",
stringify!(length)
)
);
assert_eq!(
unsafe { &(*std::ptr::null::<mpf_intel>()).specification as *const _ as usize },
9usize,
concat!(
"Alignment of field: ",
stringify!(mpf_intel),
"::",
stringify!(specification)
)
);
assert_eq!(
unsafe { &(*std::ptr::null::<mpf_intel>()).checksum as *const _ as usize },
10usize,
concat!(
"Alignment of field: ",
stringify!(mpf_intel),
"::",
stringify!(checksum)
)
);
assert_eq!(
unsafe { &(*std::ptr::null::<mpf_intel>()).feature1 as *const _ as usize },
11usize,
concat!(
"Alignment of field: ",
stringify!(mpf_intel),
"::",
stringify!(feature1)
)
);
assert_eq!(
unsafe { &(*std::ptr::null::<mpf_intel>()).feature2 as *const _ as usize },
12usize,
concat!(
"Alignment of field: ",
stringify!(mpf_intel),
"::",
stringify!(feature2)
)
);
assert_eq!(
unsafe { &(*std::ptr::null::<mpf_intel>()).feature3 as *const _ as usize },
13usize,
concat!(
"Alignment of field: ",
stringify!(mpf_intel),
"::",
stringify!(feature3)
)
);
assert_eq!(
unsafe { &(*std::ptr::null::<mpf_intel>()).feature4 as *const _ as usize },
14usize,
concat!(
"Alignment of field: ",
stringify!(mpf_intel),
"::",
stringify!(feature4)
)
);
assert_eq!(
unsafe { &(*std::ptr::null::<mpf_intel>()).feature5 as *const _ as usize },
15usize,
concat!(
"Alignment of field: ",
stringify!(mpf_intel),
"::",
stringify!(feature5)
)
);
}
impl Clone for mpf_intel {
fn clone(&self) -> Self {
*self
}
}
#[repr(C)]
#[derive(Debug, Default, Copy)]
pub struct mpc_table {
pub signature: [::std::os::raw::c_char; 4usize],
pub length: ::std::os::raw::c_ushort,
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,
pub lapic: ::std::os::raw::c_uint,
pub reserved: ::std::os::raw::c_uint,
}
#[test]
fn bindgen_test_layout_mpc_table() {
assert_eq!(
::std::mem::size_of::<mpc_table>(),
44usize,
concat!("Size of: ", stringify!(mpc_table))
);
assert_eq!(
::std::mem::align_of::<mpc_table>(),
4usize,
concat!("Alignment of ", stringify!(mpc_table))
);
assert_eq!(
unsafe { &(*std::ptr::null::<mpc_table>()).signature as *const _ as usize },
0usize,
concat!(
"Alignment of field: ",
stringify!(mpc_table),
"::",
stringify!(signature)
)
);
assert_eq!(
unsafe { &(*std::ptr::null::<mpc_table>()).length as *const _ as usize },
4usize,
concat!(
"Alignment of field: ",
stringify!(mpc_table),
"::",
stringify!(length)
)
);
assert_eq!(
unsafe { &(*std::ptr::null::<mpc_table>()).spec as *const _ as usize },
6usize,
concat!(
"Alignment of field: ",
stringify!(mpc_table),
"::",
stringify!(spec)
)
);
assert_eq!(
unsafe { &(*std::ptr::null::<mpc_table>()).checksum as *const _ as usize },
7usize,
concat!(
"Alignment of field: ",
stringify!(mpc_table),
"::",
stringify!(checksum)
)
);
assert_eq!(
unsafe { &(*std::ptr::null::<mpc_table>()).oem as *const _ as usize },
8usize,
concat!(
"Alignment of field: ",
stringify!(mpc_table),
"::",
stringify!(oem)
)
);
assert_eq!(
unsafe { &(*std::ptr::null::<mpc_table>()).productid as *const _ as usize },
16usize,
concat!(
"Alignment of field: ",
stringify!(mpc_table),
"::",
stringify!(productid)
)
);
assert_eq!(
unsafe { &(*std::ptr::null::<mpc_table>()).oemptr as *const _ as usize },
28usize,
concat!(
"Alignment of field: ",
stringify!(mpc_table),
"::",
stringify!(oemptr)
)
);
assert_eq!(
unsafe { &(*std::ptr::null::<mpc_table>()).oemsize as *const _ as usize },
32usize,
concat!(
"Alignment of field: ",
stringify!(mpc_table),
"::",
stringify!(oemsize)
)
);
assert_eq!(
unsafe { &(*std::ptr::null::<mpc_table>()).oemcount as *const _ as usize },
34usize,
concat!(
"Alignment of field: ",
stringify!(mpc_table),
"::",
stringify!(oemcount)
)
);
assert_eq!(
unsafe { &(*std::ptr::null::<mpc_table>()).lapic as *const _ as usize },
36usize,
concat!(
"Alignment of field: ",
stringify!(mpc_table),
"::",
stringify!(lapic)
)
);
assert_eq!(
unsafe { &(*std::ptr::null::<mpc_table>()).reserved as *const _ as usize },
40usize,
concat!(
"Alignment of field: ",
stringify!(mpc_table),
"::",
stringify!(reserved)
)
);
}
impl Clone for mpc_table {
fn clone(&self) -> Self {
*self
}
}
#[repr(C)]
#[derive(Debug, Default, Copy)]
pub struct mpc_cpu {
pub type_: ::std::os::raw::c_uchar,
pub apicid: ::std::os::raw::c_uchar,
pub apicver: ::std::os::raw::c_uchar,
pub cpuflag: ::std::os::raw::c_uchar,
pub cpufeature: ::std::os::raw::c_uint,
pub featureflag: ::std::os::raw::c_uint,
pub reserved: [::std::os::raw::c_uint; 2usize],
}
#[test]
fn bindgen_test_layout_mpc_cpu() {
assert_eq!(
::std::mem::size_of::<mpc_cpu>(),
20usize,
concat!("Size of: ", stringify!(mpc_cpu))
);
assert_eq!(
::std::mem::align_of::<mpc_cpu>(),
4usize,
concat!("Alignment of ", stringify!(mpc_cpu))
);
assert_eq!(
unsafe { &(*std::ptr::null::<mpc_cpu>()).type_ as *const _ as usize },
0usize,
concat!(
"Alignment of field: ",
stringify!(mpc_cpu),
"::",
stringify!(type_)
)
);
assert_eq!(
unsafe { &(*std::ptr::null::<mpc_cpu>()).apicid as *const _ as usize },
1usize,
concat!(
"Alignment of field: ",
stringify!(mpc_cpu),
"::",
stringify!(apicid)
)
);
assert_eq!(
unsafe { &(*std::ptr::null::<mpc_cpu>()).apicver as *const _ as usize },
2usize,
concat!(
"Alignment of field: ",
stringify!(mpc_cpu),
"::",
stringify!(apicver)
)
);
assert_eq!(
unsafe { &(*std::ptr::null::<mpc_cpu>()).cpuflag as *const _ as usize },
3usize,
concat!(
"Alignment of field: ",
stringify!(mpc_cpu),
"::",
stringify!(cpuflag)
)
);
assert_eq!(
unsafe { &(*std::ptr::null::<mpc_cpu>()).cpufeature as *const _ as usize },
4usize,
concat!(
"Alignment of field: ",
stringify!(mpc_cpu),
"::",
stringify!(cpufeature)
)
);
assert_eq!(
unsafe { &(*std::ptr::null::<mpc_cpu>()).featureflag as *const _ as usize },
8usize,
concat!(
"Alignment of field: ",
stringify!(mpc_cpu),
"::",
stringify!(featureflag)
)
);
assert_eq!(
unsafe { &(*std::ptr::null::<mpc_cpu>()).reserved as *const _ as usize },
12usize,
concat!(
"Alignment of field: ",
stringify!(mpc_cpu),
"::",
stringify!(reserved)
)
);
}
impl Clone for mpc_cpu {
fn clone(&self) -> Self {
*self
}
}
#[repr(C)]
#[derive(Debug, Default, Copy)]
pub struct mpc_bus {
pub type_: ::std::os::raw::c_uchar,
pub busid: ::std::os::raw::c_uchar,
pub bustype: [::std::os::raw::c_uchar; 6usize],
}
#[test]
fn bindgen_test_layout_mpc_bus() {
assert_eq!(
::std::mem::size_of::<mpc_bus>(),
8usize,
concat!("Size of: ", stringify!(mpc_bus))
);
assert_eq!(
::std::mem::align_of::<mpc_bus>(),
1usize,
concat!("Alignment of ", stringify!(mpc_bus))
);
assert_eq!(
unsafe { &(*std::ptr::null::<mpc_bus>()).type_ as *const _ as usize },
0usize,
concat!(
"Alignment of field: ",
stringify!(mpc_bus),
"::",
stringify!(type_)
)
);
assert_eq!(
unsafe { &(*std::ptr::null::<mpc_bus>()).busid as *const _ as usize },
1usize,
concat!(
"Alignment of field: ",
stringify!(mpc_bus),
"::",
stringify!(busid)
)
);
assert_eq!(
unsafe { &(*std::ptr::null::<mpc_bus>()).bustype as *const _ as usize },
2usize,
concat!(
"Alignment of field: ",
stringify!(mpc_bus),
"::",
stringify!(bustype)
)
);
}
impl Clone for mpc_bus {
fn clone(&self) -> Self {
*self
}
}
#[repr(C)]
#[derive(Debug, Default, Copy)]
pub struct mpc_ioapic {
pub type_: ::std::os::raw::c_uchar,
pub apicid: ::std::os::raw::c_uchar,
pub apicver: ::std::os::raw::c_uchar,
pub flags: ::std::os::raw::c_uchar,
pub apicaddr: ::std::os::raw::c_uint,
}
#[test]
fn bindgen_test_layout_mpc_ioapic() {
assert_eq!(
::std::mem::size_of::<mpc_ioapic>(),
8usize,
concat!("Size of: ", stringify!(mpc_ioapic))
);
assert_eq!(
::std::mem::align_of::<mpc_ioapic>(),
4usize,
concat!("Alignment of ", stringify!(mpc_ioapic))
);
assert_eq!(
unsafe { &(*std::ptr::null::<mpc_ioapic>()).type_ as *const _ as usize },
0usize,
concat!(
"Alignment of field: ",
stringify!(mpc_ioapic),
"::",
stringify!(type_)
)
);
assert_eq!(
unsafe { &(*std::ptr::null::<mpc_ioapic>()).apicid as *const _ as usize },
1usize,
concat!(
"Alignment of field: ",
stringify!(mpc_ioapic),
"::",
stringify!(apicid)
)
);
assert_eq!(
unsafe { &(*std::ptr::null::<mpc_ioapic>()).apicver as *const _ as usize },
2usize,
concat!(
"Alignment of field: ",
stringify!(mpc_ioapic),
"::",
stringify!(apicver)
)
);
assert_eq!(
unsafe { &(*std::ptr::null::<mpc_ioapic>()).flags as *const _ as usize },
3usize,
concat!(
"Alignment of field: ",
stringify!(mpc_ioapic),
"::",
stringify!(flags)
)
);
assert_eq!(
unsafe { &(*std::ptr::null::<mpc_ioapic>()).apicaddr as *const _ as usize },
4usize,
concat!(
"Alignment of field: ",
stringify!(mpc_ioapic),
"::",
stringify!(apicaddr)
)
);
}
impl Clone for mpc_ioapic {
fn clone(&self) -> Self {
*self
}
}
#[repr(C)]
#[derive(Debug, Default, Copy)]
pub struct mpc_intsrc {
pub type_: ::std::os::raw::c_uchar,
pub irqtype: ::std::os::raw::c_uchar,
pub irqflag: ::std::os::raw::c_ushort,
pub srcbus: ::std::os::raw::c_uchar,
pub srcbusirq: ::std::os::raw::c_uchar,
pub dstapic: ::std::os::raw::c_uchar,
pub dstirq: ::std::os::raw::c_uchar,
}
#[test]
fn bindgen_test_layout_mpc_intsrc() {
assert_eq!(
::std::mem::size_of::<mpc_intsrc>(),
8usize,
concat!("Size of: ", stringify!(mpc_intsrc))
);
assert_eq!(
::std::mem::align_of::<mpc_intsrc>(),
2usize,
concat!("Alignment of ", stringify!(mpc_intsrc))
);
assert_eq!(
unsafe { &(*std::ptr::null::<mpc_intsrc>()).type_ as *const _ as usize },
0usize,
concat!(
"Alignment of field: ",
stringify!(mpc_intsrc),
"::",
stringify!(type_)
)
);
assert_eq!(
unsafe { &(*std::ptr::null::<mpc_intsrc>()).irqtype as *const _ as usize },
1usize,
concat!(
"Alignment of field: ",
stringify!(mpc_intsrc),
"::",
stringify!(irqtype)
)
);
assert_eq!(
unsafe { &(*std::ptr::null::<mpc_intsrc>()).irqflag as *const _ as usize },
2usize,
concat!(
"Alignment of field: ",
stringify!(mpc_intsrc),
"::",
stringify!(irqflag)
)
);
assert_eq!(
unsafe { &(*std::ptr::null::<mpc_intsrc>()).srcbus as *const _ as usize },
4usize,
concat!(
"Alignment of field: ",
stringify!(mpc_intsrc),
"::",
stringify!(srcbus)
)
);
assert_eq!(
unsafe { &(*std::ptr::null::<mpc_intsrc>()).srcbusirq as *const _ as usize },
5usize,
concat!(
"Alignment of field: ",
stringify!(mpc_intsrc),
"::",
stringify!(srcbusirq)
)
);
assert_eq!(
unsafe { &(*std::ptr::null::<mpc_intsrc>()).dstapic as *const _ as usize },
6usize,
concat!(
"Alignment of field: ",
stringify!(mpc_intsrc),
"::",
stringify!(dstapic)
)
);
assert_eq!(
unsafe { &(*std::ptr::null::<mpc_intsrc>()).dstirq as *const _ as usize },
7usize,
concat!(
"Alignment of field: ",
stringify!(mpc_intsrc),
"::",
stringify!(dstirq)
)
);
}
impl Clone for mpc_intsrc {
fn clone(&self) -> Self {
*self
}
}
pub const mp_irq_source_types_mp_INT: mp_irq_source_types = 0;
pub const mp_irq_source_types_mp_NMI: mp_irq_source_types = 1;
pub const mp_irq_source_types_mp_SMI: mp_irq_source_types = 2;
pub const mp_irq_source_types_mp_ExtINT: mp_irq_source_types = 3;
pub type mp_irq_source_types = ::std::os::raw::c_uint;
#[repr(C)]
#[derive(Debug, Default, Copy)]
pub struct mpc_lintsrc {
pub type_: ::std::os::raw::c_uchar,
pub irqtype: ::std::os::raw::c_uchar,
pub irqflag: ::std::os::raw::c_ushort,
pub srcbusid: ::std::os::raw::c_uchar,
pub srcbusirq: ::std::os::raw::c_uchar,
pub destapic: ::std::os::raw::c_uchar,
pub destapiclint: ::std::os::raw::c_uchar,
}
#[test]
fn bindgen_test_layout_mpc_lintsrc() {
assert_eq!(
::std::mem::size_of::<mpc_lintsrc>(),
8usize,
concat!("Size of: ", stringify!(mpc_lintsrc))
);
assert_eq!(
::std::mem::align_of::<mpc_lintsrc>(),
2usize,
concat!("Alignment of ", stringify!(mpc_lintsrc))
);
assert_eq!(
unsafe { &(*std::ptr::null::<mpc_lintsrc>()).type_ as *const _ as usize },
0usize,
concat!(
"Alignment of field: ",
stringify!(mpc_lintsrc),
"::",
stringify!(type_)
)
);
assert_eq!(
unsafe { &(*std::ptr::null::<mpc_lintsrc>()).irqtype as *const _ as usize },
1usize,
concat!(
"Alignment of field: ",
stringify!(mpc_lintsrc),
"::",
stringify!(irqtype)
)
);
assert_eq!(
unsafe { &(*std::ptr::null::<mpc_lintsrc>()).irqflag as *const _ as usize },
2usize,
concat!(
"Alignment of field: ",
stringify!(mpc_lintsrc),
"::",
stringify!(irqflag)
)
);
assert_eq!(
unsafe { &(*std::ptr::null::<mpc_lintsrc>()).srcbusid as *const _ as usize },
4usize,
concat!(
"Alignment of field: ",
stringify!(mpc_lintsrc),
"::",
stringify!(srcbusid)
)
);
assert_eq!(
unsafe { &(*std::ptr::null::<mpc_lintsrc>()).srcbusirq as *const _ as usize },
5usize,
concat!(
"Alignment of field: ",
stringify!(mpc_lintsrc),
"::",
stringify!(srcbusirq)
)
);
assert_eq!(
unsafe { &(*std::ptr::null::<mpc_lintsrc>()).destapic as *const _ as usize },
6usize,
concat!(
"Alignment of field: ",
stringify!(mpc_lintsrc),
"::",
stringify!(destapic)
)
);
assert_eq!(
unsafe { &(*std::ptr::null::<mpc_lintsrc>()).destapiclint as *const _ as usize },
7usize,
concat!(
"Alignment of field: ",
stringify!(mpc_lintsrc),
"::",
stringify!(destapiclint)
)
);
}
impl Clone for mpc_lintsrc {
fn clone(&self) -> Self {
*self
}
}
#[repr(C)]
#[derive(Debug, Default, Copy)]
pub struct mpc_oemtable {
pub signature: [::std::os::raw::c_char; 4usize],
pub length: ::std::os::raw::c_ushort,
pub rev: ::std::os::raw::c_char,
pub checksum: ::std::os::raw::c_char,
pub mpc: [::std::os::raw::c_char; 8usize],
}
#[test]
fn bindgen_test_layout_mpc_oemtable() {
assert_eq!(
::std::mem::size_of::<mpc_oemtable>(),
16usize,
concat!("Size of: ", stringify!(mpc_oemtable))
);
assert_eq!(
::std::mem::align_of::<mpc_oemtable>(),
2usize,
concat!("Alignment of ", stringify!(mpc_oemtable))
);
assert_eq!(
unsafe { &(*std::ptr::null::<mpc_oemtable>()).signature as *const _ as usize },
0usize,
concat!(
"Alignment of field: ",
stringify!(mpc_oemtable),
"::",
stringify!(signature)
)
);
assert_eq!(
unsafe { &(*std::ptr::null::<mpc_oemtable>()).length as *const _ as usize },
4usize,
concat!(
"Alignment of field: ",
stringify!(mpc_oemtable),
"::",
stringify!(length)
)
);
assert_eq!(
unsafe { &(*std::ptr::null::<mpc_oemtable>()).rev as *const _ as usize },
6usize,
concat!(
"Alignment of field: ",
stringify!(mpc_oemtable),
"::",
stringify!(rev)
)
);
assert_eq!(
unsafe { &(*std::ptr::null::<mpc_oemtable>()).checksum as *const _ as usize },
7usize,
concat!(
"Alignment of field: ",
stringify!(mpc_oemtable),
"::",
stringify!(checksum)
)
);
assert_eq!(
unsafe { &(*std::ptr::null::<mpc_oemtable>()).mpc as *const _ as usize },
8usize,
concat!(
"Alignment of field: ",
stringify!(mpc_oemtable),
"::",
stringify!(mpc)
)
);
}
impl Clone for mpc_oemtable {
fn clone(&self) -> Self {
*self
}
}
pub const mp_bustype_MP_BUS_ISA: mp_bustype = 1;
pub const mp_bustype_MP_BUS_EISA: mp_bustype = 2;
pub const mp_bustype_MP_BUS_PCI: mp_bustype = 3;
pub type mp_bustype = ::std::os::raw::c_uint;

View File

@@ -1,30 +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]
byteorder = { workspace = true }
crc-any = "2.5.0"
io-uring = { version = "0.7.10", 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 }

View File

@@ -1,111 +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),
}
pub type DiskFileResult<T> = std::result::Result<T, DiskFileError>;
/// A wrapper for [`RawFd`] capturing the lifetime of a corresponding [`DiskFile`].
///
/// This fulfills the same role as [`BorrowedFd`] but is tailored to the limitations
/// by some implementations of [`DiskFile`], which wrap the effective [`File`]
/// in an `Arc<Mutex<T>>`, making the use of [`BorrowedFd`] impossible.
///
/// [`BorrowedFd`]: std::os::fd::BorrowedFd
#[derive(Copy, Clone, Debug)]
pub struct BorrowedDiskFd<'fd> {
raw_fd: RawFd,
_lifetime: PhantomData<&'fd OwnedFd>,
}
impl BorrowedDiskFd<'_> {
pub(super) fn new(raw_fd: RawFd) -> Self {
Self {
raw_fd,
_lifetime: PhantomData,
}
}
}
impl AsRawFd for BorrowedDiskFd<'_> {
fn as_raw_fd(&self) -> RawFd {
self.raw_fd
}
}
/// Abstraction over the effective [`File`] backing up a block device,
/// with support for synchronous and asynchronous I/O.
///
/// This allows abstracting over raw image formats as well as structured
/// image formats.
pub trait DiskFile: Send {
fn size(&mut self) -> DiskFileResult<u64>;
fn new_async_io(&self, ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>>;
fn topology(&mut self) -> DiskTopology {
DiskTopology::default()
}
/// Returns the file descriptor of the underlying disk image file.
///
/// The file descriptor is supposed to be used for `fcntl()` calls but no
/// other operation.
fn fd(&mut self) -> BorrowedDiskFd<'_>;
}
#[derive(Error, Debug)]
pub enum AsyncIoError {
/// Failed vectored reading from file.
#[error("Failed vectored reading from file")]
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 submitting batch requests.
#[error("Failed submitting batch requests: {0}")]
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 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(())
}
}

View File

@@ -1,173 +0,0 @@
// Copyright © 2025 Cyberus Technology GmbH
//
// SPDX-License-Identifier: Apache-2.0
//
//! Helpers for advisory file locking.
//!
//! Under the hood, the implementation uses OFD locks for the entire file,
//! as described in [[0]]. The advantage over `F_SETLKW` (currently used by
//! Rust std: `File::try_lock()`) is that only the very last `close()` on a
//! file descriptor releases the lock. This prevents mistakes and unexpected
//! behavior.
//!
//! [0]: <https://apenwarr.ca/log/20101213>.
use std::fmt::Debug;
use std::io;
use std::os::fd::{AsRawFd, RawFd};
use thiserror::Error;
/// Errors that can happen when working with file locks.
#[derive(Error, Debug)]
pub enum LockError {
/// The file is already locked.
///
/// A call to [`get_lock_state`] can help to identify the reason.
#[error("The file is already locked")]
AlreadyLocked,
/// IO error.
#[error("The lock state could not be checked or set")]
Io(#[source] io::Error),
}
/// Commands for use with [`fcntl`].
#[allow(non_camel_case_types)]
enum FcntlArg<'a> {
/// Set an OFD lock from the given lock description.
F_OFD_SETLK(&'a libc::flock),
/// Get the first OFD lock for the given lock description.
F_OFD_GETLK(&'a mut libc::flock),
}
/// Wrapper for [`libc::fcntl`] that properly sets the function arguments.
fn fcntl(fd: RawFd, arg: FcntlArg) -> libc::c_int {
// SAFETY: We use a valid FD.
unsafe {
match arg {
FcntlArg::F_OFD_SETLK(flock) => libc::fcntl(fd, libc::F_OFD_SETLK, flock),
FcntlArg::F_OFD_GETLK(flock) => libc::fcntl(fd, libc::F_OFD_GETLK, flock),
}
}
}
/// Describes the type of lock you want to set.
#[derive(Clone, Copy, Debug)]
pub enum LockType {
/// Clear a lock.
Unlock,
/// Set a write lock (exclusive).
Write,
/// Set a read lock (shared).
Read,
}
impl LockType {
pub const fn to_libc_val(self) -> libc::c_int {
match self {
Self::Unlock => libc::F_UNLCK as libc::c_int,
Self::Write => libc::F_WRLCK as libc::c_int,
Self::Read => libc::F_RDLCK as libc::c_int,
}
}
}
/// Describes the current state of a lock.
#[derive(Debug)]
pub enum LockState {
/// No lock set.
Unlocked,
/// Locked for reading (non-exclusive).
SharedRead,
/// Locked for writing (exclusive mode).
ExclusiveWrite,
}
impl LockState {
fn new(value: libc::c_int) -> Self {
const F_UNLCK: libc::c_int = libc::F_UNLCK as libc::c_int;
const F_WRLCK: libc::c_int = libc::F_WRLCK as libc::c_int;
const F_RDLCK: libc::c_int = libc::F_RDLCK as libc::c_int;
match value {
F_UNLCK => Self::Unlocked,
F_WRLCK => Self::ExclusiveWrite,
F_RDLCK => Self::SharedRead,
// This is so unlikely that we want to avoid the complexity of
// coping with this error case. Can only fail if either Linux
// is broken or memory is messed up.
other => panic!("Unexpected lock state: {other}"),
}
}
}
/// Returns a [`struct@libc::flock`] structure for the whole file.
const fn get_flock(lock_type: LockType) -> libc::flock {
libc::flock {
l_type: lock_type.to_libc_val() as libc::c_short,
l_whence: libc::SEEK_SET as libc::c_short,
l_start: 0,
l_len: 0, /* EOF */
l_pid: 0, /* filled by callee */
}
}
/// Tries to acquire a lock using [`fcntl`] with respect to the given
/// parameters.
///
/// Please note that `fcntl()` OFD locks are **advisory locks**, which do not
/// prevent to `open()` a file if a lock is already placed.
///
/// # Parameters
/// - `file`: The file to acquire a lock for [`LockType`]. The file's state will
/// be logically mutated, but not technically.
/// - `lock_type`: The [`LockType`]
pub fn try_acquire_lock<Fd: AsRawFd>(file: Fd, lock_type: LockType) -> Result<(), LockError> {
let flock = get_flock(lock_type);
let res = fcntl(file.as_raw_fd(), FcntlArg::F_OFD_SETLK(&flock));
match res {
0 => Ok(()),
-1 => {
let io_error = io::Error::last_os_error();
let errno = io_error.raw_os_error().unwrap();
match errno {
// See man page for error code:
// <https://man7.org/linux/man-pages/man2/fcntl.2.html>
libc::EAGAIN | libc::EACCES => Err(LockError::AlreadyLocked),
_ => Err(LockError::Io(io_error)),
}
}
val => panic!("Unexpected return value from fcntl(): {val}"),
}
}
/// Clears a lock.
///
/// # Parameters
/// - `file`: The file to clear all locks for [`LockType`].
pub fn clear_lock<Fd: AsRawFd>(file: Fd) -> Result<(), LockError> {
try_acquire_lock(file, LockType::Unlock)
}
/// Returns the current lock state using [`fcntl`] with respect to the given
/// parameters.
///
/// # Parameters
/// - `file`: The file for which to get the lock state.
pub fn get_lock_state<Fd: AsRawFd>(file: Fd) -> Result<LockState, LockError> {
let mut flock = get_flock(LockType::Write);
let res = fcntl(file.as_raw_fd(), FcntlArg::F_OFD_GETLK(&mut flock));
match res {
0 => {
let state = flock.l_type as libc::c_int;
let state = LockState::new(state);
Ok(state)
}
-1 => {
let io_error = io::Error::last_os_error();
Err(LockError::Io(io_error))
}
val => panic!("Unexpected return value from fcntl(): {val}"),
}
}

View File

@@ -1,91 +0,0 @@
// Copyright © 2021 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
use std::fs::File;
use std::io::{Read, Seek, SeekFrom, Write};
use std::os::unix::io::{AsRawFd, RawFd};
use crate::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 size(&self) -> std::result::Result<u64, crate::Error> {
Ok(self.size)
}
}
impl Clone for FixedVhd {
fn clone(&self) -> Self {
Self {
file: self.file.try_clone().expect("FixedVhd cloning failed"),
size: self.size,
position: self.position,
}
}
}

View File

@@ -1,922 +0,0 @@
// 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 fcntl;
pub mod fixed_vhd;
#[cfg(feature = "io_uring")]
/// Enabled with the `"io_uring"` feature
pub mod fixed_vhd_async;
pub mod fixed_vhd_sync;
pub mod qcow;
pub mod qcow_sync;
#[cfg(feature = "io_uring")]
/// Async primitives based on `io-uring`
///
/// Enabled with the `"io_uring"` feature
pub mod raw_async;
pub mod raw_async_aio;
pub mod raw_sync;
pub mod vhd;
pub mod vhdx;
pub mod vhdx_sync;
use std::alloc::{Layout, alloc_zeroed, dealloc};
use std::collections::VecDeque;
use std::fmt::Debug;
use std::fs::File;
use std::io::{self, IoSlice, IoSliceMut, Read, Seek, SeekFrom, Write};
use std::os::linux::fs::MetadataExt;
use std::os::unix::io::AsRawFd;
use std::path::Path;
use std::sync::Arc;
use std::time::Instant;
use std::{cmp, result};
#[cfg(feature = "io_uring")]
use io_uring::{IoUring, Probe, opcode};
use libc::{S_IFBLK, S_IFMT, ioctl};
use serde::{Deserialize, Serialize};
use smallvec::SmallVec;
use thiserror::Error;
use virtio_bindings::virtio_blk::*;
use virtio_queue::DescriptorChain;
use vm_memory::bitmap::Bitmap;
use vm_memory::{
ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryError, GuestMemoryLoadGuard,
};
use vm_virtio::{AccessPlatform, Translatable};
use vmm_sys_util::eventfd::EventFd;
use vmm_sys_util::{aio, ioctl_io_nr};
use crate::async_io::{AsyncIo, AsyncIoError, AsyncIoResult};
use crate::vhdx::VhdxError;
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(#[source] GuestMemoryError),
#[error("Guest gave us offsets that would have overflowed a usize")]
CheckedOffset(GuestAddress, usize /* sector offset */),
#[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("Failed to detect image type")]
DetectImageType(#[source] std::io::Error),
#[error("Failure in fixed vhd")]
FixedVhdError(#[source] std::io::Error),
#[error("Getting a block's metadata fails for any reason")]
GetFileMetadata,
#[error("The requested operation would cause a seek beyond disk end")]
InvalidOffset,
#[error("Failure in qcow")]
QcowError(#[source] qcow::Error),
#[error("Failure in raw file")]
RawFileError(#[source] std::io::Error),
#[error("The requested operation does not support multiple descriptors")]
TooManyDescriptors,
#[error("Failure in vhdx")]
VhdxError(#[source] VhdxError),
}
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_serial(disk_path: &Path) -> Vec<u8> {
let mut default_serial = 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_serial[..bytes_to_copy].clone_from_slice(&disk_id[..bytes_to_copy])
}
}
default_serial
}
#[derive(Error, Debug)]
pub enum ExecuteError {
#[error("Bad request")]
BadRequest(#[source] Error),
#[error("Failed to flush")]
Flush(#[source] io::Error),
#[error("Failed to read")]
Read(#[source] GuestMemoryError),
#[error("Failed to read_exact")]
ReadExact(#[source] io::Error),
#[error("Can't execute an operation other than `read` on a read-only device")]
ReadOnly,
#[error("Failed to seek")]
Seek(#[source] io::Error),
#[error("Failed to write")]
Write(#[source] GuestMemoryError),
#[error("Failed to write_all")]
WriteAll(#[source] io::Error),
#[error("Unsupported request: {0}")]
Unsupported(u32),
#[error("Failed to submit io uring")]
SubmitIoUring(#[source] io::Error),
#[error("Failed to get guest address")]
GetHostAddress(#[source] GuestMemoryError),
#[error("Failed to async read")]
AsyncRead(#[source] AsyncIoError),
#[error("Failed to async write")]
AsyncWrite(#[source] AsyncIoError),
#[error("failed to async flush")]
AsyncFlush(#[source] AsyncIoError),
#[error("Failed allocating a temporary buffer")]
TemporaryBufferAllocation(#[source] io::Error),
}
impl ExecuteError {
pub fn status(&self) -> u8 {
let status = match *self {
ExecuteError::BadRequest(_) => VIRTIO_BLK_S_IOERR,
ExecuteError::Flush(_) => VIRTIO_BLK_S_IOERR,
ExecuteError::Read(_) => VIRTIO_BLK_S_IOERR,
ExecuteError::ReadExact(_) => VIRTIO_BLK_S_IOERR,
ExecuteError::ReadOnly => VIRTIO_BLK_S_IOERR,
ExecuteError::Seek(_) => VIRTIO_BLK_S_IOERR,
ExecuteError::Write(_) => VIRTIO_BLK_S_IOERR,
ExecuteError::WriteAll(_) => 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,
};
status as u8
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum RequestType {
In,
Out,
Flush,
GetDeviceId,
Unsupported(u32),
}
pub fn request_type<B: Bitmap + 'static>(
mem: &vm_memory::GuestMemoryMmap<B>,
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<B: Bitmap + 'static>(
mem: &vm_memory::GuestMemoryMmap<B>,
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)
}
const DEFAULT_DESCRIPTOR_VEC_SIZE: usize = 32;
#[derive(Debug)]
pub struct AlignedOperation {
origin_ptr: u64,
aligned_ptr: u64,
size: usize,
layout: Layout,
}
pub struct BatchRequest {
pub offset: libc::off_t,
pub iovecs: SmallVec<[libc::iovec; DEFAULT_DESCRIPTOR_VEC_SIZE]>,
pub user_data: u64,
pub request_type: RequestType,
}
pub struct ExecuteAsync {
// `true` if the execution will complete asynchronously
pub async_complete: bool,
// request need to be batched for submission if any
pub batch_request: Option<BatchRequest>,
}
#[derive(Debug)]
pub struct Request {
pub request_type: RequestType,
pub sector: u64,
pub data_descriptors: SmallVec<[(GuestAddress, u32); DEFAULT_DESCRIPTOR_VEC_SIZE]>,
pub status_addr: GuestAddress,
pub writeback: bool,
pub aligned_operations: SmallVec<[AlignedOperation; DEFAULT_DESCRIPTOR_VEC_SIZE]>,
pub start: Instant,
}
impl Request {
pub fn parse<B: Bitmap + 'static>(
desc_chain: &mut DescriptorChain<GuestMemoryLoadGuard<vm_memory::GuestMemoryMmap<B>>>,
access_platform: Option<&Arc<dyn AccessPlatform>>,
) -> result::Result<Request, Error> {
let hdr_desc = desc_chain
.next()
.ok_or(Error::DescriptorChainTooShort)
.inspect_err(|_| {
error!("Missing head descriptor");
})?;
// 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(DEFAULT_DESCRIPTOR_VEC_SIZE),
status_addr: GuestAddress(0),
writeback: true,
aligned_operations: SmallVec::with_capacity(DEFAULT_DESCRIPTOR_VEC_SIZE),
start: Instant::now(),
};
let status_desc;
let mut desc = desc_chain
.next()
.ok_or(Error::DescriptorChainTooShort)
.inspect_err(|_| {
error!("Only head descriptor present: request = {:?}", req);
})?;
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)
.inspect_err(|_| {
error!("DescriptorChain corrupted: request = {:?}", req);
})?;
}
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, B: Bitmap + 'static>(
&self,
disk: &mut T,
disk_nsectors: u64,
mem: &vm_memory::GuestMemoryMmap<B>,
serial: &[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 => {
let mut buf = vec![0u8; *data_len as usize];
disk.read_exact(&mut buf).map_err(ExecuteError::ReadExact)?;
mem.read_exact_volatile_from(
*data_addr,
&mut buf.as_slice(),
*data_len as usize,
)
.map_err(ExecuteError::Read)?;
len += data_len;
}
RequestType::Out => {
let mut buf: Vec<u8> = Vec::new();
mem.write_all_volatile_to(*data_addr, &mut buf, *data_len as usize)
.map_err(ExecuteError::Write)?;
disk.write_all(&buf).map_err(ExecuteError::WriteAll)?;
if !self.writeback {
disk.flush().map_err(ExecuteError::Flush)?;
}
}
RequestType::Flush => disk.flush().map_err(ExecuteError::Flush)?,
RequestType::GetDeviceId => {
if (*data_len as usize) < serial.len() {
return Err(ExecuteError::BadRequest(Error::InvalidOffset));
}
mem.write_slice(serial, *data_addr)
.map_err(ExecuteError::Write)?;
}
RequestType::Unsupported(t) => return Err(ExecuteError::Unsupported(t)),
};
}
Ok(len)
}
pub fn execute_async<B: Bitmap + 'static>(
&mut self,
mem: &vm_memory::GuestMemoryMmap<B>,
disk_nsectors: u64,
disk_image: &mut dyn AsyncIo,
serial: &[u8],
user_data: u64,
) -> result::Result<ExecuteAsync, 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; DEFAULT_DESCRIPTOR_VEC_SIZE]> =
SmallVec::with_capacity(self.data_descriptors.len());
for &(data_addr, data_len) in &self.data_descriptors {
let _: u32 = data_len; // compiler-checked documentation
const _: () = assert!(
core::mem::size_of::<u32>() <= core::mem::size_of::<usize>(),
"unsupported platform"
);
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;
}
let data_len = data_len as usize;
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)
.map_err(ExecuteError::GetHostAddress)?
.ptr_guard();
// Verify the buffer alignment.
// In case it's not properly aligned, an intermediate buffer is
// created with the correct alignment, and a copy from/to the
// origin buffer is performed, depending on the type of operation.
let iov_base = if !(origin_ptr.as_ptr() as u64).is_multiple_of(SECTOR_SIZE) {
let layout = Layout::from_size_align(data_len, 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_ptr(), aligned_ptr, data_len) };
}
// Store both origin and aligned pointers for complete_async()
// to process them.
self.aligned_operations.push(AlignedOperation {
origin_ptr: origin_ptr.as_ptr() as u64,
aligned_ptr: aligned_ptr as u64,
size: data_len,
layout,
});
aligned_ptr as *mut libc::c_void
} else {
origin_ptr.as_ptr() as *mut libc::c_void
};
let iovec = libc::iovec {
iov_base,
iov_len: data_len as libc::size_t,
};
iovecs.push(iovec);
}
let mut ret = ExecuteAsync {
async_complete: true,
batch_request: None,
};
// 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);
}
if disk_image.batch_requests_enabled() {
ret.batch_request = Some(BatchRequest {
offset,
iovecs,
user_data,
request_type,
});
} else {
disk_image
.read_vectored(offset, &iovecs, user_data)
.map_err(ExecuteError::AsyncRead)?;
}
}
RequestType::Out => {
if disk_image.batch_requests_enabled() {
ret.batch_request = Some(BatchRequest {
offset,
iovecs,
user_data,
request_type,
});
} else {
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) < serial.len() {
return Err(ExecuteError::BadRequest(Error::InvalidOffset));
}
mem.write_slice(serial, data_addr)
.map_err(ExecuteError::Write)?;
ret.async_complete = false;
return Ok(ret);
}
RequestType::Unsupported(t) => return Err(ExecuteError::Unsupported(t)),
}
Ok(ret)
}
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, Serialize, Deserialize)]
#[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, Serialize, Deserialize)]
#[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 aio can be used on the current system.
pub fn block_aio_is_supported() -> bool {
aio::IoContext::new(1).is_ok()
}
/// Check if io_uring for block device can be used on the current system, as
/// it correctly supports the expected io_uring features.
pub fn block_io_uring_is_supported() -> bool {
#[cfg(not(feature = "io_uring"))]
{
info!("io_uring is disabled by crate features");
false
}
#[cfg(feature = "io_uring")]
{
let error_msg = "io_uring not supported:";
// Check we can create an io_uring instance, which effectively verifies
// that io_uring_setup() syscall is supported.
let io_uring = match IoUring::new(1) {
Ok(io_uring) => io_uring,
Err(e) => {
info!("{} failed to create io_uring instance: {}", error_msg, e);
return false;
}
};
let submitter = io_uring.submitter();
let mut probe = Probe::new();
// Check we can register a probe to validate supported operations.
match submitter.register_probe(&mut probe) {
Ok(_) => {}
Err(e) => {
info!("{} failed to register a probe: {}", error_msg, e);
return false;
}
}
// Check IORING_OP_FSYNC is supported
if !probe.is_supported(opcode::Fsync::CODE) {
info!("{} IORING_OP_FSYNC operation not supported", error_msg);
return false;
}
// Check IORING_OP_READV is supported
if !probe.is_supported(opcode::Readv::CODE) {
info!("{} IORING_OP_READV operation not supported", error_msg);
return false;
}
// Check IORING_OP_WRITEV is supported
if !probe.is_supported(opcode::Writev::CODE) {
info!("{} IORING_OP_WRITEV operation not supported", error_msg);
return false;
}
true
}
}
pub trait AsyncAdaptor {
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<()>
where
Self: Read + Seek,
{
// Convert libc::iovec into IoSliceMut
let mut slices: SmallVec<[IoSliceMut; DEFAULT_DESCRIPTOR_VEC_SIZE]> =
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::<libc::iovec, &mut [u8]>(*iovec)
}));
}
let result = {
// Move the cursor to the right offset
self.seek(SeekFrom::Start(offset as u64))
.map_err(AsyncIoError::ReadVectored)?;
let mut r = 0;
for b in slices.iter_mut() {
r += self.read(b).map_err(AsyncIoError::ReadVectored)?;
}
r
};
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<()>
where
Self: Write + Seek,
{
// Convert libc::iovec into IoSlice
let mut slices: SmallVec<[IoSlice; DEFAULT_DESCRIPTOR_VEC_SIZE]> =
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::<libc::iovec, &mut [u8]>(*iovec)
}));
}
let result = {
// Move the cursor to the right offset
self.seek(SeekFrom::Start(offset as u64))
.map_err(AsyncIoError::WriteVectored)?;
let mut r = 0;
for b in slices.iter() {
r += self.write(b).map_err(AsyncIoError::WriteVectored)?;
}
r
};
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<()>
where
Self: Write,
{
let result: i32 = {
// Flush
self.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(())
}
}
pub enum ImageType {
FixedVhd,
Qcow2,
Raw,
Vhdx,
}
const QCOW_MAGIC: u32 = 0x5146_49fb;
const VHDX_SIGN: u64 = 0x656C_6966_7864_6876;
/// Read a block into memory aligned by the source block size (needed for O_DIRECT)
pub fn read_aligned_block_size(f: &mut File) -> std::io::Result<Vec<u8>> {
let blocksize = DiskTopology::probe(f)?.logical_block_size as usize;
// SAFETY: We are allocating memory that is naturally aligned (size = alignment) and we meet
// requirements for safety from Vec::from_raw_parts() as we are using the global allocator
// and transferring ownership of the memory.
let mut data = unsafe {
Vec::from_raw_parts(
alloc_zeroed(Layout::from_size_align_unchecked(blocksize, blocksize)),
blocksize,
blocksize,
)
};
f.read_exact(&mut data)?;
Ok(data)
}
/// Determine image type through file parsing.
pub fn detect_image_type(f: &mut File) -> std::io::Result<ImageType> {
let block = read_aligned_block_size(f)?;
// Check 4 first bytes to get the header value and determine the image type
let image_type = if u32::from_be_bytes(block[0..4].try_into().unwrap()) == QCOW_MAGIC {
ImageType::Qcow2
} else if vhd::is_fixed_vhd(f)? {
ImageType::FixedVhd
} else if u64::from_le_bytes(block[0..8].try_into().unwrap()) == VHDX_SIGN {
ImageType::Vhdx
} else {
ImageType::Raw
};
Ok(image_type)
}
pub trait BlockBackend: Read + Write + Seek + Send + Debug {
fn size(&self) -> Result<u64, Error>;
}
#[derive(Debug)]
pub struct DiskTopology {
pub logical_block_size: u64,
pub physical_block_size: u64,
pub minimum_io_size: u64,
pub optimal_io_size: u64,
}
impl Default for DiskTopology {
fn default() -> Self {
Self {
logical_block_size: 512,
physical_block_size: 512,
minimum_io_size: 512,
optimal_io_size: 0,
}
}
}
ioctl_io_nr!(BLKSSZGET, 0x12, 104);
ioctl_io_nr!(BLKPBSZGET, 0x12, 123);
ioctl_io_nr!(BLKIOMIN, 0x12, 120);
ioctl_io_nr!(BLKIOOPT, 0x12, 121);
enum BlockSize {
LogicalBlock,
PhysicalBlock,
MinimumIo,
OptimalIo,
}
impl DiskTopology {
fn is_block_device(f: &File) -> std::io::Result<bool> {
let mut stat = std::mem::MaybeUninit::<libc::stat>::uninit();
// SAFETY: FFI call with a valid fd and buffer
let ret = unsafe { libc::fstat(f.as_raw_fd(), stat.as_mut_ptr()) };
if ret != 0 {
return Err(std::io::Error::last_os_error());
}
// SAFETY: stat is valid at this point
let is_block = unsafe { (*stat.as_ptr()).st_mode & S_IFMT == S_IFBLK };
Ok(is_block)
}
// libc::ioctl() takes different types on different architectures
fn query_block_size(f: &File, block_size_type: BlockSize) -> std::io::Result<u64> {
let mut block_size = 0;
// SAFETY: FFI call with correct arguments
let ret = unsafe {
ioctl(
f.as_raw_fd(),
match block_size_type {
BlockSize::LogicalBlock => BLKSSZGET(),
BlockSize::PhysicalBlock => BLKPBSZGET(),
BlockSize::MinimumIo => BLKIOMIN(),
BlockSize::OptimalIo => BLKIOOPT(),
} as _,
&mut block_size,
)
};
if ret != 0 {
return Err(std::io::Error::last_os_error());
};
Ok(block_size)
}
pub fn probe(f: &File) -> std::io::Result<Self> {
if !Self::is_block_device(f)? {
return Ok(DiskTopology::default());
}
Ok(DiskTopology {
logical_block_size: Self::query_block_size(f, BlockSize::LogicalBlock)?,
physical_block_size: Self::query_block_size(f, BlockSize::PhysicalBlock)?,
minimum_io_size: Self::query_block_size(f, BlockSize::MinimumIo)?,
optimal_io_size: Self::query_block_size(f, BlockSize::OptimalIo)?,
})
}
}

View File

@@ -1,244 +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 vmm_sys_util::eventfd::EventFd;
use crate::async_io::{
AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult,
};
use crate::{BatchRequest, DiskTopology, RequestType};
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(&self.file) {
topology
} else {
warn!("Unable to get device topology. Using default topology");
DiskTopology::default()
}
}
fn fd(&mut self) -> BorrowedDiskFd<'_> {
BorrowedDiskFd::new(self.file.as_raw_fd())
}
}
pub struct 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(())
}
}

View File

@@ -1,156 +0,0 @@
// Copyright © 2023 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
//
// Copyright © 2023 Crusoe Energy Systems LLC
//
use std::fs::File;
use std::io::{Seek, SeekFrom};
use std::os::unix::io::{AsRawFd, RawFd};
use vmm_sys_util::aio;
use vmm_sys_util::eventfd::EventFd;
use crate::DiskTopology;
use crate::async_io::{
AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult,
};
pub struct RawFileDiskAio {
file: File,
}
impl RawFileDiskAio {
pub fn new(file: File) -> Self {
RawFileDiskAio { file }
}
}
impl DiskFile for RawFileDiskAio {
fn size(&mut self) -> DiskFileResult<u64> {
self.file
.seek(SeekFrom::End(0))
.map_err(DiskFileError::Size)
}
fn new_async_io(&self, ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
Ok(Box::new(
RawFileAsyncAio::new(self.file.as_raw_fd(), ring_depth)
.map_err(DiskFileError::NewAsyncIo)?,
) as Box<dyn AsyncIo>)
}
fn topology(&mut self) -> DiskTopology {
if let Ok(topology) = DiskTopology::probe(&self.file) {
topology
} else {
warn!("Unable to get device topology. Using default topology");
DiskTopology::default()
}
}
fn fd(&mut self) -> BorrowedDiskFd<'_> {
BorrowedDiskFd::new(self.file.as_raw_fd())
}
}
pub struct RawFileAsyncAio {
fd: RawFd,
ctx: aio::IoContext,
eventfd: EventFd,
}
impl RawFileAsyncAio {
pub fn new(fd: RawFd, queue_depth: u32) -> std::io::Result<Self> {
let eventfd = EventFd::new(libc::EFD_NONBLOCK)?;
let ctx = aio::IoContext::new(queue_depth)?;
Ok(RawFileAsyncAio { fd, ctx, eventfd })
}
}
impl AsyncIo for RawFileAsyncAio {
fn notifier(&self) -> &EventFd {
&self.eventfd
}
fn read_vectored(
&mut self,
offset: libc::off_t,
iovecs: &[libc::iovec],
user_data: u64,
) -> AsyncIoResult<()> {
let iocbs = [&mut aio::IoControlBlock {
aio_fildes: self.fd.as_raw_fd() as u32,
aio_lio_opcode: aio::IOCB_CMD_PREADV as u16,
aio_buf: iovecs.as_ptr() as u64,
aio_nbytes: iovecs.len() as u64,
aio_offset: offset,
aio_data: user_data,
aio_flags: aio::IOCB_FLAG_RESFD,
aio_resfd: self.eventfd.as_raw_fd() as u32,
..Default::default()
}];
let _ = self
.ctx
.submit(&iocbs[..])
.map_err(AsyncIoError::ReadVectored)?;
Ok(())
}
fn write_vectored(
&mut self,
offset: libc::off_t,
iovecs: &[libc::iovec],
user_data: u64,
) -> AsyncIoResult<()> {
let iocbs = [&mut aio::IoControlBlock {
aio_fildes: self.fd.as_raw_fd() as u32,
aio_lio_opcode: aio::IOCB_CMD_PWRITEV as u16,
aio_buf: iovecs.as_ptr() as u64,
aio_nbytes: iovecs.len() as u64,
aio_offset: offset,
aio_data: user_data,
aio_flags: aio::IOCB_FLAG_RESFD,
aio_resfd: self.eventfd.as_raw_fd() as u32,
..Default::default()
}];
let _ = self
.ctx
.submit(&iocbs[..])
.map_err(AsyncIoError::WriteVectored)?;
Ok(())
}
fn fsync(&mut self, user_data: Option<u64>) -> AsyncIoResult<()> {
if let Some(user_data) = user_data {
let iocbs = [&mut aio::IoControlBlock {
aio_fildes: self.fd.as_raw_fd() as u32,
aio_lio_opcode: aio::IOCB_CMD_FSYNC as u16,
aio_data: user_data,
aio_flags: aio::IOCB_FLAG_RESFD,
aio_resfd: self.eventfd.as_raw_fd() as u32,
..Default::default()
}];
let _ = self.ctx.submit(&iocbs[..]).map_err(AsyncIoError::Fsync)?;
} else {
// SAFETY: FFI call with a valid fd
unsafe { libc::fsync(self.fd) };
}
Ok(())
}
fn next_completed_request(&mut self) -> Option<(u64, i32)> {
let mut events: [aio::IoEvent; 1] = [aio::IoEvent::default()];
let rc = self.ctx.get_events(0, &mut events, None).unwrap();
if rc == 0 {
None
} else {
Some((events[0].data, events[0].res as i32))
}
}
}

View File

@@ -1,242 +0,0 @@
// Copyright © 2021 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
use std::collections::btree_map::BTreeMap;
use std::fs::File;
use std::io::{Read, Seek, SeekFrom, Write};
use std::os::fd::{AsRawFd, RawFd};
use byteorder::{BigEndian, ByteOrder};
use remain::sorted;
use thiserror::Error;
use uuid::Uuid;
use crate::BlockBackend;
use crate::vhdx::vhdx_bat::{BatEntry, VhdxBatError};
use crate::vhdx::vhdx_header::{RegionInfo, RegionTableEntry, VhdxHeader, VhdxHeaderError};
use crate::vhdx::vhdx_io::VhdxIoError;
use crate::vhdx::vhdx_metadata::{DiskSpec, VhdxMetadataError};
mod vhdx_bat;
mod vhdx_header;
mod vhdx_io;
mod vhdx_metadata;
#[sorted]
#[derive(Error, Debug)]
pub enum VhdxError {
#[error("Not a VHDx file")]
NotVhdx(#[source] VhdxHeaderError),
#[error("Failed to parse VHDx header")]
ParseVhdxHeader(#[source] VhdxHeaderError),
#[error("Failed to parse VHDx metadata")]
ParseVhdxMetadata(#[source] VhdxMetadataError),
#[error("Failed to parse VHDx region entries")]
ParseVhdxRegionEntry(#[source] VhdxHeaderError),
#[error("Failed reading metadata")]
ReadBatEntry(#[source] VhdxBatError),
#[error("Failed reading sector from disk")]
ReadFailed(#[source] VhdxIoError),
#[error("Failed writing to sector on disk")]
WriteFailed(#[source] VhdxIoError),
}
pub type Result<T> = std::result::Result<T, VhdxError>;
#[derive(Debug)]
pub struct Vhdx {
file: File,
vhdx_header: VhdxHeader,
region_entries: BTreeMap<u64, u64>,
bat_entry: RegionTableEntry,
mdr_entry: RegionTableEntry,
disk_spec: DiskSpec,
bat_entries: Vec<BatEntry>,
current_offset: u64,
first_write: bool,
}
impl Vhdx {
/// Parse the Vhdx header, BAT, and metadata from a file and store info
// in Vhdx structure.
pub fn new(mut file: File) -> Result<Vhdx> {
let vhdx_header = VhdxHeader::new(&mut file).map_err(VhdxError::ParseVhdxHeader)?;
let collected_entries = RegionInfo::new(
&mut file,
vhdx_header::REGION_TABLE_1_START,
vhdx_header.region_entry_count(),
)
.map_err(VhdxError::ParseVhdxRegionEntry)?;
let bat_entry = collected_entries.bat_entry;
let mdr_entry = collected_entries.mdr_entry;
let disk_spec =
DiskSpec::new(&mut file, &mdr_entry).map_err(VhdxError::ParseVhdxMetadata)?;
let bat_entries = BatEntry::collect_bat_entries(&mut file, &disk_spec, &bat_entry)
.map_err(VhdxError::ReadBatEntry)?;
Ok(Vhdx {
file,
vhdx_header,
region_entries: collected_entries.region_entries,
bat_entry,
mdr_entry,
disk_spec,
bat_entries,
current_offset: 0,
first_write: true,
})
}
pub fn virtual_disk_size(&self) -> u64 {
self.disk_spec.virtual_disk_size
}
}
impl Read for Vhdx {
/// Wrapper function to satisfy Read trait implementation for VHDx disk.
/// Convert the offset to sector index and buffer length to sector count.
fn read(&mut self, buf: &mut [u8]) -> std::result::Result<usize, std::io::Error> {
let sector_count = (buf.len() as u64).div_ceil(self.disk_spec.logical_sector_size as u64);
let sector_index = self.current_offset / self.disk_spec.logical_sector_size as u64;
let result = vhdx_io::read(
&mut self.file,
buf,
&self.disk_spec,
&self.bat_entries,
sector_index,
sector_count,
)
.map_err(|e| {
std::io::Error::other(format!(
"Failed reading {sector_count} sectors from VHDx at index {sector_index}: {e}"
))
})?;
self.current_offset = self.current_offset.checked_add(result as u64).unwrap();
Ok(result)
}
}
impl Write for Vhdx {
fn flush(&mut self) -> std::result::Result<(), std::io::Error> {
self.file.flush()
}
/// Wrapper function to satisfy Write trait implementation for VHDx disk.
/// Convert the offset to sector index and buffer length to sector count.
fn write(&mut self, buf: &[u8]) -> std::result::Result<usize, std::io::Error> {
let sector_count = (buf.len() as u64).div_ceil(self.disk_spec.logical_sector_size as u64);
let sector_index = self.current_offset / self.disk_spec.logical_sector_size as u64;
if self.first_write {
self.first_write = false;
self.vhdx_header
.update(&mut self.file)
.map_err(|e| std::io::Error::other(format!("Failed to update VHDx header: {e}")))?;
}
let result = vhdx_io::write(
&mut self.file,
buf,
&mut self.disk_spec,
self.bat_entry.file_offset,
&mut self.bat_entries,
sector_index,
sector_count,
)
.map_err(|e| {
std::io::Error::other(format!(
"Failed writing {sector_count} sectors on VHDx at index {sector_index}: {e}"
))
})?;
self.current_offset = self.current_offset.checked_add(result as u64).unwrap();
Ok(result)
}
}
impl Seek for Vhdx {
/// Wrapper function to satisfy Seek trait implementation for VHDx disk.
/// Updates the offset field in the Vhdx struct.
fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
let new_offset: Option<u64> = match pos {
SeekFrom::Start(off) => Some(off),
SeekFrom::End(off) => {
if off < 0 {
0i64.checked_sub(off).and_then(|increment| {
self.virtual_disk_size().checked_sub(increment as u64)
})
} else {
self.virtual_disk_size().checked_add(off as u64)
}
}
SeekFrom::Current(off) => {
if off < 0 {
0i64.checked_sub(off)
.and_then(|increment| self.current_offset.checked_sub(increment as u64))
} else {
self.current_offset.checked_add(off as u64)
}
}
};
if let Some(o) = new_offset
&& o <= self.virtual_disk_size()
{
self.current_offset = o;
return Ok(o);
}
Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"Failed seek operation",
))
}
}
impl BlockBackend for Vhdx {
fn size(&self) -> std::result::Result<u64, crate::Error> {
Ok(self.virtual_disk_size())
}
}
impl Clone for Vhdx {
fn clone(&self) -> Self {
Vhdx {
file: self.file.try_clone().unwrap(),
vhdx_header: self.vhdx_header.clone(),
region_entries: self.region_entries.clone(),
bat_entry: self.bat_entry,
mdr_entry: self.mdr_entry,
disk_spec: self.disk_spec.clone(),
bat_entries: self.bat_entries.clone(),
current_offset: self.current_offset,
first_write: self.first_write,
}
}
}
impl AsRawFd for Vhdx {
fn as_raw_fd(&self) -> RawFd {
self.file.as_raw_fd()
}
}
pub(crate) fn uuid_from_guid(buf: &[u8]) -> Uuid {
// The first 3 fields of UUID are stored in Big Endian format, and
// the last 8 bytes are stored as byte array. Therefore, we read the
// first 3 fields in Big Endian format instead of Little Endian.
Uuid::from_fields_le(
BigEndian::read_u32(&buf[0..4]),
BigEndian::read_u16(&buf[4..6]),
BigEndian::read_u16(&buf[6..8]),
buf[8..16].try_into().unwrap(),
)
}

View File

@@ -1,106 +0,0 @@
// Copyright © 2021 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
use std::fs::File;
use std::io::{self, Seek, SeekFrom};
use std::mem::size_of;
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use remain::sorted;
use thiserror::Error;
use crate::vhdx::vhdx_header::RegionTableEntry;
use crate::vhdx::vhdx_metadata::DiskSpec;
// Payload BAT Entry States
pub const PAYLOAD_BLOCK_NOT_PRESENT: u64 = 0;
pub const PAYLOAD_BLOCK_UNDEFINED: u64 = 1;
pub const PAYLOAD_BLOCK_ZERO: u64 = 2;
pub const PAYLOAD_BLOCK_UNMAPPED: u64 = 3;
pub const PAYLOAD_BLOCK_FULLY_PRESENT: u64 = 6;
pub const PAYLOAD_BLOCK_PARTIALLY_PRESENT: u64 = 7;
// Mask for the BAT state
pub const BAT_STATE_BIT_MASK: u64 = 0x07;
// Mask for the offset within the file in units of 1 MB
pub const BAT_FILE_OFF_MASK: u64 = 0xFFFFFFFFFFF00000;
#[sorted]
#[derive(Error, Debug)]
pub enum VhdxBatError {
#[error("Invalid BAT entry")]
InvalidBatEntry,
#[error("Invalid BAT entry count")]
InvalidEntryCount,
#[error("Failed to read BAT entry")]
ReadBat(#[source] io::Error),
#[error("Failed to write BAT entry")]
WriteBat(#[source] io::Error),
}
pub type Result<T> = std::result::Result<T, VhdxBatError>;
#[derive(Default, Clone, Debug)]
pub struct BatEntry(pub u64);
impl BatEntry {
// Read all BAT entries presented on the disk and insert them to a vector
pub fn collect_bat_entries(
f: &mut File,
disk_spec: &DiskSpec,
bat_entry: &RegionTableEntry,
) -> Result<Vec<BatEntry>> {
let entry_count = BatEntry::calculate_entries(
disk_spec.block_size,
disk_spec.virtual_disk_size,
disk_spec.chunk_ratio,
);
if entry_count as usize > (bat_entry.length as usize / size_of::<BatEntry>()) {
return Err(VhdxBatError::InvalidEntryCount);
}
let mut bat: Vec<BatEntry> = Vec::with_capacity(bat_entry.length as usize);
let offset = bat_entry.file_offset;
for i in 0..entry_count {
f.seek(SeekFrom::Start(offset + i * size_of::<u64>() as u64))
.map_err(VhdxBatError::ReadBat)?;
let bat_entry = BatEntry(
f.read_u64::<LittleEndian>()
.map_err(VhdxBatError::ReadBat)?,
);
bat.insert(i as usize, bat_entry);
}
Ok(bat)
}
// Calculate the number of entries in the BAT
fn calculate_entries(block_size: u32, virtual_disk_size: u64, chunk_ratio: u64) -> u64 {
let data_blocks_count = virtual_disk_size.div_ceil(block_size as u64);
data_blocks_count + (data_blocks_count - 1) / chunk_ratio
}
// Routine for writing BAT entries to the disk
pub fn write_bat_entries(
f: &mut File,
bat_offset: u64,
bat_entries: &[BatEntry],
) -> Result<()> {
for i in 0..bat_entries.len() as u64 {
f.seek(SeekFrom::Start(bat_offset + i * size_of::<u64>() as u64))
.map_err(VhdxBatError::WriteBat)?;
let bat_entry = match bat_entries.get(i as usize) {
Some(entry) => entry.0,
None => {
return Err(VhdxBatError::InvalidBatEntry);
}
};
f.write_u64::<LittleEndian>(bat_entry)
.map_err(VhdxBatError::WriteBat)?;
}
Ok(())
}
}

View File

@@ -1,471 +0,0 @@
// Copyright © 2021 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
extern crate log;
use std::collections::btree_map::BTreeMap;
use std::fs::File;
use std::io::{self, Read, Seek, SeekFrom, Write};
use std::mem::size_of;
use byteorder::{ByteOrder, LittleEndian, ReadBytesExt};
use remain::sorted;
use thiserror::Error;
use uuid::Uuid;
const VHDX_SIGN: u64 = 0x656C_6966_7864_6876; // "vhdxfile"
const HEADER_SIGN: u32 = 0x6461_6568; // "head"
const REGION_SIGN: u32 = 0x6967_6572; // "regi"
const FILE_START: u64 = 0; // The first element
const HEADER_1_START: u64 = 64 * 1024; // Header 1 start in Bytes
const HEADER_2_START: u64 = 128 * 1024; // Header 2 start in Bytes
pub const REGION_TABLE_1_START: u64 = 192 * 1024; // Region 1 start in Bytes
const REGION_TABLE_2_START: u64 = 256 * 1024; // Region 2 start in Bytes
const HEADER_SIZE: u64 = 4 * 1024; // Each header is 64 KiB, but only first 4 kiB contains info
const REGION_SIZE: u64 = 64 * 1024; // Each region size is 64 KiB
const REGION_ENTRY_REQUIRED: u32 = 1;
const BAT_GUID: &str = "2DC27766-F623-4200-9D64-115E9BFD4A08"; // BAT GUID
const MDR_GUID: &str = "8B7CA206-4790-4B9A-B8FE-575F050F886E"; // Metadata GUID
#[sorted]
#[derive(Error, Debug)]
pub enum VhdxHeaderError {
#[error("Failed to calculate checksum")]
CalculateChecksum,
#[error("BAT entry is not unique")]
DuplicateBATEntry,
#[error("Metadata region entry is not unique")]
DuplicateMDREntry,
#[error("Checksum doesn't match for")]
InvalidChecksum(String),
#[error("Invalid entry count")]
InvalidEntryCount,
#[error("Not a valid VHDx header")]
InvalidHeaderSign,
#[error("Not a valid VHDx region")]
InvalidRegionSign,
#[error("Couldn't parse Uuid for region entry {0}")]
InvalidUuid(#[source] uuid::Error),
#[error("Not a VHDx file")]
InvalidVHDXSign,
#[error("No valid header found")]
NoValidHeader,
#[error("Cannot read checksum")]
ReadChecksum,
#[error("Failed to read File Type Identifier {0}")]
ReadFileTypeIdentifier(#[source] io::Error),
#[error("Failed to read headers {0}")]
ReadHeader(#[source] io::Error),
#[error("Failed to read metadata {0}")]
ReadMetadata(#[source] std::io::Error),
#[error("Failed to read region table entries {0}")]
ReadRegionTableEntries(#[source] io::Error),
#[error("Failed to read region table header {0}")]
ReadRegionTableHeader(#[source] io::Error),
#[error("Failed to read region entries")]
RegionEntryCollectionFailed,
#[error("Overlapping regions found")]
RegionOverlap,
#[error("Reserved region has non-zero value")]
ReservedIsNonZero,
#[error("Failed to seek in File Type Identifier {0}")]
SeekFileTypeIdentifier(#[source] io::Error),
#[error("Failed to seek in headers {0}")]
SeekHeader(#[source] io::Error),
#[error("Failed to seek in region table entries {0}")]
SeekRegionTableEntries(#[source] io::Error),
#[error("Failed to seek in region table header {0}")]
SeekRegionTableHeader(#[source] io::Error),
#[error("We do not recognize this entry")]
UnrecognizedRegionEntry,
#[error("Failed to write header {0}")]
WriteHeader(#[source] io::Error),
}
pub type Result<T> = std::result::Result<T, VhdxHeaderError>;
#[derive(Clone, Debug)]
pub struct FileTypeIdentifier {
pub _signature: u64,
}
impl FileTypeIdentifier {
/// Reads the File Type Identifier structure from a reference VHDx file
pub fn new(f: &mut File) -> Result<FileTypeIdentifier> {
f.seek(SeekFrom::Start(FILE_START))
.map_err(VhdxHeaderError::SeekFileTypeIdentifier)?;
let _signature = f
.read_u64::<LittleEndian>()
.map_err(VhdxHeaderError::ReadFileTypeIdentifier)?;
if _signature != VHDX_SIGN {
return Err(VhdxHeaderError::InvalidVHDXSign);
}
Ok(FileTypeIdentifier { _signature })
}
}
#[repr(C, packed)]
#[derive(Clone, Copy, Debug)]
pub struct Header {
pub signature: u32,
pub checksum: u32,
pub sequence_number: u64,
pub file_write_guid: u128,
pub data_write_guid: u128,
pub log_guid: u128,
pub log_version: u16,
pub version: u16,
pub log_length: u32,
pub log_offset: u64,
}
impl Header {
/// Reads the Header structure from a reference VHDx file
pub fn new(f: &mut File, start: u64) -> Result<Header> {
// Read the whole header into a buffer. We will need it for
// calculating checksum.
let mut buffer = [0; HEADER_SIZE as usize];
f.seek(SeekFrom::Start(start))
.map_err(VhdxHeaderError::SeekHeader)?;
f.read_exact(&mut buffer)
.map_err(VhdxHeaderError::ReadHeader)?;
// SAFETY: buffer is of correct size and has been successfully filled.
let header = unsafe { *(buffer.as_ptr() as *mut Header) };
if header.signature != HEADER_SIGN {
return Err(VhdxHeaderError::InvalidHeaderSign);
}
let new_checksum = calculate_checksum(&mut buffer, size_of::<u32>());
if header.checksum != new_checksum {
return Err(VhdxHeaderError::InvalidChecksum(String::from("Header")));
}
Ok(header)
}
/// Converts the header structure into a buffer
fn write_to_buffer(&self, buffer: &mut [u8; HEADER_SIZE as usize]) {
// SAFETY: self is a valid header.
let reference = unsafe {
std::slice::from_raw_parts(self as *const Header as *const u8, HEADER_SIZE as usize)
};
*buffer = reference.try_into().unwrap();
}
/// Creates and returns new updated header from the provided current header
fn update_header(
f: &mut File,
current_header: &Header,
change_data_guid: bool,
mut file_write_guid: u128,
start: u64,
) -> Result<Header> {
let mut buffer = [0u8; HEADER_SIZE as usize];
let mut data_write_guid = current_header.data_write_guid;
if change_data_guid {
data_write_guid = Uuid::new_v4().as_u128();
}
if file_write_guid == 0 {
file_write_guid = current_header.file_write_guid;
}
let mut new_header = Header {
signature: current_header.signature,
checksum: 0,
sequence_number: current_header.sequence_number + 1,
file_write_guid,
data_write_guid,
log_guid: current_header.log_guid,
log_version: current_header.log_version,
version: current_header.version,
log_length: current_header.log_length,
log_offset: current_header.log_offset,
};
new_header.write_to_buffer(&mut buffer);
new_header.checksum = calculate_checksum(&mut buffer, size_of::<u32>());
new_header.write_to_buffer(&mut buffer);
f.seek(SeekFrom::Start(start))
.map_err(VhdxHeaderError::SeekHeader)?;
f.write(&buffer).map_err(VhdxHeaderError::WriteHeader)?;
Ok(new_header)
}
}
#[repr(C, packed)]
#[derive(Clone, Copy, Debug)]
struct RegionTableHeader {
pub signature: u32,
pub checksum: u32,
pub entry_count: u32,
pub reserved: u32,
}
impl RegionTableHeader {
/// Reads the Region Table Header structure from a reference VHDx file
pub fn new(f: &mut File, start: u64) -> Result<RegionTableHeader> {
// Read the whole header into a buffer. We will need it for calculating
// checksum.
let mut buffer = [0u8; REGION_SIZE as usize];
f.seek(SeekFrom::Start(start))
.map_err(VhdxHeaderError::SeekRegionTableHeader)?;
f.read_exact(&mut buffer)
.map_err(VhdxHeaderError::ReadRegionTableHeader)?;
// SAFETY: buffer is of correct size and has been successfully filled.
let region_table_header = unsafe { *(buffer.as_ptr() as *mut RegionTableHeader) };
if region_table_header.signature != REGION_SIGN {
return Err(VhdxHeaderError::InvalidRegionSign);
}
let new_checksum = calculate_checksum(&mut buffer, size_of::<u32>());
if region_table_header.checksum != new_checksum {
return Err(VhdxHeaderError::InvalidChecksum(String::from("Region")));
}
if region_table_header.entry_count > 2047 {
return Err(VhdxHeaderError::InvalidEntryCount);
}
if region_table_header.reserved != 0 {
return Err(VhdxHeaderError::ReservedIsNonZero);
}
Ok(region_table_header)
}
}
pub struct RegionInfo {
pub bat_entry: RegionTableEntry,
pub mdr_entry: RegionTableEntry,
pub region_entries: BTreeMap<u64, u64>,
}
impl RegionInfo {
/// Collect all entries in a BTreeMap from the Region Table and identifies
/// BAT and metadata regions
pub fn new(f: &mut File, region_start: u64, entry_count: u32) -> Result<RegionInfo> {
let mut bat_entry: Option<RegionTableEntry> = None;
let mut mdr_entry: Option<RegionTableEntry> = None;
let mut offset = 0;
let mut region_entries = BTreeMap::new();
let mut buffer = [0; REGION_SIZE as usize];
// Seek after the Region Table Header
f.seek(SeekFrom::Start(
region_start + size_of::<RegionTableHeader>() as u64,
))
.map_err(VhdxHeaderError::SeekRegionTableEntries)?;
f.read_exact(&mut buffer)
.map_err(VhdxHeaderError::ReadRegionTableEntries)?;
for _ in 0..entry_count {
let entry =
RegionTableEntry::new(&buffer[offset..offset + size_of::<RegionTableEntry>()])?;
offset += size_of::<RegionTableEntry>();
let start = entry.file_offset;
let end = start + entry.length as u64;
for (region_ent_start, region_ent_end) in region_entries.iter() {
if !((start >= *region_ent_start) || (end <= *region_ent_end)) {
return Err(VhdxHeaderError::RegionOverlap);
}
}
region_entries.insert(entry.file_offset, entry.file_offset + entry.length as u64);
if entry.guid == Uuid::parse_str(BAT_GUID).map_err(VhdxHeaderError::InvalidUuid)? {
if bat_entry.is_none() {
bat_entry = Some(entry);
continue;
}
return Err(VhdxHeaderError::DuplicateBATEntry);
}
if entry.guid == Uuid::parse_str(MDR_GUID).map_err(VhdxHeaderError::InvalidUuid)? {
if mdr_entry.is_none() {
mdr_entry = Some(entry);
continue;
}
return Err(VhdxHeaderError::DuplicateMDREntry);
}
if (entry.required & REGION_ENTRY_REQUIRED) == 1 {
// This implementation doesn't recognize this field.
// Therefore, according to the spec, we are throwing an error.
return Err(VhdxHeaderError::UnrecognizedRegionEntry);
}
}
if bat_entry.is_none() || mdr_entry.is_none() {
region_entries.clear();
return Err(VhdxHeaderError::RegionEntryCollectionFailed);
}
// It's safe to unwrap as we checked both entries have been filled.
// Otherwise, an error is already returned.
let bat_entry = bat_entry.unwrap();
let mdr_entry = mdr_entry.unwrap();
Ok(RegionInfo {
bat_entry,
mdr_entry,
region_entries,
})
}
}
#[repr(C, packed)]
#[derive(Clone, Copy, Debug)]
pub struct RegionTableEntry {
pub guid: Uuid,
pub file_offset: u64,
pub length: u32,
pub required: u32,
}
impl RegionTableEntry {
/// Reads one Region Entry from a Region Table index that starts from 0
pub fn new(buffer: &[u8]) -> Result<RegionTableEntry> {
assert!(buffer.len() == std::mem::size_of::<RegionTableEntry>());
// SAFETY: the assertion above makes sure the buffer size is correct.
let mut region_table_entry = unsafe { *(buffer.as_ptr() as *mut RegionTableEntry) };
let uuid = crate::vhdx::uuid_from_guid(buffer);
region_table_entry.guid = uuid;
Ok(region_table_entry)
}
}
enum HeaderNo {
First,
Second,
}
/// Contains the information from the header of a VHDx file
#[derive(Clone, Debug)]
pub struct VhdxHeader {
_file_type_identifier: FileTypeIdentifier,
header_1: Header,
header_2: Header,
region_table_1: RegionTableHeader,
_region_table_2: RegionTableHeader,
}
impl VhdxHeader {
/// Creates a VhdxHeader from a reference to a file
pub fn new(f: &mut File) -> Result<VhdxHeader> {
Ok(VhdxHeader {
_file_type_identifier: FileTypeIdentifier::new(f)?,
header_1: Header::new(f, HEADER_1_START)?,
header_2: Header::new(f, HEADER_2_START)?,
region_table_1: RegionTableHeader::new(f, REGION_TABLE_1_START)?,
_region_table_2: RegionTableHeader::new(f, REGION_TABLE_2_START)?,
})
}
/// Identify the current header and return both headers along with an
/// integer indicating the current header.
fn current_header(
header_1: Result<Header>,
header_2: Result<Header>,
) -> Result<(HeaderNo, Header)> {
let header_1 = header_1.ok();
let header_2 = header_2.ok();
match (header_1, header_2) {
(None, None) => Err(VhdxHeaderError::NoValidHeader),
(Some(header_1), None) => Ok((HeaderNo::First, header_1)),
(None, Some(header_2)) => Ok((HeaderNo::Second, header_2)),
(Some(header_1), Some(header_2)) => {
if header_1.sequence_number >= header_2.sequence_number {
Ok((HeaderNo::First, header_1))
} else {
Ok((HeaderNo::Second, header_2))
}
}
}
}
/// This takes two headers and update the noncurrent header with the
/// current one. Returns both headers as a tuple sequenced the way it was
/// received from the parameter list.
fn update_header(
f: &mut File,
header_1: Result<Header>,
header_2: Result<Header>,
guid: u128,
) -> Result<(Header, Header)> {
let (header_no, current_header) = VhdxHeader::current_header(header_1, header_2)?;
match header_no {
HeaderNo::First => {
let other_header =
Header::update_header(f, &current_header, true, guid, HEADER_2_START)?;
Ok((current_header, other_header))
}
HeaderNo::Second => {
let other_header =
Header::update_header(f, &current_header, true, guid, HEADER_1_START)?;
Ok((other_header, current_header))
}
}
}
// Update the provided headers according to the spec
fn update_headers(
f: &mut File,
header_1: Result<Header>,
header_2: Result<Header>,
guid: u128,
) -> Result<(Header, Header)> {
// According to the spec, update twice
let (header_1, header_2) = VhdxHeader::update_header(f, header_1, header_2, guid)?;
VhdxHeader::update_header(f, Ok(header_1), Ok(header_2), guid)
}
pub fn update(&mut self, f: &mut File) -> Result<()> {
let headers = VhdxHeader::update_headers(f, Ok(self.header_1), Ok(self.header_2), 0)?;
self.header_1 = headers.0;
self.header_2 = headers.1;
Ok(())
}
pub fn region_entry_count(&self) -> u32 {
self.region_table_1.entry_count
}
}
/// Calculates the checksum of a buffer that itself contains its checksum
/// Therefore, before calculating, the existing checksum is retrieved and the
/// corresponding field is made zero. After the calculation, the existing checksum
/// is put back to the buffer.
fn calculate_checksum(buffer: &mut [u8], csum_offset: usize) -> u32 {
// Read the original checksum from the buffer
let orig_csum = LittleEndian::read_u32(&buffer[csum_offset..csum_offset + 4]);
// Zero the checksum in the buffer
LittleEndian::write_u32(&mut buffer[csum_offset..csum_offset + 4], 0);
// Calculate the checksum on the resulting buffer
let mut crc = crc_any::CRC::crc32c();
crc.digest(&buffer);
let new_csum = crc.get_crc() as u32;
// Put back the original checksum in the buffer
LittleEndian::write_u32(&mut buffer[csum_offset..csum_offset + 4], orig_csum);
new_csum
}

View File

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

View File

@@ -1,335 +0,0 @@
// Copyright © 2021 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
use std::fs::File;
use std::io::{self, Read, Seek, SeekFrom};
use std::mem::size_of;
use byteorder::{LittleEndian, ReadBytesExt};
use remain::sorted;
use thiserror::Error;
use uuid::Uuid;
use crate::vhdx::vhdx_header::RegionTableEntry;
const METADATA_SIGN: u64 = 0x6174_6164_6174_656D;
const METADATA_ENTRY_SIZE: usize = 32;
const METADATA_MAX_ENTRIES: u16 = 2047;
// The size including the table header and entries
const METADATA_TABLE_MAX_SIZE: usize = METADATA_ENTRY_SIZE * (METADATA_MAX_ENTRIES as usize + 1);
const METADATA_FLAGS_IS_REQUIRED: u32 = 0x04;
pub const BLOCK_SIZE_MIN: u32 = 1 << 20; // 1 MiB
const BLOCK_SIZE_MAX: u32 = 256 << 20; // 256 MiB
const MAX_SECTORS_PER_BLOCK: u64 = 1 << 23;
const BLOCK_HAS_PARENT: u32 = 0x02; // Has a parent or a backing file
// GUID for known metadata items
const METADATA_FILE_PARAMETER: &str = "CAA16737-FA36-4D43-B3B6-33F0AA44E76B";
const METADATA_VIRTUAL_DISK_SIZE: &str = "2FA54224-CD1B-4876-B211-5DBED83BF4B8";
const METADATA_VIRTUAL_DISK_ID: &str = "BECA12AB-B2E6-4523-93EF-C309E000C746";
const METADATA_LOGICAL_SECTOR_SIZE: &str = "8141BF1D-A96F-4709-BA47-F233A8FAAB5F";
const METADATA_PHYSICAL_SECTOR_SIZE: &str = "CDA348C7-445D-4471-9CC9-E9885251C556";
const METADATA_PARENT_LOCATOR: &str = "A8D35F2D-B30B-454D-ABF7-D3D84834AB0C";
const METADATA_FILE_PARAMETER_PRESENT: u16 = 0x01;
const METADATA_VIRTUAL_DISK_SIZE_PRESENT: u16 = 0x02;
const METADATA_VIRTUAL_DISK_ID_PRESENT: u16 = 0x04;
const METADATA_LOGICAL_SECTOR_SIZE_PRESENT: u16 = 0x08;
const METADATA_PHYSICAL_SECTOR_SIZE_PRESENT: u16 = 0x10;
const METADATA_PARENT_LOCATOR_PRESENT: u16 = 0x20;
const METADATA_ALL_PRESENT: u16 = METADATA_FILE_PARAMETER_PRESENT
| METADATA_VIRTUAL_DISK_SIZE_PRESENT
| METADATA_VIRTUAL_DISK_ID_PRESENT
| METADATA_LOGICAL_SECTOR_SIZE_PRESENT
| METADATA_PHYSICAL_SECTOR_SIZE_PRESENT;
const METADATA_LENGTH_MAX: u32 = 1 << 20; // 1 MiB
#[sorted]
#[derive(Error, Debug)]
pub enum VhdxMetadataError {
#[error("Invalid block size count")]
InvalidBlockSize,
#[error("Invalid disk size {0}")]
InvalidDiskSize(u64),
#[error("Invalid metadata entry count")]
InvalidEntryCount,
#[error("Invalid logical sector size")]
InvalidLogicalSectorSize,
#[error("Invalid metadata ID")]
InvalidMetadataItem,
#[error("Invalid metadata length")]
InvalidMetadataLength,
#[error("Metadata sign doesn't match")]
InvalidMetadataSign,
#[error("Invalid physical sector size")]
InvalidPhysicalSectorSize,
#[error("Invalid UUID")]
InvalidUuid(#[source] uuid::Error),
#[error("Invalid value")]
InvalidValue,
#[error("Not all required metadata found")]
MissingMetadata,
#[error("Failed to read metadata headers {0}")]
ReadMetadata(#[source] io::Error),
#[error("Reserved region has non-zero value")]
ReservedIsNonZero,
#[error("This implementation doesn't support this metadata flag")]
UnsupportedFlag,
}
pub type Result<T> = std::result::Result<T, VhdxMetadataError>;
#[derive(Default, Clone, Debug)]
pub struct DiskSpec {
pub disk_id: u128,
pub image_size: u64,
pub block_size: u32,
pub has_parent: bool,
pub sectors_per_block: u32,
pub virtual_disk_size: u64,
pub logical_sector_size: u32,
pub physical_sector_size: u32,
pub chunk_ratio: u64,
pub total_sectors: u64,
}
impl DiskSpec {
/// Parse all metadata from the provided file and store info in DiskSpec
/// structure.
pub fn new(f: &mut File, metadata_region: &RegionTableEntry) -> Result<DiskSpec> {
let mut disk_spec = DiskSpec::default();
let mut metadata_presence: u16 = 0;
let mut offset = 0;
let metadata = f.metadata().map_err(VhdxMetadataError::ReadMetadata)?;
disk_spec.image_size = metadata.len();
let mut buffer = [0u8; METADATA_TABLE_MAX_SIZE];
f.seek(SeekFrom::Start(metadata_region.file_offset))
.map_err(VhdxMetadataError::ReadMetadata)?;
f.read_exact(&mut buffer)
.map_err(VhdxMetadataError::ReadMetadata)?;
let metadata_header =
MetadataTableHeader::new(&buffer[0..size_of::<MetadataTableHeader>()])?;
offset += size_of::<MetadataTableHeader>();
for _ in 0..metadata_header.entry_count {
let metadata_entry =
MetadataTableEntry::new(&buffer[offset..offset + size_of::<MetadataTableEntry>()])?;
f.seek(SeekFrom::Start(
metadata_region.file_offset + metadata_entry.offset as u64,
))
.map_err(VhdxMetadataError::ReadMetadata)?;
if metadata_entry.item_id
== Uuid::parse_str(METADATA_FILE_PARAMETER)
.map_err(VhdxMetadataError::InvalidUuid)?
{
disk_spec.block_size = f
.read_u32::<LittleEndian>()
.map_err(VhdxMetadataError::ReadMetadata)?;
// MUST be at least 1 MiB and not greater than 256 MiB
if disk_spec.block_size < BLOCK_SIZE_MIN || disk_spec.block_size > BLOCK_SIZE_MAX {
return Err(VhdxMetadataError::InvalidBlockSize);
}
// MUST be power of 2
if !disk_spec.block_size.is_power_of_two() {
return Err(VhdxMetadataError::InvalidBlockSize);
}
let bits = f
.read_u32::<LittleEndian>()
.map_err(VhdxMetadataError::ReadMetadata)?;
disk_spec.has_parent = bits & BLOCK_HAS_PARENT != 0;
metadata_presence |= METADATA_FILE_PARAMETER_PRESENT;
} else if metadata_entry.item_id
== Uuid::parse_str(METADATA_VIRTUAL_DISK_SIZE)
.map_err(VhdxMetadataError::InvalidUuid)?
{
disk_spec.virtual_disk_size = f
.read_u64::<LittleEndian>()
.map_err(VhdxMetadataError::ReadMetadata)?;
metadata_presence |= METADATA_VIRTUAL_DISK_SIZE_PRESENT;
} else if metadata_entry.item_id
== Uuid::parse_str(METADATA_VIRTUAL_DISK_ID)
.map_err(VhdxMetadataError::InvalidUuid)?
{
disk_spec.disk_id = f
.read_u128::<LittleEndian>()
.map_err(VhdxMetadataError::ReadMetadata)?;
metadata_presence |= METADATA_VIRTUAL_DISK_ID_PRESENT;
} else if metadata_entry.item_id
== Uuid::parse_str(METADATA_LOGICAL_SECTOR_SIZE)
.map_err(VhdxMetadataError::InvalidUuid)?
{
disk_spec.logical_sector_size = f
.read_u32::<LittleEndian>()
.map_err(VhdxMetadataError::ReadMetadata)?;
if !(disk_spec.logical_sector_size == 512 || disk_spec.logical_sector_size == 4096)
{
return Err(VhdxMetadataError::InvalidLogicalSectorSize);
}
metadata_presence |= METADATA_LOGICAL_SECTOR_SIZE_PRESENT;
} else if metadata_entry.item_id
== Uuid::parse_str(METADATA_PHYSICAL_SECTOR_SIZE)
.map_err(VhdxMetadataError::InvalidUuid)?
{
disk_spec.physical_sector_size = f
.read_u32::<LittleEndian>()
.map_err(VhdxMetadataError::ReadMetadata)?;
if !(disk_spec.physical_sector_size == 512
|| disk_spec.physical_sector_size == 4096)
{
return Err(VhdxMetadataError::InvalidPhysicalSectorSize);
}
metadata_presence |= METADATA_PHYSICAL_SECTOR_SIZE_PRESENT;
} else if metadata_entry.item_id
== Uuid::parse_str(METADATA_PARENT_LOCATOR)
.map_err(VhdxMetadataError::InvalidUuid)?
{
metadata_presence |= METADATA_PARENT_LOCATOR_PRESENT;
} else {
return Err(VhdxMetadataError::InvalidMetadataItem);
}
if (metadata_entry.flag_bits & METADATA_FLAGS_IS_REQUIRED) == 0 {
return Err(VhdxMetadataError::UnsupportedFlag);
}
offset += size_of::<MetadataTableEntry>();
}
// Check if all required metadata are present
if metadata_presence != METADATA_ALL_PRESENT {
return Err(VhdxMetadataError::MissingMetadata);
}
// Make sure virtual disk size is not zero
if (metadata_presence & METADATA_VIRTUAL_DISK_SIZE_PRESENT != 0)
&& disk_spec.virtual_disk_size == 0
{
return Err(VhdxMetadataError::InvalidDiskSize(
disk_spec.virtual_disk_size,
));
}
// Check if the virtual disk size is a multiple of the logical sector
// size.
if ((metadata_presence & METADATA_LOGICAL_SECTOR_SIZE_PRESENT) != 0)
&& (disk_spec.virtual_disk_size % disk_spec.logical_sector_size as u64 != 0)
{
return Err(VhdxMetadataError::InvalidBlockSize);
}
disk_spec.sectors_per_block =
DiskSpec::sectors_per_block(disk_spec.block_size, disk_spec.logical_sector_size)?;
disk_spec.chunk_ratio =
DiskSpec::chunk_ratio(disk_spec.block_size, disk_spec.logical_sector_size)?;
disk_spec.total_sectors =
disk_spec.virtual_disk_size / disk_spec.logical_sector_size as u64;
Ok(disk_spec)
}
/// Calculates the number of sectors per block
fn sectors_per_block(block_size: u32, logical_sector_size: u32) -> Result<u32> {
let sectors_per_block = block_size / logical_sector_size;
if !sectors_per_block.is_power_of_two() {
return Err(VhdxMetadataError::InvalidValue);
}
Ok(sectors_per_block)
}
/// Calculate the chunk ratio
fn chunk_ratio(block_size: u32, logical_sector_size: u32) -> Result<u64> {
let chunk_ratio = (MAX_SECTORS_PER_BLOCK * logical_sector_size as u64) / block_size as u64;
if !chunk_ratio.is_power_of_two() {
return Err(VhdxMetadataError::InvalidValue);
}
Ok(chunk_ratio)
}
}
#[repr(C, packed)]
#[derive(Default, Debug, Clone, Copy)]
struct MetadataTableHeader {
signature: u64,
reserved: u16,
entry_count: u16,
_reserved2: [u8; 20],
}
impl MetadataTableHeader {
pub fn new(buffer: &[u8]) -> Result<MetadataTableHeader> {
assert!(buffer.len() == std::mem::size_of::<MetadataTableHeader>());
// SAFETY: the assertion above makes sure the buffer size is correct.
let metadata_table_header = unsafe { *(buffer.as_ptr() as *mut MetadataTableHeader) };
if metadata_table_header.signature != METADATA_SIGN {
return Err(VhdxMetadataError::InvalidMetadataSign);
}
if metadata_table_header.entry_count > METADATA_MAX_ENTRIES {
return Err(VhdxMetadataError::InvalidEntryCount);
}
if metadata_table_header.reserved != 0 {
return Err(VhdxMetadataError::ReservedIsNonZero);
}
Ok(metadata_table_header)
}
}
#[repr(C, packed)]
#[derive(Default, Debug, Clone, Copy)]
pub struct MetadataTableEntry {
item_id: Uuid,
offset: u32,
length: u32,
flag_bits: u32,
reserved: u32,
}
impl MetadataTableEntry {
/// Parse one metadata entry from the buffer
fn new(buffer: &[u8]) -> Result<MetadataTableEntry> {
assert!(buffer.len() == std::mem::size_of::<MetadataTableEntry>());
// SAFETY: the assertion above makes sure the buffer size is correct.
let mut metadata_table_entry = unsafe { *(buffer.as_ptr() as *mut MetadataTableEntry) };
let uuid = crate::vhdx::uuid_from_guid(buffer);
metadata_table_entry.item_id = uuid;
if metadata_table_entry.length > METADATA_LENGTH_MAX {
return Err(VhdxMetadataError::InvalidMetadataLength);
}
if metadata_table_entry.length == 0 && metadata_table_entry.offset != 0 {
return Err(VhdxMetadataError::InvalidMetadataLength);
}
if metadata_table_entry.reserved != 0 {
return Err(VhdxMetadataError::ReservedIsNonZero);
}
Ok(metadata_table_entry)
}
}

View File

@@ -1,107 +0,0 @@
// Copyright © 2021 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
use std::collections::VecDeque;
use std::fs::File;
use std::os::fd::AsRawFd;
use vmm_sys_util::eventfd::EventFd;
use crate::AsyncAdaptor;
use crate::async_io::{
AsyncIo, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult,
};
use crate::vhdx::{Result as VhdxResult, Vhdx};
pub struct VhdxDiskSync {
vhdx_file: Vhdx,
}
impl VhdxDiskSync {
pub fn new(f: File) -> VhdxResult<Self> {
Ok(VhdxDiskSync {
vhdx_file: Vhdx::new(f)?,
})
}
}
impl DiskFile for VhdxDiskSync {
fn size(&mut self) -> DiskFileResult<u64> {
Ok(self.vhdx_file.virtual_disk_size())
}
fn new_async_io(&self, _ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
Ok(
Box::new(VhdxSync::new(self.vhdx_file.clone()).map_err(DiskFileError::NewAsyncIo)?)
as Box<dyn AsyncIo>,
)
}
fn fd(&mut self) -> BorrowedDiskFd<'_> {
BorrowedDiskFd::new(self.vhdx_file.as_raw_fd())
}
}
pub struct VhdxSync {
vhdx_file: Vhdx,
eventfd: EventFd,
completion_list: VecDeque<(u64, i32)>,
}
impl VhdxSync {
pub fn new(vhdx_file: Vhdx) -> std::io::Result<Self> {
Ok(VhdxSync {
vhdx_file,
eventfd: EventFd::new(libc::EFD_NONBLOCK)?,
completion_list: VecDeque::new(),
})
}
}
impl AsyncAdaptor for Vhdx {}
impl AsyncIo for VhdxSync {
fn notifier(&self) -> &EventFd {
&self.eventfd
}
fn read_vectored(
&mut self,
offset: libc::off_t,
iovecs: &[libc::iovec],
user_data: u64,
) -> AsyncIoResult<()> {
self.vhdx_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.vhdx_file.write_vectored_sync(
offset,
iovecs,
user_data,
&self.eventfd,
&mut self.completion_list,
)
}
fn fsync(&mut self, user_data: Option<u64>) -> AsyncIoResult<()> {
self.vhdx_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()
}
}

24
block_util/Cargo.toml Normal file
View File

@@ -0,0 +1,24 @@
[package]
name = "block_util"
version = "0.1.0"
authors = ["The Cloud Hypervisor Authors"]
edition = "2018"
[features]
default = []
io_uring = []
[dependencies]
io-uring = ">=0.4.0"
libc = "0.2.91"
log = "0.4.14"
qcow = { path = "../qcow" }
serde = ">=1.0.27"
serde_derive = ">=1.0.27"
serde_json = ">=1.0.9"
thiserror = "1.0"
virtio-bindings = { version = "0.1", features = ["virtio-v5_0_0"]}
vm-memory = { version = "0.5.0", features = ["backend-mmap", "backend-atomic"] }
vm-virtio = { path = "../vm-virtio" }
vmm-sys-util = ">=0.3.1"

View File

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

View File

@@ -2,41 +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 size(&mut self) -> DiskFileResult<u64> {
Ok(self.0.size().unwrap())
Ok(self.size)
}
fn new_async_io(&self, ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
Ok(Box::new(
FixedVhdAsync::new(self.0.as_raw_fd(), ring_depth, self.0.size().unwrap())
FixedVhdAsync::new(self.file.as_raw_fd(), ring_depth, self.size)
.map_err(DiskFileError::NewAsyncIo)?,
) as Box<dyn AsyncIo>)
}
fn fd(&mut self) -> BorrowedDiskFd<'_> {
BorrowedDiskFd::new(self.0.as_raw_fd())
}
}
pub struct FixedVhdAsync {
@@ -63,7 +64,7 @@ impl AsyncIo for FixedVhdAsync {
fn read_vectored(
&mut self,
offset: libc::off_t,
iovecs: &[libc::iovec],
iovecs: Vec<libc::iovec>,
user_data: u64,
) -> AsyncIoResult<()> {
if offset as u64 >= self.size {
@@ -82,7 +83,7 @@ impl AsyncIo for FixedVhdAsync {
fn write_vectored(
&mut self,
offset: libc::off_t,
iovecs: &[libc::iovec],
iovecs: Vec<libc::iovec>,
user_data: u64,
) -> AsyncIoResult<()> {
if offset as u64 >= self.size {
@@ -103,15 +104,7 @@ impl AsyncIo for FixedVhdAsync {
self.raw_file_async.fsync(user_data)
}
fn next_completed_request(&mut self) -> Option<(u64, i32)> {
self.raw_file_async.next_completed_request()
}
fn 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)
fn complete(&mut self) -> Vec<(u64, i32)> {
self.raw_file_async.complete()
}
}

View File

@@ -2,41 +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 size(&mut self) -> DiskFileResult<u64> {
Ok(self.0.size().unwrap())
Ok(self.size)
}
fn new_async_io(&self, _ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
Ok(Box::new(
FixedVhdSync::new(self.0.as_raw_fd(), self.0.size().unwrap())
FixedVhdSync::new(self.file.as_raw_fd(), self.size)
.map_err(DiskFileError::NewAsyncIo)?,
) as Box<dyn AsyncIo>)
}
fn fd(&mut self) -> BorrowedDiskFd<'_> {
BorrowedDiskFd::new(self.0.as_raw_fd())
}
}
pub struct FixedVhdSync {
@@ -61,7 +62,7 @@ impl AsyncIo for FixedVhdSync {
fn read_vectored(
&mut self,
offset: libc::off_t,
iovecs: &[libc::iovec],
iovecs: Vec<libc::iovec>,
user_data: u64,
) -> AsyncIoResult<()> {
if offset as u64 >= self.size {
@@ -80,7 +81,7 @@ impl AsyncIo for FixedVhdSync {
fn write_vectored(
&mut self,
offset: libc::off_t,
iovecs: &[libc::iovec],
iovecs: Vec<libc::iovec>,
user_data: u64,
) -> AsyncIoResult<()> {
if offset as u64 >= self.size {
@@ -100,7 +101,7 @@ impl AsyncIo for FixedVhdSync {
self.raw_file_sync.fsync(user_data)
}
fn next_completed_request(&mut self) -> Option<(u64, i32)> {
self.raw_file_sync.next_completed_request()
fn complete(&mut self) -> Vec<(u64, i32)> {
self.raw_file_sync.complete()
}
}

701
block_util/src/lib.rs Normal file
View File

@@ -0,0 +1,701 @@
// 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;
#[macro_use]
extern crate serde_derive;
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;
use crate::async_io::{AsyncIo, AsyncIoError, AsyncIoResult, DiskFileError, DiskFileResult};
#[cfg(feature = "io_uring")]
use io_uring::{opcode, IoUring, Probe};
use serde::ser::{Serialize, SerializeStruct, Serializer};
use std::cmp;
use std::convert::TryInto;
use std::fs::File;
use std::io::{self, IoSlice, IoSliceMut, Read, Seek, SeekFrom, Write};
use std::os::linux::fs::MetadataExt;
#[cfg(feature = "io_uring")]
use std::os::unix::io::AsRawFd;
use std::path::Path;
use std::result;
use std::sync::{Arc, Mutex};
use virtio_bindings::bindings::virtio_blk::*;
use vm_memory::{ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryError, GuestMemoryMmap};
use vm_virtio::DescriptorChain;
use vmm_sys_util::eventfd::EventFd;
const SECTOR_SHIFT: u8 = 9;
pub const SECTOR_SIZE: u64 = 0x01 << SECTOR_SHIFT;
#[derive(Debug)]
pub enum Error {
/// Guest gave us bad memory addresses.
GuestMemory(GuestMemoryError),
/// Guest gave us offsets that would have overflowed a usize.
CheckedOffset(GuestAddress, usize),
/// Guest gave us a write only descriptor that protocol says to read from.
UnexpectedWriteOnlyDescriptor,
/// Guest gave us a read only descriptor that protocol says to write to.
UnexpectedReadOnlyDescriptor,
/// Guest gave us too few descriptors in a descriptor chain.
DescriptorChainTooShort,
/// Guest gave us a descriptor that was too short to use.
DescriptorLengthTooSmall,
/// Getting a block's metadata fails for any reason.
GetFileMetadata,
/// The requested operation would cause a seek beyond disk end.
InvalidOffset,
/// 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(Debug)]
pub enum ExecuteError {
BadRequest(Error),
Flush(io::Error),
Read(GuestMemoryError),
Seek(io::Error),
Write(GuestMemoryError),
Unsupported(u32),
SubmitIoUring(io::Error),
GetHostAddress(GuestMemoryError),
AsyncRead(AsyncIoError),
AsyncWrite(AsyncIoError),
AsyncFlush(AsyncIoError),
}
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,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
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 Request {
pub request_type: RequestType,
pub sector: u64,
pub data_descriptors: Vec<(GuestAddress, u32)>,
pub status_addr: GuestAddress,
pub writeback: bool,
}
impl Request {
pub fn parse(
avail_desc: &DescriptorChain,
mem: &GuestMemoryMmap,
) -> result::Result<Request, Error> {
// The head contains the request type which MUST be readable.
if avail_desc.is_write_only() {
return Err(Error::UnexpectedWriteOnlyDescriptor);
}
let mut req = Request {
request_type: request_type(&mem, avail_desc.addr)?,
sector: sector(&mem, avail_desc.addr)?,
data_descriptors: Vec::new(),
status_addr: GuestAddress(0),
writeback: true,
};
let status_desc;
let mut desc = avail_desc
.next_descriptor()
.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 {
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, desc.len));
desc = desc
.next_descriptor()
.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;
Ok(req)
}
#[allow(clippy::ptr_arg)]
pub fn execute<T: Seek + Read + Write>(
&self,
disk: &mut T,
disk_nsectors: u64,
mem: &GuestMemoryMmap,
disk_id: &Vec<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.as_slice(), *data_addr)
.map_err(ExecuteError::Write)?;
}
RequestType::Unsupported(t) => return Err(ExecuteError::Unsupported(t)),
};
}
Ok(len)
}
pub fn execute_async(
&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 = Vec::new();
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(sector)
.ok_or(ExecuteError::BadRequest(Error::InvalidOffset))?;
if top > disk_nsectors {
return Err(ExecuteError::BadRequest(Error::InvalidOffset));
}
let buf = mem
.get_slice(*data_addr, *data_len as usize)
.map_err(ExecuteError::GetHostAddress)?
.as_ptr();
let iovec = libc::iovec {
iov_base: buf as *mut libc::c_void,
iov_len: *data_len as libc::size_t,
};
iovecs.push(iovec);
}
// Queue operations expected to be submitted.
match request_type {
RequestType::In => {
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 set_writeback(&mut self, writeback: bool) {
self.writeback = writeback
}
}
#[derive(Copy, Clone, Debug, Default, Deserialize)]
#[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],
}
// We must explicitly implement Serialize since the structure is packed and
// it's unsafe to borrow from a packed structure. And by default, if we derive
// Serialize from serde, it will borrow the values from the structure.
// That's why this implementation copies each field separately before it
// serializes the entire structure field by field.
impl Serialize for VirtioBlockConfig {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let capacity = self.capacity;
let size_max = self.size_max;
let seg_max = self.seg_max;
let geometry = self.geometry;
let blk_size = self.blk_size;
let physical_block_exp = self.physical_block_exp;
let alignment_offset = self.alignment_offset;
let min_io_size = self.min_io_size;
let opt_io_size = self.opt_io_size;
let writeback = self.writeback;
let unused = self.unused;
let num_queues = self.num_queues;
let max_discard_sectors = self.max_discard_sectors;
let max_discard_seg = self.max_discard_seg;
let discard_sector_alignment = self.discard_sector_alignment;
let max_write_zeroes_sectors = self.max_write_zeroes_sectors;
let max_write_zeroes_seg = self.max_write_zeroes_seg;
let write_zeroes_may_unmap = self.write_zeroes_may_unmap;
let unused1 = self.unused1;
let mut virtio_block_config = serializer.serialize_struct("VirtioBlockConfig", 60)?;
virtio_block_config.serialize_field("capacity", &capacity)?;
virtio_block_config.serialize_field("size_max", &size_max)?;
virtio_block_config.serialize_field("seg_max", &seg_max)?;
virtio_block_config.serialize_field("geometry", &geometry)?;
virtio_block_config.serialize_field("blk_size", &blk_size)?;
virtio_block_config.serialize_field("physical_block_exp", &physical_block_exp)?;
virtio_block_config.serialize_field("alignment_offset", &alignment_offset)?;
virtio_block_config.serialize_field("min_io_size", &min_io_size)?;
virtio_block_config.serialize_field("opt_io_size", &opt_io_size)?;
virtio_block_config.serialize_field("writeback", &writeback)?;
virtio_block_config.serialize_field("unused", &unused)?;
virtio_block_config.serialize_field("num_queues", &num_queues)?;
virtio_block_config.serialize_field("max_discard_sectors", &max_discard_sectors)?;
virtio_block_config.serialize_field("max_discard_seg", &max_discard_seg)?;
virtio_block_config
.serialize_field("discard_sector_alignment", &discard_sector_alignment)?;
virtio_block_config
.serialize_field("max_write_zeroes_sectors", &max_write_zeroes_sectors)?;
virtio_block_config.serialize_field("max_write_zeroes_seg", &max_write_zeroes_seg)?;
virtio_block_config.serialize_field("write_zeroes_may_unmap", &write_zeroes_may_unmap)?;
virtio_block_config.serialize_field("unused1", &unused1)?;
virtio_block_config.end()
}
}
unsafe impl ByteValued for VirtioBlockConfig {}
#[derive(Copy, Clone, Debug, Default, Deserialize)]
#[repr(C, packed)]
pub struct VirtioBlockGeometry {
pub cylinders: u16,
pub heads: u8,
pub sectors: u8,
}
// We must explicitly implement Serialize since the structure is packed and
// it's unsafe to borrow from a packed structure. And by default, if we derive
// Serialize from serde, it will borrow the values from the structure.
// That's why this implementation copies each field separately before it
// serializes the entire structure field by field.
impl Serialize for VirtioBlockGeometry {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let cylinders = self.cylinders;
let heads = self.heads;
let sectors = self.sectors;
let mut virtio_block_geometry = serializer.serialize_struct("VirtioBlockGeometry", 4)?;
virtio_block_geometry.serialize_field("cylinders", &cylinders)?;
virtio_block_geometry.serialize_field("heads", &heads)?;
virtio_block_geometry.serialize_field("sectors", &sectors)?;
virtio_block_geometry.end()
}
}
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.
#[cfg(feature = "io_uring")]
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 event_fd = match EventFd::new(libc::EFD_NONBLOCK) {
Ok(fd) => fd,
Err(e) => {
info!("{} failed to create eventfd: {}", error_msg, e);
return false;
}
};
// Check we can register an eventfd as this is going to be needed while
// using io_uring with the virtio block device. This also validates that
// io_uring_register() syscall is supported.
match submitter.register_eventfd(event_fd.as_raw_fd()) {
Ok(_) => {}
Err(e) => {
info!("{} failed to register eventfd: {}", error_msg, e);
return false;
}
}
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_READ is supported
if !probe.is_supported(opcode::Read::CODE) {
info!("{} IORING_OP_READ operation not supported", error_msg);
return false;
}
// Check IORING_OP_WRITE is supported
if !probe.is_supported(opcode::Write::CODE) {
info!("{} IORING_OP_WRITE operation not supported", error_msg);
return false;
}
true
}
#[cfg(not(feature = "io_uring"))]
pub fn block_io_uring_is_supported() -> bool {
false
}
pub fn disk_size(file: &mut dyn Seek, semaphore: &mut Arc<Mutex<()>>) -> DiskFileResult<u64> {
// Take the semaphore to ensure other threads are not interacting with
// the underlying file.
let _lock = semaphore.lock().unwrap();
Ok(file.seek(SeekFrom::End(0)).map_err(DiskFileError::Size)? as u64)
}
pub trait ReadSeekFile: Read + Seek {}
impl<F: Read + Seek> ReadSeekFile for F {}
pub fn read_vectored_sync(
offset: libc::off_t,
iovecs: Vec<libc::iovec>,
user_data: u64,
file: &mut dyn ReadSeekFile,
eventfd: &EventFd,
completion_list: &mut Vec<(u64, i32)>,
semaphore: &mut Arc<Mutex<()>>,
) -> AsyncIoResult<()> {
// Convert libc::iovec into IoSliceMut
let mut slices = Vec::new();
for iovec in iovecs.iter() {
slices.push(IoSliceMut::new(unsafe { std::mem::transmute(*iovec) }));
}
let result = {
// Take the semaphore to ensure other threads are not interacting
// with the underlying file.
let _lock = semaphore.lock().unwrap();
// 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((user_data, result as i32));
eventfd.write(1).unwrap();
Ok(())
}
pub trait WriteSeekFile: Write + Seek {}
impl<F: Write + Seek> WriteSeekFile for F {}
pub fn write_vectored_sync(
offset: libc::off_t,
iovecs: Vec<libc::iovec>,
user_data: u64,
file: &mut dyn WriteSeekFile,
eventfd: &EventFd,
completion_list: &mut Vec<(u64, i32)>,
semaphore: &mut Arc<Mutex<()>>,
) -> AsyncIoResult<()> {
// Convert libc::iovec into IoSlice
let mut slices = Vec::new();
for iovec in iovecs.iter() {
slices.push(IoSlice::new(unsafe { std::mem::transmute(*iovec) }));
}
let result = {
// Take the semaphore to ensure other threads are not interacting
// with the underlying file.
let _lock = semaphore.lock().unwrap();
// 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((user_data, result as i32));
eventfd.write(1).unwrap();
Ok(())
}
pub fn fsync_sync(
user_data: Option<u64>,
file: &mut dyn Write,
eventfd: &EventFd,
completion_list: &mut Vec<(u64, i32)>,
semaphore: &mut Arc<Mutex<()>>,
) -> AsyncIoResult<()> {
let result: i32 = {
// Take the semaphore to ensure other threads are not interacting
// with the underlying file.
let _lock = semaphore.lock().unwrap();
// Flush
file.flush().map_err(AsyncIoError::Fsync)?;
0
};
if let Some(user_data) = user_data {
completion_list.push((user_data, result));
eventfd.write(1).unwrap();
}
Ok(())
}
pub enum ImageType {
FixedVhd,
Qcow2,
Raw,
}
const QCOW_MAGIC: u32 = 0x5146_49fb;
/// 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 {
ImageType::Raw
};
Ok(image_type)
}

View File

@@ -2,66 +2,60 @@
//
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
use std::collections::VecDeque;
use crate::async_io::{AsyncIo, AsyncIoResult, DiskFile, DiskFileResult};
use crate::{disk_size, fsync_sync, read_vectored_sync, write_vectored_sync};
use qcow::{QcowFile, RawFile};
use std::fs::File;
use std::io::{Seek, SeekFrom};
use std::os::fd::AsRawFd;
use std::sync::{Arc, Mutex};
use vmm_sys_util::eventfd::EventFd;
use crate::AsyncAdaptor;
use crate::async_io::{
AsyncIo, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult,
};
use crate::qcow::{QcowFile, RawFile, Result as QcowResult};
pub struct QcowDiskSync {
qcow_file: QcowFile,
semaphore: Arc<Mutex<()>>,
}
impl QcowDiskSync {
pub fn new(file: File, direct_io: bool) -> QcowResult<Self> {
Ok(QcowDiskSync {
qcow_file: QcowFile::from(RawFile::new(file, direct_io))?,
})
pub fn new(file: File, direct_io: bool) -> Self {
QcowDiskSync {
qcow_file: QcowFile::from(RawFile::new(file, direct_io))
.expect("Failed creating QcowFile"),
semaphore: Arc::new(Mutex::new(())),
}
}
}
impl DiskFile for QcowDiskSync {
fn size(&mut self) -> DiskFileResult<u64> {
self.qcow_file
.seek(SeekFrom::End(0))
.map_err(DiskFileError::Size)
disk_size(&mut self.qcow_file, &mut self.semaphore)
}
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>)
}
fn fd(&mut self) -> BorrowedDiskFd<'_> {
BorrowedDiskFd::new(self.qcow_file.as_raw_fd())
Ok(Box::new(QcowSync::new(
self.qcow_file.clone(),
self.semaphore.clone(),
)) as Box<dyn AsyncIo>)
}
}
pub struct QcowSync {
qcow_file: QcowFile,
eventfd: EventFd,
completion_list: VecDeque<(u64, i32)>,
completion_list: Vec<(u64, i32)>,
semaphore: Arc<Mutex<()>>,
}
impl QcowSync {
pub fn new(qcow_file: QcowFile) -> Self {
pub fn new(qcow_file: QcowFile, semaphore: Arc<Mutex<()>>) -> Self {
QcowSync {
qcow_file,
eventfd: EventFd::new(libc::EFD_NONBLOCK)
.expect("Failed creating EventFd for QcowSync"),
completion_list: VecDeque::new(),
completion_list: Vec::new(),
semaphore,
}
}
}
impl AsyncAdaptor for QcowFile {}
impl AsyncIo for QcowSync {
fn notifier(&self) -> &EventFd {
&self.eventfd
@@ -70,39 +64,48 @@ impl AsyncIo for QcowSync {
fn read_vectored(
&mut self,
offset: libc::off_t,
iovecs: &[libc::iovec],
iovecs: Vec<libc::iovec>,
user_data: u64,
) -> AsyncIoResult<()> {
self.qcow_file.read_vectored_sync(
read_vectored_sync(
offset,
iovecs,
user_data,
&mut self.qcow_file,
&self.eventfd,
&mut self.completion_list,
&mut self.semaphore,
)
}
fn write_vectored(
&mut self,
offset: libc::off_t,
iovecs: &[libc::iovec],
iovecs: Vec<libc::iovec>,
user_data: u64,
) -> AsyncIoResult<()> {
self.qcow_file.write_vectored_sync(
write_vectored_sync(
offset,
iovecs,
user_data,
&mut self.qcow_file,
&self.eventfd,
&mut self.completion_list,
&mut self.semaphore,
)
}
fn fsync(&mut self, user_data: Option<u64>) -> AsyncIoResult<()> {
self.qcow_file
.fsync_sync(user_data, &self.eventfd, &mut self.completion_list)
fsync_sync(
user_data,
&mut self.qcow_file,
&self.eventfd,
&mut self.completion_list,
&mut self.semaphore,
)
}
fn next_completed_request(&mut self) -> Option<(u64, i32)> {
self.completion_list.pop_front()
fn complete(&mut self) -> Vec<(u64, i32)> {
self.completion_list.drain(..).collect()
}
}

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