mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f6557acf60 | ||
|
|
4d041bd603 | ||
|
|
e0fda7bef9 | ||
|
|
9e2d051237 |
4
.github/dependabot.yml
vendored
4
.github/dependabot.yml
vendored
@@ -4,7 +4,7 @@ updates:
|
|||||||
directory: "/"
|
directory: "/"
|
||||||
schedule:
|
schedule:
|
||||||
interval: daily
|
interval: daily
|
||||||
open-pull-requests-limit: 1
|
open-pull-requests-limit: 3
|
||||||
allow:
|
allow:
|
||||||
- dependency-type: direct
|
- dependency-type: direct
|
||||||
- dependency-type: indirect
|
- dependency-type: indirect
|
||||||
@@ -12,7 +12,7 @@ updates:
|
|||||||
directory: "/fuzz"
|
directory: "/fuzz"
|
||||||
schedule:
|
schedule:
|
||||||
interval: daily
|
interval: daily
|
||||||
open-pull-requests-limit: 1
|
open-pull-requests-limit: 3
|
||||||
allow:
|
allow:
|
||||||
- dependency-type: direct
|
- dependency-type: direct
|
||||||
- dependency-type: indirect
|
- dependency-type: indirect
|
||||||
|
|||||||
7
.github/workflows/audit.yaml
vendored
7
.github/workflows/audit.yaml
vendored
@@ -4,13 +4,12 @@ on:
|
|||||||
paths:
|
paths:
|
||||||
- '**/Cargo.toml'
|
- '**/Cargo.toml'
|
||||||
- '**/Cargo.lock'
|
- '**/Cargo.lock'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
security_audit:
|
security_audit:
|
||||||
name: Audit
|
name: Audit
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v1
|
||||||
- uses: actions-rust-lang/audit@v1
|
- uses: actions-rs/audit-check@v1
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
59
.github/workflows/build.yaml
vendored
59
.github/workflows/build.yaml
vendored
@@ -1,11 +1,9 @@
|
|||||||
name: Cloud Hypervisor Build
|
name: Cloud Hypervisor Build
|
||||||
on: [pull_request, merge_group]
|
on: [pull_request, create]
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
if: github.event_name == 'pull_request'
|
||||||
name: Build
|
name: Build
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
@@ -15,13 +13,13 @@ jobs:
|
|||||||
- stable
|
- stable
|
||||||
- beta
|
- beta
|
||||||
- nightly
|
- nightly
|
||||||
- "1.82.0"
|
- 1.54
|
||||||
target:
|
target:
|
||||||
- x86_64-unknown-linux-gnu
|
- x86_64-unknown-linux-gnu
|
||||||
- x86_64-unknown-linux-musl
|
- x86_64-unknown-linux-musl
|
||||||
steps:
|
steps:
|
||||||
- name: Code checkout
|
- name: Code checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
@@ -29,43 +27,34 @@ jobs:
|
|||||||
run: sudo apt install -y musl-tools
|
run: sudo apt install -y musl-tools
|
||||||
|
|
||||||
- name: Install Rust toolchain (${{ matrix.rust }})
|
- name: Install Rust toolchain (${{ matrix.rust }})
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
toolchain: ${{ matrix.rust }}
|
toolchain: ${{ matrix.rust }}
|
||||||
target: ${{ matrix.target }}
|
target: ${{ matrix.target }}
|
||||||
|
override: true
|
||||||
|
|
||||||
- name: Build (default features)
|
- name: Debug Check (default features)
|
||||||
run: cargo rustc --locked --bin cloud-hypervisor -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
run: |
|
||||||
|
git rev-list origin/main..$GITHUB_SHA | xargs -t -I % sh -c 'git checkout %; cargo check --all --target=${{ matrix.target }}'
|
||||||
|
git checkout $GITHUB_SHA
|
||||||
|
|
||||||
|
- name: Build (default + tdx)
|
||||||
|
run: cargo rustc --bin cloud-hypervisor --features "tdx" -- -D warnings
|
||||||
|
|
||||||
|
- name: Build (acpi,kvm)
|
||||||
|
run: cargo rustc --bin cloud-hypervisor --no-default-features --features "acpi,kvm" -- -D warnings
|
||||||
|
|
||||||
- name: Build (kvm)
|
- name: Build (kvm)
|
||||||
run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
run: cargo rustc --bin cloud-hypervisor --no-default-features --features "kvm" -- -D warnings
|
||||||
|
|
||||||
- name: Build (default features + tdx)
|
- name: Build (acpi,mshv)
|
||||||
run: cargo rustc --locked --bin cloud-hypervisor --features "tdx" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
run: cargo rustc --bin cloud-hypervisor --no-default-features --features "acpi,mshv" -- -D warnings
|
||||||
|
|
||||||
- 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 (mshv)
|
- name: Build (mshv)
|
||||||
run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "mshv" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
run: cargo rustc --bin cloud-hypervisor --no-default-features --features "mshv" -- -D warnings
|
||||||
|
|
||||||
- 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
|
|
||||||
|
|
||||||
- name: Release Build (default features)
|
- name: Release Build (default features)
|
||||||
run: cargo build --locked --all --release --target=${{ matrix.target }}
|
run: cargo build --all --release --target=${{ matrix.target }}
|
||||||
|
|
||||||
- name: Check build did not modify any files
|
- name: Check build did not modify any files
|
||||||
run: test -z "$(git status --porcelain)"
|
run: test -z "$(git status --porcelain)"
|
||||||
|
|||||||
8
.github/workflows/dco.yaml
vendored
8
.github/workflows/dco.yaml
vendored
@@ -1,18 +1,16 @@
|
|||||||
name: DCO
|
name: DCO
|
||||||
on: [pull_request, merge_group]
|
on:
|
||||||
|
pull_request:
|
||||||
jobs:
|
jobs:
|
||||||
check:
|
check:
|
||||||
name: DCO Check ("Signed-Off-By")
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v2
|
||||||
- name: Set up Python 3.x
|
- name: Set up Python 3.x
|
||||||
uses: actions/setup-python@v1
|
uses: actions/setup-python@v1
|
||||||
with:
|
with:
|
||||||
python-version: '3.x'
|
python-version: '3.x'
|
||||||
- name: Check DCO
|
- name: Check DCO
|
||||||
if: ${{ github.event_name == 'pull_request' }}
|
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
36
.github/workflows/docker-image.yaml
vendored
36
.github/workflows/docker-image.yaml
vendored
@@ -1,52 +1,46 @@
|
|||||||
name: Cloud Hypervisor's Docker image update
|
name: Cloud Hypervisor's Docker image update
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: main
|
branches: main
|
||||||
paths: resources/Dockerfile
|
paths: resources/Dockerfile
|
||||||
pull_request:
|
pull_request:
|
||||||
paths: resources/Dockerfile
|
paths: resources/Dockerfile
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
env:
|
|
||||||
REGISTRY: ghcr.io
|
|
||||||
IMAGE_NAME: ${{ github.repository }}
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
main:
|
main:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Code checkout
|
- name: Code checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v1
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v1
|
||||||
|
|
||||||
- name: Login to ghcr
|
- name: Login to DockerHub
|
||||||
uses: docker/login-action@v3
|
if: ${{ github.event_name == 'push' }}
|
||||||
|
uses: docker/login-action@v1
|
||||||
with:
|
with:
|
||||||
registry: ${{ env.REGISTRY }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
username: ${{ github.actor }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
|
|
||||||
- name: Docker meta
|
- name: Docker meta
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@v5
|
uses: docker/metadata-action@v3
|
||||||
with:
|
with:
|
||||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
# list of Docker images to use as base name for tags
|
||||||
|
images: cloudhypervisor/dev
|
||||||
# generate Docker tags based on the following events/attributes
|
# generate Docker tags based on the following events/attributes
|
||||||
tags: |
|
tags: |
|
||||||
type=raw,value=20250307-2
|
type=raw,value={{date 'YYYYMMDD'}}-0
|
||||||
type=sha
|
type=sha
|
||||||
|
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
if: ${{ github.event_name == 'push' }}
|
if: ${{ github.event_name == 'push' }}
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v2
|
||||||
with:
|
with:
|
||||||
file: ./resources/Dockerfile
|
file: ./resources/Dockerfile
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
@@ -55,7 +49,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Build only
|
- name: Build only
|
||||||
if: ${{ github.event_name == 'pull_request' }}
|
if: ${{ github.event_name == 'pull_request' }}
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v2
|
||||||
with:
|
with:
|
||||||
file: ./resources/Dockerfile
|
file: ./resources/Dockerfile
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
|
|||||||
32
.github/workflows/formatting.yaml
vendored
32
.github/workflows/formatting.yaml
vendored
@@ -1,32 +0,0 @@
|
|||||||
name: Cloud Hypervisor Code Formatting
|
|
||||||
on: [pull_request, merge_group]
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
name: Code Formatting
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
rust:
|
|
||||||
- nightly
|
|
||||||
target:
|
|
||||||
- x86_64-unknown-linux-gnu
|
|
||||||
- aarch64-unknown-linux-musl
|
|
||||||
env:
|
|
||||||
RUSTFLAGS: -D warnings
|
|
||||||
steps:
|
|
||||||
- name: Code checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
- name: Install Rust toolchain (${{ matrix.rust }})
|
|
||||||
uses: dtolnay/rust-toolchain@stable
|
|
||||||
with:
|
|
||||||
toolchain: ${{ matrix.rust }}
|
|
||||||
target: ${{ matrix.target }}
|
|
||||||
components: rustfmt
|
|
||||||
- name: Formatting (rustfmt)
|
|
||||||
run: cargo fmt --all -- --check
|
|
||||||
- name: Formatting (fuzz) (rustfmt)
|
|
||||||
run: cargo fmt --all --manifest-path fuzz/Cargo.toml -- --check
|
|
||||||
21
.github/workflows/fuzz-build.yaml
vendored
21
.github/workflows/fuzz-build.yaml
vendored
@@ -1,11 +1,9 @@
|
|||||||
name: Cloud Hypervisor Cargo Fuzz Build
|
name: Cloud Hypervisor Cargo Fuzz Build
|
||||||
on: [pull_request, merge_group]
|
on: [pull_request, create]
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
if: github.event_name == 'pull_request'
|
||||||
name: Cargo Fuzz Build
|
name: Cargo Fuzz Build
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
@@ -14,19 +12,18 @@ jobs:
|
|||||||
- nightly
|
- nightly
|
||||||
target:
|
target:
|
||||||
- x86_64-unknown-linux-gnu
|
- x86_64-unknown-linux-gnu
|
||||||
env:
|
|
||||||
RUSTFLAGS: -D warnings
|
|
||||||
steps:
|
steps:
|
||||||
- name: Code checkout
|
- name: Code checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v2
|
||||||
- name: Install Rust toolchain (${{ matrix.rust }})
|
- name: Install Rust toolchain (${{ matrix.rust }})
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
toolchain: ${{ matrix.rust }}
|
toolchain: ${{ matrix.rust }}
|
||||||
target: ${{ matrix.target }}
|
target: ${{ matrix.target }}
|
||||||
|
override: true
|
||||||
- name: Install Cargo fuzz
|
- name: Install Cargo fuzz
|
||||||
run: cargo install cargo-fuzz
|
# Temporary fix for cargo-fuzz on latest nightly: https://github.com/rust-fuzz/cargo-fuzz/issues/276
|
||||||
- name: Fuzz Build
|
#run: cargo install cargo-fuzz
|
||||||
|
run: cargo install --git https://github.com/rust-fuzz/cargo-fuzz --rev b4df3e58f767b5cad8d1aa6753961003f56f3609
|
||||||
|
- name: Cargo Fuzz Build
|
||||||
run: cargo fuzz build
|
run: cargo fuzz build
|
||||||
- name: Fuzz Check
|
|
||||||
run: cargo fuzz check
|
|
||||||
|
|||||||
25
.github/workflows/gitlint.yaml
vendored
25
.github/workflows/gitlint.yaml
vendored
@@ -1,25 +0,0 @@
|
|||||||
name: Commit messages check
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
gitlint:
|
|
||||||
name: Check commit messages
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
ref: ${{ github.event.pull_request.head.sha }}
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Set up Python 3.10
|
|
||||||
uses: actions/setup-python@v3
|
|
||||||
with:
|
|
||||||
python-version: "3.10"
|
|
||||||
- name: Install dependencies
|
|
||||||
run: |
|
|
||||||
python -m pip install --upgrade pip
|
|
||||||
pip install --upgrade gitlint
|
|
||||||
- name: Lint git commit messages
|
|
||||||
run: |
|
|
||||||
gitlint --commits origin/$GITHUB_BASE_REF..
|
|
||||||
25
.github/workflows/hadolint.yaml
vendored
25
.github/workflows/hadolint.yaml
vendored
@@ -1,25 +0,0 @@
|
|||||||
name: Lint Dockerfile
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
paths:
|
|
||||||
- resources/Dockerfile
|
|
||||||
pull_request:
|
|
||||||
paths:
|
|
||||||
- resources/Dockerfile
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
hadolint:
|
|
||||||
name: Run Hadolint Dockerfile Linter
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Lint Dockerfile
|
|
||||||
uses: hadolint/hadolint-action@master
|
|
||||||
with:
|
|
||||||
dockerfile: ./resources/Dockerfile
|
|
||||||
format: tty
|
|
||||||
no-fail: false
|
|
||||||
verbose: true
|
|
||||||
failure-threshold: info
|
|
||||||
54
.github/workflows/integration-arm64.yaml
vendored
54
.github/workflows/integration-arm64.yaml
vendored
@@ -1,54 +0,0 @@
|
|||||||
name: Cloud Hypervisor Tests (ARM64)
|
|
||||||
on: [pull_request, merge_group]
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
timeout-minutes: 120
|
|
||||||
name: Tests (ARM64)
|
|
||||||
runs-on: bookworm-arm64
|
|
||||||
steps:
|
|
||||||
- name: Fix workspace permissions
|
|
||||||
run: sudo chown -R runner:runner ${GITHUB_WORKSPACE}
|
|
||||||
- name: Code checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Run unit tests (musl)
|
|
||||||
run: scripts/dev_cli.sh tests --unit --libc musl
|
|
||||||
- name: Load openvswitch module
|
|
||||||
run: sudo modprobe openvswitch
|
|
||||||
- name: Run integration tests (musl)
|
|
||||||
timeout-minutes: 60
|
|
||||||
run: scripts/dev_cli.sh tests --integration --libc musl
|
|
||||||
- name: Install Azure CLI
|
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
|
||||||
run: |
|
|
||||||
sudo apt install -y ca-certificates curl apt-transport-https lsb-release gnupg
|
|
||||||
curl -sL https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/microsoft.gpg > /dev/null
|
|
||||||
echo "deb [arch=arm64] https://packages.microsoft.com/repos/azure-cli/ bookworm main" | sudo tee /etc/apt/sources.list.d/azure-cli.list
|
|
||||||
sudo apt update
|
|
||||||
sudo apt install -y azure-cli
|
|
||||||
- name: Download Windows image
|
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
IMG_BASENAME=windows-11-iot-enterprise-aarch64.raw
|
|
||||||
IMG_PATH=$HOME/workloads/$IMG_BASENAME
|
|
||||||
IMG_GZ_PATH=$HOME/workloads/$IMG_BASENAME.gz
|
|
||||||
IMG_GZ_BLOB_NAME=windows-11-iot-enterprise-aarch64-9-min.raw.gz
|
|
||||||
cp "scripts/$IMG_BASENAME.sha1" "$HOME/workloads/"
|
|
||||||
pushd "$HOME/workloads"
|
|
||||||
if sha1sum "$IMG_BASENAME.sha1" --check; then
|
|
||||||
exit
|
|
||||||
fi
|
|
||||||
popd
|
|
||||||
mkdir -p "$HOME/workloads"
|
|
||||||
az storage blob download --container-name private-images --file "$IMG_GZ_PATH" --name "$IMG_GZ_BLOB_NAME" --connection-string "${{ secrets.CH_PRIVATE_IMAGES }}"
|
|
||||||
gzip -d $IMG_GZ_PATH
|
|
||||||
- name: Run Windows guest integration tests
|
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
|
||||||
timeout-minutes: 30
|
|
||||||
run: scripts/dev_cli.sh tests --integration-windows --libc musl
|
|
||||||
22
.github/workflows/integration-metrics.yaml
vendored
22
.github/workflows/integration-metrics.yaml
vendored
@@ -1,22 +0,0 @@
|
|||||||
name: Cloud Hypervisor Tests (Metrics)
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
name: Tests (Metrics)
|
|
||||||
runs-on: bare-metal-9950x
|
|
||||||
env:
|
|
||||||
METRICS_PUBLISH_KEY: ${{ secrets.METRICS_PUBLISH_KEY }}
|
|
||||||
steps:
|
|
||||||
- name: Code checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Run metrics tests
|
|
||||||
timeout-minutes: 60
|
|
||||||
run: scripts/dev_cli.sh tests --metrics -- -- --report-file /root/workloads/metrics.json
|
|
||||||
- name: Upload metrics report
|
|
||||||
run: 'curl -X PUT https://ch-metrics.azurewebsites.net/api/publishmetrics -H "x-functions-key: $METRICS_PUBLISH_KEY" -T ~/workloads/metrics.json'
|
|
||||||
25
.github/workflows/integration-rate-limiter.yaml
vendored
25
.github/workflows/integration-rate-limiter.yaml
vendored
@@ -1,25 +0,0 @@
|
|||||||
name: Cloud Hypervisor Tests (Rate-Limiter)
|
|
||||||
on: [merge_group, pull_request]
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
name: Tests (Rate-Limiter)
|
|
||||||
runs-on: ${{ github.event_name == 'pull_request' && 'ubuntu-latest' || 'bare-metal-9950x' }}
|
|
||||||
env:
|
|
||||||
AUTH_DOWNLOAD_TOKEN: ${{ secrets.AUTH_DOWNLOAD_TOKEN }}
|
|
||||||
steps:
|
|
||||||
- name: Code checkout
|
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Run rate-limiter integration tests
|
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
|
||||||
timeout-minutes: 20
|
|
||||||
run: scripts/dev_cli.sh tests --integration-rate-limiter
|
|
||||||
- name: Skipping build for PR
|
|
||||||
if: ${{ github.event_name == 'pull_request' }}
|
|
||||||
run: echo "Skipping build for PR"
|
|
||||||
33
.github/workflows/integration-vfio.yaml
vendored
33
.github/workflows/integration-vfio.yaml
vendored
@@ -1,33 +0,0 @@
|
|||||||
name: Cloud Hypervisor Tests (VFIO)
|
|
||||||
on: [merge_group, pull_request]
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
name: Tests (VFIO)
|
|
||||||
runs-on: ${{ github.event_name == 'pull_request' && 'ubuntu-latest' || 'vfio-nvidia' }}
|
|
||||||
env:
|
|
||||||
AUTH_DOWNLOAD_TOKEN: ${{ secrets.AUTH_DOWNLOAD_TOKEN }}
|
|
||||||
steps:
|
|
||||||
- name: Fix workspace permissions
|
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
|
||||||
run: sudo chown -R runner:runner ${GITHUB_WORKSPACE}
|
|
||||||
- name: Code checkout
|
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Run VFIO integration tests
|
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
|
||||||
timeout-minutes: 15
|
|
||||||
run: scripts/dev_cli.sh tests --integration-vfio
|
|
||||||
# Most tests are failing with musl see #6790
|
|
||||||
# - name: Run VFIO integration tests for musl
|
|
||||||
# if: ${{ github.event_name != 'pull_request' }}
|
|
||||||
# timeout-minutes: 15
|
|
||||||
# run: scripts/dev_cli.sh tests --integration-vfio --libc musl
|
|
||||||
- name: Skipping build for PR
|
|
||||||
if: ${{ github.event_name == 'pull_request' }}
|
|
||||||
run: echo "Skipping build for PR"
|
|
||||||
50
.github/workflows/integration-windows.yaml
vendored
50
.github/workflows/integration-windows.yaml
vendored
@@ -1,50 +0,0 @@
|
|||||||
name: Cloud Hypervisor Tests (Windows Guest)
|
|
||||||
on: [merge_group, pull_request]
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
name: Tests (Windows Guest)
|
|
||||||
runs-on: ${{ github.event_name == 'pull_request' && 'ubuntu-latest' || 'garm-jammy-16' }}
|
|
||||||
steps:
|
|
||||||
- name: Code checkout
|
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Install Docker
|
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
|
||||||
run: |
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get -y install ca-certificates curl gnupg
|
|
||||||
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
|
|
||||||
sudo chmod a+r /usr/share/keyrings/docker-archive-keyring.gpg
|
|
||||||
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt install -y docker-ce docker-ce-cli
|
|
||||||
- name: Install Azure CLI
|
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
|
||||||
run: |
|
|
||||||
sudo apt install -y ca-certificates curl apt-transport-https lsb-release gnupg
|
|
||||||
curl -sL https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/microsoft.gpg > /dev/null
|
|
||||||
echo "deb [arch=amd64] https://packages.microsoft.com/repos/azure-cli/ jammy main" | sudo tee /etc/apt/sources.list.d/azure-cli.list
|
|
||||||
sudo apt update
|
|
||||||
sudo apt install -y azure-cli
|
|
||||||
- name: Download Windows image
|
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
|
||||||
run: |
|
|
||||||
mkdir $HOME/workloads
|
|
||||||
az storage blob download --container-name private-images --file "$HOME/workloads/windows-server-2022-amd64-2.raw" --name windows-server-2022-amd64-2.raw --connection-string "${{ secrets.CH_PRIVATE_IMAGES }}"
|
|
||||||
- name: Run Windows guest integration tests
|
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
|
||||||
timeout-minutes: 15
|
|
||||||
run: scripts/dev_cli.sh tests --integration-windows
|
|
||||||
- name: Run Windows guest integration tests for musl
|
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
|
||||||
timeout-minutes: 15
|
|
||||||
run: scripts/dev_cli.sh tests --integration-windows --libc musl
|
|
||||||
- name: Skipping build for PR
|
|
||||||
if: ${{ github.event_name == 'pull_request' }}
|
|
||||||
run: echo "Skipping build for PR"
|
|
||||||
52
.github/workflows/integration-x86-64.yaml
vendored
52
.github/workflows/integration-x86-64.yaml
vendored
@@ -1,52 +0,0 @@
|
|||||||
name: Cloud Hypervisor Tests (x86-64)
|
|
||||||
on: [pull_request, merge_group]
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
timeout-minutes: 60
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
runner: ['garm-jammy', "garm-jammy-amd"]
|
|
||||||
libc: ["musl", 'gnu']
|
|
||||||
name: Tests (x86-64)
|
|
||||||
runs-on: ${{ github.event_name == 'pull_request' && !(matrix.runner == 'garm-jammy' && matrix.libc == 'gnu') && 'ubuntu-latest' || format('{0}-16', matrix.runner) }}
|
|
||||||
steps:
|
|
||||||
- name: Code checkout
|
|
||||||
if: ${{ github.event_name != 'pull_request' || (matrix.runner == 'garm-jammy' && matrix.libc == 'gnu') }}
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Install Docker
|
|
||||||
if: ${{ github.event_name != 'pull_request' || (matrix.runner == 'garm-jammy' && matrix.libc == 'gnu') }}
|
|
||||||
run: |
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get -y install ca-certificates curl gnupg
|
|
||||||
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
|
|
||||||
sudo chmod a+r /usr/share/keyrings/docker-archive-keyring.gpg
|
|
||||||
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt install -y docker-ce docker-ce-cli
|
|
||||||
- name: Prepare for VDPA
|
|
||||||
if: ${{ github.event_name != 'pull_request' || (matrix.runner == 'garm-jammy' && matrix.libc == 'gnu') }}
|
|
||||||
run: scripts/prepare_vdpa.sh
|
|
||||||
- name: Run unit tests
|
|
||||||
if: ${{ github.event_name != 'pull_request' || (matrix.runner == 'garm-jammy' && matrix.libc == 'gnu') }}
|
|
||||||
run: scripts/dev_cli.sh tests --unit --libc ${{ matrix.libc }}
|
|
||||||
- name: Load openvswitch module
|
|
||||||
if: ${{ github.event_name != 'pull_request' || (matrix.runner == 'garm-jammy' && matrix.libc == 'gnu') }}
|
|
||||||
run: sudo modprobe openvswitch
|
|
||||||
- name: Run integration tests
|
|
||||||
if: ${{ github.event_name != 'pull_request' || (matrix.runner == 'garm-jammy' && matrix.libc == 'gnu') }}
|
|
||||||
timeout-minutes: 40
|
|
||||||
run: scripts/dev_cli.sh tests --integration --libc ${{ matrix.libc }}
|
|
||||||
- name: Run live-migration integration tests
|
|
||||||
if: ${{ github.event_name != 'pull_request' || (matrix.runner == 'garm-jammy' && matrix.libc == 'gnu') }}
|
|
||||||
timeout-minutes: 20
|
|
||||||
run: scripts/dev_cli.sh tests --integration-live-migration --libc ${{ matrix.libc }}
|
|
||||||
- name: Skipping build for PR
|
|
||||||
if: ${{ github.event_name == 'pull_request' && matrix.runner != 'garm-jammy' && matrix.libc != 'gnu' }}
|
|
||||||
run: echo "Skipping build for PR"
|
|
||||||
16
.github/workflows/lychee.yaml
vendored
16
.github/workflows/lychee.yaml
vendored
@@ -1,16 +0,0 @@
|
|||||||
name: Link Check (lychee)
|
|
||||||
on:
|
|
||||||
pull_request
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
link_check:
|
|
||||||
name: Link Check
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Code checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Link Availability Check
|
|
||||||
uses: lycheeverse/lychee-action@master
|
|
||||||
with:
|
|
||||||
args: --verbose --config .lychee.toml .
|
|
||||||
14
.github/workflows/openapi.yaml
vendored
14
.github/workflows/openapi.yaml
vendored
@@ -1,14 +0,0 @@
|
|||||||
name: Cloud Hypervisor OpenAPI Validation
|
|
||||||
on: [pull_request, merge_group]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
Validate:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
container: openapitools/openapi-generator-cli
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- name: Validate OpenAPI
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
run: |
|
|
||||||
/usr/local/bin/docker-entrypoint.sh validate -i vmm/src/api/openapi/cloud-hypervisor.yaml
|
|
||||||
32
.github/workflows/package-consistency.yaml
vendored
32
.github/workflows/package-consistency.yaml
vendored
@@ -1,32 +0,0 @@
|
|||||||
name: Cloud Hypervisor Consistency
|
|
||||||
on: [pull_request, merge_group]
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
name: Rust VMM Consistency Check
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Code checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: sudo apt install -y python3
|
|
||||||
|
|
||||||
- name: Install Rust toolchain stable
|
|
||||||
uses: dtolnay/rust-toolchain@stable
|
|
||||||
with:
|
|
||||||
toolchain: stable
|
|
||||||
|
|
||||||
- name: Check Rust VMM Package Consistency of root Workspace
|
|
||||||
run: python3 scripts/package-consistency-check.py github.com/rust-vmm
|
|
||||||
|
|
||||||
- name: Check Rust VMM Package Consistency of fuzz Workspace
|
|
||||||
run: |
|
|
||||||
pushd fuzz
|
|
||||||
python3 ../scripts/package-consistency-check.py github.com/rust-vmm
|
|
||||||
popd
|
|
||||||
39
.github/workflows/preview-riscv64.yaml
vendored
39
.github/workflows/preview-riscv64.yaml
vendored
@@ -1,39 +0,0 @@
|
|||||||
name: Cloud Hypervisor RISC-V 64-bit Preview
|
|
||||||
on: [pull_request, merge_group]
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
name: Cargo
|
|
||||||
runs-on: riscv64-qemu-host
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
module:
|
|
||||||
- hypervisor
|
|
||||||
- arch
|
|
||||||
- vm-allocator
|
|
||||||
- devices
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Code checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Install Rust toolchain
|
|
||||||
run: /opt/scripts/exec-in-qemu.sh rustup default 1.82.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)"
|
|
||||||
61
.github/workflows/quality-aarch64.yaml
vendored
Normal file
61
.github/workflows/quality-aarch64.yaml
vendored
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
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
|
||||||
|
continue-on-error: ${{ matrix.experimental }}
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
rust:
|
||||||
|
- stable
|
||||||
|
target:
|
||||||
|
- aarch64-unknown-linux-gnu
|
||||||
|
experimental: [false]
|
||||||
|
include:
|
||||||
|
- rust: beta
|
||||||
|
target: aarch64-unknown-linux-gnu
|
||||||
|
experimental: true
|
||||||
|
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: Formatting (rustfmt)
|
||||||
|
run: cargo fmt -- --check
|
||||||
|
|
||||||
|
- name: Clippy (kvm)
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
use-cross: true
|
||||||
|
command: clippy
|
||||||
|
args: --target=${{ matrix.target }} --all --no-default-features --features "kvm" -- -D warnings
|
||||||
|
|
||||||
|
- name: Clippy (kvm,acpi)
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
use-cross: true
|
||||||
|
command: clippy
|
||||||
|
args: --target=${{ matrix.target }} --all --no-default-features --features "kvm,acpi" -- -D warnings
|
||||||
|
|
||||||
|
- name: Clippy (all features,kvm)
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
use-cross: true
|
||||||
|
command: clippy
|
||||||
|
args: --target=${{ matrix.target }} --all --no-default-features --features "common,kvm" -- -D warnings
|
||||||
|
|
||||||
|
- name: Clippy (default)
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
use-cross: true
|
||||||
|
command: clippy
|
||||||
|
args: --target=${{ matrix.target }} --all -- -D warnings
|
||||||
125
.github/workflows/quality.yaml
vendored
125
.github/workflows/quality.yaml
vendored
@@ -1,132 +1,61 @@
|
|||||||
name: Cloud Hypervisor Quality Checks
|
name: Cloud Hypervisor Quality Checks
|
||||||
on: [pull_request, merge_group]
|
on: [pull_request, create]
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: Quality (clippy)
|
if: github.event_name == 'pull_request'
|
||||||
|
name: Quality (clippy, rustfmt)
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
continue-on-error: ${{ matrix.experimental }}
|
continue-on-error: ${{ matrix.experimental }}
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
rust:
|
rust:
|
||||||
- beta
|
|
||||||
- stable
|
- stable
|
||||||
target:
|
target:
|
||||||
- aarch64-unknown-linux-gnu
|
|
||||||
- aarch64-unknown-linux-musl
|
|
||||||
- x86_64-unknown-linux-gnu
|
- x86_64-unknown-linux-gnu
|
||||||
- x86_64-unknown-linux-musl
|
experimental: [false]
|
||||||
|
|
||||||
include:
|
include:
|
||||||
- rust: beta
|
- rust: beta
|
||||||
|
target: x86_64-unknown-linux-gnu
|
||||||
experimental: true
|
experimental: true
|
||||||
- rust: stable
|
|
||||||
experimental: false
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Code checkout
|
- name: Code checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v2
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Install Rust toolchain (${{ matrix.rust }})
|
- name: Install Rust toolchain (${{ matrix.rust }})
|
||||||
uses: actions-rs/toolchain@v1
|
uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
toolchain: ${{ matrix.rust }}
|
toolchain: ${{ matrix.rust }}
|
||||||
target: ${{ matrix.target }}
|
target: ${{ matrix.target }}
|
||||||
override: true
|
override: true
|
||||||
components: clippy
|
components: rustfmt, clippy
|
||||||
|
|
||||||
- name: Bisectability Check (default features)
|
- name: Formatting (rustfmt)
|
||||||
if: ${{ github.event_name == 'pull_request' && matrix.target == 'x86_64-unknown-linux-gnu' }}
|
run: cargo fmt -- --check
|
||||||
run: |
|
|
||||||
set -e
|
- name: Clippy (all features,kvm)
|
||||||
commits=$(git rev-list origin/${{ github.base_ref }}..${{ github.sha }})
|
run: cargo clippy --all --all-targets --no-default-features --tests --features "common,kvm" -- -D warnings
|
||||||
for commit in $commits; do git checkout $commit; cargo check --tests --examples --all --target=${{ matrix.target }}; done
|
|
||||||
git checkout ${{ github.sha }}
|
- 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)
|
- name: Clippy (kvm)
|
||||||
uses: actions-rs/cargo@v1
|
run: cargo clippy --all --all-targets --no-default-features --tests --features "kvm" -- -D warnings
|
||||||
with:
|
|
||||||
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
|
|
||||||
command: clippy
|
|
||||||
args: --target=${{ matrix.target }} --locked --all --all-targets --no-default-features --tests --examples --features "kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
|
||||||
|
|
||||||
- name: Clippy (default features)
|
- name: Clippy (acpi,mshv)
|
||||||
uses: actions-rs/cargo@v1
|
run: cargo clippy --all --all-targets --no-default-features --tests --features "acpi,mshv" -- -D warnings
|
||||||
with:
|
|
||||||
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
|
|
||||||
command: clippy
|
|
||||||
args: --target=${{ matrix.target }} --locked --all --all-targets --tests --examples -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
|
||||||
|
|
||||||
- name: Clippy (default features + guest_debug)
|
|
||||||
uses: actions-rs/cargo@v1
|
|
||||||
with:
|
|
||||||
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
|
|
||||||
command: clippy
|
|
||||||
args: --target=${{ matrix.target }} --locked --all --all-targets --tests --examples --features "guest_debug" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
|
||||||
|
|
||||||
- name: Clippy (default features + pvmemcontrol)
|
|
||||||
uses: actions-rs/cargo@v1
|
|
||||||
with:
|
|
||||||
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
|
|
||||||
command: clippy
|
|
||||||
args: --target=${{ matrix.target }} --locked --all --all-targets --tests --examples --features "pvmemcontrol" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
|
||||||
|
|
||||||
- name: Clippy (default features + tracing)
|
|
||||||
uses: actions-rs/cargo@v1
|
|
||||||
with:
|
|
||||||
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
|
|
||||||
command: clippy
|
|
||||||
args: --target=${{ matrix.target }} --locked --all --all-targets --tests --examples --features "tracing" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
|
||||||
|
|
||||||
- name: Clippy (mshv)
|
- name: Clippy (mshv)
|
||||||
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
|
run: cargo clippy --all --all-targets --no-default-features --tests --features "mshv" -- -D warnings
|
||||||
uses: actions-rs/cargo@v1
|
|
||||||
with:
|
|
||||||
command: clippy
|
|
||||||
args: --target=${{ matrix.target }} --locked --all --all-targets --no-default-features --tests --examples --features "mshv" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
|
||||||
|
|
||||||
- name: Clippy (mshv + kvm)
|
- name: Clippy (integration tests)
|
||||||
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
|
run: cargo clippy --all --all-targets --tests -- -D warnings
|
||||||
uses: actions-rs/cargo@v1
|
|
||||||
with:
|
|
||||||
command: clippy
|
|
||||||
args: --target=${{ matrix.target }} --locked --all --all-targets --no-default-features --tests --examples --features "mshv,kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
|
||||||
|
|
||||||
- name: Clippy (sev_snp)
|
|
||||||
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
|
|
||||||
uses: actions-rs/cargo@v1
|
|
||||||
with:
|
|
||||||
command: clippy
|
|
||||||
args: --target=${{ matrix.target }} --locked --all --all-targets --no-default-features --tests --examples --features "sev_snp" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
|
||||||
|
|
||||||
- name: Clippy (igvm)
|
|
||||||
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
|
|
||||||
uses: actions-rs/cargo@v1
|
|
||||||
with:
|
|
||||||
command: clippy
|
|
||||||
args: --target=${{ matrix.target }} --locked --all --all-targets --no-default-features --tests --examples --features "igvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
|
||||||
|
|
||||||
- name: Clippy (kvm + tdx)
|
|
||||||
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
|
|
||||||
uses: actions-rs/cargo@v1
|
|
||||||
with:
|
|
||||||
command: clippy
|
|
||||||
args: --target=${{ matrix.target }} --locked --all --all-targets --no-default-features --tests --examples --features "tdx,kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
|
||||||
|
|
||||||
- name: Check build did not modify any files
|
- name: Check build did not modify any files
|
||||||
run: test -z "$(git status --porcelain)"
|
run: test -z "$(git status --porcelain)"
|
||||||
|
|
||||||
typos:
|
|
||||||
if: github.event_name == 'pull_request'
|
|
||||||
name: Typos / Spellcheck
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
# Executes "typos ."
|
|
||||||
- uses: crate-ci/typos@v1.16.11
|
|
||||||
|
|||||||
186
.github/workflows/release.yaml
vendored
186
.github/workflows/release.yaml
vendored
@@ -1,95 +1,133 @@
|
|||||||
name: Cloud Hypervisor Release
|
name: Cloud Hypervisor Release
|
||||||
on: [create, merge_group]
|
on: [create]
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ github.token }}
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
if: (github.event_name == 'create' && github.event.ref_type == 'tag') || github.event_name == 'merge_group'
|
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
||||||
name: Release ${{ matrix.platform.target }}
|
name: Release
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
platform:
|
|
||||||
- target: x86_64-unknown-linux-gnu
|
|
||||||
args: --all --release --features mshv
|
|
||||||
name_ch: cloud-hypervisor
|
|
||||||
name_ch_remote: ch-remote
|
|
||||||
- target: x86_64-unknown-linux-musl
|
|
||||||
args: --all --release --features mshv
|
|
||||||
name_ch: cloud-hypervisor-static
|
|
||||||
name_ch_remote: ch-remote-static
|
|
||||||
- target: aarch64-unknown-linux-musl
|
|
||||||
args: --all --release
|
|
||||||
name_ch: cloud-hypervisor-static-aarch64
|
|
||||||
name_ch_remote: ch-remote-static-aarch64
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Code checkout
|
- name: Code checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v2
|
||||||
- name: Install musl-gcc
|
- name: Install musl-gcc
|
||||||
if: contains(matrix.platform.target, 'musl')
|
run: sudo apt install -y musl-tools
|
||||||
run: sudo apt install -y musl-tools
|
|
||||||
- name: Create release directory
|
- name: Create release directory
|
||||||
if: |
|
|
||||||
github.event_name == 'create' && github.event.ref_type == 'tag' &&
|
|
||||||
matrix.platform.target == 'x86_64-unknown-linux-gnu'
|
|
||||||
run: rsync -rv --exclude=.git . ../cloud-hypervisor-${{ github.event.ref }}
|
run: rsync -rv --exclude=.git . ../cloud-hypervisor-${{ github.event.ref }}
|
||||||
- name: Build ${{ matrix.platform.target }}
|
- name: Install Rust toolchain (x86_64-unknown-linux-gnu)
|
||||||
uses: houseabsolute/actions-rust-cross@v0
|
uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
command: build
|
toolchain: stable
|
||||||
target: ${{ matrix.platform.target }}
|
target: x86_64-unknown-linux-gnu
|
||||||
args: ${{ matrix.platform.args }}
|
- name: Install Rust toolchain (x86_64-unknown-linux-musl)
|
||||||
strip: true
|
uses: actions-rs/toolchain@v1
|
||||||
toolchain: "1.82.0"
|
|
||||||
- name: Copy Release Binaries
|
|
||||||
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
cp target/${{ matrix.platform.target }}/release/cloud-hypervisor ./${{ matrix.platform.name_ch }}
|
|
||||||
cp target/${{ matrix.platform.target }}/release/ch-remote ./${{ matrix.platform.name_ch_remote }}
|
|
||||||
- name: Upload Release Artifacts
|
|
||||||
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
with:
|
||||||
name: Artifacts for ${{ matrix.platform.target }}
|
toolchain: stable
|
||||||
path: |
|
target: x86_64-unknown-linux-musl
|
||||||
./${{ matrix.platform.name_ch }}
|
- name: Build
|
||||||
./${{ matrix.platform.name_ch_remote }}
|
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: Install Rust toolchain (aarch64-unknown-linux-musl)
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
toolchain: stable
|
||||||
|
target: aarch64-unknown-linux-musl
|
||||||
|
override: true
|
||||||
|
- name: Static Build (AArch64)
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
use-cross: true
|
||||||
|
command: build
|
||||||
|
args: --all --release --target=aarch64-unknown-linux-musl
|
||||||
- name: Vendor
|
- name: Vendor
|
||||||
if: |
|
|
||||||
github.event_name == 'create' && github.event.ref_type == 'tag' &&
|
|
||||||
matrix.platform.target == 'x86_64-unknown-linux-gnu'
|
|
||||||
working-directory: ../cloud-hypervisor-${{ github.event.ref }}
|
working-directory: ../cloud-hypervisor-${{ github.event.ref }}
|
||||||
run: |
|
run: |
|
||||||
mkdir ../vendor-cargo-home
|
mkdir ../vendor-cargo-home
|
||||||
export CARGO_HOME=$(realpath ../vendor-cargo-home)
|
export CARGO_HOME=$(realpath ../vendor-cargo-home)
|
||||||
mkdir .cargo
|
mkdir .cargo
|
||||||
cargo vendor > .cargo/config.toml
|
cargo vendor > .cargo/config.toml
|
||||||
- name: Create vendored source archive
|
- name: Create Release
|
||||||
if: |
|
id: create_release
|
||||||
github.event_name == 'create' && github.event.ref_type == 'tag' &&
|
uses: actions/create-release@v1
|
||||||
matrix.platform.target == 'x86_64-unknown-linux-gnu'
|
env:
|
||||||
run: tar cJf cloud-hypervisor-${{ github.event.ref }}.tar.xz ../cloud-hypervisor-${{ github.event.ref }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- 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@v1
|
|
||||||
with:
|
with:
|
||||||
|
tag_name: ${{ github.ref }}
|
||||||
|
release_name: ${{ github.ref }}
|
||||||
draft: true
|
draft: true
|
||||||
files: |
|
prerelease: true
|
||||||
./${{ matrix.platform.name_ch }}
|
- name: Create vendored source archive
|
||||||
./${{ matrix.platform.name_ch_remote }}
|
working-directory: ../
|
||||||
./cloud-hypervisor-${{ github.event.ref }}.tar.xz
|
run: tar cJf cloud-hypervisor-${{ github.event.ref }}.tar.xz cloud-hypervisor-${{ github.event.ref }}
|
||||||
|
- name: Upload cloud-hypervisor vendored source archive
|
||||||
|
id: upload-release-cloud-hypervisor-vendored-sources
|
||||||
|
uses: actions/upload-release-asset@v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||||
|
asset_path: ../cloud-hypervisor-${{ github.event.ref }}.tar.xz
|
||||||
|
asset_name: cloud-hypervisor-${{ github.event.ref }}.tar.xz
|
||||||
|
asset_content_type: application/x-xz
|
||||||
|
- 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
|
||||||
|
- name: Upload static-ch-remote
|
||||||
|
id: upload-release-static-ch-remote
|
||||||
|
uses: actions/upload-release-asset@v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||||
|
asset_path: target/x86_64-unknown-linux-musl/release/ch-remote
|
||||||
|
asset_name: ch-remote-static
|
||||||
|
asset_content_type: application/octet-stream
|
||||||
|
- name: Upload static AArch64 cloud-hypervisor
|
||||||
|
id: upload-release-static-aarch64-cloud-hypervisor
|
||||||
|
uses: actions/upload-release-asset@v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||||
|
asset_path: target/aarch64-unknown-linux-musl/release/cloud-hypervisor
|
||||||
|
asset_name: cloud-hypervisor-static-aarch64
|
||||||
|
asset_content_type: application/octet-stream
|
||||||
|
- name: Upload static AArch64 ch-remote
|
||||||
|
id: upload-release-static-aarch64-ch-remote
|
||||||
|
uses: actions/upload-release-asset@v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||||
|
asset_path: target/aarch64-unknown-linux-musl/release/ch-remote
|
||||||
|
asset_name: ch-remote-static-aarch64
|
||||||
|
asset_content_type: application/octet-stream
|
||||||
|
|||||||
12
.github/workflows/reuse.yaml
vendored
12
.github/workflows/reuse.yaml
vendored
@@ -1,12 +0,0 @@
|
|||||||
name: REUSE Compliance Check
|
|
||||||
|
|
||||||
on: [push, pull_request]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
reuse:
|
|
||||||
name: REUSE Compliance Check
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- name: REUSE Compliance Check
|
|
||||||
uses: fsfe/reuse-action@v3
|
|
||||||
20
.github/workflows/shlint.yaml
vendored
20
.github/workflows/shlint.yaml
vendored
@@ -1,20 +0,0 @@
|
|||||||
name: Shell scripts check
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
merge_group:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
sh-checker:
|
|
||||||
name: Check shell scripts
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
- name: Run the shell script checkers
|
|
||||||
uses: luizm/action-sh-checker@master
|
|
||||||
env:
|
|
||||||
SHFMT_OPTS: -i 4 -d
|
|
||||||
SHELLCHECK_OPTS: -x --source-path scripts
|
|
||||||
21
.github/workflows/taplo.yaml
vendored
21
.github/workflows/taplo.yaml
vendored
@@ -1,21 +0,0 @@
|
|||||||
name: Cargo.toml Formatting (taplo)
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
paths:
|
|
||||||
- '**/Cargo.toml'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
cargo_toml_format:
|
|
||||||
name: Cargo.toml Formatting
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Code checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
- name: Install Rust toolchain
|
|
||||||
uses: dtolnay/rust-toolchain@stable
|
|
||||||
- name: Install build dependencies
|
|
||||||
run: sudo apt-get update && sudo apt-get -yqq install build-essential libssl-dev
|
|
||||||
- name: Install taplo
|
|
||||||
run: cargo install taplo-cli --locked
|
|
||||||
- name: Check formatting
|
|
||||||
run: taplo fmt --check
|
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -4,6 +4,3 @@
|
|||||||
**/*.rs.bk
|
**/*.rs.bk
|
||||||
**/Cargo.lock
|
**/Cargo.lock
|
||||||
**/rusty-tags.vi
|
**/rusty-tags.vi
|
||||||
/rpm/SOURCES
|
|
||||||
/.vscode
|
|
||||||
/vendor
|
|
||||||
|
|||||||
20
.gitlint
20
.gitlint
@@ -1,20 +0,0 @@
|
|||||||
[general]
|
|
||||||
extra-path=scripts/gitlint/rules.py
|
|
||||||
regex-style-search=true
|
|
||||||
|
|
||||||
[ignore-by-author-name]
|
|
||||||
regex=dependabot
|
|
||||||
ignore=all
|
|
||||||
|
|
||||||
# default 72
|
|
||||||
[title-max-length]
|
|
||||||
line-length=72
|
|
||||||
|
|
||||||
# default 80
|
|
||||||
[body-max-line-length]
|
|
||||||
line-length=72
|
|
||||||
|
|
||||||
# Allow developers to add long links to useful resources
|
|
||||||
[ignore-by-body]
|
|
||||||
regex=^https?:\/\/
|
|
||||||
ignore=body-max-line-length
|
|
||||||
18
.lychee.toml
18
.lychee.toml
@@ -1,18 +0,0 @@
|
|||||||
verbose = "info"
|
|
||||||
|
|
||||||
exclude = [
|
|
||||||
# Availability of links below should be manually verified.
|
|
||||||
# Page for intel SGX support, returns 403 while querying.
|
|
||||||
'^https://www.intel.com/content/www/us/en/developer/tools/software-guard-extensions/linux-overview.html',
|
|
||||||
# Page for intel TDX support, returns 403 while querying.
|
|
||||||
'^https://www.intel.com/content/www/us/en/developer/tools/trust-domain-extensions/overview.html',
|
|
||||||
# Page for TPM, returns 403 while querying.
|
|
||||||
'^https://trustedcomputinggroup.org/wp-content/uploads/PC-Client-Specific-Platform-TPM-Profile-for-TPM-2p0-v1p05p_r14_pub.pdf',
|
|
||||||
|
|
||||||
# GitHub user smibarber referenced in `CREDITS.md` no longer exist
|
|
||||||
'^https://github.com/smibarber',
|
|
||||||
]
|
|
||||||
|
|
||||||
max_retries = 3
|
|
||||||
|
|
||||||
retry_wait_time = 5
|
|
||||||
12
.reuse/dep5
12
.reuse/dep5
@@ -1,12 +0,0 @@
|
|||||||
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
|
||||||
Upstream-Name: cloud-hypervisor
|
|
||||||
Upstream-Contact: <>
|
|
||||||
Source: https://www.cloudhypervisor.org
|
|
||||||
|
|
||||||
Files: docs/*.md *.md
|
|
||||||
Copyright: 2024
|
|
||||||
License: CC-BY-4.0
|
|
||||||
|
|
||||||
Files: scripts/* test_data/* *.toml .git* fuzz/Cargo.lock fuzz/.gitignore resources/linux-config-* vmm/src/api/openapi/cloud-hypervisor.yaml CODEOWNERS Cargo.lock
|
|
||||||
Copyright: 2024
|
|
||||||
License: Apache-2.0
|
|
||||||
@@ -1,4 +1 @@
|
|||||||
edition = "2021"
|
edition = "2018"
|
||||||
group_imports="StdExternalCrate"
|
|
||||||
imports_granularity="Module"
|
|
||||||
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
include = ["**/Cargo.toml"]
|
|
||||||
|
|
||||||
[formatting]
|
|
||||||
reorder_arrays = true
|
|
||||||
reorder_keys = true
|
|
||||||
21
.typos.toml
21
.typos.toml
@@ -1,21 +0,0 @@
|
|||||||
# Configuration for https://github.com/crate-ci/typos
|
|
||||||
|
|
||||||
[files]
|
|
||||||
extend-exclude = [
|
|
||||||
"hypervisor/src/kvm/x86_64/mod.rs",
|
|
||||||
"resources/linux-config-*",
|
|
||||||
]
|
|
||||||
|
|
||||||
[default.extend-words]
|
|
||||||
ba = "ba"
|
|
||||||
CLASSE = "CLASSE"
|
|
||||||
conectix = "conectix"
|
|
||||||
Dake = "Dake"
|
|
||||||
EXTINT = "EXTINT"
|
|
||||||
INOUT = "INOUT"
|
|
||||||
liness = "liness"
|
|
||||||
outout = "outout"
|
|
||||||
|
|
||||||
[default.extend-identifiers]
|
|
||||||
fo = "fo"
|
|
||||||
fpr = "fpr"
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
# Add the list of code owners here (using their GitHub username)
|
|
||||||
* @cloud-hypervisor/cloud-hypervisor-reviewers
|
|
||||||
@@ -2,41 +2,22 @@
|
|||||||
|
|
||||||
Cloud Hypervisor is an open source project licensed under the [Apache v2
|
Cloud Hypervisor is an open source project licensed under the [Apache v2
|
||||||
License](https://opensource.org/licenses/Apache-2.0) and the [BSD 3
|
License](https://opensource.org/licenses/Apache-2.0) and the [BSD 3
|
||||||
Clause](https://opensource.org/licenses/BSD-3-Clause) license. Individual files
|
Clause](https://opensource.org/licenses/BSD-3-Clause) license. Contributions
|
||||||
contain details of their licensing and changes to that file are under the same
|
can be made under either license or both. Individual files contain details of
|
||||||
license unless the contribution changes the license of the file. When importing
|
their licensing and changes to that file are under the same license unless the
|
||||||
code from a third party project (e.g. Firecracker or crosvm) please respect the
|
contribution changes the license of the file. When importing code from a third
|
||||||
license of those projects.
|
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).
|
|
||||||
|
|
||||||
## Coding Style
|
## Coding Style
|
||||||
|
|
||||||
We follow the [Rust Style](https://github.com/rust-lang/rust/tree/HEAD/src/doc/style-guide/src)
|
We follow the [Rust Style](https://github.com/rust-dev-tools/fmt-rfcs/blob/master/guide/guide.md)
|
||||||
convention and enforce it through the Continuous Integration (CI) process calling into `rustfmt`
|
convention and enforce it through the Continuous Integration (CI) process calling into `rustfmt`
|
||||||
for each submitted Pull Request (PR).
|
for each submitted Pull Request (PR).
|
||||||
|
|
||||||
## 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
|
## Certificate of Origin
|
||||||
|
|
||||||
In order to get a clear contribution chain of trust we use the [signed-off-by language](https://web.archive.org/web/20230406041855/https://01.org/community/signed-process)
|
In order to get a clear contribution chain of trust we use the [signed-off-by language](https://01.org/community/signed-process)
|
||||||
used by the Linux kernel project.
|
used by the Linux kernel project.
|
||||||
|
|
||||||
## Patch format
|
## Patch format
|
||||||
@@ -79,12 +60,14 @@ you want to merge your changes to `cloud-hypervisor`:
|
|||||||
|
|
||||||
1. Fork the [cloud-hypervisor](https://github.com/cloud-hypervisor/cloud-hypervisor) project
|
1. Fork the [cloud-hypervisor](https://github.com/cloud-hypervisor/cloud-hypervisor) project
|
||||||
into your github organization.
|
into your github organization.
|
||||||
1. Within your fork, create a branch for your contribution.
|
2. Within your fork, create a branch for your contribution.
|
||||||
1. [Create a pull request](https://help.github.com/articles/creating-a-pull-request-from-a-fork/)
|
3. [Create a pull request](https://help.github.com/articles/creating-a-pull-request-from-a-fork/)
|
||||||
against the main branch of the Cloud Hypervisor repository.
|
against the main branch of the Cloud Hypervisor repository.
|
||||||
1. To update your pull request amend existing commits whenever applicable and
|
4. 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.
|
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
|
## Issue tracking
|
||||||
|
|
||||||
@@ -112,5 +95,5 @@ Fixes #88
|
|||||||
Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
|
Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
|
||||||
```
|
```
|
||||||
|
|
||||||
Then, after the corresponding PR is merged, GitHub will automatically close that issue when parsing the
|
Then, after the corresponding PR is merged, Github will automatically close that issue when parsing the
|
||||||
[commit message](https://help.github.com/articles/closing-issues-via-commit-messages/).
|
[commit message](https://help.github.com/articles/closing-issues-via-commit-messages/).
|
||||||
|
|||||||
2268
Cargo.lock
generated
2268
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
162
Cargo.toml
162
Cargo.toml
@@ -1,126 +1,92 @@
|
|||||||
[package]
|
[package]
|
||||||
authors = ["The Cloud Hypervisor Authors"]
|
|
||||||
build = "build.rs"
|
|
||||||
default-run = "cloud-hypervisor"
|
|
||||||
description = "Open source Virtual Machine Monitor (VMM) that runs on top of KVM & MSHV"
|
|
||||||
edition = "2021"
|
|
||||||
homepage = "https://github.com/cloud-hypervisor/cloud-hypervisor"
|
|
||||||
license = "Apache-2.0 AND BSD-3-Clause"
|
|
||||||
name = "cloud-hypervisor"
|
name = "cloud-hypervisor"
|
||||||
version = "45.0.0"
|
version = "22.1.0"
|
||||||
|
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"
|
||||||
# Minimum buildable version:
|
# Minimum buildable version:
|
||||||
# Keep in sync with version in .github/workflows/build.yaml
|
# Keep in sync with version in .github/workflows/build.yaml
|
||||||
# Policy on MSRV (see #4318):
|
rust-version = "1.54"
|
||||||
# Can only be bumped by:
|
|
||||||
# a.) A dependency requires it,
|
|
||||||
# b.) If we want to use a new feature and that MSRV is at least 6 months old,
|
|
||||||
# c.) There is a security issue that is addressed by the toolchain update.
|
|
||||||
rust-version = "1.82.0"
|
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
codegen-units = 1
|
|
||||||
lto = true
|
lto = true
|
||||||
opt-level = "s"
|
|
||||||
strip = true
|
|
||||||
|
|
||||||
[profile.profiling]
|
|
||||||
debug = true
|
|
||||||
inherits = "release"
|
|
||||||
strip = false
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.94"
|
anyhow = "1.0.55"
|
||||||
api_client = { path = "api_client" }
|
api_client = { path = "api_client" }
|
||||||
clap = { version = "4.5.13", features = ["string"] }
|
clap = { version = "3.1.5", features = ["wrap_help","cargo"] }
|
||||||
dhat = { version = "0.3.3", optional = true }
|
epoll = "4.3.1"
|
||||||
epoll = "4.3.3"
|
|
||||||
event_monitor = { path = "event_monitor" }
|
event_monitor = { path = "event_monitor" }
|
||||||
hypervisor = { path = "hypervisor" }
|
hypervisor = { path = "hypervisor" }
|
||||||
libc = "0.2.167"
|
libc = "0.2.119"
|
||||||
log = { version = "0.4.22", features = ["std"] }
|
log = { version = "0.4.14", features = ["std"] }
|
||||||
option_parser = { path = "option_parser" }
|
option_parser = { path = "option_parser" }
|
||||||
seccompiler = { workspace = true }
|
seccompiler = "0.2.0"
|
||||||
serde_json = "1.0.120"
|
serde_json = "1.0.79"
|
||||||
signal-hook = "0.3.17"
|
signal-hook = "0.3.13"
|
||||||
thiserror = "2.0.6"
|
thiserror = "1.0.30"
|
||||||
tpm = { path = "tpm" }
|
|
||||||
tracer = { path = "tracer" }
|
|
||||||
vm-memory = { workspace = true }
|
|
||||||
vmm = { path = "vmm" }
|
vmm = { path = "vmm" }
|
||||||
vmm-sys-util = { workspace = true }
|
vmm-sys-util = "0.9.0"
|
||||||
zbus = { version = "4.4.0", optional = true }
|
vm-memory = "0.7.0"
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
clap = { version = "3.1.5", features = ["wrap_help"] }
|
||||||
|
|
||||||
|
# List of patched crates
|
||||||
|
[patch.crates-io]
|
||||||
|
kvm-bindings = { git = "https://github.com/cloud-hypervisor/kvm-bindings", branch = "ch-v0.5.0-tdx" }
|
||||||
|
kvm-ioctls = { git = "https://github.com/rust-vmm/kvm-ioctls", branch = "main" }
|
||||||
|
versionize_derive = { git = "https://github.com/cloud-hypervisor/versionize_derive", branch = "ch" }
|
||||||
|
virtio-queue = { git = "https://github.com/rust-vmm/vm-virtio", branch = "main" }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
dirs = "6.0.0"
|
dirs = "4.0.0"
|
||||||
|
lazy_static= "1.4.0"
|
||||||
net_util = { path = "net_util" }
|
net_util = { path = "net_util" }
|
||||||
once_cell = "1.20.2"
|
serde_json = "1.0.79"
|
||||||
serde_json = "1.0.120"
|
|
||||||
test_infra = { path = "test_infra" }
|
test_infra = { path = "test_infra" }
|
||||||
wait-timeout = "0.2.0"
|
wait-timeout = "0.2.0"
|
||||||
|
|
||||||
# Please adjust `vmm::feature_list()` accordingly when changing the
|
|
||||||
# feature list below
|
|
||||||
[features]
|
[features]
|
||||||
dbus_api = ["vmm/dbus_api", "zbus"]
|
default = ["common", "kvm"]
|
||||||
default = ["io_uring", "kvm"]
|
# Common features for all hypervisors
|
||||||
dhat-heap = ["dhat", "vmm/dhat-heap"] # For heap profiling
|
common = ["acpi", "cmos", "fwdebug"]
|
||||||
guest_debug = ["vmm/guest_debug"]
|
acpi = ["vmm/acpi"]
|
||||||
igvm = ["mshv", "vmm/igvm"]
|
cmos = ["vmm/cmos"]
|
||||||
io_uring = ["vmm/io_uring"]
|
fwdebug = ["vmm/fwdebug"]
|
||||||
|
gdb = ["vmm/gdb"]
|
||||||
kvm = ["vmm/kvm"]
|
kvm = ["vmm/kvm"]
|
||||||
mshv = ["vmm/mshv"]
|
mshv = ["vmm/mshv"]
|
||||||
pvmemcontrol = ["vmm/pvmemcontrol"]
|
|
||||||
sev_snp = ["igvm", "mshv", "vmm/sev_snp"]
|
|
||||||
tdx = ["vmm/tdx"]
|
tdx = ["vmm/tdx"]
|
||||||
tracing = ["tracer/tracing", "vmm/tracing"]
|
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
"api_client",
|
"acpi_tables",
|
||||||
"arch",
|
"api_client",
|
||||||
"block",
|
"arch",
|
||||||
"devices",
|
"block_util",
|
||||||
"event_monitor",
|
"devices",
|
||||||
"hypervisor",
|
"event_monitor",
|
||||||
"net_gen",
|
"hypervisor",
|
||||||
"net_util",
|
"net_gen",
|
||||||
"option_parser",
|
"net_util",
|
||||||
"pci",
|
"option_parser",
|
||||||
"performance-metrics",
|
"pci",
|
||||||
"rate_limiter",
|
"performance-metrics",
|
||||||
"serial_buffer",
|
"qcow",
|
||||||
"test_infra",
|
"rate_limiter",
|
||||||
"tracer",
|
"vfio_user",
|
||||||
"vhost_user_block",
|
"vhdx",
|
||||||
"vhost_user_net",
|
"vhost_user_block",
|
||||||
"virtio-devices",
|
"vhost_user_net",
|
||||||
"vm-allocator",
|
"virtio-devices",
|
||||||
"vm-device",
|
"vmm",
|
||||||
"vm-migration",
|
"vm-allocator",
|
||||||
"vm-virtio",
|
"vm-device",
|
||||||
"vmm",
|
"vm-migration",
|
||||||
|
"vm-virtio"
|
||||||
]
|
]
|
||||||
|
|
||||||
[workspace.dependencies]
|
|
||||||
# rust-vmm crates
|
|
||||||
acpi_tables = { git = "https://github.com/rust-vmm/acpi_tables", branch = "main" }
|
|
||||||
kvm-bindings = "0.10.0"
|
|
||||||
kvm-ioctls = "0.19.1"
|
|
||||||
linux-loader = "0.13.0"
|
|
||||||
mshv-bindings = "0.3.3"
|
|
||||||
mshv-ioctls = "0.3.3"
|
|
||||||
seccompiler = "0.5.0"
|
|
||||||
vfio-bindings = { git = "https://github.com/rust-vmm/vfio", branch = "main" }
|
|
||||||
vfio-ioctls = { git = "https://github.com/rust-vmm/vfio", branch = "main", default-features = false }
|
|
||||||
vfio_user = { git = "https://github.com/rust-vmm/vfio-user", branch = "main" }
|
|
||||||
vhost = { git = "https://github.com/rust-vmm/vhost", rev = "d983ae0" }
|
|
||||||
vhost-user-backend = { git = "https://github.com/rust-vmm/vhost", rev = "d983ae0" }
|
|
||||||
virtio-bindings = "0.2.4"
|
|
||||||
virtio-queue = "0.14.0"
|
|
||||||
vm-fdt = { git = "https://github.com/rust-vmm/vm-fdt", branch = "main" }
|
|
||||||
vm-memory = "0.16.1"
|
|
||||||
vmm-sys-util = "0.12.1"
|
|
||||||
|
|
||||||
# igvm crates
|
|
||||||
igvm = "0.3.4"
|
|
||||||
igvm_defs = "0.3.1"
|
|
||||||
|
|||||||
396
Jenkinsfile
vendored
Normal file
396
Jenkinsfile
vendored
Normal file
@@ -0,0 +1,396 @@
|
|||||||
|
def runWorkers = true
|
||||||
|
pipeline{
|
||||||
|
agent none
|
||||||
|
stages {
|
||||||
|
stage ('Early checks') {
|
||||||
|
agent { node { label 'built-in' } }
|
||||||
|
stages {
|
||||||
|
stage ('Checkout') {
|
||||||
|
steps {
|
||||||
|
checkout scm
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage ('Check for documentation only changes') {
|
||||||
|
when {
|
||||||
|
expression {
|
||||||
|
return docsFileOnly()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
script {
|
||||||
|
runWorkers = false
|
||||||
|
echo "Documentation only changes, no need to run the CI"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage ('Check for RFC/WIP builds') {
|
||||||
|
when {
|
||||||
|
changeRequest comparator: 'REGEXP', title: '.*(rfc|RFC|wip|WIP).*'
|
||||||
|
beforeAgent true
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
error("Failing as this is marked as a WIP or RFC PR.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage ('Cancel older builds') {
|
||||||
|
when { not { branch 'main' } }
|
||||||
|
steps {
|
||||||
|
cancelPreviousBuilds()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage ('Build') {
|
||||||
|
parallel {
|
||||||
|
stage ('Worker build') {
|
||||||
|
agent { node { label 'hirsute' } }
|
||||||
|
when {
|
||||||
|
beforeAgent true
|
||||||
|
expression {
|
||||||
|
return runWorkers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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 "sudo modprobe openvswitch"
|
||||||
|
sh "scripts/dev_cli.sh tests --integration"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage ('AArch64 worker build') {
|
||||||
|
agent { node { label 'bionic-arm64' } }
|
||||||
|
when {
|
||||||
|
beforeAgent true
|
||||||
|
expression {
|
||||||
|
return runWorkers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stages {
|
||||||
|
stage ('Checkout') {
|
||||||
|
steps {
|
||||||
|
checkout scm
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage ('Run unit tests') {
|
||||||
|
steps {
|
||||||
|
sh "scripts/dev_cli.sh tests --unit --libc musl"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage ('Run integration tests') {
|
||||||
|
options {
|
||||||
|
timeout(time: 1, unit: 'HOURS')
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
sh "sudo modprobe openvswitch"
|
||||||
|
sh "scripts/dev_cli.sh tests --integration --libc musl"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
post {
|
||||||
|
always {
|
||||||
|
sh "sudo chown -R jenkins.jenkins ${WORKSPACE}"
|
||||||
|
deleteDir()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage ('Worker build (musl)') {
|
||||||
|
agent { node { label 'hirsute' } }
|
||||||
|
when {
|
||||||
|
beforeAgent true
|
||||||
|
expression {
|
||||||
|
return runWorkers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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 "sudo modprobe openvswitch"
|
||||||
|
sh "scripts/dev_cli.sh tests --integration --libc musl"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage ('Worker build SGX') {
|
||||||
|
agent { node { label 'bionic-sgx' } }
|
||||||
|
when {
|
||||||
|
beforeAgent true
|
||||||
|
allOf {
|
||||||
|
branch 'main'
|
||||||
|
expression {
|
||||||
|
return runWorkers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stages {
|
||||||
|
stage ('Checkout') {
|
||||||
|
steps {
|
||||||
|
checkout scm
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage ('Run SGX integration tests') {
|
||||||
|
options {
|
||||||
|
timeout(time: 1, unit: 'HOURS')
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
sh "scripts/dev_cli.sh tests --integration-sgx"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage ('Run SGX integration tests for musl') {
|
||||||
|
options {
|
||||||
|
timeout(time: 1, unit: 'HOURS')
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
sh "scripts/dev_cli.sh tests --integration-sgx --libc musl"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
post {
|
||||||
|
always {
|
||||||
|
sh "sudo chown -R jenkins.jenkins ${WORKSPACE}"
|
||||||
|
deleteDir()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage ('Worker build VFIO') {
|
||||||
|
agent { node { label 'bionic-vfio' } }
|
||||||
|
when {
|
||||||
|
beforeAgent true
|
||||||
|
allOf {
|
||||||
|
branch 'main'
|
||||||
|
expression {
|
||||||
|
return runWorkers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stages {
|
||||||
|
stage ('Checkout') {
|
||||||
|
steps {
|
||||||
|
checkout scm
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage ('Run VFIO integration tests') {
|
||||||
|
options {
|
||||||
|
timeout(time: 1, unit: 'HOURS')
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
sh "scripts/dev_cli.sh tests --integration-vfio"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage ('Run VFIO integration tests for musl') {
|
||||||
|
options {
|
||||||
|
timeout(time: 1, unit: 'HOURS')
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
sh "scripts/dev_cli.sh tests --integration-vfio --libc musl"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
post {
|
||||||
|
always {
|
||||||
|
sh "sudo chown -R jenkins.jenkins ${WORKSPACE}"
|
||||||
|
deleteDir()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage ('Worker build - Windows guest') {
|
||||||
|
agent { node { label 'hirsute' } }
|
||||||
|
when {
|
||||||
|
beforeAgent true
|
||||||
|
expression {
|
||||||
|
return runWorkers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
environment {
|
||||||
|
AZURE_CONNECTION_STRING = credentials('46b4e7d6-315f-4cc1-8333-b58780863b9b')
|
||||||
|
}
|
||||||
|
stages {
|
||||||
|
stage ('Checkout') {
|
||||||
|
steps {
|
||||||
|
checkout scm
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage ('Install azure-cli') {
|
||||||
|
steps {
|
||||||
|
installAzureCli()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage ('Download assets') {
|
||||||
|
steps {
|
||||||
|
sh "mkdir ${env.HOME}/workloads"
|
||||||
|
sh 'az storage blob download --container-name private-images --file "$HOME/workloads/windows-server-2019.raw" --name windows-server-2019.raw --connection-string "$AZURE_CONNECTION_STRING"'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage ('Run Windows guest integration tests') {
|
||||||
|
options {
|
||||||
|
timeout(time: 1, unit: 'HOURS')
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
sh "scripts/dev_cli.sh tests --integration-windows"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage ('Run Windows guest integration tests for musl') {
|
||||||
|
options {
|
||||||
|
timeout(time: 1, unit: 'HOURS')
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
sh "scripts/dev_cli.sh tests --integration-windows --libc musl"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage ('Worker build - Live Migration') {
|
||||||
|
agent { node { label 'hirsute-small' } }
|
||||||
|
when {
|
||||||
|
beforeAgent true
|
||||||
|
expression {
|
||||||
|
return runWorkers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stages {
|
||||||
|
stage ('Checkout') {
|
||||||
|
steps {
|
||||||
|
checkout scm
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage ('Run live-migration integration tests') {
|
||||||
|
options {
|
||||||
|
timeout(time: 1, unit: 'HOURS')
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
sh "sudo modprobe openvswitch"
|
||||||
|
sh "scripts/dev_cli.sh tests --integration-live-migration"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage ('Run live-migration integration tests for musl') {
|
||||||
|
options {
|
||||||
|
timeout(time: 1, unit: 'HOURS')
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
sh "sudo modprobe openvswitch"
|
||||||
|
sh "scripts/dev_cli.sh tests --integration-live-migration --libc musl"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage ('Worker build - Metrics') {
|
||||||
|
agent { node { label 'focal-metrics' } }
|
||||||
|
when {
|
||||||
|
branch 'main'
|
||||||
|
beforeAgent true
|
||||||
|
expression {
|
||||||
|
return runWorkers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
environment {
|
||||||
|
METRICS_PUBLISH_KEY = credentials('52e0945f-ce7a-43d1-87af-67d1d87cc40f')
|
||||||
|
}
|
||||||
|
stages {
|
||||||
|
stage ('Checkout') {
|
||||||
|
steps {
|
||||||
|
checkout scm
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage ('Run metrics tests') {
|
||||||
|
options {
|
||||||
|
timeout(time: 1, unit: 'HOURS')
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
sh 'scripts/dev_cli.sh tests --metrics -- -- --report-file /root/workloads/metrics.json'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage ('Upload metrics report') {
|
||||||
|
steps {
|
||||||
|
sh 'curl -X PUT https://cloud-hypervisor-metrics.azurewebsites.net/api/publishmetrics -H "x-functions-key: $METRICS_PUBLISH_KEY" -T ~/workloads/metrics.json'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
post {
|
||||||
|
regression {
|
||||||
|
script {
|
||||||
|
if (env.BRANCH_NAME == 'main') {
|
||||||
|
slackSend (color: '#ff0000', message: '"main" branch build is now failing')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fixed {
|
||||||
|
script {
|
||||||
|
if (env.BRANCH_NAME == 'main') {
|
||||||
|
slackSend (color: '#00ff00', message: '"main" 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def installAzureCli() {
|
||||||
|
sh "sudo apt install -y ca-certificates curl apt-transport-https lsb-release gnupg"
|
||||||
|
sh "curl -sL https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/microsoft.gpg > /dev/null"
|
||||||
|
sh "echo \"deb [arch=amd64] https://packages.microsoft.com/repos/azure-cli/ hirsute main\" | sudo tee /etc/apt/sources.list.d/azure-cli.list"
|
||||||
|
sh "sudo apt update"
|
||||||
|
sh "sudo apt install -y azure-cli"
|
||||||
|
}
|
||||||
|
|
||||||
|
def boolean docsFileOnly() {
|
||||||
|
if (env.CHANGE_TARGET == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sh(
|
||||||
|
returnStatus: true,
|
||||||
|
script: "git diff --name-only origin/${env.CHANGE_TARGET}... | grep -v '\\.md'"
|
||||||
|
) != 0
|
||||||
|
}
|
||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
- Sebastien Boeuf - @sboeuf
|
- Sebastien Boeuf - @sboeuf
|
||||||
- Robert Bradford - @rbradford
|
- Robert Bradford - @rbradford
|
||||||
- Bo Chen - @likebreath
|
|
||||||
- Samuel Ortiz - @sameo
|
- Samuel Ortiz - @sameo
|
||||||
- Wei Liu - @liuw
|
- Wei Liu - @liuw
|
||||||
- Michael Zhao - @michael2012z
|
- Michael Zhao - @michael2012z
|
||||||
|
|||||||
425
README.md
425
README.md
@@ -4,43 +4,42 @@
|
|||||||
- [Architectures](#architectures)
|
- [Architectures](#architectures)
|
||||||
- [Guest OS](#guest-os)
|
- [Guest OS](#guest-os)
|
||||||
- [2. Getting Started](#2-getting-started)
|
- [2. Getting Started](#2-getting-started)
|
||||||
- [Host OS](#host-os)
|
- [Preparation](#preparation)
|
||||||
- [Use Pre-built Binaries](#use-pre-built-binaries)
|
- [Install prerequisites](#install-prerequisites)
|
||||||
- [Packages](#packages)
|
- [Clone and build](#clone-and-build)
|
||||||
- [Building from Source](#building-from-source)
|
- [Containerized builds and tests](#containerized-builds-and-tests)
|
||||||
- [Booting Linux](#booting-linux)
|
- [Run](#run)
|
||||||
- [Firmware Booting](#firmware-booting)
|
- [Cloud image](#cloud-image)
|
||||||
- [Custom Kernel and Disk Image](#custom-kernel-and-disk-image)
|
- [Custom kernel and disk image](#custom-kernel-and-disk-image)
|
||||||
- [Building your Kernel](#building-your-kernel)
|
- [Building your kernel](#building-your-kernel)
|
||||||
- [Disk image](#disk-image)
|
- [Disk image](#disk-image)
|
||||||
- [Booting the guest VM](#booting-the-guest-vm)
|
- [Booting the guest VM](#booting-the-guest-vm)
|
||||||
- [3. Status](#3-status)
|
- [3. Status](#3-status)
|
||||||
- [Hot Plug](#hot-plug)
|
- [Hot Plug](#hot-plug)
|
||||||
- [Device Model](#device-model)
|
- [Device Model](#device-model)
|
||||||
- [Roadmap](#roadmap)
|
- [TODO](#todo)
|
||||||
- [4. Relationship with _Rust VMM_ Project](#4-relationship-with-rust-vmm-project)
|
- [4. `rust-vmm` project dependency](#4-rust-vmm-project-dependency)
|
||||||
- [Differences with Firecracker and crosvm](#differences-with-firecracker-and-crosvm)
|
- [Firecracker and crosvm](#firecracker-and-crosvm)
|
||||||
- [5. Community](#5-community)
|
- [5. Community](#5-community)
|
||||||
- [Contribute](#contribute)
|
- [Contribute](#contribute)
|
||||||
- [Slack](#slack)
|
- [Join us](#join-us)
|
||||||
- [Mailing list](#mailing-list)
|
|
||||||
- [Security issues](#security-issues)
|
- [Security issues](#security-issues)
|
||||||
|
|
||||||
# 1. What is Cloud Hypervisor?
|
# 1. What is Cloud Hypervisor?
|
||||||
|
|
||||||
Cloud Hypervisor is an open source Virtual Machine Monitor (VMM) that runs on
|
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)
|
top of [KVM](https://www.kernel.org/doc/Documentation/virtual/kvm/api.txt)
|
||||||
hypervisor and the Microsoft Hypervisor (MSHV).
|
hypervisor and Microsoft Hypervisor (MSHV).
|
||||||
|
|
||||||
The project focuses on running modern, _Cloud Workloads_, on specific, common,
|
The project focuses on exclusively running modern, cloud workloads, on top of
|
||||||
hardware architectures. In this case _Cloud Workloads_ refers to those that are
|
a limited set of hardware architectures and platforms. Cloud workloads refers
|
||||||
run by customers inside a Cloud Service Provider. This means modern operating
|
to those that are usually run by customers inside a cloud provider. For our
|
||||||
systems with most I/O handled by
|
purposes this means modern operating systems with most I/O handled by
|
||||||
paravirtualised devices (e.g. _virtio_), no requirement for legacy devices, and
|
paravirtualised devices (i.e. virtio), no requirement for legacy devices, and
|
||||||
64-bit CPUs.
|
64-bit CPUs.
|
||||||
|
|
||||||
Cloud Hypervisor is implemented in [Rust](https://www.rust-lang.org/) and is
|
Cloud Hypervisor is implemented in [Rust](https://www.rust-lang.org/) and is
|
||||||
based on the [Rust VMM](https://github.com/rust-vmm) crates.
|
based on the [rust-vmm](https://github.com/rust-vmm) crates.
|
||||||
|
|
||||||
## Objectives
|
## Objectives
|
||||||
|
|
||||||
@@ -59,13 +58,9 @@ based on the [Rust VMM](https://github.com/rust-vmm) crates.
|
|||||||
|
|
||||||
### Architectures
|
### Architectures
|
||||||
|
|
||||||
Cloud Hypervisor supports the `x86-64`, `AArch64` and `riscv64`
|
Cloud Hypervisor supports the `x86-64` and `AArch64` architectures. There are
|
||||||
architectures, with functionality varying across these platforms. The
|
some small differences in functionality between the two architectures
|
||||||
functionality differences between `x86-64` and `AArch64` are documented
|
(see [#1125](https://github.com/cloud-hypervisor/cloud-hypervisor/issues/1125)).
|
||||||
in [#1125](https://github.com/cloud-hypervisor/cloud-hypervisor/issues/1125).
|
|
||||||
The `riscv64` architecture support is experimental and offers limited
|
|
||||||
functionality. For more details and instructions, please refer to [riscv
|
|
||||||
documentation](docs/riscv.md).
|
|
||||||
|
|
||||||
### Guest OS
|
### Guest OS
|
||||||
|
|
||||||
@@ -73,184 +68,189 @@ Cloud Hypervisor supports `64-bit Linux` and Windows 10/Windows Server 2019.
|
|||||||
|
|
||||||
# 2. Getting Started
|
# 2. Getting Started
|
||||||
|
|
||||||
The following sections describe how to build and run Cloud Hypervisor.
|
Below sections describe how to build and run Cloud Hypervisor on the `x86_64`
|
||||||
|
platform. For getting started on the `AArch64` platform, please refer to the
|
||||||
|
[Arm64 documentation](docs/arm64.md).
|
||||||
|
|
||||||
## Prerequisites for AArch64
|
## Preparation
|
||||||
|
|
||||||
- AArch64 servers (recommended) or development boards equipped with the GICv3
|
We create a folder to build and run `cloud-hypervisor` at `$HOME/cloud-hypervisor`
|
||||||
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.
|
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
$ wget https://cloud-images.ubuntu.com/focal/current/focal-server-cloudimg-amd64.img
|
$ export CLOUDH=$HOME/cloud-hypervisor
|
||||||
$ qemu-img convert -p -f qcow2 -O raw focal-server-cloudimg-amd64.img focal-server-cloudimg-amd64.raw
|
$ mkdir $CLOUDH
|
||||||
$ wget https://github.com/cloud-hypervisor/rust-hypervisor-firmware/releases/download/0.4.2/hypervisor-fw
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The Ubuntu cloud images do not ship with a default password so it necessary to
|
## Install prerequisites
|
||||||
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).
|
You need to install some prerequisite packages in order to build and test Cloud
|
||||||
This seeds the image with a default username/password of `cloud/cloud123`. It
|
Hypervisor. Here, all the steps are based on Ubuntu, for other Linux
|
||||||
is only necessary to add this disk image on the first boot. Script also assigns
|
distributions please replace the package manager and package name.
|
||||||
default IP address using `test_data/cloud-init/ubuntu/local/network-config` details
|
|
||||||
with `--net "mac=12:34:56:78:90:ab,tap="` option. Then the matching mac address
|
|
||||||
interface will be enabled as per `network-config` details.
|
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
$ sudo setcap cap_net_admin+ep ./cloud-hypervisor
|
# Install git
|
||||||
$ ./create-cloud-init.sh
|
$ sudo apt install git
|
||||||
$ ./cloud-hypervisor \
|
# 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.3.2/hypervisor-fw
|
||||||
|
$ popd
|
||||||
|
```
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ pushd $CLOUDH
|
||||||
|
$ sudo setcap cap_net_admin+ep ./cloud-hypervisor/target/release/cloud-hypervisor
|
||||||
|
$ ./cloud-hypervisor/target/release/cloud-hypervisor \
|
||||||
--kernel ./hypervisor-fw \
|
--kernel ./hypervisor-fw \
|
||||||
--disk path=focal-server-cloudimg-amd64.raw path=/tmp/ubuntu-cloudinit.img \
|
--disk path=focal-server-cloudimg-amd64.raw \
|
||||||
--cpus boot=4 \
|
--cpus boot=4 \
|
||||||
--memory size=1024M \
|
--memory size=1024M \
|
||||||
--net "tap=,mac=,ip=,mask="
|
--net "tap=,mac=,ip=,mask="
|
||||||
|
$ popd
|
||||||
```
|
```
|
||||||
|
|
||||||
If access to the firmware messages or interaction with the boot loader (e.g.
|
Multiple arguments can be given to the `--disk` parameter.
|
||||||
GRUB) is required then it necessary to switch to the serial console instead of
|
|
||||||
`virtio-console`.
|
|
||||||
|
|
||||||
```shell
|
### Custom kernel and disk image
|
||||||
$ ./cloud-hypervisor \
|
|
||||||
--kernel ./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=" \
|
|
||||||
--serial tty \
|
|
||||||
--console off
|
|
||||||
```
|
|
||||||
|
|
||||||
### Custom Kernel and Disk Image
|
#### Building your kernel
|
||||||
|
|
||||||
#### Building your Kernel
|
Cloud Hypervisor also supports direct kernel boot into a `vmlinux` ELF kernel.
|
||||||
|
In order to support virtio-watchdog we have our own development branch. You are
|
||||||
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.
|
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:
|
To build the kernel:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
|
|
||||||
# Clone the Cloud Hypervisor Linux branch
|
# Clone the Cloud Hypervisor Linux branch
|
||||||
$ git clone --depth 1 https://github.com/cloud-hypervisor/linux.git -b ch-6.12.8 linux-cloud-hypervisor
|
$ pushd $CLOUDH
|
||||||
|
$ git clone --depth 1 https://github.com/cloud-hypervisor/linux.git -b ch-5.15.12 linux-cloud-hypervisor
|
||||||
$ pushd linux-cloud-hypervisor
|
$ pushd linux-cloud-hypervisor
|
||||||
$ make ch_defconfig
|
|
||||||
# Do native build of the x86-64 kernel
|
# Use the cloud-hypervisor kernel config to build your kernel
|
||||||
|
$ cp $CLOUDH/cloud-hypervisor/resources/linux-config-x86_64 .config
|
||||||
$ KCFLAGS="-Wa,-mx86-used-note=no" make bzImage -j `nproc`
|
$ KCFLAGS="-Wa,-mx86-used-note=no" make bzImage -j `nproc`
|
||||||
# Do native build of the AArch64 kernel
|
|
||||||
$ make -j `nproc`
|
|
||||||
$ popd
|
$ popd
|
||||||
```
|
```
|
||||||
|
|
||||||
For x86-64, the `vmlinux` kernel image will then be located at
|
The `vmlinux` kernel image will then be located at
|
||||||
`linux-cloud-hypervisor/arch/x86/boot/compressed/vmlinux.bin`.
|
`linux-cloud-hypervisor/arch/x86/boot/compressed/vmlinux.bin`.
|
||||||
For AArch64, the `Image` kernel image will then be located at
|
|
||||||
`linux-cloud-hypervisor/arch/arm64/boot/Image`.
|
|
||||||
|
|
||||||
#### Disk image
|
#### Disk image
|
||||||
|
|
||||||
For the disk image the same Ubuntu image as before can be used. This contains
|
For the disk image, we will use a Ubuntu cloud image that contains a root
|
||||||
an `ext4` root filesystem.
|
partition:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
$ wget https://cloud-images.ubuntu.com/focal/current/focal-server-cloudimg-amd64.img # x86-64
|
$ pushd $CLOUDH
|
||||||
$ wget https://cloud-images.ubuntu.com/focal/current/focal-server-cloudimg-arm64.img # AArch64
|
$ wget https://cloud-images.ubuntu.com/focal/current/focal-server-cloudimg-amd64.img
|
||||||
$ qemu-img convert -p -f qcow2 -O raw focal-server-cloudimg-amd64.img focal-server-cloudimg-amd64.raw # x86-64
|
$ qemu-img convert -p -f qcow2 -O raw focal-server-cloudimg-amd64.img focal-server-cloudimg-amd64.raw
|
||||||
$ qemu-img convert -p -f qcow2 -O raw focal-server-cloudimg-arm64.img focal-server-cloudimg-arm64.raw # AArch64
|
$ popd
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Booting the guest VM
|
#### Booting the guest VM
|
||||||
|
|
||||||
These sample commands boot the disk image using the custom kernel whilst also
|
Now we can directly boot into our custom kernel and make it use the Ubuntu root
|
||||||
supplying the desired kernel command line.
|
partition. If we want to have 4 vCPUs and 1024 MBytes of memory:
|
||||||
|
|
||||||
- x86-64
|
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
$ sudo setcap cap_net_admin+ep ./cloud-hypervisor
|
$ pushd $CLOUDH
|
||||||
$ ./create-cloud-init.sh
|
$ sudo setcap cap_net_admin+ep ./cloud-hypervisor/target/release/cloud-hypervisor
|
||||||
$ ./cloud-hypervisor \
|
$ ./cloud-hypervisor/target/release/cloud-hypervisor \
|
||||||
--kernel ./linux-cloud-hypervisor/arch/x86/boot/compressed/vmlinux.bin \
|
--kernel ./linux-cloud-hypervisor/arch/x86/boot/compressed/vmlinux.bin \
|
||||||
--disk path=focal-server-cloudimg-amd64.raw path=/tmp/ubuntu-cloudinit.img \
|
--disk path=focal-server-cloudimg-amd64.raw \
|
||||||
--cmdline "console=hvc0 root=/dev/vda1 rw" \
|
--cmdline "console=hvc0 root=/dev/vda1 rw" \
|
||||||
--cpus boot=4 \
|
--cpus boot=4 \
|
||||||
--memory size=1024M \
|
--memory size=1024M \
|
||||||
--net "tap=,mac=,ip=,mask="
|
--net "tap=,mac=,ip=,mask="
|
||||||
```
|
```
|
||||||
|
|
||||||
- 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="
|
|
||||||
```
|
```
|
||||||
|
$ ./cloud-hypervisor/target/release/cloud-hypervisor \
|
||||||
If earlier kernel messages are required the serial console should be used instead of `virtio-console`.
|
|
||||||
|
|
||||||
- x86-64
|
|
||||||
|
|
||||||
```shell
|
|
||||||
$ ./cloud-hypervisor \
|
|
||||||
--kernel ./linux-cloud-hypervisor/arch/x86/boot/compressed/vmlinux.bin \
|
--kernel ./linux-cloud-hypervisor/arch/x86/boot/compressed/vmlinux.bin \
|
||||||
--console off \
|
--console off \
|
||||||
--serial tty \
|
--serial tty \
|
||||||
@@ -261,52 +261,35 @@ $ ./cloud-hypervisor \
|
|||||||
--net "tap=,mac=,ip=,mask="
|
--net "tap=,mac=,ip=,mask="
|
||||||
```
|
```
|
||||||
|
|
||||||
- AArch64
|
|
||||||
|
|
||||||
```shell
|
|
||||||
$ ./cloud-hypervisor \
|
|
||||||
--kernel ./linux-cloud-hypervisor/arch/arm64/boot/Image \
|
|
||||||
--console off \
|
|
||||||
--serial tty \
|
|
||||||
--disk path=focal-server-cloudimg-arm64.raw \
|
|
||||||
--cmdline "console=ttyAMA0 root=/dev/vda1 rw" \
|
|
||||||
--cpus boot=4 \
|
|
||||||
--memory size=1024M \
|
|
||||||
--net "tap=,mac=,ip=,mask="
|
|
||||||
```
|
|
||||||
|
|
||||||
# 3. Status
|
# 3. Status
|
||||||
|
|
||||||
Cloud Hypervisor is under active development. The following stability
|
Cloud Hypervisor is under active development. The following stability guarantees
|
||||||
guarantees are currently made:
|
are currently made:
|
||||||
|
|
||||||
* The API (including command line options) will not be removed or changed in a
|
* 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
|
breaking way without a minimum of 2 releases notice. Where possible warnings
|
||||||
warnings will be given about the use of deprecated functionality and the
|
will be given about the use of deprecated functionality and the deprecations
|
||||||
deprecations will be documented in the release notes.
|
will be documented in the release notes.
|
||||||
|
|
||||||
* Point releases will be made between individual releases where there are
|
* Point releases will be made between individual releases where there are
|
||||||
substantial bug fixes or security issues that need to be fixed. These point
|
substantial bug fixes or security issues that need to be fixed.
|
||||||
releases will only include bug fixes.
|
|
||||||
|
|
||||||
Currently the following items are **not** guaranteed across updates:
|
Currently the following items are **not** guaranteed across updates:
|
||||||
|
|
||||||
* Snapshot/restore is not supported across different versions
|
* Snapshot/restore is not supported across different versions
|
||||||
* Live migration is not supported across different versions
|
* Live migration is not supported across different versions
|
||||||
* The following features are considered experimental and may change
|
* The following features are considered experimental and may change
|
||||||
substantially between releases: TDX, vfio-user, vDPA.
|
substantially between releases: TDX, SGX.
|
||||||
|
|
||||||
Further details can be found in the [release documentation](docs/releases.md).
|
|
||||||
|
|
||||||
As of 2023-01-03, the following cloud images are supported:
|
As of 2021-04-29, the following cloud images are supported:
|
||||||
|
|
||||||
- [Ubuntu Focal](https://cloud-images.ubuntu.com/focal/current/) (focal-server-cloudimg-{amd64,arm64}.img)
|
- [Ubuntu Bionic](https://cloud-images.ubuntu.com/bionic/current/) (cloudimg)
|
||||||
- [Ubuntu Jammy](https://cloud-images.ubuntu.com/jammy/current/) (jammy-server-cloudimg-{amd64,arm64}.img )
|
- [Ubuntu Focal](https://cloud-images.ubuntu.com/focal/current/) (cloudimg)
|
||||||
- [Fedora 36](https://archives.fedoraproject.org/pub/archive/fedora/linux/releases/36/Cloud/) ([Fedora-Cloud-Base-36-1.5.x86_64.raw.xz](https://archives.fedoraproject.org/pub/archive/fedora/linux/releases/36/Cloud/x86_64/images/) / [Fedora-Cloud-Base-36-1.5.aarch64.raw.xz](https://archives.fedoraproject.org/pub/archive/fedora/linux/releases/36/Cloud/aarch64/images/))
|
- [Ubuntu Groovy](https://cloud-images.ubuntu.com/groovy/current/) (cloudimg)
|
||||||
|
- [Ubuntu Hirsute](https://cloud-images.ubuntu.com/hirsute/current/) (cloudimg)
|
||||||
|
|
||||||
Direct kernel boot to userspace should work with a rootfs from most
|
Direct kernel boot to userspace should work with a rootfs from most
|
||||||
distributions although you may need to enable exotic filesystem types in the
|
distributions.
|
||||||
reference kernel configuration (e.g. XFS or btrfs.)
|
|
||||||
|
|
||||||
## Hot Plug
|
## Hot Plug
|
||||||
|
|
||||||
@@ -319,12 +302,14 @@ Cloud Hypervisor supports hotplug of CPUs, passthrough devices (VFIO),
|
|||||||
Details of the device model can be found in this
|
Details of the device model can be found in this
|
||||||
[documentation](docs/device_model.md).
|
[documentation](docs/device_model.md).
|
||||||
|
|
||||||
## Roadmap
|
## TODO
|
||||||
|
|
||||||
The project roadmap is tracked through a [GitHub
|
We are not tracking the Cloud Hypervisor TODO list from a specific git tracked
|
||||||
project](https://github.com/orgs/cloud-hypervisor/projects/6).
|
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,
|
In order to satisfy the design goal of having a high-performance,
|
||||||
security-focused hypervisor the decision was made to use the
|
security-focused hypervisor the decision was made to use the
|
||||||
@@ -333,26 +318,39 @@ focus on memory and thread safety makes it an ideal candidate for implementing
|
|||||||
VMMs.
|
VMMs.
|
||||||
|
|
||||||
Instead of implementing the VMM components from scratch, Cloud Hypervisor is
|
Instead of implementing the VMM components from scratch, Cloud Hypervisor is
|
||||||
importing the [Rust VMM](https://github.com/rust-vmm) crates, and sharing code
|
importing the [rust-vmm](https://github.com/rust-vmm) crates, and sharing code
|
||||||
and architecture together with other VMMs like e.g. Amazon's
|
and architecture together with other VMMs like e.g. Amazon's
|
||||||
[Firecracker](https://firecracker-microvm.github.io/) and Google's
|
[Firecracker](https://firecracker-microvm.github.io/) and Google's
|
||||||
[crosvm](https://chromium.googlesource.com/chromiumos/platform/crosvm/).
|
[crosvm](https://chromium.googlesource.com/chromiumos/platform/crosvm/).
|
||||||
|
|
||||||
Cloud Hypervisor embraces the _Rust VMM_ project's goals, which is to be able
|
Cloud Hypervisor embraces the rust-vmm project goals, which is to be able to
|
||||||
to share and re-use as many virtualization crates as possible.
|
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.
|
||||||
|
|
||||||
|
## Firecracker and crosvm
|
||||||
|
|
||||||
A large part of the Cloud Hypervisor code is based on either the Firecracker or
|
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
|
the crosvm projects implementations. Both of these are VMMs written in Rust with
|
||||||
with a focus on safety and security, like Cloud Hypervisor.
|
a focus on safety and security, like Cloud Hypervisor.
|
||||||
|
|
||||||
The goal of the Cloud Hypervisor project differs from the aforementioned
|
However we want to emphasize that the Cloud Hypervisor project is neither a fork
|
||||||
projects in that it aims to be a general purpose VMM for _Cloud Workloads_ and
|
nor a reimplementation of any of those projects. The goals and use cases we're
|
||||||
not limited to container/serverless or client workloads.
|
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.
|
||||||
|
|
||||||
The Cloud Hypervisor community thanks the communities of both the Firecracker
|
Our primary target is not to support client or serverless use cases, and as such
|
||||||
and crosvm projects for their excellent work.
|
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
|
# 5. Community
|
||||||
|
|
||||||
@@ -362,27 +360,20 @@ repository.
|
|||||||
|
|
||||||
## Contribute
|
## Contribute
|
||||||
|
|
||||||
The project strongly believes in building a global, diverse and collaborative
|
We are working on building a global, diverse and collaborative community around
|
||||||
community around the Cloud Hypervisor project. Anyone who is interested in
|
the Cloud Hypervisor project. Anyone who is interested in
|
||||||
[contributing](CONTRIBUTING.md) to the project is welcome to participate.
|
[contributing](CONTRIBUTING.md) to the project is welcome to participate.
|
||||||
|
|
||||||
Contributing to a open source project like Cloud Hypervisor covers a lot more
|
We believe that contributing to a open source project like Cloud Hypervisor
|
||||||
than just sending code. Testing, documentation, pull request
|
covers a lot more than just sending code. Testing, documentation, pull request
|
||||||
reviews, bug reports, feature requests, project improvement suggestions, etc,
|
reviews, bug reports, feature requests, project improvement suggestions, etc,
|
||||||
are all equal and welcome means of contribution. See the
|
are all equal and welcome means of contribution. See the
|
||||||
[CONTRIBUTING](CONTRIBUTING.md) document for more details.
|
[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),
|
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).
|
and [join us on Slack](https://cloud-hypervisor.slack.com/).
|
||||||
|
|
||||||
## 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
|
## Security issues
|
||||||
|
|
||||||
|
|||||||
8
acpi_tables/Cargo.toml
Normal file
8
acpi_tables/Cargo.toml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
[package]
|
||||||
|
name = "acpi_tables"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["The Cloud Hypervisor Authors"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
vm-memory = "0.7.0"
|
||||||
1894
acpi_tables/src/aml.rs
Normal file
1894
acpi_tables/src/aml.rs
Normal file
File diff suppressed because it is too large
Load Diff
12
acpi_tables/src/lib.rs
Normal file
12
acpi_tables/src/lib.rs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
// Copyright © 2019 Intel Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
//
|
||||||
|
|
||||||
|
pub mod aml;
|
||||||
|
pub mod rsdp;
|
||||||
|
pub mod sdt;
|
||||||
|
|
||||||
|
fn generate_checksum(data: &[u8]) -> u8 {
|
||||||
|
(255 - data.iter().fold(0u8, |acc, x| acc.wrapping_add(*x))).wrapping_add(1)
|
||||||
|
}
|
||||||
68
acpi_tables/src/rsdp.rs
Normal file
68
acpi_tables/src/rsdp.rs
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
// Copyright © 2019 Intel Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
//
|
||||||
|
|
||||||
|
use vm_memory::ByteValued;
|
||||||
|
|
||||||
|
#[repr(packed)]
|
||||||
|
#[derive(Clone, Copy, Default)]
|
||||||
|
pub struct Rsdp {
|
||||||
|
pub signature: [u8; 8],
|
||||||
|
pub checksum: u8,
|
||||||
|
pub oem_id: [u8; 6],
|
||||||
|
pub revision: u8,
|
||||||
|
_rsdt_addr: u32,
|
||||||
|
pub length: u32,
|
||||||
|
pub xsdt_addr: u64,
|
||||||
|
pub extended_checksum: u8,
|
||||||
|
_reserved: [u8; 3],
|
||||||
|
}
|
||||||
|
|
||||||
|
// SAFETY: Rsdp only contains a series of integers
|
||||||
|
unsafe impl ByteValued for Rsdp {}
|
||||||
|
|
||||||
|
impl Rsdp {
|
||||||
|
pub fn new(oem_id: [u8; 6], xsdt_addr: u64) -> Self {
|
||||||
|
let mut rsdp = Rsdp {
|
||||||
|
signature: *b"RSD PTR ",
|
||||||
|
checksum: 0,
|
||||||
|
oem_id,
|
||||||
|
revision: 2,
|
||||||
|
_rsdt_addr: 0,
|
||||||
|
length: std::mem::size_of::<Rsdp>() as u32,
|
||||||
|
xsdt_addr,
|
||||||
|
extended_checksum: 0,
|
||||||
|
_reserved: [0; 3],
|
||||||
|
};
|
||||||
|
|
||||||
|
rsdp.checksum = super::generate_checksum(&rsdp.as_slice()[0..19]);
|
||||||
|
rsdp.extended_checksum = super::generate_checksum(rsdp.as_slice());
|
||||||
|
rsdp
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn len() -> usize {
|
||||||
|
std::mem::size_of::<Rsdp>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::Rsdp;
|
||||||
|
use vm_memory::bytes::ByteValued;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_rsdp() {
|
||||||
|
let rsdp = Rsdp::new(*b"CHYPER", 0xdead_beef);
|
||||||
|
let sum = rsdp
|
||||||
|
.as_slice()
|
||||||
|
.iter()
|
||||||
|
.fold(0u8, |acc, x| acc.wrapping_add(*x));
|
||||||
|
assert_eq!(sum, 0);
|
||||||
|
let sum: u8 = rsdp
|
||||||
|
.as_slice()
|
||||||
|
.iter()
|
||||||
|
.fold(0u8, |acc, x| acc.wrapping_add(*x));
|
||||||
|
assert_eq!(sum, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
146
acpi_tables/src/sdt.rs
Normal file
146
acpi_tables/src/sdt.rs
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
// 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 fn mmio_address<T>(address: u64) -> Self {
|
||||||
|
GenericAddress {
|
||||||
|
address_space_id: 0,
|
||||||
|
register_bit_width: 8 * std::mem::size_of::<T>() as u8,
|
||||||
|
register_bit_offset: 0,
|
||||||
|
access_size: std::mem::size_of::<T>() as u8,
|
||||||
|
address,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Sdt {
|
||||||
|
data: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::len_without_is_empty)]
|
||||||
|
impl Sdt {
|
||||||
|
pub fn new(
|
||||||
|
signature: [u8; 4],
|
||||||
|
length: u32,
|
||||||
|
revision: u8,
|
||||||
|
oem_id: [u8; 6],
|
||||||
|
oem_table: [u8; 8],
|
||||||
|
oem_revision: u32,
|
||||||
|
) -> Self {
|
||||||
|
assert!(length >= 36);
|
||||||
|
|
||||||
|
let mut data = Vec::with_capacity(length as usize);
|
||||||
|
data.extend_from_slice(&signature);
|
||||||
|
data.extend_from_slice(&length.to_le_bytes());
|
||||||
|
data.push(revision);
|
||||||
|
data.push(0); // checksum
|
||||||
|
data.extend_from_slice(&oem_id);
|
||||||
|
data.extend_from_slice(&oem_table);
|
||||||
|
data.extend_from_slice(&oem_revision.to_le_bytes());
|
||||||
|
data.extend_from_slice(b"CLDH");
|
||||||
|
data.extend_from_slice(&0u32.to_le_bytes());
|
||||||
|
assert_eq!(data.len(), 36);
|
||||||
|
|
||||||
|
data.resize(length as usize, 0);
|
||||||
|
let mut sdt = Sdt { data };
|
||||||
|
|
||||||
|
sdt.update_checksum();
|
||||||
|
sdt
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_checksum(&mut self) {
|
||||||
|
self.data[9] = 0;
|
||||||
|
let checksum = super::generate_checksum(self.data.as_slice());
|
||||||
|
self.data[9] = checksum
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_slice(&self) -> &[u8] {
|
||||||
|
self.data.as_slice()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn append<T>(&mut self, value: T) {
|
||||||
|
let orig_length = self.data.len();
|
||||||
|
let new_length = orig_length + std::mem::size_of::<T>();
|
||||||
|
self.data.resize(new_length, 0);
|
||||||
|
self.write_u32(4, new_length as u32);
|
||||||
|
self.write(orig_length, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn append_slice(&mut self, data: &[u8]) {
|
||||||
|
let orig_length = self.data.len();
|
||||||
|
let new_length = orig_length + data.len();
|
||||||
|
self.write_u32(4, new_length as u32);
|
||||||
|
self.data.extend_from_slice(data);
|
||||||
|
self.update_checksum();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write a value at the given offset
|
||||||
|
pub fn write<T>(&mut self, offset: usize, value: T) {
|
||||||
|
assert!((offset + (std::mem::size_of::<T>() - 1)) < self.data.len());
|
||||||
|
unsafe {
|
||||||
|
*(((self.data.as_mut_ptr() as usize) + offset) as *mut T) = value;
|
||||||
|
}
|
||||||
|
self.update_checksum();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_u8(&mut self, offset: usize, val: u8) {
|
||||||
|
self.write(offset, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_u16(&mut self, offset: usize, val: u16) {
|
||||||
|
self.write(offset, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_u32(&mut self, offset: usize, val: u32) {
|
||||||
|
self.write(offset, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_u64(&mut self, offset: usize, val: u64) {
|
||||||
|
self.write(offset, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.data.len()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::Sdt;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_sdt() {
|
||||||
|
let mut sdt = Sdt::new(*b"TEST", 40, 1, *b"CLOUDH", *b"TESTTEST", 1);
|
||||||
|
let sum: u8 = sdt
|
||||||
|
.as_slice()
|
||||||
|
.iter()
|
||||||
|
.fold(0u8, |acc, x| acc.wrapping_add(*x));
|
||||||
|
assert_eq!(sum, 0);
|
||||||
|
sdt.write_u32(36, 0x12345678);
|
||||||
|
let sum: u8 = sdt
|
||||||
|
.as_slice()
|
||||||
|
.iter()
|
||||||
|
.fold(0u8, |acc, x| acc.wrapping_add(*x));
|
||||||
|
assert_eq!(sum, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,8 @@
|
|||||||
[package]
|
[package]
|
||||||
authors = ["The Cloud Hypervisor Authors"]
|
|
||||||
edition = "2021"
|
|
||||||
name = "api_client"
|
name = "api_client"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
authors = ["The Cloud Hypervisor Authors"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
thiserror = "2.0.6"
|
vmm-sys-util = "0.9.0"
|
||||||
vmm-sys-util = { workspace = true }
|
|
||||||
|
|||||||
@@ -3,28 +3,41 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
//
|
//
|
||||||
|
|
||||||
|
use std::fmt;
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
use std::os::unix::io::RawFd;
|
use std::os::unix::io::RawFd;
|
||||||
|
|
||||||
use thiserror::Error;
|
|
||||||
use vmm_sys_util::sock_ctrl_msg::ScmSocket;
|
use vmm_sys_util::sock_ctrl_msg::ScmSocket;
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[error("Error writing to or reading from HTTP socket: {0}")]
|
|
||||||
Socket(std::io::Error),
|
Socket(std::io::Error),
|
||||||
#[error("Error sending file descriptors: {0}")]
|
|
||||||
SocketSendFds(vmm_sys_util::errno::Error),
|
SocketSendFds(vmm_sys_util::errno::Error),
|
||||||
#[error("Error parsing HTTP status code: {0}")]
|
|
||||||
StatusCodeParsing(std::num::ParseIntError),
|
StatusCodeParsing(std::num::ParseIntError),
|
||||||
#[error("HTTP output is missing protocol statement")]
|
|
||||||
MissingProtocol,
|
MissingProtocol,
|
||||||
#[error("Error parsing HTTP Content-Length field: {0}")]
|
|
||||||
ContentLengthParsing(std::num::ParseIntError),
|
ContentLengthParsing(std::num::ParseIntError),
|
||||||
#[error("Server responded with an error: {0:?}: {1:?}")]
|
|
||||||
ServerResponse(StatusCode, Option<String>),
|
ServerResponse(StatusCode, Option<String>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Error {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
use Error::*;
|
||||||
|
match self {
|
||||||
|
Socket(e) => write!(f, "Error writing to or reading from HTTP socket: {}", e),
|
||||||
|
SocketSendFds(e) => write!(f, "Error writing to or reading from HTTP socket: {}", e),
|
||||||
|
StatusCodeParsing(e) => write!(f, "Error parsing HTTP status code: {}", e),
|
||||||
|
MissingProtocol => write!(f, "HTTP output is missing protocol statement"),
|
||||||
|
ContentLengthParsing(e) => write!(f, "Error parsing HTTP Content-Length field: {}", e),
|
||||||
|
ServerResponse(s, o) => {
|
||||||
|
if let Some(o) = o {
|
||||||
|
write!(f, "Server responded with an error: {:?}: {}", s, o)
|
||||||
|
} else {
|
||||||
|
write!(f, "Server responded with an error: {:?}", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub enum StatusCode {
|
pub enum StatusCode {
|
||||||
Continue,
|
Continue,
|
||||||
@@ -66,7 +79,7 @@ impl StatusCode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn get_header<'a>(res: &'a str, header: &'a str) -> Option<&'a str> {
|
fn get_header<'a>(res: &'a str, header: &'a str) -> Option<&'a str> {
|
||||||
let header_str = format!("{header}: ");
|
let header_str = format!("{}: ", header);
|
||||||
res.find(&header_str)
|
res.find(&header_str)
|
||||||
.map(|o| &res[o + header_str.len()..o + res[o..].find('\r').unwrap()])
|
.map(|o| &res[o + header_str.len()..o + res[o..].find('\r').unwrap()])
|
||||||
}
|
}
|
||||||
@@ -88,10 +101,6 @@ fn parse_http_response(socket: &mut dyn Read) -> Result<Option<String>, Error> {
|
|||||||
loop {
|
loop {
|
||||||
let mut bytes = vec![0; 256];
|
let mut bytes = vec![0; 256];
|
||||||
let count = socket.read(&mut bytes).map_err(Error::Socket)?;
|
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());
|
res.push_str(std::str::from_utf8(&bytes[0..count]).unwrap());
|
||||||
|
|
||||||
// End of headers
|
// End of headers
|
||||||
@@ -118,7 +127,7 @@ fn parse_http_response(socket: &mut dyn Read) -> Result<Option<String>, Error> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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)?;
|
let status_code = get_status_code(&res)?;
|
||||||
|
|
||||||
if status_code.is_server_error() {
|
if status_code.is_server_error() {
|
||||||
@@ -128,19 +137,18 @@ fn parse_http_response(socket: &mut dyn Read) -> Result<Option<String>, Error> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Make an API request using the fully qualified command name.
|
pub fn simple_api_command_with_fds<T: Read + Write + ScmSocket>(
|
||||||
/// 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>(
|
|
||||||
socket: &mut T,
|
socket: &mut T,
|
||||||
method: &str,
|
method: &str,
|
||||||
full_command: &str,
|
c: &str,
|
||||||
request_body: Option<&str>,
|
request_body: Option<&str>,
|
||||||
request_fds: Vec<RawFd>,
|
request_fds: Vec<RawFd>,
|
||||||
) -> Result<Option<String>, Error> {
|
) -> Result<(), Error> {
|
||||||
socket
|
socket
|
||||||
.send_with_fds(
|
.send_with_fds(
|
||||||
&[format!(
|
&[format!(
|
||||||
"{method} /api/v1/{full_command} HTTP/1.1\r\nHost: localhost\r\nAccept: */*\r\n"
|
"{} /api/v1/vm.{} HTTP/1.1\r\nHost: localhost\r\nAccept: */*\r\n",
|
||||||
|
method, c
|
||||||
)
|
)
|
||||||
.as_bytes()],
|
.as_bytes()],
|
||||||
&request_fds,
|
&request_fds,
|
||||||
@@ -163,69 +171,12 @@ pub fn simple_api_full_command_with_fds_and_response<T: Read + Write + ScmSocket
|
|||||||
|
|
||||||
socket.flush().map_err(Error::Socket)?;
|
socket.flush().map_err(Error::Socket)?;
|
||||||
|
|
||||||
parse_http_response(socket)
|
if let Some(body) = parse_http_response(socket)? {
|
||||||
}
|
println!("{}", body);
|
||||||
|
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
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>(
|
pub fn simple_api_command<T: Read + Write + ScmSocket>(
|
||||||
socket: &mut T,
|
socket: &mut T,
|
||||||
method: &str,
|
method: &str,
|
||||||
|
|||||||
@@ -1,29 +1,31 @@
|
|||||||
[package]
|
[package]
|
||||||
authors = ["The Chromium OS Authors"]
|
|
||||||
edition = "2021"
|
|
||||||
name = "arch"
|
name = "arch"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
authors = ["The Chromium OS Authors"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
kvm = ["hypervisor/kvm"]
|
acpi = ["acpi_tables"]
|
||||||
sev_snp = []
|
|
||||||
tdx = []
|
tdx = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.94"
|
acpi_tables = { path = "../acpi_tables", optional = true }
|
||||||
byteorder = "1.5.0"
|
anyhow = "1.0.55"
|
||||||
|
byteorder = "1.4.3"
|
||||||
hypervisor = { path = "../hypervisor" }
|
hypervisor = { path = "../hypervisor" }
|
||||||
libc = "0.2.167"
|
libc = "0.2.119"
|
||||||
linux-loader = { workspace = true, features = ["bzimage", "elf", "pe"] }
|
linux-loader = { version = "0.4.0", features = ["elf", "bzimage", "pe"] }
|
||||||
log = "0.4.22"
|
log = "0.4.14"
|
||||||
serde = { version = "1.0.208", features = ["derive", "rc"] }
|
serde = { version = "1.0.136", features = ["rc"] }
|
||||||
thiserror = "2.0.6"
|
serde_derive = "1.0.136"
|
||||||
uuid = "1.12.1"
|
thiserror = "1.0.30"
|
||||||
vm-memory = { workspace = true, features = ["backend-bitmap", "backend-mmap"] }
|
versionize = "0.1.6"
|
||||||
|
versionize_derive = "0.1.4"
|
||||||
|
vm-memory = { version = "0.7.0", features = ["backend-mmap", "backend-bitmap"] }
|
||||||
vm-migration = { path = "../vm-migration" }
|
vm-migration = { path = "../vm-migration" }
|
||||||
vmm-sys-util = { workspace = true, features = ["with-serde"] }
|
vmm-sys-util = { version = "0.9.0", features = ["with-serde"] }
|
||||||
|
|
||||||
[target.'cfg(any(target_arch = "aarch64", target_arch = "riscv64"))'.dependencies]
|
[target.'cfg(target_arch = "aarch64")'.dependencies]
|
||||||
fdt_parser = { version = "0.1.5", package = "fdt" }
|
fdt_parser = { version = "0.1.3", package = 'fdt'}
|
||||||
vm-fdt = { workspace = true }
|
vm-fdt = { git = "https://github.com/rust-vmm/vm-fdt", branch = "main" }
|
||||||
|
|||||||
@@ -6,25 +6,26 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the THIRD-PARTY file.
|
// found in the THIRD-PARTY file.
|
||||||
|
|
||||||
|
use crate::{NumaNodes, PciSpaceInfo};
|
||||||
|
use byteorder::{BigEndian, ByteOrder};
|
||||||
|
use std::cmp;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::ffi::CStr;
|
use std::ffi::CStr;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::path::Path;
|
use std::result;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::str;
|
||||||
use std::{cmp, fs, result, str};
|
|
||||||
|
|
||||||
use byteorder::{BigEndian, ByteOrder};
|
use super::super::DeviceType;
|
||||||
use hypervisor::arch::aarch64::gic::Vgic;
|
use super::super::GuestMemoryMmap;
|
||||||
use thiserror::Error;
|
use super::super::InitramfsConfig;
|
||||||
use vm_fdt::{FdtWriter, FdtWriterResult};
|
use super::get_fdt_addr;
|
||||||
use vm_memory::{Address, Bytes, GuestMemory, GuestMemoryError, GuestMemoryRegion};
|
use super::gic::GicDevice;
|
||||||
|
|
||||||
use super::super::{DeviceType, GuestMemoryMmap, InitramfsConfig};
|
|
||||||
use super::layout::{
|
use super::layout::{
|
||||||
IRQ_BASE, MEM_32BIT_DEVICES_SIZE, MEM_32BIT_DEVICES_START, MEM_PCI_IO_SIZE, MEM_PCI_IO_START,
|
IRQ_BASE, MEM_32BIT_DEVICES_SIZE, MEM_32BIT_DEVICES_START, MEM_PCI_IO_SIZE, MEM_PCI_IO_START,
|
||||||
PCI_HIGH_BASE, PCI_MMIO_CONFIG_SIZE_PER_SEGMENT,
|
PCI_HIGH_BASE, PCI_MMIO_CONFIG_SIZE_PER_SEGMENT,
|
||||||
};
|
};
|
||||||
use crate::{NumaNodes, PciSpaceInfo};
|
use vm_fdt::{FdtWriter, FdtWriterResult};
|
||||||
|
use vm_memory::{Address, Bytes, GuestAddress, GuestMemory, GuestMemoryError, GuestMemoryRegion};
|
||||||
|
|
||||||
// This is a value for uniquely identifying the FDT node declaring the interrupt controller.
|
// This is a value for uniquely identifying the FDT node declaring the interrupt controller.
|
||||||
const GIC_PHANDLE: u32 = 1;
|
const GIC_PHANDLE: u32 = 1;
|
||||||
@@ -39,12 +40,8 @@ const VIRTIO_IOMMU_PHANDLE: u32 = 5;
|
|||||||
// NOTE: Keep FIRST_VCPU_PHANDLE the last PHANDLE defined.
|
// NOTE: Keep FIRST_VCPU_PHANDLE the last PHANDLE defined.
|
||||||
// This is a value for uniquely identifying the FDT node containing the first vCPU.
|
// This is a value for uniquely identifying the FDT node containing the first vCPU.
|
||||||
// The last number of vCPU phandle depends on the number of vCPUs.
|
// The last number of vCPU phandle depends on the number of vCPUs.
|
||||||
const FIRST_VCPU_PHANDLE: u32 = 8;
|
const FIRST_VCPU_PHANDLE: u32 = 6;
|
||||||
|
|
||||||
// This is a value for uniquely identifying the FDT node containing the L2 cache info
|
|
||||||
const L2_CACHE_PHANDLE: u32 = 6;
|
|
||||||
// This is a value for uniquely identifying the FDT node containing the L3 cache info
|
|
||||||
const L3_CACHE_PHANDLE: u32 = 7;
|
|
||||||
// Read the documentation specified when appending the root node to the FDT.
|
// Read the documentation specified when appending the root node to the FDT.
|
||||||
const ADDRESS_CELLS: u32 = 0x2;
|
const ADDRESS_CELLS: u32 = 0x2;
|
||||||
const SIZE_CELLS: u32 = 0x2;
|
const SIZE_CELLS: u32 = 0x2;
|
||||||
@@ -54,6 +51,8 @@ const SIZE_CELLS: u32 = 0x2;
|
|||||||
// Look for "The 1st cell..."
|
// Look for "The 1st cell..."
|
||||||
const GIC_FDT_IRQ_TYPE_SPI: u32 = 0;
|
const GIC_FDT_IRQ_TYPE_SPI: u32 = 0;
|
||||||
const GIC_FDT_IRQ_TYPE_PPI: u32 = 1;
|
const GIC_FDT_IRQ_TYPE_PPI: u32 = 1;
|
||||||
|
const GIC_FDT_IRQ_PPI_CPU_SHIFT: u32 = 8;
|
||||||
|
const GIC_FDT_IRQ_PPI_CPU_MASK: u32 = 0xff << GIC_FDT_IRQ_PPI_CPU_SHIFT;
|
||||||
|
|
||||||
// From https://elixir.bootlin.com/linux/v4.9.62/source/include/dt-bindings/interrupt-controller/irq.h#L17
|
// From https://elixir.bootlin.com/linux/v4.9.62/source/include/dt-bindings/interrupt-controller/irq.h#L17
|
||||||
const IRQ_TYPE_EDGE_RISING: u32 = 1;
|
const IRQ_TYPE_EDGE_RISING: u32 = 1;
|
||||||
@@ -77,140 +76,13 @@ pub trait DeviceInfoForFdt {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Errors thrown while configuring the Flattened Device Tree for aarch64.
|
/// Errors thrown while configuring the Flattened Device Tree for aarch64.
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
/// Failure in writing FDT in memory.
|
/// Failure in writing FDT in memory.
|
||||||
#[error("Failure in writing FDT in memory: {0}")]
|
|
||||||
WriteFdtToMemory(GuestMemoryError),
|
WriteFdtToMemory(GuestMemoryError),
|
||||||
}
|
}
|
||||||
type Result<T> = result::Result<T, Error>;
|
type Result<T> = result::Result<T, Error>;
|
||||||
|
|
||||||
pub enum CacheLevel {
|
|
||||||
/// L1 data cache
|
|
||||||
L1D = 0,
|
|
||||||
/// L1 instruction cache
|
|
||||||
L1I = 1,
|
|
||||||
/// L2 cache
|
|
||||||
L2 = 2,
|
|
||||||
/// L3 cache
|
|
||||||
L3 = 3,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// NOTE: cache size file directory example,
|
|
||||||
/// "/sys/devices/system/cpu/cpu0/cache/index0/size".
|
|
||||||
pub fn get_cache_size(cache_level: CacheLevel) -> u32 {
|
|
||||||
let mut file_directory: String = "/sys/devices/system/cpu/cpu0/cache".to_string();
|
|
||||||
match cache_level {
|
|
||||||
CacheLevel::L1D => file_directory += "/index0/size",
|
|
||||||
CacheLevel::L1I => file_directory += "/index1/size",
|
|
||||||
CacheLevel::L2 => file_directory += "/index2/size",
|
|
||||||
CacheLevel::L3 => file_directory += "/index3/size",
|
|
||||||
}
|
|
||||||
|
|
||||||
let file_path = Path::new(&file_directory);
|
|
||||||
if !file_path.exists() {
|
|
||||||
warn!("File: {} does not exist.", file_directory);
|
|
||||||
0
|
|
||||||
} else {
|
|
||||||
info!("File: {} exist.", file_directory);
|
|
||||||
|
|
||||||
let src = fs::read_to_string(file_directory).expect("File not exists or file corrupted.");
|
|
||||||
// The content of the file is as simple as a size, like: "32K"
|
|
||||||
let src = src.trim();
|
|
||||||
let src_digits: u32 = src[0..src.len() - 1].parse().unwrap();
|
|
||||||
let src_unit = &src[src.len() - 1..];
|
|
||||||
|
|
||||||
src_digits
|
|
||||||
* match src_unit {
|
|
||||||
"K" => 1024,
|
|
||||||
"M" => 1024u32.pow(2),
|
|
||||||
"G" => 1024u32.pow(3),
|
|
||||||
_ => 1,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// NOTE: coherency_line_size file directory example,
|
|
||||||
/// "/sys/devices/system/cpu/cpu0/cache/index0/coherency_line_size".
|
|
||||||
pub fn get_cache_coherency_line_size(cache_level: CacheLevel) -> u32 {
|
|
||||||
let mut file_directory: String = "/sys/devices/system/cpu/cpu0/cache".to_string();
|
|
||||||
match cache_level {
|
|
||||||
CacheLevel::L1D => file_directory += "/index0/coherency_line_size",
|
|
||||||
CacheLevel::L1I => file_directory += "/index1/coherency_line_size",
|
|
||||||
CacheLevel::L2 => file_directory += "/index2/coherency_line_size",
|
|
||||||
CacheLevel::L3 => file_directory += "/index3/coherency_line_size",
|
|
||||||
}
|
|
||||||
|
|
||||||
let file_path = Path::new(&file_directory);
|
|
||||||
if !file_path.exists() {
|
|
||||||
warn!("File: {} does not exist.", file_directory);
|
|
||||||
0
|
|
||||||
} else {
|
|
||||||
info!("File: {} exist.", file_directory);
|
|
||||||
|
|
||||||
let src = fs::read_to_string(file_directory).expect("File not exists or file corrupted.");
|
|
||||||
src.trim().parse::<u32>().unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// NOTE: number_of_sets file directory example,
|
|
||||||
/// "/sys/devices/system/cpu/cpu0/cache/index0/number_of_sets".
|
|
||||||
pub fn get_cache_number_of_sets(cache_level: CacheLevel) -> u32 {
|
|
||||||
let mut file_directory: String = "/sys/devices/system/cpu/cpu0/cache".to_string();
|
|
||||||
match cache_level {
|
|
||||||
CacheLevel::L1D => file_directory += "/index0/number_of_sets",
|
|
||||||
CacheLevel::L1I => file_directory += "/index1/number_of_sets",
|
|
||||||
CacheLevel::L2 => file_directory += "/index2/number_of_sets",
|
|
||||||
CacheLevel::L3 => file_directory += "/index3/number_of_sets",
|
|
||||||
}
|
|
||||||
|
|
||||||
let file_path = Path::new(&file_directory);
|
|
||||||
if !file_path.exists() {
|
|
||||||
warn!("File: {} does not exist.", file_directory);
|
|
||||||
0
|
|
||||||
} else {
|
|
||||||
info!("File: {} exist.", file_directory);
|
|
||||||
|
|
||||||
let src = fs::read_to_string(file_directory).expect("File not exists or file corrupted.");
|
|
||||||
src.trim().parse::<u32>().unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// NOTE: shared_cpu_list file directory example,
|
|
||||||
/// "/sys/devices/system/cpu/cpu0/cache/index0/shared_cpu_list".
|
|
||||||
pub fn get_cache_shared(cache_level: CacheLevel) -> bool {
|
|
||||||
let mut file_directory: String = "/sys/devices/system/cpu/cpu0/cache".to_string();
|
|
||||||
let mut result = true;
|
|
||||||
|
|
||||||
match cache_level {
|
|
||||||
CacheLevel::L1D | CacheLevel::L1I => result = false,
|
|
||||||
CacheLevel::L2 => file_directory += "/index2/shared_cpu_list",
|
|
||||||
CacheLevel::L3 => file_directory += "/index3/shared_cpu_list",
|
|
||||||
}
|
|
||||||
|
|
||||||
if !result {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let file_path = Path::new(&file_directory);
|
|
||||||
if !file_path.exists() {
|
|
||||||
warn!("File: {} does not exist.", file_directory);
|
|
||||||
result = false;
|
|
||||||
} else {
|
|
||||||
info!("File: {} exist.", file_directory);
|
|
||||||
|
|
||||||
let src = fs::read_to_string(file_directory).expect("File not exists or file corrupted.");
|
|
||||||
let src = src.trim();
|
|
||||||
if src.is_empty() {
|
|
||||||
result = false;
|
|
||||||
} else {
|
|
||||||
result = src.contains('-') || src.contains(',');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates the flattened device tree for this aarch64 VM.
|
/// Creates the flattened device tree for this aarch64 VM.
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn create_fdt<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHasher>(
|
pub fn create_fdt<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHasher>(
|
||||||
@@ -219,7 +91,7 @@ pub fn create_fdt<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHash
|
|||||||
vcpu_mpidr: Vec<u64>,
|
vcpu_mpidr: Vec<u64>,
|
||||||
vcpu_topology: Option<(u8, u8, u8)>,
|
vcpu_topology: Option<(u8, u8, u8)>,
|
||||||
device_info: &HashMap<(DeviceType, String), T, S>,
|
device_info: &HashMap<(DeviceType, String), T, S>,
|
||||||
gic_device: &Arc<Mutex<dyn Vgic>>,
|
gic_device: &dyn GicDevice,
|
||||||
initrd: &Option<InitramfsConfig>,
|
initrd: &Option<InitramfsConfig>,
|
||||||
pci_space_info: &[PciSpaceInfo],
|
pci_space_info: &[PciSpaceInfo],
|
||||||
numa_nodes: &NumaNodes,
|
numa_nodes: &NumaNodes,
|
||||||
@@ -249,7 +121,7 @@ pub fn create_fdt<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHash
|
|||||||
create_gic_node(&mut fdt, gic_device)?;
|
create_gic_node(&mut fdt, gic_device)?;
|
||||||
create_timer_node(&mut fdt)?;
|
create_timer_node(&mut fdt)?;
|
||||||
if pmu_supported {
|
if pmu_supported {
|
||||||
create_pmu_node(&mut fdt)?;
|
create_pmu_node(&mut fdt, vcpu_mpidr.len())?;
|
||||||
}
|
}
|
||||||
create_clock_node(&mut fdt)?;
|
create_clock_node(&mut fdt)?;
|
||||||
create_psci_node(&mut fdt)?;
|
create_psci_node(&mut fdt)?;
|
||||||
@@ -269,8 +141,9 @@ pub fn create_fdt<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHash
|
|||||||
|
|
||||||
pub fn write_fdt_to_memory(fdt_final: Vec<u8>, guest_mem: &GuestMemoryMmap) -> Result<()> {
|
pub fn write_fdt_to_memory(fdt_final: Vec<u8>, guest_mem: &GuestMemoryMmap) -> Result<()> {
|
||||||
// Write FDT to memory.
|
// Write FDT to memory.
|
||||||
|
let fdt_address = GuestAddress(get_fdt_addr());
|
||||||
guest_mem
|
guest_mem
|
||||||
.write_slice(fdt_final.as_slice(), super::layout::FDT_START)
|
.write_slice(fdt_final.as_slice(), fdt_address)
|
||||||
.map_err(Error::WriteFdtToMemory)?;
|
.map_err(Error::WriteFdtToMemory)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -288,71 +161,9 @@ fn create_cpu_nodes(
|
|||||||
fdt.property_u32("#size-cells", 0x0)?;
|
fdt.property_u32("#size-cells", 0x0)?;
|
||||||
|
|
||||||
let num_cpus = vcpu_mpidr.len();
|
let num_cpus = vcpu_mpidr.len();
|
||||||
let (threads_per_core, cores_per_package, packages) = vcpu_topology.unwrap_or((1, 1, 1));
|
|
||||||
let max_cpus: u32 = (threads_per_core * cores_per_package * packages).into();
|
|
||||||
|
|
||||||
// Add cache info.
|
|
||||||
// L1 Data Cache Info.
|
|
||||||
let mut l1_d_cache_size: u32 = 0;
|
|
||||||
let mut l1_d_cache_line_size: u32 = 0;
|
|
||||||
let mut l1_d_cache_sets: u32 = 0;
|
|
||||||
|
|
||||||
// L1 Instruction Cache Info.
|
|
||||||
let mut l1_i_cache_size: u32 = 0;
|
|
||||||
let mut l1_i_cache_line_size: u32 = 0;
|
|
||||||
let mut l1_i_cache_sets: u32 = 0;
|
|
||||||
|
|
||||||
// L2 Cache Info.
|
|
||||||
let mut l2_cache_size: u32 = 0;
|
|
||||||
let mut l2_cache_line_size: u32 = 0;
|
|
||||||
let mut l2_cache_sets: u32 = 0;
|
|
||||||
|
|
||||||
// L3 Cache Info.
|
|
||||||
let mut l3_cache_size: u32 = 0;
|
|
||||||
let mut l3_cache_line_size: u32 = 0;
|
|
||||||
let mut l3_cache_sets: u32 = 0;
|
|
||||||
|
|
||||||
// Cache Shared Info.
|
|
||||||
let mut l2_cache_shared: bool = false;
|
|
||||||
let mut l3_cache_shared: bool = false;
|
|
||||||
|
|
||||||
let cache_path = Path::new("/sys/devices/system/cpu/cpu0/cache");
|
|
||||||
let cache_exist: bool = cache_path.exists();
|
|
||||||
if !cache_exist {
|
|
||||||
warn!("cache sysfs system does not exist.");
|
|
||||||
} else {
|
|
||||||
info!("cache sysfs system exists.");
|
|
||||||
// L1 Data Cache Info.
|
|
||||||
l1_d_cache_size = get_cache_size(CacheLevel::L1D);
|
|
||||||
l1_d_cache_line_size = get_cache_coherency_line_size(CacheLevel::L1D);
|
|
||||||
l1_d_cache_sets = get_cache_number_of_sets(CacheLevel::L1D);
|
|
||||||
|
|
||||||
// L1 Instruction Cache Info.
|
|
||||||
l1_i_cache_size = get_cache_size(CacheLevel::L1I);
|
|
||||||
l1_i_cache_line_size = get_cache_coherency_line_size(CacheLevel::L1I);
|
|
||||||
l1_i_cache_sets = get_cache_number_of_sets(CacheLevel::L1I);
|
|
||||||
|
|
||||||
// L2 Cache Info.
|
|
||||||
l2_cache_size = get_cache_size(CacheLevel::L2);
|
|
||||||
l2_cache_line_size = get_cache_coherency_line_size(CacheLevel::L2);
|
|
||||||
l2_cache_sets = get_cache_number_of_sets(CacheLevel::L2);
|
|
||||||
|
|
||||||
// L3 Cache Info.
|
|
||||||
l3_cache_size = get_cache_size(CacheLevel::L3);
|
|
||||||
l3_cache_line_size = get_cache_coherency_line_size(CacheLevel::L3);
|
|
||||||
l3_cache_sets = get_cache_number_of_sets(CacheLevel::L3);
|
|
||||||
|
|
||||||
// Cache Shared Info.
|
|
||||||
if l2_cache_size != 0 {
|
|
||||||
l2_cache_shared = get_cache_shared(CacheLevel::L2);
|
|
||||||
}
|
|
||||||
if l3_cache_size != 0 {
|
|
||||||
l3_cache_shared = get_cache_shared(CacheLevel::L3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (cpu_id, mpidr) in vcpu_mpidr.iter().enumerate().take(num_cpus) {
|
for (cpu_id, mpidr) in vcpu_mpidr.iter().enumerate().take(num_cpus) {
|
||||||
let cpu_name = format!("cpu@{cpu_id:x}");
|
let cpu_name = format!("cpu@{:x}", cpu_id);
|
||||||
let cpu_node = fdt.begin_node(&cpu_name)?;
|
let cpu_node = fdt.begin_node(&cpu_name)?;
|
||||||
fdt.property_string("device_type", "cpu")?;
|
fdt.property_string("device_type", "cpu")?;
|
||||||
fdt.property_string("compatible", "arm,arm-v8")?;
|
fdt.property_string("compatible", "arm,arm-v8")?;
|
||||||
@@ -375,112 +186,26 @@ fn create_cpu_nodes(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if cache_exist && l1_d_cache_size != 0 && l1_i_cache_size != 0 {
|
|
||||||
// Add cache info.
|
|
||||||
fdt.property_u32("d-cache-size", l1_d_cache_size)?;
|
|
||||||
fdt.property_u32("d-cache-line-size", l1_d_cache_line_size)?;
|
|
||||||
fdt.property_u32("d-cache-sets", l1_d_cache_sets)?;
|
|
||||||
|
|
||||||
fdt.property_u32("i-cache-size", l1_i_cache_size)?;
|
|
||||||
fdt.property_u32("i-cache-line-size", l1_i_cache_line_size)?;
|
|
||||||
fdt.property_u32("i-cache-sets", l1_i_cache_sets)?;
|
|
||||||
|
|
||||||
if l2_cache_size != 0 && !l2_cache_shared {
|
|
||||||
fdt.property_u32(
|
|
||||||
"next-level-cache",
|
|
||||||
cpu_id as u32 + max_cpus + FIRST_VCPU_PHANDLE + L2_CACHE_PHANDLE,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let l2_cache_name = "l2-cache0";
|
|
||||||
let l2_cache_node = fdt.begin_node(l2_cache_name)?;
|
|
||||||
// PHANDLE is used to mark device node, and PHANDLE is unique. To avoid phandle
|
|
||||||
// conflicts with other device nodes, consider the previous CPU PHANDLE, so the
|
|
||||||
// CPU L2 cache PHANDLE must start from the largest CPU PHANDLE plus 1.
|
|
||||||
fdt.property_u32(
|
|
||||||
"phandle",
|
|
||||||
cpu_id as u32 + max_cpus + FIRST_VCPU_PHANDLE + L2_CACHE_PHANDLE,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
fdt.property_string("compatible", "cache")?;
|
|
||||||
fdt.property_u32("cache-size", l2_cache_size)?;
|
|
||||||
fdt.property_u32("cache-line-size", l2_cache_line_size)?;
|
|
||||||
fdt.property_u32("cache-sets", l2_cache_sets)?;
|
|
||||||
fdt.property_u32("cache-level", 2)?;
|
|
||||||
|
|
||||||
if l3_cache_size != 0 && l3_cache_shared {
|
|
||||||
let package_id: u32 = cpu_id as u32 / cores_per_package as u32;
|
|
||||||
fdt.property_u32(
|
|
||||||
"next-level-cache",
|
|
||||||
package_id
|
|
||||||
+ num_cpus as u32
|
|
||||||
+ max_cpus
|
|
||||||
+ FIRST_VCPU_PHANDLE
|
|
||||||
+ L2_CACHE_PHANDLE
|
|
||||||
+ L3_CACHE_PHANDLE,
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
fdt.end_node(l2_cache_node)?;
|
|
||||||
}
|
|
||||||
if l2_cache_size != 0 && l2_cache_shared {
|
|
||||||
warn!("L2 cache shared with other cpus");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fdt.end_node(cpu_node)?;
|
fdt.end_node(cpu_node)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if cache_exist && l3_cache_size != 0 && !l2_cache_shared && l3_cache_shared {
|
|
||||||
let mut i: u32 = 0;
|
|
||||||
while i < packages.into() {
|
|
||||||
let l3_cache_name = "l3-cache0";
|
|
||||||
let l3_cache_node = fdt.begin_node(l3_cache_name)?;
|
|
||||||
// ARM L3 cache is generally shared within the package (socket), so the
|
|
||||||
// L3 cache node pointed to by the CPU in the package has the same L3
|
|
||||||
// cache PHANDLE. The L3 cache phandle must start from the largest L2
|
|
||||||
// cache PHANDLE plus 1 to avoid duplication.
|
|
||||||
fdt.property_u32(
|
|
||||||
"phandle",
|
|
||||||
i + num_cpus as u32
|
|
||||||
+ max_cpus
|
|
||||||
+ FIRST_VCPU_PHANDLE
|
|
||||||
+ L2_CACHE_PHANDLE
|
|
||||||
+ L3_CACHE_PHANDLE,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
fdt.property_string("compatible", "cache")?;
|
|
||||||
fdt.property_null("cache-unified")?;
|
|
||||||
fdt.property_u32("cache-size", l3_cache_size)?;
|
|
||||||
fdt.property_u32("cache-line-size", l3_cache_line_size)?;
|
|
||||||
fdt.property_u32("cache-sets", l3_cache_sets)?;
|
|
||||||
fdt.property_u32("cache-level", 3)?;
|
|
||||||
fdt.end_node(l3_cache_node)?;
|
|
||||||
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(topology) = vcpu_topology {
|
if let Some(topology) = vcpu_topology {
|
||||||
let (threads_per_core, cores_per_package, packages) = topology;
|
let (threads_per_core, cores_per_package, packages) = topology;
|
||||||
let cpu_map_node = fdt.begin_node("cpu-map")?;
|
let cpu_map_node = fdt.begin_node("cpu-map")?;
|
||||||
|
|
||||||
// Create device tree nodes with regard of above mapping.
|
// Create device tree nodes with regard of above mapping.
|
||||||
for package_idx in 0..packages {
|
for cluster_idx in 0..packages {
|
||||||
let package_name = format!("socket{package_idx:x}");
|
let cluster_name = format!("cluster{:x}", cluster_idx);
|
||||||
let package_node = fdt.begin_node(&package_name)?;
|
let cluster_node = fdt.begin_node(&cluster_name)?;
|
||||||
|
|
||||||
// Cluster is the container of cores, and it is mandatory in the CPU topology.
|
|
||||||
// Add a default "cluster0" in each socket/package.
|
|
||||||
let cluster_node = fdt.begin_node("cluster0")?;
|
|
||||||
|
|
||||||
for core_idx in 0..cores_per_package {
|
for core_idx in 0..cores_per_package {
|
||||||
let core_name = format!("core{core_idx:x}");
|
let core_name = format!("core{:x}", core_idx);
|
||||||
let core_node = fdt.begin_node(&core_name)?;
|
let core_node = fdt.begin_node(&core_name)?;
|
||||||
|
|
||||||
for thread_idx in 0..threads_per_core {
|
for thread_idx in 0..threads_per_core {
|
||||||
let thread_name = format!("thread{thread_idx:x}");
|
let thread_name = format!("thread{:x}", thread_idx);
|
||||||
let thread_node = fdt.begin_node(&thread_name)?;
|
let thread_node = fdt.begin_node(&thread_name)?;
|
||||||
let cpu_idx = threads_per_core * cores_per_package * package_idx
|
let cpu_idx = threads_per_core * cores_per_package * cluster_idx
|
||||||
+ threads_per_core * core_idx
|
+ threads_per_core * core_idx
|
||||||
+ thread_idx;
|
+ thread_idx;
|
||||||
fdt.property_u32("cpu", cpu_idx as u32 + FIRST_VCPU_PHANDLE)?;
|
fdt.property_u32("cpu", cpu_idx as u32 + FIRST_VCPU_PHANDLE)?;
|
||||||
@@ -490,7 +215,6 @@ fn create_cpu_nodes(
|
|||||||
fdt.end_node(core_node)?;
|
fdt.end_node(core_node)?;
|
||||||
}
|
}
|
||||||
fdt.end_node(cluster_node)?;
|
fdt.end_node(cluster_node)?;
|
||||||
fdt.end_node(package_node)?;
|
|
||||||
}
|
}
|
||||||
fdt.end_node(cpu_map_node)?;
|
fdt.end_node(cpu_map_node)?;
|
||||||
} else {
|
} else {
|
||||||
@@ -507,118 +231,40 @@ fn create_memory_node(
|
|||||||
guest_mem: &GuestMemoryMmap,
|
guest_mem: &GuestMemoryMmap,
|
||||||
numa_nodes: &NumaNodes,
|
numa_nodes: &NumaNodes,
|
||||||
) -> FdtWriterResult<()> {
|
) -> FdtWriterResult<()> {
|
||||||
// See https://github.com/torvalds/linux/blob/58ae0b51506802713aa0e9956d1853ba4c722c98/Documentation/devicetree/bindings/numa.txt
|
|
||||||
// for NUMA setting in memory node.
|
|
||||||
if numa_nodes.len() > 1 {
|
if numa_nodes.len() > 1 {
|
||||||
for numa_node_idx in 0..numa_nodes.len() {
|
for numa_node_idx in 0..numa_nodes.len() {
|
||||||
let numa_node = numa_nodes.get(&(numa_node_idx as u32));
|
let numa_node = numa_nodes.get(&(numa_node_idx as u32));
|
||||||
let mut mem_reg_prop: Vec<u64> = Vec::new();
|
|
||||||
let mut node_memory_addr: u64 = 0;
|
|
||||||
// Each memory zone of numa will have its own memory node, but
|
// Each memory zone of numa will have its own memory node, but
|
||||||
// different numa nodes should not share same memory zones.
|
// different numa nodes should not share same memory zones.
|
||||||
for memory_region in numa_node.unwrap().memory_regions.iter() {
|
for memory_region in numa_node.unwrap().memory_regions.iter() {
|
||||||
let memory_region_start_addr: u64 = memory_region.start_addr().raw_value();
|
let memory_region_start_addr: u64 = memory_region.start_addr().raw_value();
|
||||||
let memory_region_size: u64 = memory_region.size() as u64;
|
let memory_region_size: u64 = memory_region.size() as u64;
|
||||||
mem_reg_prop.push(memory_region_start_addr);
|
let mem_reg_prop = [memory_region_start_addr, memory_region_size];
|
||||||
mem_reg_prop.push(memory_region_size);
|
// With feature `acpi` enabled, RAM at 0-4M is for edk2 only
|
||||||
// Set the node address the first non-zero region address
|
// and should be hidden to the guest.
|
||||||
if node_memory_addr == 0 {
|
#[cfg(feature = "acpi")]
|
||||||
node_memory_addr = memory_region_start_addr;
|
if memory_region_start_addr == 0 {
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let memory_node_name = format!("memory@{:x}", memory_region_start_addr);
|
||||||
|
let memory_node = fdt.begin_node(&memory_node_name)?;
|
||||||
|
fdt.property_string("device_type", "memory")?;
|
||||||
|
fdt.property_array_u64("reg", &mem_reg_prop)?;
|
||||||
|
fdt.property_u32("numa-node-id", numa_node_idx as u32)?;
|
||||||
|
fdt.end_node(memory_node)?;
|
||||||
}
|
}
|
||||||
let memory_node_name = format!("memory@{node_memory_addr:x}");
|
|
||||||
let memory_node = fdt.begin_node(&memory_node_name)?;
|
|
||||||
fdt.property_string("device_type", "memory")?;
|
|
||||||
fdt.property_array_u64("reg", &mem_reg_prop)?;
|
|
||||||
fdt.property_u32("numa-node-id", numa_node_idx as u32)?;
|
|
||||||
fdt.end_node(memory_node)?;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Note: memory regions from "GuestMemory" are sorted and non-zero sized.
|
let mem_size = guest_mem.last_addr().raw_value() - super::layout::RAM_64BIT_START + 1;
|
||||||
let ram_regions = {
|
// See https://github.com/torvalds/linux/blob/master/Documentation/devicetree/booting-without-of.txt#L960
|
||||||
let mut ram_regions = Vec::new();
|
// for an explanation of this.
|
||||||
let mut current_start = guest_mem
|
let mem_reg_prop = [super::layout::RAM_64BIT_START as u64, mem_size as u64];
|
||||||
.iter()
|
let memory_node = fdt.begin_node("memory")?;
|
||||||
.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
|
fdt.property_string("device_type", "memory")?;
|
||||||
.iter()
|
fdt.property_array_u64("reg", &mem_reg_prop)?;
|
||||||
.map(|m| (m.start_addr().raw_value(), m.len()))
|
fdt.end_node(memory_node)?;
|
||||||
{
|
|
||||||
if current_end == start {
|
|
||||||
// This zone is continuous with the previous one.
|
|
||||||
current_end += size;
|
|
||||||
} else {
|
|
||||||
ram_regions.push((current_start, current_end));
|
|
||||||
|
|
||||||
current_start = start;
|
|
||||||
current_end = start + size;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ram_regions.push((current_start, current_end));
|
|
||||||
|
|
||||||
ram_regions
|
|
||||||
};
|
|
||||||
|
|
||||||
if ram_regions.len() > 2 {
|
|
||||||
panic!(
|
|
||||||
"There should be up to two non-continuous regions, divided by the
|
|
||||||
gap at the end of 32bit address space."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the memory node for memory region before the gap
|
|
||||||
{
|
|
||||||
let (first_region_start, first_region_end) = ram_regions
|
|
||||||
.first()
|
|
||||||
.expect("There should be at last one memory region");
|
|
||||||
let ram_start = super::layout::RAM_START.raw_value();
|
|
||||||
let mem_32bit_reserved_start = super::layout::MEM_32BIT_RESERVED_START.raw_value();
|
|
||||||
|
|
||||||
if !((first_region_start <= &ram_start)
|
|
||||||
&& (first_region_end > &ram_start)
|
|
||||||
&& (first_region_end <= &mem_32bit_reserved_start))
|
|
||||||
{
|
|
||||||
panic!(
|
|
||||||
"Unexpected first memory region layout: (start: 0x{:08x}, end: 0x{:08x}).
|
|
||||||
ram_start: 0x{:08x}, mem_32bit_reserved_start: 0x{:08x}",
|
|
||||||
first_region_start, first_region_end, ram_start, mem_32bit_reserved_start
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mem_size = first_region_end - ram_start;
|
|
||||||
let mem_reg_prop = [ram_start, mem_size];
|
|
||||||
let memory_node_name = format!("memory@{:x}", ram_start);
|
|
||||||
let memory_node = fdt.begin_node(&memory_node_name)?;
|
|
||||||
fdt.property_string("device_type", "memory")?;
|
|
||||||
fdt.property_array_u64("reg", &mem_reg_prop)?;
|
|
||||||
fdt.end_node(memory_node)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the memory map entry for memory region after the gap if any
|
|
||||||
if let Some((second_region_start, second_region_end)) = ram_regions.get(1) {
|
|
||||||
let ram_64bit_start = super::layout::RAM_64BIT_START.raw_value();
|
|
||||||
|
|
||||||
if second_region_start != &ram_64bit_start {
|
|
||||||
panic!(
|
|
||||||
"Unexpected second memory region layout: start: 0x{:08x}, ram_64bit_start: 0x{:08x}",
|
|
||||||
second_region_start, ram_64bit_start
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mem_size = second_region_end - ram_64bit_start;
|
|
||||||
let mem_reg_prop = [ram_64bit_start, mem_size];
|
|
||||||
let memory_node_name = format!("memory@{:x}", ram_64bit_start);
|
|
||||||
let memory_node = fdt.begin_node(&memory_node_name)?;
|
|
||||||
fdt.property_string("device_type", "memory")?;
|
|
||||||
fdt.property_array_u64("reg", &mem_reg_prop)?;
|
|
||||||
fdt.end_node(memory_node)?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -633,7 +279,7 @@ fn create_chosen_node(
|
|||||||
fdt.property_string("bootargs", cmdline)?;
|
fdt.property_string("bootargs", cmdline)?;
|
||||||
|
|
||||||
if let Some(initrd_config) = initrd {
|
if let Some(initrd_config) = initrd {
|
||||||
let initrd_start = initrd_config.address.raw_value();
|
let initrd_start = initrd_config.address.raw_value() as u64;
|
||||||
let initrd_end = initrd_config.address.raw_value() + initrd_config.size as u64;
|
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-start", initrd_start)?;
|
||||||
fdt.property_u64("linux,initrd-end", initrd_end)?;
|
fdt.property_u64("linux,initrd-end", initrd_end)?;
|
||||||
@@ -644,18 +290,18 @@ fn create_chosen_node(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_gic_node(fdt: &mut FdtWriter, gic_device: &Arc<Mutex<dyn Vgic>>) -> FdtWriterResult<()> {
|
fn create_gic_node(fdt: &mut FdtWriter, gic_device: &dyn GicDevice) -> FdtWriterResult<()> {
|
||||||
let gic_reg_prop = gic_device.lock().unwrap().device_properties();
|
let gic_reg_prop = gic_device.device_properties();
|
||||||
|
|
||||||
let intc_node = fdt.begin_node("intc")?;
|
let intc_node = fdt.begin_node("intc")?;
|
||||||
|
|
||||||
fdt.property_string("compatible", gic_device.lock().unwrap().fdt_compatibility())?;
|
fdt.property_string("compatible", gic_device.fdt_compatibility())?;
|
||||||
fdt.property_null("interrupt-controller")?;
|
fdt.property_null("interrupt-controller")?;
|
||||||
// "interrupt-cells" field specifies the number of cells needed to encode an
|
// "interrupt-cells" field specifies the number of cells needed to encode an
|
||||||
// interrupt source. The type shall be a <u32> and the value shall be 3 if no PPI affinity description
|
// interrupt source. The type shall be a <u32> and the value shall be 3 if no PPI affinity description
|
||||||
// is required.
|
// is required.
|
||||||
fdt.property_u32("#interrupt-cells", 3)?;
|
fdt.property_u32("#interrupt-cells", 3)?;
|
||||||
fdt.property_array_u64("reg", &gic_reg_prop)?;
|
fdt.property_array_u64("reg", gic_reg_prop)?;
|
||||||
fdt.property_u32("phandle", GIC_PHANDLE)?;
|
fdt.property_u32("phandle", GIC_PHANDLE)?;
|
||||||
fdt.property_u32("#address-cells", 2)?;
|
fdt.property_u32("#address-cells", 2)?;
|
||||||
fdt.property_u32("#size-cells", 2)?;
|
fdt.property_u32("#size-cells", 2)?;
|
||||||
@@ -663,18 +309,18 @@ fn create_gic_node(fdt: &mut FdtWriter, gic_device: &Arc<Mutex<dyn Vgic>>) -> Fd
|
|||||||
|
|
||||||
let gic_intr_prop = [
|
let gic_intr_prop = [
|
||||||
GIC_FDT_IRQ_TYPE_PPI,
|
GIC_FDT_IRQ_TYPE_PPI,
|
||||||
gic_device.lock().unwrap().fdt_maint_irq(),
|
gic_device.fdt_maint_irq(),
|
||||||
IRQ_TYPE_LEVEL_HI,
|
IRQ_TYPE_LEVEL_HI,
|
||||||
];
|
];
|
||||||
fdt.property_array_u32("interrupts", &gic_intr_prop)?;
|
fdt.property_array_u32("interrupts", &gic_intr_prop)?;
|
||||||
|
|
||||||
if gic_device.lock().unwrap().msi_compatible() {
|
if gic_device.msi_compatible() {
|
||||||
let msic_node = fdt.begin_node("msic")?;
|
let msic_node = fdt.begin_node("msic")?;
|
||||||
fdt.property_string("compatible", gic_device.lock().unwrap().msi_compatibility())?;
|
fdt.property_string("compatible", gic_device.msi_compatibility())?;
|
||||||
fdt.property_null("msi-controller")?;
|
fdt.property_null("msi-controller")?;
|
||||||
fdt.property_u32("phandle", MSI_PHANDLE)?;
|
fdt.property_u32("phandle", MSI_PHANDLE)?;
|
||||||
let msi_reg_prop = gic_device.lock().unwrap().msi_properties();
|
let msi_reg_prop = gic_device.msi_properties();
|
||||||
fdt.property_array_u64("reg", &msi_reg_prop)?;
|
fdt.property_array_u64("reg", msi_reg_prop)?;
|
||||||
fdt.end_node(msic_node)?;
|
fdt.end_node(msic_node)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -869,9 +515,16 @@ fn create_devices_node<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::Buil
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_pmu_node(fdt: &mut FdtWriter) -> FdtWriterResult<()> {
|
fn create_pmu_node(fdt: &mut FdtWriter, cpu_nums: usize) -> FdtWriterResult<()> {
|
||||||
|
let num_cpus = cpu_nums as u64 as u32;
|
||||||
let compatible = "arm,armv8-pmuv3";
|
let compatible = "arm,armv8-pmuv3";
|
||||||
let irq = [GIC_FDT_IRQ_TYPE_PPI, AARCH64_PMU_IRQ, IRQ_TYPE_LEVEL_HI];
|
let cpu_mask: u32 =
|
||||||
|
(((1 << num_cpus) - 1) << GIC_FDT_IRQ_PPI_CPU_SHIFT) & GIC_FDT_IRQ_PPI_CPU_MASK;
|
||||||
|
let irq = [
|
||||||
|
GIC_FDT_IRQ_TYPE_PPI,
|
||||||
|
AARCH64_PMU_IRQ,
|
||||||
|
cpu_mask | IRQ_TYPE_LEVEL_HI,
|
||||||
|
];
|
||||||
|
|
||||||
let pmu_node = fdt.begin_node("pmu")?;
|
let pmu_node = fdt.begin_node("pmu")?;
|
||||||
fdt.property_string("compatible", compatible)?;
|
fdt.property_string("compatible", compatible)?;
|
||||||
@@ -892,22 +545,23 @@ fn create_pci_nodes(
|
|||||||
for pci_device_info_elem in pci_device_info.iter() {
|
for pci_device_info_elem in pci_device_info.iter() {
|
||||||
// EDK2 requires the PCIe high space above 4G address.
|
// EDK2 requires the PCIe high space above 4G address.
|
||||||
// The actual space in CLH follows the RAM. If the RAM space is small, the PCIe high space
|
// The actual space in CLH follows the RAM. If the RAM space is small, the PCIe high space
|
||||||
// could fall below 4G.
|
// could fall bellow 4G.
|
||||||
// Here we cut off PCI device space below 8G in FDT to workaround the EDK2 check.
|
// Here we cut off PCI device space below 8G in FDT to workaround the EDK2 check.
|
||||||
// But the address written in ACPI is not impacted.
|
// But the address written in ACPI is not impacted.
|
||||||
let (pci_device_base_64bit, pci_device_size_64bit) =
|
let (pci_device_base_64bit, pci_device_size_64bit) = if cfg!(feature = "acpi")
|
||||||
if pci_device_info_elem.pci_device_space_start < PCI_HIGH_BASE.raw_value() {
|
&& (pci_device_info_elem.pci_device_space_start < PCI_HIGH_BASE)
|
||||||
(
|
{
|
||||||
PCI_HIGH_BASE.raw_value(),
|
(
|
||||||
pci_device_info_elem.pci_device_space_size
|
PCI_HIGH_BASE,
|
||||||
- (PCI_HIGH_BASE.raw_value() - pci_device_info_elem.pci_device_space_start),
|
pci_device_info_elem.pci_device_space_size
|
||||||
)
|
- (PCI_HIGH_BASE - pci_device_info_elem.pci_device_space_start),
|
||||||
} else {
|
)
|
||||||
(
|
} else {
|
||||||
pci_device_info_elem.pci_device_space_start,
|
(
|
||||||
pci_device_info_elem.pci_device_space_size,
|
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
|
// There is no specific requirement of the 32bit MMIO range, and
|
||||||
// therefore at least we can make these ranges 4K aligned.
|
// therefore at least we can make these ranges 4K aligned.
|
||||||
let pci_device_size_32bit: u64 =
|
let pci_device_size_32bit: u64 =
|
||||||
@@ -1000,7 +654,7 @@ fn create_pci_nodes(
|
|||||||
|
|
||||||
// See kernel document Documentation/devicetree/bindings/virtio/iommu.txt
|
// See kernel document Documentation/devicetree/bindings/virtio/iommu.txt
|
||||||
// for virtio-iommu node settings.
|
// for virtio-iommu node settings.
|
||||||
let virtio_iommu_node_name = format!("virtio_iommu@{virtio_iommu_bdf:x}");
|
let virtio_iommu_node_name = format!("virtio_iommu@{:x}", virtio_iommu_bdf);
|
||||||
let virtio_iommu_node = fdt.begin_node(&virtio_iommu_node_name)?;
|
let virtio_iommu_node = fdt.begin_node(&virtio_iommu_node_name)?;
|
||||||
fdt.property_u32("#iommu-cells", 1)?;
|
fdt.property_u32("#iommu-cells", 1)?;
|
||||||
fdt.property_string("compatible", "virtio,pci-iommu")?;
|
fdt.property_string("compatible", "virtio,pci-iommu")?;
|
||||||
@@ -1109,7 +763,10 @@ fn print_node(node: fdt_parser::node::FdtNode<'_, '_>, n_spaces: usize) {
|
|||||||
// - At first, try to convert it to CStr and print,
|
// - At first, try to convert it to CStr and print,
|
||||||
// - If failed, print it as u32 array.
|
// - If failed, print it as u32 array.
|
||||||
let value_result = match CStr::from_bytes_with_nul(value) {
|
let value_result = match CStr::from_bytes_with_nul(value) {
|
||||||
Ok(value_cstr) => value_cstr.to_str().ok(),
|
Ok(value_cstr) => match value_cstr.to_str() {
|
||||||
|
Ok(value_str) => Some(value_str),
|
||||||
|
Err(_e) => None,
|
||||||
|
},
|
||||||
Err(_e) => None,
|
Err(_e) => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use kvm_ioctls::DeviceFd;
|
use super::{Error, Result};
|
||||||
|
use crate::layout::IRQ_BASE;
|
||||||
use crate::arch::aarch64::gic::{Error, Result};
|
use hypervisor::kvm::kvm_bindings::{
|
||||||
use crate::device::HypervisorDeviceError;
|
|
||||||
use crate::kvm::kvm_bindings::{
|
|
||||||
kvm_device_attr, KVM_DEV_ARM_VGIC_GRP_DIST_REGS, KVM_DEV_ARM_VGIC_GRP_NR_IRQS,
|
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
|
Distributor registers as detailed at page 456 from
|
||||||
@@ -78,71 +77,55 @@ static VGIC_DIST_REGS: &[DistReg] = &[
|
|||||||
VGIC_DIST_REG!(GICD_IPRIORITYR, 8, 0),
|
VGIC_DIST_REG!(GICD_IPRIORITYR, 8, 0),
|
||||||
];
|
];
|
||||||
|
|
||||||
fn dist_attr_set(gic: &DeviceFd, offset: u32, val: u32) -> Result<()> {
|
fn dist_attr_access(
|
||||||
let gic_dist_attr = kvm_device_attr {
|
gic: &Arc<dyn hypervisor::Device>,
|
||||||
group: KVM_DEV_ARM_VGIC_GRP_DIST_REGS,
|
offset: u32,
|
||||||
attr: offset as u64,
|
val: &u32,
|
||||||
addr: &val as *const u32 as u64,
|
set: bool,
|
||||||
flags: 0,
|
) -> Result<()> {
|
||||||
};
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
let mut gic_dist_attr = kvm_device_attr {
|
let mut gic_dist_attr = kvm_device_attr {
|
||||||
group: KVM_DEV_ARM_VGIC_GRP_DIST_REGS,
|
group: KVM_DEV_ARM_VGIC_GRP_DIST_REGS,
|
||||||
attr: offset as u64,
|
attr: offset as u64,
|
||||||
addr: &mut val as *mut u32 as u64,
|
addr: val as *const u32 as u64,
|
||||||
flags: 0,
|
flags: 0,
|
||||||
};
|
};
|
||||||
|
if set {
|
||||||
// SAFETY: gic_dist_attr.addr is safe to write to.
|
gic.set_device_attr(&gic_dist_attr)
|
||||||
unsafe { gic.get_device_attr(&mut gic_dist_attr) }.map_err(|e| {
|
.map_err(Error::SetDeviceAttribute)?;
|
||||||
Error::GetDeviceAttribute(HypervisorDeviceError::GetDeviceAttribute(e.into()))
|
} else {
|
||||||
})?;
|
gic.get_device_attr(&mut gic_dist_attr)
|
||||||
|
.map_err(Error::GetDeviceAttribute)?;
|
||||||
Ok(val)
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the distributor control register.
|
/// Get the distributor control register.
|
||||||
pub fn read_ctlr(gic: &DeviceFd) -> Result<u32> {
|
pub fn read_ctlr(gic: &Arc<dyn hypervisor::Device>) -> Result<u32> {
|
||||||
dist_attr_get(gic, GICD_CTLR)
|
let val: u32 = 0;
|
||||||
|
dist_attr_access(gic, GICD_CTLR, &val, false)?;
|
||||||
|
Ok(val)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the distributor control register.
|
/// Set the distributor control register.
|
||||||
pub fn write_ctlr(gic: &DeviceFd, val: u32) -> Result<()> {
|
pub fn write_ctlr(gic: &Arc<dyn hypervisor::Device>, val: u32) -> Result<()> {
|
||||||
dist_attr_set(gic, GICD_CTLR, val)
|
dist_attr_access(gic, GICD_CTLR, &val, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_interrupts_num(gic: &DeviceFd) -> Result<u32> {
|
fn get_interrupts_num(gic: &Arc<dyn hypervisor::Device>) -> Result<u32> {
|
||||||
let mut num_irq = 0;
|
let num_irq = 0;
|
||||||
|
|
||||||
let mut nr_irqs_attr = kvm_device_attr {
|
let mut nr_irqs_attr = kvm_device_attr {
|
||||||
group: KVM_DEV_ARM_VGIC_GRP_NR_IRQS,
|
group: KVM_DEV_ARM_VGIC_GRP_NR_IRQS,
|
||||||
attr: 0,
|
attr: 0,
|
||||||
addr: &mut num_irq as *mut u32 as u64,
|
addr: &num_irq as *const u32 as u64,
|
||||||
flags: 0,
|
flags: 0,
|
||||||
};
|
};
|
||||||
// SAFETY: nr_irqs_attr.addr is safe to write to.
|
gic.get_device_attr(&mut nr_irqs_attr)
|
||||||
unsafe { gic.get_device_attr(&mut nr_irqs_attr) }.map_err(|e| {
|
.map_err(Error::GetDeviceAttribute)?;
|
||||||
Error::GetDeviceAttribute(HypervisorDeviceError::GetDeviceAttribute(e.into()))
|
|
||||||
})?;
|
|
||||||
Ok(num_irq)
|
Ok(num_irq)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compute_reg_len(gic: &DeviceFd, reg: &DistReg, base: u32) -> Result<u32> {
|
fn compute_reg_len(gic: &Arc<dyn hypervisor::Device>, 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;
|
|
||||||
|
|
||||||
let mut end = base;
|
let mut end = base;
|
||||||
let num_irq = get_interrupts_num(gic)?;
|
let num_irq = get_interrupts_num(gic)?;
|
||||||
if reg.length > 0 {
|
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
|
// 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
|
// that the model has. It is also the type of register where
|
||||||
// a register relates to multiple interrupts.
|
// a register relates to multiple interrupts.
|
||||||
end = base + (reg.bpi as u32 * (num_irq - LAYOUT_IRQ_BASE) / 8);
|
end = base + (reg.bpi as u32 * (num_irq - IRQ_BASE) / 8);
|
||||||
if reg.bpi as u32 * (num_irq - LAYOUT_IRQ_BASE) % 8 > 0 {
|
if reg.bpi as u32 * (num_irq - IRQ_BASE) % 8 > 0 {
|
||||||
end += REG_SIZE as u32;
|
end += REG_SIZE as u32;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -164,7 +147,7 @@ fn compute_reg_len(gic: &DeviceFd, reg: &DistReg, base: u32) -> Result<u32> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Set distributor registers of the GIC.
|
/// 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;
|
let mut idx = 0;
|
||||||
|
|
||||||
for dreg in VGIC_DIST_REGS {
|
for dreg in VGIC_DIST_REGS {
|
||||||
@@ -172,7 +155,8 @@ pub fn set_dist_regs(gic: &DeviceFd, state: &[u32]) -> Result<()> {
|
|||||||
let end = compute_reg_len(gic, dreg, base)?;
|
let end = compute_reg_len(gic, dreg, base)?;
|
||||||
|
|
||||||
while base < end {
|
while base < end {
|
||||||
dist_attr_set(gic, base, state[idx])?;
|
let val = state[idx];
|
||||||
|
dist_attr_access(gic, base, &val, true)?;
|
||||||
idx += 1;
|
idx += 1;
|
||||||
base += REG_SIZE as u32;
|
base += REG_SIZE as u32;
|
||||||
}
|
}
|
||||||
@@ -180,7 +164,7 @@ pub fn set_dist_regs(gic: &DeviceFd, state: &[u32]) -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
/// Get distributor registers of the GIC.
|
/// 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();
|
let mut state = Vec::new();
|
||||||
|
|
||||||
for dreg in VGIC_DIST_REGS {
|
for dreg in VGIC_DIST_REGS {
|
||||||
@@ -188,7 +172,9 @@ pub fn get_dist_regs(gic: &DeviceFd) -> Result<Vec<u32>> {
|
|||||||
let end = compute_reg_len(gic, dreg, base)?;
|
let end = compute_reg_len(gic, dreg, base)?;
|
||||||
|
|
||||||
while base < end {
|
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;
|
base += REG_SIZE as u32;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
261
arch/src/aarch64/gic/gicv3.rs
Normal file
261
arch/src/aarch64/gic/gicv3.rs
Normal file
@@ -0,0 +1,261 @@
|
|||||||
|
// Copyright 2021 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
|
||||||
|
//
|
||||||
|
// This file implements the GicV3 device.
|
||||||
|
|
||||||
|
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::{
|
||||||
|
construct_gicr_typers, get_redist_regs, set_redist_regs,
|
||||||
|
};
|
||||||
|
use crate::aarch64::gic::GicDevice;
|
||||||
|
use crate::layout;
|
||||||
|
use anyhow::anyhow;
|
||||||
|
use hypervisor::kvm::kvm_bindings;
|
||||||
|
use hypervisor::CpuState;
|
||||||
|
use std::any::Any;
|
||||||
|
use std::convert::TryInto;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::{boxed::Box, result};
|
||||||
|
use versionize::{VersionMap, Versionize, VersionizeResult};
|
||||||
|
use versionize_derive::Versionize;
|
||||||
|
use vm_migration::{
|
||||||
|
Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable,
|
||||||
|
VersionMapped,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// 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 for the GicV3
|
||||||
|
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(Versionize)]
|
||||||
|
pub struct Gicv3State {
|
||||||
|
dist: Vec<u32>,
|
||||||
|
rdist: Vec<u32>,
|
||||||
|
icc: Vec<u32>,
|
||||||
|
// special register that enables interrupts and affinity routing
|
||||||
|
gicd_ctlr: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VersionMapped for Gicv3State {}
|
||||||
|
|
||||||
|
impl KvmGicV3 {
|
||||||
|
// 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::GIC_V3_DIST_START
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the size of the GIC distributor.
|
||||||
|
pub fn get_dist_size() -> u64 {
|
||||||
|
layout::GIC_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 * layout::GIC_V3_REDIST_SIZE
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Save the state of GIC.
|
||||||
|
fn state(&self, gicr_typers: &[u64]) -> Result<Gicv3State> {
|
||||||
|
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_its_device(&mut self, _its_device: Option<Arc<dyn hypervisor::Device>>) {}
|
||||||
|
|
||||||
|
fn set_gicr_typers(&mut self, vcpu_states: &[CpuState]) {
|
||||||
|
let gicr_typers = construct_gicr_typers(vcpu_states);
|
||||||
|
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: &mut 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();
|
||||||
|
Snapshot::new_from_versioned_state(&self.id(), &self.state(&gicr_typers).unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn restore(&mut self, snapshot: Snapshot) -> std::result::Result<(), MigratableError> {
|
||||||
|
let gicr_typers = self.gicr_typers.clone();
|
||||||
|
self.set_state(&gicr_typers, &snapshot.to_versioned_state(&self.id())?)
|
||||||
|
.map_err(|e| {
|
||||||
|
MigratableError::Restore(anyhow!("Could not restore GICv3 state {:?}", e))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Pausable for KvmGicV3 {
|
||||||
|
fn pause(&mut self) -> std::result::Result<(), MigratableError> {
|
||||||
|
// Flush redistributors pending tables to guest RAM.
|
||||||
|
save_pending_tables(self.device()).map_err(|e| {
|
||||||
|
MigratableError::Pause(anyhow!("Could not save GICv3 GIC pending tables {:?}", e))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Transportable for KvmGicV3 {}
|
||||||
|
impl Migratable for KvmGicV3 {}
|
||||||
|
}
|
||||||
514
arch/src/aarch64/gic/gicv3_its.rs
Normal file
514
arch/src/aarch64/gic/gicv3_its.rs
Normal file
@@ -0,0 +1,514 @@
|
|||||||
|
// Copyright 2021 Arm Limited (or its affiliates). All rights reserved.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
//
|
||||||
|
// This file implements the GicV3 device with ITS (Virtual Interrupt Translation Service).
|
||||||
|
|
||||||
|
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::redist_regs::{
|
||||||
|
construct_gicr_typers, get_redist_regs, set_redist_regs,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::aarch64::gic::gicv3::kvm::KvmGicV3;
|
||||||
|
use crate::aarch64::gic::kvm::{save_pending_tables, KvmGicDevice};
|
||||||
|
use crate::aarch64::gic::GicDevice;
|
||||||
|
use crate::layout;
|
||||||
|
use anyhow::anyhow;
|
||||||
|
use hypervisor::kvm::kvm_bindings;
|
||||||
|
use hypervisor::CpuState;
|
||||||
|
use std::any::Any;
|
||||||
|
use std::convert::TryInto;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::{boxed::Box, result};
|
||||||
|
use versionize::{VersionMap, Versionize, VersionizeResult};
|
||||||
|
use versionize_derive::Versionize;
|
||||||
|
use vm_migration::{
|
||||||
|
Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable,
|
||||||
|
VersionMapped,
|
||||||
|
};
|
||||||
|
|
||||||
|
const GITS_CTLR: u32 = 0x0000;
|
||||||
|
const GITS_IIDR: u32 = 0x0004;
|
||||||
|
const GITS_CBASER: u32 = 0x0080;
|
||||||
|
const GITS_CWRITER: u32 = 0x0088;
|
||||||
|
const GITS_CREADR: u32 = 0x0090;
|
||||||
|
const GITS_BASER: u32 = 0x0100;
|
||||||
|
|
||||||
|
/// Errors thrown while saving/restoring the GICv3ITS.
|
||||||
|
#[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),
|
||||||
|
/// Error in saving GICv3ITS IIDR register.
|
||||||
|
SaveITSIIDR(crate::aarch64::gic::Error),
|
||||||
|
/// Error in restoring GICv3ITS IIDR register.
|
||||||
|
RestoreITSIIDR(crate::aarch64::gic::Error),
|
||||||
|
/// Error in saving GICv3ITS CBASER register.
|
||||||
|
SaveITSCBASER(crate::aarch64::gic::Error),
|
||||||
|
/// Error in restoring GICv3ITS CBASER register.
|
||||||
|
RestoreITSCBASER(crate::aarch64::gic::Error),
|
||||||
|
/// Error in saving GICv3ITS CREADR register.
|
||||||
|
SaveITSCREADR(crate::aarch64::gic::Error),
|
||||||
|
/// Error in restoring GICv3ITS CREADR register.
|
||||||
|
RestoreITSCREADR(crate::aarch64::gic::Error),
|
||||||
|
/// Error in saving GICv3ITS CWRITER register.
|
||||||
|
SaveITSCWRITER(crate::aarch64::gic::Error),
|
||||||
|
/// Error in restoring GICv3ITS CWRITER register.
|
||||||
|
RestoreITSCWRITER(crate::aarch64::gic::Error),
|
||||||
|
/// Error in saving GICv3ITS BASER register.
|
||||||
|
SaveITSBASER(crate::aarch64::gic::Error),
|
||||||
|
/// Error in restoring GICv3ITS BASER register.
|
||||||
|
RestoreITSBASER(crate::aarch64::gic::Error),
|
||||||
|
/// Error in saving GICv3ITS CTLR register.
|
||||||
|
SaveITSCTLR(crate::aarch64::gic::Error),
|
||||||
|
/// Error in restoring GICv3ITS CTLR register.
|
||||||
|
RestoreITSCTLR(crate::aarch64::gic::Error),
|
||||||
|
/// Error in saving GICv3ITS restore tables.
|
||||||
|
SaveITSTables(crate::aarch64::gic::Error),
|
||||||
|
/// Error in restoring GICv3ITS restore tables.
|
||||||
|
RestoreITSTables(crate::aarch64::gic::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
type Result<T> = result::Result<T, Error>;
|
||||||
|
|
||||||
|
/// Access an ITS device attribute.
|
||||||
|
///
|
||||||
|
/// This is a helper function to get/set the ITS device attribute depending
|
||||||
|
/// the bool parameter `set` provided.
|
||||||
|
pub fn gicv3_its_attr_access(
|
||||||
|
its_device: &Arc<dyn hypervisor::Device>,
|
||||||
|
group: u32,
|
||||||
|
attr: u32,
|
||||||
|
val: &u64,
|
||||||
|
set: bool,
|
||||||
|
) -> crate::aarch64::gic::Result<()> {
|
||||||
|
let mut gicv3_its_attr = kvm_bindings::kvm_device_attr {
|
||||||
|
group,
|
||||||
|
attr: attr as u64,
|
||||||
|
addr: val as *const u64 as u64,
|
||||||
|
flags: 0,
|
||||||
|
};
|
||||||
|
if set {
|
||||||
|
its_device
|
||||||
|
.set_device_attr(&gicv3_its_attr)
|
||||||
|
.map_err(crate::aarch64::gic::Error::SetDeviceAttribute)?;
|
||||||
|
} else {
|
||||||
|
its_device
|
||||||
|
.get_device_attr(&mut gicv3_its_attr)
|
||||||
|
.map_err(crate::aarch64::gic::Error::GetDeviceAttribute)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Function that saves/restores ITS tables into guest RAM.
|
||||||
|
///
|
||||||
|
/// The tables get flushed to guest RAM whenever the VM gets stopped.
|
||||||
|
pub fn gicv3_its_tables_access(
|
||||||
|
its_device: &Arc<dyn hypervisor::Device>,
|
||||||
|
save: bool,
|
||||||
|
) -> crate::aarch64::gic::Result<()> {
|
||||||
|
let attr = if save {
|
||||||
|
u64::from(kvm_bindings::KVM_DEV_ARM_ITS_SAVE_TABLES)
|
||||||
|
} else {
|
||||||
|
u64::from(kvm_bindings::KVM_DEV_ARM_ITS_RESTORE_TABLES)
|
||||||
|
};
|
||||||
|
|
||||||
|
let init_gic_attr = kvm_bindings::kvm_device_attr {
|
||||||
|
group: kvm_bindings::KVM_DEV_ARM_VGIC_GRP_CTRL,
|
||||||
|
attr,
|
||||||
|
addr: 0,
|
||||||
|
flags: 0,
|
||||||
|
};
|
||||||
|
its_device
|
||||||
|
.set_device_attr(&init_gic_attr)
|
||||||
|
.map_err(crate::aarch64::gic::Error::SetDeviceAttribute)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct KvmGicV3Its {
|
||||||
|
/// The hypervisor agnostic device for the GicV3
|
||||||
|
device: Arc<dyn hypervisor::Device>,
|
||||||
|
|
||||||
|
/// The hypervisor agnostic device for the Its device
|
||||||
|
its_device: Option<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,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Versionize)]
|
||||||
|
pub struct Gicv3ItsState {
|
||||||
|
dist: Vec<u32>,
|
||||||
|
rdist: Vec<u32>,
|
||||||
|
icc: Vec<u32>,
|
||||||
|
// special register that enables interrupts and affinity routing
|
||||||
|
gicd_ctlr: u32,
|
||||||
|
its_ctlr: u64,
|
||||||
|
its_iidr: u64,
|
||||||
|
its_cbaser: u64,
|
||||||
|
its_cwriter: u64,
|
||||||
|
its_creadr: u64,
|
||||||
|
its_baser: [u64; 8],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VersionMapped for Gicv3ItsState {}
|
||||||
|
|
||||||
|
impl KvmGicV3Its {
|
||||||
|
fn get_msi_size() -> u64 {
|
||||||
|
layout::GIC_V3_ITS_SIZE
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_msi_addr(vcpu_count: u64) -> u64 {
|
||||||
|
KvmGicV3::get_redists_addr(vcpu_count) - KvmGicV3Its::get_msi_size()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Save the state of GICv3ITS.
|
||||||
|
fn state(&self, gicr_typers: &[u64]) -> Result<Gicv3ItsState> {
|
||||||
|
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)?;
|
||||||
|
|
||||||
|
let its_baser_state: [u64; 8] = [0; 8];
|
||||||
|
for i in 0..8 {
|
||||||
|
gicv3_its_attr_access(
|
||||||
|
self.its_device().unwrap(),
|
||||||
|
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
|
||||||
|
GITS_BASER + i * 8,
|
||||||
|
&its_baser_state[i as usize],
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.map_err(Error::SaveITSBASER)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let its_ctlr_state: u64 = 0;
|
||||||
|
gicv3_its_attr_access(
|
||||||
|
self.its_device().unwrap(),
|
||||||
|
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
|
||||||
|
GITS_CTLR,
|
||||||
|
&its_ctlr_state,
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.map_err(Error::SaveITSCTLR)?;
|
||||||
|
|
||||||
|
let its_cbaser_state: u64 = 0;
|
||||||
|
gicv3_its_attr_access(
|
||||||
|
self.its_device().unwrap(),
|
||||||
|
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
|
||||||
|
GITS_CBASER,
|
||||||
|
&its_cbaser_state,
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.map_err(Error::SaveITSCBASER)?;
|
||||||
|
|
||||||
|
let its_creadr_state: u64 = 0;
|
||||||
|
gicv3_its_attr_access(
|
||||||
|
self.its_device().unwrap(),
|
||||||
|
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
|
||||||
|
GITS_CREADR,
|
||||||
|
&its_creadr_state,
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.map_err(Error::SaveITSCREADR)?;
|
||||||
|
|
||||||
|
let its_cwriter_state: u64 = 0;
|
||||||
|
gicv3_its_attr_access(
|
||||||
|
self.its_device().unwrap(),
|
||||||
|
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
|
||||||
|
GITS_CWRITER,
|
||||||
|
&its_cwriter_state,
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.map_err(Error::SaveITSCWRITER)?;
|
||||||
|
|
||||||
|
let its_iidr_state: u64 = 0;
|
||||||
|
gicv3_its_attr_access(
|
||||||
|
self.its_device().unwrap(),
|
||||||
|
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
|
||||||
|
GITS_IIDR,
|
||||||
|
&its_iidr_state,
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.map_err(Error::SaveITSIIDR)?;
|
||||||
|
|
||||||
|
Ok(Gicv3ItsState {
|
||||||
|
dist: dist_state,
|
||||||
|
rdist: rdist_state,
|
||||||
|
icc: icc_state,
|
||||||
|
gicd_ctlr,
|
||||||
|
its_ctlr: its_ctlr_state,
|
||||||
|
its_iidr: its_iidr_state,
|
||||||
|
its_cbaser: its_cbaser_state,
|
||||||
|
its_cwriter: its_cwriter_state,
|
||||||
|
its_creadr: its_creadr_state,
|
||||||
|
its_baser: its_baser_state,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Restore the state of GICv3ITS.
|
||||||
|
fn set_state(&mut self, gicr_typers: &[u64], state: &Gicv3ItsState) -> 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)?;
|
||||||
|
|
||||||
|
//Restore GICv3ITS registers
|
||||||
|
gicv3_its_attr_access(
|
||||||
|
self.its_device().unwrap(),
|
||||||
|
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
|
||||||
|
GITS_IIDR,
|
||||||
|
&state.its_iidr,
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
.map_err(Error::RestoreITSIIDR)?;
|
||||||
|
|
||||||
|
gicv3_its_attr_access(
|
||||||
|
self.its_device().unwrap(),
|
||||||
|
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
|
||||||
|
GITS_CBASER,
|
||||||
|
&state.its_cbaser,
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
.map_err(Error::RestoreITSCBASER)?;
|
||||||
|
|
||||||
|
gicv3_its_attr_access(
|
||||||
|
self.its_device().unwrap(),
|
||||||
|
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
|
||||||
|
GITS_CREADR,
|
||||||
|
&state.its_creadr,
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
.map_err(Error::RestoreITSCREADR)?;
|
||||||
|
|
||||||
|
gicv3_its_attr_access(
|
||||||
|
self.its_device().unwrap(),
|
||||||
|
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
|
||||||
|
GITS_CWRITER,
|
||||||
|
&state.its_cwriter,
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
.map_err(Error::RestoreITSCWRITER)?;
|
||||||
|
|
||||||
|
for i in 0..8 {
|
||||||
|
gicv3_its_attr_access(
|
||||||
|
self.its_device().unwrap(),
|
||||||
|
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
|
||||||
|
GITS_BASER + i * 8,
|
||||||
|
&state.its_baser[i as usize],
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
.map_err(Error::RestoreITSBASER)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore ITS tables
|
||||||
|
gicv3_its_tables_access(self.its_device().unwrap(), false)
|
||||||
|
.map_err(Error::RestoreITSTables)?;
|
||||||
|
|
||||||
|
gicv3_its_attr_access(
|
||||||
|
self.its_device().unwrap(),
|
||||||
|
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
|
||||||
|
GITS_CTLR,
|
||||||
|
&state.its_ctlr,
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
.map_err(Error::RestoreITSCTLR)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GicDevice for KvmGicV3Its {
|
||||||
|
fn device(&self) -> &Arc<dyn hypervisor::Device> {
|
||||||
|
&self.device
|
||||||
|
}
|
||||||
|
|
||||||
|
fn its_device(&self) -> Option<&Arc<dyn hypervisor::Device>> {
|
||||||
|
self.its_device.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
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_its_device(&mut self, its_device: Option<Arc<dyn hypervisor::Device>>) {
|
||||||
|
self.its_device = its_device;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_gicr_typers(&mut self, vcpu_states: &[CpuState]) {
|
||||||
|
let gicr_typers = construct_gicr_typers(vcpu_states);
|
||||||
|
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,
|
||||||
|
its_device: None,
|
||||||
|
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: &mut dyn GicDevice,
|
||||||
|
) -> crate::aarch64::gic::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(crate::aarch64::gic::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,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
gic_device.set_its_device(Some(its_fd));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const GIC_V3_ITS_SNAPSHOT_ID: &str = "gic-v3-its";
|
||||||
|
impl Snapshottable for KvmGicV3Its {
|
||||||
|
fn id(&self) -> String {
|
||||||
|
GIC_V3_ITS_SNAPSHOT_ID.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
|
||||||
|
let gicr_typers = self.gicr_typers.clone();
|
||||||
|
Snapshot::new_from_versioned_state(&self.id(), &self.state(&gicr_typers).unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn restore(&mut self, snapshot: Snapshot) -> std::result::Result<(), MigratableError> {
|
||||||
|
let gicr_typers = self.gicr_typers.clone();
|
||||||
|
self.set_state(&gicr_typers, &snapshot.to_versioned_state(&self.id())?)
|
||||||
|
.map_err(|e| {
|
||||||
|
MigratableError::Restore(anyhow!("Could not restore GICv3ITS state {:?}", e))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Pausable for KvmGicV3Its {
|
||||||
|
fn pause(&mut self) -> std::result::Result<(), MigratableError> {
|
||||||
|
// Flush redistributors pending tables to guest RAM.
|
||||||
|
save_pending_tables(self.device()).map_err(|e| {
|
||||||
|
MigratableError::Pause(anyhow!(
|
||||||
|
"Could not save GICv3ITS GIC pending tables {:?}",
|
||||||
|
e
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
// Flush ITS tables to guest RAM.
|
||||||
|
gicv3_its_tables_access(self.its_device().unwrap(), true).map_err(|e| {
|
||||||
|
MigratableError::Pause(anyhow!("Could not save GICv3ITS ITS tables {:?}", e))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Transportable for KvmGicV3Its {}
|
||||||
|
impl Migratable for KvmGicV3Its {}
|
||||||
|
}
|
||||||
@@ -1,17 +1,14 @@
|
|||||||
// Copyright 2022 Arm Limited (or its affiliates). All rights reserved.
|
|
||||||
// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use kvm_ioctls::DeviceFd;
|
use super::{Error, Result};
|
||||||
|
use hypervisor::kvm::kvm_bindings::{
|
||||||
use crate::arch::aarch64::gic::{Error, Result};
|
|
||||||
use crate::device::HypervisorDeviceError;
|
|
||||||
use crate::kvm::kvm_bindings::{
|
|
||||||
kvm_device_attr, KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS, KVM_REG_ARM64_SYSREG_CRM_MASK,
|
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_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_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_REG_ARM64_SYSREG_OP1_SHIFT, KVM_REG_ARM64_SYSREG_OP2_MASK, KVM_REG_ARM64_SYSREG_OP2_SHIFT,
|
||||||
};
|
};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
const KVM_DEV_ARM_VGIC_V3_MPIDR_SHIFT: u32 = 32;
|
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_DEV_ARM_VGIC_V3_MPIDR_MASK: u64 = 0xffffffff << KVM_DEV_ARM_VGIC_V3_MPIDR_SHIFT as u64;
|
||||||
@@ -80,41 +77,31 @@ static VGIC_ICC_REGS: &[u64] = &[
|
|||||||
SYS_ICC_AP1R3_EL1,
|
SYS_ICC_AP1R3_EL1,
|
||||||
];
|
];
|
||||||
|
|
||||||
fn icc_attr_set(gic: &DeviceFd, offset: u64, typer: u64, val: u32) -> Result<()> {
|
fn icc_attr_access(
|
||||||
let gic_icc_attr = kvm_device_attr {
|
gic: &Arc<dyn hypervisor::Device>,
|
||||||
group: KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS,
|
offset: u64,
|
||||||
attr: ((typer & KVM_DEV_ARM_VGIC_V3_MPIDR_MASK) | offset), // this needs the mpidr
|
typer: u64,
|
||||||
addr: &val as *const u32 as u64,
|
val: &u32,
|
||||||
flags: 0,
|
set: bool,
|
||||||
};
|
) -> Result<()> {
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
let mut gic_icc_attr = kvm_device_attr {
|
let mut gic_icc_attr = kvm_device_attr {
|
||||||
group: KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS,
|
group: KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS,
|
||||||
attr: ((typer & KVM_DEV_ARM_VGIC_V3_MPIDR_MASK) | offset), // this needs the mpidr
|
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,
|
flags: 0,
|
||||||
};
|
};
|
||||||
|
if set {
|
||||||
// SAFETY: gic_icc_attr.addr is safe to write to.
|
gic.set_device_attr(&gic_icc_attr)
|
||||||
unsafe { gic.get_device_attr(&mut gic_icc_attr) }.map_err(|e| {
|
.map_err(Error::SetDeviceAttribute)?;
|
||||||
Error::GetDeviceAttribute(HypervisorDeviceError::GetDeviceAttribute(e.into()))
|
} else {
|
||||||
})?;
|
gic.get_device_attr(&mut gic_icc_attr)
|
||||||
|
.map_err(Error::GetDeviceAttribute)?;
|
||||||
Ok(val)
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get ICC registers.
|
/// 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();
|
let mut state: Vec<u32> = Vec::new();
|
||||||
// We need this for the ICC_AP<m>R<n>_EL1 registers.
|
// We need this for the ICC_AP<m>R<n>_EL1 registers.
|
||||||
let mut num_priority_bits = 0;
|
let mut num_priority_bits = 0;
|
||||||
@@ -122,9 +109,10 @@ pub fn get_icc_regs(gic: &DeviceFd, gicr_typer: &[u64]) -> Result<Vec<u32>> {
|
|||||||
for ix in gicr_typer {
|
for ix in gicr_typer {
|
||||||
let i = *ix;
|
let i = *ix;
|
||||||
for icc_offset in VGIC_ICC_REGS {
|
for icc_offset in VGIC_ICC_REGS {
|
||||||
|
let val = 0;
|
||||||
if *icc_offset == SYS_ICC_CTLR_EL1 {
|
if *icc_offset == SYS_ICC_CTLR_EL1 {
|
||||||
// calculate priority bits by reading the ctrl_el1 register.
|
// 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).
|
// 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_
|
// See page 194 from https://static.docs.arm.com/ihi0069/c/IHI0069C_gic_
|
||||||
// architecture_specification.pdf.
|
// architecture_specification.pdf.
|
||||||
@@ -144,7 +132,8 @@ pub fn get_icc_regs(gic: &DeviceFd, gicr_typer: &[u64]) -> Result<Vec<u32>> {
|
|||||||
// 7 bits of priority.
|
// 7 bits of priority.
|
||||||
else if *icc_offset == SYS_ICC_AP0R1_EL1 || *icc_offset == SYS_ICC_AP1R1_EL1 {
|
else if *icc_offset == SYS_ICC_AP0R1_EL1 || *icc_offset == SYS_ICC_AP1R1_EL1 {
|
||||||
if num_priority_bits >= 6 {
|
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
|
} else if *icc_offset == SYS_ICC_AP0R2_EL1
|
||||||
|| *icc_offset == SYS_ICC_AP0R3_EL1
|
|| *icc_offset == SYS_ICC_AP0R3_EL1
|
||||||
@@ -152,10 +141,12 @@ pub fn get_icc_regs(gic: &DeviceFd, gicr_typer: &[u64]) -> Result<Vec<u32>> {
|
|||||||
|| *icc_offset == SYS_ICC_AP1R3_EL1
|
|| *icc_offset == SYS_ICC_AP1R3_EL1
|
||||||
{
|
{
|
||||||
if num_priority_bits == 7 {
|
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 {
|
} else {
|
||||||
state.push(icc_attr_get(gic, *icc_offset, i)?);
|
icc_attr_access(gic, *icc_offset, i, &val, false)?;
|
||||||
|
state.push(val);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -163,7 +154,11 @@ pub fn get_icc_regs(gic: &DeviceFd, gicr_typer: &[u64]) -> Result<Vec<u32>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Set ICC registers.
|
/// 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 num_priority_bits = 0;
|
||||||
let mut idx = 0;
|
let mut idx = 0;
|
||||||
for ix in gicr_typer {
|
for ix in gicr_typer {
|
||||||
@@ -176,7 +171,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 *icc_offset == SYS_ICC_AP0R1_EL1 || *icc_offset == SYS_ICC_AP1R1_EL1 {
|
||||||
if num_priority_bits >= 6 {
|
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;
|
idx += 1;
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
@@ -187,12 +182,12 @@ pub fn set_icc_regs(gic: &DeviceFd, gicr_typer: &[u64], state: &[u32]) -> Result
|
|||||||
|| *icc_offset == SYS_ICC_AP1R3_EL1
|
|| *icc_offset == SYS_ICC_AP1R3_EL1
|
||||||
{
|
{
|
||||||
if num_priority_bits == 7 {
|
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;
|
idx += 1;
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
icc_attr_set(gic, *icc_offset, i, state[idx])?;
|
icc_attr_access(gic, *icc_offset, i, &state[idx], true)?;
|
||||||
idx += 1;
|
idx += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
220
arch/src/aarch64/gic/mod.rs
Normal file
220
arch/src/aarch64/gic/mod.rs
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
// Copyright 2021 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
|
||||||
|
|
||||||
|
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 hypervisor::CpuState;
|
||||||
|
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 hypervisor agnostic Device of the ITS device
|
||||||
|
fn its_device(&self) -> Option<&Arc<dyn hypervisor::Device>> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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] {
|
||||||
|
&[]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_its_device(&mut self, its_device: Option<Arc<dyn hypervisor::Device>>);
|
||||||
|
|
||||||
|
/// Get the values of GICR_TYPER for each vCPU.
|
||||||
|
fn set_gicr_typers(&mut self, vcpu_states: &[CpuState]);
|
||||||
|
|
||||||
|
/// 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: &mut 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 {
|
||||||
|
flags,
|
||||||
|
group,
|
||||||
|
attr,
|
||||||
|
addr,
|
||||||
|
};
|
||||||
|
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 {
|
||||||
|
flags,
|
||||||
|
group,
|
||||||
|
attr,
|
||||||
|
addr,
|
||||||
|
};
|
||||||
|
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_NUM;
|
||||||
|
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 mut device = Self::create_device(vgic_fd, vcpu_count);
|
||||||
|
|
||||||
|
Self::init_device_attributes(vm, &mut *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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,18 +1,10 @@
|
|||||||
// Copyright 2022 Arm Limited (or its affiliates). All rights reserved.
|
|
||||||
// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use kvm_ioctls::DeviceFd;
|
use super::{Error, Result};
|
||||||
|
use hypervisor::kvm::kvm_bindings::{kvm_device_attr, KVM_DEV_ARM_VGIC_GRP_REDIST_REGS};
|
||||||
use crate::arch::aarch64::gic::{Error, Result};
|
use hypervisor::CpuState;
|
||||||
use crate::device::HypervisorDeviceError;
|
use std::sync::Arc;
|
||||||
use crate::kvm::kvm_bindings::{
|
|
||||||
kvm_device_attr, kvm_one_reg, 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,
|
|
||||||
};
|
|
||||||
use crate::kvm::VcpuKvmState;
|
|
||||||
use crate::CpuState;
|
|
||||||
|
|
||||||
// Relevant redistributor registers that we want to save/restore.
|
// Relevant redistributor registers that we want to save/restore.
|
||||||
const GICR_CTLR: u32 = 0x0000;
|
const GICR_CTLR: u32 = 0x0000;
|
||||||
@@ -39,14 +31,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_SHIFT: u32 = 32;
|
||||||
const KVM_DEV_ARM_VGIC_V3_MPIDR_MASK: u64 = 0xffffffff << KVM_DEV_ARM_VGIC_V3_MPIDR_SHIFT as u64;
|
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.
|
/// 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.
|
/// distributor.
|
||||||
/// Each register has a different number
|
/// Each register has a different number
|
||||||
/// of bits_per_irq and is therefore variable length.
|
/// of bits_per_irq and is therefore variable length.
|
||||||
@@ -96,41 +82,31 @@ static VGIC_SGI_REGS: &[RdistReg] = &[
|
|||||||
VGIC_RDIST_REG!(GICR_IPRIORITYR0, 32),
|
VGIC_RDIST_REG!(GICR_IPRIORITYR0, 32),
|
||||||
];
|
];
|
||||||
|
|
||||||
fn redist_attr_set(gic: &DeviceFd, offset: u32, typer: u64, val: u32) -> Result<()> {
|
fn redist_attr_access(
|
||||||
let gic_redist_attr = kvm_device_attr {
|
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,
|
group: KVM_DEV_ARM_VGIC_GRP_REDIST_REGS,
|
||||||
attr: (typer & KVM_DEV_ARM_VGIC_V3_MPIDR_MASK) | (offset as u64), // this needs the mpidr
|
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,
|
flags: 0,
|
||||||
};
|
};
|
||||||
|
if set {
|
||||||
gic.set_device_attr(&gic_redist_attr).map_err(|e| {
|
gic.set_device_attr(&gic_dist_attr)
|
||||||
Error::SetDeviceAttribute(HypervisorDeviceError::SetDeviceAttribute(e.into()))
|
.map_err(Error::SetDeviceAttribute)?;
|
||||||
})?;
|
} else {
|
||||||
|
gic.get_device_attr(&mut gic_dist_attr)
|
||||||
|
.map_err(Error::GetDeviceAttribute)?;
|
||||||
|
}
|
||||||
Ok(())
|
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(
|
fn access_redists_aux(
|
||||||
gic: &DeviceFd,
|
gic: &Arc<dyn hypervisor::Device>,
|
||||||
gicr_typer: &[u64],
|
gicr_typer: &[u64],
|
||||||
state: &mut Vec<u32>,
|
state: &mut Vec<u32>,
|
||||||
reg_list: &[RdistReg],
|
reg_list: &[RdistReg],
|
||||||
@@ -143,11 +119,14 @@ fn access_redists_aux(
|
|||||||
let end = base + rdreg.length as u32;
|
let end = base + rdreg.length as u32;
|
||||||
|
|
||||||
while base < end {
|
while base < end {
|
||||||
|
let mut val = 0;
|
||||||
if set {
|
if set {
|
||||||
redist_attr_set(gic, base, *i, state[*idx])?;
|
val = state[*idx];
|
||||||
|
redist_attr_access(gic, base, *i, &val, true)?;
|
||||||
*idx += 1;
|
*idx += 1;
|
||||||
} else {
|
} 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;
|
base += REG_SIZE as u32;
|
||||||
}
|
}
|
||||||
@@ -157,7 +136,7 @@ fn access_redists_aux(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Get redistributor registers.
|
/// 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 state = Vec::new();
|
||||||
let mut idx: usize = 0;
|
let mut idx: usize = 0;
|
||||||
access_redists_aux(
|
access_redists_aux(
|
||||||
@@ -174,7 +153,11 @@ pub fn get_redist_regs(gic: &DeviceFd, gicr_typer: &[u64]) -> Result<Vec<u32>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Set redistributor registers.
|
/// 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 idx: usize = 0;
|
||||||
let mut mut_state = state.to_owned();
|
let mut mut_state = state.to_owned();
|
||||||
access_redists_aux(
|
access_redists_aux(
|
||||||
@@ -210,18 +193,17 @@ pub fn construct_gicr_typers(vcpu_states: &[CpuState]) -> Vec<u64> {
|
|||||||
*/
|
*/
|
||||||
let mut gicr_typers: Vec<u64> = Vec::new();
|
let mut gicr_typers: Vec<u64> = Vec::new();
|
||||||
for (index, state) in vcpu_states.iter().enumerate() {
|
for (index, state) in vcpu_states.iter().enumerate() {
|
||||||
let state: VcpuKvmState = state.clone().into();
|
let last = {
|
||||||
let last = (index == vcpu_states.len() - 1) as u64;
|
if index == vcpu_states.len() - 1 {
|
||||||
// state.sys_regs is a big collection of system registers, including MIPDR_EL1
|
1
|
||||||
let mpidr: Vec<kvm_one_reg> = state
|
} else {
|
||||||
.sys_regs
|
0
|
||||||
.into_iter()
|
}
|
||||||
.filter(|reg| reg.id == KVM_ARM64_SYSREG_MPIDR_EL1)
|
};
|
||||||
.collect();
|
|
||||||
//calculate affinity
|
//calculate affinity
|
||||||
let mut cpu_affid = mpidr[0].addr & 1095233437695;
|
let mut cpu_affid = state.mpidr & 1095233437695;
|
||||||
cpu_affid = ((cpu_affid & 0xFF00000000) >> 8) | (cpu_affid & 0xFFFFFF);
|
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.push((cpu_affid << 32) | (1 << 24) | (index as u64) << 8 | (last << 4));
|
||||||
}
|
}
|
||||||
|
|
||||||
gicr_typers
|
gicr_typers
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
//
|
//
|
||||||
// Memory layout of AArch64 guest:
|
// Memory layout of Aarch64 guest:
|
||||||
//
|
//
|
||||||
// Physical +---------------------------------------------------------------+
|
// Physical +---------------------------------------------------------------+
|
||||||
// address | |
|
// address | |
|
||||||
@@ -20,13 +20,6 @@
|
|||||||
// | DRAM |
|
// | DRAM |
|
||||||
// | |
|
// | |
|
||||||
// | |
|
// | |
|
||||||
// 4GB +---------------------------------------------------------------+
|
|
||||||
// | 32-bit devices hole |
|
|
||||||
// 4GB-64M +---------------------------------------------------------------+
|
|
||||||
// | |
|
|
||||||
// | |
|
|
||||||
// | DRAM |
|
|
||||||
// | |
|
|
||||||
// | |
|
// | |
|
||||||
// 1GB +---------------------------------------------------------------+
|
// 1GB +---------------------------------------------------------------+
|
||||||
// | |
|
// | |
|
||||||
@@ -42,11 +35,11 @@
|
|||||||
// | Legacy devices space |
|
// | Legacy devices space |
|
||||||
// | |
|
// | |
|
||||||
// 144 M +---------------------------------------------------------------|
|
// 144 M +---------------------------------------------------------------|
|
||||||
// | |
|
|
||||||
// | Reserved (now GIC is here) |
|
// | Reserved (now GIC is here) |
|
||||||
|
// 64 M +---------------------------------------------------------------+
|
||||||
|
// | |
|
||||||
|
// | UEFI space |
|
||||||
// | |
|
// | |
|
||||||
// 4 M +---------------------------------------------------------------+
|
|
||||||
// | UEFI flash |
|
|
||||||
// 0GB +---------------------------------------------------------------+
|
// 0GB +---------------------------------------------------------------+
|
||||||
//
|
//
|
||||||
//
|
//
|
||||||
@@ -55,16 +48,17 @@ use vm_memory::GuestAddress;
|
|||||||
|
|
||||||
/// 0x0 ~ 0x40_0000 (4 MiB) is reserved to UEFI
|
/// 0x0 ~ 0x40_0000 (4 MiB) is reserved to UEFI
|
||||||
/// UEFI binary size is required less than 3 MiB, reserving 4 MiB is enough.
|
/// UEFI binary size is required less than 3 MiB, reserving 4 MiB is enough.
|
||||||
pub const UEFI_START: GuestAddress = GuestAddress(0);
|
pub const UEFI_START: u64 = 0x0;
|
||||||
|
pub const MEM_UEFI_START: GuestAddress = GuestAddress(0);
|
||||||
pub const UEFI_SIZE: u64 = 0x040_0000;
|
pub const UEFI_SIZE: u64 = 0x040_0000;
|
||||||
|
|
||||||
/// Below this address will reside the GIC, above this address will reside the MMIO devices.
|
/// 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.
|
/// See kernel file arch/arm64/include/uapi/asm/kvm.h for the GIC related definitions.
|
||||||
/// 0x08ff_0000 ~ 0x0900_0000 is reserved for GICv3 Distributor
|
/// 0x08ff_0000 ~ 0x0900_0000 is reserved for GICv3 Distributor
|
||||||
pub const GIC_V3_DIST_SIZE: u64 = 0x01_0000;
|
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);
|
pub const GIC_V3_DIST_START: u64 = MAPPED_IO_START - GIC_V3_DIST_SIZE;
|
||||||
/// Below 0x08ff_0000 is reserved for GICv3 Redistributor.
|
/// Below 0x08ff_0000 is reserved for GICv3 Redistributor.
|
||||||
/// The size defined here is for each vcpu.
|
/// The size defined here is for each vcpu.
|
||||||
/// The total size is 'number_of_vcpu * GIC_V3_REDIST_SIZE'
|
/// The total size is 'number_of_vcpu * GIC_V3_REDIST_SIZE'
|
||||||
@@ -73,9 +67,9 @@ pub const GIC_V3_REDIST_SIZE: u64 = 0x02_0000;
|
|||||||
pub const GIC_V3_ITS_SIZE: u64 = 0x02_0000;
|
pub const GIC_V3_ITS_SIZE: u64 = 0x02_0000;
|
||||||
|
|
||||||
/// Space 0x0900_0000 ~ 0x0905_0000 is reserved for legacy devices.
|
/// Space 0x0900_0000 ~ 0x0905_0000 is reserved for legacy devices.
|
||||||
pub const LEGACY_SERIAL_MAPPED_IO_START: GuestAddress = MAPPED_IO_START;
|
pub const LEGACY_SERIAL_MAPPED_IO_START: u64 = 0x0900_0000;
|
||||||
pub const LEGACY_RTC_MAPPED_IO_START: GuestAddress = GuestAddress(0x0901_0000);
|
pub const LEGACY_RTC_MAPPED_IO_START: u64 = 0x0901_0000;
|
||||||
pub const LEGACY_GPIO_MAPPED_IO_START: GuestAddress = GuestAddress(0x0902_0000);
|
pub const LEGACY_GPIO_MAPPED_IO_START: u64 = 0x0902_0000;
|
||||||
|
|
||||||
/// Space 0x0905_0000 ~ 0x0906_0000 is reserved for pcie io address
|
/// 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_START: GuestAddress = GuestAddress(0x0905_0000);
|
||||||
@@ -91,41 +85,28 @@ pub const PCI_MMCONFIG_SIZE: u64 = 256 << 20;
|
|||||||
// One bus with potentially 256 devices (32 slots x 8 functions).
|
// One bus with potentially 256 devices (32 slots x 8 functions).
|
||||||
pub const PCI_MMIO_CONFIG_SIZE_PER_SEGMENT: u64 = 4096 * 256;
|
pub const PCI_MMIO_CONFIG_SIZE_PER_SEGMENT: u64 = 4096 * 256;
|
||||||
|
|
||||||
/// Start of RAM.
|
/// Start of RAM on 64 bit ARM.
|
||||||
pub const RAM_START: GuestAddress = GuestAddress(0x4000_0000);
|
pub const RAM_64BIT_START: u64 = 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);
|
|
||||||
|
|
||||||
/// Kernel command line maximum size.
|
/// Kernel command line maximum size.
|
||||||
/// As per `arch/arm64/include/uapi/asm/setup.h`.
|
/// As per `arch/arm64/include/uapi/asm/setup.h`.
|
||||||
pub const CMDLINE_MAX_SIZE: usize = 2048;
|
pub const CMDLINE_MAX_SIZE: usize = 2048;
|
||||||
|
|
||||||
/// FDT is at the beginning of RAM.
|
/// FDT is at the beginning of RAM.
|
||||||
pub const FDT_START: GuestAddress = RAM_START;
|
/// Maximum size of the device tree blob as specified in https://www.kernel.org/doc/Documentation/arm64/booting.txt.
|
||||||
/// Maximum size of the device tree blob as specified in [the kernel
|
pub const FDT_START: u64 = RAM_64BIT_START;
|
||||||
/// documentation](https://www.kernel.org/doc/Documentation/arm64/booting.txt).
|
pub const FDT_MAX_SIZE: usize = 0x20_0000;
|
||||||
pub const FDT_MAX_SIZE: u64 = 0x20_0000;
|
|
||||||
|
|
||||||
/// Put ACPI table above dtb
|
/// Put ACPI table above dtb
|
||||||
pub const ACPI_START: GuestAddress = GuestAddress(RAM_START.0 + FDT_MAX_SIZE);
|
pub const ACPI_START: u64 = RAM_64BIT_START + FDT_MAX_SIZE as u64;
|
||||||
pub const ACPI_MAX_SIZE: u64 = 0x20_0000;
|
pub const ACPI_MAX_SIZE: usize = 0x20_0000;
|
||||||
pub const RSDP_POINTER: GuestAddress = ACPI_START;
|
pub const RSDP_POINTER: GuestAddress = GuestAddress(ACPI_START);
|
||||||
|
|
||||||
/// Kernel start after FDT and ACPI
|
/// Kernel start after FDT and ACPI
|
||||||
pub const KERNEL_START: GuestAddress = GuestAddress(ACPI_START.0 + ACPI_MAX_SIZE);
|
pub const KERNEL_START: u64 = ACPI_START + ACPI_MAX_SIZE as u64;
|
||||||
|
|
||||||
/// Pci high memory base
|
/// Pci high memory base
|
||||||
pub const PCI_HIGH_BASE: GuestAddress = GuestAddress(0x2_0000_0000);
|
pub const PCI_HIGH_BASE: u64 = 0x2_0000_0000_u64;
|
||||||
|
|
||||||
// As per virt/kvm/arm/vgic/vgic-kvm-device.c we need
|
// As per virt/kvm/arm/vgic/vgic-kvm-device.c we need
|
||||||
// the number of interrupts our GIC will support to be:
|
// the number of interrupts our GIC will support to be:
|
||||||
|
|||||||
@@ -4,62 +4,53 @@
|
|||||||
|
|
||||||
/// Module for the flattened device tree.
|
/// Module for the flattened device tree.
|
||||||
pub mod fdt;
|
pub mod fdt;
|
||||||
|
/// Module for the global interrupt controller configuration.
|
||||||
|
pub mod gic;
|
||||||
/// Layout for this aarch64 system.
|
/// Layout for this aarch64 system.
|
||||||
pub mod layout;
|
pub mod layout;
|
||||||
/// Module for system registers definition
|
/// Logic for configuring aarch64 registers.
|
||||||
pub mod regs;
|
pub mod regs;
|
||||||
/// Module for loading UEFI binary.
|
/// Module for loading UEFI binary.
|
||||||
pub mod uefi;
|
pub mod uefi;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::fmt::Debug;
|
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
|
|
||||||
use hypervisor::arch::aarch64::gic::Vgic;
|
|
||||||
use log::{log_enabled, Level};
|
|
||||||
use thiserror::Error;
|
|
||||||
use vm_memory::{Address, GuestAddress, GuestMemory, GuestMemoryAtomic};
|
|
||||||
|
|
||||||
pub use self::fdt::DeviceInfoForFdt;
|
pub use self::fdt::DeviceInfoForFdt;
|
||||||
use crate::{DeviceType, GuestMemoryMmap, NumaNodes, PciSpaceInfo, RegionType};
|
use crate::{DeviceType, GuestMemoryMmap, NumaNodes, PciSpaceInfo, RegionType};
|
||||||
|
use gic::GicDevice;
|
||||||
pub const _NSIG: i32 = 65;
|
use log::{log_enabled, Level};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::convert::TryInto;
|
||||||
|
use std::fmt::Debug;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use vm_memory::{Address, GuestAddress, GuestMemory, GuestUsize};
|
||||||
|
|
||||||
/// Errors thrown while configuring aarch64 system.
|
/// Errors thrown while configuring aarch64 system.
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
/// Failed to create a FDT.
|
/// Failed to create a FDT.
|
||||||
#[error("Failed to create a FDT")]
|
|
||||||
SetupFdt,
|
SetupFdt,
|
||||||
|
|
||||||
/// Failed to write FDT to memory.
|
/// Failed to write FDT to memory.
|
||||||
#[error("Failed to write FDT to memory: {0}")]
|
|
||||||
WriteFdtToMemory(fdt::Error),
|
WriteFdtToMemory(fdt::Error),
|
||||||
|
|
||||||
/// Failed to create a GIC.
|
/// Failed to create a GIC.
|
||||||
#[error("Failed to create a GIC")]
|
SetupGic(gic::Error),
|
||||||
SetupGic,
|
|
||||||
|
|
||||||
/// Failed to compute the initramfs address.
|
/// Failed to compute the initramfs address.
|
||||||
#[error("Failed to compute the initramfs address")]
|
|
||||||
InitramfsAddress,
|
InitramfsAddress,
|
||||||
|
|
||||||
/// Error configuring the general purpose registers
|
/// Error configuring the general purpose registers
|
||||||
#[error("Error configuring the general purpose registers: {0}")]
|
RegsConfiguration(regs::Error),
|
||||||
RegsConfiguration(hypervisor::HypervisorCpuError),
|
|
||||||
|
|
||||||
/// Error configuring the MPIDR register
|
/// Error configuring the MPIDR register
|
||||||
#[error("Error configuring the MPIDR register: {0}")]
|
|
||||||
VcpuRegMpidr(hypervisor::HypervisorCpuError),
|
VcpuRegMpidr(hypervisor::HypervisorCpuError),
|
||||||
|
|
||||||
/// Error initializing PMU for vcpu
|
/// Error initializing PMU for vcpu
|
||||||
#[error("Error initializing PMU for vcpu")]
|
|
||||||
VcpuInitPmu,
|
VcpuInitPmu,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Error> for super::Error {
|
impl From<Error> for super::Error {
|
||||||
fn from(e: Error) -> super::Error {
|
fn from(e: Error) -> super::Error {
|
||||||
super::Error::PlatformSpecific(e)
|
super::Error::AArch64Setup(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,31 +64,45 @@ pub struct EntryPoint {
|
|||||||
|
|
||||||
/// Configure the specified VCPU, and return its MPIDR.
|
/// Configure the specified VCPU, and return its MPIDR.
|
||||||
pub fn configure_vcpu(
|
pub fn configure_vcpu(
|
||||||
vcpu: &Arc<dyn hypervisor::Vcpu>,
|
fd: &Arc<dyn hypervisor::Vcpu>,
|
||||||
id: u8,
|
id: u8,
|
||||||
boot_setup: Option<(EntryPoint, &GuestMemoryAtomic<GuestMemoryMmap>)>,
|
kernel_entry_point: Option<EntryPoint>,
|
||||||
) -> super::Result<u64> {
|
) -> super::Result<u64> {
|
||||||
if let Some((kernel_entry_point, _guest_memory)) = boot_setup {
|
if let Some(kernel_entry_point) = kernel_entry_point {
|
||||||
vcpu.setup_regs(
|
regs::setup_regs(fd, id, kernel_entry_point.entry_addr.raw_value())
|
||||||
id,
|
.map_err(Error::RegsConfiguration)?;
|
||||||
kernel_entry_point.entry_addr.raw_value(),
|
|
||||||
super::layout::FDT_START.raw_value(),
|
|
||||||
)
|
|
||||||
.map_err(Error::RegsConfiguration)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let mpidr = vcpu
|
let mpidr = fd.read_mpidr().map_err(Error::VcpuRegMpidr)?;
|
||||||
.get_sys_reg(regs::MPIDR_EL1)
|
|
||||||
.map_err(Error::VcpuRegMpidr)?;
|
|
||||||
Ok(mpidr)
|
Ok(mpidr)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn arch_memory_regions() -> Vec<(GuestAddress, usize, RegionType)> {
|
pub fn arch_memory_regions(size: GuestUsize) -> Vec<(GuestAddress, usize, RegionType)> {
|
||||||
|
// Normally UEFI should be loaded to a flash area at the beginning of memory.
|
||||||
|
// But now flash memory type is not supported.
|
||||||
|
// As a workaround, we take 4 MiB memory from the main RAM for UEFI.
|
||||||
|
// As a result, the RAM that the guest can see is less than what has been
|
||||||
|
// assigned in command line, when ACPI and UEFI is enabled.
|
||||||
|
let ram_deduction = if cfg!(feature = "acpi") {
|
||||||
|
layout::UEFI_SIZE
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
vec![
|
vec![
|
||||||
// 0 MiB ~ 256 MiB: UEFI, GIC and legacy devices
|
// 0 ~ 4 MiB: Reserved for UEFI space
|
||||||
|
#[cfg(feature = "acpi")]
|
||||||
|
(GuestAddress(0), layout::UEFI_SIZE as usize, RegionType::Ram),
|
||||||
|
#[cfg(not(feature = "acpi"))]
|
||||||
(
|
(
|
||||||
GuestAddress(0),
|
GuestAddress(0),
|
||||||
layout::MEM_32BIT_DEVICES_START.0 as usize,
|
layout::UEFI_SIZE as usize,
|
||||||
|
RegionType::Reserved,
|
||||||
|
),
|
||||||
|
// 4 MiB ~ 256 MiB: Gic and legacy devices
|
||||||
|
(
|
||||||
|
GuestAddress(layout::UEFI_SIZE),
|
||||||
|
(layout::MEM_32BIT_DEVICES_START.0 - layout::UEFI_SIZE) as usize,
|
||||||
RegionType::Reserved,
|
RegionType::Reserved,
|
||||||
),
|
),
|
||||||
// 256 MiB ~ 768 MiB: MMIO space
|
// 256 MiB ~ 768 MiB: MMIO space
|
||||||
@@ -112,20 +117,12 @@ pub fn arch_memory_regions() -> Vec<(GuestAddress, usize, RegionType)> {
|
|||||||
layout::PCI_MMCONFIG_SIZE as usize,
|
layout::PCI_MMCONFIG_SIZE as usize,
|
||||||
RegionType::Reserved,
|
RegionType::Reserved,
|
||||||
),
|
),
|
||||||
// 1GiB ~ 4032 MiB: RAM before the gap
|
// 1 GiB ~ : Ram
|
||||||
(
|
(
|
||||||
layout::RAM_START,
|
GuestAddress(layout::RAM_64BIT_START),
|
||||||
layout::MEM_32BIT_RESERVED_START.unchecked_offset_from(layout::RAM_START) as usize,
|
(size - ram_deduction) as usize,
|
||||||
RegionType::Ram,
|
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,
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,7 +137,7 @@ pub fn configure_system<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::Bui
|
|||||||
initrd: &Option<super::InitramfsConfig>,
|
initrd: &Option<super::InitramfsConfig>,
|
||||||
pci_space_info: &[PciSpaceInfo],
|
pci_space_info: &[PciSpaceInfo],
|
||||||
virtio_iommu_bdf: Option<u32>,
|
virtio_iommu_bdf: Option<u32>,
|
||||||
gic_device: &Arc<Mutex<dyn Vgic>>,
|
gic_device: &dyn GicDevice,
|
||||||
numa_nodes: &NumaNodes,
|
numa_nodes: &NumaNodes,
|
||||||
pmu_supported: bool,
|
pmu_supported: bool,
|
||||||
) -> super::Result<()> {
|
) -> super::Result<()> {
|
||||||
@@ -182,15 +179,33 @@ pub fn initramfs_load_addr(
|
|||||||
if guest_mem.address_in_range(offset) {
|
if guest_mem.address_in_range(offset) {
|
||||||
Ok(offset.raw_value())
|
Ok(offset.raw_value())
|
||||||
} else {
|
} 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 {
|
/// Returns the memory address where the kernel could be loaded.
|
||||||
let host_cpu_phys_bits = hypervisor.get_host_ipa_limit().try_into().unwrap();
|
pub fn get_kernel_start() -> u64 {
|
||||||
|
layout::KERNEL_START
|
||||||
|
}
|
||||||
|
|
||||||
|
///Return guest memory address where the uefi should be loaded.
|
||||||
|
pub fn get_uefi_start() -> u64 {
|
||||||
|
layout::UEFI_START
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auxiliary function to get the address where the device tree blob is loaded.
|
||||||
|
fn get_fdt_addr() -> u64 {
|
||||||
|
layout::FDT_START
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_host_cpu_phys_bits() -> u8 {
|
||||||
|
// A dummy hypervisor created only for querying the host IPA size and will
|
||||||
|
// be freed after the query.
|
||||||
|
let hv = hypervisor::new().unwrap();
|
||||||
|
let host_cpu_phys_bits = hv.get_host_ipa_limit().try_into().unwrap();
|
||||||
if host_cpu_phys_bits == 0 {
|
if host_cpu_phys_bits == 0 {
|
||||||
// Host kernel does not support `get_host_ipa_limit`,
|
// Host kernel does not support `get_host_ipa_limit`,
|
||||||
// we return the default value 40 here.
|
// we return the default value 40 here.
|
||||||
@@ -206,11 +221,10 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_arch_memory_regions_dram() {
|
fn test_arch_memory_regions_dram() {
|
||||||
let regions = arch_memory_regions();
|
let regions = arch_memory_regions((1usize << 32) as u64); //4GB
|
||||||
assert_eq!(6, regions.len());
|
assert_eq!(5, regions.len());
|
||||||
assert_eq!(layout::RAM_START, regions[3].0);
|
assert_eq!(GuestAddress(layout::RAM_64BIT_START), regions[4].0);
|
||||||
assert_eq!(RegionType::Ram, regions[3].2);
|
assert_eq!(1usize << 32, regions[4].1);
|
||||||
assert_eq!(RegionType::Reserved, regions[5].2);
|
|
||||||
assert_eq!(RegionType::Ram, regions[4].2);
|
assert_eq!(RegionType::Ram, regions[4].2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,43 +1,75 @@
|
|||||||
// Copyright 2022 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
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
// AArch64 system register encoding:
|
|
||||||
// See https://developer.arm.com/documentation/ddi0487 (chapter D12)
|
|
||||||
//
|
//
|
||||||
// 31 22 21 20 19 18 16 15 12 11 8 7 5 4 0
|
// 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
|
||||||
// |1101010100| L | op0 | op1 | CRn | CRm | op2 | Rt |
|
// found in the THIRD-PARTY file.
|
||||||
// +----------+---+-----+-----+-----+-----+-----+----+
|
|
||||||
//
|
|
||||||
// Notes:
|
|
||||||
// - L and Rt are reserved as implementation defined fields, ignored.
|
|
||||||
|
|
||||||
const SYSREG_HEAD: u32 = 0b1101010100u32 << 22;
|
use super::get_fdt_addr;
|
||||||
const SYSREG_OP0_SHIFT: u32 = 19;
|
use hypervisor::kvm::kvm_bindings::{
|
||||||
const SYSREG_OP0_MASK: u32 = 0b11u32 << 19;
|
kvm_regs, user_pt_regs, KVM_REG_ARM64, KVM_REG_ARM_CORE, KVM_REG_SIZE_U64,
|
||||||
const SYSREG_OP1_SHIFT: u32 = 16;
|
};
|
||||||
const SYSREG_OP1_MASK: u32 = 0b111u32 << 16;
|
use hypervisor::{arm64_core_reg_id, offset__of};
|
||||||
const SYSREG_CRN_SHIFT: u32 = 12;
|
use std::sync::Arc;
|
||||||
const SYSREG_CRN_MASK: u32 = 0b1111u32 << 12;
|
use std::{mem, result};
|
||||||
const SYSREG_CRM_SHIFT: u32 = 8;
|
|
||||||
const SYSREG_CRM_MASK: u32 = 0b1111u32 << 8;
|
|
||||||
const SYSREG_OP2_SHIFT: u32 = 5;
|
|
||||||
const SYSREG_OP2_MASK: u32 = 0b111u32 << 5;
|
|
||||||
|
|
||||||
/// Define the ID of system registers
|
/// Errors thrown while setting aarch64 registers.
|
||||||
#[macro_export]
|
#[derive(Debug)]
|
||||||
macro_rules! arm64_sys_reg {
|
pub enum Error {
|
||||||
($name: tt, $op0: tt, $op1: tt, $crn: tt, $crm: tt, $op2: tt) => {
|
/// Failed to set core register (PC, PSTATE or general purpose ones).
|
||||||
pub const $name: u32 = SYSREG_HEAD
|
SetCoreRegister(hypervisor::HypervisorCpuError),
|
||||||
| ((($op0 as u32) << SYSREG_OP0_SHIFT) & SYSREG_OP0_MASK as u32)
|
/// Failed to get a system register.
|
||||||
| ((($op1 as u32) << SYSREG_OP1_SHIFT) & SYSREG_OP1_MASK as u32)
|
GetSysRegister(hypervisor::HypervisorCpuError),
|
||||||
| ((($crn as u32) << SYSREG_CRN_SHIFT) & SYSREG_CRN_MASK as u32)
|
|
||||||
| ((($crm as u32) << SYSREG_CRM_SHIFT) & SYSREG_CRM_MASK as u32)
|
|
||||||
| ((($op2 as u32) << SYSREG_OP2_SHIFT) & SYSREG_OP2_MASK as u32);
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
type Result<T> = result::Result<T, Error>;
|
||||||
|
|
||||||
arm64_sys_reg!(MPIDR_EL1, 3, 0, 0, 0, 5);
|
#[allow(non_upper_case_globals)]
|
||||||
arm64_sys_reg!(ID_AA64MMFR0_EL1, 3, 0, 0, 7, 0);
|
// PSR (Processor State Register) bits.
|
||||||
arm64_sys_reg!(TTBR1_EL1, 3, 0, 2, 0, 1);
|
// Taken from arch/arm64/include/uapi/asm/ptrace.h.
|
||||||
arm64_sys_reg!(TCR_EL1, 3, 0, 2, 0, 2);
|
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) -> 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() as u64,
|
||||||
|
)
|
||||||
|
.map_err(Error::SetCoreRegister)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,28 +1,19 @@
|
|||||||
// Copyright 2020 Arm Limited (or its affiliates). All rights reserved.
|
// Copyright 2020 Arm Limited (or its affiliates). All rights reserved.
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
use std::io::{Read, Seek, SeekFrom};
|
use std::io::{Read, Seek, SeekFrom};
|
||||||
use std::os::fd::AsFd;
|
|
||||||
use std::result;
|
use std::result;
|
||||||
|
use vm_memory::{Bytes, GuestAddress, GuestMemory};
|
||||||
use thiserror::Error;
|
|
||||||
use vm_memory::{GuestAddress, GuestMemory};
|
|
||||||
|
|
||||||
/// Errors thrown while loading UEFI binary
|
/// Errors thrown while loading UEFI binary
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
/// Unable to seek to UEFI image start.
|
/// Unable to seek to UEFI image start.
|
||||||
#[error("Unable to seek to UEFI image start")]
|
|
||||||
SeekUefiStart,
|
SeekUefiStart,
|
||||||
/// Unable to seek to UEFI image end.
|
/// Unable to seek to UEFI image end.
|
||||||
#[error("Unable to seek to UEFI image end")]
|
|
||||||
SeekUefiEnd,
|
SeekUefiEnd,
|
||||||
/// UEFI image too big.
|
/// UEFI image too big.
|
||||||
#[error("UEFI image too big")]
|
|
||||||
UefiTooBig,
|
UefiTooBig,
|
||||||
/// Unable to read UEFI image
|
/// Unable to read UEFI image
|
||||||
#[error("Unable to read UEFI image")]
|
|
||||||
ReadUefiImage,
|
ReadUefiImage,
|
||||||
}
|
}
|
||||||
type Result<T> = result::Result<T, Error>;
|
type Result<T> = result::Result<T, Error>;
|
||||||
@@ -33,7 +24,7 @@ pub fn load_uefi<F, M: GuestMemory>(
|
|||||||
uefi_image: &mut F,
|
uefi_image: &mut F,
|
||||||
) -> Result<()>
|
) -> Result<()>
|
||||||
where
|
where
|
||||||
F: Read + Seek + AsFd,
|
F: Read + Seek,
|
||||||
{
|
{
|
||||||
let uefi_size = uefi_image
|
let uefi_size = uefi_image
|
||||||
.seek(SeekFrom::End(0))
|
.seek(SeekFrom::End(0))
|
||||||
@@ -43,8 +34,10 @@ where
|
|||||||
if uefi_size > 0x300000 {
|
if uefi_size > 0x300000 {
|
||||||
return Err(Error::UefiTooBig);
|
return Err(Error::UefiTooBig);
|
||||||
}
|
}
|
||||||
uefi_image.rewind().map_err(|_| Error::SeekUefiStart)?;
|
uefi_image
|
||||||
|
.seek(SeekFrom::Start(0))
|
||||||
|
.map_err(|_| Error::SeekUefiStart)?;
|
||||||
guest_mem
|
guest_mem
|
||||||
.read_exact_volatile_from(guest_addr, &mut uefi_image.as_fd(), uefi_size)
|
.read_exact_from(guest_addr, uefi_image, uefi_size)
|
||||||
.map_err(|_| Error::ReadUefiImage)
|
.map_err(|_| Error::ReadUefiImage)
|
||||||
}
|
}
|
||||||
|
|||||||
106
arch/src/lib.rs
106
arch/src/lib.rs
@@ -1,4 +1,3 @@
|
|||||||
// Copyright © 2024 Institute of Software, CAS. All rights reserved.
|
|
||||||
// Copyright 2020 Arm Limited (or its affiliates). All rights reserved.
|
// Copyright 2020 Arm Limited (or its affiliates). All rights reserved.
|
||||||
// Copyright © 2020, Oracle and/or its affiliates.
|
// Copyright © 2020, Oracle and/or its affiliates.
|
||||||
//
|
//
|
||||||
@@ -6,61 +5,60 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
//! Implements platform specific functionality.
|
//! Implements platform specific functionality.
|
||||||
//! Supported platforms: x86_64, aarch64, riscv64.
|
//! Supported platforms: x86_64, aarch64.
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate log;
|
extern crate log;
|
||||||
|
#[macro_use]
|
||||||
use std::collections::BTreeMap;
|
extern crate serde_derive;
|
||||||
use std::sync::Arc;
|
|
||||||
use std::{fmt, result};
|
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
#[cfg(target_arch = "x86_64")]
|
#[cfg(target_arch = "x86_64")]
|
||||||
use crate::x86_64::SgxEpcSection;
|
use crate::x86_64::SgxEpcSection;
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
use std::fmt;
|
||||||
|
use std::result;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use versionize::{VersionMap, Versionize, VersionizeError, VersionizeResult};
|
||||||
|
use versionize_derive::Versionize;
|
||||||
|
use vm_migration::VersionMapped;
|
||||||
|
|
||||||
type GuestMemoryMmap = vm_memory::GuestMemoryMmap<vm_memory::bitmap::AtomicBitmap>;
|
type GuestMemoryMmap = vm_memory::GuestMemoryMmap<vm_memory::bitmap::AtomicBitmap>;
|
||||||
type GuestRegionMmap = vm_memory::GuestRegionMmap<vm_memory::bitmap::AtomicBitmap>;
|
type GuestRegionMmap = vm_memory::GuestRegionMmap<vm_memory::bitmap::AtomicBitmap>;
|
||||||
|
|
||||||
/// Type for returning error code.
|
/// Type for returning error code.
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[cfg(target_arch = "x86_64")]
|
#[cfg(target_arch = "x86_64")]
|
||||||
#[error("Platform specific error (x86_64): {0:?}")]
|
/// X86_64 specific error triggered during system configuration.
|
||||||
PlatformSpecific(x86_64::Error),
|
X86_64Setup(x86_64::Error),
|
||||||
#[cfg(target_arch = "aarch64")]
|
#[cfg(target_arch = "aarch64")]
|
||||||
#[error("Platform specific error (aarch64): {0:?}")]
|
/// AArch64 specific error triggered during system configuration.
|
||||||
PlatformSpecific(aarch64::Error),
|
AArch64Setup(aarch64::Error),
|
||||||
#[cfg(target_arch = "riscv64")]
|
/// The zero page extends past the end of guest_mem.
|
||||||
#[error("Platform specific error (riscv64): {0:?}")]
|
|
||||||
PlatformSpecific(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: {0}")]
|
|
||||||
ModlistSetup(#[source] vm_memory::GuestMemoryError),
|
|
||||||
#[error("RSDP extends past the end of guest memory")]
|
|
||||||
RsdpPastRamEnd,
|
|
||||||
#[error("Failed to setup Zero Page for bzImage")]
|
|
||||||
ZeroPageSetup(#[source] vm_memory::GuestMemoryError),
|
|
||||||
#[error("Zero Page for bzImage past RAM end")]
|
|
||||||
ZeroPagePastRamEnd,
|
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.
|
/// Type for returning public functions outcome.
|
||||||
pub type Result<T> = result::Result<T, Error>;
|
pub type Result<T> = result::Result<T, Error>;
|
||||||
|
|
||||||
/// Type for memory region types.
|
/// Type for memory region types.
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Copy, PartialEq, Debug, Serialize, Deserialize, Versionize)]
|
||||||
pub enum RegionType {
|
pub enum RegionType {
|
||||||
/// RAM type
|
/// RAM type
|
||||||
Ram,
|
Ram,
|
||||||
@@ -78,6 +76,8 @@ pub enum RegionType {
|
|||||||
Reserved,
|
Reserved,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl VersionMapped for RegionType {}
|
||||||
|
|
||||||
/// Module for aarch64 related functionality.
|
/// Module for aarch64 related functionality.
|
||||||
#[cfg(target_arch = "aarch64")]
|
#[cfg(target_arch = "aarch64")]
|
||||||
pub mod aarch64;
|
pub mod aarch64;
|
||||||
@@ -85,19 +85,8 @@ pub mod aarch64;
|
|||||||
#[cfg(target_arch = "aarch64")]
|
#[cfg(target_arch = "aarch64")]
|
||||||
pub use aarch64::{
|
pub use aarch64::{
|
||||||
arch_memory_regions, configure_system, configure_vcpu, fdt::DeviceInfoForFdt,
|
arch_memory_regions, configure_system, configure_vcpu, fdt::DeviceInfoForFdt,
|
||||||
get_host_cpu_phys_bits, initramfs_load_addr, layout, layout::CMDLINE_MAX_SIZE,
|
get_host_cpu_phys_bits, get_kernel_start, get_uefi_start, initramfs_load_addr, layout,
|
||||||
layout::IRQ_BASE, uefi, EntryPoint, _NSIG,
|
layout::CMDLINE_MAX_SIZE, layout::IRQ_BASE, uefi, EntryPoint,
|
||||||
};
|
|
||||||
|
|
||||||
/// Module for riscv64 related functionality.
|
|
||||||
#[cfg(target_arch = "riscv64")]
|
|
||||||
pub mod riscv64;
|
|
||||||
|
|
||||||
#[cfg(target_arch = "riscv64")]
|
|
||||||
pub use riscv64::{
|
|
||||||
arch_memory_regions, configure_system, configure_vcpu, fdt::DeviceInfoForFdt,
|
|
||||||
get_host_cpu_phys_bits, initramfs_load_addr, layout, layout::CMDLINE_MAX_SIZE,
|
|
||||||
layout::IRQ_BASE, EntryPoint, _NSIG,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(target_arch = "x86_64")]
|
#[cfg(target_arch = "x86_64")]
|
||||||
@@ -106,16 +95,15 @@ pub mod x86_64;
|
|||||||
#[cfg(target_arch = "x86_64")]
|
#[cfg(target_arch = "x86_64")]
|
||||||
pub use x86_64::{
|
pub use x86_64::{
|
||||||
arch_memory_regions, configure_system, configure_vcpu, generate_common_cpuid,
|
arch_memory_regions, configure_system, configure_vcpu, generate_common_cpuid,
|
||||||
generate_ram_ranges, get_host_cpu_phys_bits, initramfs_load_addr, layout,
|
get_host_cpu_phys_bits, initramfs_load_addr, layout, layout::CMDLINE_MAX_SIZE,
|
||||||
layout::CMDLINE_MAX_SIZE, layout::CMDLINE_START, regs, CpuidConfig, CpuidFeatureEntry,
|
layout::CMDLINE_START, regs, CpuidFeatureEntry, EntryPoint,
|
||||||
EntryPoint, _NSIG,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Safe wrapper for `sysconf(_SC_PAGESIZE)`.
|
/// Safe wrapper for `sysconf(_SC_PAGESIZE)`.
|
||||||
#[cfg(target_arch = "x86_64")]
|
#[cfg(target_arch = "x86_64")]
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn pagesize() -> usize {
|
fn pagesize() -> usize {
|
||||||
// SAFETY: Trivially safe
|
// Trivially safe
|
||||||
unsafe { libc::sysconf(libc::_SC_PAGESIZE) as usize }
|
unsafe { libc::sysconf(libc::_SC_PAGESIZE) as usize }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,7 +112,6 @@ pub struct NumaNode {
|
|||||||
pub memory_regions: Vec<Arc<GuestRegionMmap>>,
|
pub memory_regions: Vec<Arc<GuestRegionMmap>>,
|
||||||
pub hotplug_regions: Vec<Arc<GuestRegionMmap>>,
|
pub hotplug_regions: Vec<Arc<GuestRegionMmap>>,
|
||||||
pub cpus: Vec<u8>,
|
pub cpus: Vec<u8>,
|
||||||
pub pci_segments: Vec<u16>,
|
|
||||||
pub distances: BTreeMap<u32, u8>,
|
pub distances: BTreeMap<u32, u8>,
|
||||||
pub memory_zones: Vec<String>,
|
pub memory_zones: Vec<String>,
|
||||||
#[cfg(target_arch = "x86_64")]
|
#[cfg(target_arch = "x86_64")]
|
||||||
@@ -147,7 +134,7 @@ pub enum DeviceType {
|
|||||||
/// Device Type: Virtio.
|
/// Device Type: Virtio.
|
||||||
Virtio(u32),
|
Virtio(u32),
|
||||||
/// Device Type: Serial.
|
/// Device Type: Serial.
|
||||||
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
|
#[cfg(target_arch = "aarch64")]
|
||||||
Serial,
|
Serial,
|
||||||
/// Device Type: RTC.
|
/// Device Type: RTC.
|
||||||
#[cfg(target_arch = "aarch64")]
|
#[cfg(target_arch = "aarch64")]
|
||||||
@@ -162,22 +149,21 @@ pub const PAGE_SIZE: usize = 4096;
|
|||||||
|
|
||||||
impl fmt::Display for DeviceType {
|
impl fmt::Display for DeviceType {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(f, "{self:?}")
|
write!(f, "{:?}", self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Structure to describe MMIO device information
|
/// Structure to describe MMIO device information
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
|
#[cfg(target_arch = "aarch64")]
|
||||||
pub struct MmioDeviceInfo {
|
pub struct MmioDeviceInfo {
|
||||||
pub addr: u64,
|
pub addr: u64,
|
||||||
pub len: u64,
|
|
||||||
pub irq: u32,
|
pub irq: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Structure to describe PCI space information
|
/// Structure to describe PCI space information
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
|
#[cfg(target_arch = "aarch64")]
|
||||||
pub struct PciSpaceInfo {
|
pub struct PciSpaceInfo {
|
||||||
pub pci_segment_id: u16,
|
pub pci_segment_id: u16,
|
||||||
pub mmio_config_address: u64,
|
pub mmio_config_address: u64,
|
||||||
@@ -185,7 +171,7 @@ pub struct PciSpaceInfo {
|
|||||||
pub pci_device_space_size: u64,
|
pub pci_device_space_size: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
|
#[cfg(target_arch = "aarch64")]
|
||||||
impl DeviceInfoForFdt for MmioDeviceInfo {
|
impl DeviceInfoForFdt for MmioDeviceInfo {
|
||||||
fn addr(&self) -> u64 {
|
fn addr(&self) -> u64 {
|
||||||
self.addr
|
self.addr
|
||||||
@@ -194,6 +180,6 @@ impl DeviceInfoForFdt for MmioDeviceInfo {
|
|||||||
self.irq
|
self.irq
|
||||||
}
|
}
|
||||||
fn length(&self) -> u64 {
|
fn length(&self) -> u64 {
|
||||||
self.len
|
4096
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,484 +0,0 @@
|
|||||||
// Copyright © 2024 Institute of Software, CAS. All rights reserved.
|
|
||||||
// Copyright 2020 Arm Limited (or its affiliates). All rights reserved.
|
|
||||||
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
//
|
|
||||||
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
|
||||||
// found in the THIRD-PARTY file.
|
|
||||||
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::ffi::CStr;
|
|
||||||
use std::fmt::Debug;
|
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
use std::{cmp, result, str};
|
|
||||||
|
|
||||||
use byteorder::{BigEndian, ByteOrder};
|
|
||||||
use hypervisor::arch::riscv64::aia::Vaia;
|
|
||||||
use thiserror::Error;
|
|
||||||
use vm_fdt::{FdtWriter, FdtWriterResult};
|
|
||||||
use vm_memory::{Address, Bytes, GuestMemory, GuestMemoryError, GuestMemoryRegion};
|
|
||||||
|
|
||||||
use super::super::{DeviceType, GuestMemoryMmap, InitramfsConfig};
|
|
||||||
use super::layout::{
|
|
||||||
IRQ_BASE, MEM_32BIT_DEVICES_SIZE, MEM_32BIT_DEVICES_START, MEM_PCI_IO_SIZE, MEM_PCI_IO_START,
|
|
||||||
PCI_HIGH_BASE, PCI_MMIO_CONFIG_SIZE_PER_SEGMENT,
|
|
||||||
};
|
|
||||||
use crate::PciSpaceInfo;
|
|
||||||
|
|
||||||
const AIA_APLIC_PHANDLE: u32 = 1;
|
|
||||||
const AIA_IMSIC_PHANDLE: u32 = 2;
|
|
||||||
const CPU_INTC_BASE_PHANDLE: u32 = 3;
|
|
||||||
const CPU_BASE_PHANDLE: u32 = 256 + CPU_INTC_BASE_PHANDLE;
|
|
||||||
// Read the documentation specified when appending the root node to the FDT.
|
|
||||||
const ADDRESS_CELLS: u32 = 0x2;
|
|
||||||
const SIZE_CELLS: u32 = 0x2;
|
|
||||||
|
|
||||||
// From https://elixir.bootlin.com/linux/v6.10/source/include/dt-bindings/interrupt-controller/irq.h#L14
|
|
||||||
const _IRQ_TYPE_EDGE_RISING: u32 = 1;
|
|
||||||
const IRQ_TYPE_LEVEL_HI: u32 = 4;
|
|
||||||
|
|
||||||
const S_MODE_EXT_IRQ: u32 = 9;
|
|
||||||
|
|
||||||
/// Trait for devices to be added to the Flattened Device Tree.
|
|
||||||
pub trait DeviceInfoForFdt {
|
|
||||||
/// Returns the address where this device will be loaded.
|
|
||||||
fn addr(&self) -> u64;
|
|
||||||
/// Returns the associated interrupt for this device.
|
|
||||||
fn irq(&self) -> u32;
|
|
||||||
/// Returns the amount of memory that needs to be reserved for this device.
|
|
||||||
fn length(&self) -> u64;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Errors thrown while configuring the Flattened Device Tree for riscv64.
|
|
||||||
#[derive(Debug, Error)]
|
|
||||||
pub enum Error {
|
|
||||||
/// Failure in writing FDT in memory.
|
|
||||||
#[error("Failure in writing FDT in memory: {0}")]
|
|
||||||
WriteFdtToMemory(GuestMemoryError),
|
|
||||||
}
|
|
||||||
type Result<T> = result::Result<T, Error>;
|
|
||||||
|
|
||||||
/// Creates the flattened device tree for this riscv64 VM.
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
|
||||||
pub fn create_fdt<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHasher>(
|
|
||||||
guest_mem: &GuestMemoryMmap,
|
|
||||||
cmdline: &str,
|
|
||||||
num_vcpu: u32,
|
|
||||||
device_info: &HashMap<(DeviceType, String), T, S>,
|
|
||||||
aia_device: &Arc<Mutex<dyn Vaia>>,
|
|
||||||
initrd: &Option<InitramfsConfig>,
|
|
||||||
pci_space_info: &[PciSpaceInfo],
|
|
||||||
) -> FdtWriterResult<Vec<u8>> {
|
|
||||||
// Allocate stuff necessary for the holding the blob.
|
|
||||||
let mut fdt = FdtWriter::new()?;
|
|
||||||
|
|
||||||
// For an explanation why these nodes were introduced in the blob take a look at
|
|
||||||
// https://github.com/devicetree-org/devicetree-specification/releases/tag/v0.4
|
|
||||||
// In chapter 3.
|
|
||||||
|
|
||||||
// Header or the root node as per above mentioned documentation.
|
|
||||||
let root_node = fdt.begin_node("")?;
|
|
||||||
fdt.property_string("compatible", "linux,dummy-virt")?;
|
|
||||||
// For info on #address-cells and size-cells resort to Table 3.1 Root Node
|
|
||||||
// Properties
|
|
||||||
fdt.property_u32("#address-cells", ADDRESS_CELLS)?;
|
|
||||||
fdt.property_u32("#size-cells", SIZE_CELLS)?;
|
|
||||||
create_cpu_nodes(&mut fdt, num_vcpu)?;
|
|
||||||
create_memory_node(&mut fdt, guest_mem)?;
|
|
||||||
create_chosen_node(&mut fdt, cmdline, initrd)?;
|
|
||||||
create_aia_node(&mut fdt, aia_device)?;
|
|
||||||
create_devices_node(&mut fdt, device_info)?;
|
|
||||||
create_pci_nodes(&mut fdt, pci_space_info)?;
|
|
||||||
|
|
||||||
// End Header node.
|
|
||||||
fdt.end_node(root_node)?;
|
|
||||||
|
|
||||||
let fdt_final = fdt.finish()?;
|
|
||||||
|
|
||||||
Ok(fdt_final)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn write_fdt_to_memory(fdt_final: Vec<u8>, guest_mem: &GuestMemoryMmap) -> Result<()> {
|
|
||||||
// Write FDT to memory.
|
|
||||||
guest_mem
|
|
||||||
.write_slice(fdt_final.as_slice(), super::layout::FDT_START)
|
|
||||||
.map_err(Error::WriteFdtToMemory)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Following are the auxiliary function for creating the different nodes that we append to our FDT.
|
|
||||||
fn create_cpu_nodes(fdt: &mut FdtWriter, num_cpus: u32) -> FdtWriterResult<()> {
|
|
||||||
// See https://elixir.bootlin.com/linux/v6.10/source/Documentation/devicetree/bindings/riscv/cpus.yaml
|
|
||||||
let cpus = fdt.begin_node("cpus")?;
|
|
||||||
// As per documentation, on RISC-V 64-bit systems value should be set to 1.
|
|
||||||
fdt.property_u32("#address-cells", 0x01)?;
|
|
||||||
fdt.property_u32("#size-cells", 0x0)?;
|
|
||||||
// TODO: Retrieve CPU frequency from cpu timer regs
|
|
||||||
let timebase_frequency: u32 = 0x989680;
|
|
||||||
fdt.property_u32("timebase-frequency", timebase_frequency)?;
|
|
||||||
|
|
||||||
for cpu_index in 0..num_cpus {
|
|
||||||
let cpu = fdt.begin_node(&format!("cpu@{:x}", cpu_index))?;
|
|
||||||
fdt.property_string("device_type", "cpu")?;
|
|
||||||
fdt.property_string("compatible", "riscv")?;
|
|
||||||
fdt.property_string("mmu-type", "sv48")?;
|
|
||||||
fdt.property_string("riscv,isa", "rv64imafdc_smaia_ssaia")?;
|
|
||||||
fdt.property_string("status", "okay")?;
|
|
||||||
fdt.property_u32("reg", cpu_index)?;
|
|
||||||
fdt.property_u32("phandle", CPU_BASE_PHANDLE + cpu_index)?;
|
|
||||||
|
|
||||||
// interrupt controller node
|
|
||||||
let intc_node = fdt.begin_node("interrupt-controller")?;
|
|
||||||
fdt.property_string("compatible", "riscv,cpu-intc")?;
|
|
||||||
fdt.property_u32("#interrupt-cells", 1u32)?;
|
|
||||||
fdt.property_null("interrupt-controller")?;
|
|
||||||
fdt.property_u32("phandle", CPU_INTC_BASE_PHANDLE + cpu_index)?;
|
|
||||||
fdt.end_node(intc_node)?;
|
|
||||||
|
|
||||||
fdt.end_node(cpu)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
fdt.end_node(cpus)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_memory_node(fdt: &mut FdtWriter, guest_mem: &GuestMemoryMmap) -> FdtWriterResult<()> {
|
|
||||||
// Note: memory regions from "GuestMemory" are sorted and non-zero sized.
|
|
||||||
let ram_regions = {
|
|
||||||
let mut ram_regions = Vec::new();
|
|
||||||
let mut current_start = guest_mem
|
|
||||||
.iter()
|
|
||||||
.next()
|
|
||||||
.map(GuestMemoryRegion::start_addr)
|
|
||||||
.expect("GuestMemory must have one memory region at least")
|
|
||||||
.raw_value();
|
|
||||||
let mut current_end = current_start;
|
|
||||||
|
|
||||||
for (start, size) in guest_mem
|
|
||||||
.iter()
|
|
||||||
.map(|m| (m.start_addr().raw_value(), m.len()))
|
|
||||||
{
|
|
||||||
if current_end == start {
|
|
||||||
// This zone is continuous with the previous one.
|
|
||||||
current_end += size;
|
|
||||||
} else {
|
|
||||||
ram_regions.push((current_start, current_end));
|
|
||||||
|
|
||||||
current_start = start;
|
|
||||||
current_end = start + size;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ram_regions.push((current_start, current_end));
|
|
||||||
|
|
||||||
ram_regions
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut mem_reg_property = Vec::new();
|
|
||||||
for region in ram_regions {
|
|
||||||
let mem_size = region.1 - region.0;
|
|
||||||
mem_reg_property.push(region.0);
|
|
||||||
mem_reg_property.push(mem_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
let ram_start = super::layout::RAM_START.raw_value();
|
|
||||||
let memory_node_name = format!("memory@{:x}", ram_start);
|
|
||||||
let memory_node = fdt.begin_node(&memory_node_name)?;
|
|
||||||
fdt.property_string("device_type", "memory")?;
|
|
||||||
fdt.property_array_u64("reg", &mem_reg_property)?;
|
|
||||||
fdt.end_node(memory_node)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_chosen_node(
|
|
||||||
fdt: &mut FdtWriter,
|
|
||||||
cmdline: &str,
|
|
||||||
initrd: &Option<InitramfsConfig>,
|
|
||||||
) -> FdtWriterResult<()> {
|
|
||||||
let chosen_node = fdt.begin_node("chosen")?;
|
|
||||||
fdt.property_string("bootargs", cmdline)?;
|
|
||||||
|
|
||||||
if let Some(initrd_config) = initrd {
|
|
||||||
let initrd_start = initrd_config.address.raw_value();
|
|
||||||
let initrd_end = initrd_config.address.raw_value() + initrd_config.size as u64;
|
|
||||||
fdt.property_u64("linux,initrd-start", initrd_start)?;
|
|
||||||
fdt.property_u64("linux,initrd-end", initrd_end)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
fdt.end_node(chosen_node)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_aia_node(fdt: &mut FdtWriter, aia_device: &Arc<Mutex<dyn Vaia>>) -> FdtWriterResult<()> {
|
|
||||||
// IMSIC
|
|
||||||
if aia_device.lock().unwrap().msi_compatible() {
|
|
||||||
use super::layout::IMSIC_START;
|
|
||||||
let imsic_name = format!("imsics@{:x}", IMSIC_START.0);
|
|
||||||
let imsic_node = fdt.begin_node(&imsic_name)?;
|
|
||||||
|
|
||||||
fdt.property_string(
|
|
||||||
"compatible",
|
|
||||||
aia_device.lock().unwrap().imsic_compatibility(),
|
|
||||||
)?;
|
|
||||||
let imsic_reg_prop = aia_device.lock().unwrap().imsic_properties();
|
|
||||||
fdt.property_array_u32("reg", &imsic_reg_prop)?;
|
|
||||||
fdt.property_u32("#interrupt-cells", 0u32)?;
|
|
||||||
fdt.property_null("interrupt-controller")?;
|
|
||||||
fdt.property_null("msi-controller")?;
|
|
||||||
// TODO complete num-ids
|
|
||||||
fdt.property_u32("riscv,num-ids", 2047u32)?;
|
|
||||||
fdt.property_u32("phandle", AIA_IMSIC_PHANDLE)?;
|
|
||||||
|
|
||||||
let mut irq_cells = Vec::new();
|
|
||||||
let num_cpus = aia_device.lock().unwrap().vcpu_count();
|
|
||||||
for i in 0..num_cpus {
|
|
||||||
irq_cells.push(CPU_INTC_BASE_PHANDLE + i);
|
|
||||||
irq_cells.push(S_MODE_EXT_IRQ);
|
|
||||||
}
|
|
||||||
fdt.property_array_u32("interrupts-extended", &irq_cells)?;
|
|
||||||
|
|
||||||
fdt.end_node(imsic_node)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// APLIC
|
|
||||||
use super::layout::APLIC_START;
|
|
||||||
let aplic_name = format!("aplic@{:x}", APLIC_START.0);
|
|
||||||
let aplic_node = fdt.begin_node(&aplic_name)?;
|
|
||||||
|
|
||||||
fdt.property_string(
|
|
||||||
"compatible",
|
|
||||||
aia_device.lock().unwrap().aplic_compatibility(),
|
|
||||||
)?;
|
|
||||||
let reg_cells = aia_device.lock().unwrap().aplic_properties();
|
|
||||||
fdt.property_array_u32("reg", ®_cells)?;
|
|
||||||
fdt.property_u32("#interrupt-cells", 2u32)?;
|
|
||||||
fdt.property_null("interrupt-controller")?;
|
|
||||||
// TODO complete num-srcs
|
|
||||||
fdt.property_u32("riscv,num-sources", 96u32)?;
|
|
||||||
fdt.property_u32("phandle", AIA_APLIC_PHANDLE)?;
|
|
||||||
fdt.property_u32("msi-parent", AIA_IMSIC_PHANDLE)?;
|
|
||||||
|
|
||||||
fdt.end_node(aplic_node)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_serial_node<T: DeviceInfoForFdt + Clone + Debug>(
|
|
||||||
fdt: &mut FdtWriter,
|
|
||||||
dev_info: &T,
|
|
||||||
) -> FdtWriterResult<()> {
|
|
||||||
let serial_reg_prop = [dev_info.addr(), dev_info.length()];
|
|
||||||
let irq = [dev_info.irq() - IRQ_BASE, IRQ_TYPE_LEVEL_HI];
|
|
||||||
|
|
||||||
let serial_node = fdt.begin_node(&format!("serial@{:x}", dev_info.addr()))?;
|
|
||||||
fdt.property_string("compatible", "ns16550a")?;
|
|
||||||
fdt.property_array_u64("reg", &serial_reg_prop)?;
|
|
||||||
fdt.property_u32("clock-frequency", 3686400)?;
|
|
||||||
fdt.property_u32("interrupt-parent", AIA_APLIC_PHANDLE)?;
|
|
||||||
fdt.property_array_u32("interrupts", &irq)?;
|
|
||||||
fdt.end_node(serial_node)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_devices_node<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHasher>(
|
|
||||||
fdt: &mut FdtWriter,
|
|
||||||
dev_info: &HashMap<(DeviceType, String), T, S>,
|
|
||||||
) -> FdtWriterResult<()> {
|
|
||||||
for ((device_type, _device_id), info) in dev_info {
|
|
||||||
match device_type {
|
|
||||||
DeviceType::Serial => create_serial_node(fdt, info)?,
|
|
||||||
DeviceType::Virtio(_) => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_pci_nodes(fdt: &mut FdtWriter, pci_device_info: &[PciSpaceInfo]) -> FdtWriterResult<()> {
|
|
||||||
// Add node for PCIe controller.
|
|
||||||
// See Documentation/devicetree/bindings/pci/host-generic-pci.txt in the kernel
|
|
||||||
// and https://elinux.org/Device_Tree_Usage.
|
|
||||||
// In multiple PCI segments setup, each PCI segment needs a PCI node.
|
|
||||||
for pci_device_info_elem in pci_device_info.iter() {
|
|
||||||
// EDK2 requires the PCIe high space above 4G address.
|
|
||||||
// The actual space in CLH follows the RAM. If the RAM space is small, the PCIe high space
|
|
||||||
// could fall below 4G.
|
|
||||||
// Here we cut off PCI device space below 8G in FDT to workaround the EDK2 check.
|
|
||||||
// But the address written in ACPI is not impacted.
|
|
||||||
let (pci_device_base_64bit, pci_device_size_64bit) =
|
|
||||||
if pci_device_info_elem.pci_device_space_start < PCI_HIGH_BASE.raw_value() {
|
|
||||||
(
|
|
||||||
PCI_HIGH_BASE.raw_value(),
|
|
||||||
pci_device_info_elem.pci_device_space_size
|
|
||||||
- (PCI_HIGH_BASE.raw_value() - pci_device_info_elem.pci_device_space_start),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
(
|
|
||||||
pci_device_info_elem.pci_device_space_start,
|
|
||||||
pci_device_info_elem.pci_device_space_size,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
// There is no specific requirement of the 32bit MMIO range, and
|
|
||||||
// therefore at least we can make these ranges 4K aligned.
|
|
||||||
let pci_device_size_32bit: u64 =
|
|
||||||
MEM_32BIT_DEVICES_SIZE / ((1 << 12) * pci_device_info.len() as u64) * (1 << 12);
|
|
||||||
let pci_device_base_32bit: u64 = MEM_32BIT_DEVICES_START.0
|
|
||||||
+ pci_device_size_32bit * pci_device_info_elem.pci_segment_id as u64;
|
|
||||||
|
|
||||||
let ranges = [
|
|
||||||
// io addresses. Since AArch64 will not use IO address,
|
|
||||||
// we can set the same IO address range for every segment.
|
|
||||||
0x1000000,
|
|
||||||
0_u32,
|
|
||||||
0_u32,
|
|
||||||
(MEM_PCI_IO_START.0 >> 32) as u32,
|
|
||||||
MEM_PCI_IO_START.0 as u32,
|
|
||||||
(MEM_PCI_IO_SIZE >> 32) as u32,
|
|
||||||
MEM_PCI_IO_SIZE as u32,
|
|
||||||
// mmio addresses
|
|
||||||
0x2000000, // (ss = 10: 32-bit memory space)
|
|
||||||
(pci_device_base_32bit >> 32) as u32, // PCI address
|
|
||||||
pci_device_base_32bit as u32,
|
|
||||||
(pci_device_base_32bit >> 32) as u32, // CPU address
|
|
||||||
pci_device_base_32bit as u32,
|
|
||||||
(pci_device_size_32bit >> 32) as u32, // size
|
|
||||||
pci_device_size_32bit as u32,
|
|
||||||
// device addresses
|
|
||||||
0x3000000, // (ss = 11: 64-bit memory space)
|
|
||||||
(pci_device_base_64bit >> 32) as u32, // PCI address
|
|
||||||
pci_device_base_64bit as u32,
|
|
||||||
(pci_device_base_64bit >> 32) as u32, // CPU address
|
|
||||||
pci_device_base_64bit as u32,
|
|
||||||
(pci_device_size_64bit >> 32) as u32, // size
|
|
||||||
pci_device_size_64bit as u32,
|
|
||||||
];
|
|
||||||
let bus_range = [0, 0]; // Only bus 0
|
|
||||||
let reg = [
|
|
||||||
pci_device_info_elem.mmio_config_address,
|
|
||||||
PCI_MMIO_CONFIG_SIZE_PER_SEGMENT,
|
|
||||||
];
|
|
||||||
// See kernel document Documentation/devicetree/bindings/pci/pci-msi.txt
|
|
||||||
let msi_map = [
|
|
||||||
// rid-base: A single cell describing the first RID matched by the entry.
|
|
||||||
0x0,
|
|
||||||
// msi-controller: A single phandle to an MSI controller.
|
|
||||||
AIA_IMSIC_PHANDLE,
|
|
||||||
// msi-base: An msi-specifier describing the msi-specifier produced for the
|
|
||||||
// first RID matched by the entry.
|
|
||||||
(pci_device_info_elem.pci_segment_id as u32) << 8,
|
|
||||||
// length: A single cell describing how many consecutive RIDs are matched
|
|
||||||
// following the rid-base.
|
|
||||||
0x100,
|
|
||||||
];
|
|
||||||
|
|
||||||
let pci_node_name = format!("pci@{:x}", pci_device_info_elem.mmio_config_address);
|
|
||||||
let pci_node = fdt.begin_node(&pci_node_name)?;
|
|
||||||
|
|
||||||
fdt.property_string("compatible", "pci-host-ecam-generic")?;
|
|
||||||
fdt.property_string("device_type", "pci")?;
|
|
||||||
fdt.property_array_u32("ranges", &ranges)?;
|
|
||||||
fdt.property_array_u32("bus-range", &bus_range)?;
|
|
||||||
fdt.property_u32(
|
|
||||||
"linux,pci-domain",
|
|
||||||
pci_device_info_elem.pci_segment_id as u32,
|
|
||||||
)?;
|
|
||||||
fdt.property_u32("#address-cells", 3)?;
|
|
||||||
fdt.property_u32("#size-cells", 2)?;
|
|
||||||
fdt.property_array_u64("reg", ®)?;
|
|
||||||
fdt.property_u32("#interrupt-cells", 1)?;
|
|
||||||
fdt.property_null("interrupt-map")?;
|
|
||||||
fdt.property_null("interrupt-map-mask")?;
|
|
||||||
fdt.property_null("dma-coherent")?;
|
|
||||||
fdt.property_array_u32("msi-map", &msi_map)?;
|
|
||||||
fdt.property_u32("msi-parent", AIA_IMSIC_PHANDLE)?;
|
|
||||||
|
|
||||||
fdt.end_node(pci_node)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse the DTB binary and print for debugging
|
|
||||||
pub fn print_fdt(dtb: &[u8]) {
|
|
||||||
match fdt_parser::Fdt::new(dtb) {
|
|
||||||
Ok(fdt) => {
|
|
||||||
if let Some(root) = fdt.find_node("/") {
|
|
||||||
debug!("Printing the FDT:");
|
|
||||||
print_node(root, 0);
|
|
||||||
} else {
|
|
||||||
debug!("Failed to find root node in FDT for debugging.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(_) => debug!("Failed to parse FDT for debugging."),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn print_node(node: fdt_parser::node::FdtNode<'_, '_>, n_spaces: usize) {
|
|
||||||
debug!("{:indent$}{}/", "", node.name, indent = n_spaces);
|
|
||||||
for property in node.properties() {
|
|
||||||
let name = property.name;
|
|
||||||
|
|
||||||
// If the property is 'compatible', its value requires special handling.
|
|
||||||
// The u8 array could contain multiple null-terminated strings.
|
|
||||||
// We copy the original array and simply replace all 'null' characters with spaces.
|
|
||||||
let value = if name == "compatible" {
|
|
||||||
let mut compatible = vec![0u8; 256];
|
|
||||||
let handled_value = property
|
|
||||||
.value
|
|
||||||
.iter()
|
|
||||||
.map(|&c| if c == 0 { b' ' } else { c })
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
let len = cmp::min(255, handled_value.len());
|
|
||||||
compatible[..len].copy_from_slice(&handled_value[..len]);
|
|
||||||
compatible[..(len + 1)].to_vec()
|
|
||||||
} else {
|
|
||||||
property.value.to_vec()
|
|
||||||
};
|
|
||||||
let value = &value;
|
|
||||||
|
|
||||||
// Now the value can be either:
|
|
||||||
// - A null-terminated C string, or
|
|
||||||
// - Binary data
|
|
||||||
// We follow a very simple logic to present the value:
|
|
||||||
// - At first, try to convert it to CStr and print,
|
|
||||||
// - If failed, print it as u32 array.
|
|
||||||
let value_result = match CStr::from_bytes_with_nul(value) {
|
|
||||||
Ok(value_cstr) => match value_cstr.to_str() {
|
|
||||||
Ok(value_str) => Some(value_str),
|
|
||||||
Err(_e) => None,
|
|
||||||
},
|
|
||||||
Err(_e) => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(value_str) = value_result {
|
|
||||||
debug!(
|
|
||||||
"{:indent$}{} : {:#?}",
|
|
||||||
"",
|
|
||||||
name,
|
|
||||||
value_str,
|
|
||||||
indent = (n_spaces + 2)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
let mut array = Vec::with_capacity(256);
|
|
||||||
array.resize(value.len() / 4, 0u32);
|
|
||||||
BigEndian::read_u32_into(value, &mut array);
|
|
||||||
debug!(
|
|
||||||
"{:indent$}{} : {:X?}",
|
|
||||||
"",
|
|
||||||
name,
|
|
||||||
array,
|
|
||||||
indent = (n_spaces + 2)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print children nodes if there is any
|
|
||||||
for child in node.children() {
|
|
||||||
print_node(child, n_spaces + 2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
// Copyright © 2024 Institute of Software, CAS. All rights reserved.
|
|
||||||
// Copyright 2020 Arm Limited (or its affiliates). All rights reserved.
|
|
||||||
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
//
|
|
||||||
// Memory layout of RISC-V 64-bit guest:
|
|
||||||
//
|
|
||||||
// Physical +---------------------------------------------------------------+
|
|
||||||
// address | |
|
|
||||||
// end | |
|
|
||||||
// ~ ~ ~ ~
|
|
||||||
// | |
|
|
||||||
// | Highmem PCI MMIO space |
|
|
||||||
// | |
|
|
||||||
// RAM end +---------------------------------------------------------------+
|
|
||||||
// (dynamic, | |
|
|
||||||
// including | |
|
|
||||||
// hotplug ~ ~ ~ ~
|
|
||||||
// memory) | |
|
|
||||||
// | DRAM |
|
|
||||||
// | |
|
|
||||||
// | |
|
|
||||||
// | |
|
|
||||||
// | |
|
|
||||||
// 1 GB +---------------------------------------------------------------+
|
|
||||||
// | |
|
|
||||||
// | PCI MMCONFIG space |
|
|
||||||
// | |
|
|
||||||
// 768 MB +---------------------------------------------------------------+
|
|
||||||
// | |
|
|
||||||
// | |
|
|
||||||
// | PCI MMIO space |
|
|
||||||
// | |
|
|
||||||
// 256 MB +---------------------------------------------------------------|
|
|
||||||
// | |
|
|
||||||
// | Legacy devices space |
|
|
||||||
// | |
|
|
||||||
// 128 MB +---------------------------------------------------------------|
|
|
||||||
// | |
|
|
||||||
// | IMSICs |
|
|
||||||
// | |
|
|
||||||
// 64 MB +---------------------------------------------------------------+
|
|
||||||
// | |
|
|
||||||
// | APLICs |
|
|
||||||
// | |
|
|
||||||
// 0 GB +---------------------------------------------------------------+
|
|
||||||
//
|
|
||||||
//
|
|
||||||
|
|
||||||
use vm_memory::GuestAddress;
|
|
||||||
|
|
||||||
/// AIA related devices
|
|
||||||
/// See https://elixir.bootlin.com/linux/v6.10/source/arch/riscv/include/uapi/asm/kvm.h
|
|
||||||
/// 0x0 ~ 0x0400_0000 (64 MiB) resides APLICs
|
|
||||||
pub const APLIC_START: GuestAddress = GuestAddress(0);
|
|
||||||
pub const APLIC_SIZE: u64 = 0x4000;
|
|
||||||
|
|
||||||
/// 0x0400_0000 ~ 0x0800_0000 (64 MiB) resides IMSICs
|
|
||||||
pub const IMSIC_START: GuestAddress = GuestAddress(0x0400_0000);
|
|
||||||
pub const IMSIC_SIZE: u64 = 0x1000;
|
|
||||||
|
|
||||||
/// Below this address will reside the AIA, above this address will reside the MMIO devices.
|
|
||||||
const MAPPED_IO_START: GuestAddress = GuestAddress(0x0800_0000);
|
|
||||||
|
|
||||||
/// Space 0x0800_0000 ~ 0x1000_0000 is reserved for legacy devices.
|
|
||||||
pub const LEGACY_SERIAL_MAPPED_IO_START: GuestAddress = MAPPED_IO_START;
|
|
||||||
|
|
||||||
/// Space 0x0905_0000 ~ 0x0906_0000 is reserved for pcie io address
|
|
||||||
pub const MEM_PCI_IO_START: GuestAddress = GuestAddress(0x0905_0000);
|
|
||||||
pub const MEM_PCI_IO_SIZE: u64 = 0x1_0000;
|
|
||||||
|
|
||||||
/// Starting from 0x1000_0000 (256MiB) to 0x3000_0000 (768MiB) is used for PCIE MMIO
|
|
||||||
pub const MEM_32BIT_DEVICES_START: GuestAddress = GuestAddress(0x1000_0000);
|
|
||||||
pub const MEM_32BIT_DEVICES_SIZE: u64 = 0x2000_0000;
|
|
||||||
|
|
||||||
/// PCI MMCONFIG space (start: after the device space at 768MiB, length: 256MiB)
|
|
||||||
pub const PCI_MMCONFIG_START: GuestAddress = GuestAddress(0x3000_0000);
|
|
||||||
pub const PCI_MMCONFIG_SIZE: u64 = 256 << 20;
|
|
||||||
// One bus with potentially 256 devices (32 slots x 8 functions).
|
|
||||||
pub const PCI_MMIO_CONFIG_SIZE_PER_SEGMENT: u64 = 4096 * 256;
|
|
||||||
|
|
||||||
/// Start of RAM.
|
|
||||||
pub const RAM_START: GuestAddress = GuestAddress(0x4000_0000);
|
|
||||||
|
|
||||||
/// Kernel command line maximum size on RISC-V.
|
|
||||||
/// See https://elixir.bootlin.com/linux/v6.10/source/arch/riscv/include/uapi/asm/setup.h
|
|
||||||
pub const CMDLINE_MAX_SIZE: usize = 1024;
|
|
||||||
|
|
||||||
/// FDT is at the beginning of RAM.
|
|
||||||
pub const FDT_START: GuestAddress = RAM_START;
|
|
||||||
pub const FDT_MAX_SIZE: u64 = 0x1_0000;
|
|
||||||
|
|
||||||
/// Kernel start after FDT
|
|
||||||
pub const KERNEL_START: GuestAddress = GuestAddress(RAM_START.0 + FDT_MAX_SIZE);
|
|
||||||
|
|
||||||
/// Pci high memory base
|
|
||||||
pub const PCI_HIGH_BASE: GuestAddress = GuestAddress(0x2_0000_0000);
|
|
||||||
|
|
||||||
/// First usable interrupt on riscv64
|
|
||||||
pub const IRQ_BASE: u32 = 0;
|
|
||||||
|
|
||||||
// As per https://elixir.bootlin.com/linux/v6.10/source/arch/riscv/include/asm/kvm_host.h#L31
|
|
||||||
/// Number of supported interrupts
|
|
||||||
pub const IRQ_NUM: u32 = 1023;
|
|
||||||
@@ -1,173 +0,0 @@
|
|||||||
// Copyright © 2024 Institute of Software, CAS. All rights reserved.
|
|
||||||
// Copyright 2020 Arm Limited (or its affiliates). All rights reserved.
|
|
||||||
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
/// Module for the flattened device tree.
|
|
||||||
pub mod fdt;
|
|
||||||
/// Layout for this riscv64 system.
|
|
||||||
pub mod layout;
|
|
||||||
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::fmt::Debug;
|
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
|
|
||||||
use hypervisor::arch::riscv64::aia::Vaia;
|
|
||||||
use log::{log_enabled, Level};
|
|
||||||
use thiserror::Error;
|
|
||||||
use vm_memory::{Address, GuestAddress, GuestMemory, GuestMemoryAtomic};
|
|
||||||
|
|
||||||
pub use self::fdt::DeviceInfoForFdt;
|
|
||||||
use crate::{DeviceType, GuestMemoryMmap, PciSpaceInfo, RegionType};
|
|
||||||
|
|
||||||
pub const _NSIG: i32 = 65;
|
|
||||||
|
|
||||||
/// Errors thrown while configuring riscv64 system.
|
|
||||||
#[derive(Debug, Error)]
|
|
||||||
pub enum Error {
|
|
||||||
/// Failed to create a FDT.
|
|
||||||
#[error("Failed to create a FDT")]
|
|
||||||
SetupFdt,
|
|
||||||
|
|
||||||
/// Failed to write FDT to memory.
|
|
||||||
#[error("Failed to write FDT to memory: {0}")]
|
|
||||||
WriteFdtToMemory(fdt::Error),
|
|
||||||
|
|
||||||
/// Failed to create a AIA.
|
|
||||||
#[error("Failed to create a AIA")]
|
|
||||||
SetupAia,
|
|
||||||
|
|
||||||
/// Failed to compute the initramfs address.
|
|
||||||
#[error("Failed to compute the initramfs address")]
|
|
||||||
InitramfsAddress,
|
|
||||||
|
|
||||||
/// Error configuring the general purpose registers
|
|
||||||
#[error("Error configuring the general purpose registers: {0}")]
|
|
||||||
RegsConfiguration(hypervisor::HypervisorCpuError),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Error> for super::Error {
|
|
||||||
fn from(e: Error) -> super::Error {
|
|
||||||
super::Error::PlatformSpecific(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
|
||||||
/// Specifies the entry point address where the guest must start
|
|
||||||
/// executing code.
|
|
||||||
pub struct EntryPoint {
|
|
||||||
/// Address in guest memory where the guest must start execution
|
|
||||||
pub entry_addr: GuestAddress,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Configure the specified VCPU, and return its MPIDR.
|
|
||||||
pub fn configure_vcpu(
|
|
||||||
vcpu: &Arc<dyn hypervisor::Vcpu>,
|
|
||||||
id: u8,
|
|
||||||
boot_setup: Option<(EntryPoint, &GuestMemoryAtomic<GuestMemoryMmap>)>,
|
|
||||||
) -> super::Result<()> {
|
|
||||||
if let Some((kernel_entry_point, _guest_memory)) = boot_setup {
|
|
||||||
vcpu.setup_regs(
|
|
||||||
id,
|
|
||||||
kernel_entry_point.entry_addr.raw_value(),
|
|
||||||
layout::FDT_START.raw_value(),
|
|
||||||
)
|
|
||||||
.map_err(Error::RegsConfiguration)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn arch_memory_regions() -> Vec<(GuestAddress, usize, RegionType)> {
|
|
||||||
vec![
|
|
||||||
// 0 MiB ~ 256 MiB: AIA and legacy devices
|
|
||||||
(
|
|
||||||
GuestAddress(0),
|
|
||||||
layout::MEM_32BIT_DEVICES_START.0 as usize,
|
|
||||||
RegionType::Reserved,
|
|
||||||
),
|
|
||||||
// 256 MiB ~ 768 MiB: MMIO space
|
|
||||||
(
|
|
||||||
layout::MEM_32BIT_DEVICES_START,
|
|
||||||
layout::MEM_32BIT_DEVICES_SIZE as usize,
|
|
||||||
RegionType::SubRegion,
|
|
||||||
),
|
|
||||||
// 768 MiB ~ 1 GiB: reserved. The leading 256M for PCIe MMCONFIG space
|
|
||||||
(
|
|
||||||
layout::PCI_MMCONFIG_START,
|
|
||||||
layout::PCI_MMCONFIG_SIZE as usize,
|
|
||||||
RegionType::Reserved,
|
|
||||||
),
|
|
||||||
// 1GiB ~ inf: RAM
|
|
||||||
(layout::RAM_START, usize::MAX, RegionType::Ram),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Configures the system and should be called once per vm before starting vcpu threads.
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
|
||||||
pub fn configure_system<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHasher>(
|
|
||||||
guest_mem: &GuestMemoryMmap,
|
|
||||||
cmdline: &str,
|
|
||||||
num_vcpu: u32,
|
|
||||||
device_info: &HashMap<(DeviceType, String), T, S>,
|
|
||||||
initrd: &Option<super::InitramfsConfig>,
|
|
||||||
pci_space_info: &[PciSpaceInfo],
|
|
||||||
aia_device: &Arc<Mutex<dyn Vaia>>,
|
|
||||||
) -> super::Result<()> {
|
|
||||||
let fdt_final = fdt::create_fdt(
|
|
||||||
guest_mem,
|
|
||||||
cmdline,
|
|
||||||
num_vcpu,
|
|
||||||
device_info,
|
|
||||||
aia_device,
|
|
||||||
initrd,
|
|
||||||
pci_space_info,
|
|
||||||
)
|
|
||||||
.map_err(|_| Error::SetupFdt)?;
|
|
||||||
|
|
||||||
if log_enabled!(Level::Debug) {
|
|
||||||
fdt::print_fdt(&fdt_final);
|
|
||||||
}
|
|
||||||
|
|
||||||
fdt::write_fdt_to_memory(fdt_final, guest_mem).map_err(Error::WriteFdtToMemory)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the memory address where the initramfs could be loaded.
|
|
||||||
pub fn initramfs_load_addr(
|
|
||||||
guest_mem: &GuestMemoryMmap,
|
|
||||||
initramfs_size: usize,
|
|
||||||
) -> super::Result<u64> {
|
|
||||||
let round_to_pagesize = |size| (size + (super::PAGE_SIZE - 1)) & !(super::PAGE_SIZE - 1);
|
|
||||||
match guest_mem
|
|
||||||
.last_addr()
|
|
||||||
.checked_sub(round_to_pagesize(initramfs_size) as u64 - 1)
|
|
||||||
{
|
|
||||||
Some(offset) => {
|
|
||||||
if guest_mem.address_in_range(offset) {
|
|
||||||
Ok(offset.raw_value())
|
|
||||||
} else {
|
|
||||||
Err(super::Error::PlatformSpecific(Error::InitramfsAddress))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => Err(super::Error::PlatformSpecific(Error::InitramfsAddress)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_host_cpu_phys_bits(_hypervisor: &Arc<dyn hypervisor::Hypervisor>) -> u8 {
|
|
||||||
40
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_arch_memory_regions_dram() {
|
|
||||||
let regions = arch_memory_regions();
|
|
||||||
assert_eq!(4, regions.len());
|
|
||||||
assert_eq!(layout::RAM_START, regions[3].0);
|
|
||||||
assert_eq!(RegionType::Ram, regions[3].2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -5,6 +5,10 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE-BSD-3-Clause file.
|
// found in the LICENSE-BSD-3-Clause file.
|
||||||
|
|
||||||
|
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
|
||||||
|
use hypervisor::x86_64::LapicState;
|
||||||
|
use std::io::Cursor;
|
||||||
|
use std::mem;
|
||||||
use std::result;
|
use std::result;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
@@ -16,6 +20,32 @@ pub const APIC_LVT1: usize = 0x360;
|
|||||||
pub const APIC_MODE_NMI: u32 = 0x4;
|
pub const APIC_MODE_NMI: u32 = 0x4;
|
||||||
pub const APIC_MODE_EXTINT: u32 = 0x7;
|
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 {
|
pub fn set_apic_delivery_mode(reg: u32, mode: u32) -> u32 {
|
||||||
((reg) & !0x700) | ((mode) << 8)
|
((reg) & !0x700) | ((mode) << 8)
|
||||||
}
|
}
|
||||||
@@ -27,13 +57,42 @@ pub fn set_apic_delivery_mode(reg: u32, mode: u32) -> u32 {
|
|||||||
pub fn set_lint(vcpu: &Arc<dyn hypervisor::Vcpu>) -> Result<()> {
|
pub fn set_lint(vcpu: &Arc<dyn hypervisor::Vcpu>) -> Result<()> {
|
||||||
let mut klapic = vcpu.get_lapic()?;
|
let mut klapic = vcpu.get_lapic()?;
|
||||||
|
|
||||||
let lvt_lint0 = klapic.get_klapic_reg(APIC_LVT0);
|
let lvt_lint0 = get_klapic_reg(&klapic, APIC_LVT0);
|
||||||
klapic.set_klapic_reg(
|
set_klapic_reg(
|
||||||
|
&mut klapic,
|
||||||
APIC_LVT0,
|
APIC_LVT0,
|
||||||
set_apic_delivery_mode(lvt_lint0, APIC_MODE_EXTINT),
|
set_apic_delivery_mode(lvt_lint0, APIC_MODE_EXTINT),
|
||||||
);
|
);
|
||||||
let lvt_lint1 = klapic.get_klapic_reg(APIC_LVT1);
|
let lvt_lint1 = get_klapic_reg(&klapic, APIC_LVT1);
|
||||||
klapic.set_klapic_reg(APIC_LVT1, set_apic_delivery_mode(lvt_lint1, APIC_MODE_NMI));
|
set_klapic_reg(
|
||||||
|
&mut klapic,
|
||||||
|
APIC_LVT1,
|
||||||
|
set_apic_delivery_mode(lvt_lint1, APIC_MODE_NMI),
|
||||||
|
);
|
||||||
|
|
||||||
vcpu.set_lapic(&klapic)
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -24,9 +24,6 @@ pub const LOW_RAM_START: GuestAddress = GuestAddress(0x0);
|
|||||||
|
|
||||||
// == Fixed addresses within the "Low RAM" range: ==
|
// == 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
|
// Initial GDT/IDT needed to boot kernel
|
||||||
pub const BOOT_GDT_START: GuestAddress = GuestAddress(0x500);
|
pub const BOOT_GDT_START: GuestAddress = GuestAddress(0x500);
|
||||||
pub const BOOT_IDT_START: GuestAddress = GuestAddress(0x520);
|
pub const BOOT_IDT_START: GuestAddress = GuestAddress(0x520);
|
||||||
@@ -107,11 +104,6 @@ pub const KVM_TSS_SIZE: u64 = (3 * 4) << 10;
|
|||||||
pub const KVM_IDENTITY_MAP_START: GuestAddress = GuestAddress(KVM_TSS_START.0 + KVM_TSS_SIZE);
|
pub const KVM_IDENTITY_MAP_START: GuestAddress = GuestAddress(KVM_TSS_START.0 + KVM_TSS_SIZE);
|
||||||
pub const KVM_IDENTITY_MAP_SIZE: u64 = 4 << 10;
|
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
|
// IOAPIC
|
||||||
pub const IOAPIC_START: GuestAddress = GuestAddress(0xfec0_0000);
|
pub const IOAPIC_START: GuestAddress = GuestAddress(0xfec0_0000);
|
||||||
pub const IOAPIC_SIZE: u64 = 0x20;
|
pub const IOAPIC_SIZE: u64 = 0x20;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,4 @@
|
|||||||
// Copyright 2017 The Chromium OS Authors. All rights reserved.
|
// Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE-BSD-3-Clause file.
|
// found in the LICENSE-BSD-3-Clause file.
|
||||||
|
|
||||||
@@ -17,7 +15,7 @@ pub const MP_IRQDIR_DEFAULT: ::std::os::raw::c_uint = 0;
|
|||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Debug, Default, Copy, Clone)]
|
#[derive(Debug, Default, Copy, Clone)]
|
||||||
pub struct mpf_intel {
|
pub struct mpf_intel {
|
||||||
pub signature: [::std::os::raw::c_uchar; 4usize],
|
pub signature: [::std::os::raw::c_char; 4usize],
|
||||||
pub physptr: ::std::os::raw::c_uint,
|
pub physptr: ::std::os::raw::c_uint,
|
||||||
pub length: ::std::os::raw::c_uchar,
|
pub length: ::std::os::raw::c_uchar,
|
||||||
pub specification: ::std::os::raw::c_uchar,
|
pub specification: ::std::os::raw::c_uchar,
|
||||||
@@ -32,12 +30,12 @@ pub struct mpf_intel {
|
|||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Debug, Default, Copy, Clone)]
|
#[derive(Debug, Default, Copy, Clone)]
|
||||||
pub struct mpc_table {
|
pub struct mpc_table {
|
||||||
pub signature: [::std::os::raw::c_uchar; 4usize],
|
pub signature: [::std::os::raw::c_char; 4usize],
|
||||||
pub length: ::std::os::raw::c_ushort,
|
pub length: ::std::os::raw::c_ushort,
|
||||||
pub spec: ::std::os::raw::c_uchar,
|
pub spec: ::std::os::raw::c_char,
|
||||||
pub checksum: ::std::os::raw::c_uchar,
|
pub checksum: ::std::os::raw::c_char,
|
||||||
pub oem: [::std::os::raw::c_uchar; 8usize],
|
pub oem: [::std::os::raw::c_char; 8usize],
|
||||||
pub productid: [::std::os::raw::c_uchar; 12usize],
|
pub productid: [::std::os::raw::c_char; 12usize],
|
||||||
pub oemptr: ::std::os::raw::c_uint,
|
pub oemptr: ::std::os::raw::c_uint,
|
||||||
pub oemsize: ::std::os::raw::c_ushort,
|
pub oemsize: ::std::os::raw::c_ushort,
|
||||||
pub oemcount: ::std::os::raw::c_ushort,
|
pub oemcount: ::std::os::raw::c_ushort,
|
||||||
@@ -106,9 +104,9 @@ pub struct mpc_lintsrc {
|
|||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Debug, Default, Copy, Clone)]
|
#[derive(Debug, Default, Copy, Clone)]
|
||||||
pub struct mpc_oemtable {
|
pub struct mpc_oemtable {
|
||||||
pub signature: [::std::os::raw::c_uchar; 4usize],
|
pub signature: [::std::os::raw::c_char; 4usize],
|
||||||
pub length: ::std::os::raw::c_ushort,
|
pub length: ::std::os::raw::c_ushort,
|
||||||
pub rev: ::std::os::raw::c_uchar,
|
pub rev: ::std::os::raw::c_char,
|
||||||
pub checksum: ::std::os::raw::c_uchar,
|
pub checksum: ::std::os::raw::c_char,
|
||||||
pub mpc: [::std::os::raw::c_uchar; 8usize],
|
pub mpc: [::std::os::raw::c_char; 8usize],
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,15 +5,15 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE-BSD-3-Clause file.
|
// found in the LICENSE-BSD-3-Clause file.
|
||||||
|
|
||||||
use std::{mem, result, slice};
|
|
||||||
|
|
||||||
use libc::c_uchar;
|
|
||||||
use thiserror::Error;
|
|
||||||
use vm_memory::{Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryError};
|
|
||||||
|
|
||||||
use crate::layout::{APIC_START, HIGH_RAM_START, IOAPIC_START};
|
use crate::layout::{APIC_START, HIGH_RAM_START, IOAPIC_START};
|
||||||
use crate::x86_64::{get_x2apic_id, mpspec};
|
use crate::x86_64::mpspec;
|
||||||
use crate::GuestMemoryMmap;
|
use crate::GuestMemoryMmap;
|
||||||
|
use libc::c_char;
|
||||||
|
use std::io;
|
||||||
|
use std::mem;
|
||||||
|
use std::result;
|
||||||
|
use std::slice;
|
||||||
|
use vm_memory::{Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryError};
|
||||||
|
|
||||||
// This is a workaround to the Rust enforcement specifying that any implementation of a foreign
|
// This is a workaround to the Rust enforcement specifying that any implementation of a foreign
|
||||||
// trait (in this case `ByteValued`) where:
|
// trait (in this case `ByteValued`) where:
|
||||||
@@ -37,53 +37,36 @@ struct MpfIntelWrapper(mpspec::mpf_intel);
|
|||||||
|
|
||||||
// SAFETY: These `mpspec` wrapper types are only data, reading them from data is a safe initialization.
|
// SAFETY: These `mpspec` wrapper types are only data, reading them from data is a safe initialization.
|
||||||
unsafe impl ByteValued for MpcBusWrapper {}
|
unsafe impl ByteValued for MpcBusWrapper {}
|
||||||
// SAFETY: see above
|
|
||||||
unsafe impl ByteValued for MpcCpuWrapper {}
|
unsafe impl ByteValued for MpcCpuWrapper {}
|
||||||
// SAFETY: see above
|
|
||||||
unsafe impl ByteValued for MpcIntsrcWrapper {}
|
unsafe impl ByteValued for MpcIntsrcWrapper {}
|
||||||
// SAFETY: see above
|
|
||||||
unsafe impl ByteValued for MpcIoapicWrapper {}
|
unsafe impl ByteValued for MpcIoapicWrapper {}
|
||||||
// SAFETY: see above
|
|
||||||
unsafe impl ByteValued for MpcTableWrapper {}
|
unsafe impl ByteValued for MpcTableWrapper {}
|
||||||
// SAFETY: see above
|
|
||||||
unsafe impl ByteValued for MpcLintsrcWrapper {}
|
unsafe impl ByteValued for MpcLintsrcWrapper {}
|
||||||
// SAFETY: see above
|
|
||||||
unsafe impl ByteValued for MpfIntelWrapper {}
|
unsafe impl ByteValued for MpfIntelWrapper {}
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
/// There was too little guest memory to store the entire MP table.
|
/// There was too little guest memory to store the entire MP table.
|
||||||
#[error("There was too little guest memory to store the entire MP table")]
|
|
||||||
NotEnoughMemory,
|
NotEnoughMemory,
|
||||||
/// The MP table has too little address space to be stored.
|
/// The MP table has too little address space to be stored.
|
||||||
#[error("The MP table has too little address space to be stored")]
|
|
||||||
AddressOverflow,
|
AddressOverflow,
|
||||||
/// Failure while zeroing out the memory for the MP table.
|
/// Failure while zeroing out the memory for the MP table.
|
||||||
#[error("Failure while zeroing out the memory for the MP table: {0}")]
|
|
||||||
Clear(GuestMemoryError),
|
Clear(GuestMemoryError),
|
||||||
/// Number of CPUs exceeds the maximum supported CPUs
|
/// Number of CPUs exceeds the maximum supported CPUs
|
||||||
#[error("Number of CPUs exceeds the maximum supported CPUs")]
|
|
||||||
TooManyCpus,
|
TooManyCpus,
|
||||||
/// Failure to write the MP floating pointer.
|
/// Failure to write the MP floating pointer.
|
||||||
#[error("Failure to write the MP floating pointer: {0}")]
|
|
||||||
WriteMpfIntel(GuestMemoryError),
|
WriteMpfIntel(GuestMemoryError),
|
||||||
/// Failure to write MP CPU entry.
|
/// Failure to write MP CPU entry.
|
||||||
#[error("Failure to write MP CPU entry: {0}")]
|
|
||||||
WriteMpcCpu(GuestMemoryError),
|
WriteMpcCpu(GuestMemoryError),
|
||||||
/// Failure to write MP ioapic entry.
|
/// Failure to write MP ioapic entry.
|
||||||
#[error("Failure to write MP ioapic entry: {0}")]
|
|
||||||
WriteMpcIoapic(GuestMemoryError),
|
WriteMpcIoapic(GuestMemoryError),
|
||||||
/// Failure to write MP bus entry.
|
/// Failure to write MP bus entry.
|
||||||
#[error("Failure to write MP bus entry: {0}")]
|
|
||||||
WriteMpcBus(GuestMemoryError),
|
WriteMpcBus(GuestMemoryError),
|
||||||
/// Failure to write MP interrupt source entry.
|
/// Failure to write MP interrupt source entry.
|
||||||
#[error("Failure to write MP interrupt source entry: {0}")]
|
|
||||||
WriteMpcIntsrc(GuestMemoryError),
|
WriteMpcIntsrc(GuestMemoryError),
|
||||||
/// Failure to write MP local interrupt source entry.
|
/// Failure to write MP local interrupt source entry.
|
||||||
#[error("Failure to write MP local interrupt source entry: {0}")]
|
|
||||||
WriteMpcLintsrc(GuestMemoryError),
|
WriteMpcLintsrc(GuestMemoryError),
|
||||||
/// Failure to write MP table header.
|
/// Failure to write MP table header.
|
||||||
#[error("Failure to write MP table header: {0}")]
|
|
||||||
WriteMpcTable(GuestMemoryError),
|
WriteMpcTable(GuestMemoryError),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,20 +77,25 @@ pub type Result<T> = result::Result<T, Error>;
|
|||||||
// a large number for FC usecases.
|
// a large number for FC usecases.
|
||||||
pub const MAX_SUPPORTED_CPUS: u32 = 254;
|
pub const MAX_SUPPORTED_CPUS: u32 = 254;
|
||||||
|
|
||||||
|
// Convenience macro for making arrays of diverse character types.
|
||||||
|
macro_rules! char_array {
|
||||||
|
($t:ty; $( $c:expr ),*) => ( [ $( $c as $t ),* ] )
|
||||||
|
}
|
||||||
|
|
||||||
// Most of these variables are sourced from the Intel MP Spec 1.4.
|
// Most of these variables are sourced from the Intel MP Spec 1.4.
|
||||||
const SMP_MAGIC_IDENT: &[c_uchar; 4] = b"_MP_";
|
const SMP_MAGIC_IDENT: [c_char; 4] = char_array!(c_char; '_', 'M', 'P', '_');
|
||||||
const MPC_SIGNATURE: &[c_uchar; 4] = b"PCMP";
|
const MPC_SIGNATURE: [c_char; 4] = char_array!(c_char; 'P', 'C', 'M', 'P');
|
||||||
const MPC_SPEC: u8 = 4;
|
const MPC_SPEC: i8 = 4;
|
||||||
const MPC_OEM: &[c_uchar; 8] = b"FC ";
|
const MPC_OEM: [c_char; 8] = char_array!(c_char; 'F', 'C', ' ', ' ', ' ', ' ', ' ', ' ');
|
||||||
const MPC_PRODUCT_ID: &[c_uchar; 12] = &[b'0'; 12];
|
const MPC_PRODUCT_ID: [c_char; 12] = ['0' as c_char; 12];
|
||||||
const BUS_TYPE_ISA: &[c_uchar; 6] = b"ISA ";
|
const BUS_TYPE_ISA: [u8; 6] = char_array!(u8; 'I', 'S', 'A', ' ', ' ', ' ');
|
||||||
const APIC_VERSION: u8 = 0x14;
|
const APIC_VERSION: u8 = 0x14;
|
||||||
const CPU_STEPPING: u32 = 0x600;
|
const CPU_STEPPING: u32 = 0x600;
|
||||||
const CPU_FEATURE_APIC: u32 = 0x200;
|
const CPU_FEATURE_APIC: u32 = 0x200;
|
||||||
const CPU_FEATURE_FPU: u32 = 0x001;
|
const CPU_FEATURE_FPU: u32 = 0x001;
|
||||||
|
|
||||||
fn compute_checksum<T: Copy>(v: &T) -> u8 {
|
fn compute_checksum<T: Copy>(v: &T) -> u8 {
|
||||||
// SAFETY: we are only reading the bytes within the size of the `T` reference `v`.
|
// 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 v_slice = unsafe { slice::from_raw_parts(v as *const T as *const u8, mem::size_of::<T>()) };
|
||||||
let mut checksum: u8 = 0;
|
let mut checksum: u8 = 0;
|
||||||
for i in v_slice.iter() {
|
for i in v_slice.iter() {
|
||||||
@@ -132,18 +120,9 @@ fn compute_mp_size(num_cpus: u8) -> usize {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Performs setup of the MP table for the given `num_cpus`.
|
/// Performs setup of the MP table for the given `num_cpus`.
|
||||||
pub fn setup_mptable(
|
pub fn setup_mptable(offset: GuestAddress, mem: &GuestMemoryMmap, num_cpus: u8) -> Result<()> {
|
||||||
offset: GuestAddress,
|
if num_cpus as u32 > MAX_SUPPORTED_CPUS {
|
||||||
mem: &GuestMemoryMmap,
|
return Err(Error::TooManyCpus);
|
||||||
num_cpus: u8,
|
|
||||||
topology: Option<(u8, u8, u8)>,
|
|
||||||
) -> Result<()> {
|
|
||||||
if num_cpus > 0 {
|
|
||||||
let cpu_id_max = num_cpus - 1;
|
|
||||||
let x2apic_id_max = get_x2apic_id(cpu_id_max.into(), topology);
|
|
||||||
if x2apic_id_max >= MAX_SUPPORTED_CPUS {
|
|
||||||
return Err(Error::TooManyCpus);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Used to keep track of the next base pointer into the MP table.
|
// Used to keep track of the next base pointer into the MP table.
|
||||||
@@ -157,7 +136,7 @@ pub fn setup_mptable(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut checksum: u8 = 0;
|
let mut checksum: u8 = 0;
|
||||||
let ioapicid: u8 = MAX_SUPPORTED_CPUS as u8 + 1;
|
let ioapicid: u8 = num_cpus + 1;
|
||||||
|
|
||||||
// The checked_add here ensures the all of the following base_mp.unchecked_add's will be without
|
// The checked_add here ensures the all of the following base_mp.unchecked_add's will be without
|
||||||
// overflow.
|
// overflow.
|
||||||
@@ -169,13 +148,13 @@ pub fn setup_mptable(
|
|||||||
return Err(Error::AddressOverflow);
|
return Err(Error::AddressOverflow);
|
||||||
}
|
}
|
||||||
|
|
||||||
mem.read_exact_volatile_from(base_mp, &mut vec![0; mp_size].as_slice(), mp_size)
|
mem.read_exact_from(base_mp, &mut io::repeat(0), mp_size)
|
||||||
.map_err(Error::Clear)?;
|
.map_err(Error::Clear)?;
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut mpf_intel = MpfIntelWrapper(mpspec::mpf_intel::default());
|
let mut mpf_intel = MpfIntelWrapper(mpspec::mpf_intel::default());
|
||||||
let size = mem::size_of::<MpfIntelWrapper>() as u64;
|
let size = mem::size_of::<MpfIntelWrapper>() as u64;
|
||||||
mpf_intel.0.signature = *SMP_MAGIC_IDENT;
|
mpf_intel.0.signature = SMP_MAGIC_IDENT;
|
||||||
mpf_intel.0.length = 1;
|
mpf_intel.0.length = 1;
|
||||||
mpf_intel.0.specification = 4;
|
mpf_intel.0.specification = 4;
|
||||||
mpf_intel.0.physptr = (base_mp.raw_value() + size) as u32;
|
mpf_intel.0.physptr = (base_mp.raw_value() + size) as u32;
|
||||||
@@ -195,7 +174,7 @@ pub fn setup_mptable(
|
|||||||
for cpu_id in 0..num_cpus {
|
for cpu_id in 0..num_cpus {
|
||||||
let mut mpc_cpu = MpcCpuWrapper(mpspec::mpc_cpu::default());
|
let mut mpc_cpu = MpcCpuWrapper(mpspec::mpc_cpu::default());
|
||||||
mpc_cpu.0.type_ = mpspec::MP_PROCESSOR as u8;
|
mpc_cpu.0.type_ = mpspec::MP_PROCESSOR as u8;
|
||||||
mpc_cpu.0.apicid = get_x2apic_id(cpu_id as u32, topology) as u8;
|
mpc_cpu.0.apicid = cpu_id;
|
||||||
mpc_cpu.0.apicver = APIC_VERSION;
|
mpc_cpu.0.apicver = APIC_VERSION;
|
||||||
mpc_cpu.0.cpuflag = mpspec::CPU_ENABLED as u8
|
mpc_cpu.0.cpuflag = mpspec::CPU_ENABLED as u8
|
||||||
| if cpu_id == 0 {
|
| if cpu_id == 0 {
|
||||||
@@ -216,7 +195,7 @@ pub fn setup_mptable(
|
|||||||
let mut mpc_bus = MpcBusWrapper(mpspec::mpc_bus::default());
|
let mut mpc_bus = MpcBusWrapper(mpspec::mpc_bus::default());
|
||||||
mpc_bus.0.type_ = mpspec::MP_BUS as u8;
|
mpc_bus.0.type_ = mpspec::MP_BUS as u8;
|
||||||
mpc_bus.0.busid = 0;
|
mpc_bus.0.busid = 0;
|
||||||
mpc_bus.0.bustype = *BUS_TYPE_ISA;
|
mpc_bus.0.bustype = BUS_TYPE_ISA;
|
||||||
mem.write_obj(mpc_bus, base_mp)
|
mem.write_obj(mpc_bus, base_mp)
|
||||||
.map_err(Error::WriteMpcBus)?;
|
.map_err(Error::WriteMpcBus)?;
|
||||||
base_mp = base_mp.unchecked_add(size as u64);
|
base_mp = base_mp.unchecked_add(size as u64);
|
||||||
@@ -287,14 +266,14 @@ pub fn setup_mptable(
|
|||||||
|
|
||||||
{
|
{
|
||||||
let mut mpc_table = MpcTableWrapper(mpspec::mpc_table::default());
|
let mut mpc_table = MpcTableWrapper(mpspec::mpc_table::default());
|
||||||
mpc_table.0.signature = *MPC_SIGNATURE;
|
mpc_table.0.signature = MPC_SIGNATURE;
|
||||||
mpc_table.0.length = table_end.unchecked_offset_from(table_base) as u16;
|
mpc_table.0.length = table_end.unchecked_offset_from(table_base) as u16;
|
||||||
mpc_table.0.spec = MPC_SPEC;
|
mpc_table.0.spec = MPC_SPEC;
|
||||||
mpc_table.0.oem = *MPC_OEM;
|
mpc_table.0.oem = MPC_OEM;
|
||||||
mpc_table.0.productid = *MPC_PRODUCT_ID;
|
mpc_table.0.productid = MPC_PRODUCT_ID;
|
||||||
mpc_table.0.lapic = APIC_START.0 as u32;
|
mpc_table.0.lapic = APIC_START.0 as u32;
|
||||||
checksum = checksum.wrapping_add(compute_checksum(&mpc_table.0));
|
checksum = checksum.wrapping_add(compute_checksum(&mpc_table.0));
|
||||||
mpc_table.0.checksum = (!checksum).wrapping_add(1);
|
mpc_table.0.checksum = (!checksum).wrapping_add(1) as i8;
|
||||||
mem.write_obj(mpc_table, table_base)
|
mem.write_obj(mpc_table, table_base)
|
||||||
.map_err(Error::WriteMpcTable)?;
|
.map_err(Error::WriteMpcTable)?;
|
||||||
}
|
}
|
||||||
@@ -304,11 +283,9 @@ pub fn setup_mptable(
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use vm_memory::bitmap::BitmapSlice;
|
|
||||||
use vm_memory::{GuestUsize, VolatileMemoryError, VolatileSlice, WriteVolatile};
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::layout::MPTABLE_START;
|
use crate::layout::MPTABLE_START;
|
||||||
|
use vm_memory::{GuestAddress, GuestUsize};
|
||||||
|
|
||||||
fn table_entry_size(type_: u8) -> usize {
|
fn table_entry_size(type_: u8) -> usize {
|
||||||
match type_ as u32 {
|
match type_ as u32 {
|
||||||
@@ -317,7 +294,7 @@ mod tests {
|
|||||||
mpspec::MP_IOAPIC => mem::size_of::<MpcIoapicWrapper>(),
|
mpspec::MP_IOAPIC => mem::size_of::<MpcIoapicWrapper>(),
|
||||||
mpspec::MP_INTSRC => mem::size_of::<MpcIntsrcWrapper>(),
|
mpspec::MP_INTSRC => mem::size_of::<MpcIntsrcWrapper>(),
|
||||||
mpspec::MP_LINTSRC => mem::size_of::<MpcLintsrcWrapper>(),
|
mpspec::MP_LINTSRC => mem::size_of::<MpcLintsrcWrapper>(),
|
||||||
_ => panic!("unrecognized mpc table entry type: {type_}"),
|
_ => panic!("unrecognized mpc table entry type: {}", type_),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -327,7 +304,7 @@ mod tests {
|
|||||||
let mem =
|
let mem =
|
||||||
GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(num_cpus))]).unwrap();
|
GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(num_cpus))]).unwrap();
|
||||||
|
|
||||||
setup_mptable(MPTABLE_START, &mem, num_cpus, None).unwrap();
|
setup_mptable(MPTABLE_START, &mem, num_cpus).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -336,7 +313,7 @@ mod tests {
|
|||||||
let mem = GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(num_cpus) - 1)])
|
let mem = GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(num_cpus) - 1)])
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
setup_mptable(MPTABLE_START, &mem, num_cpus, None).unwrap_err();
|
assert!(setup_mptable(MPTABLE_START, &mem, num_cpus).is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -345,7 +322,7 @@ mod tests {
|
|||||||
let mem =
|
let mem =
|
||||||
GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(num_cpus))]).unwrap();
|
GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(num_cpus))]).unwrap();
|
||||||
|
|
||||||
setup_mptable(MPTABLE_START, &mem, num_cpus, None).unwrap();
|
setup_mptable(MPTABLE_START, &mem, num_cpus).unwrap();
|
||||||
|
|
||||||
let mpf_intel: MpfIntelWrapper = mem.read_obj(MPTABLE_START).unwrap();
|
let mpf_intel: MpfIntelWrapper = mem.read_obj(MPTABLE_START).unwrap();
|
||||||
|
|
||||||
@@ -361,31 +338,27 @@ mod tests {
|
|||||||
let mem =
|
let mem =
|
||||||
GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(num_cpus))]).unwrap();
|
GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(num_cpus))]).unwrap();
|
||||||
|
|
||||||
setup_mptable(MPTABLE_START, &mem, num_cpus, None).unwrap();
|
setup_mptable(MPTABLE_START, &mem, num_cpus).unwrap();
|
||||||
|
|
||||||
let mpf_intel: MpfIntelWrapper = mem.read_obj(MPTABLE_START).unwrap();
|
let mpf_intel: MpfIntelWrapper = mem.read_obj(MPTABLE_START).unwrap();
|
||||||
let mpc_offset = GuestAddress(mpf_intel.0.physptr as GuestUsize);
|
let mpc_offset = GuestAddress(mpf_intel.0.physptr as GuestUsize);
|
||||||
let mpc_table: MpcTableWrapper = mem.read_obj(mpc_offset).unwrap();
|
let mpc_table: MpcTableWrapper = mem.read_obj(mpc_offset).unwrap();
|
||||||
|
|
||||||
struct Sum(u8);
|
struct Sum(u8);
|
||||||
impl WriteVolatile for Sum {
|
impl io::Write for Sum {
|
||||||
fn write_volatile<B: BitmapSlice>(
|
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||||
&mut self,
|
for v in buf.iter() {
|
||||||
buf: &VolatileSlice<B>,
|
|
||||||
) -> result::Result<usize, VolatileMemoryError> {
|
|
||||||
let mut tmp = vec![0u8; buf.len()];
|
|
||||||
tmp.write_all_volatile(buf)?;
|
|
||||||
|
|
||||||
for v in tmp.iter() {
|
|
||||||
self.0 = self.0.wrapping_add(*v);
|
self.0 = self.0.wrapping_add(*v);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(buf.len())
|
Ok(buf.len())
|
||||||
}
|
}
|
||||||
|
fn flush(&mut self) -> io::Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut sum = Sum(0);
|
let mut sum = Sum(0);
|
||||||
mem.write_volatile_to(mpc_offset, &mut sum, mpc_table.0.length as usize)
|
mem.write_to(mpc_offset, &mut sum, mpc_table.0.length as usize)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(sum.0, 0);
|
assert_eq!(sum.0, 0);
|
||||||
}
|
}
|
||||||
@@ -399,7 +372,7 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
for i in 0..MAX_SUPPORTED_CPUS as u8 {
|
for i in 0..MAX_SUPPORTED_CPUS as u8 {
|
||||||
setup_mptable(MPTABLE_START, &mem, i, None).unwrap();
|
setup_mptable(MPTABLE_START, &mem, i).unwrap();
|
||||||
|
|
||||||
let mpf_intel: MpfIntelWrapper = mem.read_obj(MPTABLE_START).unwrap();
|
let mpf_intel: MpfIntelWrapper = mem.read_obj(MPTABLE_START).unwrap();
|
||||||
let mpc_offset = GuestAddress(mpf_intel.0.physptr as GuestUsize);
|
let mpc_offset = GuestAddress(mpf_intel.0.physptr as GuestUsize);
|
||||||
@@ -432,7 +405,7 @@ mod tests {
|
|||||||
let mem =
|
let mem =
|
||||||
GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(cpus as u8))]).unwrap();
|
GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(cpus as u8))]).unwrap();
|
||||||
|
|
||||||
let result = setup_mptable(MPTABLE_START, &mem, cpus as u8, None);
|
let result = setup_mptable(MPTABLE_START, &mem, cpus as u8);
|
||||||
result.unwrap_err();
|
assert!(result.is_err());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,57 +6,40 @@
|
|||||||
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE-BSD-3-Clause file.
|
// found in the LICENSE-BSD-3-Clause file.
|
||||||
|
use crate::layout::{BOOT_GDT_START, BOOT_IDT_START, PVH_INFO_START};
|
||||||
|
use crate::GuestMemoryMmap;
|
||||||
|
use hypervisor::arch::x86::gdt::{gdt_entry, segment_from_gdt};
|
||||||
|
use hypervisor::arch::x86::regs::*;
|
||||||
|
use hypervisor::x86_64::{FpuState, SpecialRegisters, StandardRegisters};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::{mem, result};
|
use std::{mem, result};
|
||||||
|
|
||||||
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 vm_memory::{Address, Bytes, GuestMemory, GuestMemoryError};
|
||||||
|
|
||||||
use crate::layout::{
|
#[derive(Debug)]
|
||||||
BOOT_GDT_START, BOOT_IDT_START, BOOT_STACK_POINTER, PVH_INFO_START, ZERO_PAGE_START,
|
|
||||||
};
|
|
||||||
use crate::{EntryPoint, GuestMemoryMmap};
|
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
/// Failed to get SREGs for this CPU.
|
/// Failed to get SREGs for this CPU.
|
||||||
#[error("Failed to get SREGs for this CPU: {0}")]
|
|
||||||
GetStatusRegisters(hypervisor::HypervisorCpuError),
|
GetStatusRegisters(hypervisor::HypervisorCpuError),
|
||||||
/// Failed to set base registers for this CPU.
|
/// Failed to set base registers for this CPU.
|
||||||
#[error("Failed to set base registers for this CPU: {0}")]
|
|
||||||
SetBaseRegisters(hypervisor::HypervisorCpuError),
|
SetBaseRegisters(hypervisor::HypervisorCpuError),
|
||||||
/// Failed to configure the FPU.
|
/// Failed to configure the FPU.
|
||||||
#[error("Failed to configure the FPU: {0}")]
|
|
||||||
SetFpuRegisters(hypervisor::HypervisorCpuError),
|
SetFpuRegisters(hypervisor::HypervisorCpuError),
|
||||||
/// Setting up MSRs failed.
|
/// Setting up MSRs failed.
|
||||||
#[error("Setting up MSRs failed: {0}")]
|
|
||||||
SetModelSpecificRegisters(hypervisor::HypervisorCpuError),
|
SetModelSpecificRegisters(hypervisor::HypervisorCpuError),
|
||||||
/// Failed to set SREGs for this CPU.
|
/// Failed to set SREGs for this CPU.
|
||||||
#[error("Failed to set SREGs for this CPU: {0}")]
|
|
||||||
SetStatusRegisters(hypervisor::HypervisorCpuError),
|
SetStatusRegisters(hypervisor::HypervisorCpuError),
|
||||||
/// Checking the GDT address failed.
|
/// Checking the GDT address failed.
|
||||||
#[error("Checking the GDT address failed")]
|
|
||||||
CheckGdtAddr,
|
CheckGdtAddr,
|
||||||
/// Writing the GDT to RAM failed.
|
/// Writing the GDT to RAM failed.
|
||||||
#[error("Writing the GDT to RAM failed: {0}")]
|
|
||||||
WriteGdt(GuestMemoryError),
|
WriteGdt(GuestMemoryError),
|
||||||
/// Writing the IDT to RAM failed.
|
/// Writing the IDT to RAM failed.
|
||||||
#[error("Writing the IDT to RAM failed: {0}")]
|
|
||||||
WriteIdt(GuestMemoryError),
|
WriteIdt(GuestMemoryError),
|
||||||
/// Writing PDPTE to RAM failed.
|
/// Writing PDPTE to RAM failed.
|
||||||
#[error("Writing PDPTE to RAM failed: {0}")]
|
|
||||||
WritePdpteAddress(GuestMemoryError),
|
WritePdpteAddress(GuestMemoryError),
|
||||||
/// Writing PDE to RAM failed.
|
/// Writing PDE to RAM failed.
|
||||||
#[error("Writing PDE to RAM failed: {0}")]
|
|
||||||
WritePdeAddress(GuestMemoryError),
|
WritePdeAddress(GuestMemoryError),
|
||||||
/// Writing PML4 to RAM failed.
|
/// Writing PML4 to RAM failed.
|
||||||
#[error("Writing PML4 to RAM failed: {0}")]
|
|
||||||
WritePml4Address(GuestMemoryError),
|
WritePml4Address(GuestMemoryError),
|
||||||
/// Writing PML5 to RAM failed.
|
/// Writing PML5 to RAM failed.
|
||||||
#[error("Writing PML5 to RAM failed: {0}")]
|
|
||||||
WritePml5Address(GuestMemoryError),
|
WritePml5Address(GuestMemoryError),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,7 +66,7 @@ pub fn setup_fpu(vcpu: &Arc<dyn hypervisor::Vcpu>) -> Result<()> {
|
|||||||
///
|
///
|
||||||
/// * `vcpu` - Structure for the VCPU that holds the VCPU's fd.
|
/// * `vcpu` - Structure for the VCPU that holds the VCPU's fd.
|
||||||
pub fn setup_msrs(vcpu: &Arc<dyn hypervisor::Vcpu>) -> Result<()> {
|
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)?;
|
.map_err(Error::SetModelSpecificRegisters)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -94,21 +77,13 @@ pub fn setup_msrs(vcpu: &Arc<dyn hypervisor::Vcpu>) -> Result<()> {
|
|||||||
/// # Arguments
|
/// # Arguments
|
||||||
///
|
///
|
||||||
/// * `vcpu` - Structure for the VCPU that holds the VCPU's fd.
|
/// * `vcpu` - Structure for the VCPU that holds the VCPU's fd.
|
||||||
/// * `entry_point` - Description of the boot entry to set up.
|
/// * `boot_ip` - Starting instruction pointer.
|
||||||
pub fn setup_regs(vcpu: &Arc<dyn hypervisor::Vcpu>, entry_point: EntryPoint) -> Result<()> {
|
pub fn setup_regs(vcpu: &Arc<dyn hypervisor::Vcpu>, boot_ip: u64) -> Result<()> {
|
||||||
let mut regs = vcpu.create_standard_regs();
|
let regs = StandardRegisters {
|
||||||
match entry_point.setup_header {
|
rflags: 0x0000000000000002u64,
|
||||||
None => {
|
rbx: PVH_INFO_START.raw_value(),
|
||||||
regs.set_rflags(0x0000000000000002u64);
|
rip: boot_ip,
|
||||||
regs.set_rip(entry_point.entry_addr.raw_value());
|
..Default::default()
|
||||||
regs.set_rbx(PVH_INFO_START.raw_value());
|
|
||||||
}
|
|
||||||
Some(_) => {
|
|
||||||
regs.set_rflags(0x0000000000000002u64);
|
|
||||||
regs.set_rip(entry_point.entry_addr.raw_value());
|
|
||||||
regs.set_rsp(BOOT_STACK_POINTER.raw_value());
|
|
||||||
regs.set_rsi(ZERO_PAGE_START.raw_value());
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
vcpu.set_regs(®s).map_err(Error::SetBaseRegisters)
|
vcpu.set_regs(®s).map_err(Error::SetBaseRegisters)
|
||||||
}
|
}
|
||||||
@@ -149,7 +124,7 @@ pub fn configure_segments_and_sregs(
|
|||||||
mem: &GuestMemoryMmap,
|
mem: &GuestMemoryMmap,
|
||||||
sregs: &mut SpecialRegisters,
|
sregs: &mut SpecialRegisters,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let gdt_table: [u64; BOOT_GDT_MAX] = {
|
let gdt_table: [u64; BOOT_GDT_MAX as usize] = {
|
||||||
// Configure GDT entries as specified by PVH boot protocol
|
// Configure GDT entries as specified by PVH boot protocol
|
||||||
[
|
[
|
||||||
gdt_entry(0, 0, 0), // NULL
|
gdt_entry(0, 0, 0), // NULL
|
||||||
@@ -188,9 +163,9 @@ pub fn configure_segments_and_sregs(
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use vm_memory::GuestAddress;
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::GuestMemoryMmap;
|
||||||
|
use vm_memory::GuestAddress;
|
||||||
|
|
||||||
fn create_guest_mem() -> GuestMemoryMmap {
|
fn create_guest_mem() -> GuestMemoryMmap {
|
||||||
GuestMemoryMmap::from_ranges(&[(GuestAddress(0), 0x10000)]).unwrap()
|
GuestMemoryMmap::from_ranges(&[(GuestAddress(0), 0x10000)]).unwrap()
|
||||||
|
|||||||
@@ -6,35 +6,46 @@
|
|||||||
//
|
//
|
||||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||||
|
|
||||||
use std::{mem, result, slice};
|
|
||||||
|
|
||||||
use thiserror::Error;
|
|
||||||
use uuid::Uuid;
|
|
||||||
use vm_memory::{Address, ByteValued, Bytes, GuestAddress};
|
|
||||||
|
|
||||||
use crate::layout::SMBIOS_START;
|
use crate::layout::SMBIOS_START;
|
||||||
use crate::GuestMemoryMmap;
|
use crate::GuestMemoryMmap;
|
||||||
|
use std::fmt::{self, Display};
|
||||||
|
use std::mem;
|
||||||
|
use std::result;
|
||||||
|
use std::slice;
|
||||||
|
use vm_memory::ByteValued;
|
||||||
|
use vm_memory::{Address, Bytes, GuestAddress};
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[allow(unused_variables)]
|
||||||
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
/// There was too little guest memory to store the entire SMBIOS table.
|
/// There was too little guest memory to store the entire SMBIOS table.
|
||||||
#[error("There was too little guest memory to store the SMBIOS table")]
|
|
||||||
NotEnoughMemory,
|
NotEnoughMemory,
|
||||||
/// The SMBIOS table has too little address space to be stored.
|
/// The SMBIOS table has too little address space to be stored.
|
||||||
#[error("The SMBIOS table has too little address space to be stored")]
|
|
||||||
AddressOverflow,
|
AddressOverflow,
|
||||||
/// Failure while zeroing out the memory for the SMBIOS table.
|
/// Failure while zeroing out the memory for the SMBIOS table.
|
||||||
#[error("Failure while zeroing out the memory for the SMBIOS table")]
|
|
||||||
Clear,
|
Clear,
|
||||||
/// Failure to write SMBIOS entrypoint structure
|
/// Failure to write SMBIOS entrypoint structure
|
||||||
#[error("Failure to write SMBIOS entrypoint structure")]
|
|
||||||
WriteSmbiosEp,
|
WriteSmbiosEp,
|
||||||
/// Failure to write additional data to memory
|
/// Failure to write additional data to memory
|
||||||
#[error("Failure to write additional data to memory")]
|
|
||||||
WriteData,
|
WriteData,
|
||||||
/// Failure to parse uuid, uuid format may be error
|
}
|
||||||
#[error("Failure to parse uuid: {0}")]
|
|
||||||
ParseUuid(uuid::Error),
|
impl std::error::Error for Error {}
|
||||||
|
|
||||||
|
impl Display for Error {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
use self::Error::*;
|
||||||
|
|
||||||
|
let description = match self {
|
||||||
|
NotEnoughMemory => "There was too little guest memory to store the SMBIOS table",
|
||||||
|
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>;
|
pub type Result<T> = result::Result<T, Error>;
|
||||||
@@ -43,13 +54,12 @@ pub type Result<T> = result::Result<T, Error>;
|
|||||||
const SM3_MAGIC_IDENT: &[u8; 5usize] = b"_SM3_";
|
const SM3_MAGIC_IDENT: &[u8; 5usize] = b"_SM3_";
|
||||||
const BIOS_INFORMATION: u8 = 0;
|
const BIOS_INFORMATION: u8 = 0;
|
||||||
const SYSTEM_INFORMATION: u8 = 1;
|
const SYSTEM_INFORMATION: u8 = 1;
|
||||||
const OEM_STRINGS: u8 = 11;
|
|
||||||
const END_OF_TABLE: u8 = 127;
|
const END_OF_TABLE: u8 = 127;
|
||||||
const PCI_SUPPORTED: u64 = 1 << 7;
|
const PCI_SUPPORTED: u64 = 1 << 7;
|
||||||
const IS_VIRTUAL_MACHINE: u8 = 1 << 4;
|
const IS_VIRTUAL_MACHINE: u8 = 1 << 4;
|
||||||
|
|
||||||
fn compute_checksum<T: Copy>(v: &T) -> u8 {
|
fn compute_checksum<T: Copy>(v: &T) -> u8 {
|
||||||
// SAFETY: we are only reading the bytes within the size of the `T` reference `v`.
|
// 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 v_slice = unsafe { slice::from_raw_parts(v as *const T as *const u8, mem::size_of::<T>()) };
|
||||||
let mut checksum: u8 = 0;
|
let mut checksum: u8 = 0;
|
||||||
for i in v_slice.iter() {
|
for i in v_slice.iter() {
|
||||||
@@ -58,85 +68,75 @@ fn compute_checksum<T: Copy>(v: &T) -> u8 {
|
|||||||
(!checksum).wrapping_add(1)
|
(!checksum).wrapping_add(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
#[repr(packed)]
|
#[repr(packed)]
|
||||||
#[derive(Default, Copy, Clone)]
|
#[derive(Default, Copy)]
|
||||||
struct Smbios30Entrypoint {
|
pub struct Smbios30Entrypoint {
|
||||||
signature: [u8; 5usize],
|
pub signature: [u8; 5usize],
|
||||||
checksum: u8,
|
pub checksum: u8,
|
||||||
length: u8,
|
pub length: u8,
|
||||||
majorver: u8,
|
pub majorver: u8,
|
||||||
minorver: u8,
|
pub minorver: u8,
|
||||||
docrev: u8,
|
pub docrev: u8,
|
||||||
revision: u8,
|
pub revision: u8,
|
||||||
reserved: u8,
|
pub reserved: u8,
|
||||||
max_size: u32,
|
pub max_size: u32,
|
||||||
physptr: u64,
|
pub physptr: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
impl Clone for Smbios30Entrypoint {
|
||||||
#[repr(packed)]
|
fn clone(&self) -> Self {
|
||||||
#[derive(Default, Copy, Clone)]
|
*self
|
||||||
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)]
|
#[repr(packed)]
|
||||||
#[derive(Default, Copy, Clone)]
|
#[derive(Default, Copy)]
|
||||||
struct SmbiosSysInfo {
|
pub struct SmbiosBiosInfo {
|
||||||
r#type: u8,
|
pub typ: u8,
|
||||||
length: u8,
|
pub length: u8,
|
||||||
handle: u16,
|
pub handle: u16,
|
||||||
manufacturer: u8,
|
pub vendor: u8,
|
||||||
product_name: u8,
|
pub version: u8,
|
||||||
version: u8,
|
pub start_addr: u16,
|
||||||
serial_number: u8,
|
pub release_date: u8,
|
||||||
uuid: [u8; 16usize],
|
pub rom_size: u8,
|
||||||
wake_up_type: u8,
|
pub characteristics: u64,
|
||||||
sku: u8,
|
pub characteristics_ext1: u8,
|
||||||
family: u8,
|
pub characteristics_ext2: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
impl Clone for SmbiosBiosInfo {
|
||||||
#[repr(packed)]
|
fn clone(&self) -> Self {
|
||||||
#[derive(Default, Copy, Clone)]
|
*self
|
||||||
struct SmbiosOemStrings {
|
}
|
||||||
r#type: u8,
|
|
||||||
length: u8,
|
|
||||||
handle: u16,
|
|
||||||
count: u8,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
#[repr(packed)]
|
#[repr(packed)]
|
||||||
#[derive(Default, Copy, Clone)]
|
#[derive(Default, Copy)]
|
||||||
struct SmbiosEndOfTable {
|
pub struct SmbiosSysInfo {
|
||||||
r#type: u8,
|
pub typ: u8,
|
||||||
length: u8,
|
pub length: u8,
|
||||||
handle: u16,
|
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,
|
||||||
}
|
}
|
||||||
|
|
||||||
// SAFETY: data structure only contain a series of integers
|
impl Clone for SmbiosSysInfo {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
*self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SAFETY: These data structures only contain a series of integers
|
||||||
unsafe impl ByteValued for Smbios30Entrypoint {}
|
unsafe impl ByteValued for Smbios30Entrypoint {}
|
||||||
// SAFETY: data structure only contain a series of integers
|
|
||||||
unsafe impl ByteValued for SmbiosBiosInfo {}
|
unsafe impl ByteValued for SmbiosBiosInfo {}
|
||||||
// SAFETY: data structure only contain a series of integers
|
|
||||||
unsafe impl ByteValued for SmbiosSysInfo {}
|
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>(
|
fn write_and_incr<T: ByteValued>(
|
||||||
mem: &GuestMemoryMmap,
|
mem: &GuestMemoryMmap,
|
||||||
@@ -162,12 +162,7 @@ fn write_string(
|
|||||||
Ok(curptr)
|
Ok(curptr)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setup_smbios(
|
pub fn setup_smbios(mem: &GuestMemoryMmap) -> Result<u64> {
|
||||||
mem: &GuestMemoryMmap,
|
|
||||||
serial_number: Option<&str>,
|
|
||||||
uuid: Option<&str>,
|
|
||||||
oem_strings: Option<&[&str]>,
|
|
||||||
) -> Result<u64> {
|
|
||||||
let physptr = GuestAddress(SMBIOS_START)
|
let physptr = GuestAddress(SMBIOS_START)
|
||||||
.checked_add(mem::size_of::<Smbios30Entrypoint>() as u64)
|
.checked_add(mem::size_of::<Smbios30Entrypoint>() as u64)
|
||||||
.ok_or(Error::NotEnoughMemory)?;
|
.ok_or(Error::NotEnoughMemory)?;
|
||||||
@@ -177,7 +172,7 @@ pub fn setup_smbios(
|
|||||||
{
|
{
|
||||||
handle += 1;
|
handle += 1;
|
||||||
let smbios_biosinfo = SmbiosBiosInfo {
|
let smbios_biosinfo = SmbiosBiosInfo {
|
||||||
r#type: BIOS_INFORMATION,
|
typ: BIOS_INFORMATION,
|
||||||
length: mem::size_of::<SmbiosBiosInfo>() as u8,
|
length: mem::size_of::<SmbiosBiosInfo>() as u8,
|
||||||
handle,
|
handle,
|
||||||
vendor: 1, // First string written in this section
|
vendor: 1, // First string written in this section
|
||||||
@@ -194,59 +189,29 @@ pub fn setup_smbios(
|
|||||||
|
|
||||||
{
|
{
|
||||||
handle += 1;
|
handle += 1;
|
||||||
|
|
||||||
let uuid_number = uuid
|
|
||||||
.map(Uuid::parse_str)
|
|
||||||
.transpose()
|
|
||||||
.map_err(Error::ParseUuid)?
|
|
||||||
.unwrap_or(Uuid::nil());
|
|
||||||
let smbios_sysinfo = SmbiosSysInfo {
|
let smbios_sysinfo = SmbiosSysInfo {
|
||||||
r#type: SYSTEM_INFORMATION,
|
typ: SYSTEM_INFORMATION,
|
||||||
length: mem::size_of::<SmbiosSysInfo>() as u8,
|
length: mem::size_of::<SmbiosSysInfo>() as u8,
|
||||||
handle,
|
handle,
|
||||||
manufacturer: 1, // First string written in this section
|
manufacturer: 1, // First string written in this section
|
||||||
product_name: 2, // Second 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()
|
..Default::default()
|
||||||
};
|
};
|
||||||
curptr = write_and_incr(mem, smbios_sysinfo, curptr)?;
|
curptr = write_and_incr(mem, smbios_sysinfo, curptr)?;
|
||||||
curptr = write_string(mem, "Cloud Hypervisor", curptr)?;
|
curptr = write_string(mem, "Cloud Hypervisor", 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)?;
|
curptr = write_and_incr(mem, 0u8, curptr)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
handle += 1;
|
handle += 1;
|
||||||
let smbios_end = SmbiosEndOfTable {
|
let smbios_sysinfo = SmbiosSysInfo {
|
||||||
r#type: END_OF_TABLE,
|
typ: END_OF_TABLE,
|
||||||
length: mem::size_of::<SmbiosEndOfTable>() as u8,
|
length: mem::size_of::<SmbiosSysInfo>() as u8,
|
||||||
handle,
|
handle,
|
||||||
|
..Default::default()
|
||||||
};
|
};
|
||||||
curptr = write_and_incr(mem, smbios_end, curptr)?;
|
curptr = write_and_incr(mem, smbios_sysinfo, curptr)?;
|
||||||
curptr = write_and_incr(mem, 0u8, curptr)?;
|
|
||||||
curptr = write_and_incr(mem, 0u8, curptr)?;
|
curptr = write_and_incr(mem, 0u8, curptr)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -268,7 +233,7 @@ pub fn setup_smbios(
|
|||||||
.map_err(|_| Error::WriteSmbiosEp)?;
|
.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)]
|
#[cfg(test)]
|
||||||
@@ -298,7 +263,7 @@ mod tests {
|
|||||||
fn entrypoint_checksum() {
|
fn entrypoint_checksum() {
|
||||||
let mem = GuestMemoryMmap::from_ranges(&[(GuestAddress(SMBIOS_START), 4096)]).unwrap();
|
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();
|
let smbios_ep: Smbios30Entrypoint = mem.read_obj(GuestAddress(SMBIOS_START)).unwrap();
|
||||||
|
|
||||||
|
|||||||
@@ -1,24 +1,18 @@
|
|||||||
// Copyright © 2021 Intel Corporation
|
// Copyright © 2021 Intel Corporation
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
use crate::GuestMemoryMmap;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{Read, Seek, SeekFrom};
|
use std::io::{Read, Seek, SeekFrom};
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use uuid::Uuid;
|
|
||||||
use vm_memory::{ByteValued, Bytes, GuestAddress, GuestMemoryError};
|
use vm_memory::{ByteValued, Bytes, GuestAddress, GuestMemoryError};
|
||||||
|
|
||||||
use crate::GuestMemoryMmap;
|
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum TdvfError {
|
pub enum TdvfError {
|
||||||
#[error("Failed read TDVF descriptor: {0}")]
|
#[error("Failed read TDVF descriptor: {0}")]
|
||||||
ReadDescriptor(#[source] std::io::Error),
|
ReadDescriptor(#[source] std::io::Error),
|
||||||
#[error("Failed read TDVF descriptor offset: {0}")]
|
#[error("Failed read TDVF descriptor offset: {0}")]
|
||||||
ReadDescriptorOffset(#[source] std::io::Error),
|
ReadDescriptorOffset(#[source] std::io::Error),
|
||||||
#[error("Failed read GUID table: {0}")]
|
|
||||||
ReadGuidTable(#[source] std::io::Error),
|
|
||||||
#[error("Invalid descriptor signature")]
|
#[error("Invalid descriptor signature")]
|
||||||
InvalidDescriptorSignature,
|
InvalidDescriptorSignature,
|
||||||
#[error("Invalid descriptor size")]
|
#[error("Invalid descriptor size")]
|
||||||
@@ -27,15 +21,10 @@ pub enum TdvfError {
|
|||||||
InvalidDescriptorVersion,
|
InvalidDescriptorVersion,
|
||||||
#[error("Failed to write HOB details to guest memory: {0}")]
|
#[error("Failed to write HOB details to guest memory: {0}")]
|
||||||
GuestMemoryWriteHob(#[source] GuestMemoryError),
|
GuestMemoryWriteHob(#[source] GuestMemoryError),
|
||||||
#[error("Failed to create Uuid: {0}")]
|
|
||||||
UuidCreation(#[source] uuid::Error),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const TABLE_FOOTER_GUID: &str = "96b582de-1fb2-45f7-baea-a366c55a082d";
|
|
||||||
const TDVF_METADATA_OFFSET_GUID: &str = "e47a6535-984a-4798-865e-4685a7bf8ec2";
|
|
||||||
|
|
||||||
// TDVF_DESCRIPTOR
|
// TDVF_DESCRIPTOR
|
||||||
#[repr(C, packed)]
|
#[repr(packed)]
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct TdvfDescriptor {
|
pub struct TdvfDescriptor {
|
||||||
signature: [u8; 4],
|
signature: [u8; 4],
|
||||||
@@ -45,7 +34,7 @@ pub struct TdvfDescriptor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TDVF_SECTION
|
// TDVF_SECTION
|
||||||
#[repr(C, packed)]
|
#[repr(packed)]
|
||||||
#[derive(Clone, Copy, Default, Debug)]
|
#[derive(Clone, Copy, Default, Debug)]
|
||||||
pub struct TdvfSection {
|
pub struct TdvfSection {
|
||||||
pub data_offset: u32,
|
pub data_offset: u32,
|
||||||
@@ -57,7 +46,7 @@ pub struct TdvfSection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[repr(u32)]
|
#[repr(u32)]
|
||||||
#[derive(Clone, Copy, Debug, Default)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub enum TdvfSectionType {
|
pub enum TdvfSectionType {
|
||||||
Bfv,
|
Bfv,
|
||||||
Cfv,
|
Cfv,
|
||||||
@@ -66,76 +55,16 @@ pub enum TdvfSectionType {
|
|||||||
PermMem,
|
PermMem,
|
||||||
Payload,
|
Payload,
|
||||||
PayloadParam,
|
PayloadParam,
|
||||||
#[default]
|
|
||||||
Reserved = 0xffffffff,
|
Reserved = 0xffffffff,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tdvf_descriptor_offset(file: &mut File) -> Result<(SeekFrom, bool), TdvfError> {
|
impl Default for TdvfSectionType {
|
||||||
// Let's first try to identify the presence of the table footer GUID
|
fn default() -> Self {
|
||||||
file.seek(SeekFrom::End(-0x30))
|
TdvfSectionType::Reserved
|
||||||
.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,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// If we end up here, this means the firmware doesn't support the new way
|
pub fn parse_tdvf_sections(file: &mut File) -> Result<Vec<TdvfSection>, TdvfError> {
|
||||||
// of exposing the TDVF descriptor offset through the table of GUIDs.
|
|
||||||
// That's why we fallback onto the deprecated method.
|
|
||||||
|
|
||||||
// The 32-bit offset to the TDVF metadata is located 32 bytes from
|
// The 32-bit offset to the TDVF metadata is located 32 bytes from
|
||||||
// the end of the file.
|
// the end of the file.
|
||||||
// See "TDVF Metadata Pointer" in "TDX Virtual Firmware Design Guide
|
// See "TDVF Metadata Pointer" in "TDX Virtual Firmware Design Guide
|
||||||
@@ -145,21 +74,13 @@ fn tdvf_descriptor_offset(file: &mut File) -> Result<(SeekFrom, bool), TdvfError
|
|||||||
let mut descriptor_offset: [u8; 4] = [0; 4];
|
let mut descriptor_offset: [u8; 4] = [0; 4];
|
||||||
file.read_exact(&mut descriptor_offset)
|
file.read_exact(&mut descriptor_offset)
|
||||||
.map_err(TdvfError::ReadDescriptorOffset)?;
|
.map_err(TdvfError::ReadDescriptorOffset)?;
|
||||||
|
let descriptor_offset = u32::from_le_bytes(descriptor_offset) as u64;
|
||||||
|
|
||||||
Ok((
|
file.seek(SeekFrom::Start(descriptor_offset))
|
||||||
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)
|
|
||||||
.map_err(TdvfError::ReadDescriptor)?;
|
.map_err(TdvfError::ReadDescriptor)?;
|
||||||
|
|
||||||
let mut descriptor: TdvfDescriptor = Default::default();
|
let mut descriptor: TdvfDescriptor = Default::default();
|
||||||
// SAFETY: we read exactly the size of the descriptor header
|
// Safe as we read exactly the size of the descriptor header
|
||||||
file.read_exact(unsafe {
|
file.read_exact(unsafe {
|
||||||
std::slice::from_raw_parts_mut(
|
std::slice::from_raw_parts_mut(
|
||||||
&mut descriptor as *mut _ as *mut u8,
|
&mut descriptor as *mut _ as *mut u8,
|
||||||
@@ -186,7 +107,7 @@ pub fn parse_tdvf_sections(file: &mut File) -> Result<(Vec<TdvfSection>, bool),
|
|||||||
let mut sections = Vec::new();
|
let mut sections = Vec::new();
|
||||||
sections.resize_with(descriptor.num_sections as usize, TdvfSection::default);
|
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 {
|
file.read_exact(unsafe {
|
||||||
std::slice::from_raw_parts_mut(
|
std::slice::from_raw_parts_mut(
|
||||||
sections.as_mut_ptr() as *mut u8,
|
sections.as_mut_ptr() as *mut u8,
|
||||||
@@ -195,21 +116,26 @@ pub fn parse_tdvf_sections(file: &mut File) -> Result<(Vec<TdvfSection>, bool),
|
|||||||
})
|
})
|
||||||
.map_err(TdvfError::ReadDescriptor)?;
|
.map_err(TdvfError::ReadDescriptor)?;
|
||||||
|
|
||||||
Ok((sections, guid_found))
|
Ok(sections)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(u16)]
|
#[repr(u16)]
|
||||||
#[derive(Copy, Clone, Debug, Default)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
enum HobType {
|
enum HobType {
|
||||||
Handoff = 0x1,
|
Handoff = 0x1,
|
||||||
ResourceDescriptor = 0x3,
|
ResourceDescriptor = 0x3,
|
||||||
GuidExtension = 0x4,
|
GuidExtension = 0x4,
|
||||||
#[default]
|
|
||||||
Unused = 0xfffe,
|
Unused = 0xfffe,
|
||||||
EndOfHobList = 0xffff,
|
EndOfHobList = 0xffff,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C, packed)]
|
impl Default for HobType {
|
||||||
|
fn default() -> Self {
|
||||||
|
HobType::Unused
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
#[derive(Copy, Clone, Default, Debug)]
|
#[derive(Copy, Clone, Default, Debug)]
|
||||||
struct HobHeader {
|
struct HobHeader {
|
||||||
r#type: HobType,
|
r#type: HobType,
|
||||||
@@ -217,7 +143,7 @@ struct HobHeader {
|
|||||||
reserved: u32,
|
reserved: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C, packed)]
|
#[repr(C)]
|
||||||
#[derive(Copy, Clone, Default, Debug)]
|
#[derive(Copy, Clone, Default, Debug)]
|
||||||
struct HobHandoffInfoTable {
|
struct HobHandoffInfoTable {
|
||||||
header: HobHeader,
|
header: HobHeader,
|
||||||
@@ -230,7 +156,7 @@ struct HobHandoffInfoTable {
|
|||||||
efi_end_of_hob_list: u64,
|
efi_end_of_hob_list: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C, packed)]
|
#[repr(C)]
|
||||||
#[derive(Copy, Clone, Default, Debug)]
|
#[derive(Copy, Clone, Default, Debug)]
|
||||||
struct EfiGuid {
|
struct EfiGuid {
|
||||||
data1: u32,
|
data1: u32,
|
||||||
@@ -239,7 +165,7 @@ struct EfiGuid {
|
|||||||
data4: [u8; 8],
|
data4: [u8; 8],
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C, packed)]
|
#[repr(C)]
|
||||||
#[derive(Copy, Clone, Default, Debug)]
|
#[derive(Copy, Clone, Default, Debug)]
|
||||||
struct HobResourceDescriptor {
|
struct HobResourceDescriptor {
|
||||||
header: HobHeader,
|
header: HobHeader,
|
||||||
@@ -250,7 +176,7 @@ struct HobResourceDescriptor {
|
|||||||
resource_length: u64,
|
resource_length: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C, packed)]
|
#[repr(C)]
|
||||||
#[derive(Copy, Clone, Default, Debug)]
|
#[derive(Copy, Clone, Default, Debug)]
|
||||||
struct HobGuidType {
|
struct HobGuidType {
|
||||||
header: HobHeader,
|
header: HobHeader,
|
||||||
@@ -258,39 +184,39 @@ struct HobGuidType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[repr(u32)]
|
#[repr(u32)]
|
||||||
#[derive(Clone, Copy, Debug, Default)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub enum PayloadImageType {
|
pub enum PayloadImageType {
|
||||||
#[default]
|
|
||||||
ExecutablePayload,
|
ExecutablePayload,
|
||||||
BzImage,
|
BzImage,
|
||||||
RawVmLinux,
|
RawVmLinux,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C, packed)]
|
impl Default for PayloadImageType {
|
||||||
|
fn default() -> Self {
|
||||||
|
PayloadImageType::ExecutablePayload
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
#[derive(Copy, Clone, Default, Debug)]
|
#[derive(Copy, Clone, Default, Debug)]
|
||||||
pub struct PayloadInfo {
|
pub struct PayloadInfo {
|
||||||
pub image_type: PayloadImageType,
|
pub image_type: PayloadImageType,
|
||||||
pub entry_point: u64,
|
pub entry_point: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C, packed)]
|
#[repr(C)]
|
||||||
#[derive(Copy, Clone, Default, Debug)]
|
#[derive(Copy, Clone, Default, Debug)]
|
||||||
struct TdPayload {
|
struct TdPayload {
|
||||||
guid_type: HobGuidType,
|
guid_type: HobGuidType,
|
||||||
payload_info: PayloadInfo,
|
payload_info: PayloadInfo,
|
||||||
}
|
}
|
||||||
|
|
||||||
// SAFETY: data structure only contain a series of integers
|
// SAFETY: These data structures only contain a series of integers
|
||||||
unsafe impl ByteValued for HobHeader {}
|
unsafe impl ByteValued for HobHeader {}
|
||||||
// SAFETY: data structure only contain a series of integers
|
|
||||||
unsafe impl ByteValued for HobHandoffInfoTable {}
|
unsafe impl ByteValued for HobHandoffInfoTable {}
|
||||||
// SAFETY: data structure only contain a series of integers
|
|
||||||
unsafe impl ByteValued for HobResourceDescriptor {}
|
unsafe impl ByteValued for HobResourceDescriptor {}
|
||||||
// SAFETY: data structure only contain a series of integers
|
|
||||||
unsafe impl ByteValued for HobGuidType {}
|
unsafe impl ByteValued for HobGuidType {}
|
||||||
// SAFETY: data structure only contain a series of integers
|
|
||||||
unsafe impl ByteValued for PayloadInfo {}
|
unsafe impl ByteValued for PayloadInfo {}
|
||||||
// SAFETY: data structure only contain a series of integers
|
|
||||||
unsafe impl ByteValued for TdPayload {}
|
unsafe impl ByteValued for TdPayload {}
|
||||||
|
|
||||||
pub struct TdHob {
|
pub struct TdHob {
|
||||||
@@ -299,7 +225,7 @@ pub struct TdHob {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn align_hob(v: u64) -> u64 {
|
fn align_hob(v: u64) -> u64 {
|
||||||
v.div_ceil(8) * 8
|
(v + 7) / 8 * 8
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TdHob {
|
impl TdHob {
|
||||||
@@ -386,29 +312,22 @@ impl TdHob {
|
|||||||
physical_start: u64,
|
physical_start: u64,
|
||||||
resource_length: u64,
|
resource_length: u64,
|
||||||
ram: bool,
|
ram: bool,
|
||||||
guid_found: bool,
|
|
||||||
) -> Result<(), TdvfError> {
|
) -> Result<(), TdvfError> {
|
||||||
self.add_resource(
|
self.add_resource(
|
||||||
mem,
|
mem,
|
||||||
physical_start,
|
physical_start,
|
||||||
resource_length,
|
resource_length,
|
||||||
if ram {
|
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 */
|
0 /* EFI_RESOURCE_SYSTEM_MEMORY */
|
||||||
} else {
|
} else {
|
||||||
0x5 /*EFI_RESOURCE_MEMORY_RESERVED */
|
0x5 /*EFI_RESOURCE_MEMORY_RESERVED */
|
||||||
},
|
},
|
||||||
/* TODO:
|
/* TODO:
|
||||||
* QEMU currently fills it in like this:
|
* QEMU currently fills it in like this:
|
||||||
* EFI_RESOURCE_ATTRIBUTE_PRESENT | EFI_RESOURCE_ATTRIBUTE_INITIALIZED | EFI_RESOURCE_ATTRIBUTE_TESTED
|
* EFI_RESOURCE_ATTRIBUTE_PRESENT | EFI_RESOURCE_ATTRIBUTE_INITIALIZED | EFI_RESOURCE_ATTRIBUTE_ENCRYPTED | EFI_RESOURCE_ATTRIBUTE_TESTED
|
||||||
* which differs from the spec (due to TDVF implementation issue?)
|
* which differs from the spec (due to TDVF implementation issue?)
|
||||||
*/
|
*/
|
||||||
0x7,
|
0x04000007,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -526,9 +445,9 @@ mod tests {
|
|||||||
#[ignore]
|
#[ignore]
|
||||||
fn test_parse_tdvf_sections() {
|
fn test_parse_tdvf_sections() {
|
||||||
let mut f = std::fs::File::open("tdvf.fd").unwrap();
|
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 {
|
for section in sections {
|
||||||
eprintln!("{section:x?}")
|
eprintln!("{:x?}", section)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
[package]
|
|
||||||
authors = ["The Chromium OS Authors", "The Cloud Hypervisor Authors"]
|
|
||||||
edition = "2021"
|
|
||||||
name = "block"
|
|
||||||
version = "0.1.0"
|
|
||||||
|
|
||||||
[features]
|
|
||||||
default = []
|
|
||||||
io_uring = ["dep:io-uring"]
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
byteorder = "1.5.0"
|
|
||||||
crc-any = "2.4.4"
|
|
||||||
io-uring = { version = "0.6.4", optional = true }
|
|
||||||
libc = "0.2.167"
|
|
||||||
log = "0.4.22"
|
|
||||||
remain = "0.2.14"
|
|
||||||
serde = { version = "1.0.208", features = ["derive"] }
|
|
||||||
smallvec = "1.13.2"
|
|
||||||
thiserror = "2.0.6"
|
|
||||||
uuid = { version = "1.12.1", features = ["v4"] }
|
|
||||||
virtio-bindings = { workspace = true, features = ["virtio-v5_0_0"] }
|
|
||||||
virtio-queue = { workspace = true }
|
|
||||||
vm-memory = { workspace = true, features = [
|
|
||||||
"backend-atomic",
|
|
||||||
"backend-bitmap",
|
|
||||||
"backend-mmap",
|
|
||||||
] }
|
|
||||||
vm-virtio = { path = "../vm-virtio" }
|
|
||||||
vmm-sys-util = { workspace = true }
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
// Copyright © 2021 Intel Corporation
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
|
||||||
|
|
||||||
use thiserror::Error;
|
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
|
||||||
|
|
||||||
use crate::DiskTopology;
|
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
|
||||||
pub enum DiskFileError {
|
|
||||||
/// Failed getting disk file size.
|
|
||||||
#[error("Failed getting disk file size: {0}")]
|
|
||||||
Size(#[source] std::io::Error),
|
|
||||||
/// Failed creating a new AsyncIo.
|
|
||||||
#[error("Failed creating a new AsyncIo: {0}")]
|
|
||||||
NewAsyncIo(#[source] std::io::Error),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type DiskFileResult<T> = std::result::Result<T, DiskFileError>;
|
|
||||||
|
|
||||||
pub trait DiskFile: Send {
|
|
||||||
fn size(&mut self) -> DiskFileResult<u64>;
|
|
||||||
fn new_async_io(&self, ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>>;
|
|
||||||
fn topology(&mut self) -> DiskTopology {
|
|
||||||
DiskTopology::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
|
||||||
pub enum AsyncIoError {
|
|
||||||
/// Failed vectored reading from file.
|
|
||||||
#[error("Failed vectored reading from file: {0}")]
|
|
||||||
ReadVectored(#[source] std::io::Error),
|
|
||||||
/// Failed vectored writing to file.
|
|
||||||
#[error("Failed vectored writing to file: {0}")]
|
|
||||||
WriteVectored(#[source] std::io::Error),
|
|
||||||
/// Failed synchronizing file.
|
|
||||||
#[error("Failed synchronizing file: {0}")]
|
|
||||||
Fsync(#[source] std::io::Error),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type AsyncIoResult<T> = std::result::Result<T, AsyncIoError>;
|
|
||||||
|
|
||||||
pub trait AsyncIo: Send {
|
|
||||||
fn notifier(&self) -> &EventFd;
|
|
||||||
fn read_vectored(
|
|
||||||
&mut self,
|
|
||||||
offset: libc::off_t,
|
|
||||||
iovecs: &[libc::iovec],
|
|
||||||
user_data: u64,
|
|
||||||
) -> AsyncIoResult<()>;
|
|
||||||
fn write_vectored(
|
|
||||||
&mut self,
|
|
||||||
offset: libc::off_t,
|
|
||||||
iovecs: &[libc::iovec],
|
|
||||||
user_data: u64,
|
|
||||||
) -> AsyncIoResult<()>;
|
|
||||||
fn fsync(&mut self, user_data: Option<u64>) -> AsyncIoResult<()>;
|
|
||||||
fn next_completed_request(&mut self) -> Option<(u64, i32)>;
|
|
||||||
}
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
// Copyright © 2021 Intel Corporation
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
use std::fs::File;
|
|
||||||
use std::io::{Read, Seek, SeekFrom, Write};
|
|
||||||
use std::os::unix::io::{AsRawFd, RawFd};
|
|
||||||
|
|
||||||
use crate::vhd::VhdFooter;
|
|
||||||
use crate::BlockBackend;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct FixedVhd {
|
|
||||||
file: File,
|
|
||||||
size: u64,
|
|
||||||
position: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FixedVhd {
|
|
||||||
pub fn new(mut file: File) -> std::io::Result<Self> {
|
|
||||||
let footer = VhdFooter::new(&mut file)?;
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
file,
|
|
||||||
size: footer.current_size(),
|
|
||||||
position: 0,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsRawFd for FixedVhd {
|
|
||||||
fn as_raw_fd(&self) -> RawFd {
|
|
||||||
self.file.as_raw_fd()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Read for FixedVhd {
|
|
||||||
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
|
||||||
match self.file.read(buf) {
|
|
||||||
Ok(r) => {
|
|
||||||
self.position = self.position.checked_add(r.try_into().unwrap()).unwrap();
|
|
||||||
Ok(r)
|
|
||||||
}
|
|
||||||
Err(e) => Err(e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Write for FixedVhd {
|
|
||||||
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
|
||||||
match self.file.write(buf) {
|
|
||||||
Ok(r) => {
|
|
||||||
self.position = self.position.checked_add(r.try_into().unwrap()).unwrap();
|
|
||||||
Ok(r)
|
|
||||||
}
|
|
||||||
Err(e) => Err(e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn flush(&mut self) -> std::io::Result<()> {
|
|
||||||
self.file.sync_all()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Seek for FixedVhd {
|
|
||||||
fn seek(&mut self, newpos: SeekFrom) -> std::io::Result<u64> {
|
|
||||||
match self.file.seek(newpos) {
|
|
||||||
Ok(pos) => {
|
|
||||||
self.position = pos;
|
|
||||||
Ok(pos)
|
|
||||||
}
|
|
||||||
Err(e) => Err(e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BlockBackend for FixedVhd {
|
|
||||||
fn size(&self) -> std::result::Result<u64, crate::Error> {
|
|
||||||
Ok(self.size)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Clone for FixedVhd {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
Self {
|
|
||||||
file: self.file.try_clone().expect("FixedVhd cloning failed"),
|
|
||||||
size: self.size,
|
|
||||||
position: self.position,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,152 +0,0 @@
|
|||||||
// Copyright © 2023 Intel Corporation
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
|
||||||
//
|
|
||||||
// Copyright © 2023 Crusoe Energy Systems LLC
|
|
||||||
//
|
|
||||||
|
|
||||||
use std::fs::File;
|
|
||||||
use std::io::{Seek, SeekFrom};
|
|
||||||
use std::os::unix::io::{AsRawFd, RawFd};
|
|
||||||
|
|
||||||
use vmm_sys_util::aio;
|
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
|
||||||
|
|
||||||
use crate::async_io::{
|
|
||||||
AsyncIo, AsyncIoError, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult,
|
|
||||||
};
|
|
||||||
use crate::DiskTopology;
|
|
||||||
|
|
||||||
pub struct RawFileDiskAio {
|
|
||||||
file: File,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RawFileDiskAio {
|
|
||||||
pub fn new(file: File) -> Self {
|
|
||||||
RawFileDiskAio { file }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DiskFile for RawFileDiskAio {
|
|
||||||
fn size(&mut self) -> DiskFileResult<u64> {
|
|
||||||
self.file
|
|
||||||
.seek(SeekFrom::End(0))
|
|
||||||
.map_err(DiskFileError::Size)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new_async_io(&self, ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
|
|
||||||
Ok(Box::new(
|
|
||||||
RawFileAsyncAio::new(self.file.as_raw_fd(), ring_depth)
|
|
||||||
.map_err(DiskFileError::NewAsyncIo)?,
|
|
||||||
) as Box<dyn AsyncIo>)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn topology(&mut self) -> DiskTopology {
|
|
||||||
if let Ok(topology) = DiskTopology::probe(&self.file) {
|
|
||||||
topology
|
|
||||||
} else {
|
|
||||||
warn!("Unable to get device topology. Using default topology");
|
|
||||||
DiskTopology::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct RawFileAsyncAio {
|
|
||||||
fd: RawFd,
|
|
||||||
ctx: aio::IoContext,
|
|
||||||
eventfd: EventFd,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RawFileAsyncAio {
|
|
||||||
pub fn new(fd: RawFd, queue_depth: u32) -> std::io::Result<Self> {
|
|
||||||
let eventfd = EventFd::new(libc::EFD_NONBLOCK)?;
|
|
||||||
let ctx = aio::IoContext::new(queue_depth)?;
|
|
||||||
|
|
||||||
Ok(RawFileAsyncAio { fd, ctx, eventfd })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsyncIo for RawFileAsyncAio {
|
|
||||||
fn notifier(&self) -> &EventFd {
|
|
||||||
&self.eventfd
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_vectored(
|
|
||||||
&mut self,
|
|
||||||
offset: libc::off_t,
|
|
||||||
iovecs: &[libc::iovec],
|
|
||||||
user_data: u64,
|
|
||||||
) -> AsyncIoResult<()> {
|
|
||||||
let iocbs = [&mut aio::IoControlBlock {
|
|
||||||
aio_fildes: self.fd.as_raw_fd() as u32,
|
|
||||||
aio_lio_opcode: aio::IOCB_CMD_PREADV as u16,
|
|
||||||
aio_buf: iovecs.as_ptr() as u64,
|
|
||||||
aio_nbytes: iovecs.len() as u64,
|
|
||||||
aio_offset: offset,
|
|
||||||
aio_data: user_data,
|
|
||||||
aio_flags: aio::IOCB_FLAG_RESFD,
|
|
||||||
aio_resfd: self.eventfd.as_raw_fd() as u32,
|
|
||||||
..Default::default()
|
|
||||||
}];
|
|
||||||
let _ = self
|
|
||||||
.ctx
|
|
||||||
.submit(&iocbs[..])
|
|
||||||
.map_err(AsyncIoError::ReadVectored)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_vectored(
|
|
||||||
&mut self,
|
|
||||||
offset: libc::off_t,
|
|
||||||
iovecs: &[libc::iovec],
|
|
||||||
user_data: u64,
|
|
||||||
) -> AsyncIoResult<()> {
|
|
||||||
let iocbs = [&mut aio::IoControlBlock {
|
|
||||||
aio_fildes: self.fd.as_raw_fd() as u32,
|
|
||||||
aio_lio_opcode: aio::IOCB_CMD_PWRITEV as u16,
|
|
||||||
aio_buf: iovecs.as_ptr() as u64,
|
|
||||||
aio_nbytes: iovecs.len() as u64,
|
|
||||||
aio_offset: offset,
|
|
||||||
aio_data: user_data,
|
|
||||||
aio_flags: aio::IOCB_FLAG_RESFD,
|
|
||||||
aio_resfd: self.eventfd.as_raw_fd() as u32,
|
|
||||||
..Default::default()
|
|
||||||
}];
|
|
||||||
let _ = self
|
|
||||||
.ctx
|
|
||||||
.submit(&iocbs[..])
|
|
||||||
.map_err(AsyncIoError::WriteVectored)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fsync(&mut self, user_data: Option<u64>) -> AsyncIoResult<()> {
|
|
||||||
if let Some(user_data) = user_data {
|
|
||||||
let iocbs = [&mut aio::IoControlBlock {
|
|
||||||
aio_fildes: self.fd.as_raw_fd() as u32,
|
|
||||||
aio_lio_opcode: aio::IOCB_CMD_FSYNC as u16,
|
|
||||||
aio_data: user_data,
|
|
||||||
aio_flags: aio::IOCB_FLAG_RESFD,
|
|
||||||
aio_resfd: self.eventfd.as_raw_fd() as u32,
|
|
||||||
..Default::default()
|
|
||||||
}];
|
|
||||||
let _ = self.ctx.submit(&iocbs[..]).map_err(AsyncIoError::Fsync)?;
|
|
||||||
} else {
|
|
||||||
// SAFETY: FFI call with a valid fd
|
|
||||||
unsafe { libc::fsync(self.fd) };
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn next_completed_request(&mut self) -> Option<(u64, i32)> {
|
|
||||||
let mut events: [aio::IoEvent; 1] = [aio::IoEvent::default()];
|
|
||||||
let rc = self.ctx.get_events(0, &mut events, None).unwrap();
|
|
||||||
if rc == 0 {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some((events[0].data, events[0].res as i32))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,221 +0,0 @@
|
|||||||
// Copyright © 2021 Intel Corporation
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
use std::fs::File;
|
|
||||||
use std::io::{self, Read, Seek, SeekFrom, Write};
|
|
||||||
|
|
||||||
use remain::sorted;
|
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
use crate::vhdx::vhdx_bat::{self, BatEntry, VhdxBatError};
|
|
||||||
use crate::vhdx::vhdx_metadata::{self, DiskSpec};
|
|
||||||
|
|
||||||
const SECTOR_SIZE: u64 = 512;
|
|
||||||
|
|
||||||
#[sorted]
|
|
||||||
#[derive(Error, Debug)]
|
|
||||||
pub enum VhdxIoError {
|
|
||||||
#[error("Invalid BAT entry state")]
|
|
||||||
InvalidBatEntryState,
|
|
||||||
#[error("Invalid BAT entry count")]
|
|
||||||
InvalidBatIndex,
|
|
||||||
#[error("Invalid disk size")]
|
|
||||||
InvalidDiskSize,
|
|
||||||
#[error("Failed reading sector blocks from file {0}")]
|
|
||||||
ReadSectorBlock(#[source] io::Error),
|
|
||||||
#[error("Failed changing file length {0}")]
|
|
||||||
ResizeFile(#[source] io::Error),
|
|
||||||
#[error("Differencing mode is not supported yet")]
|
|
||||||
UnsupportedMode,
|
|
||||||
#[error("Failed writing BAT to file {0}")]
|
|
||||||
WriteBat(#[source] VhdxBatError),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, VhdxIoError>;
|
|
||||||
|
|
||||||
macro_rules! align {
|
|
||||||
($n:expr, $align:expr) => {{
|
|
||||||
$n.div_ceil($align) * $align
|
|
||||||
}};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
struct Sector {
|
|
||||||
bat_index: u64,
|
|
||||||
free_sectors: u64,
|
|
||||||
free_bytes: u64,
|
|
||||||
file_offset: u64,
|
|
||||||
block_offset: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Sector {
|
|
||||||
/// Translate sector index and count of data in file to actual offsets and
|
|
||||||
/// BAT index.
|
|
||||||
pub fn new(
|
|
||||||
disk_spec: &DiskSpec,
|
|
||||||
bat: &[BatEntry],
|
|
||||||
sector_index: u64,
|
|
||||||
sector_count: u64,
|
|
||||||
) -> Result<Sector> {
|
|
||||||
let mut sector = Sector::default();
|
|
||||||
|
|
||||||
sector.bat_index = sector_index / disk_spec.sectors_per_block as u64;
|
|
||||||
sector.block_offset = sector_index % disk_spec.sectors_per_block as u64;
|
|
||||||
sector.free_sectors = disk_spec.sectors_per_block as u64 - sector.block_offset;
|
|
||||||
if sector.free_sectors > sector_count {
|
|
||||||
sector.free_sectors = sector_count;
|
|
||||||
}
|
|
||||||
|
|
||||||
sector.free_bytes = sector.free_sectors * disk_spec.logical_sector_size as u64;
|
|
||||||
sector.block_offset *= disk_spec.logical_sector_size as u64;
|
|
||||||
|
|
||||||
let bat_entry = match bat.get(sector.bat_index as usize) {
|
|
||||||
Some(entry) => entry.0,
|
|
||||||
None => {
|
|
||||||
return Err(VhdxIoError::InvalidBatIndex);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
sector.file_offset = bat_entry & vhdx_bat::BAT_FILE_OFF_MASK;
|
|
||||||
if sector.file_offset != 0 {
|
|
||||||
sector.file_offset += sector.block_offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(sector)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// VHDx IO read routine: requires relative sector index and count for the
|
|
||||||
/// requested data.
|
|
||||||
pub fn read(
|
|
||||||
f: &mut File,
|
|
||||||
buf: &mut [u8],
|
|
||||||
disk_spec: &DiskSpec,
|
|
||||||
bat: &[BatEntry],
|
|
||||||
mut sector_index: u64,
|
|
||||||
mut sector_count: u64,
|
|
||||||
) -> Result<usize> {
|
|
||||||
if disk_spec.has_parent {
|
|
||||||
return Err(VhdxIoError::UnsupportedMode);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut read_count: usize = 0;
|
|
||||||
while sector_count > 0 {
|
|
||||||
let sector = Sector::new(disk_spec, bat, sector_index, sector_count)?;
|
|
||||||
|
|
||||||
let bat_entry = match bat.get(sector.bat_index as usize) {
|
|
||||||
Some(entry) => entry.0,
|
|
||||||
None => {
|
|
||||||
return Err(VhdxIoError::InvalidBatIndex);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
match bat_entry & vhdx_bat::BAT_STATE_BIT_MASK {
|
|
||||||
vhdx_bat::PAYLOAD_BLOCK_NOT_PRESENT
|
|
||||||
| vhdx_bat::PAYLOAD_BLOCK_UNDEFINED
|
|
||||||
| vhdx_bat::PAYLOAD_BLOCK_UNMAPPED
|
|
||||||
| vhdx_bat::PAYLOAD_BLOCK_ZERO => {}
|
|
||||||
vhdx_bat::PAYLOAD_BLOCK_FULLY_PRESENT => {
|
|
||||||
f.seek(SeekFrom::Start(sector.file_offset))
|
|
||||||
.map_err(VhdxIoError::ReadSectorBlock)?;
|
|
||||||
f.read_exact(
|
|
||||||
&mut buf
|
|
||||||
[read_count..(read_count + (sector.free_sectors * SECTOR_SIZE) as usize)],
|
|
||||||
)
|
|
||||||
.map_err(VhdxIoError::ReadSectorBlock)?;
|
|
||||||
}
|
|
||||||
vhdx_bat::PAYLOAD_BLOCK_PARTIALLY_PRESENT => {
|
|
||||||
return Err(VhdxIoError::UnsupportedMode);
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
return Err(VhdxIoError::InvalidBatEntryState);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
sector_count -= sector.free_sectors;
|
|
||||||
sector_index += sector.free_sectors;
|
|
||||||
read_count += sector.free_bytes as usize;
|
|
||||||
}
|
|
||||||
Ok(read_count)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// VHDx IO write routine: requires relative sector index and count for the
|
|
||||||
/// requested data.
|
|
||||||
pub fn write(
|
|
||||||
f: &mut File,
|
|
||||||
buf: &[u8],
|
|
||||||
disk_spec: &mut DiskSpec,
|
|
||||||
bat_offset: u64,
|
|
||||||
bat: &mut [BatEntry],
|
|
||||||
mut sector_index: u64,
|
|
||||||
mut sector_count: u64,
|
|
||||||
) -> Result<usize> {
|
|
||||||
if disk_spec.has_parent {
|
|
||||||
return Err(VhdxIoError::UnsupportedMode);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut write_count: usize = 0;
|
|
||||||
while sector_count > 0 {
|
|
||||||
let sector = Sector::new(disk_spec, bat, sector_index, sector_count)?;
|
|
||||||
|
|
||||||
let bat_entry = match bat.get(sector.bat_index as usize) {
|
|
||||||
Some(entry) => entry.0,
|
|
||||||
None => {
|
|
||||||
return Err(VhdxIoError::InvalidBatIndex);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
match bat_entry & vhdx_bat::BAT_STATE_BIT_MASK {
|
|
||||||
vhdx_bat::PAYLOAD_BLOCK_NOT_PRESENT
|
|
||||||
| vhdx_bat::PAYLOAD_BLOCK_UNDEFINED
|
|
||||||
| vhdx_bat::PAYLOAD_BLOCK_UNMAPPED
|
|
||||||
| vhdx_bat::PAYLOAD_BLOCK_ZERO => {
|
|
||||||
let file_offset =
|
|
||||||
align!(disk_spec.image_size, vhdx_metadata::BLOCK_SIZE_MIN as u64);
|
|
||||||
let new_size = file_offset
|
|
||||||
.checked_add(disk_spec.block_size as u64)
|
|
||||||
.ok_or(VhdxIoError::InvalidDiskSize)?;
|
|
||||||
|
|
||||||
f.set_len(new_size).map_err(VhdxIoError::ResizeFile)?;
|
|
||||||
disk_spec.image_size = new_size;
|
|
||||||
|
|
||||||
let new_bat_entry = file_offset
|
|
||||||
| (vhdx_bat::PAYLOAD_BLOCK_FULLY_PRESENT & vhdx_bat::BAT_STATE_BIT_MASK);
|
|
||||||
bat[sector.bat_index as usize] = BatEntry(new_bat_entry);
|
|
||||||
BatEntry::write_bat_entries(f, bat_offset, bat).map_err(VhdxIoError::WriteBat)?;
|
|
||||||
|
|
||||||
if file_offset < vhdx_metadata::BLOCK_SIZE_MIN as u64 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
f.seek(SeekFrom::Start(file_offset))
|
|
||||||
.map_err(VhdxIoError::ReadSectorBlock)?;
|
|
||||||
f.write_all(
|
|
||||||
&buf[write_count..(write_count + (sector.free_sectors * SECTOR_SIZE) as usize)],
|
|
||||||
)
|
|
||||||
.map_err(VhdxIoError::ReadSectorBlock)?;
|
|
||||||
}
|
|
||||||
vhdx_bat::PAYLOAD_BLOCK_FULLY_PRESENT => {
|
|
||||||
if sector.file_offset < vhdx_metadata::BLOCK_SIZE_MIN as u64 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
f.seek(SeekFrom::Start(sector.file_offset))
|
|
||||||
.map_err(VhdxIoError::ReadSectorBlock)?;
|
|
||||||
f.write_all(
|
|
||||||
&buf[write_count..(write_count + (sector.free_sectors * SECTOR_SIZE) as usize)],
|
|
||||||
)
|
|
||||||
.map_err(VhdxIoError::ReadSectorBlock)?;
|
|
||||||
}
|
|
||||||
vhdx_bat::PAYLOAD_BLOCK_PARTIALLY_PRESENT => {
|
|
||||||
return Err(VhdxIoError::UnsupportedMode);
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
return Err(VhdxIoError::InvalidBatEntryState);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
sector_count -= sector.free_sectors;
|
|
||||||
sector_index += sector.free_sectors;
|
|
||||||
write_count += sector.free_bytes as usize;
|
|
||||||
}
|
|
||||||
Ok(write_count)
|
|
||||||
}
|
|
||||||
24
block_util/Cargo.toml
Normal file
24
block_util/Cargo.toml
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
[package]
|
||||||
|
name = "block_util"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["The Cloud Hypervisor Authors"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = []
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
io-uring = "0.5.2"
|
||||||
|
libc = "0.2.119"
|
||||||
|
log = "0.4.14"
|
||||||
|
qcow = { path = "../qcow" }
|
||||||
|
thiserror = "1.0.30"
|
||||||
|
versionize = "0.1.6"
|
||||||
|
versionize_derive = "0.1.4"
|
||||||
|
vhdx = { path = "../vhdx" }
|
||||||
|
virtio-bindings = { version = "0.1.0", features = ["virtio-v5_0_0"] }
|
||||||
|
virtio-queue = { git = "https://github.com/rust-vmm/vm-virtio", branch = "main" }
|
||||||
|
vm-memory = { version = "0.7.0", features = ["backend-mmap", "backend-atomic", "backend-bitmap"] }
|
||||||
|
vm-virtio = { path = "../vm-virtio" }
|
||||||
|
vmm-sys-util = "0.9.0"
|
||||||
|
|
||||||
146
block_util/src/async_io.rs
Normal file
146
block_util/src/async_io.rs
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
// Copyright © 2021 Intel Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||||
|
|
||||||
|
use libc::{ioctl, S_IFBLK, S_IFMT};
|
||||||
|
use std::convert::TryInto;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::os::unix::io::AsRawFd;
|
||||||
|
use thiserror::Error;
|
||||||
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
use vmm_sys_util::{ioctl_expr, ioctl_io_nr, ioctl_ioc_nr};
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum DiskFileError {
|
||||||
|
/// Failed getting disk file size.
|
||||||
|
#[error("Failed getting disk file size: {0}")]
|
||||||
|
Size(#[source] std::io::Error),
|
||||||
|
/// Failed creating a new AsyncIo.
|
||||||
|
#[error("Failed creating a new AsyncIo: {0}")]
|
||||||
|
NewAsyncIo(#[source] std::io::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct DiskTopology {
|
||||||
|
pub logical_block_size: u64,
|
||||||
|
pub physical_block_size: u64,
|
||||||
|
pub minimum_io_size: u64,
|
||||||
|
pub optimal_io_size: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for DiskTopology {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
logical_block_size: 512,
|
||||||
|
physical_block_size: 512,
|
||||||
|
minimum_io_size: 512,
|
||||||
|
optimal_io_size: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ioctl_io_nr!(BLKSSZGET, 0x12, 104);
|
||||||
|
ioctl_io_nr!(BLKPBSZGET, 0x12, 123);
|
||||||
|
ioctl_io_nr!(BLKIOMIN, 0x12, 120);
|
||||||
|
ioctl_io_nr!(BLKIOOPT, 0x12, 121);
|
||||||
|
|
||||||
|
enum BlockSize {
|
||||||
|
LogicalBlock,
|
||||||
|
PhysicalBlock,
|
||||||
|
MinimumIo,
|
||||||
|
OptimalIo,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DiskTopology {
|
||||||
|
fn is_block_device(f: &mut File) -> std::io::Result<bool> {
|
||||||
|
let mut stat = std::mem::MaybeUninit::<libc::stat>::uninit();
|
||||||
|
let ret = unsafe { libc::fstat(f.as_raw_fd(), stat.as_mut_ptr()) };
|
||||||
|
if ret != 0 {
|
||||||
|
return Err(std::io::Error::last_os_error());
|
||||||
|
}
|
||||||
|
|
||||||
|
let is_block = unsafe { (*stat.as_ptr()).st_mode & S_IFMT == S_IFBLK };
|
||||||
|
Ok(is_block)
|
||||||
|
}
|
||||||
|
|
||||||
|
// libc::ioctl() takes different types on different architectures
|
||||||
|
#[allow(clippy::useless_conversion)]
|
||||||
|
fn query_block_size(f: &mut File, block_size_type: BlockSize) -> std::io::Result<u64> {
|
||||||
|
let mut block_size = 0;
|
||||||
|
let ret = unsafe {
|
||||||
|
ioctl(
|
||||||
|
f.as_raw_fd(),
|
||||||
|
match block_size_type {
|
||||||
|
BlockSize::LogicalBlock => BLKSSZGET(),
|
||||||
|
BlockSize::PhysicalBlock => BLKPBSZGET(),
|
||||||
|
BlockSize::MinimumIo => BLKIOMIN(),
|
||||||
|
BlockSize::OptimalIo => BLKIOOPT(),
|
||||||
|
}
|
||||||
|
.try_into()
|
||||||
|
.unwrap(),
|
||||||
|
&mut block_size,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
if ret != 0 {
|
||||||
|
return Err(std::io::Error::last_os_error());
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(block_size)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn probe(f: &mut File) -> std::io::Result<Self> {
|
||||||
|
if !Self::is_block_device(f)? {
|
||||||
|
return Ok(DiskTopology::default());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(DiskTopology {
|
||||||
|
logical_block_size: Self::query_block_size(f, BlockSize::LogicalBlock)?,
|
||||||
|
physical_block_size: Self::query_block_size(f, BlockSize::PhysicalBlock)?,
|
||||||
|
minimum_io_size: Self::query_block_size(f, BlockSize::MinimumIo)?,
|
||||||
|
optimal_io_size: Self::query_block_size(f, BlockSize::OptimalIo)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type DiskFileResult<T> = std::result::Result<T, DiskFileError>;
|
||||||
|
|
||||||
|
pub trait DiskFile: Send + Sync {
|
||||||
|
fn size(&mut self) -> DiskFileResult<u64>;
|
||||||
|
fn new_async_io(&self, ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>>;
|
||||||
|
fn topology(&mut self) -> DiskTopology {
|
||||||
|
DiskTopology::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum AsyncIoError {
|
||||||
|
/// Failed vectored reading from file.
|
||||||
|
#[error("Failed vectored reading from file: {0}")]
|
||||||
|
ReadVectored(#[source] std::io::Error),
|
||||||
|
/// Failed vectored writing to file.
|
||||||
|
#[error("Failed vectored writing to file: {0}")]
|
||||||
|
WriteVectored(#[source] std::io::Error),
|
||||||
|
/// Failed synchronizing file.
|
||||||
|
#[error("Failed synchronizing file: {0}")]
|
||||||
|
Fsync(#[source] std::io::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type AsyncIoResult<T> = std::result::Result<T, AsyncIoError>;
|
||||||
|
|
||||||
|
pub trait AsyncIo: Send + 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)>;
|
||||||
|
}
|
||||||
@@ -2,34 +2,39 @@
|
|||||||
//
|
//
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use std::fs::File;
|
|
||||||
use std::os::unix::io::{AsRawFd, RawFd};
|
|
||||||
|
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
|
||||||
|
|
||||||
use crate::async_io::{
|
use crate::async_io::{
|
||||||
AsyncIo, AsyncIoError, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult,
|
AsyncIo, AsyncIoError, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult,
|
||||||
};
|
};
|
||||||
use crate::fixed_vhd::FixedVhd;
|
|
||||||
use crate::raw_async::RawFileAsync;
|
use crate::raw_async::RawFileAsync;
|
||||||
use crate::BlockBackend;
|
use crate::vhd::VhdFooter;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::os::unix::io::{AsRawFd, RawFd};
|
||||||
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
pub struct FixedVhdDiskAsync(FixedVhd);
|
pub struct FixedVhdDiskAsync {
|
||||||
|
file: File,
|
||||||
|
size: u64,
|
||||||
|
}
|
||||||
|
|
||||||
impl FixedVhdDiskAsync {
|
impl FixedVhdDiskAsync {
|
||||||
pub fn new(file: File) -> std::io::Result<Self> {
|
pub fn new(mut file: File) -> std::io::Result<Self> {
|
||||||
Ok(Self(FixedVhd::new(file)?))
|
let footer = VhdFooter::new(&mut file)?;
|
||||||
|
|
||||||
|
Ok(FixedVhdDiskAsync {
|
||||||
|
file,
|
||||||
|
size: footer.current_size(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DiskFile for FixedVhdDiskAsync {
|
impl DiskFile for FixedVhdDiskAsync {
|
||||||
fn size(&mut self) -> DiskFileResult<u64> {
|
fn size(&mut self) -> DiskFileResult<u64> {
|
||||||
Ok(self.0.size().unwrap())
|
Ok(self.size)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_async_io(&self, ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
|
fn new_async_io(&self, ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
|
||||||
Ok(Box::new(
|
Ok(Box::new(
|
||||||
FixedVhdAsync::new(self.0.as_raw_fd(), ring_depth, self.0.size().unwrap())
|
FixedVhdAsync::new(self.file.as_raw_fd(), ring_depth, self.size)
|
||||||
.map_err(DiskFileError::NewAsyncIo)?,
|
.map_err(DiskFileError::NewAsyncIo)?,
|
||||||
) as Box<dyn AsyncIo>)
|
) as Box<dyn AsyncIo>)
|
||||||
}
|
}
|
||||||
@@ -59,7 +64,7 @@ impl AsyncIo for FixedVhdAsync {
|
|||||||
fn read_vectored(
|
fn read_vectored(
|
||||||
&mut self,
|
&mut self,
|
||||||
offset: libc::off_t,
|
offset: libc::off_t,
|
||||||
iovecs: &[libc::iovec],
|
iovecs: Vec<libc::iovec>,
|
||||||
user_data: u64,
|
user_data: u64,
|
||||||
) -> AsyncIoResult<()> {
|
) -> AsyncIoResult<()> {
|
||||||
if offset as u64 >= self.size {
|
if offset as u64 >= self.size {
|
||||||
@@ -78,7 +83,7 @@ impl AsyncIo for FixedVhdAsync {
|
|||||||
fn write_vectored(
|
fn write_vectored(
|
||||||
&mut self,
|
&mut self,
|
||||||
offset: libc::off_t,
|
offset: libc::off_t,
|
||||||
iovecs: &[libc::iovec],
|
iovecs: Vec<libc::iovec>,
|
||||||
user_data: u64,
|
user_data: u64,
|
||||||
) -> AsyncIoResult<()> {
|
) -> AsyncIoResult<()> {
|
||||||
if offset as u64 >= self.size {
|
if offset as u64 >= self.size {
|
||||||
@@ -99,7 +104,7 @@ impl AsyncIo for FixedVhdAsync {
|
|||||||
self.raw_file_async.fsync(user_data)
|
self.raw_file_async.fsync(user_data)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn next_completed_request(&mut self) -> Option<(u64, i32)> {
|
fn complete(&mut self) -> Vec<(u64, i32)> {
|
||||||
self.raw_file_async.next_completed_request()
|
self.raw_file_async.complete()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,34 +2,39 @@
|
|||||||
//
|
//
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use std::fs::File;
|
|
||||||
use std::os::unix::io::{AsRawFd, RawFd};
|
|
||||||
|
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
|
||||||
|
|
||||||
use crate::async_io::{
|
use crate::async_io::{
|
||||||
AsyncIo, AsyncIoError, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult,
|
AsyncIo, AsyncIoError, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult,
|
||||||
};
|
};
|
||||||
use crate::fixed_vhd::FixedVhd;
|
|
||||||
use crate::raw_sync::RawFileSync;
|
use crate::raw_sync::RawFileSync;
|
||||||
use crate::BlockBackend;
|
use crate::vhd::VhdFooter;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::os::unix::io::{AsRawFd, RawFd};
|
||||||
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
pub struct FixedVhdDiskSync(FixedVhd);
|
pub struct FixedVhdDiskSync {
|
||||||
|
file: File,
|
||||||
|
size: u64,
|
||||||
|
}
|
||||||
|
|
||||||
impl FixedVhdDiskSync {
|
impl FixedVhdDiskSync {
|
||||||
pub fn new(file: File) -> std::io::Result<Self> {
|
pub fn new(mut file: File) -> std::io::Result<Self> {
|
||||||
Ok(Self(FixedVhd::new(file)?))
|
let footer = VhdFooter::new(&mut file)?;
|
||||||
|
|
||||||
|
Ok(FixedVhdDiskSync {
|
||||||
|
file,
|
||||||
|
size: footer.current_size(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DiskFile for FixedVhdDiskSync {
|
impl DiskFile for FixedVhdDiskSync {
|
||||||
fn size(&mut self) -> DiskFileResult<u64> {
|
fn size(&mut self) -> DiskFileResult<u64> {
|
||||||
Ok(self.0.size().unwrap())
|
Ok(self.size)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_async_io(&self, _ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
|
fn new_async_io(&self, _ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
|
||||||
Ok(Box::new(
|
Ok(Box::new(
|
||||||
FixedVhdSync::new(self.0.as_raw_fd(), self.0.size().unwrap())
|
FixedVhdSync::new(self.file.as_raw_fd(), self.size)
|
||||||
.map_err(DiskFileError::NewAsyncIo)?,
|
.map_err(DiskFileError::NewAsyncIo)?,
|
||||||
) as Box<dyn AsyncIo>)
|
) as Box<dyn AsyncIo>)
|
||||||
}
|
}
|
||||||
@@ -57,7 +62,7 @@ impl AsyncIo for FixedVhdSync {
|
|||||||
fn read_vectored(
|
fn read_vectored(
|
||||||
&mut self,
|
&mut self,
|
||||||
offset: libc::off_t,
|
offset: libc::off_t,
|
||||||
iovecs: &[libc::iovec],
|
iovecs: Vec<libc::iovec>,
|
||||||
user_data: u64,
|
user_data: u64,
|
||||||
) -> AsyncIoResult<()> {
|
) -> AsyncIoResult<()> {
|
||||||
if offset as u64 >= self.size {
|
if offset as u64 >= self.size {
|
||||||
@@ -76,7 +81,7 @@ impl AsyncIo for FixedVhdSync {
|
|||||||
fn write_vectored(
|
fn write_vectored(
|
||||||
&mut self,
|
&mut self,
|
||||||
offset: libc::off_t,
|
offset: libc::off_t,
|
||||||
iovecs: &[libc::iovec],
|
iovecs: Vec<libc::iovec>,
|
||||||
user_data: u64,
|
user_data: u64,
|
||||||
) -> AsyncIoResult<()> {
|
) -> AsyncIoResult<()> {
|
||||||
if offset as u64 >= self.size {
|
if offset as u64 >= self.size {
|
||||||
@@ -96,7 +101,7 @@ impl AsyncIo for FixedVhdSync {
|
|||||||
self.raw_file_sync.fsync(user_data)
|
self.raw_file_sync.fsync(user_data)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn next_completed_request(&mut self) -> Option<(u64, i32)> {
|
fn complete(&mut self) -> Vec<(u64, i32)> {
|
||||||
self.raw_file_sync.next_completed_request()
|
self.raw_file_sync.complete()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -12,88 +12,62 @@
|
|||||||
extern crate log;
|
extern crate log;
|
||||||
|
|
||||||
pub mod async_io;
|
pub mod async_io;
|
||||||
pub mod fixed_vhd;
|
|
||||||
#[cfg(feature = "io_uring")]
|
|
||||||
/// Enabled with the `"io_uring"` feature
|
|
||||||
pub mod fixed_vhd_async;
|
pub mod fixed_vhd_async;
|
||||||
pub mod fixed_vhd_sync;
|
pub mod fixed_vhd_sync;
|
||||||
pub mod qcow;
|
|
||||||
pub mod qcow_sync;
|
pub mod qcow_sync;
|
||||||
#[cfg(feature = "io_uring")]
|
|
||||||
/// Async primitives based on `io-uring`
|
|
||||||
///
|
|
||||||
/// Enabled with the `"io_uring"` feature
|
|
||||||
pub mod raw_async;
|
pub mod raw_async;
|
||||||
pub mod raw_async_aio;
|
|
||||||
pub mod raw_sync;
|
pub mod raw_sync;
|
||||||
pub mod vhd;
|
pub mod vhd;
|
||||||
pub mod vhdx;
|
|
||||||
pub mod vhdx_sync;
|
pub mod vhdx_sync;
|
||||||
|
|
||||||
|
use crate::async_io::{AsyncIo, AsyncIoError, AsyncIoResult};
|
||||||
|
use io_uring::{opcode, IoUring, Probe};
|
||||||
use std::alloc::{alloc_zeroed, dealloc, Layout};
|
use std::alloc::{alloc_zeroed, dealloc, Layout};
|
||||||
use std::collections::VecDeque;
|
use std::cmp;
|
||||||
use std::fmt::Debug;
|
use std::convert::TryInto;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{self, IoSlice, IoSliceMut, Read, Seek, SeekFrom, Write};
|
use std::io::{self, IoSlice, IoSliceMut, Read, Seek, SeekFrom, Write};
|
||||||
use std::os::linux::fs::MetadataExt;
|
use std::os::linux::fs::MetadataExt;
|
||||||
use std::os::unix::io::AsRawFd;
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::sync::{Arc, MutexGuard};
|
use std::result;
|
||||||
use std::time::Instant;
|
use std::sync::Arc;
|
||||||
use std::{cmp, result};
|
use std::sync::MutexGuard;
|
||||||
|
use versionize::{VersionMap, Versionize, VersionizeResult};
|
||||||
#[cfg(feature = "io_uring")]
|
use versionize_derive::Versionize;
|
||||||
use io_uring::{opcode, IoUring, Probe};
|
use virtio_bindings::bindings::virtio_blk::*;
|
||||||
use libc::{ioctl, S_IFBLK, S_IFMT};
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use smallvec::SmallVec;
|
|
||||||
use thiserror::Error;
|
|
||||||
use virtio_bindings::virtio_blk::*;
|
|
||||||
use virtio_queue::DescriptorChain;
|
use virtio_queue::DescriptorChain;
|
||||||
use vm_memory::bitmap::Bitmap;
|
|
||||||
use vm_memory::{
|
use vm_memory::{
|
||||||
ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryError, GuestMemoryLoadGuard,
|
bitmap::AtomicBitmap, bitmap::Bitmap, ByteValued, Bytes, GuestAddress, GuestMemory,
|
||||||
|
GuestMemoryError, GuestMemoryLoadGuard,
|
||||||
};
|
};
|
||||||
use vm_virtio::{AccessPlatform, Translatable};
|
use vm_virtio::{AccessPlatform, Translatable};
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
use vmm_sys_util::{aio, ioctl_io_nr, ioctl_ioc_nr};
|
|
||||||
|
|
||||||
use crate::async_io::{AsyncIo, AsyncIoError, AsyncIoResult};
|
type GuestMemoryMmap = vm_memory::GuestMemoryMmap<AtomicBitmap>;
|
||||||
use crate::vhdx::VhdxError;
|
|
||||||
|
|
||||||
const SECTOR_SHIFT: u8 = 9;
|
const SECTOR_SHIFT: u8 = 9;
|
||||||
pub const SECTOR_SIZE: u64 = 0x01 << SECTOR_SHIFT;
|
pub const SECTOR_SIZE: u64 = 0x01 << SECTOR_SHIFT;
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[error("Guest gave us bad memory addresses")]
|
/// Guest gave us bad memory addresses.
|
||||||
GuestMemory(GuestMemoryError),
|
GuestMemory(GuestMemoryError),
|
||||||
#[error("Guest gave us offsets that would have overflowed a usize")]
|
/// Guest gave us offsets that would have overflowed a usize.
|
||||||
CheckedOffset(GuestAddress, usize),
|
CheckedOffset(GuestAddress, usize),
|
||||||
#[error("Guest gave us a write only descriptor that protocol says to read from")]
|
/// Guest gave us a write only descriptor that protocol says to read from.
|
||||||
UnexpectedWriteOnlyDescriptor,
|
UnexpectedWriteOnlyDescriptor,
|
||||||
#[error("Guest gave us a read only descriptor that protocol says to write to")]
|
/// Guest gave us a read only descriptor that protocol says to write to.
|
||||||
UnexpectedReadOnlyDescriptor,
|
UnexpectedReadOnlyDescriptor,
|
||||||
#[error("Guest gave us too few descriptors in a descriptor chain")]
|
/// Guest gave us too few descriptors in a descriptor chain.
|
||||||
DescriptorChainTooShort,
|
DescriptorChainTooShort,
|
||||||
#[error("Guest gave us a descriptor that was too short to use")]
|
/// Guest gave us a descriptor that was too short to use.
|
||||||
DescriptorLengthTooSmall,
|
DescriptorLengthTooSmall,
|
||||||
#[error("Failed to detect image type: {0}")]
|
/// Getting a block's metadata fails for any reason.
|
||||||
DetectImageType(std::io::Error),
|
|
||||||
#[error("Failure in fixed vhd: {0}")]
|
|
||||||
FixedVhdError(std::io::Error),
|
|
||||||
#[error("Getting a block's metadata fails for any reason")]
|
|
||||||
GetFileMetadata,
|
GetFileMetadata,
|
||||||
#[error("The requested operation would cause a seek beyond disk end")]
|
/// The requested operation would cause a seek beyond disk end.
|
||||||
InvalidOffset,
|
InvalidOffset,
|
||||||
#[error("Failure in qcow: {0}")]
|
/// The requested operation does not support multiple descriptors.
|
||||||
QcowError(qcow::Error),
|
|
||||||
#[error("Failure in raw file: {0}")]
|
|
||||||
RawFileError(std::io::Error),
|
|
||||||
#[error("The requested operation does not support multiple descriptors")]
|
|
||||||
TooManyDescriptors,
|
TooManyDescriptors,
|
||||||
#[error("Failure in vhdx: {0}")]
|
|
||||||
VhdxError(VhdxError),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_device_id(disk_path: &Path) -> result::Result<String, Error> {
|
fn build_device_id(disk_path: &Path) -> result::Result<String, Error> {
|
||||||
@@ -111,8 +85,8 @@ fn build_device_id(disk_path: &Path) -> result::Result<String, Error> {
|
|||||||
Ok(device_id)
|
Ok(device_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build_serial(disk_path: &Path) -> Vec<u8> {
|
pub fn build_disk_image_id(disk_path: &Path) -> Vec<u8> {
|
||||||
let mut default_serial = vec![0; VIRTIO_BLK_ID_BYTES as usize];
|
let mut default_disk_image_id = vec![0; VIRTIO_BLK_ID_BYTES as usize];
|
||||||
match build_device_id(disk_path) {
|
match build_device_id(disk_path) {
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
warn!("Could not generate device id. We'll use a default.");
|
warn!("Could not generate device id. We'll use a default.");
|
||||||
@@ -122,54 +96,37 @@ pub fn build_serial(disk_path: &Path) -> Vec<u8> {
|
|||||||
// This will also zero out any leftover bytes.
|
// This will also zero out any leftover bytes.
|
||||||
let disk_id = m.as_bytes();
|
let disk_id = m.as_bytes();
|
||||||
let bytes_to_copy = cmp::min(disk_id.len(), VIRTIO_BLK_ID_BYTES as usize);
|
let bytes_to_copy = cmp::min(disk_id.len(), VIRTIO_BLK_ID_BYTES as usize);
|
||||||
default_serial[..bytes_to_copy].clone_from_slice(&disk_id[..bytes_to_copy])
|
default_disk_image_id[..bytes_to_copy].clone_from_slice(&disk_id[..bytes_to_copy])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default_serial
|
default_disk_image_id
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Debug)]
|
||||||
pub enum ExecuteError {
|
pub enum ExecuteError {
|
||||||
#[error("Bad request: {0}")]
|
|
||||||
BadRequest(Error),
|
BadRequest(Error),
|
||||||
#[error("Failed to flush: {0}")]
|
|
||||||
Flush(io::Error),
|
Flush(io::Error),
|
||||||
#[error("Failed to read: {0}")]
|
|
||||||
Read(GuestMemoryError),
|
Read(GuestMemoryError),
|
||||||
#[error("Failed to read_exact: {0}")]
|
|
||||||
ReadExact(io::Error),
|
|
||||||
#[error("Failed to seek: {0}")]
|
|
||||||
Seek(io::Error),
|
Seek(io::Error),
|
||||||
#[error("Failed to write: {0}")]
|
|
||||||
Write(GuestMemoryError),
|
Write(GuestMemoryError),
|
||||||
#[error("Failed to write_all: {0}")]
|
|
||||||
WriteAll(io::Error),
|
|
||||||
#[error("Unsupported request: {0}")]
|
|
||||||
Unsupported(u32),
|
Unsupported(u32),
|
||||||
#[error("Failed to submit io uring: {0}")]
|
|
||||||
SubmitIoUring(io::Error),
|
SubmitIoUring(io::Error),
|
||||||
#[error("Failed to get guest address: {0}")]
|
|
||||||
GetHostAddress(GuestMemoryError),
|
GetHostAddress(GuestMemoryError),
|
||||||
#[error("Failed to async read: {0}")]
|
|
||||||
AsyncRead(AsyncIoError),
|
AsyncRead(AsyncIoError),
|
||||||
#[error("Failed to async write: {0}")]
|
|
||||||
AsyncWrite(AsyncIoError),
|
AsyncWrite(AsyncIoError),
|
||||||
#[error("failed to async flush: {0}")]
|
|
||||||
AsyncFlush(AsyncIoError),
|
AsyncFlush(AsyncIoError),
|
||||||
#[error("Failed allocating a temporary buffer: {0}")]
|
/// Failed allocating a temporary buffer.
|
||||||
TemporaryBufferAllocation(io::Error),
|
TemporaryBufferAllocation(io::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ExecuteError {
|
impl ExecuteError {
|
||||||
pub fn status(&self) -> u8 {
|
pub fn status(&self) -> u32 {
|
||||||
let status = match *self {
|
match *self {
|
||||||
ExecuteError::BadRequest(_) => VIRTIO_BLK_S_IOERR,
|
ExecuteError::BadRequest(_) => VIRTIO_BLK_S_IOERR,
|
||||||
ExecuteError::Flush(_) => VIRTIO_BLK_S_IOERR,
|
ExecuteError::Flush(_) => VIRTIO_BLK_S_IOERR,
|
||||||
ExecuteError::Read(_) => VIRTIO_BLK_S_IOERR,
|
ExecuteError::Read(_) => VIRTIO_BLK_S_IOERR,
|
||||||
ExecuteError::ReadExact(_) => VIRTIO_BLK_S_IOERR,
|
|
||||||
ExecuteError::Seek(_) => VIRTIO_BLK_S_IOERR,
|
ExecuteError::Seek(_) => VIRTIO_BLK_S_IOERR,
|
||||||
ExecuteError::Write(_) => VIRTIO_BLK_S_IOERR,
|
ExecuteError::Write(_) => VIRTIO_BLK_S_IOERR,
|
||||||
ExecuteError::WriteAll(_) => VIRTIO_BLK_S_IOERR,
|
|
||||||
ExecuteError::Unsupported(_) => VIRTIO_BLK_S_UNSUPP,
|
ExecuteError::Unsupported(_) => VIRTIO_BLK_S_UNSUPP,
|
||||||
ExecuteError::SubmitIoUring(_) => VIRTIO_BLK_S_IOERR,
|
ExecuteError::SubmitIoUring(_) => VIRTIO_BLK_S_IOERR,
|
||||||
ExecuteError::GetHostAddress(_) => VIRTIO_BLK_S_IOERR,
|
ExecuteError::GetHostAddress(_) => VIRTIO_BLK_S_IOERR,
|
||||||
@@ -177,12 +134,11 @@ impl ExecuteError {
|
|||||||
ExecuteError::AsyncWrite(_) => VIRTIO_BLK_S_IOERR,
|
ExecuteError::AsyncWrite(_) => VIRTIO_BLK_S_IOERR,
|
||||||
ExecuteError::AsyncFlush(_) => VIRTIO_BLK_S_IOERR,
|
ExecuteError::AsyncFlush(_) => VIRTIO_BLK_S_IOERR,
|
||||||
ExecuteError::TemporaryBufferAllocation(_) => VIRTIO_BLK_S_IOERR,
|
ExecuteError::TemporaryBufferAllocation(_) => VIRTIO_BLK_S_IOERR,
|
||||||
};
|
}
|
||||||
status as u8
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
pub enum RequestType {
|
pub enum RequestType {
|
||||||
In,
|
In,
|
||||||
Out,
|
Out,
|
||||||
@@ -191,8 +147,8 @@ pub enum RequestType {
|
|||||||
Unsupported(u32),
|
Unsupported(u32),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn request_type<B: Bitmap + 'static>(
|
pub fn request_type(
|
||||||
mem: &vm_memory::GuestMemoryMmap<B>,
|
mem: &GuestMemoryMmap,
|
||||||
desc_addr: GuestAddress,
|
desc_addr: GuestAddress,
|
||||||
) -> result::Result<RequestType, Error> {
|
) -> result::Result<RequestType, Error> {
|
||||||
let type_ = mem.read_obj(desc_addr).map_err(Error::GuestMemory)?;
|
let type_ = mem.read_obj(desc_addr).map_err(Error::GuestMemory)?;
|
||||||
@@ -205,10 +161,7 @@ pub fn request_type<B: Bitmap + 'static>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sector<B: Bitmap + 'static>(
|
fn sector(mem: &GuestMemoryMmap, desc_addr: GuestAddress) -> result::Result<u64, Error> {
|
||||||
mem: &vm_memory::GuestMemoryMmap<B>,
|
|
||||||
desc_addr: GuestAddress,
|
|
||||||
) -> result::Result<u64, Error> {
|
|
||||||
const SECTOR_OFFSET: usize = 8;
|
const SECTOR_OFFSET: usize = 8;
|
||||||
let addr = match mem.checked_offset(desc_addr, SECTOR_OFFSET) {
|
let addr = match mem.checked_offset(desc_addr, SECTOR_OFFSET) {
|
||||||
Some(v) => v,
|
Some(v) => v,
|
||||||
@@ -218,8 +171,6 @@ fn sector<B: Bitmap + 'static>(
|
|||||||
mem.read_obj(addr).map_err(Error::GuestMemory)
|
mem.read_obj(addr).map_err(Error::GuestMemory)
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_DESCRIPTOR_VEC_SIZE: usize = 32;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct AlignedOperation {
|
pub struct AlignedOperation {
|
||||||
origin_ptr: u64,
|
origin_ptr: u64,
|
||||||
@@ -232,23 +183,23 @@ pub struct AlignedOperation {
|
|||||||
pub struct Request {
|
pub struct Request {
|
||||||
pub request_type: RequestType,
|
pub request_type: RequestType,
|
||||||
pub sector: u64,
|
pub sector: u64,
|
||||||
pub data_descriptors: SmallVec<[(GuestAddress, u32); DEFAULT_DESCRIPTOR_VEC_SIZE]>,
|
pub data_descriptors: Vec<(GuestAddress, u32)>,
|
||||||
pub status_addr: GuestAddress,
|
pub status_addr: GuestAddress,
|
||||||
pub writeback: bool,
|
pub writeback: bool,
|
||||||
pub aligned_operations: SmallVec<[AlignedOperation; DEFAULT_DESCRIPTOR_VEC_SIZE]>,
|
pub aligned_operations: Vec<AlignedOperation>,
|
||||||
pub start: Instant,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Request {
|
impl Request {
|
||||||
pub fn parse<B: Bitmap + 'static>(
|
pub fn parse(
|
||||||
desc_chain: &mut DescriptorChain<GuestMemoryLoadGuard<vm_memory::GuestMemoryMmap<B>>>,
|
desc_chain: &mut DescriptorChain<GuestMemoryLoadGuard<GuestMemoryMmap>>,
|
||||||
access_platform: Option<&Arc<dyn AccessPlatform>>,
|
access_platform: Option<&Arc<dyn AccessPlatform>>,
|
||||||
) -> result::Result<Request, Error> {
|
) -> result::Result<Request, Error> {
|
||||||
let hdr_desc = desc_chain
|
let hdr_desc = desc_chain
|
||||||
.next()
|
.next()
|
||||||
.ok_or(Error::DescriptorChainTooShort)
|
.ok_or(Error::DescriptorChainTooShort)
|
||||||
.inspect_err(|_| {
|
.map_err(|e| {
|
||||||
error!("Missing head descriptor");
|
error!("Missing head descriptor");
|
||||||
|
e
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
// The head contains the request type which MUST be readable.
|
// The head contains the request type which MUST be readable.
|
||||||
@@ -258,24 +209,24 @@ impl Request {
|
|||||||
|
|
||||||
let hdr_desc_addr = hdr_desc
|
let hdr_desc_addr = hdr_desc
|
||||||
.addr()
|
.addr()
|
||||||
.translate_gva(access_platform, hdr_desc.len() as usize);
|
.translate(access_platform, hdr_desc.len() as usize);
|
||||||
|
|
||||||
let mut req = Request {
|
let mut req = Request {
|
||||||
request_type: request_type(desc_chain.memory(), hdr_desc_addr)?,
|
request_type: request_type(desc_chain.memory(), hdr_desc_addr)?,
|
||||||
sector: sector(desc_chain.memory(), hdr_desc_addr)?,
|
sector: sector(desc_chain.memory(), hdr_desc_addr)?,
|
||||||
data_descriptors: SmallVec::with_capacity(DEFAULT_DESCRIPTOR_VEC_SIZE),
|
data_descriptors: Vec::new(),
|
||||||
status_addr: GuestAddress(0),
|
status_addr: GuestAddress(0),
|
||||||
writeback: true,
|
writeback: true,
|
||||||
aligned_operations: SmallVec::with_capacity(DEFAULT_DESCRIPTOR_VEC_SIZE),
|
aligned_operations: Vec::new(),
|
||||||
start: Instant::now(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let status_desc;
|
let status_desc;
|
||||||
let mut desc = desc_chain
|
let mut desc = desc_chain
|
||||||
.next()
|
.next()
|
||||||
.ok_or(Error::DescriptorChainTooShort)
|
.ok_or(Error::DescriptorChainTooShort)
|
||||||
.inspect_err(|_| {
|
.map_err(|e| {
|
||||||
error!("Only head descriptor present: request = {:?}", req);
|
error!("Only head descriptor present: request = {:?}", req);
|
||||||
|
e
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
if !desc.has_next() {
|
if !desc.has_next() {
|
||||||
@@ -286,7 +237,6 @@ impl Request {
|
|||||||
return Err(Error::DescriptorChainTooShort);
|
return Err(Error::DescriptorChainTooShort);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
req.data_descriptors.reserve_exact(1);
|
|
||||||
while desc.has_next() {
|
while desc.has_next() {
|
||||||
if desc.is_write_only() && req.request_type == RequestType::Out {
|
if desc.is_write_only() && req.request_type == RequestType::Out {
|
||||||
return Err(Error::UnexpectedWriteOnlyDescriptor);
|
return Err(Error::UnexpectedWriteOnlyDescriptor);
|
||||||
@@ -299,15 +249,15 @@ impl Request {
|
|||||||
}
|
}
|
||||||
|
|
||||||
req.data_descriptors.push((
|
req.data_descriptors.push((
|
||||||
desc.addr()
|
desc.addr().translate(access_platform, desc.len() as usize),
|
||||||
.translate_gva(access_platform, desc.len() as usize),
|
|
||||||
desc.len(),
|
desc.len(),
|
||||||
));
|
));
|
||||||
desc = desc_chain
|
desc = desc_chain
|
||||||
.next()
|
.next()
|
||||||
.ok_or(Error::DescriptorChainTooShort)
|
.ok_or(Error::DescriptorChainTooShort)
|
||||||
.inspect_err(|_| {
|
.map_err(|e| {
|
||||||
error!("DescriptorChain corrupted: request = {:?}", req);
|
error!("DescriptorChain corrupted: request = {:?}", req);
|
||||||
|
e
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
status_desc = desc;
|
status_desc = desc;
|
||||||
@@ -324,17 +274,17 @@ impl Request {
|
|||||||
|
|
||||||
req.status_addr = status_desc
|
req.status_addr = status_desc
|
||||||
.addr()
|
.addr()
|
||||||
.translate_gva(access_platform, status_desc.len() as usize);
|
.translate(access_platform, status_desc.len() as usize);
|
||||||
|
|
||||||
Ok(req)
|
Ok(req)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn execute<T: Seek + Read + Write, B: Bitmap + 'static>(
|
pub fn execute<T: Seek + Read + Write>(
|
||||||
&self,
|
&self,
|
||||||
disk: &mut T,
|
disk: &mut T,
|
||||||
disk_nsectors: u64,
|
disk_nsectors: u64,
|
||||||
mem: &vm_memory::GuestMemoryMmap<B>,
|
mem: &GuestMemoryMmap,
|
||||||
serial: &[u8],
|
disk_id: &[u8],
|
||||||
) -> result::Result<u32, ExecuteError> {
|
) -> result::Result<u32, ExecuteError> {
|
||||||
disk.seek(SeekFrom::Start(self.sector << SECTOR_SHIFT))
|
disk.seek(SeekFrom::Start(self.sector << SECTOR_SHIFT))
|
||||||
.map_err(ExecuteError::Seek)?;
|
.map_err(ExecuteError::Seek)?;
|
||||||
@@ -353,31 +303,23 @@ impl Request {
|
|||||||
|
|
||||||
match self.request_type {
|
match self.request_type {
|
||||||
RequestType::In => {
|
RequestType::In => {
|
||||||
let mut buf = vec![0u8; *data_len as usize];
|
mem.read_exact_from(*data_addr, disk, *data_len as usize)
|
||||||
disk.read_exact(&mut buf).map_err(ExecuteError::ReadExact)?;
|
.map_err(ExecuteError::Read)?;
|
||||||
mem.read_exact_volatile_from(
|
|
||||||
*data_addr,
|
|
||||||
&mut buf.as_slice(),
|
|
||||||
*data_len as usize,
|
|
||||||
)
|
|
||||||
.map_err(ExecuteError::Read)?;
|
|
||||||
len += data_len;
|
len += data_len;
|
||||||
}
|
}
|
||||||
RequestType::Out => {
|
RequestType::Out => {
|
||||||
let mut buf: Vec<u8> = Vec::new();
|
mem.write_all_to(*data_addr, disk, *data_len as usize)
|
||||||
mem.write_all_volatile_to(*data_addr, &mut buf, *data_len as usize)
|
|
||||||
.map_err(ExecuteError::Write)?;
|
.map_err(ExecuteError::Write)?;
|
||||||
disk.write_all(&buf).map_err(ExecuteError::WriteAll)?;
|
|
||||||
if !self.writeback {
|
if !self.writeback {
|
||||||
disk.flush().map_err(ExecuteError::Flush)?;
|
disk.flush().map_err(ExecuteError::Flush)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RequestType::Flush => disk.flush().map_err(ExecuteError::Flush)?,
|
RequestType::Flush => disk.flush().map_err(ExecuteError::Flush)?,
|
||||||
RequestType::GetDeviceId => {
|
RequestType::GetDeviceId => {
|
||||||
if (*data_len as usize) < serial.len() {
|
if (*data_len as usize) < disk_id.len() {
|
||||||
return Err(ExecuteError::BadRequest(Error::InvalidOffset));
|
return Err(ExecuteError::BadRequest(Error::InvalidOffset));
|
||||||
}
|
}
|
||||||
mem.write_slice(serial, *data_addr)
|
mem.write_slice(disk_id, *data_addr)
|
||||||
.map_err(ExecuteError::Write)?;
|
.map_err(ExecuteError::Write)?;
|
||||||
}
|
}
|
||||||
RequestType::Unsupported(t) => return Err(ExecuteError::Unsupported(t)),
|
RequestType::Unsupported(t) => return Err(ExecuteError::Unsupported(t)),
|
||||||
@@ -386,20 +328,19 @@ impl Request {
|
|||||||
Ok(len)
|
Ok(len)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn execute_async<B: Bitmap + 'static>(
|
pub fn execute_async(
|
||||||
&mut self,
|
&mut self,
|
||||||
mem: &vm_memory::GuestMemoryMmap<B>,
|
mem: &GuestMemoryMmap,
|
||||||
disk_nsectors: u64,
|
disk_nsectors: u64,
|
||||||
disk_image: &mut dyn AsyncIo,
|
disk_image: &mut dyn AsyncIo,
|
||||||
serial: &[u8],
|
disk_id: &[u8],
|
||||||
user_data: u64,
|
user_data: u64,
|
||||||
) -> result::Result<bool, ExecuteError> {
|
) -> result::Result<bool, ExecuteError> {
|
||||||
let sector = self.sector;
|
let sector = self.sector;
|
||||||
let request_type = self.request_type;
|
let request_type = self.request_type;
|
||||||
let offset = (sector << SECTOR_SHIFT) as libc::off_t;
|
let offset = (sector << SECTOR_SHIFT) as libc::off_t;
|
||||||
|
|
||||||
let mut iovecs: SmallVec<[libc::iovec; DEFAULT_DESCRIPTOR_VEC_SIZE]> =
|
let mut iovecs = Vec::new();
|
||||||
SmallVec::with_capacity(self.data_descriptors.len());
|
|
||||||
for (data_addr, data_len) in &self.data_descriptors {
|
for (data_addr, data_len) in &self.data_descriptors {
|
||||||
if *data_len == 0 {
|
if *data_len == 0 {
|
||||||
continue;
|
continue;
|
||||||
@@ -418,16 +359,16 @@ impl Request {
|
|||||||
let origin_ptr = mem
|
let origin_ptr = mem
|
||||||
.get_slice(*data_addr, *data_len as usize)
|
.get_slice(*data_addr, *data_len as usize)
|
||||||
.map_err(ExecuteError::GetHostAddress)?
|
.map_err(ExecuteError::GetHostAddress)?
|
||||||
.ptr_guard();
|
.as_ptr();
|
||||||
|
|
||||||
// Verify the buffer alignment.
|
// Verify the buffer alignment.
|
||||||
// In case it's not properly aligned, an intermediate buffer is
|
// In case it's not properly aligned, an intermediate buffer is
|
||||||
// created with the correct alignment, and a copy from/to the
|
// created with the correct alignment, and a copy from/to the
|
||||||
// origin buffer is performed, depending on the type of operation.
|
// origin buffer is performed, depending on the type of operation.
|
||||||
let iov_base = if (origin_ptr.as_ptr() as u64) % SECTOR_SIZE != 0 {
|
let iov_base = if (origin_ptr as u64) % SECTOR_SIZE != 0 {
|
||||||
let layout =
|
let layout =
|
||||||
Layout::from_size_align(*data_len as usize, SECTOR_SIZE as usize).unwrap();
|
Layout::from_size_align(*data_len as usize, SECTOR_SIZE as usize).unwrap();
|
||||||
// SAFETY: layout has non-zero size
|
// Safe because layout has non-zero size
|
||||||
let aligned_ptr = unsafe { alloc_zeroed(layout) };
|
let aligned_ptr = unsafe { alloc_zeroed(layout) };
|
||||||
if aligned_ptr.is_null() {
|
if aligned_ptr.is_null() {
|
||||||
return Err(ExecuteError::TemporaryBufferAllocation(
|
return Err(ExecuteError::TemporaryBufferAllocation(
|
||||||
@@ -438,15 +379,17 @@ impl Request {
|
|||||||
// We need to perform the copy beforehand in case we're writing
|
// We need to perform the copy beforehand in case we're writing
|
||||||
// data out.
|
// data out.
|
||||||
if request_type == RequestType::Out {
|
if request_type == RequestType::Out {
|
||||||
// SAFETY: destination buffer has been allocated with
|
// Safe because destination buffer has been allocated with
|
||||||
// the proper size.
|
// the proper size.
|
||||||
unsafe { std::ptr::copy(origin_ptr.as_ptr(), aligned_ptr, *data_len as usize) };
|
unsafe {
|
||||||
|
std::ptr::copy(origin_ptr as *const u8, aligned_ptr, *data_len as usize)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store both origin and aligned pointers for complete_async()
|
// Store both origin and aligned pointers for complete_async()
|
||||||
// to process them.
|
// to process them.
|
||||||
self.aligned_operations.push(AlignedOperation {
|
self.aligned_operations.push(AlignedOperation {
|
||||||
origin_ptr: origin_ptr.as_ptr() as u64,
|
origin_ptr: origin_ptr as u64,
|
||||||
aligned_ptr: aligned_ptr as u64,
|
aligned_ptr: aligned_ptr as u64,
|
||||||
size: *data_len as usize,
|
size: *data_len as usize,
|
||||||
layout,
|
layout,
|
||||||
@@ -454,7 +397,7 @@ impl Request {
|
|||||||
|
|
||||||
aligned_ptr as *mut libc::c_void
|
aligned_ptr as *mut libc::c_void
|
||||||
} else {
|
} else {
|
||||||
origin_ptr.as_ptr() as *mut libc::c_void
|
origin_ptr as *mut libc::c_void
|
||||||
};
|
};
|
||||||
|
|
||||||
let iovec = libc::iovec {
|
let iovec = libc::iovec {
|
||||||
@@ -474,12 +417,12 @@ impl Request {
|
|||||||
.mark_dirty(0, *data_len as usize);
|
.mark_dirty(0, *data_len as usize);
|
||||||
}
|
}
|
||||||
disk_image
|
disk_image
|
||||||
.read_vectored(offset, &iovecs, user_data)
|
.read_vectored(offset, iovecs, user_data)
|
||||||
.map_err(ExecuteError::AsyncRead)?;
|
.map_err(ExecuteError::AsyncRead)?;
|
||||||
}
|
}
|
||||||
RequestType::Out => {
|
RequestType::Out => {
|
||||||
disk_image
|
disk_image
|
||||||
.write_vectored(offset, &iovecs, user_data)
|
.write_vectored(offset, iovecs, user_data)
|
||||||
.map_err(ExecuteError::AsyncWrite)?;
|
.map_err(ExecuteError::AsyncWrite)?;
|
||||||
}
|
}
|
||||||
RequestType::Flush => {
|
RequestType::Flush => {
|
||||||
@@ -493,10 +436,10 @@ impl Request {
|
|||||||
} else {
|
} else {
|
||||||
return Err(ExecuteError::BadRequest(Error::TooManyDescriptors));
|
return Err(ExecuteError::BadRequest(Error::TooManyDescriptors));
|
||||||
};
|
};
|
||||||
if (data_len as usize) < serial.len() {
|
if (data_len as usize) < disk_id.len() {
|
||||||
return Err(ExecuteError::BadRequest(Error::InvalidOffset));
|
return Err(ExecuteError::BadRequest(Error::InvalidOffset));
|
||||||
}
|
}
|
||||||
mem.write_slice(serial, data_addr)
|
mem.write_slice(disk_id, data_addr)
|
||||||
.map_err(ExecuteError::Write)?;
|
.map_err(ExecuteError::Write)?;
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
@@ -511,7 +454,7 @@ impl Request {
|
|||||||
// We need to perform the copy after the data has been read inside
|
// We need to perform the copy after the data has been read inside
|
||||||
// the aligned buffer in case we're reading data in.
|
// the aligned buffer in case we're reading data in.
|
||||||
if self.request_type == RequestType::In {
|
if self.request_type == RequestType::In {
|
||||||
// SAFETY: origin buffer has been allocated with the
|
// Safe because origin buffer has been allocated with the
|
||||||
// proper size.
|
// proper size.
|
||||||
unsafe {
|
unsafe {
|
||||||
std::ptr::copy(
|
std::ptr::copy(
|
||||||
@@ -523,7 +466,7 @@ impl Request {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Free the temporary aligned buffer.
|
// Free the temporary aligned buffer.
|
||||||
// SAFETY: aligned_ptr was allocated by alloc_zeroed with the same
|
// Safe because aligned_ptr was allocated by alloc_zeroed with the same
|
||||||
// layout
|
// layout
|
||||||
unsafe {
|
unsafe {
|
||||||
dealloc(
|
dealloc(
|
||||||
@@ -541,7 +484,7 @@ impl Request {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize)]
|
#[derive(Copy, Clone, Debug, Default, Versionize)]
|
||||||
#[repr(C, packed)]
|
#[repr(C, packed)]
|
||||||
pub struct VirtioBlockConfig {
|
pub struct VirtioBlockConfig {
|
||||||
pub capacity: u64,
|
pub capacity: u64,
|
||||||
@@ -564,7 +507,7 @@ pub struct VirtioBlockConfig {
|
|||||||
pub write_zeroes_may_unmap: u8,
|
pub write_zeroes_may_unmap: u8,
|
||||||
pub unused1: [u8; 3],
|
pub unused1: [u8; 3],
|
||||||
}
|
}
|
||||||
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize)]
|
#[derive(Copy, Clone, Debug, Default, Versionize)]
|
||||||
#[repr(C, packed)]
|
#[repr(C, packed)]
|
||||||
pub struct VirtioBlockGeometry {
|
pub struct VirtioBlockGeometry {
|
||||||
pub cylinders: u16,
|
pub cylinders: u16,
|
||||||
@@ -572,72 +515,57 @@ pub struct VirtioBlockGeometry {
|
|||||||
pub sectors: u8,
|
pub sectors: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
// SAFETY: data structure only contain a series of integers
|
// SAFETY: these data structures only contain a series of integers
|
||||||
unsafe impl ByteValued for VirtioBlockConfig {}
|
unsafe impl ByteValued for VirtioBlockConfig {}
|
||||||
// SAFETY: data structure only contain a series of integers
|
|
||||||
unsafe impl ByteValued for VirtioBlockGeometry {}
|
unsafe impl ByteValued for VirtioBlockGeometry {}
|
||||||
|
|
||||||
/// Check if aio can be used on the current system.
|
|
||||||
pub fn block_aio_is_supported() -> bool {
|
|
||||||
aio::IoContext::new(1).is_ok()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check if io_uring for block device can be used on the current system, as
|
/// Check if io_uring for block device can be used on the current system, as
|
||||||
/// it correctly supports the expected io_uring features.
|
/// it correctly supports the expected io_uring features.
|
||||||
pub fn block_io_uring_is_supported() -> bool {
|
pub fn block_io_uring_is_supported() -> bool {
|
||||||
#[cfg(not(feature = "io_uring"))]
|
let error_msg = "io_uring not supported:";
|
||||||
{
|
|
||||||
info!("io_uring is disabled by crate features");
|
// Check we can create an io_uring instance, which effectively verifies
|
||||||
false
|
// that io_uring_setup() syscall is supported.
|
||||||
|
let io_uring = match IoUring::new(1) {
|
||||||
|
Ok(io_uring) => io_uring,
|
||||||
|
Err(e) => {
|
||||||
|
info!("{} failed to create io_uring instance: {}", error_msg, e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let submitter = io_uring.submitter();
|
||||||
|
|
||||||
|
let mut probe = Probe::new();
|
||||||
|
|
||||||
|
// Check we can register a probe to validate supported operations.
|
||||||
|
match submitter.register_probe(&mut probe) {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(e) => {
|
||||||
|
info!("{} failed to register a probe: {}", error_msg, e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "io_uring")]
|
// Check IORING_OP_FSYNC is supported
|
||||||
{
|
if !probe.is_supported(opcode::Fsync::CODE) {
|
||||||
let error_msg = "io_uring not supported:";
|
info!("{} IORING_OP_FSYNC operation not supported", error_msg);
|
||||||
|
return false;
|
||||||
// Check we can create an io_uring instance, which effectively verifies
|
|
||||||
// that io_uring_setup() syscall is supported.
|
|
||||||
let io_uring = match IoUring::new(1) {
|
|
||||||
Ok(io_uring) => io_uring,
|
|
||||||
Err(e) => {
|
|
||||||
info!("{} failed to create io_uring instance: {}", error_msg, e);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let submitter = io_uring.submitter();
|
|
||||||
|
|
||||||
let mut probe = Probe::new();
|
|
||||||
|
|
||||||
// Check we can register a probe to validate supported operations.
|
|
||||||
match submitter.register_probe(&mut probe) {
|
|
||||||
Ok(_) => {}
|
|
||||||
Err(e) => {
|
|
||||||
info!("{} failed to register a probe: {}", error_msg, e);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check IORING_OP_FSYNC is supported
|
|
||||||
if !probe.is_supported(opcode::Fsync::CODE) {
|
|
||||||
info!("{} IORING_OP_FSYNC operation not supported", error_msg);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check IORING_OP_READV is supported
|
|
||||||
if !probe.is_supported(opcode::Readv::CODE) {
|
|
||||||
info!("{} IORING_OP_READV operation not supported", error_msg);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check IORING_OP_WRITEV is supported
|
|
||||||
if !probe.is_supported(opcode::Writev::CODE) {
|
|
||||||
info!("{} IORING_OP_WRITEV operation not supported", error_msg);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check IORING_OP_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
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait AsyncAdaptor<F>
|
pub trait AsyncAdaptor<F>
|
||||||
@@ -647,19 +575,15 @@ where
|
|||||||
fn read_vectored_sync(
|
fn read_vectored_sync(
|
||||||
&mut self,
|
&mut self,
|
||||||
offset: libc::off_t,
|
offset: libc::off_t,
|
||||||
iovecs: &[libc::iovec],
|
iovecs: Vec<libc::iovec>,
|
||||||
user_data: u64,
|
user_data: u64,
|
||||||
eventfd: &EventFd,
|
eventfd: &EventFd,
|
||||||
completion_list: &mut VecDeque<(u64, i32)>,
|
completion_list: &mut Vec<(u64, i32)>,
|
||||||
) -> AsyncIoResult<()> {
|
) -> AsyncIoResult<()> {
|
||||||
// Convert libc::iovec into IoSliceMut
|
// Convert libc::iovec into IoSliceMut
|
||||||
let mut slices: SmallVec<[IoSliceMut; DEFAULT_DESCRIPTOR_VEC_SIZE]> =
|
let mut slices = Vec::new();
|
||||||
SmallVec::with_capacity(iovecs.len());
|
|
||||||
for iovec in iovecs.iter() {
|
for iovec in iovecs.iter() {
|
||||||
// SAFETY: on Linux IoSliceMut wraps around libc::iovec
|
slices.push(IoSliceMut::new(unsafe { std::mem::transmute(*iovec) }));
|
||||||
slices.push(IoSliceMut::new(unsafe {
|
|
||||||
std::mem::transmute::<libc::iovec, &mut [u8]>(*iovec)
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = {
|
let result = {
|
||||||
@@ -669,14 +593,12 @@ where
|
|||||||
file.seek(SeekFrom::Start(offset as u64))
|
file.seek(SeekFrom::Start(offset as u64))
|
||||||
.map_err(AsyncIoError::ReadVectored)?;
|
.map_err(AsyncIoError::ReadVectored)?;
|
||||||
|
|
||||||
let mut r = 0;
|
// Read vectored
|
||||||
for b in slices.iter_mut() {
|
file.read_vectored(slices.as_mut_slice())
|
||||||
r += file.read(b).map_err(AsyncIoError::ReadVectored)?;
|
.map_err(AsyncIoError::ReadVectored)?
|
||||||
}
|
|
||||||
r
|
|
||||||
};
|
};
|
||||||
|
|
||||||
completion_list.push_back((user_data, result as i32));
|
completion_list.push((user_data, result as i32));
|
||||||
eventfd.write(1).unwrap();
|
eventfd.write(1).unwrap();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -685,19 +607,15 @@ where
|
|||||||
fn write_vectored_sync(
|
fn write_vectored_sync(
|
||||||
&mut self,
|
&mut self,
|
||||||
offset: libc::off_t,
|
offset: libc::off_t,
|
||||||
iovecs: &[libc::iovec],
|
iovecs: Vec<libc::iovec>,
|
||||||
user_data: u64,
|
user_data: u64,
|
||||||
eventfd: &EventFd,
|
eventfd: &EventFd,
|
||||||
completion_list: &mut VecDeque<(u64, i32)>,
|
completion_list: &mut Vec<(u64, i32)>,
|
||||||
) -> AsyncIoResult<()> {
|
) -> AsyncIoResult<()> {
|
||||||
// Convert libc::iovec into IoSlice
|
// Convert libc::iovec into IoSlice
|
||||||
let mut slices: SmallVec<[IoSlice; DEFAULT_DESCRIPTOR_VEC_SIZE]> =
|
let mut slices = Vec::new();
|
||||||
SmallVec::with_capacity(iovecs.len());
|
|
||||||
for iovec in iovecs.iter() {
|
for iovec in iovecs.iter() {
|
||||||
// SAFETY: on Linux IoSlice wraps around libc::iovec
|
slices.push(IoSlice::new(unsafe { std::mem::transmute(*iovec) }));
|
||||||
slices.push(IoSlice::new(unsafe {
|
|
||||||
std::mem::transmute::<libc::iovec, &mut [u8]>(*iovec)
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = {
|
let result = {
|
||||||
@@ -707,14 +625,12 @@ where
|
|||||||
file.seek(SeekFrom::Start(offset as u64))
|
file.seek(SeekFrom::Start(offset as u64))
|
||||||
.map_err(AsyncIoError::WriteVectored)?;
|
.map_err(AsyncIoError::WriteVectored)?;
|
||||||
|
|
||||||
let mut r = 0;
|
// Write vectored
|
||||||
for b in slices.iter() {
|
file.write_vectored(slices.as_slice())
|
||||||
r += file.write(b).map_err(AsyncIoError::WriteVectored)?;
|
.map_err(AsyncIoError::WriteVectored)?
|
||||||
}
|
|
||||||
r
|
|
||||||
};
|
};
|
||||||
|
|
||||||
completion_list.push_back((user_data, result as i32));
|
completion_list.push((user_data, result as i32));
|
||||||
eventfd.write(1).unwrap();
|
eventfd.write(1).unwrap();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -724,7 +640,7 @@ where
|
|||||||
&mut self,
|
&mut self,
|
||||||
user_data: Option<u64>,
|
user_data: Option<u64>,
|
||||||
eventfd: &EventFd,
|
eventfd: &EventFd,
|
||||||
completion_list: &mut VecDeque<(u64, i32)>,
|
completion_list: &mut Vec<(u64, i32)>,
|
||||||
) -> AsyncIoResult<()> {
|
) -> AsyncIoResult<()> {
|
||||||
let result: i32 = {
|
let result: i32 = {
|
||||||
let mut file = self.file();
|
let mut file = self.file();
|
||||||
@@ -736,7 +652,7 @@ where
|
|||||||
};
|
};
|
||||||
|
|
||||||
if let Some(user_data) = user_data {
|
if let Some(user_data) = user_data {
|
||||||
completion_list.push_back((user_data, result));
|
completion_list.push((user_data, result));
|
||||||
eventfd.write(1).unwrap();
|
eventfd.write(1).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -756,33 +672,24 @@ pub enum ImageType {
|
|||||||
const QCOW_MAGIC: u32 = 0x5146_49fb;
|
const QCOW_MAGIC: u32 = 0x5146_49fb;
|
||||||
const VHDX_SIGN: u64 = 0x656C_6966_7864_6876;
|
const VHDX_SIGN: u64 = 0x656C_6966_7864_6876;
|
||||||
|
|
||||||
/// Read a block into memory aligned by the source block size (needed for O_DIRECT)
|
|
||||||
pub fn read_aligned_block_size(f: &mut File) -> std::io::Result<Vec<u8>> {
|
|
||||||
let blocksize = DiskTopology::probe(f)?.logical_block_size as usize;
|
|
||||||
// SAFETY: We are allocating memory that is naturally aligned (size = alignment) and we meet
|
|
||||||
// requirements for safety from Vec::from_raw_parts() as we are using the global allocator
|
|
||||||
// and transferring ownership of the memory.
|
|
||||||
let mut data = unsafe {
|
|
||||||
Vec::from_raw_parts(
|
|
||||||
alloc_zeroed(Layout::from_size_align_unchecked(blocksize, blocksize)),
|
|
||||||
blocksize,
|
|
||||||
blocksize,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
f.read_exact(&mut data)?;
|
|
||||||
Ok(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Determine image type through file parsing.
|
/// Determine image type through file parsing.
|
||||||
pub fn detect_image_type(f: &mut File) -> std::io::Result<ImageType> {
|
pub fn detect_image_type(f: &mut File) -> std::io::Result<ImageType> {
|
||||||
let block = read_aligned_block_size(f)?;
|
// We must create a buffer aligned on 512 bytes with a size being a
|
||||||
|
// multiple of 512 bytes as the file might be opened with O_DIRECT flag.
|
||||||
|
#[repr(align(512))]
|
||||||
|
struct Sector {
|
||||||
|
data: [u8; 512],
|
||||||
|
}
|
||||||
|
let mut s = Sector { data: [0; 512] };
|
||||||
|
|
||||||
|
f.read_exact(&mut s.data)?;
|
||||||
|
|
||||||
// Check 4 first bytes to get the header value and determine the image type
|
// Check 4 first bytes to get the header value and determine the image type
|
||||||
let image_type = if u32::from_be_bytes(block[0..4].try_into().unwrap()) == QCOW_MAGIC {
|
let image_type = if u32::from_be_bytes(s.data[0..4].try_into().unwrap()) == QCOW_MAGIC {
|
||||||
ImageType::Qcow2
|
ImageType::Qcow2
|
||||||
} else if vhd::is_fixed_vhd(f)? {
|
} else if vhd::is_fixed_vhd(f)? {
|
||||||
ImageType::FixedVhd
|
ImageType::FixedVhd
|
||||||
} else if u64::from_le_bytes(block[0..8].try_into().unwrap()) == VHDX_SIGN {
|
} else if u64::from_le_bytes(s.data[0..8].try_into().unwrap()) == VHDX_SIGN {
|
||||||
ImageType::Vhdx
|
ImageType::Vhdx
|
||||||
} else {
|
} else {
|
||||||
ImageType::Raw
|
ImageType::Raw
|
||||||
@@ -790,89 +697,3 @@ pub fn detect_image_type(f: &mut File) -> std::io::Result<ImageType> {
|
|||||||
|
|
||||||
Ok(image_type)
|
Ok(image_type)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait BlockBackend: Read + Write + Seek + Send + Debug {
|
|
||||||
fn size(&self) -> Result<u64, Error>;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct DiskTopology {
|
|
||||||
pub logical_block_size: u64,
|
|
||||||
pub physical_block_size: u64,
|
|
||||||
pub minimum_io_size: u64,
|
|
||||||
pub optimal_io_size: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for DiskTopology {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
logical_block_size: 512,
|
|
||||||
physical_block_size: 512,
|
|
||||||
minimum_io_size: 512,
|
|
||||||
optimal_io_size: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ioctl_io_nr!(BLKSSZGET, 0x12, 104);
|
|
||||||
ioctl_io_nr!(BLKPBSZGET, 0x12, 123);
|
|
||||||
ioctl_io_nr!(BLKIOMIN, 0x12, 120);
|
|
||||||
ioctl_io_nr!(BLKIOOPT, 0x12, 121);
|
|
||||||
|
|
||||||
enum BlockSize {
|
|
||||||
LogicalBlock,
|
|
||||||
PhysicalBlock,
|
|
||||||
MinimumIo,
|
|
||||||
OptimalIo,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DiskTopology {
|
|
||||||
fn is_block_device(f: &File) -> std::io::Result<bool> {
|
|
||||||
let mut stat = std::mem::MaybeUninit::<libc::stat>::uninit();
|
|
||||||
// SAFETY: FFI call with a valid fd and buffer
|
|
||||||
let ret = unsafe { libc::fstat(f.as_raw_fd(), stat.as_mut_ptr()) };
|
|
||||||
if ret != 0 {
|
|
||||||
return Err(std::io::Error::last_os_error());
|
|
||||||
}
|
|
||||||
|
|
||||||
// SAFETY: stat is valid at this point
|
|
||||||
let is_block = unsafe { (*stat.as_ptr()).st_mode & S_IFMT == S_IFBLK };
|
|
||||||
Ok(is_block)
|
|
||||||
}
|
|
||||||
|
|
||||||
// libc::ioctl() takes different types on different architectures
|
|
||||||
fn query_block_size(f: &File, block_size_type: BlockSize) -> std::io::Result<u64> {
|
|
||||||
let mut block_size = 0;
|
|
||||||
// SAFETY: FFI call with correct arguments
|
|
||||||
let ret = unsafe {
|
|
||||||
ioctl(
|
|
||||||
f.as_raw_fd(),
|
|
||||||
match block_size_type {
|
|
||||||
BlockSize::LogicalBlock => BLKSSZGET(),
|
|
||||||
BlockSize::PhysicalBlock => BLKPBSZGET(),
|
|
||||||
BlockSize::MinimumIo => BLKIOMIN(),
|
|
||||||
BlockSize::OptimalIo => BLKIOOPT(),
|
|
||||||
} as _,
|
|
||||||
&mut block_size,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
if ret != 0 {
|
|
||||||
return Err(std::io::Error::last_os_error());
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(block_size)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn probe(f: &File) -> std::io::Result<Self> {
|
|
||||||
if !Self::is_block_device(f)? {
|
|
||||||
return Ok(DiskTopology::default());
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(DiskTopology {
|
|
||||||
logical_block_size: Self::query_block_size(f, BlockSize::LogicalBlock)?,
|
|
||||||
physical_block_size: Self::query_block_size(f, BlockSize::PhysicalBlock)?,
|
|
||||||
minimum_io_size: Self::query_block_size(f, BlockSize::MinimumIo)?,
|
|
||||||
optimal_io_size: Self::query_block_size(f, BlockSize::OptimalIo)?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,17 +2,14 @@
|
|||||||
//
|
//
|
||||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||||
|
|
||||||
use std::collections::VecDeque;
|
use crate::async_io::{AsyncIo, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult};
|
||||||
|
use crate::AsyncAdaptor;
|
||||||
|
use qcow::{QcowFile, RawFile, Result as QcowResult};
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{Seek, SeekFrom};
|
use std::io::{Seek, SeekFrom};
|
||||||
use std::sync::{Arc, Mutex, MutexGuard};
|
use std::sync::{Arc, Mutex, MutexGuard};
|
||||||
|
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
use crate::async_io::{AsyncIo, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult};
|
|
||||||
use crate::qcow::{QcowFile, RawFile, Result as QcowResult};
|
|
||||||
use crate::AsyncAdaptor;
|
|
||||||
|
|
||||||
pub struct QcowDiskSync {
|
pub struct QcowDiskSync {
|
||||||
qcow_file: Arc<Mutex<QcowFile>>,
|
qcow_file: Arc<Mutex<QcowFile>>,
|
||||||
}
|
}
|
||||||
@@ -29,7 +26,7 @@ impl DiskFile for QcowDiskSync {
|
|||||||
fn size(&mut self) -> DiskFileResult<u64> {
|
fn size(&mut self) -> DiskFileResult<u64> {
|
||||||
let mut file = self.qcow_file.lock().unwrap();
|
let mut file = self.qcow_file.lock().unwrap();
|
||||||
|
|
||||||
file.seek(SeekFrom::End(0)).map_err(DiskFileError::Size)
|
Ok(file.seek(SeekFrom::End(0)).map_err(DiskFileError::Size)? as u64)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_async_io(&self, _ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
|
fn new_async_io(&self, _ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
|
||||||
@@ -40,7 +37,7 @@ impl DiskFile for QcowDiskSync {
|
|||||||
pub struct QcowSync {
|
pub struct QcowSync {
|
||||||
qcow_file: Arc<Mutex<QcowFile>>,
|
qcow_file: Arc<Mutex<QcowFile>>,
|
||||||
eventfd: EventFd,
|
eventfd: EventFd,
|
||||||
completion_list: VecDeque<(u64, i32)>,
|
completion_list: Vec<(u64, i32)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl QcowSync {
|
impl QcowSync {
|
||||||
@@ -49,7 +46,7 @@ impl QcowSync {
|
|||||||
qcow_file,
|
qcow_file,
|
||||||
eventfd: EventFd::new(libc::EFD_NONBLOCK)
|
eventfd: EventFd::new(libc::EFD_NONBLOCK)
|
||||||
.expect("Failed creating EventFd for QcowSync"),
|
.expect("Failed creating EventFd for QcowSync"),
|
||||||
completion_list: VecDeque::new(),
|
completion_list: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -68,7 +65,7 @@ impl AsyncIo for QcowSync {
|
|||||||
fn read_vectored(
|
fn read_vectored(
|
||||||
&mut self,
|
&mut self,
|
||||||
offset: libc::off_t,
|
offset: libc::off_t,
|
||||||
iovecs: &[libc::iovec],
|
iovecs: Vec<libc::iovec>,
|
||||||
user_data: u64,
|
user_data: u64,
|
||||||
) -> AsyncIoResult<()> {
|
) -> AsyncIoResult<()> {
|
||||||
self.qcow_file.read_vectored_sync(
|
self.qcow_file.read_vectored_sync(
|
||||||
@@ -83,7 +80,7 @@ impl AsyncIo for QcowSync {
|
|||||||
fn write_vectored(
|
fn write_vectored(
|
||||||
&mut self,
|
&mut self,
|
||||||
offset: libc::off_t,
|
offset: libc::off_t,
|
||||||
iovecs: &[libc::iovec],
|
iovecs: Vec<libc::iovec>,
|
||||||
user_data: u64,
|
user_data: u64,
|
||||||
) -> AsyncIoResult<()> {
|
) -> AsyncIoResult<()> {
|
||||||
self.qcow_file.write_vectored_sync(
|
self.qcow_file.write_vectored_sync(
|
||||||
@@ -100,7 +97,7 @@ impl AsyncIo for QcowSync {
|
|||||||
.fsync_sync(user_data, &self.eventfd, &mut self.completion_list)
|
.fsync_sync(user_data, &self.eventfd, &mut self.completion_list)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn next_completed_request(&mut self) -> Option<(u64, i32)> {
|
fn complete(&mut self) -> Vec<(u64, i32)> {
|
||||||
self.completion_list.pop_front()
|
self.completion_list.drain(..).collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,17 +2,14 @@
|
|||||||
//
|
//
|
||||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||||
|
|
||||||
use std::fs::File;
|
|
||||||
use std::io::{Error, Seek, SeekFrom};
|
|
||||||
use std::os::unix::io::{AsRawFd, RawFd};
|
|
||||||
|
|
||||||
use io_uring::{opcode, types, IoUring};
|
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
|
||||||
|
|
||||||
use crate::async_io::{
|
use crate::async_io::{
|
||||||
AsyncIo, AsyncIoError, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult,
|
AsyncIo, AsyncIoError, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult, DiskTopology,
|
||||||
};
|
};
|
||||||
use crate::DiskTopology;
|
use io_uring::{opcode, squeue, types, IoUring};
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::{Seek, SeekFrom};
|
||||||
|
use std::os::unix::io::{AsRawFd, RawFd};
|
||||||
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
pub struct RawFileDisk {
|
pub struct RawFileDisk {
|
||||||
file: File,
|
file: File,
|
||||||
@@ -26,9 +23,10 @@ impl RawFileDisk {
|
|||||||
|
|
||||||
impl DiskFile for RawFileDisk {
|
impl DiskFile for RawFileDisk {
|
||||||
fn size(&mut self) -> DiskFileResult<u64> {
|
fn size(&mut self) -> DiskFileResult<u64> {
|
||||||
self.file
|
Ok(self
|
||||||
|
.file
|
||||||
.seek(SeekFrom::End(0))
|
.seek(SeekFrom::End(0))
|
||||||
.map_err(DiskFileError::Size)
|
.map_err(DiskFileError::Size)? as u64)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_async_io(&self, ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
|
fn new_async_io(&self, ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
|
||||||
@@ -39,7 +37,7 @@ impl DiskFile for RawFileDisk {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn topology(&mut self) -> DiskTopology {
|
fn topology(&mut self) -> DiskTopology {
|
||||||
if let Ok(topology) = DiskTopology::probe(&self.file) {
|
if let Ok(topology) = DiskTopology::probe(&mut self.file) {
|
||||||
topology
|
topology
|
||||||
} else {
|
} else {
|
||||||
warn!("Unable to get device topology. Using default topology");
|
warn!("Unable to get device topology. Using default topology");
|
||||||
@@ -79,21 +77,21 @@ impl AsyncIo for RawFileAsync {
|
|||||||
fn read_vectored(
|
fn read_vectored(
|
||||||
&mut self,
|
&mut self,
|
||||||
offset: libc::off_t,
|
offset: libc::off_t,
|
||||||
iovecs: &[libc::iovec],
|
iovecs: Vec<libc::iovec>,
|
||||||
user_data: u64,
|
user_data: u64,
|
||||||
) -> AsyncIoResult<()> {
|
) -> AsyncIoResult<()> {
|
||||||
let (submitter, mut sq, _) = self.io_uring.split();
|
let (submitter, mut sq, _) = self.io_uring.split();
|
||||||
|
|
||||||
// SAFETY: we know the file descriptor is valid and we
|
// Safe because we know the file descriptor is valid and we
|
||||||
// relied on vm-memory to provide the buffer address.
|
// relied on vm-memory to provide the buffer address.
|
||||||
unsafe {
|
let _ = unsafe {
|
||||||
sq.push(
|
sq.push(
|
||||||
&opcode::Readv::new(types::Fd(self.fd), iovecs.as_ptr(), iovecs.len() as u32)
|
&opcode::Readv::new(types::Fd(self.fd), iovecs.as_ptr(), iovecs.len() as u32)
|
||||||
.offset(offset.try_into().unwrap())
|
.offset(offset)
|
||||||
.build()
|
.build()
|
||||||
|
.flags(squeue::Flags::ASYNC)
|
||||||
.user_data(user_data),
|
.user_data(user_data),
|
||||||
)
|
)
|
||||||
.map_err(|_| AsyncIoError::ReadVectored(Error::other("Submission queue is full")))?
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Update the submission queue and submit new operations to the
|
// Update the submission queue and submit new operations to the
|
||||||
@@ -107,21 +105,21 @@ impl AsyncIo for RawFileAsync {
|
|||||||
fn write_vectored(
|
fn write_vectored(
|
||||||
&mut self,
|
&mut self,
|
||||||
offset: libc::off_t,
|
offset: libc::off_t,
|
||||||
iovecs: &[libc::iovec],
|
iovecs: Vec<libc::iovec>,
|
||||||
user_data: u64,
|
user_data: u64,
|
||||||
) -> AsyncIoResult<()> {
|
) -> AsyncIoResult<()> {
|
||||||
let (submitter, mut sq, _) = self.io_uring.split();
|
let (submitter, mut sq, _) = self.io_uring.split();
|
||||||
|
|
||||||
// SAFETY: we know the file descriptor is valid and we
|
// Safe because we know the file descriptor is valid and we
|
||||||
// relied on vm-memory to provide the buffer address.
|
// relied on vm-memory to provide the buffer address.
|
||||||
unsafe {
|
let _ = unsafe {
|
||||||
sq.push(
|
sq.push(
|
||||||
&opcode::Writev::new(types::Fd(self.fd), iovecs.as_ptr(), iovecs.len() as u32)
|
&opcode::Writev::new(types::Fd(self.fd), iovecs.as_ptr(), iovecs.len() as u32)
|
||||||
.offset(offset.try_into().unwrap())
|
.offset(offset)
|
||||||
.build()
|
.build()
|
||||||
|
.flags(squeue::Flags::ASYNC)
|
||||||
.user_data(user_data),
|
.user_data(user_data),
|
||||||
)
|
)
|
||||||
.map_err(|_| AsyncIoError::WriteVectored(Error::other("Submission queue is full")))?
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Update the submission queue and submit new operations to the
|
// Update the submission queue and submit new operations to the
|
||||||
@@ -136,14 +134,14 @@ impl AsyncIo for RawFileAsync {
|
|||||||
if let Some(user_data) = user_data {
|
if let Some(user_data) = user_data {
|
||||||
let (submitter, mut sq, _) = self.io_uring.split();
|
let (submitter, mut sq, _) = self.io_uring.split();
|
||||||
|
|
||||||
// SAFETY: we know the file descriptor is valid.
|
// Safe because we know the file descriptor is valid.
|
||||||
unsafe {
|
let _ = unsafe {
|
||||||
sq.push(
|
sq.push(
|
||||||
&opcode::Fsync::new(types::Fd(self.fd))
|
&opcode::Fsync::new(types::Fd(self.fd))
|
||||||
.build()
|
.build()
|
||||||
|
.flags(squeue::Flags::ASYNC)
|
||||||
.user_data(user_data),
|
.user_data(user_data),
|
||||||
)
|
)
|
||||||
.map_err(|_| AsyncIoError::Fsync(Error::other("Submission queue is full")))?
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Update the submission queue and submit new operations to the
|
// Update the submission queue and submit new operations to the
|
||||||
@@ -151,17 +149,20 @@ impl AsyncIo for RawFileAsync {
|
|||||||
sq.sync();
|
sq.sync();
|
||||||
submitter.submit().map_err(AsyncIoError::Fsync)?;
|
submitter.submit().map_err(AsyncIoError::Fsync)?;
|
||||||
} else {
|
} else {
|
||||||
// SAFETY: FFI call with a valid fd
|
|
||||||
unsafe { libc::fsync(self.fd) };
|
unsafe { libc::fsync(self.fd) };
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn next_completed_request(&mut self) -> Option<(u64, i32)> {
|
fn complete(&mut self) -> Vec<(u64, i32)> {
|
||||||
self.io_uring
|
let mut completion_list = Vec::new();
|
||||||
.completion()
|
|
||||||
.next()
|
let cq = self.io_uring.completion();
|
||||||
.map(|entry| (entry.user_data(), entry.result()))
|
for cq_entry in cq {
|
||||||
|
completion_list.push((cq_entry.user_data(), cq_entry.result()));
|
||||||
|
}
|
||||||
|
|
||||||
|
completion_list
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,18 +2,14 @@
|
|||||||
//
|
//
|
||||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||||
|
|
||||||
use std::collections::VecDeque;
|
use crate::async_io::{
|
||||||
|
AsyncIo, AsyncIoError, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult, DiskTopology,
|
||||||
|
};
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{Seek, SeekFrom};
|
use std::io::{Seek, SeekFrom};
|
||||||
use std::os::unix::io::{AsRawFd, RawFd};
|
use std::os::unix::io::{AsRawFd, RawFd};
|
||||||
|
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
use crate::async_io::{
|
|
||||||
AsyncIo, AsyncIoError, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult,
|
|
||||||
};
|
|
||||||
use crate::DiskTopology;
|
|
||||||
|
|
||||||
pub struct RawFileDiskSync {
|
pub struct RawFileDiskSync {
|
||||||
file: File,
|
file: File,
|
||||||
}
|
}
|
||||||
@@ -26,9 +22,10 @@ impl RawFileDiskSync {
|
|||||||
|
|
||||||
impl DiskFile for RawFileDiskSync {
|
impl DiskFile for RawFileDiskSync {
|
||||||
fn size(&mut self) -> DiskFileResult<u64> {
|
fn size(&mut self) -> DiskFileResult<u64> {
|
||||||
self.file
|
Ok(self
|
||||||
|
.file
|
||||||
.seek(SeekFrom::End(0))
|
.seek(SeekFrom::End(0))
|
||||||
.map_err(DiskFileError::Size)
|
.map_err(DiskFileError::Size)? as u64)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_async_io(&self, _ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
|
fn new_async_io(&self, _ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
|
||||||
@@ -36,7 +33,7 @@ impl DiskFile for RawFileDiskSync {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn topology(&mut self) -> DiskTopology {
|
fn topology(&mut self) -> DiskTopology {
|
||||||
if let Ok(topology) = DiskTopology::probe(&self.file) {
|
if let Ok(topology) = DiskTopology::probe(&mut self.file) {
|
||||||
topology
|
topology
|
||||||
} else {
|
} else {
|
||||||
warn!("Unable to get device topology. Using default topology");
|
warn!("Unable to get device topology. Using default topology");
|
||||||
@@ -48,7 +45,7 @@ impl DiskFile for RawFileDiskSync {
|
|||||||
pub struct RawFileSync {
|
pub struct RawFileSync {
|
||||||
fd: RawFd,
|
fd: RawFd,
|
||||||
eventfd: EventFd,
|
eventfd: EventFd,
|
||||||
completion_list: VecDeque<(u64, i32)>,
|
completion_list: Vec<(u64, i32)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RawFileSync {
|
impl RawFileSync {
|
||||||
@@ -56,7 +53,7 @@ impl RawFileSync {
|
|||||||
RawFileSync {
|
RawFileSync {
|
||||||
fd,
|
fd,
|
||||||
eventfd: EventFd::new(libc::EFD_NONBLOCK).expect("Failed creating EventFd for RawFile"),
|
eventfd: EventFd::new(libc::EFD_NONBLOCK).expect("Failed creating EventFd for RawFile"),
|
||||||
completion_list: VecDeque::new(),
|
completion_list: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -69,14 +66,13 @@ impl AsyncIo for RawFileSync {
|
|||||||
fn read_vectored(
|
fn read_vectored(
|
||||||
&mut self,
|
&mut self,
|
||||||
offset: libc::off_t,
|
offset: libc::off_t,
|
||||||
iovecs: &[libc::iovec],
|
iovecs: Vec<libc::iovec>,
|
||||||
user_data: u64,
|
user_data: u64,
|
||||||
) -> AsyncIoResult<()> {
|
) -> AsyncIoResult<()> {
|
||||||
// SAFETY: FFI call with valid arguments
|
|
||||||
let result = unsafe {
|
let result = unsafe {
|
||||||
libc::preadv(
|
libc::preadv(
|
||||||
self.fd as libc::c_int,
|
self.fd as libc::c_int,
|
||||||
iovecs.as_ptr(),
|
iovecs.as_ptr() as *const libc::iovec,
|
||||||
iovecs.len() as libc::c_int,
|
iovecs.len() as libc::c_int,
|
||||||
offset,
|
offset,
|
||||||
)
|
)
|
||||||
@@ -85,7 +81,7 @@ impl AsyncIo for RawFileSync {
|
|||||||
return Err(AsyncIoError::ReadVectored(std::io::Error::last_os_error()));
|
return Err(AsyncIoError::ReadVectored(std::io::Error::last_os_error()));
|
||||||
}
|
}
|
||||||
|
|
||||||
self.completion_list.push_back((user_data, result as i32));
|
self.completion_list.push((user_data, result as i32));
|
||||||
self.eventfd.write(1).unwrap();
|
self.eventfd.write(1).unwrap();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -94,14 +90,13 @@ impl AsyncIo for RawFileSync {
|
|||||||
fn write_vectored(
|
fn write_vectored(
|
||||||
&mut self,
|
&mut self,
|
||||||
offset: libc::off_t,
|
offset: libc::off_t,
|
||||||
iovecs: &[libc::iovec],
|
iovecs: Vec<libc::iovec>,
|
||||||
user_data: u64,
|
user_data: u64,
|
||||||
) -> AsyncIoResult<()> {
|
) -> AsyncIoResult<()> {
|
||||||
// SAFETY: FFI call with valid arguments
|
|
||||||
let result = unsafe {
|
let result = unsafe {
|
||||||
libc::pwritev(
|
libc::pwritev(
|
||||||
self.fd as libc::c_int,
|
self.fd as libc::c_int,
|
||||||
iovecs.as_ptr(),
|
iovecs.as_ptr() as *const libc::iovec,
|
||||||
iovecs.len() as libc::c_int,
|
iovecs.len() as libc::c_int,
|
||||||
offset,
|
offset,
|
||||||
)
|
)
|
||||||
@@ -110,28 +105,27 @@ impl AsyncIo for RawFileSync {
|
|||||||
return Err(AsyncIoError::WriteVectored(std::io::Error::last_os_error()));
|
return Err(AsyncIoError::WriteVectored(std::io::Error::last_os_error()));
|
||||||
}
|
}
|
||||||
|
|
||||||
self.completion_list.push_back((user_data, result as i32));
|
self.completion_list.push((user_data, result as i32));
|
||||||
self.eventfd.write(1).unwrap();
|
self.eventfd.write(1).unwrap();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fsync(&mut self, user_data: Option<u64>) -> AsyncIoResult<()> {
|
fn fsync(&mut self, user_data: Option<u64>) -> AsyncIoResult<()> {
|
||||||
// SAFETY: FFI call
|
|
||||||
let result = unsafe { libc::fsync(self.fd as libc::c_int) };
|
let result = unsafe { libc::fsync(self.fd as libc::c_int) };
|
||||||
if result < 0 {
|
if result < 0 {
|
||||||
return Err(AsyncIoError::Fsync(std::io::Error::last_os_error()));
|
return Err(AsyncIoError::Fsync(std::io::Error::last_os_error()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(user_data) = user_data {
|
if let Some(user_data) = user_data {
|
||||||
self.completion_list.push_back((user_data, result));
|
self.completion_list.push((user_data, result as i32));
|
||||||
self.eventfd.write(1).unwrap();
|
self.eventfd.write(1).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn next_completed_request(&mut self) -> Option<(u64, i32)> {
|
fn complete(&mut self) -> Vec<(u64, i32)> {
|
||||||
self.completion_list.pop_front()
|
self.completion_list.drain(..).collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,10 +2,9 @@
|
|||||||
//
|
//
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use std::convert::TryInto;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{Seek, SeekFrom};
|
use std::io::{Read, Seek, SeekFrom};
|
||||||
|
|
||||||
use crate::{read_aligned_block_size, DiskTopology};
|
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub struct VhdFooter {
|
pub struct VhdFooter {
|
||||||
@@ -28,33 +27,37 @@ pub struct VhdFooter {
|
|||||||
|
|
||||||
impl VhdFooter {
|
impl VhdFooter {
|
||||||
pub fn new(file: &mut File) -> std::io::Result<VhdFooter> {
|
pub fn new(file: &mut File) -> std::io::Result<VhdFooter> {
|
||||||
let blocksize = DiskTopology::probe(file)?.logical_block_size as usize;
|
// We must create a buffer aligned on 512 bytes with a size being a
|
||||||
|
// multiple of 512 bytes as the file might be opened with O_DIRECT flag.
|
||||||
|
#[repr(align(512))]
|
||||||
|
struct Sector {
|
||||||
|
data: [u8; 512],
|
||||||
|
}
|
||||||
|
let mut s = Sector { data: [0; 512] };
|
||||||
|
|
||||||
// Place the cursor in the last block of the file
|
// Place the cursor 512 bytes before the end of the file, as this is
|
||||||
file.seek(SeekFrom::End(0 - (blocksize as i64)))?;
|
// where the footer starts.
|
||||||
// Read in the last block
|
file.seek(SeekFrom::End(-512))?;
|
||||||
let data = read_aligned_block_size(file)?;
|
|
||||||
|
|
||||||
// We only care about the last sector
|
// Fill in the VhdFooter structure
|
||||||
let offset = blocksize - 512;
|
file.read_exact(&mut s.data)?;
|
||||||
let sector = &data[offset..];
|
|
||||||
|
|
||||||
Ok(VhdFooter {
|
Ok(VhdFooter {
|
||||||
cookie: u64::from_be_bytes(sector[0..8].try_into().unwrap()),
|
cookie: u64::from_be_bytes(s.data[0..8].try_into().unwrap()),
|
||||||
features: u32::from_be_bytes(sector[8..12].try_into().unwrap()),
|
features: u32::from_be_bytes(s.data[8..12].try_into().unwrap()),
|
||||||
file_format_version: u32::from_be_bytes(sector[12..16].try_into().unwrap()),
|
file_format_version: u32::from_be_bytes(s.data[12..16].try_into().unwrap()),
|
||||||
data_offset: u64::from_be_bytes(sector[16..24].try_into().unwrap()),
|
data_offset: u64::from_be_bytes(s.data[16..24].try_into().unwrap()),
|
||||||
time_stamp: u32::from_be_bytes(sector[24..28].try_into().unwrap()),
|
time_stamp: u32::from_be_bytes(s.data[24..28].try_into().unwrap()),
|
||||||
creator_application: u32::from_be_bytes(sector[28..32].try_into().unwrap()),
|
creator_application: u32::from_be_bytes(s.data[28..32].try_into().unwrap()),
|
||||||
creator_version: u32::from_be_bytes(sector[32..36].try_into().unwrap()),
|
creator_version: u32::from_be_bytes(s.data[32..36].try_into().unwrap()),
|
||||||
creator_host_os: u32::from_be_bytes(sector[36..40].try_into().unwrap()),
|
creator_host_os: u32::from_be_bytes(s.data[36..40].try_into().unwrap()),
|
||||||
original_size: u64::from_be_bytes(sector[40..48].try_into().unwrap()),
|
original_size: u64::from_be_bytes(s.data[40..48].try_into().unwrap()),
|
||||||
current_size: u64::from_be_bytes(sector[48..56].try_into().unwrap()),
|
current_size: u64::from_be_bytes(s.data[48..56].try_into().unwrap()),
|
||||||
disk_geometry: u32::from_be_bytes(sector[56..60].try_into().unwrap()),
|
disk_geometry: u32::from_be_bytes(s.data[56..60].try_into().unwrap()),
|
||||||
disk_type: u32::from_be_bytes(sector[60..64].try_into().unwrap()),
|
disk_type: u32::from_be_bytes(s.data[60..64].try_into().unwrap()),
|
||||||
checksum: u32::from_be_bytes(sector[64..68].try_into().unwrap()),
|
checksum: u32::from_be_bytes(s.data[64..68].try_into().unwrap()),
|
||||||
unique_id: u128::from_be_bytes(sector[68..84].try_into().unwrap()),
|
unique_id: u128::from_be_bytes(s.data[68..84].try_into().unwrap()),
|
||||||
saved_state: u8::from_be_bytes(sector[84..85].try_into().unwrap()),
|
saved_state: u8::from_be_bytes(s.data[84..85].try_into().unwrap()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,13 +121,11 @@ pub fn is_fixed_vhd(f: &mut File) -> std::io::Result<bool> {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use super::{is_fixed_vhd, VhdFooter};
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{Seek, SeekFrom, Write};
|
use std::io::{Seek, SeekFrom, Write};
|
||||||
|
|
||||||
use vmm_sys_util::tempfile::TempFile;
|
use vmm_sys_util::tempfile::TempFile;
|
||||||
|
|
||||||
use super::{is_fixed_vhd, VhdFooter};
|
|
||||||
|
|
||||||
fn valid_fixed_vhd_footer() -> Vec<u8> {
|
fn valid_fixed_vhd_footer() -> Vec<u8> {
|
||||||
vec![
|
vec![
|
||||||
0x63, 0x6f, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x78, // cookie
|
0x63, 0x6f, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x78, // cookie
|
||||||
@@ -2,16 +2,13 @@
|
|||||||
//
|
//
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use std::collections::VecDeque;
|
use crate::async_io::{AsyncIo, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult};
|
||||||
|
use crate::AsyncAdaptor;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::sync::{Arc, Mutex, MutexGuard};
|
use std::sync::{Arc, Mutex, MutexGuard};
|
||||||
|
use vhdx::vhdx::{Result as VhdxResult, Vhdx};
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
use crate::async_io::{AsyncIo, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult};
|
|
||||||
use crate::vhdx::{Result as VhdxResult, Vhdx};
|
|
||||||
use crate::AsyncAdaptor;
|
|
||||||
|
|
||||||
pub struct VhdxDiskSync {
|
pub struct VhdxDiskSync {
|
||||||
vhdx_file: Arc<Mutex<Vhdx>>,
|
vhdx_file: Arc<Mutex<Vhdx>>,
|
||||||
}
|
}
|
||||||
@@ -40,7 +37,7 @@ impl DiskFile for VhdxDiskSync {
|
|||||||
pub struct VhdxSync {
|
pub struct VhdxSync {
|
||||||
vhdx_file: Arc<Mutex<Vhdx>>,
|
vhdx_file: Arc<Mutex<Vhdx>>,
|
||||||
eventfd: EventFd,
|
eventfd: EventFd,
|
||||||
completion_list: VecDeque<(u64, i32)>,
|
completion_list: Vec<(u64, i32)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VhdxSync {
|
impl VhdxSync {
|
||||||
@@ -48,7 +45,7 @@ impl VhdxSync {
|
|||||||
Ok(VhdxSync {
|
Ok(VhdxSync {
|
||||||
vhdx_file,
|
vhdx_file,
|
||||||
eventfd: EventFd::new(libc::EFD_NONBLOCK)?,
|
eventfd: EventFd::new(libc::EFD_NONBLOCK)?,
|
||||||
completion_list: VecDeque::new(),
|
completion_list: Vec::new(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -67,7 +64,7 @@ impl AsyncIo for VhdxSync {
|
|||||||
fn read_vectored(
|
fn read_vectored(
|
||||||
&mut self,
|
&mut self,
|
||||||
offset: libc::off_t,
|
offset: libc::off_t,
|
||||||
iovecs: &[libc::iovec],
|
iovecs: Vec<libc::iovec>,
|
||||||
user_data: u64,
|
user_data: u64,
|
||||||
) -> AsyncIoResult<()> {
|
) -> AsyncIoResult<()> {
|
||||||
self.vhdx_file.read_vectored_sync(
|
self.vhdx_file.read_vectored_sync(
|
||||||
@@ -82,7 +79,7 @@ impl AsyncIo for VhdxSync {
|
|||||||
fn write_vectored(
|
fn write_vectored(
|
||||||
&mut self,
|
&mut self,
|
||||||
offset: libc::off_t,
|
offset: libc::off_t,
|
||||||
iovecs: &[libc::iovec],
|
iovecs: Vec<libc::iovec>,
|
||||||
user_data: u64,
|
user_data: u64,
|
||||||
) -> AsyncIoResult<()> {
|
) -> AsyncIoResult<()> {
|
||||||
self.vhdx_file.write_vectored_sync(
|
self.vhdx_file.write_vectored_sync(
|
||||||
@@ -99,7 +96,7 @@ impl AsyncIo for VhdxSync {
|
|||||||
.fsync_sync(user_data, &self.eventfd, &mut self.completion_list)
|
.fsync_sync(user_data, &self.eventfd, &mut self.completion_list)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn next_completed_request(&mut self) -> Option<(u64, i32)> {
|
fn complete(&mut self) -> Vec<(u64, i32)> {
|
||||||
self.completion_list.pop_front()
|
self.completion_list.drain(..).collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
20
build.rs
20
build.rs
@@ -3,31 +3,25 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
//
|
//
|
||||||
|
|
||||||
use std::env;
|
#[macro_use(crate_version)]
|
||||||
|
extern crate clap;
|
||||||
|
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut version = "v".to_owned() + env!("CARGO_PKG_VERSION");
|
let mut version = "v".to_owned() + crate_version!();
|
||||||
|
|
||||||
if let Ok(git_out) = Command::new("git").args(["describe", "--dirty"]).output() {
|
if let Ok(git_out) = Command::new("git").args(&["describe", "--dirty"]).output() {
|
||||||
if git_out.status.success() {
|
if git_out.status.success() {
|
||||||
if let Ok(git_out_str) = String::from_utf8(git_out.stdout) {
|
if let Ok(git_out_str) = String::from_utf8(git_out.stdout) {
|
||||||
version = git_out_str;
|
version = git_out_str;
|
||||||
// Pop the trailing newline.
|
|
||||||
version.pop();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Append CH_EXTRA_VERSION to version if it is set.
|
|
||||||
if let Ok(extra_version) = env::var("CH_EXTRA_VERSION") {
|
|
||||||
println!("cargo:rerun-if-env-changed=CH_EXTRA_VERSION");
|
|
||||||
version.push_str(&format!("-{}", extra_version));
|
|
||||||
}
|
|
||||||
|
|
||||||
// This println!() has a special behavior, as it will set the environment
|
// This println!() has a special behavior, as it will set the environment
|
||||||
// variable BUILD_VERSION, so that it can be reused from the binary.
|
// variable BUILT_VERSION, so that it can be reused from the binary.
|
||||||
// Particularly, this is used from src/main.rs to display the exact
|
// Particularly, this is used from src/main.rs to display the exact
|
||||||
// version.
|
// version.
|
||||||
println!("cargo:rustc-env=BUILD_VERSION={version}");
|
println!("cargo:rustc-env=BUILT_VERSION={}", version);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,38 +1,27 @@
|
|||||||
[package]
|
[package]
|
||||||
authors = ["The Chromium OS Authors"]
|
|
||||||
edition = "2021"
|
|
||||||
name = "devices"
|
name = "devices"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
authors = ["The Chromium OS Authors"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
acpi_tables = { workspace = true }
|
acpi_tables = { path = "../acpi_tables", optional = true }
|
||||||
anyhow = "1.0.94"
|
anyhow = "1.0.55"
|
||||||
arch = { path = "../arch" }
|
arch = { path = "../arch" }
|
||||||
bitflags = "2.6.0"
|
bitflags = "1.3.2"
|
||||||
byteorder = "1.5.0"
|
byteorder = "1.4.3"
|
||||||
event_monitor = { path = "../event_monitor" }
|
epoll = "4.3.1"
|
||||||
hypervisor = { path = "../hypervisor" }
|
libc = "0.2.119"
|
||||||
libc = "0.2.167"
|
log = "0.4.14"
|
||||||
log = "0.4.22"
|
versionize = "0.1.6"
|
||||||
num_enum = "0.7.2"
|
versionize_derive = "0.1.4"
|
||||||
pci = { path = "../pci" }
|
|
||||||
serde = { version = "1.0.208", features = ["derive"] }
|
|
||||||
thiserror = "2.0.6"
|
|
||||||
tpm = { path = "../tpm" }
|
|
||||||
vm-allocator = { path = "../vm-allocator" }
|
|
||||||
vm-device = { path = "../vm-device" }
|
vm-device = { path = "../vm-device" }
|
||||||
vm-memory = { workspace = true, features = [
|
vm-memory = "0.7.0"
|
||||||
"backend-atomic",
|
|
||||||
"backend-bitmap",
|
|
||||||
"backend-mmap",
|
|
||||||
] }
|
|
||||||
vm-migration = { path = "../vm-migration" }
|
vm-migration = { path = "../vm-migration" }
|
||||||
vmm-sys-util = { workspace = true }
|
vmm-sys-util = "0.9.0"
|
||||||
|
|
||||||
[target.'cfg(any(target_arch = "aarch64", target_arch = "riscv64"))'.dependencies]
|
|
||||||
arch = { path = "../arch" }
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
kvm = ["arch/kvm"]
|
acpi = ["acpi_tables"]
|
||||||
pvmemcontrol = []
|
cmos = []
|
||||||
|
fwdebug = []
|
||||||
|
|||||||
@@ -3,39 +3,29 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
//
|
//
|
||||||
|
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use super::AcpiNotificationFlags;
|
||||||
|
use acpi_tables::{aml, aml::Aml};
|
||||||
use std::sync::{Arc, Barrier};
|
use std::sync::{Arc, Barrier};
|
||||||
use std::thread;
|
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
use acpi_tables::{aml, Aml, AmlSink};
|
|
||||||
use vm_device::interrupt::InterruptSourceGroup;
|
use vm_device::interrupt::InterruptSourceGroup;
|
||||||
use vm_device::BusDevice;
|
use vm_device::BusDevice;
|
||||||
use vm_memory::GuestAddress;
|
use vm_memory::GuestAddress;
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
use super::AcpiNotificationFlags;
|
|
||||||
|
|
||||||
pub const GED_DEVICE_ACPI_SIZE: usize = 0x1;
|
pub const GED_DEVICE_ACPI_SIZE: usize = 0x1;
|
||||||
|
|
||||||
/// A device for handling ACPI shutdown and reboot
|
/// A device for handling ACPI shutdown and reboot
|
||||||
pub struct AcpiShutdownDevice {
|
pub struct AcpiShutdownDevice {
|
||||||
exit_evt: EventFd,
|
exit_evt: EventFd,
|
||||||
reset_evt: EventFd,
|
reset_evt: EventFd,
|
||||||
vcpus_kill_signalled: Arc<AtomicBool>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AcpiShutdownDevice {
|
impl AcpiShutdownDevice {
|
||||||
/// Constructs a device that will signal the given event when the guest requests it.
|
/// Constructs a device that will signal the given event when the guest requests it.
|
||||||
pub fn new(
|
pub fn new(exit_evt: EventFd, reset_evt: EventFd) -> AcpiShutdownDevice {
|
||||||
exit_evt: EventFd,
|
|
||||||
reset_evt: EventFd,
|
|
||||||
vcpus_kill_signalled: Arc<AtomicBool>,
|
|
||||||
) -> AcpiShutdownDevice {
|
|
||||||
AcpiShutdownDevice {
|
AcpiShutdownDevice {
|
||||||
exit_evt,
|
exit_evt,
|
||||||
reset_evt,
|
reset_evt,
|
||||||
vcpus_kill_signalled,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -53,13 +43,6 @@ impl BusDevice for AcpiShutdownDevice {
|
|||||||
if let Err(e) = self.reset_evt.write(1) {
|
if let Err(e) = self.reset_evt.write(1) {
|
||||||
error!("Error triggering ACPI reset event: {}", e);
|
error!("Error triggering ACPI reset event: {}", e);
|
||||||
}
|
}
|
||||||
// Spin until we are sure the reset_evt has been handled and that when
|
|
||||||
// we return from the KVM_RUN we will exit rather than re-enter the guest.
|
|
||||||
while !self.vcpus_kill_signalled.load(Ordering::SeqCst) {
|
|
||||||
// This is more effective than thread::yield_now() at
|
|
||||||
// avoiding a priority inversion with the VMM thread
|
|
||||||
thread::sleep(std::time::Duration::from_millis(1));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// The ACPI DSDT table specifies the S5 sleep state (shutdown) as value 5
|
// The ACPI DSDT table specifies the S5 sleep state (shutdown) as value 5
|
||||||
const S5_SLEEP_VALUE: u8 = 5;
|
const S5_SLEEP_VALUE: u8 = 5;
|
||||||
@@ -70,13 +53,6 @@ impl BusDevice for AcpiShutdownDevice {
|
|||||||
if let Err(e) = self.exit_evt.write(1) {
|
if let Err(e) = self.exit_evt.write(1) {
|
||||||
error!("Error triggering ACPI shutdown event: {}", e);
|
error!("Error triggering ACPI shutdown event: {}", e);
|
||||||
}
|
}
|
||||||
// Spin until we are sure the reset_evt has been handled and that when
|
|
||||||
// we return from the KVM_RUN we will exit rather than re-enter the guest.
|
|
||||||
while !self.vcpus_kill_signalled.load(Ordering::SeqCst) {
|
|
||||||
// This is more effective than thread::yield_now() at
|
|
||||||
// avoiding a priority inversion with the VMM thread
|
|
||||||
thread::sleep(std::time::Duration::from_millis(1));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@@ -126,39 +102,39 @@ impl BusDevice for AcpiGedDevice {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "acpi")]
|
||||||
impl Aml for AcpiGedDevice {
|
impl Aml for AcpiGedDevice {
|
||||||
fn to_aml_bytes(&self, sink: &mut dyn AmlSink) {
|
fn append_aml_bytes(&self, bytes: &mut Vec<u8>) {
|
||||||
aml::Device::new(
|
aml::Device::new(
|
||||||
"_SB_.GEC_".into(),
|
"_SB_.GED_".into(),
|
||||||
vec![
|
vec![
|
||||||
&aml::Name::new("_HID".into(), &aml::EISAName::new("PNP0A06")),
|
&aml::Name::new("_HID".into(), &"ACPI0013"),
|
||||||
&aml::Name::new("_UID".into(), &"Generic Event Controller"),
|
&aml::Name::new("_UID".into(), &aml::ZERO),
|
||||||
&aml::Name::new(
|
&aml::Name::new(
|
||||||
"_CRS".into(),
|
"_CRS".into(),
|
||||||
&aml::ResourceTemplate::new(vec![&aml::AddressSpace::new_memory(
|
&aml::ResourceTemplate::new(vec![&aml::Interrupt::new(
|
||||||
aml::AddressSpaceCacheable::NotCacheable,
|
|
||||||
true,
|
true,
|
||||||
self.address.0,
|
true,
|
||||||
self.address.0 + GED_DEVICE_ACPI_SIZE as u64 - 1,
|
false,
|
||||||
None,
|
false,
|
||||||
|
self.ged_irq,
|
||||||
)]),
|
)]),
|
||||||
),
|
),
|
||||||
&aml::OpRegion::new(
|
&aml::OpRegion::new(
|
||||||
"GDST".into(),
|
"GDST".into(),
|
||||||
aml::OpRegionSpace::SystemMemory,
|
aml::OpRegionSpace::SystemMemory,
|
||||||
&(self.address.0 as usize),
|
self.address.0 as usize,
|
||||||
&GED_DEVICE_ACPI_SIZE,
|
GED_DEVICE_ACPI_SIZE,
|
||||||
),
|
),
|
||||||
&aml::Field::new(
|
&aml::Field::new(
|
||||||
"GDST".into(),
|
"GDST".into(),
|
||||||
aml::FieldAccessType::Byte,
|
aml::FieldAccessType::Byte,
|
||||||
aml::FieldLockRule::NoLock,
|
|
||||||
aml::FieldUpdateRule::WriteAsZeroes,
|
aml::FieldUpdateRule::WriteAsZeroes,
|
||||||
vec![aml::FieldEntry::Named(*b"GDAT", 8)],
|
vec![aml::FieldEntry::Named(*b"GDAT", 8)],
|
||||||
),
|
),
|
||||||
&aml::Method::new(
|
&aml::Method::new(
|
||||||
"ESCN".into(),
|
"_EVT".into(),
|
||||||
0,
|
1,
|
||||||
true,
|
true,
|
||||||
vec![
|
vec![
|
||||||
&aml::Store::new(&aml::Local(0), &aml::Path::new("GDAT")),
|
&aml::Store::new(&aml::Local(0), &aml::Path::new("GDAT")),
|
||||||
@@ -189,31 +165,7 @@ impl Aml for AcpiGedDevice {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
.to_aml_bytes(sink);
|
.append_aml_bytes(bytes)
|
||||||
aml::Device::new(
|
|
||||||
"_SB_.GED_".into(),
|
|
||||||
vec![
|
|
||||||
&aml::Name::new("_HID".into(), &"ACPI0013"),
|
|
||||||
&aml::Name::new("_UID".into(), &aml::ZERO),
|
|
||||||
&aml::Name::new(
|
|
||||||
"_CRS".into(),
|
|
||||||
&aml::ResourceTemplate::new(vec![&aml::Interrupt::new(
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
self.ged_irq,
|
|
||||||
)]),
|
|
||||||
),
|
|
||||||
&aml::Method::new(
|
|
||||||
"_EVT".into(),
|
|
||||||
1,
|
|
||||||
true,
|
|
||||||
vec![&aml::MethodCall::new("\\_SB_.GEC_.ESCN".into(), vec![])],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
.to_aml_bytes(sink)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,144 +0,0 @@
|
|||||||
// Copyright © 2024 Institute of Software, CAS. All rights reserved.
|
|
||||||
// Copyright 2020, ARM Limited.
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
|
||||||
|
|
||||||
use super::interrupt_controller::{Error, InterruptController};
|
|
||||||
extern crate arch;
|
|
||||||
use std::result;
|
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
|
|
||||||
use arch::layout;
|
|
||||||
use hypervisor::arch::riscv64::aia::{Vaia, VaiaConfig};
|
|
||||||
use hypervisor::{AiaState, CpuState};
|
|
||||||
use vm_device::interrupt::{
|
|
||||||
InterruptIndex, InterruptManager, InterruptSourceConfig, InterruptSourceGroup,
|
|
||||||
LegacyIrqSourceConfig, MsiIrqGroupConfig,
|
|
||||||
};
|
|
||||||
use vm_memory::address::Address;
|
|
||||||
use vm_migration::{Migratable, Pausable, Snapshottable, Transportable};
|
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
|
||||||
|
|
||||||
type Result<T> = result::Result<T, Error>;
|
|
||||||
|
|
||||||
// Reserve 32 IRQs for legacy devices.
|
|
||||||
pub const IRQ_LEGACY_BASE: usize = layout::IRQ_BASE as usize;
|
|
||||||
pub const IRQ_LEGACY_COUNT: usize = 32;
|
|
||||||
// TODO: AIA snapshotting is not yet completed.
|
|
||||||
pub const _AIA_SNAPSHOT_ID: &str = "";
|
|
||||||
|
|
||||||
// Aia (Advance Interrupt Architecture) struct provides all the functionality of a
|
|
||||||
// AIA device. It wraps a hypervisor-emulated AIA device (Vaia) provided by the
|
|
||||||
// `hypervisor` crate.
|
|
||||||
// Aia struct also implements InterruptController to provide interrupt delivery
|
|
||||||
// service.
|
|
||||||
pub struct Aia {
|
|
||||||
interrupt_source_group: Arc<dyn InterruptSourceGroup>,
|
|
||||||
// The hypervisor agnostic virtual AIA
|
|
||||||
vaia: Arc<Mutex<dyn Vaia>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Aia {
|
|
||||||
pub fn new(
|
|
||||||
vcpu_count: u8,
|
|
||||||
interrupt_manager: Arc<dyn InterruptManager<GroupConfig = MsiIrqGroupConfig>>,
|
|
||||||
vm: Arc<dyn hypervisor::Vm>,
|
|
||||||
) -> Result<Aia> {
|
|
||||||
let interrupt_source_group = interrupt_manager
|
|
||||||
.create_group(MsiIrqGroupConfig {
|
|
||||||
base: IRQ_LEGACY_BASE as InterruptIndex,
|
|
||||||
count: IRQ_LEGACY_COUNT as InterruptIndex,
|
|
||||||
})
|
|
||||||
.map_err(Error::CreateInterruptSourceGroup)?;
|
|
||||||
|
|
||||||
let vaia = vm
|
|
||||||
.create_vaia(Aia::create_default_config(vcpu_count as u64))
|
|
||||||
.map_err(Error::CreateAia)?;
|
|
||||||
|
|
||||||
let aia = Aia {
|
|
||||||
interrupt_source_group,
|
|
||||||
vaia,
|
|
||||||
};
|
|
||||||
aia.enable()?;
|
|
||||||
|
|
||||||
Ok(aia)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn restore_vaia(
|
|
||||||
&mut self,
|
|
||||||
state: Option<AiaState>,
|
|
||||||
_saved_vcpu_states: &[CpuState],
|
|
||||||
) -> Result<()> {
|
|
||||||
self.vaia
|
|
||||||
.clone()
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.set_state(&state.unwrap())
|
|
||||||
.map_err(Error::RestoreAia)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn enable(&self) -> Result<()> {
|
|
||||||
// Set irqfd for legacy interrupts
|
|
||||||
self.interrupt_source_group
|
|
||||||
.enable()
|
|
||||||
.map_err(Error::EnableInterrupt)?;
|
|
||||||
|
|
||||||
// Set irq_routing for legacy interrupts.
|
|
||||||
// irqchip: Hardcode to 0 as we support only 1 APLIC
|
|
||||||
// pin: Use irq number as pin
|
|
||||||
for i in IRQ_LEGACY_BASE..(IRQ_LEGACY_BASE + IRQ_LEGACY_COUNT) {
|
|
||||||
let config = LegacyIrqSourceConfig {
|
|
||||||
irqchip: 0,
|
|
||||||
pin: (i - IRQ_LEGACY_BASE) as u32,
|
|
||||||
};
|
|
||||||
self.interrupt_source_group
|
|
||||||
.update(
|
|
||||||
i as InterruptIndex,
|
|
||||||
InterruptSourceConfig::LegacyIrq(config),
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
.map_err(Error::EnableInterrupt)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.interrupt_source_group
|
|
||||||
.set_gsi()
|
|
||||||
.map_err(Error::EnableInterrupt)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Default config implied by arch::layout
|
|
||||||
pub fn create_default_config(vcpu_count: u64) -> VaiaConfig {
|
|
||||||
VaiaConfig {
|
|
||||||
vcpu_count: vcpu_count as u32,
|
|
||||||
aplic_addr: layout::APLIC_START.raw_value(),
|
|
||||||
imsic_addr: layout::IMSIC_START.raw_value(),
|
|
||||||
nr_irqs: layout::IRQ_NUM,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_vaia(&mut self) -> Result<Arc<Mutex<dyn Vaia>>> {
|
|
||||||
Ok(self.vaia.clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InterruptController for Aia {
|
|
||||||
// This should be called anytime an interrupt needs to be injected into the
|
|
||||||
// running guest.
|
|
||||||
fn service_irq(&mut self, irq: usize) -> Result<()> {
|
|
||||||
self.interrupt_source_group
|
|
||||||
.trigger(irq as InterruptIndex)
|
|
||||||
.map_err(Error::TriggerInterrupt)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn notifier(&self, irq: usize) -> Option<EventFd> {
|
|
||||||
self.interrupt_source_group.notifier(irq as InterruptIndex)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Snapshottable for Aia {}
|
|
||||||
impl Pausable for Aia {}
|
|
||||||
impl Transportable for Aia {}
|
|
||||||
impl Migratable for Aia {}
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
// Copyright © 2023 Cyberus Technology
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
//
|
|
||||||
|
|
||||||
//! Module for [`DebugconState`].
|
|
||||||
|
|
||||||
use std::io;
|
|
||||||
use std::io::Write;
|
|
||||||
use std::sync::{Arc, Barrier};
|
|
||||||
|
|
||||||
use vm_device::BusDevice;
|
|
||||||
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
|
|
||||||
|
|
||||||
/// I/O-port.
|
|
||||||
pub const DEFAULT_PORT: u64 = 0xe9;
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct DebugconState {}
|
|
||||||
|
|
||||||
/// Emulates a debug console similar to the QEMU debugcon device. This device
|
|
||||||
/// is stateless and only prints the bytes (usually text) that are written to
|
|
||||||
/// it.
|
|
||||||
///
|
|
||||||
/// This device is only available on x86.
|
|
||||||
///
|
|
||||||
/// Reference:
|
|
||||||
/// - https://github.com/qemu/qemu/blob/master/hw/char/debugcon.c
|
|
||||||
/// - https://phip1611.de/blog/how-to-use-qemus-debugcon-feature-and-write-to-a-file/
|
|
||||||
pub struct DebugConsole {
|
|
||||||
id: String,
|
|
||||||
out: Box<dyn io::Write + Send>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DebugConsole {
|
|
||||||
pub fn new(id: String, out: Box<dyn io::Write + Send>) -> Self {
|
|
||||||
Self { id, out }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BusDevice for DebugConsole {
|
|
||||||
fn read(&mut self, _base: u64, _offset: u64, _data: &mut [u8]) {}
|
|
||||||
|
|
||||||
fn write(&mut self, _base: u64, _offset: u64, data: &[u8]) -> Option<Arc<Barrier>> {
|
|
||||||
if let Err(e) = self.out.write_all(data) {
|
|
||||||
// unlikely
|
|
||||||
error!("debug-console: failed writing data: {e:?}");
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Snapshottable for DebugConsole {
|
|
||||||
fn id(&self) -> String {
|
|
||||||
self.id.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn snapshot(&mut self) -> Result<Snapshot, MigratableError> {
|
|
||||||
Snapshot::new_from_state(&())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Pausable for DebugConsole {}
|
|
||||||
impl Transportable for DebugConsole {}
|
|
||||||
impl Migratable for DebugConsole {}
|
|
||||||
@@ -4,44 +4,36 @@
|
|||||||
|
|
||||||
use super::interrupt_controller::{Error, InterruptController};
|
use super::interrupt_controller::{Error, InterruptController};
|
||||||
extern crate arch;
|
extern crate arch;
|
||||||
|
use arch::aarch64::gic::GicDevice;
|
||||||
use std::result;
|
use std::result;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use anyhow::anyhow;
|
|
||||||
use arch::layout;
|
|
||||||
use hypervisor::arch::aarch64::gic::{Vgic, VgicConfig};
|
|
||||||
use hypervisor::{CpuState, GicState};
|
|
||||||
use vm_device::interrupt::{
|
use vm_device::interrupt::{
|
||||||
InterruptIndex, InterruptManager, InterruptSourceConfig, InterruptSourceGroup,
|
InterruptIndex, InterruptManager, InterruptSourceConfig, InterruptSourceGroup,
|
||||||
LegacyIrqSourceConfig, MsiIrqGroupConfig,
|
LegacyIrqSourceConfig, MsiIrqGroupConfig,
|
||||||
};
|
};
|
||||||
use vm_memory::address::Address;
|
|
||||||
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
|
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
type Result<T> = result::Result<T, Error>;
|
type Result<T> = result::Result<T, Error>;
|
||||||
|
|
||||||
// Reserve 32 IRQs for legacy devices.
|
// Reserve 32 IRQs for legacy device.
|
||||||
pub const IRQ_LEGACY_BASE: usize = layout::IRQ_BASE as usize;
|
pub const IRQ_LEGACY_BASE: usize = arch::layout::IRQ_BASE as usize;
|
||||||
pub const IRQ_LEGACY_COUNT: usize = 32;
|
pub const IRQ_LEGACY_COUNT: usize = 32;
|
||||||
pub const GIC_SNAPSHOT_ID: &str = "gic-v3-its";
|
|
||||||
|
|
||||||
// Gic (Generic Interrupt Controller) struct provides all the functionality of a
|
// This Gic struct implements InterruptController to provide interrupt delivery service.
|
||||||
// GIC device. It wraps a hypervisor-emulated GIC device (Vgic) provided by the
|
// The Gic source files in arch/ folder maintain the Aarch64 specific Gic device.
|
||||||
// `hypervisor` crate.
|
// The 2 Gic instances could be merged together.
|
||||||
// Gic struct also implements InterruptController to provide interrupt delivery
|
// Leave this refactoring to future. Two options may be considered:
|
||||||
// service.
|
// 1. Move Gic*.rs from arch/ folder here.
|
||||||
|
// 2. Move this file and ioapic.rs to arch/, as they are architecture specific.
|
||||||
pub struct Gic {
|
pub struct Gic {
|
||||||
interrupt_source_group: Arc<dyn InterruptSourceGroup>,
|
interrupt_source_group: Arc<dyn InterruptSourceGroup>,
|
||||||
// The hypervisor agnostic virtual GIC
|
gic_device: Option<Arc<Mutex<Box<dyn GicDevice>>>>,
|
||||||
vgic: Option<Arc<Mutex<dyn Vgic>>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Gic {
|
impl Gic {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
vcpu_count: u8,
|
_vcpu_count: u8,
|
||||||
interrupt_manager: Arc<dyn InterruptManager<GroupConfig = MsiIrqGroupConfig>>,
|
interrupt_manager: Arc<dyn InterruptManager<GroupConfig = MsiIrqGroupConfig>>,
|
||||||
vm: Arc<dyn hypervisor::Vm>,
|
|
||||||
) -> Result<Gic> {
|
) -> Result<Gic> {
|
||||||
let interrupt_source_group = interrupt_manager
|
let interrupt_source_group = interrupt_manager
|
||||||
.create_group(MsiIrqGroupConfig {
|
.create_group(MsiIrqGroupConfig {
|
||||||
@@ -50,34 +42,22 @@ impl Gic {
|
|||||||
})
|
})
|
||||||
.map_err(Error::CreateInterruptSourceGroup)?;
|
.map_err(Error::CreateInterruptSourceGroup)?;
|
||||||
|
|
||||||
let vgic = vm
|
Ok(Gic {
|
||||||
.create_vgic(Gic::create_default_config(vcpu_count as u64))
|
|
||||||
.map_err(Error::CreateGic)?;
|
|
||||||
|
|
||||||
let gic = Gic {
|
|
||||||
interrupt_source_group,
|
interrupt_source_group,
|
||||||
vgic: Some(vgic),
|
gic_device: None,
|
||||||
};
|
})
|
||||||
gic.enable()?;
|
|
||||||
|
|
||||||
Ok(gic)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn restore_vgic(
|
pub fn set_gic_device(&mut self, gic_device: Arc<Mutex<Box<dyn GicDevice>>>) {
|
||||||
&mut self,
|
self.gic_device = Some(gic_device);
|
||||||
state: Option<GicState>,
|
|
||||||
saved_vcpu_states: &[CpuState],
|
|
||||||
) -> Result<()> {
|
|
||||||
self.set_gicr_typers(saved_vcpu_states);
|
|
||||||
self.vgic
|
|
||||||
.clone()
|
|
||||||
.unwrap()
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.set_state(&state.unwrap())
|
|
||||||
.map_err(Error::RestoreGic)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_gic_device(&self) -> Option<&Arc<Mutex<Box<dyn GicDevice>>>> {
|
||||||
|
self.gic_device.as_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InterruptController for Gic {
|
||||||
fn enable(&self) -> Result<()> {
|
fn enable(&self) -> Result<()> {
|
||||||
// Set irqfd for legacy interrupts
|
// Set irqfd for legacy interrupts
|
||||||
self.interrupt_source_group
|
self.interrupt_source_group
|
||||||
@@ -96,45 +76,12 @@ impl Gic {
|
|||||||
.update(
|
.update(
|
||||||
i as InterruptIndex,
|
i as InterruptIndex,
|
||||||
InterruptSourceConfig::LegacyIrq(config),
|
InterruptSourceConfig::LegacyIrq(config),
|
||||||
false,
|
|
||||||
false,
|
|
||||||
)
|
)
|
||||||
.map_err(Error::EnableInterrupt)?;
|
.map_err(Error::EnableInterrupt)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.interrupt_source_group
|
|
||||||
.set_gsi()
|
|
||||||
.map_err(Error::EnableInterrupt)?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Default config implied by arch::layout
|
|
||||||
pub fn create_default_config(vcpu_count: u64) -> VgicConfig {
|
|
||||||
let redists_size = layout::GIC_V3_REDIST_SIZE * vcpu_count;
|
|
||||||
let redists_addr = layout::GIC_V3_DIST_START.raw_value() - redists_size;
|
|
||||||
VgicConfig {
|
|
||||||
vcpu_count,
|
|
||||||
dist_addr: layout::GIC_V3_DIST_START.raw_value(),
|
|
||||||
dist_size: layout::GIC_V3_DIST_SIZE,
|
|
||||||
redists_addr,
|
|
||||||
redists_size,
|
|
||||||
msi_addr: redists_addr - layout::GIC_V3_ITS_SIZE,
|
|
||||||
msi_size: layout::GIC_V3_ITS_SIZE,
|
|
||||||
nr_irqs: layout::IRQ_NUM,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_vgic(&mut self) -> Result<Arc<Mutex<dyn Vgic>>> {
|
|
||||||
Ok(self.vgic.clone().unwrap())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_gicr_typers(&mut self, vcpu_states: &[CpuState]) {
|
|
||||||
let vgic = self.vgic.as_ref().unwrap().clone();
|
|
||||||
vgic.lock().unwrap().set_gicr_typers(vcpu_states);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InterruptController for Gic {
|
|
||||||
// This should be called anytime an interrupt needs to be injected into the
|
// This should be called anytime an interrupt needs to be injected into the
|
||||||
// running guest.
|
// running guest.
|
||||||
fn service_irq(&mut self, irq: usize) -> Result<()> {
|
fn service_irq(&mut self, irq: usize) -> Result<()> {
|
||||||
@@ -149,31 +96,3 @@ impl InterruptController for Gic {
|
|||||||
self.interrupt_source_group.notifier(irq as InterruptIndex)
|
self.interrupt_source_group.notifier(irq as InterruptIndex)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Snapshottable for Gic {
|
|
||||||
fn id(&self) -> String {
|
|
||||||
GIC_SNAPSHOT_ID.to_string()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
|
|
||||||
let vgic = self.vgic.as_ref().unwrap().clone();
|
|
||||||
let state = vgic.lock().unwrap().state().unwrap();
|
|
||||||
Snapshot::new_from_state(&state)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Pausable for Gic {
|
|
||||||
fn pause(&mut self) -> std::result::Result<(), MigratableError> {
|
|
||||||
// Flush tables to guest RAM
|
|
||||||
let vgic = self.vgic.as_ref().unwrap().clone();
|
|
||||||
vgic.lock().unwrap().save_data_tables().map_err(|e| {
|
|
||||||
MigratableError::Pause(anyhow!(
|
|
||||||
"Could not save GICv3ITS GIC pending tables {:?}",
|
|
||||||
e
|
|
||||||
))
|
|
||||||
})?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Transportable for Gic {}
|
|
||||||
impl Migratable for Gic {}
|
|
||||||
|
|||||||
@@ -1,55 +1,29 @@
|
|||||||
// Copyright © 2024 Institute of Software, CAS. All rights reserved.
|
|
||||||
// Copyright 2020, ARM Limited.
|
// Copyright 2020, ARM Limited.
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||||
|
|
||||||
use std::{io, result};
|
use std::io;
|
||||||
|
use std::result;
|
||||||
use thiserror::Error;
|
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
/// Invalid trigger mode.
|
/// Invalid trigger mode.
|
||||||
#[error("Invalid trigger mode")]
|
|
||||||
InvalidTriggerMode,
|
InvalidTriggerMode,
|
||||||
/// Invalid delivery mode.
|
/// Invalid delivery mode.
|
||||||
#[error("Invalid delivery mode")]
|
|
||||||
InvalidDeliveryMode,
|
InvalidDeliveryMode,
|
||||||
/// Failed creating the interrupt source group.
|
/// Failed creating the interrupt source group.
|
||||||
#[error("Failed creating the interrupt source group: {0}")]
|
|
||||||
CreateInterruptSourceGroup(io::Error),
|
CreateInterruptSourceGroup(io::Error),
|
||||||
/// Failed triggering the interrupt.
|
/// Failed triggering the interrupt.
|
||||||
#[error("Failed triggering the interrupt: {0}")]
|
|
||||||
TriggerInterrupt(io::Error),
|
TriggerInterrupt(io::Error),
|
||||||
/// Failed masking the interrupt.
|
/// Failed masking the interrupt.
|
||||||
#[error("Failed masking the interrupt: {0}")]
|
|
||||||
MaskInterrupt(io::Error),
|
MaskInterrupt(io::Error),
|
||||||
/// Failed unmasking the interrupt.
|
/// Failed unmasking the interrupt.
|
||||||
#[error("Failed unmasking the interrupt: {0}")]
|
|
||||||
UnmaskInterrupt(io::Error),
|
UnmaskInterrupt(io::Error),
|
||||||
/// Failed updating the interrupt.
|
/// Failed updating the interrupt.
|
||||||
#[error("Failed updating the interrupt: {0}")]
|
|
||||||
UpdateInterrupt(io::Error),
|
UpdateInterrupt(io::Error),
|
||||||
/// Failed enabling the interrupt.
|
/// Failed enabling the interrupt.
|
||||||
#[error("Failed enabling the interrupt: {0}")]
|
|
||||||
EnableInterrupt(io::Error),
|
EnableInterrupt(io::Error),
|
||||||
#[cfg(target_arch = "aarch64")]
|
|
||||||
/// Failed creating GIC device.
|
|
||||||
#[error("Failed creating GIC device: {0}")]
|
|
||||||
CreateGic(hypervisor::HypervisorVmError),
|
|
||||||
#[cfg(target_arch = "aarch64")]
|
|
||||||
/// Failed restoring GIC device.
|
|
||||||
#[error("Failed restoring GIC device: {0}")]
|
|
||||||
RestoreGic(hypervisor::arch::aarch64::gic::Error),
|
|
||||||
#[cfg(target_arch = "riscv64")]
|
|
||||||
/// Failed creating AIA device.
|
|
||||||
#[error("Failed creating AIA device: {0}")]
|
|
||||||
CreateAia(hypervisor::HypervisorVmError),
|
|
||||||
#[cfg(target_arch = "riscv64")]
|
|
||||||
/// Failed restoring AIA device.
|
|
||||||
#[error("Failed restoring AIA device: {0}")]
|
|
||||||
RestoreAia(hypervisor::arch::riscv64::aia::Error),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Result<T> = result::Result<T, Error>;
|
type Result<T> = result::Result<T, Error>;
|
||||||
@@ -76,9 +50,11 @@ pub struct MsiMessage {
|
|||||||
// Introduce trait InterruptController to uniform the interrupt
|
// Introduce trait InterruptController to uniform the interrupt
|
||||||
// service provided for devices.
|
// service provided for devices.
|
||||||
// Device manager uses this trait without caring whether it is a
|
// Device manager uses this trait without caring whether it is a
|
||||||
// IOAPIC (X86), GIC (Arm) or AIA (RISC-V).
|
// IOAPIC (X86) or GIC (Arm).
|
||||||
pub trait InterruptController: Send {
|
pub trait InterruptController: Send {
|
||||||
fn service_irq(&mut self, irq: usize) -> Result<()>;
|
fn service_irq(&mut self, irq: usize) -> Result<()>;
|
||||||
|
#[cfg(target_arch = "aarch64")]
|
||||||
|
fn enable(&self) -> Result<()>;
|
||||||
#[cfg(target_arch = "x86_64")]
|
#[cfg(target_arch = "x86_64")]
|
||||||
fn end_of_interrupt(&mut self, vec: u8);
|
fn end_of_interrupt(&mut self, vec: u8);
|
||||||
fn notifier(&self, irq: usize) -> Option<EventFd>;
|
fn notifier(&self, irq: usize) -> Option<EventFd>;
|
||||||
|
|||||||
@@ -9,22 +9,24 @@
|
|||||||
// Implementation of an intel 82093AA Input/Output Advanced Programmable Interrupt Controller
|
// Implementation of an intel 82093AA Input/Output Advanced Programmable Interrupt Controller
|
||||||
// See https://pdos.csail.mit.edu/6.828/2016/readings/ia32/ioapic.pdf for a specification.
|
// See https://pdos.csail.mit.edu/6.828/2016/readings/ia32/ioapic.pdf for a specification.
|
||||||
|
|
||||||
|
use super::interrupt_controller::{Error, InterruptController};
|
||||||
|
use anyhow::anyhow;
|
||||||
|
use byteorder::{ByteOrder, LittleEndian};
|
||||||
use std::result;
|
use std::result;
|
||||||
use std::sync::{Arc, Barrier};
|
use std::sync::{Arc, Barrier};
|
||||||
|
use versionize::{VersionMap, Versionize, VersionizeResult};
|
||||||
use byteorder::{ByteOrder, LittleEndian};
|
use versionize_derive::Versionize;
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use vm_device::interrupt::{
|
use vm_device::interrupt::{
|
||||||
InterruptIndex, InterruptManager, InterruptSourceConfig, InterruptSourceGroup,
|
InterruptIndex, InterruptManager, InterruptSourceConfig, InterruptSourceGroup,
|
||||||
MsiIrqGroupConfig, MsiIrqSourceConfig,
|
MsiIrqGroupConfig, MsiIrqSourceConfig,
|
||||||
};
|
};
|
||||||
use vm_device::BusDevice;
|
use vm_device::BusDevice;
|
||||||
use vm_memory::GuestAddress;
|
use vm_memory::GuestAddress;
|
||||||
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
|
use vm_migration::{
|
||||||
|
Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable, VersionMapped,
|
||||||
|
};
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
use super::interrupt_controller::{Error, InterruptController};
|
|
||||||
|
|
||||||
type Result<T> = result::Result<T, Error>;
|
type Result<T> = result::Result<T, Error>;
|
||||||
|
|
||||||
// I/O REDIRECTION TABLE REGISTER
|
// I/O REDIRECTION TABLE REGISTER
|
||||||
@@ -135,7 +137,7 @@ pub struct Ioapic {
|
|||||||
interrupt_source_group: Arc<dyn InterruptSourceGroup>,
|
interrupt_source_group: Arc<dyn InterruptSourceGroup>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Versionize)]
|
||||||
pub struct IoapicState {
|
pub struct IoapicState {
|
||||||
id_reg: u32,
|
id_reg: u32,
|
||||||
reg_sel: u32,
|
reg_sel: u32,
|
||||||
@@ -143,6 +145,7 @@ pub struct IoapicState {
|
|||||||
used_entries: [bool; NUM_IOAPIC_PINS],
|
used_entries: [bool; NUM_IOAPIC_PINS],
|
||||||
apic_address: u64,
|
apic_address: u64,
|
||||||
}
|
}
|
||||||
|
impl VersionMapped for IoapicState {}
|
||||||
|
|
||||||
impl BusDevice for Ioapic {
|
impl BusDevice for Ioapic {
|
||||||
fn read(&mut self, _base: u64, offset: u64, data: &mut [u8]) {
|
fn read(&mut self, _base: u64, offset: u64, data: &mut [u8]) {
|
||||||
@@ -191,7 +194,6 @@ impl Ioapic {
|
|||||||
id: String,
|
id: String,
|
||||||
apic_address: GuestAddress,
|
apic_address: GuestAddress,
|
||||||
interrupt_manager: Arc<dyn InterruptManager<GroupConfig = MsiIrqGroupConfig>>,
|
interrupt_manager: Arc<dyn InterruptManager<GroupConfig = MsiIrqGroupConfig>>,
|
||||||
state: Option<IoapicState>,
|
|
||||||
) -> Result<Ioapic> {
|
) -> Result<Ioapic> {
|
||||||
let interrupt_source_group = interrupt_manager
|
let interrupt_source_group = interrupt_manager
|
||||||
.create_group(MsiIrqGroupConfig {
|
.create_group(MsiIrqGroupConfig {
|
||||||
@@ -200,52 +202,17 @@ impl Ioapic {
|
|||||||
})
|
})
|
||||||
.map_err(Error::CreateInterruptSourceGroup)?;
|
.map_err(Error::CreateInterruptSourceGroup)?;
|
||||||
|
|
||||||
let (id_reg, reg_sel, reg_entries, used_entries, apic_address) = if let Some(state) = &state
|
|
||||||
{
|
|
||||||
(
|
|
||||||
state.id_reg,
|
|
||||||
state.reg_sel,
|
|
||||||
state.reg_entries,
|
|
||||||
state.used_entries,
|
|
||||||
GuestAddress(state.apic_address),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
(
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
[0x10000; NUM_IOAPIC_PINS],
|
|
||||||
[false; NUM_IOAPIC_PINS],
|
|
||||||
apic_address,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
// The IOAPIC is created with entries already masked. The guest will be
|
// The IOAPIC is created with entries already masked. The guest will be
|
||||||
// in charge of unmasking them if/when necessary.
|
// in charge of unmasking them if/when necessary.
|
||||||
let ioapic = Ioapic {
|
Ok(Ioapic {
|
||||||
id,
|
id,
|
||||||
id_reg,
|
id_reg: 0,
|
||||||
reg_sel,
|
reg_sel: 0,
|
||||||
reg_entries,
|
reg_entries: [0x10000; NUM_IOAPIC_PINS],
|
||||||
used_entries,
|
used_entries: [false; NUM_IOAPIC_PINS],
|
||||||
apic_address,
|
apic_address,
|
||||||
interrupt_source_group,
|
interrupt_source_group,
|
||||||
};
|
})
|
||||||
|
|
||||||
// When restoring the Ioapic, we must enable used entries.
|
|
||||||
if state.is_some() {
|
|
||||||
for (irq, entry) in ioapic.used_entries.iter().enumerate() {
|
|
||||||
if *entry {
|
|
||||||
ioapic.update_entry(irq, false)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ioapic
|
|
||||||
.interrupt_source_group
|
|
||||||
.set_gsi()
|
|
||||||
.map_err(Error::UpdateInterrupt)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(ioapic)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ioapic_write(&mut self, val: u32) {
|
fn ioapic_write(&mut self, val: u32) {
|
||||||
@@ -281,7 +248,7 @@ impl Ioapic {
|
|||||||
}
|
}
|
||||||
// The entry must be updated through the interrupt source
|
// The entry must be updated through the interrupt source
|
||||||
// group.
|
// group.
|
||||||
if let Err(e) = self.update_entry(index, true) {
|
if let Err(e) = self.update_entry(index) {
|
||||||
error!("Failed updating IOAPIC entry: {:?}", e);
|
error!("Failed updating IOAPIC entry: {:?}", e);
|
||||||
}
|
}
|
||||||
// Store the information this IRQ is now being used.
|
// Store the information this IRQ is now being used.
|
||||||
@@ -332,7 +299,22 @@ impl Ioapic {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_entry(&self, irq: usize, set_gsi: bool) -> Result<()> {
|
fn set_state(&mut self, state: &IoapicState) -> Result<()> {
|
||||||
|
self.id_reg = state.id_reg;
|
||||||
|
self.reg_sel = state.reg_sel;
|
||||||
|
self.reg_entries = state.reg_entries;
|
||||||
|
self.used_entries = state.used_entries;
|
||||||
|
self.apic_address = GuestAddress(state.apic_address);
|
||||||
|
for (irq, entry) in self.used_entries.iter().enumerate() {
|
||||||
|
if *entry {
|
||||||
|
self.update_entry(irq)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_entry(&self, irq: usize) -> Result<()> {
|
||||||
let entry = self.reg_entries[irq];
|
let entry = self.reg_entries[irq];
|
||||||
|
|
||||||
// Validate Destination Mode value, and retrieve Destination ID
|
// Validate Destination Mode value, and retrieve Destination ID
|
||||||
@@ -346,9 +328,9 @@ impl Ioapic {
|
|||||||
|
|
||||||
// Generate MSI message address
|
// Generate MSI message address
|
||||||
let low_addr: u32 = self.apic_address.0 as u32
|
let low_addr: u32 = self.apic_address.0 as u32
|
||||||
| (u32::from(destination_id) << 12)
|
| u32::from(destination_id) << 12
|
||||||
| (u32::from(redirection_hint) << 3)
|
| u32::from(redirection_hint) << 3
|
||||||
| (u32::from(destination_mode) << 2);
|
| u32::from(destination_mode) << 2;
|
||||||
|
|
||||||
// Validate Trigger Mode value
|
// Validate Trigger Mode value
|
||||||
let trigger_mode = trigger_mode(entry);
|
let trigger_mode = trigger_mode(entry);
|
||||||
@@ -372,9 +354,9 @@ impl Ioapic {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Generate MSI message data
|
// Generate MSI message data
|
||||||
let data: u32 = (u32::from(trigger_mode) << 15)
|
let data: u32 = u32::from(trigger_mode) << 15
|
||||||
| (u32::from(remote_irr(entry)) << 14)
|
| u32::from(remote_irr(entry)) << 14
|
||||||
| (u32::from(delivery_mode) << 8)
|
| u32::from(delivery_mode) << 8
|
||||||
| u32::from(vector(entry));
|
| u32::from(vector(entry));
|
||||||
|
|
||||||
let config = MsiIrqSourceConfig {
|
let config = MsiIrqSourceConfig {
|
||||||
@@ -385,14 +367,19 @@ impl Ioapic {
|
|||||||
};
|
};
|
||||||
|
|
||||||
self.interrupt_source_group
|
self.interrupt_source_group
|
||||||
.update(
|
.update(irq as InterruptIndex, InterruptSourceConfig::MsiIrq(config))
|
||||||
irq as InterruptIndex,
|
|
||||||
InterruptSourceConfig::MsiIrq(config),
|
|
||||||
interrupt_mask(entry) == 1,
|
|
||||||
set_gsi,
|
|
||||||
)
|
|
||||||
.map_err(Error::UpdateInterrupt)?;
|
.map_err(Error::UpdateInterrupt)?;
|
||||||
|
|
||||||
|
if interrupt_mask(entry) == 1 {
|
||||||
|
self.interrupt_source_group
|
||||||
|
.mask(irq as InterruptIndex)
|
||||||
|
.map_err(Error::MaskInterrupt)?;
|
||||||
|
} else {
|
||||||
|
self.interrupt_source_group
|
||||||
|
.unmask(irq as InterruptIndex)
|
||||||
|
.map_err(Error::UnmaskInterrupt)?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -418,7 +405,7 @@ impl InterruptController for Ioapic {
|
|||||||
self.interrupt_source_group
|
self.interrupt_source_group
|
||||||
.trigger(irq as InterruptIndex)
|
.trigger(irq as InterruptIndex)
|
||||||
.map_err(Error::TriggerInterrupt)?;
|
.map_err(Error::TriggerInterrupt)?;
|
||||||
debug!("Interrupt {irq} successfully delivered");
|
debug!("Interrupt successfully delivered");
|
||||||
|
|
||||||
// If trigger mode is level sensitive, set the Remote IRR bit.
|
// If trigger mode is level sensitive, set the Remote IRR bit.
|
||||||
// It will be cleared when the EOI is received.
|
// It will be cleared when the EOI is received.
|
||||||
@@ -442,7 +429,18 @@ impl Snapshottable for Ioapic {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
|
fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
|
||||||
Snapshot::new_from_state(&self.state())
|
Snapshot::new_from_versioned_state(&self.id, &self.state())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn restore(&mut self, snapshot: Snapshot) -> std::result::Result<(), MigratableError> {
|
||||||
|
self.set_state(&snapshot.to_versioned_state(&self.id)?)
|
||||||
|
.map_err(|e| {
|
||||||
|
MigratableError::Restore(anyhow!(
|
||||||
|
"Could not restore state for {}: {:?}",
|
||||||
|
self.id,
|
||||||
|
e
|
||||||
|
))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +1,16 @@
|
|||||||
// Copyright 2017 The Chromium OS Authors. All rights reserved.
|
// Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
|
||||||
|
|
||||||
|
use libc::{clock_gettime, gmtime_r, timespec, tm, CLOCK_REALTIME};
|
||||||
use std::cmp::min;
|
use std::cmp::min;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::mem;
|
||||||
use std::sync::{Arc, Barrier};
|
use std::sync::{Arc, Barrier};
|
||||||
use std::{mem, thread};
|
use vm_device::BusDevice;
|
||||||
|
|
||||||
// https://github.com/rust-lang/libc/issues/1848
|
// https://github.com/rust-lang/libc/issues/1848
|
||||||
#[cfg_attr(target_env = "musl", allow(deprecated))]
|
#[cfg_attr(target_env = "musl", allow(deprecated))]
|
||||||
use libc::time_t;
|
use libc::time_t;
|
||||||
use libc::{clock_gettime, gmtime_r, timespec, tm, CLOCK_REALTIME};
|
|
||||||
use vm_device::BusDevice;
|
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
|
||||||
|
|
||||||
const INDEX_MASK: u8 = 0x7f;
|
const INDEX_MASK: u8 = 0x7f;
|
||||||
const INDEX_OFFSET: u64 = 0x0;
|
const INDEX_OFFSET: u64 = 0x0;
|
||||||
@@ -25,20 +21,13 @@ const DATA_LEN: usize = 128;
|
|||||||
pub struct Cmos {
|
pub struct Cmos {
|
||||||
index: u8,
|
index: u8,
|
||||||
data: [u8; DATA_LEN],
|
data: [u8; DATA_LEN],
|
||||||
reset_evt: EventFd,
|
|
||||||
vcpus_kill_signalled: Option<Arc<AtomicBool>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Cmos {
|
impl Cmos {
|
||||||
/// Constructs a CMOS/RTC device with initial data.
|
/// Constructs a CMOS/RTC device with initial data.
|
||||||
/// `mem_below_4g` is the size of memory in bytes below the 32-bit gap.
|
/// `mem_below_4g` is the size of memory in bytes below the 32-bit gap.
|
||||||
/// `mem_above_4g` is the size of memory in bytes above the 32-bit gap.
|
/// `mem_above_4g` is the size of memory in bytes above the 32-bit gap.
|
||||||
pub fn new(
|
pub fn new(mem_below_4g: u64, mem_above_4g: u64) -> Cmos {
|
||||||
mem_below_4g: u64,
|
|
||||||
mem_above_4g: u64,
|
|
||||||
reset_evt: EventFd,
|
|
||||||
vcpus_kill_signalled: Option<Arc<AtomicBool>>,
|
|
||||||
) -> Cmos {
|
|
||||||
let mut data = [0u8; DATA_LEN];
|
let mut data = [0u8; DATA_LEN];
|
||||||
|
|
||||||
// Extended memory from 16 MB to 4 GB in units of 64 KB
|
// Extended memory from 16 MB to 4 GB in units of 64 KB
|
||||||
@@ -55,12 +44,7 @@ impl Cmos {
|
|||||||
data[0x5c] = (high_mem >> 8) as u8;
|
data[0x5c] = (high_mem >> 8) as u8;
|
||||||
data[0x5d] = (high_mem >> 16) as u8;
|
data[0x5d] = (high_mem >> 16) as u8;
|
||||||
|
|
||||||
Cmos {
|
Cmos { index: 0, data }
|
||||||
index: 0,
|
|
||||||
data,
|
|
||||||
reset_evt,
|
|
||||||
vcpus_kill_signalled,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,24 +56,8 @@ impl BusDevice for Cmos {
|
|||||||
}
|
}
|
||||||
|
|
||||||
match offset {
|
match offset {
|
||||||
INDEX_OFFSET => self.index = data[0],
|
INDEX_OFFSET => self.index = data[0] & INDEX_MASK,
|
||||||
DATA_OFFSET => {
|
DATA_OFFSET => self.data[self.index as usize] = data[0],
|
||||||
if self.index == 0x8f && data[0] == 0 {
|
|
||||||
info!("CMOS reset");
|
|
||||||
self.reset_evt.write(1).unwrap();
|
|
||||||
if let Some(vcpus_kill_signalled) = self.vcpus_kill_signalled.take() {
|
|
||||||
// Spin until we are sure the reset_evt has been handled and that when
|
|
||||||
// we return from the KVM_RUN we will exit rather than re-enter the guest.
|
|
||||||
while !vcpus_kill_signalled.load(Ordering::SeqCst) {
|
|
||||||
// This is more effective than thread::yield_now() at
|
|
||||||
// avoiding a priority inversion with the VMM thread
|
|
||||||
thread::sleep(std::time::Duration::from_millis(1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.data[(self.index & INDEX_MASK) as usize] = data[0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
o => warn!("bad write offset on CMOS device: {}", o),
|
o => warn!("bad write offset on CMOS device: {}", o),
|
||||||
};
|
};
|
||||||
None
|
None
|
||||||
@@ -116,7 +84,7 @@ impl BusDevice for Cmos {
|
|||||||
let day;
|
let day;
|
||||||
let month;
|
let month;
|
||||||
let year;
|
let year;
|
||||||
// SAFETY: The clock_gettime and gmtime_r calls are safe as long as the structs they are
|
// The clock_gettime and gmtime_r calls are safe as long as the structs they are
|
||||||
// given are large enough, and neither of them fail. It is safe to zero initialize
|
// given are large enough, and neither of them fail. It is safe to zero initialize
|
||||||
// the tm and timespec struct because it contains only plain data.
|
// the tm and timespec struct because it contains only plain data.
|
||||||
let update_in_progress = unsafe {
|
let update_in_progress = unsafe {
|
||||||
@@ -152,7 +120,7 @@ impl BusDevice for Cmos {
|
|||||||
0x08 => to_bcd(month as u8),
|
0x08 => to_bcd(month as u8),
|
||||||
0x09 => to_bcd((year % 100) as u8),
|
0x09 => to_bcd((year % 100) as u8),
|
||||||
// Bit 5 for 32kHz clock. Bit 7 for Update in Progress
|
// Bit 5 for 32kHz clock. Bit 7 for Update in Progress
|
||||||
0x0a => (1 << 5) | ((update_in_progress as u8) << 7),
|
0x0a => 1 << 5 | (update_in_progress as u8) << 7,
|
||||||
// Bit 0-6 are reserved and must be 0.
|
// Bit 0-6 are reserved and must be 0.
|
||||||
// Bit 7 must be 1 (CMOS has power)
|
// Bit 7 must be 1 (CMOS has power)
|
||||||
0x0d => 1 << 7,
|
0x0d => 1 << 7,
|
||||||
|
|||||||
@@ -1,87 +0,0 @@
|
|||||||
// Copyright © 2022 Intel Corporation
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
//
|
|
||||||
|
|
||||||
use std::fmt;
|
|
||||||
use std::time::Instant;
|
|
||||||
|
|
||||||
use vm_device::BusDevice;
|
|
||||||
|
|
||||||
/// Debug I/O port, see:
|
|
||||||
/// https://www.intel.com/content/www/us/en/support/articles/000005500/boards-and-kits.html
|
|
||||||
///
|
|
||||||
/// Since we're not a physical platform, we can freely assign code ranges for
|
|
||||||
/// debugging specific parts of our virtual platform.
|
|
||||||
pub enum DebugIoPortRange {
|
|
||||||
Firmware,
|
|
||||||
Bootloader,
|
|
||||||
Kernel,
|
|
||||||
Userspace,
|
|
||||||
Custom,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_arch = "x86_64")]
|
|
||||||
const DEBUG_IOPORT_PREFIX: &str = "Debug I/O port";
|
|
||||||
|
|
||||||
#[cfg(target_arch = "x86_64")]
|
|
||||||
impl DebugIoPortRange {
|
|
||||||
fn from_u8(value: u8) -> DebugIoPortRange {
|
|
||||||
match value {
|
|
||||||
0x00..=0x1f => DebugIoPortRange::Firmware,
|
|
||||||
0x20..=0x3f => DebugIoPortRange::Bootloader,
|
|
||||||
0x40..=0x5f => DebugIoPortRange::Kernel,
|
|
||||||
0x60..=0x7f => DebugIoPortRange::Userspace,
|
|
||||||
_ => DebugIoPortRange::Custom,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_arch = "x86_64")]
|
|
||||||
impl fmt::Display for DebugIoPortRange {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
DebugIoPortRange::Firmware => write!(f, "{DEBUG_IOPORT_PREFIX}: Firmware"),
|
|
||||||
DebugIoPortRange::Bootloader => write!(f, "{DEBUG_IOPORT_PREFIX}: Bootloader"),
|
|
||||||
DebugIoPortRange::Kernel => write!(f, "{DEBUG_IOPORT_PREFIX}: Kernel"),
|
|
||||||
DebugIoPortRange::Userspace => write!(f, "{DEBUG_IOPORT_PREFIX}: Userspace"),
|
|
||||||
DebugIoPortRange::Custom => write!(f, "{DEBUG_IOPORT_PREFIX}: Custom"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct DebugPort {
|
|
||||||
timestamp: Instant,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DebugPort {
|
|
||||||
pub fn new(timestamp: Instant) -> Self {
|
|
||||||
Self { timestamp }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BusDevice for DebugPort {
|
|
||||||
fn read(&mut self, _base: u64, _offset: u64, _data: &mut [u8]) {
|
|
||||||
error!("Invalid read to debug port")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write(
|
|
||||||
&mut self,
|
|
||||||
_base: u64,
|
|
||||||
_offset: u64,
|
|
||||||
data: &[u8],
|
|
||||||
) -> Option<std::sync::Arc<std::sync::Barrier>> {
|
|
||||||
let elapsed = self.timestamp.elapsed();
|
|
||||||
|
|
||||||
let code = data[0];
|
|
||||||
warn!(
|
|
||||||
"[{} code 0x{:x}] {}.{:>06} seconds",
|
|
||||||
DebugIoPortRange::from_u8(code),
|
|
||||||
code,
|
|
||||||
elapsed.as_secs(),
|
|
||||||
elapsed.as_micros()
|
|
||||||
);
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -8,7 +8,6 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
use std::sync::{Arc, Barrier};
|
use std::sync::{Arc, Barrier};
|
||||||
|
|
||||||
use vm_device::BusDevice;
|
use vm_device::BusDevice;
|
||||||
|
|
||||||
/// Provides firmware debug output via I/O port controls
|
/// Provides firmware debug output via I/O port controls
|
||||||
|
|||||||
@@ -7,16 +7,17 @@
|
|||||||
//! This module implements an ARM PrimeCell General Purpose Input/Output(PL061) to support gracefully poweroff microvm from external.
|
//! This module implements an ARM PrimeCell General Purpose Input/Output(PL061) to support gracefully poweroff microvm from external.
|
||||||
//!
|
//!
|
||||||
|
|
||||||
|
use crate::{read_le_u32, write_le_u32};
|
||||||
|
use std::result;
|
||||||
use std::sync::{Arc, Barrier};
|
use std::sync::{Arc, Barrier};
|
||||||
use std::{io, result};
|
use std::{fmt, io};
|
||||||
|
use versionize::{VersionMap, Versionize, VersionizeResult};
|
||||||
use serde::{Deserialize, Serialize};
|
use versionize_derive::Versionize;
|
||||||
use thiserror::Error;
|
|
||||||
use vm_device::interrupt::InterruptSourceGroup;
|
use vm_device::interrupt::InterruptSourceGroup;
|
||||||
use vm_device::BusDevice;
|
use vm_device::BusDevice;
|
||||||
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
|
use vm_migration::{
|
||||||
|
Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable, VersionMapped,
|
||||||
use crate::{read_le_u32, write_le_u32};
|
};
|
||||||
|
|
||||||
const OFS_DATA: u64 = 0x400; // Data Register
|
const OFS_DATA: u64 = 0x400; // Data Register
|
||||||
const GPIODIR: u64 = 0x400; // Direction Register
|
const GPIODIR: u64 = 0x400; // Direction Register
|
||||||
@@ -30,7 +31,7 @@ const GPIOIC: u64 = 0x41c; // Interrupt Clear Register
|
|||||||
const GPIOAFSEL: u64 = 0x420; // Mode Control Select Register
|
const GPIOAFSEL: u64 = 0x420; // Mode Control Select Register
|
||||||
// From 0x424 to 0xFDC => reserved space.
|
// From 0x424 to 0xFDC => reserved space.
|
||||||
// From 0xFE0 to 0xFFC => Peripheral and PrimeCell Identification Registers which are Read Only registers.
|
// From 0xFE0 to 0xFFC => Peripheral and PrimeCell Identification Registers which are Read Only registers.
|
||||||
// These registers can conceptually be treated as a 32-bit register, and PartNumber[11:0] is used to identify the peripheral.
|
// Thses registers can conceptually be treated as a 32-bit register, and PartNumber[11:0] is used to identify the peripheral.
|
||||||
// We are putting the expected values (look at 'Reset value' column from above mentioned document) in an array.
|
// We are putting the expected values (look at 'Reset value' column from above mentioned document) in an array.
|
||||||
const GPIO_ID: [u8; 8] = [0x61, 0x10, 0x14, 0x00, 0x0d, 0xf0, 0x05, 0xb1];
|
const GPIO_ID: [u8; 8] = [0x61, 0x10, 0x14, 0x00, 0x0d, 0xf0, 0x05, 0xb1];
|
||||||
// ID Margins
|
// ID Margins
|
||||||
@@ -39,18 +40,29 @@ const GPIO_ID_HIGH: u64 = 0x1000;
|
|||||||
|
|
||||||
const N_GPIOS: u32 = 8;
|
const N_GPIOS: u32 = 8;
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[error("Bad Write Offset: {0}")]
|
|
||||||
BadWriteOffset(u64),
|
BadWriteOffset(u64),
|
||||||
#[error("GPIO interrupt disabled by guest driver.")]
|
|
||||||
GpioInterruptDisabled,
|
GpioInterruptDisabled,
|
||||||
#[error("Could not trigger GPIO interrupt: {0}.")]
|
|
||||||
GpioInterruptFailure(io::Error),
|
GpioInterruptFailure(io::Error),
|
||||||
#[error("Invalid GPIO Input key triggered: {0}.")]
|
|
||||||
GpioTriggerKeyFailure(u32),
|
GpioTriggerKeyFailure(u32),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Error {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Error::BadWriteOffset(offset) => write!(f, "Bad Write Offset: {}", offset),
|
||||||
|
Error::GpioInterruptDisabled => write!(f, "GPIO interrupt disabled by guest driver.",),
|
||||||
|
Error::GpioInterruptFailure(ref e) => {
|
||||||
|
write!(f, "Could not trigger GPIO interrupt: {}.", e)
|
||||||
|
}
|
||||||
|
Error::GpioTriggerKeyFailure(key) => {
|
||||||
|
write!(f, "Invalid GPIO Input key triggerd: {}.", key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type Result<T> = result::Result<T, Error>;
|
type Result<T> = result::Result<T, Error>;
|
||||||
|
|
||||||
/// A GPIO device following the PL061 specification.
|
/// A GPIO device following the PL061 specification.
|
||||||
@@ -77,7 +89,7 @@ pub struct Gpio {
|
|||||||
interrupt: Arc<dyn InterruptSourceGroup>,
|
interrupt: Arc<dyn InterruptSourceGroup>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Versionize)]
|
||||||
pub struct GpioState {
|
pub struct GpioState {
|
||||||
data: u32,
|
data: u32,
|
||||||
old_in_data: u32,
|
old_in_data: u32,
|
||||||
@@ -90,41 +102,22 @@ pub struct GpioState {
|
|||||||
afsel: u32,
|
afsel: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl VersionMapped for GpioState {}
|
||||||
|
|
||||||
impl Gpio {
|
impl Gpio {
|
||||||
/// Constructs an PL061 GPIO device.
|
/// Constructs an PL061 GPIO device.
|
||||||
pub fn new(
|
pub fn new(id: String, interrupt: Arc<dyn InterruptSourceGroup>) -> Self {
|
||||||
id: String,
|
|
||||||
interrupt: Arc<dyn InterruptSourceGroup>,
|
|
||||||
state: Option<GpioState>,
|
|
||||||
) -> Self {
|
|
||||||
let (data, old_in_data, dir, isense, ibe, iev, im, istate, afsel) =
|
|
||||||
if let Some(state) = state {
|
|
||||||
(
|
|
||||||
state.data,
|
|
||||||
state.old_in_data,
|
|
||||||
state.dir,
|
|
||||||
state.isense,
|
|
||||||
state.ibe,
|
|
||||||
state.iev,
|
|
||||||
state.im,
|
|
||||||
state.istate,
|
|
||||||
state.afsel,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
(0, 0, 0, 0, 0, 0, 0, 0, 0)
|
|
||||||
};
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
id,
|
id,
|
||||||
data,
|
data: 0,
|
||||||
old_in_data,
|
old_in_data: 0,
|
||||||
dir,
|
dir: 0,
|
||||||
isense,
|
isense: 0,
|
||||||
ibe,
|
ibe: 0,
|
||||||
iev,
|
iev: 0,
|
||||||
im,
|
im: 0,
|
||||||
istate,
|
istate: 0,
|
||||||
afsel,
|
afsel: 0,
|
||||||
interrupt,
|
interrupt,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -143,12 +136,24 @@ impl Gpio {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_state(&mut self, state: &GpioState) {
|
||||||
|
self.data = state.data;
|
||||||
|
self.old_in_data = state.old_in_data;
|
||||||
|
self.dir = state.dir;
|
||||||
|
self.isense = state.isense;
|
||||||
|
self.ibe = state.ibe;
|
||||||
|
self.iev = state.iev;
|
||||||
|
self.im = state.im;
|
||||||
|
self.istate = state.istate;
|
||||||
|
self.afsel = state.afsel;
|
||||||
|
}
|
||||||
|
|
||||||
fn pl061_internal_update(&mut self) {
|
fn pl061_internal_update(&mut self) {
|
||||||
// FIXME:
|
// FIXME:
|
||||||
// Missing Output Interrupt Emulation.
|
// Missing Output Interrupt Emulation.
|
||||||
|
|
||||||
// Input Edging Interrupt Emulation.
|
// Input Edging Interrupt Emulation.
|
||||||
let changed = (self.old_in_data ^ self.data) & !self.dir;
|
let changed = ((self.old_in_data ^ self.data) & !self.dir) as u32;
|
||||||
if changed > 0 {
|
if changed > 0 {
|
||||||
self.old_in_data = self.data;
|
self.old_in_data = self.data;
|
||||||
for i in 0..N_GPIOS {
|
for i in 0..N_GPIOS {
|
||||||
@@ -314,7 +319,12 @@ impl Snapshottable for Gpio {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
|
fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
|
||||||
Snapshot::new_from_state(&self.state())
|
Snapshot::new_from_versioned_state(&self.id, &self.state())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn restore(&mut self, snapshot: Snapshot) -> std::result::Result<(), MigratableError> {
|
||||||
|
self.set_state(&snapshot.to_versioned_state(&self.id)?);
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -324,11 +334,12 @@ impl Migratable for Gpio {}
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::{read_le_u32, write_le_u32};
|
||||||
|
use std::sync::Arc;
|
||||||
use vm_device::interrupt::{InterruptIndex, InterruptSourceConfig};
|
use vm_device::interrupt::{InterruptIndex, InterruptSourceConfig};
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
const GPIO_NAME: &str = "gpio";
|
const GPIO_NAME: &str = "gpio";
|
||||||
const LEGACY_GPIO_MAPPED_IO_START: u64 = 0x0902_0000;
|
const LEGACY_GPIO_MAPPED_IO_START: u64 = 0x0902_0000;
|
||||||
|
|
||||||
@@ -345,16 +356,10 @@ mod tests {
|
|||||||
&self,
|
&self,
|
||||||
_index: InterruptIndex,
|
_index: InterruptIndex,
|
||||||
_config: InterruptSourceConfig,
|
_config: InterruptSourceConfig,
|
||||||
_masked: bool,
|
|
||||||
_set_gsi: bool,
|
|
||||||
) -> result::Result<(), std::io::Error> {
|
) -> result::Result<(), std::io::Error> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_gsi(&self) -> result::Result<(), std::io::Error> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn notifier(&self, _index: InterruptIndex) -> Option<EventFd> {
|
fn notifier(&self, _index: InterruptIndex) -> Option<EventFd> {
|
||||||
Some(self.event_fd.try_clone().unwrap())
|
Some(self.event_fd.try_clone().unwrap())
|
||||||
}
|
}
|
||||||
@@ -372,7 +377,6 @@ mod tests {
|
|||||||
let mut gpio = Gpio::new(
|
let mut gpio = Gpio::new(
|
||||||
String::from(GPIO_NAME),
|
String::from(GPIO_NAME),
|
||||||
Arc::new(TestInterrupt::new(intr_evt.try_clone().unwrap())),
|
Arc::new(TestInterrupt::new(intr_evt.try_clone().unwrap())),
|
||||||
None,
|
|
||||||
);
|
);
|
||||||
let mut data = [0; 4];
|
let mut data = [0; 4];
|
||||||
|
|
||||||
|
|||||||
@@ -1,29 +1,20 @@
|
|||||||
// Copyright 2017 The Chromium OS Authors. All rights reserved.
|
// Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE-BSD-3-Clause file.
|
// found in the LICENSE-BSD-3-Clause file.
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
|
||||||
|
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
|
||||||
use std::sync::{Arc, Barrier};
|
use std::sync::{Arc, Barrier};
|
||||||
use std::thread;
|
|
||||||
|
|
||||||
use vm_device::BusDevice;
|
use vm_device::BusDevice;
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
/// A i8042 PS/2 controller that emulates just enough to shutdown the machine.
|
/// A i8042 PS/2 controller that emulates just enough to shutdown the machine.
|
||||||
pub struct I8042Device {
|
pub struct I8042Device {
|
||||||
reset_evt: EventFd,
|
reset_evt: EventFd,
|
||||||
vcpus_kill_signalled: Arc<AtomicBool>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl I8042Device {
|
impl I8042Device {
|
||||||
/// Constructs a i8042 device that will signal the given event when the guest requests it.
|
/// Constructs a i8042 device that will signal the given event when the guest requests it.
|
||||||
pub fn new(reset_evt: EventFd, vcpus_kill_signalled: Arc<AtomicBool>) -> I8042Device {
|
pub fn new(reset_evt: EventFd) -> I8042Device {
|
||||||
I8042Device {
|
I8042Device { reset_evt }
|
||||||
reset_evt,
|
|
||||||
vcpus_kill_signalled,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,13 +38,6 @@ impl BusDevice for I8042Device {
|
|||||||
if let Err(e) = self.reset_evt.write(1) {
|
if let Err(e) = self.reset_evt.write(1) {
|
||||||
error!("Error triggering i8042 reset event: {}", e);
|
error!("Error triggering i8042 reset event: {}", e);
|
||||||
}
|
}
|
||||||
// Spin until we are sure the reset_evt has been handled and that when
|
|
||||||
// we return from the KVM_RUN we will exit rather than re-enter the guest.
|
|
||||||
while !self.vcpus_kill_signalled.load(Ordering::SeqCst) {
|
|
||||||
// This is more effective than thread::yield_now() at
|
|
||||||
// avoiding a priority inversion with the VMM thread
|
|
||||||
thread::sleep(std::time::Duration::from_millis(1));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
None
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user