mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
93d7e01b41 | ||
|
|
4fe593e12d | ||
|
|
4203a61947 | ||
|
|
e397e739bf | ||
|
|
2724edd1e5 | ||
|
|
269844da73 | ||
|
|
a299a10874 | ||
|
|
015941e294 | ||
|
|
ca6fe2a98e | ||
|
|
f4090b0196 | ||
|
|
3432c0ce5e | ||
|
|
ec70af1606 | ||
|
|
bb0d82c365 | ||
|
|
2d98a16d05 | ||
|
|
cd1a645421 | ||
|
|
4485210de5 | ||
|
|
0aa858c266 | ||
|
|
499e8433c3 | ||
|
|
ff27b00f5a | ||
|
|
d09af361bc |
@@ -1,3 +0,0 @@
|
|||||||
[profile.default]
|
|
||||||
# Don't let one individual test run for more than 10 minutes
|
|
||||||
slow-timeout = { period = "60s", terminate-after = 10 }
|
|
||||||
76
.github/dependabot.yml
vendored
76
.github/dependabot.yml
vendored
@@ -1,70 +1,18 @@
|
|||||||
version: 2
|
version: 2
|
||||||
updates:
|
updates:
|
||||||
- package-ecosystem: cargo
|
- package-ecosystem: cargo
|
||||||
directories:
|
|
||||||
- "/"
|
|
||||||
- "/fuzz"
|
|
||||||
schedule:
|
|
||||||
interval: weekly
|
|
||||||
allow:
|
|
||||||
- dependency-name: "acpi_tables"
|
|
||||||
- dependency-name: "kvm-bindings"
|
|
||||||
- dependency-name: "kvm-ioctls"
|
|
||||||
- dependency-name: "linux-loader"
|
|
||||||
- dependency-name: "micro_http"
|
|
||||||
- dependency-name: "mshv-bindings"
|
|
||||||
- dependency-name: "mshv-ioctls"
|
|
||||||
- dependency-name: "seccompiler"
|
|
||||||
- dependency-name: "vfio-bindings"
|
|
||||||
- dependency-name: "vfio-ioctls"
|
|
||||||
- dependency-name: "vfio_user"
|
|
||||||
- dependency-name: "vhost"
|
|
||||||
- dependency-name: "vhost-user-backend"
|
|
||||||
- dependency-name: "virtio-bindings"
|
|
||||||
- dependency-name: "virtio-queue"
|
|
||||||
- dependency-name: "vm-fdt"
|
|
||||||
- dependency-name: "vm-memory"
|
|
||||||
- dependency-name: "vmm-sys-util"
|
|
||||||
groups:
|
|
||||||
rust-vmm:
|
|
||||||
patterns:
|
|
||||||
- "*"
|
|
||||||
- package-ecosystem: cargo
|
|
||||||
directories:
|
|
||||||
- "/"
|
|
||||||
- "/fuzz"
|
|
||||||
schedule:
|
|
||||||
interval: weekly
|
|
||||||
allow:
|
|
||||||
- dependency-type: all
|
|
||||||
ignore:
|
|
||||||
- dependency-name: "acpi_tables"
|
|
||||||
- dependency-name: "kvm-bindings"
|
|
||||||
- dependency-name: "kvm-ioctls"
|
|
||||||
- dependency-name: "linux-loader"
|
|
||||||
- dependency-name: "micro_http"
|
|
||||||
- dependency-name: "mshv-bindings"
|
|
||||||
- dependency-name: "mshv-ioctls"
|
|
||||||
- dependency-name: "seccompiler"
|
|
||||||
- dependency-name: "vfio-bindings"
|
|
||||||
- dependency-name: "vfio-ioctls"
|
|
||||||
- dependency-name: "vfio_user"
|
|
||||||
- dependency-name: "vhost"
|
|
||||||
- dependency-name: "vhost-user-backend"
|
|
||||||
- dependency-name: "virtio-bindings"
|
|
||||||
- dependency-name: "virtio-queue"
|
|
||||||
- dependency-name: "vm-fdt"
|
|
||||||
- dependency-name: "vm-memory"
|
|
||||||
- dependency-name: "vmm-sys-util"
|
|
||||||
groups:
|
|
||||||
non-rust-vmm:
|
|
||||||
patterns:
|
|
||||||
- "*"
|
|
||||||
# Makes it possible to have another config for the same directory.
|
|
||||||
# https://github.com/dependabot/dependabot-core/issues/1778#issuecomment-1988140219
|
|
||||||
target-branch: main
|
|
||||||
- package-ecosystem: github-actions
|
|
||||||
directory: "/"
|
directory: "/"
|
||||||
schedule:
|
schedule:
|
||||||
interval: daily
|
interval: daily
|
||||||
open-pull-requests-limit: 1
|
open-pull-requests-limit: 1
|
||||||
|
allow:
|
||||||
|
- dependency-type: direct
|
||||||
|
- dependency-type: indirect
|
||||||
|
- package-ecosystem: cargo
|
||||||
|
directory: "/fuzz"
|
||||||
|
schedule:
|
||||||
|
interval: daily
|
||||||
|
open-pull-requests-limit: 1
|
||||||
|
allow:
|
||||||
|
- dependency-type: direct
|
||||||
|
- dependency-type: indirect
|
||||||
|
|||||||
7
.github/workflows/audit.yaml
vendored
7
.github/workflows/audit.yaml
vendored
@@ -4,13 +4,12 @@ on:
|
|||||||
paths:
|
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@v6
|
- 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 }}
|
||||||
43
.github/workflows/build.yaml
vendored
43
.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.89.0"
|
- "1.62"
|
||||||
target:
|
target:
|
||||||
- x86_64-unknown-linux-gnu
|
- x86_64-unknown-linux-gnu
|
||||||
- x86_64-unknown-linux-musl
|
- x86_64-unknown-linux-musl
|
||||||
steps:
|
steps:
|
||||||
- name: Code checkout
|
- name: Code checkout
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
@@ -29,46 +27,29 @@ jobs:
|
|||||||
run: sudo apt install -y musl-tools
|
run: sudo apt install -y musl-tools
|
||||||
|
|
||||||
- name: Install Rust toolchain (${{ matrix.rust }})
|
- name: Install Rust toolchain (${{ matrix.rust }})
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
toolchain: ${{ matrix.rust }}
|
toolchain: ${{ matrix.rust }}
|
||||||
target: ${{ matrix.target }}
|
target: ${{ matrix.target }}
|
||||||
|
override: true
|
||||||
|
|
||||||
- name: Build (default features)
|
- name: Build (default features)
|
||||||
run: cargo build --locked --bin cloud-hypervisor
|
run: cargo rustc --locked --bin cloud-hypervisor -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||||
|
|
||||||
- name: Build (kvm)
|
- name: Build (kvm)
|
||||||
run: cargo build --locked --bin cloud-hypervisor --no-default-features --features "kvm"
|
run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||||
|
|
||||||
- name: Build (default features + tdx)
|
- name: Build (default features + tdx)
|
||||||
run: cargo build --locked --bin cloud-hypervisor --features "tdx"
|
run: cargo rustc --locked --bin cloud-hypervisor --features "tdx" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||||
|
|
||||||
- name: Build (default features + dbus_api)
|
|
||||||
run: cargo build --locked --bin cloud-hypervisor --features "dbus_api"
|
|
||||||
|
|
||||||
- name: Build (default features + guest_debug)
|
- name: Build (default features + guest_debug)
|
||||||
run: cargo build --locked --bin cloud-hypervisor --features "guest_debug"
|
run: cargo rustc --locked --bin cloud-hypervisor --features "guest_debug" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||||
|
|
||||||
- name: Build (default features + pvmemcontrol)
|
|
||||||
run: cargo build --locked --bin cloud-hypervisor --features "pvmemcontrol"
|
|
||||||
|
|
||||||
- name: Build (default features + fw_cfg)
|
|
||||||
run: cargo build --locked --bin cloud-hypervisor --features "fw_cfg"
|
|
||||||
|
|
||||||
- name: Build (default features + ivshmem)
|
|
||||||
run: cargo build --locked --bin cloud-hypervisor --features "ivshmem"
|
|
||||||
|
|
||||||
- name: Build (mshv)
|
- name: Build (mshv)
|
||||||
run: cargo build --locked --bin cloud-hypervisor --no-default-features --features "mshv"
|
run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "mshv" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||||
|
|
||||||
- name: Build (sev_snp)
|
|
||||||
run: cargo build --locked --bin cloud-hypervisor --no-default-features --features "sev_snp"
|
|
||||||
|
|
||||||
- name: Build (igvm)
|
|
||||||
run: cargo build --locked --bin cloud-hypervisor --no-default-features --features "igvm"
|
|
||||||
|
|
||||||
- name: Build (mshv + kvm)
|
- name: Build (mshv + kvm)
|
||||||
run: cargo build --locked --bin cloud-hypervisor --no-default-features --features "mshv,kvm"
|
run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "mshv,kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||||
|
|
||||||
- name: Release Build (default features)
|
- name: Release Build (default features)
|
||||||
run: cargo build --locked --all --release --target=${{ matrix.target }}
|
run: cargo build --locked --all --release --target=${{ matrix.target }}
|
||||||
|
|||||||
10
.github/workflows/dco.yaml
vendored
10
.github/workflows/dco.yaml
vendored
@@ -1,18 +1,16 @@
|
|||||||
name: DCO
|
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@v6
|
- uses: actions/checkout@v2
|
||||||
- name: Set up Python 3.x
|
- name: Set up Python 3.x
|
||||||
uses: actions/setup-python@v6
|
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@v6
|
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=20251114-0
|
type=raw,value={{date 'YYYYMMDD'}}-0
|
||||||
type=sha
|
type=sha
|
||||||
|
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
if: ${{ github.event_name == 'push' }}
|
if: ${{ github.event_name == 'push' }}
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v2
|
||||||
with:
|
with:
|
||||||
file: ./resources/Dockerfile
|
file: ./resources/Dockerfile
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
@@ -55,7 +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@v6
|
|
||||||
- name: Install Rust toolchain (${{ matrix.rust }})
|
|
||||||
uses: dtolnay/rust-toolchain@stable
|
|
||||||
with:
|
|
||||||
toolchain: ${{ matrix.rust }}
|
|
||||||
target: ${{ matrix.target }}
|
|
||||||
components: rustfmt
|
|
||||||
- name: Formatting (rustfmt)
|
|
||||||
run: cargo fmt --all -- --check
|
|
||||||
- name: Formatting (fuzz) (rustfmt)
|
|
||||||
run: cargo fmt --all --manifest-path fuzz/Cargo.toml -- --check
|
|
||||||
21
.github/workflows/fuzz-build.yaml
vendored
21
.github/workflows/fuzz-build.yaml
vendored
@@ -1,11 +1,9 @@
|
|||||||
name: Cloud Hypervisor Cargo Fuzz Build
|
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@v6
|
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@v6
|
|
||||||
with:
|
|
||||||
ref: ${{ github.event.pull_request.head.sha }}
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Set up Python 3.10
|
|
||||||
uses: actions/setup-python@v6
|
|
||||||
with:
|
|
||||||
python-version: "3.10"
|
|
||||||
- name: Install dependencies
|
|
||||||
run: |
|
|
||||||
python -m pip install --upgrade pip
|
|
||||||
pip install --upgrade gitlint
|
|
||||||
- name: Lint git commit messages
|
|
||||||
run: |
|
|
||||||
gitlint --commits origin/$GITHUB_BASE_REF..
|
|
||||||
25
.github/workflows/hadolint.yaml
vendored
25
.github/workflows/hadolint.yaml
vendored
@@ -1,25 +0,0 @@
|
|||||||
name: Lint Dockerfile
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
paths:
|
|
||||||
- resources/Dockerfile
|
|
||||||
pull_request:
|
|
||||||
paths:
|
|
||||||
- resources/Dockerfile
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
hadolint:
|
|
||||||
name: Run Hadolint Dockerfile Linter
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v6
|
|
||||||
|
|
||||||
- name: Lint Dockerfile
|
|
||||||
uses: hadolint/hadolint-action@master
|
|
||||||
with:
|
|
||||||
dockerfile: ./resources/Dockerfile
|
|
||||||
format: tty
|
|
||||||
no-fail: false
|
|
||||||
verbose: true
|
|
||||||
failure-threshold: info
|
|
||||||
54
.github/workflows/integration-arm64.yaml
vendored
54
.github/workflows/integration-arm64.yaml
vendored
@@ -1,54 +0,0 @@
|
|||||||
name: Cloud Hypervisor Tests (ARM64)
|
|
||||||
on: [pull_request, merge_group]
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.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@v6
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Run unit tests (musl)
|
|
||||||
run: scripts/dev_cli.sh tests --unit --libc musl
|
|
||||||
- name: Load openvswitch module
|
|
||||||
run: sudo modprobe openvswitch
|
|
||||||
- name: Run integration tests (musl)
|
|
||||||
timeout-minutes: 60
|
|
||||||
run: scripts/dev_cli.sh tests --integration --libc musl
|
|
||||||
- name: Install Azure CLI
|
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
|
||||||
run: |
|
|
||||||
sudo apt install -y ca-certificates curl apt-transport-https lsb-release gnupg
|
|
||||||
curl -sL https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/microsoft.gpg > /dev/null
|
|
||||||
echo "deb [arch=arm64] https://packages.microsoft.com/repos/azure-cli/ bookworm main" | sudo tee /etc/apt/sources.list.d/azure-cli.list
|
|
||||||
sudo apt update
|
|
||||||
sudo apt install -y azure-cli
|
|
||||||
- name: Download Windows image
|
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
IMG_BASENAME=windows-11-iot-enterprise-aarch64.raw
|
|
||||||
IMG_PATH=$HOME/workloads/$IMG_BASENAME
|
|
||||||
IMG_GZ_PATH=$HOME/workloads/$IMG_BASENAME.gz
|
|
||||||
IMG_GZ_BLOB_NAME=windows-11-iot-enterprise-aarch64-9-min.raw.gz
|
|
||||||
cp "scripts/$IMG_BASENAME.sha1" "$HOME/workloads/"
|
|
||||||
pushd "$HOME/workloads"
|
|
||||||
if sha1sum "$IMG_BASENAME.sha1" --check; then
|
|
||||||
exit
|
|
||||||
fi
|
|
||||||
popd
|
|
||||||
mkdir -p "$HOME/workloads"
|
|
||||||
az storage blob download --container-name private-images --file "$IMG_GZ_PATH" --name "$IMG_GZ_BLOB_NAME" --connection-string "${{ secrets.CH_PRIVATE_IMAGES }}"
|
|
||||||
gzip -d $IMG_GZ_PATH
|
|
||||||
- name: Run Windows guest integration tests
|
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
|
||||||
timeout-minutes: 30
|
|
||||||
run: scripts/dev_cli.sh tests --integration-windows --libc musl
|
|
||||||
22
.github/workflows/integration-metrics.yaml
vendored
22
.github/workflows/integration-metrics.yaml
vendored
@@ -1,22 +0,0 @@
|
|||||||
name: Cloud Hypervisor Tests (Metrics)
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
name: Tests (Metrics)
|
|
||||||
runs-on: bare-metal-9950x
|
|
||||||
env:
|
|
||||||
METRICS_PUBLISH_KEY: ${{ secrets.METRICS_PUBLISH_KEY }}
|
|
||||||
steps:
|
|
||||||
- name: Code checkout
|
|
||||||
uses: actions/checkout@v6
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Run metrics tests
|
|
||||||
timeout-minutes: 60
|
|
||||||
run: scripts/dev_cli.sh tests --metrics -- -- --report-file /root/workloads/metrics.json
|
|
||||||
- name: Upload metrics report
|
|
||||||
run: 'curl -X PUT https://ch-metrics.azurewebsites.net/api/publishmetrics -H "x-functions-key: $METRICS_PUBLISH_KEY" -T ~/workloads/metrics.json'
|
|
||||||
25
.github/workflows/integration-rate-limiter.yaml
vendored
25
.github/workflows/integration-rate-limiter.yaml
vendored
@@ -1,25 +0,0 @@
|
|||||||
name: Cloud Hypervisor Tests (Rate-Limiter)
|
|
||||||
on: [merge_group, pull_request]
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.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@v6
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Run rate-limiter integration tests
|
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
|
||||||
timeout-minutes: 20
|
|
||||||
run: scripts/dev_cli.sh tests --integration-rate-limiter
|
|
||||||
- name: Skipping build for PR
|
|
||||||
if: ${{ github.event_name == 'pull_request' }}
|
|
||||||
run: echo "Skipping build for PR"
|
|
||||||
33
.github/workflows/integration-vfio.yaml
vendored
33
.github/workflows/integration-vfio.yaml
vendored
@@ -1,33 +0,0 @@
|
|||||||
name: Cloud Hypervisor Tests (VFIO)
|
|
||||||
on: [merge_group, pull_request]
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.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@v6
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Run VFIO integration tests
|
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
|
||||||
timeout-minutes: 15
|
|
||||||
run: scripts/dev_cli.sh tests --integration-vfio
|
|
||||||
# Most tests are failing with musl see #6790
|
|
||||||
# - name: Run VFIO integration tests for musl
|
|
||||||
# if: ${{ github.event_name != 'pull_request' }}
|
|
||||||
# timeout-minutes: 15
|
|
||||||
# run: scripts/dev_cli.sh tests --integration-vfio --libc musl
|
|
||||||
- name: Skipping build for PR
|
|
||||||
if: ${{ github.event_name == 'pull_request' }}
|
|
||||||
run: echo "Skipping build for PR"
|
|
||||||
50
.github/workflows/integration-windows.yaml
vendored
50
.github/workflows/integration-windows.yaml
vendored
@@ -1,50 +0,0 @@
|
|||||||
name: Cloud Hypervisor Tests (Windows Guest)
|
|
||||||
on: [merge_group, pull_request]
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.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@v6
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Install Docker
|
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
|
||||||
run: |
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get -y install ca-certificates curl gnupg
|
|
||||||
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
|
|
||||||
sudo chmod a+r /usr/share/keyrings/docker-archive-keyring.gpg
|
|
||||||
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt install -y docker-ce docker-ce-cli
|
|
||||||
- name: Install Azure CLI
|
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
|
||||||
run: |
|
|
||||||
sudo apt install -y ca-certificates curl apt-transport-https lsb-release gnupg
|
|
||||||
curl -sL https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/microsoft.gpg > /dev/null
|
|
||||||
echo "deb [arch=amd64] https://packages.microsoft.com/repos/azure-cli/ jammy main" | sudo tee /etc/apt/sources.list.d/azure-cli.list
|
|
||||||
sudo apt update
|
|
||||||
sudo apt install -y azure-cli
|
|
||||||
- name: Download Windows image
|
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
|
||||||
run: |
|
|
||||||
mkdir $HOME/workloads
|
|
||||||
az storage blob download --container-name private-images --file "$HOME/workloads/windows-server-2022-amd64-2.raw" --name windows-server-2022-amd64-2.raw --connection-string "${{ secrets.CH_PRIVATE_IMAGES }}"
|
|
||||||
- name: Run Windows guest integration tests
|
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
|
||||||
timeout-minutes: 15
|
|
||||||
run: scripts/dev_cli.sh tests --integration-windows
|
|
||||||
- name: Run Windows guest integration tests for musl
|
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
|
||||||
timeout-minutes: 15
|
|
||||||
run: scripts/dev_cli.sh tests --integration-windows --libc musl
|
|
||||||
- name: Skipping build for PR
|
|
||||||
if: ${{ github.event_name == 'pull_request' }}
|
|
||||||
run: echo "Skipping build for PR"
|
|
||||||
52
.github/workflows/integration-x86-64.yaml
vendored
52
.github/workflows/integration-x86-64.yaml
vendored
@@ -1,52 +0,0 @@
|
|||||||
name: Cloud Hypervisor Tests (x86-64)
|
|
||||||
on: [pull_request, merge_group]
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.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@v6
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Install Docker
|
|
||||||
if: ${{ github.event_name != 'pull_request' || (matrix.runner == 'garm-jammy' && matrix.libc == 'gnu') }}
|
|
||||||
run: |
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get -y install ca-certificates curl gnupg
|
|
||||||
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
|
|
||||||
sudo chmod a+r /usr/share/keyrings/docker-archive-keyring.gpg
|
|
||||||
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt install -y docker-ce docker-ce-cli
|
|
||||||
- name: Prepare for VDPA
|
|
||||||
if: ${{ github.event_name != 'pull_request' || (matrix.runner == 'garm-jammy' && matrix.libc == 'gnu') }}
|
|
||||||
run: scripts/prepare_vdpa.sh
|
|
||||||
- name: Run unit tests
|
|
||||||
if: ${{ github.event_name != 'pull_request' || (matrix.runner == 'garm-jammy' && matrix.libc == 'gnu') }}
|
|
||||||
run: scripts/dev_cli.sh tests --unit --libc ${{ matrix.libc }}
|
|
||||||
- name: Load openvswitch module
|
|
||||||
if: ${{ github.event_name != 'pull_request' || (matrix.runner == 'garm-jammy' && matrix.libc == 'gnu') }}
|
|
||||||
run: sudo modprobe openvswitch
|
|
||||||
- name: Run integration tests
|
|
||||||
if: ${{ github.event_name != 'pull_request' || (matrix.runner == 'garm-jammy' && matrix.libc == 'gnu') }}
|
|
||||||
timeout-minutes: 40
|
|
||||||
run: scripts/dev_cli.sh tests --integration --libc ${{ matrix.libc }}
|
|
||||||
- name: Run live-migration integration tests
|
|
||||||
if: ${{ github.event_name != 'pull_request' || (matrix.runner == 'garm-jammy' && matrix.libc == 'gnu') }}
|
|
||||||
timeout-minutes: 20
|
|
||||||
run: scripts/dev_cli.sh tests --integration-live-migration --libc ${{ matrix.libc }}
|
|
||||||
- name: Skipping build for PR
|
|
||||||
if: ${{ github.event_name == 'pull_request' && matrix.runner != 'garm-jammy' && matrix.libc != 'gnu' }}
|
|
||||||
run: echo "Skipping build for PR"
|
|
||||||
45
.github/workflows/lychee.yaml
vendored
45
.github/workflows/lychee.yaml
vendored
@@ -1,45 +0,0 @@
|
|||||||
name: Link Check (lychee)
|
|
||||||
on: pull_request
|
|
||||||
jobs:
|
|
||||||
link_check:
|
|
||||||
name: Link Check
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Code checkout
|
|
||||||
uses: actions/checkout@v6
|
|
||||||
with:
|
|
||||||
# Fetch the entire history so git diff can compare against the base branch
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Get changed files in PR
|
|
||||||
id: changed-files
|
|
||||||
uses: tj-actions/changed-files@v47 # Using a dedicated action for robustness
|
|
||||||
with:
|
|
||||||
# Compare the HEAD of the PR with the merge-base (where the PR branches off)
|
|
||||||
base_sha: ${{ github.event.pull_request.base.sha }}
|
|
||||||
|
|
||||||
# NEW STEP: Print all changed-files outputs for verification
|
|
||||||
- name: Verify Changed Files
|
|
||||||
run: |
|
|
||||||
echo "--- tj-actions/changed-files Outputs ---"
|
|
||||||
echo "any_changed: ${{ steps.changed-files.outputs.any_changed }}"
|
|
||||||
echo "all_changed_files: ${{ steps.changed-files.outputs.all_changed_files }}"
|
|
||||||
echo "added_files: ${{ steps.changed-files.outputs.added_files }}"
|
|
||||||
echo "modified_files: ${{ steps.changed-files.outputs.modified_files }}"
|
|
||||||
echo "deleted_files: ${{ steps.changed-files.outputs.deleted_files }}"
|
|
||||||
echo "renamed_files: ${{ steps.changed-files.outputs.renamed_files }}"
|
|
||||||
echo "----------------------------------------"
|
|
||||||
# This will also show if the all_changed_files string is empty or not
|
|
||||||
if [ -n "${{ steps.changed-files.outputs.all_changed_files }}" ]; then
|
|
||||||
echo "Detected changes: all_changed_files output is NOT empty."
|
|
||||||
else
|
|
||||||
echo "No changes detected: all_changed_files output IS empty."
|
|
||||||
fi
|
|
||||||
- name: Link Availability Check (Diff Only)
|
|
||||||
# MODIFIED: Only run lychee if the 'all_changed_files' output is not an empty string
|
|
||||||
if: ${{ steps.changed-files.outputs.all_changed_files != '' }}
|
|
||||||
uses: lycheeverse/lychee-action@master
|
|
||||||
with:
|
|
||||||
# Pass the space-separated list of changed files to lychee
|
|
||||||
args: --verbose --config .lychee.toml ${{ steps.changed-files.outputs.all_changed_files }}
|
|
||||||
failIfEmpty: false
|
|
||||||
fail: true
|
|
||||||
249
.github/workflows/mshv-infra.yaml
vendored
249
.github/workflows/mshv-infra.yaml
vendored
@@ -1,249 +0,0 @@
|
|||||||
name: MSHV Infra Setup
|
|
||||||
on:
|
|
||||||
workflow_call:
|
|
||||||
inputs:
|
|
||||||
ARCH:
|
|
||||||
description: 'Architecture for the VM'
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
KEY:
|
|
||||||
description: 'SSH Key Name'
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
OS_DISK_SIZE:
|
|
||||||
description: 'OS Disk Size in GB'
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
RG:
|
|
||||||
description: 'Resource Group Name'
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
VM_SKU:
|
|
||||||
description: 'VM SKU'
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
secrets:
|
|
||||||
MI_CLIENT_ID:
|
|
||||||
required: true
|
|
||||||
RUNNER_RG:
|
|
||||||
required: true
|
|
||||||
STORAGE_ACCOUNT_PATHS:
|
|
||||||
required: true
|
|
||||||
ARCH_SOURCE_PATH:
|
|
||||||
required: true
|
|
||||||
USERNAME:
|
|
||||||
required: true
|
|
||||||
outputs:
|
|
||||||
RG_NAME:
|
|
||||||
description: 'Resource group of the VM'
|
|
||||||
value: ${{ jobs.infra-setup.outputs.RG_NAME }}
|
|
||||||
VM_NAME:
|
|
||||||
description: 'Name of the VM'
|
|
||||||
value: ${{ jobs.infra-setup.outputs.VM_NAME }}
|
|
||||||
PRIVATE_IP:
|
|
||||||
description: 'Private IP of the VM'
|
|
||||||
value: ${{ jobs.infra-setup.outputs.PRIVATE_IP }}
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
jobs:
|
|
||||||
infra-setup:
|
|
||||||
name: ${{ inputs.ARCH }} VM Provision
|
|
||||||
runs-on: mshv
|
|
||||||
continue-on-error: true
|
|
||||||
outputs:
|
|
||||||
RG_NAME: ${{ steps.rg-setup.outputs.RG_NAME }}
|
|
||||||
VM_NAME: ${{ steps.vm-setup.outputs.VM_NAME }}
|
|
||||||
PRIVATE_IP: ${{ steps.get-vm-ip.outputs.PRIVATE_IP }}
|
|
||||||
steps:
|
|
||||||
- name: Install & login to AZ CLI
|
|
||||||
env:
|
|
||||||
MI_CLIENT_ID: ${{ secrets.MI_CLIENT_ID }}
|
|
||||||
run: |
|
|
||||||
set -e
|
|
||||||
echo "Installing Azure CLI if not already installed"
|
|
||||||
if ! command -v az &>/dev/null; then
|
|
||||||
curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash
|
|
||||||
else
|
|
||||||
echo "Azure CLI already installed"
|
|
||||||
fi
|
|
||||||
az --version
|
|
||||||
echo "Logging into Azure CLI using Managed Identity"
|
|
||||||
az login --identity --client-id ${MI_CLIENT_ID}
|
|
||||||
|
|
||||||
- name: Get Location
|
|
||||||
id: get-location
|
|
||||||
env:
|
|
||||||
SKU: ${{ inputs.VM_SKU }}
|
|
||||||
STORAGE_ACCOUNT_PATHS: ${{ secrets.STORAGE_ACCOUNT_PATHS }}
|
|
||||||
run: |
|
|
||||||
set -e
|
|
||||||
# Extract vCPU count from SKU (e.g., "Standard_D2s_v3" => 2)
|
|
||||||
vcpu=$(echo "$SKU" | sed -n 's/^Standard_[A-Za-z]\+\([0-9]\+\).*/\1/p')
|
|
||||||
if [[ -z "$vcpu" ]]; then
|
|
||||||
echo "Cannot extract vCPU count from SKU: $SKU"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
SUPPORTED_LOCATIONS=$(echo "$STORAGE_ACCOUNT_PATHS" | jq -r 'to_entries[] | .key')
|
|
||||||
|
|
||||||
for location in $SUPPORTED_LOCATIONS; do
|
|
||||||
family=$(az vm list-skus --size "$SKU" --location "$location" --resource-type "virtualMachines" --query '[0].family' -o tsv)
|
|
||||||
if [[ -z "$family" ]]; then
|
|
||||||
echo "Cannot determine VM family for SKU: $SKU in $location"
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
usage=$(az vm list-usage --location "$location" --query "[?name.value=='$family'] | [0]" -o json)
|
|
||||||
current=$(echo "$usage" | jq -r '.currentValue')
|
|
||||||
limit=$(echo "$usage" | jq -r '.limit')
|
|
||||||
|
|
||||||
if [[ $((limit - current)) -ge $vcpu ]]; then
|
|
||||||
echo "Sufficient quota found in $location"
|
|
||||||
echo "location=$location" >> "$GITHUB_OUTPUT"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
echo "No location found with sufficient vCPU quota for SKU: $SKU"
|
|
||||||
exit 1
|
|
||||||
|
|
||||||
- name: Create Resource Group
|
|
||||||
id: rg-setup
|
|
||||||
env:
|
|
||||||
LOCATION: ${{ steps.get-location.outputs.location }}
|
|
||||||
RG: ${{ inputs.RG }}
|
|
||||||
STORAGE_ACCOUNT_PATHS: ${{ secrets.STORAGE_ACCOUNT_PATHS }}
|
|
||||||
run: |
|
|
||||||
set -e
|
|
||||||
echo "Creating Resource Group: $RG"
|
|
||||||
# Create the resource group
|
|
||||||
echo "Creating resource group in location: ${LOCATION}"
|
|
||||||
az group create --name ${RG} --location ${LOCATION}
|
|
||||||
echo "RG_NAME=${RG}" >> $GITHUB_OUTPUT
|
|
||||||
echo "Resource group created successfully."
|
|
||||||
|
|
||||||
- name: Generate SSH Key
|
|
||||||
id: generate-ssh-key
|
|
||||||
env:
|
|
||||||
KEY: ${{ inputs.KEY }}
|
|
||||||
run: |
|
|
||||||
set -e
|
|
||||||
echo "Generating SSH key: $KEY"
|
|
||||||
mkdir -p ~/.ssh
|
|
||||||
ssh-keygen -t rsa -b 4096 -f ~/.ssh/${KEY} -N ""
|
|
||||||
|
|
||||||
- name: Create VM
|
|
||||||
id: vm-setup
|
|
||||||
env:
|
|
||||||
KEY: ${{ inputs.KEY }}
|
|
||||||
LOCATION: ${{ steps.get-location.outputs.location }}
|
|
||||||
OS_DISK_SIZE: ${{ inputs.OS_DISK_SIZE }}
|
|
||||||
RG: ${{ inputs.RG }}
|
|
||||||
RUNNER_RG: ${{ secrets.RUNNER_RG }}
|
|
||||||
USERNAME: ${{ secrets.USERNAME }}
|
|
||||||
VM_SKU: ${{ inputs.VM_SKU }}
|
|
||||||
VM_IMAGE_NAME: ${{ inputs.ARCH }}_${{ steps.get-location.outputs.location }}_image
|
|
||||||
VM_NAME: ${{ inputs.ARCH }}_${{ steps.get-location.outputs.location }}_${{ github.run_id }}
|
|
||||||
run: |
|
|
||||||
set -e
|
|
||||||
echo "Creating $VM_SKU VM: $VM_NAME"
|
|
||||||
|
|
||||||
# Extract subnet ID from the runner VM
|
|
||||||
echo "Retrieving subnet ID..."
|
|
||||||
SUBNET_ID=$(az network vnet list --resource-group ${RUNNER_RG} --query "[?contains(location, '${LOCATION}')].{SUBNETS:subnets}" | jq -r ".[0].SUBNETS[0].id")
|
|
||||||
if [[ -z "${SUBNET_ID}" ]]; then
|
|
||||||
echo "ERROR: Failed to retrieve Subnet ID."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Extract image ID from the runner VM
|
|
||||||
echo "Retrieving image ID..."
|
|
||||||
IMAGE_ID=$(az image show --resource-group ${RUNNER_RG} --name ${VM_IMAGE_NAME} --query "id" -o tsv)
|
|
||||||
if [[ -z "${IMAGE_ID}" ]]; then
|
|
||||||
echo "ERROR: Failed to retrieve Image ID."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Create VM
|
|
||||||
az vm create \
|
|
||||||
--resource-group ${RG} \
|
|
||||||
--name ${VM_NAME} \
|
|
||||||
--subnet ${SUBNET_ID} \
|
|
||||||
--size ${VM_SKU} \
|
|
||||||
--location ${LOCATION} \
|
|
||||||
--image ${IMAGE_ID} \
|
|
||||||
--os-disk-size-gb ${OS_DISK_SIZE} \
|
|
||||||
--public-ip-sku Standard \
|
|
||||||
--storage-sku Premium_LRS \
|
|
||||||
--public-ip-address "" \
|
|
||||||
--admin-username ${USERNAME} \
|
|
||||||
--ssh-key-value ~/.ssh/${KEY}.pub \
|
|
||||||
--security-type Standard \
|
|
||||||
--output json
|
|
||||||
|
|
||||||
az vm boot-diagnostics enable --name ${VM_NAME} --resource-group ${RG}
|
|
||||||
|
|
||||||
echo "VM_NAME=${VM_NAME}" >> $GITHUB_OUTPUT
|
|
||||||
echo "VM creation process completed successfully."
|
|
||||||
|
|
||||||
- name: Get VM Private IP
|
|
||||||
id: get-vm-ip
|
|
||||||
env:
|
|
||||||
RG: ${{ inputs.RG }}
|
|
||||||
VM_NAME: ${{ inputs.ARCH }}_${{ steps.get-location.outputs.location }}_${{ github.run_id }}
|
|
||||||
run: |
|
|
||||||
set -e
|
|
||||||
echo "Retrieving VM Private IP address..."
|
|
||||||
# Retrieve VM Private IP address
|
|
||||||
PRIVATE_IP=$(az vm show -g ${RG} -n ${VM_NAME} -d --query privateIps -o tsv)
|
|
||||||
if [[ -z "$PRIVATE_IP" ]]; then
|
|
||||||
echo "ERROR: Failed to retrieve private IP address."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo "PRIVATE_IP=$PRIVATE_IP" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Wait for SSH availability
|
|
||||||
env:
|
|
||||||
KEY: ${{ inputs.KEY }}
|
|
||||||
PRIVATE_IP: ${{ steps.get-vm-ip.outputs.PRIVATE_IP }}
|
|
||||||
USERNAME: ${{ secrets.USERNAME }}
|
|
||||||
run: |
|
|
||||||
echo "Waiting for SSH to be accessible..."
|
|
||||||
timeout 120 bash -c 'until ssh -o StrictHostKeyChecking=no -i ~/.ssh/${KEY} ${USERNAME}@${PRIVATE_IP} "exit" 2>/dev/null; do sleep 5; done'
|
|
||||||
echo "VM is accessible!"
|
|
||||||
|
|
||||||
- name: Remove Old Host Key
|
|
||||||
env:
|
|
||||||
PRIVATE_IP: ${{ steps.get-vm-ip.outputs.PRIVATE_IP }}
|
|
||||||
run: |
|
|
||||||
set -e
|
|
||||||
echo "Removing the old host key"
|
|
||||||
ssh-keygen -R $PRIVATE_IP
|
|
||||||
|
|
||||||
- name: SSH into VM and Install Dependencies
|
|
||||||
env:
|
|
||||||
KEY: ${{ inputs.KEY }}
|
|
||||||
PRIVATE_IP: ${{ steps.get-vm-ip.outputs.PRIVATE_IP }}
|
|
||||||
USERNAME: ${{ secrets.USERNAME }}
|
|
||||||
run: |
|
|
||||||
set -e
|
|
||||||
ssh -i ~/.ssh/${KEY} -o StrictHostKeyChecking=no ${USERNAME}@${PRIVATE_IP} << EOF
|
|
||||||
set -e
|
|
||||||
echo "Logged in successfully."
|
|
||||||
echo "Installing dependencies..."
|
|
||||||
sudo tdnf install -y git moby-engine moby-cli clang llvm pkg-config make gcc glibc-devel
|
|
||||||
echo "Installing Rust..."
|
|
||||||
curl -sSf https://sh.rustup.rs | sh -s -- --default-toolchain stable --profile default -y
|
|
||||||
export PATH="\$HOME/.cargo/bin:\$PATH"
|
|
||||||
cargo --version
|
|
||||||
sudo mkdir -p /etc/docker/
|
|
||||||
echo '{"default-ulimits":{"nofile":{"Hard":65535,"Name":"nofile","Soft":65535}}}' | sudo tee /etc/docker/daemon.json
|
|
||||||
sudo systemctl stop docker
|
|
||||||
sudo systemctl enable docker.service
|
|
||||||
sudo systemctl enable containerd.service
|
|
||||||
sudo systemctl start docker
|
|
||||||
sudo groupadd -f docker
|
|
||||||
sudo usermod -a -G docker ${USERNAME}
|
|
||||||
sudo systemctl restart docker
|
|
||||||
EOF
|
|
||||||
131
.github/workflows/mshv-integration.yaml
vendored
131
.github/workflows/mshv-integration.yaml
vendored
@@ -1,131 +0,0 @@
|
|||||||
name: Cloud Hypervisor Tests (MSHV) (x86_64)
|
|
||||||
on: [pull_request_target, merge_group]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
infra-setup:
|
|
||||||
name: MSHV Infra Setup (x86_64)
|
|
||||||
uses: ./.github/workflows/mshv-infra.yaml
|
|
||||||
with:
|
|
||||||
ARCH: x86_64
|
|
||||||
KEY: azure_key_${{ github.run_id }}
|
|
||||||
OS_DISK_SIZE: 512
|
|
||||||
RG: MSHV-INTEGRATION-${{ github.run_id }}
|
|
||||||
VM_SKU: Standard_D16s_v5
|
|
||||||
secrets:
|
|
||||||
MI_CLIENT_ID: ${{ secrets.MSHV_MI_CLIENT_ID }}
|
|
||||||
RUNNER_RG: ${{ secrets.MSHV_RUNNER_RG }}
|
|
||||||
STORAGE_ACCOUNT_PATHS: ${{ secrets.MSHV_STORAGE_ACCOUNT_PATHS }}
|
|
||||||
ARCH_SOURCE_PATH: ${{ secrets.MSHV_X86_SOURCE_PATH }}
|
|
||||||
USERNAME: ${{ secrets.MSHV_USERNAME }}
|
|
||||||
|
|
||||||
run-tests:
|
|
||||||
name: Integration Tests (x86_64)
|
|
||||||
needs: infra-setup
|
|
||||||
if: ${{ always() && needs.infra-setup.result == 'success' }}
|
|
||||||
runs-on: mshv
|
|
||||||
continue-on-error: true
|
|
||||||
steps:
|
|
||||||
- name: Run integration tests
|
|
||||||
timeout-minutes: 60
|
|
||||||
env:
|
|
||||||
KEY: azure_key_${{ github.run_id }}
|
|
||||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
|
||||||
REPO_URL: https://github.com/cloud-hypervisor/cloud-hypervisor.git
|
|
||||||
REPO_DIR: cloud-hypervisor
|
|
||||||
PRIVATE_IP: ${{ needs.infra-setup.outputs.PRIVATE_IP }}
|
|
||||||
RG: MSHV-${{ github.run_id }}
|
|
||||||
USERNAME: ${{ secrets.MSHV_USERNAME }}
|
|
||||||
run: |
|
|
||||||
set -e
|
|
||||||
echo "Connecting to the VM via SSH..."
|
|
||||||
ssh -i ~/.ssh/${KEY} -o StrictHostKeyChecking=no ${USERNAME}@${PRIVATE_IP} << EOF
|
|
||||||
set -e
|
|
||||||
echo "Logged in successfully."
|
|
||||||
export PATH="\$HOME/.cargo/bin:\$PATH"
|
|
||||||
|
|
||||||
if [[ "${{ github.event_name }}" == "pull_request_target" ]]; then
|
|
||||||
git clone --depth 1 "$REPO_URL" "$REPO_DIR"
|
|
||||||
cd "$REPO_DIR"
|
|
||||||
git fetch origin pull/${{ github.event.pull_request.number }}/merge
|
|
||||||
git checkout FETCH_HEAD
|
|
||||||
else
|
|
||||||
git clone --depth 1 --single-branch --branch "${{ github.ref_name }}" "$REPO_URL" "$REPO_DIR"
|
|
||||||
cd "$REPO_DIR"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Loading VDPA kernel modules..."
|
|
||||||
sudo modprobe vdpa
|
|
||||||
sudo modprobe vhost_vdpa
|
|
||||||
sudo modprobe vdpa_sim
|
|
||||||
sudo modprobe vdpa_sim_blk
|
|
||||||
sudo modprobe vdpa_sim_net
|
|
||||||
|
|
||||||
echo "Creating VDPA devices..."
|
|
||||||
sudo vdpa dev add name vdpa-blk0 mgmtdev vdpasim_blk
|
|
||||||
sudo vdpa dev add name vdpa-blk1 mgmtdev vdpasim_blk
|
|
||||||
sudo vdpa dev add name vdpa-blk2 mgmtdev vdpasim_net
|
|
||||||
|
|
||||||
echo "Setting permissions..."
|
|
||||||
for i in 0 1 2; do
|
|
||||||
dev="/dev/vhost-vdpa-$i"
|
|
||||||
if [ -e "$dev" ]; then
|
|
||||||
sudo chown $USER:$USER "$dev"
|
|
||||||
sudo chmod 660 "$dev"
|
|
||||||
else
|
|
||||||
echo "Warning: Device $dev not found"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
sudo ./scripts/dev_cli.sh tests --hypervisor mshv --integration
|
|
||||||
EOF
|
|
||||||
|
|
||||||
- name: Dump dmesg
|
|
||||||
if: always()
|
|
||||||
continue-on-error: true
|
|
||||||
env:
|
|
||||||
KEY: azure_key_${{ github.run_id }}
|
|
||||||
PRIVATE_IP: ${{ needs.infra-setup.outputs.PRIVATE_IP }}
|
|
||||||
USERNAME: ${{ secrets.MSHV_USERNAME }}
|
|
||||||
run: |
|
|
||||||
ssh -i ~/.ssh/${KEY} -o StrictHostKeyChecking=no ${USERNAME}@${PRIVATE_IP} << EOF
|
|
||||||
sudo dmesg
|
|
||||||
EOF
|
|
||||||
|
|
||||||
- name: Dump serial console logs
|
|
||||||
if: always()
|
|
||||||
continue-on-error: true
|
|
||||||
env:
|
|
||||||
RG_NAME: ${{ needs.infra-setup.outputs.RG_NAME }}
|
|
||||||
VM_NAME: ${{ needs.infra-setup.outputs.VM_NAME }}
|
|
||||||
run: |
|
|
||||||
set -e
|
|
||||||
az vm boot-diagnostics get-boot-log --name "${VM_NAME}" --resource-group "${RG_NAME}" | jq -r
|
|
||||||
|
|
||||||
cleanup:
|
|
||||||
name: Cleanup
|
|
||||||
needs: run-tests
|
|
||||||
if: always()
|
|
||||||
runs-on: mshv
|
|
||||||
steps:
|
|
||||||
- name: Delete RG
|
|
||||||
env:
|
|
||||||
RG: MSHV-INTEGRATION-${{ github.run_id }}
|
|
||||||
run: |
|
|
||||||
if az group exists --name ${RG}; then
|
|
||||||
az group delete --name ${RG} --yes --no-wait
|
|
||||||
else
|
|
||||||
echo "Resource Group ${RG} does not exist. Skipping deletion."
|
|
||||||
fi
|
|
||||||
echo "Cleanup process completed."
|
|
||||||
|
|
||||||
- name: Delete SSH Key
|
|
||||||
env:
|
|
||||||
KEY: azure_key_${{ github.run_id }}
|
|
||||||
run: |
|
|
||||||
if [ -f ~/.ssh/${KEY} ]; then
|
|
||||||
rm -f ~/.ssh/${KEY} ~/.ssh/${KEY}.pub
|
|
||||||
echo "SSH key deleted successfully."
|
|
||||||
else
|
|
||||||
echo "SSH key does not exist. Skipping deletion."
|
|
||||||
fi
|
|
||||||
echo "Cleanup process completed."
|
|
||||||
14
.github/workflows/openapi.yaml
vendored
14
.github/workflows/openapi.yaml
vendored
@@ -1,14 +0,0 @@
|
|||||||
name: Cloud Hypervisor OpenAPI Validation
|
|
||||||
on: [pull_request, merge_group]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
Validate:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
container: openapitools/openapi-generator-cli
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v6
|
|
||||||
- name: Validate OpenAPI
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
run: |
|
|
||||||
/usr/local/bin/docker-entrypoint.sh validate -i vmm/src/api/openapi/cloud-hypervisor.yaml
|
|
||||||
32
.github/workflows/package-consistency.yaml
vendored
32
.github/workflows/package-consistency.yaml
vendored
@@ -1,32 +0,0 @@
|
|||||||
name: Cloud Hypervisor Consistency
|
|
||||||
on: [pull_request, merge_group]
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
name: Rust VMM Consistency Check
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Code checkout
|
|
||||||
uses: actions/checkout@v6
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: sudo apt install -y python3
|
|
||||||
|
|
||||||
- name: Install Rust toolchain stable
|
|
||||||
uses: dtolnay/rust-toolchain@stable
|
|
||||||
with:
|
|
||||||
toolchain: stable
|
|
||||||
|
|
||||||
- name: Check Rust VMM Package Consistency of root Workspace
|
|
||||||
run: python3 scripts/package-consistency-check.py github.com/rust-vmm
|
|
||||||
|
|
||||||
- name: Check Rust VMM Package Consistency of fuzz Workspace
|
|
||||||
run: |
|
|
||||||
pushd fuzz
|
|
||||||
python3 ../scripts/package-consistency-check.py github.com/rust-vmm
|
|
||||||
popd
|
|
||||||
30
.github/workflows/preview-riscv64-build.yaml
vendored
30
.github/workflows/preview-riscv64-build.yaml
vendored
@@ -1,30 +0,0 @@
|
|||||||
name: Cloud Hypervisor RISC-V 64-bit kvm build Preview
|
|
||||||
on: [pull_request, merge_group]
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
name: Cargo
|
|
||||||
runs-on: riscv64-qemu-host
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Code checkout
|
|
||||||
uses: actions/checkout@v6
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Install Rust toolchain
|
|
||||||
run: /opt/scripts/exec-in-qemu.sh rustup default 1.89.0
|
|
||||||
|
|
||||||
- name: Build test (kvm)
|
|
||||||
run: /opt/scripts/exec-in-qemu.sh cargo build --locked --no-default-features --features "kvm" -p cloud-hypervisor
|
|
||||||
|
|
||||||
- name: Clippy test (kvm)
|
|
||||||
run: /opt/scripts/exec-in-qemu.sh cargo clippy --locked --no-default-features --features "kvm" -p cloud-hypervisor
|
|
||||||
|
|
||||||
- name: Check no files were modified
|
|
||||||
run: test -z "$(git status --porcelain)"
|
|
||||||
39
.github/workflows/preview-riscv64-modules.yaml
vendored
39
.github/workflows/preview-riscv64-modules.yaml
vendored
@@ -1,39 +0,0 @@
|
|||||||
name: Cloud Hypervisor RISC-V 64-bit Preview
|
|
||||||
on: [pull_request, merge_group]
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.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@v6
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Install Rust toolchain
|
|
||||||
run: /opt/scripts/exec-in-qemu.sh rustup default 1.89.0
|
|
||||||
|
|
||||||
- name: Build ${{ matrix.module }} Module (kvm)
|
|
||||||
run: /opt/scripts/exec-in-qemu.sh cargo build --locked -p ${{ matrix.module }} --no-default-features --features "kvm"
|
|
||||||
|
|
||||||
- name: Clippy ${{ matrix.module }} Module (kvm)
|
|
||||||
run: /opt/scripts/exec-in-qemu.sh cargo clippy --locked -p ${{ matrix.module }} --no-default-features --features "kvm" -- -D warnings
|
|
||||||
|
|
||||||
- name: Test ${{ matrix.module }} Module (kvm)
|
|
||||||
run: /opt/scripts/exec-in-qemu.sh cargo test --locked -p ${{ matrix.module }} --no-default-features --features "kvm"
|
|
||||||
|
|
||||||
- name: Check no files were modified
|
|
||||||
run: test -z "$(git status --porcelain)"
|
|
||||||
159
.github/workflows/quality.yaml
vendored
159
.github/workflows/quality.yaml
vendored
@@ -1,19 +1,16 @@
|
|||||||
name: Cloud Hypervisor Quality Checks
|
name: Cloud Hypervisor Quality Checks
|
||||||
on: [pull_request, merge_group]
|
on: [pull_request, create]
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: Quality (clippy)
|
if: github.event_name == 'pull_request'
|
||||||
|
name: Quality (clippy, rustfmt)
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
continue-on-error: ${{ matrix.experimental }}
|
continue-on-error: ${{ matrix.experimental }}
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
rust:
|
rust:
|
||||||
- beta
|
|
||||||
- stable
|
- stable
|
||||||
target:
|
target:
|
||||||
- aarch64-unknown-linux-gnu
|
- aarch64-unknown-linux-gnu
|
||||||
@@ -21,15 +18,23 @@ jobs:
|
|||||||
- x86_64-unknown-linux-gnu
|
- x86_64-unknown-linux-gnu
|
||||||
- x86_64-unknown-linux-musl
|
- x86_64-unknown-linux-musl
|
||||||
|
|
||||||
|
experimental: [false]
|
||||||
include:
|
include:
|
||||||
- rust: beta
|
- rust: beta
|
||||||
|
target: aarch64-unknown-linux-gnu
|
||||||
|
experimental: true
|
||||||
|
- rust: beta
|
||||||
|
target: aarch64-unknown-linux-musl
|
||||||
|
experimental: true
|
||||||
|
- rust: beta
|
||||||
|
target: x86_64-unknown-linux-gnu
|
||||||
|
experimental: true
|
||||||
|
- rust: beta
|
||||||
|
target: x86_64-unknown-linux-musl
|
||||||
experimental: true
|
experimental: true
|
||||||
- rust: stable
|
|
||||||
experimental: false
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Code checkout
|
- name: Code checkout
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
@@ -39,132 +44,70 @@ jobs:
|
|||||||
toolchain: ${{ matrix.rust }}
|
toolchain: ${{ matrix.rust }}
|
||||||
target: ${{ matrix.target }}
|
target: ${{ matrix.target }}
|
||||||
override: true
|
override: true
|
||||||
components: clippy
|
components: rustfmt, clippy
|
||||||
|
|
||||||
- name: Bisectability Check (default features)
|
- name: Debug Check (default features)
|
||||||
if: ${{ github.event_name == 'pull_request' && matrix.target == 'x86_64-unknown-linux-gnu' }}
|
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
|
||||||
run: |
|
run: |
|
||||||
set -e
|
set -e
|
||||||
commits=$(git rev-list origin/${{ github.base_ref }}..${{ github.sha }})
|
commits=$(git rev-list origin/${{ github.base_ref }}..${{ github.sha }})
|
||||||
for commit in $commits; do git checkout $commit; cargo check --tests --examples --all --target=${{ matrix.target }}; done
|
for commit in $commits; do git checkout $commit; cargo check --tests --examples --all --target=${{ matrix.target }}; done
|
||||||
git checkout ${{ github.sha }}
|
git checkout ${{ github.sha }}
|
||||||
|
|
||||||
|
- name: Formatting (rustfmt)
|
||||||
|
run: cargo fmt -- --check
|
||||||
|
|
||||||
- name: Clippy (kvm)
|
- name: Clippy (kvm)
|
||||||
uses: houseabsolute/actions-rust-cross@v1
|
|
||||||
with:
|
|
||||||
command: clippy
|
|
||||||
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
|
|
||||||
toolchain: ${{ matrix.rust }}
|
|
||||||
target: ${{ matrix.target }}
|
|
||||||
args: --locked --all --all-targets --no-default-features --tests --examples --features "kvm" -- -D warnings
|
|
||||||
|
|
||||||
- name: Clippy (mshv)
|
|
||||||
uses: houseabsolute/actions-rust-cross@v1
|
|
||||||
with:
|
|
||||||
command: clippy
|
|
||||||
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
|
|
||||||
toolchain: ${{ matrix.rust }}
|
|
||||||
target: ${{ matrix.target }}
|
|
||||||
args: --locked --all --all-targets --no-default-features --tests --examples --features "mshv" -- -D warnings
|
|
||||||
|
|
||||||
- name: Clippy (mshv + kvm)
|
|
||||||
uses: houseabsolute/actions-rust-cross@v1
|
|
||||||
with:
|
|
||||||
command: clippy
|
|
||||||
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
|
|
||||||
toolchain: ${{ matrix.rust }}
|
|
||||||
target: ${{ matrix.target }}
|
|
||||||
args: --locked --all --all-targets --no-default-features --tests --examples --features "mshv,kvm" -- -D warnings
|
|
||||||
|
|
||||||
- name: Clippy (default features)
|
|
||||||
uses: houseabsolute/actions-rust-cross@v1
|
|
||||||
with:
|
|
||||||
command: clippy
|
|
||||||
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
|
|
||||||
toolchain: ${{ matrix.rust }}
|
|
||||||
target: ${{ matrix.target }}
|
|
||||||
args: --locked --all --all-targets --tests --examples -- -D warnings
|
|
||||||
|
|
||||||
- name: Clippy (default features + guest_debug)
|
|
||||||
uses: houseabsolute/actions-rust-cross@v1
|
|
||||||
with:
|
|
||||||
command: clippy
|
|
||||||
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
|
|
||||||
toolchain: ${{ matrix.rust }}
|
|
||||||
target: ${{ matrix.target }}
|
|
||||||
args: --locked --all --all-targets --tests --examples --features "guest_debug" -- -D warnings
|
|
||||||
|
|
||||||
- name: Clippy (default features + pvmemcontrol)
|
|
||||||
uses: houseabsolute/actions-rust-cross@v1
|
|
||||||
with:
|
|
||||||
command: clippy
|
|
||||||
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
|
|
||||||
toolchain: ${{ matrix.rust }}
|
|
||||||
target: ${{ matrix.target }}
|
|
||||||
args: --locked --all --all-targets --tests --examples --features "pvmemcontrol" -- -D warnings
|
|
||||||
|
|
||||||
- name: Clippy (default features + tracing)
|
|
||||||
uses: houseabsolute/actions-rust-cross@v1
|
|
||||||
with:
|
|
||||||
command: clippy
|
|
||||||
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
|
|
||||||
toolchain: ${{ matrix.rust }}
|
|
||||||
target: ${{ matrix.target }}
|
|
||||||
args: --locked --all --all-targets --tests --examples --features "tracing" -- -D warnings
|
|
||||||
- name: Clippy (default features + fw_cfg)
|
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
|
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
|
||||||
command: clippy
|
command: clippy
|
||||||
args: --target=${{ matrix.target }} --locked --all --all-targets --tests --examples --features "fw_cfg" -- -D warnings
|
args: --target=${{ matrix.target }} --locked --all --all-targets --no-default-features --tests --examples --features "kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||||
|
|
||||||
- name: Clippy (default features + ivshmem)
|
- name: Clippy (default features)
|
||||||
uses: houseabsolute/actions-rust-cross@v1
|
uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
|
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
|
||||||
command: clippy
|
command: clippy
|
||||||
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
|
args: --target=${{ matrix.target }} --locked --all --all-targets --tests --examples -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||||
toolchain: ${{ matrix.rust }}
|
|
||||||
target: ${{ matrix.target }}
|
|
||||||
args: --locked --all --all-targets --tests --examples --features "ivshmem" -- -D warnings
|
|
||||||
|
|
||||||
- name: Clippy (sev_snp)
|
- name: Clippy (default features + guest_debug)
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
|
||||||
|
command: clippy
|
||||||
|
args: --target=${{ matrix.target }} --locked --all --all-targets --tests --examples --features "guest_debug" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||||
|
|
||||||
|
- name: Clippy (default features + tracing)
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
|
||||||
|
command: clippy
|
||||||
|
args: --target=${{ matrix.target }} --locked --all --all-targets --tests --examples --features "tracing" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||||
|
|
||||||
|
- name: Clippy (mshv)
|
||||||
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
|
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
|
||||||
uses: houseabsolute/actions-rust-cross@v1
|
uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
|
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
|
||||||
command: clippy
|
command: clippy
|
||||||
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
|
args: --target=${{ matrix.target }} --locked --all --all-targets --no-default-features --tests --examples --features "mshv" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||||
toolchain: ${{ matrix.rust }}
|
|
||||||
target: ${{ matrix.target }}
|
|
||||||
args: --locked --all --all-targets --no-default-features --tests --examples --features "sev_snp" -- -D warnings
|
|
||||||
|
|
||||||
- name: Clippy (igvm)
|
- name: Clippy (mshv + kvm)
|
||||||
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
|
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
|
||||||
uses: houseabsolute/actions-rust-cross@v1
|
uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
|
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
|
||||||
command: clippy
|
command: clippy
|
||||||
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
|
args: --target=${{ matrix.target }} --locked --all --all-targets --no-default-features --tests --examples --features "mshv,kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||||
toolchain: ${{ matrix.rust }}
|
|
||||||
target: ${{ matrix.target }}
|
|
||||||
args: --locked --all --all-targets --no-default-features --tests --examples --features "igvm" -- -D warnings
|
|
||||||
|
|
||||||
- name: Clippy (kvm + tdx)
|
- name: Clippy (kvm + tdx)
|
||||||
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
|
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
|
||||||
uses: houseabsolute/actions-rust-cross@v1
|
uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
|
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
|
||||||
command: clippy
|
command: clippy
|
||||||
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
|
args: --target=${{ matrix.target }} --locked --all --all-targets --no-default-features --tests --examples --features "tdx,kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||||
toolchain: ${{ matrix.rust }}
|
|
||||||
target: ${{ matrix.target }}
|
|
||||||
args: --locked --all --all-targets --no-default-features --tests --examples --features "tdx,kvm" -- -D warnings
|
|
||||||
|
|
||||||
- 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@v6
|
|
||||||
# Executes "typos ."
|
|
||||||
- uses: crate-ci/typos@v1.40.0
|
|
||||||
|
|||||||
196
.github/workflows/release.yaml
vendored
196
.github/workflows/release.yaml
vendored
@@ -1,69 +1,134 @@
|
|||||||
name: Cloud Hypervisor Release
|
name: Cloud Hypervisor Release
|
||||||
on: [create, merge_group]
|
on: [pull_request, create]
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ github.token }}
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
if: (github.event_name == 'create' && github.event.ref_type == 'tag') || github.event_name == 'merge_group'
|
if: (github.event_name == 'create' && github.event.ref_type == 'tag') || github.event_name == 'pull_request'
|
||||||
name: Release ${{ matrix.platform.target }}
|
name: Release
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
platform:
|
|
||||||
- target: x86_64-unknown-linux-gnu
|
|
||||||
args: --all --release --features mshv
|
|
||||||
name_ch: cloud-hypervisor
|
|
||||||
name_ch_remote: ch-remote
|
|
||||||
- target: x86_64-unknown-linux-musl
|
|
||||||
args: --all --release --features mshv
|
|
||||||
name_ch: cloud-hypervisor-static
|
|
||||||
name_ch_remote: ch-remote-static
|
|
||||||
- target: aarch64-unknown-linux-musl
|
|
||||||
args: --all --release
|
|
||||||
name_ch: cloud-hypervisor-static-aarch64
|
|
||||||
name_ch_remote: ch-remote-static-aarch64
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Code checkout
|
- name: Code checkout
|
||||||
uses: actions/checkout@v6
|
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@v1
|
uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
|
toolchain: "1.62"
|
||||||
|
target: x86_64-unknown-linux-gnu
|
||||||
|
- name: Install Rust toolchain (x86_64-unknown-linux-musl)
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
toolchain: "1.62"
|
||||||
|
target: x86_64-unknown-linux-musl
|
||||||
|
- name: Build
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
toolchain: "1.62"
|
||||||
command: build
|
command: build
|
||||||
target: ${{ matrix.platform.target }}
|
args: --all --release --no-default-features --features "kvm,mshv" --target=x86_64-unknown-linux-gnu
|
||||||
args: ${{ matrix.platform.args }}
|
- name: Static Build
|
||||||
strip: true
|
uses: actions-rs/cargo@v1
|
||||||
toolchain: "1.89.0"
|
|
||||||
- name: Copy Release Binaries
|
|
||||||
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
cp target/${{ matrix.platform.target }}/release/cloud-hypervisor ./${{ matrix.platform.name_ch }}
|
|
||||||
cp target/${{ matrix.platform.target }}/release/ch-remote ./${{ matrix.platform.name_ch_remote }}
|
|
||||||
- name: Upload Release Artifacts
|
|
||||||
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
|
||||||
uses: actions/upload-artifact@v6
|
|
||||||
with:
|
with:
|
||||||
name: Artifacts for ${{ matrix.platform.target }}
|
toolchain: "1.62"
|
||||||
path: |
|
command: build
|
||||||
./${{ matrix.platform.name_ch }}
|
args: --all --release --no-default-features --features "kvm,mshv" --target=x86_64-unknown-linux-musl
|
||||||
./${{ matrix.platform.name_ch_remote }}
|
- name: Install Rust toolchain (aarch64-unknown-linux-musl)
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
toolchain: "1.62"
|
||||||
|
target: aarch64-unknown-linux-musl
|
||||||
|
override: true
|
||||||
|
- name: Create Release
|
||||||
|
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
||||||
|
id: create_release
|
||||||
|
uses: actions/create-release@v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
tag_name: ${{ github.ref }}
|
||||||
|
release_name: ${{ github.ref }}
|
||||||
|
draft: true
|
||||||
|
prerelease: true
|
||||||
|
- name: Upload cloud-hypervisor
|
||||||
|
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
||||||
|
id: upload-release-cloud-hypervisor
|
||||||
|
uses: actions/upload-release-asset@v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||||
|
asset_path: target/x86_64-unknown-linux-gnu/release/cloud-hypervisor
|
||||||
|
asset_name: cloud-hypervisor
|
||||||
|
asset_content_type: application/octet-stream
|
||||||
|
- name: Upload static cloud-hypervisor
|
||||||
|
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
||||||
|
id: upload-release-static-cloud-hypervisor
|
||||||
|
uses: actions/upload-release-asset@v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||||
|
asset_path: target/x86_64-unknown-linux-musl/release/cloud-hypervisor
|
||||||
|
asset_name: cloud-hypervisor-static
|
||||||
|
asset_content_type: application/octet-stream
|
||||||
|
- name: Upload ch-remote
|
||||||
|
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
||||||
|
id: upload-release-ch-remote
|
||||||
|
uses: actions/upload-release-asset@v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||||
|
asset_path: target/x86_64-unknown-linux-gnu/release/ch-remote
|
||||||
|
asset_name: ch-remote
|
||||||
|
asset_content_type: application/octet-stream
|
||||||
|
- name: Upload static-ch-remote
|
||||||
|
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
||||||
|
id: upload-release-static-ch-remote
|
||||||
|
uses: actions/upload-release-asset@v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||||
|
asset_path: target/x86_64-unknown-linux-musl/release/ch-remote
|
||||||
|
asset_name: ch-remote-static
|
||||||
|
asset_content_type: application/octet-stream
|
||||||
|
- name: Clean build tree ahead of cross build
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
command: clean
|
||||||
|
- name: Static Build (AArch64)
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
use-cross: true
|
||||||
|
command: build
|
||||||
|
args: --all --release --target=aarch64-unknown-linux-musl
|
||||||
|
- name: Upload static AArch64 cloud-hypervisor
|
||||||
|
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
||||||
|
id: upload-release-static-aarch64-cloud-hypervisor
|
||||||
|
uses: actions/upload-release-asset@v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||||
|
asset_path: target/aarch64-unknown-linux-musl/release/cloud-hypervisor
|
||||||
|
asset_name: cloud-hypervisor-static-aarch64
|
||||||
|
asset_content_type: application/octet-stream
|
||||||
|
- name: Upload static AArch64 ch-remote
|
||||||
|
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
||||||
|
id: upload-release-static-aarch64-ch-remote
|
||||||
|
uses: actions/upload-release-asset@v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||||
|
asset_path: target/aarch64-unknown-linux-musl/release/ch-remote
|
||||||
|
asset_name: ch-remote-static-aarch64
|
||||||
|
asset_content_type: application/octet-stream
|
||||||
- name: Vendor
|
- name: Vendor
|
||||||
if: |
|
|
||||||
github.event_name == 'create' && github.event.ref_type == 'tag' &&
|
|
||||||
matrix.platform.target == 'x86_64-unknown-linux-gnu'
|
|
||||||
working-directory: ../cloud-hypervisor-${{ github.event.ref }}
|
working-directory: ../cloud-hypervisor-${{ github.event.ref }}
|
||||||
run: |
|
run: |
|
||||||
mkdir ../vendor-cargo-home
|
mkdir ../vendor-cargo-home
|
||||||
@@ -71,25 +136,16 @@ jobs:
|
|||||||
mkdir .cargo
|
mkdir .cargo
|
||||||
cargo vendor > .cargo/config.toml
|
cargo vendor > .cargo/config.toml
|
||||||
- name: Create vendored source archive
|
- name: Create vendored source archive
|
||||||
if: |
|
working-directory: ../
|
||||||
github.event_name == 'create' && github.event.ref_type == 'tag' &&
|
run: tar cJf cloud-hypervisor-${{ github.event.ref }}.tar.xz cloud-hypervisor-${{ github.event.ref }}
|
||||||
matrix.platform.target == 'x86_64-unknown-linux-gnu'
|
|
||||||
run: tar cJf cloud-hypervisor-${{ github.event.ref }}.tar.xz ../cloud-hypervisor-${{ github.event.ref }}
|
|
||||||
- name: Upload cloud-hypervisor vendored source archive
|
- name: Upload cloud-hypervisor vendored source archive
|
||||||
if: |
|
|
||||||
github.event_name == 'create' && github.event.ref_type == 'tag' &&
|
|
||||||
matrix.platform.target == 'x86_64-unknown-linux-gnu'
|
|
||||||
id: upload-release-cloud-hypervisor-vendored-sources
|
|
||||||
uses: actions/upload-artifact@v6
|
|
||||||
with:
|
|
||||||
path: cloud-hypervisor-${{ github.event.ref }}.tar.xz
|
|
||||||
name: cloud-hypervisor-${{ github.event.ref }}.tar.xz
|
|
||||||
- name: Create GitHub Release
|
|
||||||
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
||||||
uses: softprops/action-gh-release@v2
|
id: upload-release-cloud-hypervisor-vendored-sources
|
||||||
|
uses: actions/upload-release-asset@v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
with:
|
||||||
draft: true
|
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||||
files: |
|
asset_path: ../cloud-hypervisor-${{ github.event.ref }}.tar.xz
|
||||||
./${{ matrix.platform.name_ch }}
|
asset_name: cloud-hypervisor-${{ github.event.ref }}.tar.xz
|
||||||
./${{ matrix.platform.name_ch_remote }}
|
asset_content_type: application/x-xz
|
||||||
./cloud-hypervisor-${{ github.event.ref }}.tar.xz
|
|
||||||
|
|||||||
12
.github/workflows/reuse.yaml
vendored
12
.github/workflows/reuse.yaml
vendored
@@ -1,12 +0,0 @@
|
|||||||
name: REUSE Compliance Check
|
|
||||||
|
|
||||||
on: [push, pull_request]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
reuse:
|
|
||||||
name: REUSE Compliance Check
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v6
|
|
||||||
- name: REUSE Compliance Check
|
|
||||||
uses: fsfe/reuse-action@v6
|
|
||||||
20
.github/workflows/shlint.yaml
vendored
20
.github/workflows/shlint.yaml
vendored
@@ -1,20 +0,0 @@
|
|||||||
name: Shell scripts check
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
merge_group:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
sh-checker:
|
|
||||||
name: Check shell scripts
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v6
|
|
||||||
- name: Run the shell script checkers
|
|
||||||
uses: luizm/action-sh-checker@master
|
|
||||||
env:
|
|
||||||
SHFMT_OPTS: -i 4 -d
|
|
||||||
SHELLCHECK_OPTS: -x --source-path scripts
|
|
||||||
21
.github/workflows/taplo.yaml
vendored
21
.github/workflows/taplo.yaml
vendored
@@ -1,21 +0,0 @@
|
|||||||
name: Cargo.toml Formatting (taplo)
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
paths:
|
|
||||||
- '**/Cargo.toml'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
cargo_toml_format:
|
|
||||||
name: Cargo.toml Formatting
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Code checkout
|
|
||||||
uses: actions/checkout@v6
|
|
||||||
- name: Install Rust toolchain
|
|
||||||
uses: dtolnay/rust-toolchain@stable
|
|
||||||
- name: Install build dependencies
|
|
||||||
run: sudo apt-get update && sudo apt-get -yqq install build-essential libssl-dev
|
|
||||||
- name: Install taplo
|
|
||||||
run: cargo install taplo-cli --locked
|
|
||||||
- name: Check formatting
|
|
||||||
run: taplo fmt --check
|
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -6,5 +6,3 @@
|
|||||||
**/rusty-tags.vi
|
**/rusty-tags.vi
|
||||||
/rpm/SOURCES
|
/rpm/SOURCES
|
||||||
/.vscode
|
/.vscode
|
||||||
/vendor
|
|
||||||
__pycache__
|
|
||||||
|
|||||||
13
.gitlint
13
.gitlint
@@ -1,13 +0,0 @@
|
|||||||
[general]
|
|
||||||
extra-path=scripts/gitlint/rules
|
|
||||||
regex-style-search=true
|
|
||||||
ignore=body-max-line-length,body-hard-tab
|
|
||||||
|
|
||||||
[ignore-by-author-name]
|
|
||||||
regex=dependabot
|
|
||||||
ignore=all
|
|
||||||
|
|
||||||
# default 72
|
|
||||||
[title-max-length]
|
|
||||||
line-length=72
|
|
||||||
|
|
||||||
27
.lychee.toml
27
.lychee.toml
@@ -1,27 +0,0 @@
|
|||||||
verbose = "info"
|
|
||||||
|
|
||||||
exclude = [
|
|
||||||
# Availability of links below should be manually verified.
|
|
||||||
# Page for intel TDX support, returns 403 while querying.
|
|
||||||
'^https://www.intel.com/content/www/us/en/developer/tools/trust-domain-extensions/overview.html',
|
|
||||||
# Page for TPM, returns 403 while querying.
|
|
||||||
'^https://trustedcomputinggroup.org/wp-content/uploads/PC-Client-Specific-Platform-TPM-Profile-for-TPM-2p0-v1p05p_r14_pub.pdf',
|
|
||||||
|
|
||||||
# GitHub user smibarber referenced in `CREDITS.md` no longer exist
|
|
||||||
'^https://github.com/smibarber',
|
|
||||||
|
|
||||||
# OSDev has added bot protection and accesses my result in 403 Forbidden.
|
|
||||||
'^https://wiki.osdev.org',
|
|
||||||
# Exclude all pages with $ in the URL since $XXX is a variable
|
|
||||||
"\\$.*",
|
|
||||||
# Exclude local files
|
|
||||||
"file://.*",
|
|
||||||
]
|
|
||||||
|
|
||||||
# Exclude loopback addresses
|
|
||||||
exclude_loopback = true
|
|
||||||
|
|
||||||
|
|
||||||
max_retries = 3
|
|
||||||
|
|
||||||
retry_wait_time = 5
|
|
||||||
12
.reuse/dep5
12
.reuse/dep5
@@ -1,12 +0,0 @@
|
|||||||
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
|
||||||
Upstream-Name: cloud-hypervisor
|
|
||||||
Upstream-Contact: <>
|
|
||||||
Source: https://www.cloudhypervisor.org
|
|
||||||
|
|
||||||
Files: docs/*.md *.md
|
|
||||||
Copyright: 2024
|
|
||||||
License: CC-BY-4.0
|
|
||||||
|
|
||||||
Files: scripts/* test_data/* *.toml .git* fuzz/Cargo.lock fuzz/.gitignore resources/linux-config-* vmm/src/api/openapi/cloud-hypervisor.yaml CODEOWNERS Cargo.lock
|
|
||||||
Copyright: 2024
|
|
||||||
License: Apache-2.0
|
|
||||||
@@ -1,4 +1 @@
|
|||||||
edition = "2024"
|
edition = "2021"
|
||||||
group_imports="StdExternalCrate"
|
|
||||||
imports_granularity="Module"
|
|
||||||
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
include = ["**/Cargo.toml"]
|
|
||||||
|
|
||||||
[formatting]
|
|
||||||
reorder_arrays = true
|
|
||||||
reorder_keys = true
|
|
||||||
25
.typos.toml
25
.typos.toml
@@ -1,25 +0,0 @@
|
|||||||
# Configuration for https://github.com/crate-ci/typos
|
|
||||||
|
|
||||||
[files]
|
|
||||||
extend-exclude = [
|
|
||||||
"hypervisor/src/kvm/x86_64/mod.rs",
|
|
||||||
"resources/linux-config-*",
|
|
||||||
]
|
|
||||||
|
|
||||||
[default.extend-words]
|
|
||||||
CLASSE = "CLASSE"
|
|
||||||
Dake = "Dake"
|
|
||||||
EXTINT = "EXTINT"
|
|
||||||
INOUT = "INOUT"
|
|
||||||
MSIS = "MSIS" # MSIs (Message Signaled Interrupt)
|
|
||||||
SME = "SME" # Secure Memory Encryption
|
|
||||||
THR = "THR" # Transmitter Holding Register
|
|
||||||
TRANSLATER = "TRANSLATER"
|
|
||||||
ba = "ba"
|
|
||||||
conectix = "conectix"
|
|
||||||
liness = "liness"
|
|
||||||
outout = "outout"
|
|
||||||
|
|
||||||
[default.extend-identifiers]
|
|
||||||
fo = "fo"
|
|
||||||
fpr = "fpr"
|
|
||||||
@@ -5,7 +5,7 @@ License](https://opensource.org/licenses/Apache-2.0) and the [BSD 3
|
|||||||
Clause](https://opensource.org/licenses/BSD-3-Clause) license. Individual files
|
Clause](https://opensource.org/licenses/BSD-3-Clause) license. Individual files
|
||||||
contain details of their licensing and changes to that file are under the same
|
contain details of their licensing and changes to that file are under the same
|
||||||
license unless the contribution changes the license of the file. When importing
|
license unless the contribution changes the license of the file. When importing
|
||||||
code from a third party project (e.g. Firecracker or crosvm) please respect the
|
code from a third party project (e.g. Firecracker or CrosVM) please respect the
|
||||||
license of those projects.
|
license of those projects.
|
||||||
|
|
||||||
New code should be under the [Apache v2
|
New code should be under the [Apache v2
|
||||||
@@ -13,34 +13,12 @@ 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`
|
||||||
`clippy`, and other well-known code quality tool of the ecosystem for each submitted Pull Request (PR).
|
for each submitted Pull Request (PR).
|
||||||
|
|
||||||
## Basic Checks
|
## Basic Checks
|
||||||
|
|
||||||
```sh
|
|
||||||
# We currently rely on nightly-only formatting features
|
|
||||||
cargo +nightly fmt --all
|
|
||||||
cargo check --all-targets --tests
|
|
||||||
cargo clippy --all-targets --tests
|
|
||||||
# Please note that this will not execute integration tests.
|
|
||||||
cargo test --all-targets --tests
|
|
||||||
|
|
||||||
# To lint your last three commits
|
|
||||||
gitlint --commits "HEAD~3..HEAD"
|
|
||||||
```
|
|
||||||
|
|
||||||
### \[Optional\] Run Integration Tests
|
|
||||||
|
|
||||||
_Caution: These tests are taking a long time to complete (40+ mins) and need special setup._
|
|
||||||
|
|
||||||
```sh
|
|
||||||
bash ./scripts/dev_cli.sh tests --integration -- --test-filter '<optionally filter test by name pattern>'
|
|
||||||
```
|
|
||||||
|
|
||||||
### Setup Commit Hook
|
|
||||||
|
|
||||||
Please consider creating the following hook as `.git/hooks/pre-commit` in order
|
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
|
to ensure basic correctness of your code. You can extend this further if you
|
||||||
have specific features that you regularly develop against.
|
have specific features that you regularly develop against.
|
||||||
@@ -48,9 +26,9 @@ have specific features that you regularly develop against.
|
|||||||
```sh
|
```sh
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
cargo +nightly fmt --all -- --check || exit 1
|
cargo fmt -- --check || exit 1
|
||||||
cargo check --locked --all-targets --tests || exit 1
|
cargo check --locked --all --all-targets --tests || exit 1
|
||||||
cargo clippy --locked --all-targets --tests -- -D warnings || 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
|
You will need to `chmod +x .git/hooks/pre-commit` to have it run on every
|
||||||
@@ -58,7 +36,7 @@ commit you make.
|
|||||||
|
|
||||||
## Certificate of Origin
|
## 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
|
||||||
@@ -101,12 +79,12 @@ you want to merge your changes to `cloud-hypervisor`:
|
|||||||
|
|
||||||
1. Fork the [cloud-hypervisor](https://github.com/cloud-hypervisor/cloud-hypervisor) project
|
1. Fork the [cloud-hypervisor](https://github.com/cloud-hypervisor/cloud-hypervisor) project
|
||||||
into your github organization.
|
into your github organization.
|
||||||
1. Within your fork, create a branch for your contribution.
|
2. Within your fork, create a branch for your contribution.
|
||||||
1. [Create a pull request](https://help.github.com/articles/creating-a-pull-request-from-a-fork/)
|
3. [Create a pull request](https://help.github.com/articles/creating-a-pull-request-from-a-fork/)
|
||||||
against the main branch of the Cloud Hypervisor repository.
|
against the main branch of the Cloud Hypervisor repository.
|
||||||
1. To update your pull request amend existing commits whenever applicable and
|
4. To update your pull request amend existing commits whenever applicable and
|
||||||
then push the new changes to your pull request branch.
|
then push the new changes to your pull request branch.
|
||||||
1. Once the pull request is approved it can be integrated.
|
5. Once the pull request is approved it can be integrated.
|
||||||
|
|
||||||
## Issue tracking
|
## Issue tracking
|
||||||
|
|
||||||
@@ -134,15 +112,5 @@ Fixes #88
|
|||||||
Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
|
Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
|
||||||
```
|
```
|
||||||
|
|
||||||
Then, after the corresponding PR is merged, GitHub will automatically close that issue when parsing the
|
Then, after the corresponding PR is merged, Github will automatically close that issue when parsing the
|
||||||
[commit message](https://help.github.com/articles/closing-issues-via-commit-messages/).
|
[commit message](https://help.github.com/articles/closing-issues-via-commit-messages/).
|
||||||
|
|
||||||
## AI Generated Code
|
|
||||||
|
|
||||||
Our policy is to decline any contributions known to contain contents
|
|
||||||
generated or derived from using Large Language Models (LLMs). This
|
|
||||||
includes ChatGPT, Gemini, Claude, Copilot and similar tools.
|
|
||||||
|
|
||||||
The goal is to avoid ambiguity in license compliance and optimize the
|
|
||||||
use of limited project resources, especially for code review and
|
|
||||||
maintenance. This policy can be revisited as LLMs evolve and mature.
|
|
||||||
|
|||||||
2138
Cargo.lock
generated
2138
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
198
Cargo.toml
198
Cargo.toml
@@ -1,122 +1,102 @@
|
|||||||
# Cloud Hypervisor Workspace
|
[package]
|
||||||
#
|
name = "cloud-hypervisor"
|
||||||
# The main crate producing the binaries is in `./cloud-hypervisor`.
|
version = "30.1.0"
|
||||||
|
authors = ["The Cloud Hypervisor Authors"]
|
||||||
|
edition = "2021"
|
||||||
|
default-run = "cloud-hypervisor"
|
||||||
|
build = "build.rs"
|
||||||
|
license = "LICENSE-APACHE & LICENSE-BSD-3-Clause"
|
||||||
|
description = "Open source Virtual Machine Monitor (VMM) that runs on top of KVM"
|
||||||
|
homepage = "https://github.com/cloud-hypervisor/cloud-hypervisor"
|
||||||
|
# Minimum buildable version:
|
||||||
|
# Keep in sync with version in .github/workflows/build.yaml
|
||||||
|
# Policy on MSRV (see #4318):
|
||||||
|
# Can only be bumped by:
|
||||||
|
# a.) A dependency requires it,
|
||||||
|
# b.) If we want to use a new feature and that MSRV is at least 6 months old,
|
||||||
|
# c.) There is a security issue that is addressed by the toolchain update.
|
||||||
|
rust-version = "1.62"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
codegen-units = 1
|
|
||||||
lto = true
|
lto = true
|
||||||
|
codegen-units = 1
|
||||||
opt-level = "s"
|
opt-level = "s"
|
||||||
strip = true
|
strip = true
|
||||||
|
|
||||||
[profile.profiling]
|
[profile.profiling]
|
||||||
debug = true
|
|
||||||
inherits = "release"
|
inherits = "release"
|
||||||
strip = false
|
strip = false
|
||||||
|
debug = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = "1.0.69"
|
||||||
|
api_client = { path = "api_client" }
|
||||||
|
argh = "0.1.9"
|
||||||
|
dhat = { version = "0.3.2", optional = true }
|
||||||
|
epoll = "4.3.1"
|
||||||
|
event_monitor = { path = "event_monitor" }
|
||||||
|
hypervisor = { path = "hypervisor" }
|
||||||
|
libc = "0.2.139"
|
||||||
|
log = { version = "0.4.17", features = ["std"] }
|
||||||
|
option_parser = { path = "option_parser" }
|
||||||
|
seccompiler = "0.3.0"
|
||||||
|
serde_json = "1.0.93"
|
||||||
|
signal-hook = "0.3.14"
|
||||||
|
thiserror = "1.0.38"
|
||||||
|
tpm = { path = "tpm"}
|
||||||
|
tracer = { path = "tracer" }
|
||||||
|
vmm = { path = "vmm" }
|
||||||
|
vmm-sys-util = "0.11.0"
|
||||||
|
vm-memory = "0.10.0"
|
||||||
|
|
||||||
|
# List of patched crates
|
||||||
|
[patch.crates-io]
|
||||||
|
kvm-bindings = { git = "https://github.com/cloud-hypervisor/kvm-bindings", branch = "ch-v0.6.0-tdx" }
|
||||||
|
kvm-ioctls = { git = "https://github.com/rust-vmm/kvm-ioctls", branch = "main" }
|
||||||
|
versionize_derive = { git = "https://github.com/cloud-hypervisor/versionize_derive", branch = "ch" }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
dirs = "4.0.0"
|
||||||
|
net_util = { path = "net_util" }
|
||||||
|
once_cell = "1.17.1"
|
||||||
|
serde_json = "1.0.93"
|
||||||
|
test_infra = { path = "test_infra" }
|
||||||
|
wait-timeout = "0.2.0"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["kvm"]
|
||||||
|
dhat-heap = ["dhat"] # For heap profiling
|
||||||
|
guest_debug = ["vmm/guest_debug"]
|
||||||
|
kvm = ["vmm/kvm"]
|
||||||
|
mshv = ["vmm/mshv"]
|
||||||
|
tdx = ["vmm/tdx"]
|
||||||
|
tracing = ["vmm/tracing", "tracer/tracing"]
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
"api_client",
|
"api_client",
|
||||||
"arch",
|
"arch",
|
||||||
"block",
|
"block_util",
|
||||||
"cloud-hypervisor",
|
"devices",
|
||||||
"devices",
|
"event_monitor",
|
||||||
"event_monitor",
|
"hypervisor",
|
||||||
"hypervisor",
|
"net_gen",
|
||||||
"net_gen",
|
"net_util",
|
||||||
"net_util",
|
"option_parser",
|
||||||
"option_parser",
|
"pci",
|
||||||
"pci",
|
"performance-metrics",
|
||||||
"performance-metrics",
|
"qcow",
|
||||||
"rate_limiter",
|
"rate_limiter",
|
||||||
"serial_buffer",
|
"serial_buffer",
|
||||||
"test_infra",
|
"test_infra",
|
||||||
"tracer",
|
"tracer",
|
||||||
"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"
|
||||||
]
|
]
|
||||||
package.edition = "2024"
|
|
||||||
resolver = "3"
|
|
||||||
|
|
||||||
[workspace.dependencies]
|
|
||||||
# rust-vmm crates
|
|
||||||
acpi_tables = { git = "https://github.com/rust-vmm/acpi_tables", branch = "main" }
|
|
||||||
kvm-bindings = "0.12.1"
|
|
||||||
kvm-ioctls = "0.22.1"
|
|
||||||
linux-loader = "0.13.1"
|
|
||||||
mshv-bindings = "0.6.5"
|
|
||||||
mshv-ioctls = "0.6.5"
|
|
||||||
seccompiler = "0.5.0"
|
|
||||||
vfio-bindings = { version = "0.6.0", default-features = false }
|
|
||||||
vfio-ioctls = { version = "0.5.1", default-features = false }
|
|
||||||
vfio_user = { version = "0.1.1", default-features = false }
|
|
||||||
vhost = { version = "0.14.0", default-features = false }
|
|
||||||
vhost-user-backend = { version = "0.20.0", default-features = false }
|
|
||||||
virtio-bindings = "0.2.6"
|
|
||||||
virtio-queue = "0.16.0"
|
|
||||||
vm-fdt = "0.3.0"
|
|
||||||
vm-memory = "0.16.1"
|
|
||||||
vmm-sys-util = "0.14.0"
|
|
||||||
|
|
||||||
# igvm crates
|
|
||||||
# TODO: bump to 0.3.5 release
|
|
||||||
igvm = { git = "https://github.com/microsoft/igvm", branch = "main" }
|
|
||||||
igvm_defs = { git = "https://github.com/microsoft/igvm", branch = "main" }
|
|
||||||
|
|
||||||
# serde crates
|
|
||||||
serde = "1.0.228"
|
|
||||||
serde_json = "1.0.145"
|
|
||||||
serde_with = { version = "3.16.1", default-features = false }
|
|
||||||
|
|
||||||
# other crates
|
|
||||||
anyhow = "1.0.99"
|
|
||||||
bitflags = "2.10.0"
|
|
||||||
byteorder = "1.5.0"
|
|
||||||
cfg-if = "1.0.4"
|
|
||||||
clap = "4.5.53"
|
|
||||||
dhat = "0.3.3"
|
|
||||||
dirs = "6.0.0"
|
|
||||||
env_logger = "0.11.8"
|
|
||||||
epoll = "4.4.0"
|
|
||||||
flume = "0.12.0"
|
|
||||||
itertools = "0.14.0"
|
|
||||||
libc = "0.2.178"
|
|
||||||
log = "0.4.29"
|
|
||||||
signal-hook = "0.3.18"
|
|
||||||
thiserror = "2.0.17"
|
|
||||||
uuid = { version = "1.19.0" }
|
|
||||||
wait-timeout = "0.2.1"
|
|
||||||
zerocopy = { version = "0.8.31", default-features = false }
|
|
||||||
|
|
||||||
[workspace.lints.clippy]
|
|
||||||
# Any clippy lint (group) in alphabetical order:
|
|
||||||
# https://rust-lang.github.io/rust-clippy/master/index.html
|
|
||||||
|
|
||||||
# Groups
|
|
||||||
all = "deny" # shorthand for the other groups but here for compleness
|
|
||||||
complexity = "deny"
|
|
||||||
correctness = "deny"
|
|
||||||
perf = "deny"
|
|
||||||
style = "deny"
|
|
||||||
suspicious = "deny"
|
|
||||||
|
|
||||||
# Individual Lints
|
|
||||||
assertions_on_result_states = "deny"
|
|
||||||
if_not_else = "deny"
|
|
||||||
manual_string_new = "deny"
|
|
||||||
map_unwrap_or = "deny"
|
|
||||||
needless_pass_by_value = "deny"
|
|
||||||
redundant_else = "deny"
|
|
||||||
semicolon_if_nothing_returned = "deny"
|
|
||||||
undocumented_unsafe_blocks = "deny"
|
|
||||||
uninlined_format_args = "deny"
|
|
||||||
unnecessary_semicolon = "deny"
|
|
||||||
|
|
||||||
[workspace.lints.rust]
|
|
||||||
# `level = warn` is irrelevant here but mandatory for rustc/cargo
|
|
||||||
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(devcli_testenv)'] }
|
|
||||||
|
|||||||
452
Jenkinsfile
vendored
Normal file
452
Jenkinsfile
vendored
Normal file
@@ -0,0 +1,452 @@
|
|||||||
|
def runWorkers = true
|
||||||
|
pipeline {
|
||||||
|
agent none
|
||||||
|
options {
|
||||||
|
timeout(time: 4, unit: 'HOURS')
|
||||||
|
}
|
||||||
|
stages {
|
||||||
|
stage('Early checks') {
|
||||||
|
agent { node { label 'built-in' } }
|
||||||
|
stages {
|
||||||
|
stage('Checkout') {
|
||||||
|
steps {
|
||||||
|
checkout scm
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Check if worker build can be skipped') {
|
||||||
|
when {
|
||||||
|
expression {
|
||||||
|
return skipWorkerBuild()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
script {
|
||||||
|
runWorkers = false
|
||||||
|
echo 'No changes requring a build'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Check for RFC/WIP builds') {
|
||||||
|
when {
|
||||||
|
changeRequest comparator: 'REGEXP', title: '.*(rfc|RFC|wip|WIP).*'
|
||||||
|
beforeAgent true
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
error('Failing as this is marked as a WIP or RFC PR.')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Cancel older builds') {
|
||||||
|
when { not { branch 'main' } }
|
||||||
|
steps {
|
||||||
|
cancelPreviousBuilds()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Build') {
|
||||||
|
parallel {
|
||||||
|
stage('Worker build') {
|
||||||
|
agent { node { label 'jammy' } }
|
||||||
|
when {
|
||||||
|
beforeAgent true
|
||||||
|
expression {
|
||||||
|
return runWorkers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stages {
|
||||||
|
stage('Checkout') {
|
||||||
|
steps {
|
||||||
|
checkout scm
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Prepare environment') {
|
||||||
|
steps {
|
||||||
|
sh 'scripts/prepare_vdpa.sh'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Run OpenAPI tests') {
|
||||||
|
steps {
|
||||||
|
sh 'scripts/run_openapi_tests.sh'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Run unit tests') {
|
||||||
|
steps {
|
||||||
|
sh 'scripts/dev_cli.sh tests --unit'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Run integration tests') {
|
||||||
|
options {
|
||||||
|
timeout(time: 1, unit: 'HOURS')
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
sh 'sudo modprobe openvswitch'
|
||||||
|
sh 'scripts/dev_cli.sh tests --integration'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Run live-migration integration tests') {
|
||||||
|
options {
|
||||||
|
timeout(time: 1, unit: 'HOURS')
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
sh 'sudo modprobe openvswitch'
|
||||||
|
sh 'scripts/dev_cli.sh tests --integration-live-migration'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Run unit tests for musl') {
|
||||||
|
steps {
|
||||||
|
sh 'scripts/dev_cli.sh tests --unit --libc musl'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Run integration tests for musl') {
|
||||||
|
options {
|
||||||
|
timeout(time: 1, unit: 'HOURS')
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
sh 'sudo modprobe openvswitch'
|
||||||
|
sh 'scripts/dev_cli.sh tests --integration --libc musl'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Run live-migration integration tests for musl') {
|
||||||
|
options {
|
||||||
|
timeout(time: 1, unit: 'HOURS')
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
sh 'sudo modprobe openvswitch'
|
||||||
|
sh 'scripts/dev_cli.sh tests --integration-live-migration --libc musl'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('AArch64 worker build') {
|
||||||
|
agent { node { label 'bionic-arm64' } }
|
||||||
|
when {
|
||||||
|
beforeAgent true
|
||||||
|
expression {
|
||||||
|
return runWorkers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
environment {
|
||||||
|
AZURE_CONNECTION_STRING = credentials('46b4e7d6-315f-4cc1-8333-b58780863b9b')
|
||||||
|
}
|
||||||
|
stages {
|
||||||
|
stage('Checkout') {
|
||||||
|
steps {
|
||||||
|
checkout scm
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Run unit tests') {
|
||||||
|
steps {
|
||||||
|
sh 'scripts/dev_cli.sh tests --unit --libc musl'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Run integration tests') {
|
||||||
|
options {
|
||||||
|
timeout(time: 1, unit: 'HOURS')
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
sh 'sudo modprobe openvswitch'
|
||||||
|
sh 'scripts/dev_cli.sh tests --integration --libc musl'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Install azure-cli') {
|
||||||
|
steps {
|
||||||
|
installAzureCli('bionic', 'arm64')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Download Windows image') {
|
||||||
|
steps {
|
||||||
|
sh '''#!/bin/bash -x
|
||||||
|
IMG_BASENAME=windows-11-iot-enterprise-aarch64.raw
|
||||||
|
IMG_PATH=$HOME/workloads/$IMG_BASENAME
|
||||||
|
IMG_GZ_PATH=$HOME/workloads/$IMG_BASENAME.gz
|
||||||
|
IMG_GZ_BLOB_NAME=windows-11-iot-enterprise-aarch64-9-min.raw.gz
|
||||||
|
cp "scripts/$IMG_BASENAME.sha1" "$HOME/workloads/"
|
||||||
|
pushd "$HOME/workloads"
|
||||||
|
if sha1sum "$IMG_BASENAME.sha1" --check; then
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
popd
|
||||||
|
mkdir -p "$HOME/workloads"
|
||||||
|
az storage blob download \
|
||||||
|
--container-name private-images \
|
||||||
|
--file "$IMG_GZ_PATH" \
|
||||||
|
--name "$IMG_GZ_BLOB_NAME" \
|
||||||
|
--connection-string "$AZURE_CONNECTION_STRING"
|
||||||
|
gzip -d $IMG_GZ_PATH
|
||||||
|
'''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Run Windows guest integration tests') {
|
||||||
|
options {
|
||||||
|
timeout(time: 1, unit: 'HOURS')
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
sh 'scripts/dev_cli.sh tests --integration-windows --libc musl'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
post {
|
||||||
|
always {
|
||||||
|
sh "sudo chown -R jenkins.jenkins ${WORKSPACE}"
|
||||||
|
deleteDir()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Worker build - Windows guest') {
|
||||||
|
agent { node { label 'jammy' } }
|
||||||
|
when {
|
||||||
|
beforeAgent true
|
||||||
|
expression {
|
||||||
|
return runWorkers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
environment {
|
||||||
|
AZURE_CONNECTION_STRING = credentials('46b4e7d6-315f-4cc1-8333-b58780863b9b')
|
||||||
|
}
|
||||||
|
stages {
|
||||||
|
stage('Checkout') {
|
||||||
|
steps {
|
||||||
|
checkout scm
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Install azure-cli') {
|
||||||
|
steps {
|
||||||
|
installAzureCli('jammy', 'amd64')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Download assets') {
|
||||||
|
steps {
|
||||||
|
sh "mkdir ${env.HOME}/workloads"
|
||||||
|
sh 'az storage blob download --container-name private-images --file "$HOME/workloads/windows-server-2022-amd64-2.raw" --name windows-server-2022-amd64-2.raw --connection-string "$AZURE_CONNECTION_STRING"'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Run Windows guest integration tests') {
|
||||||
|
options {
|
||||||
|
timeout(time: 1, unit: 'HOURS')
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
sh 'scripts/dev_cli.sh tests --integration-windows'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Run Windows guest integration tests for musl') {
|
||||||
|
options {
|
||||||
|
timeout(time: 1, unit: 'HOURS')
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
sh 'scripts/dev_cli.sh tests --integration-windows --libc musl'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Worker build - Metrics') {
|
||||||
|
agent { node { label 'jammy-metrics' } }
|
||||||
|
when {
|
||||||
|
branch 'main'
|
||||||
|
beforeAgent true
|
||||||
|
expression {
|
||||||
|
return runWorkers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
environment {
|
||||||
|
METRICS_PUBLISH_KEY = credentials('52e0945f-ce7a-43d1-87af-67d1d87cc40f')
|
||||||
|
}
|
||||||
|
stages {
|
||||||
|
stage('Checkout') {
|
||||||
|
steps {
|
||||||
|
checkout scm
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Run metrics tests') {
|
||||||
|
options {
|
||||||
|
timeout(time: 1, unit: 'HOURS')
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
sh 'scripts/dev_cli.sh tests --metrics -- -- --report-file /root/workloads/metrics.json'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Upload metrics report') {
|
||||||
|
steps {
|
||||||
|
sh 'curl -X PUT https://cloud-hypervisor-metrics.azurewebsites.net/api/publishmetrics -H "x-functions-key: $METRICS_PUBLISH_KEY" -T ~/workloads/metrics.json'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Worker build - Rate Limiter') {
|
||||||
|
agent { node { label 'focal-metrics' } }
|
||||||
|
when {
|
||||||
|
branch 'main'
|
||||||
|
beforeAgent true
|
||||||
|
expression {
|
||||||
|
return runWorkers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stages {
|
||||||
|
stage('Checkout') {
|
||||||
|
steps {
|
||||||
|
checkout scm
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Run rate-limiter integration tests') {
|
||||||
|
options {
|
||||||
|
timeout(time: 10, unit: 'MINUTES')
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
sh 'scripts/dev_cli.sh tests --integration-rate-limiter'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Worker build - SGX') {
|
||||||
|
agent { node { label 'jammy-sgx' } }
|
||||||
|
when {
|
||||||
|
beforeAgent true
|
||||||
|
allOf {
|
||||||
|
branch 'main'
|
||||||
|
expression {
|
||||||
|
return runWorkers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stages {
|
||||||
|
stage('Checkout') {
|
||||||
|
steps {
|
||||||
|
checkout scm
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Run SGX integration tests') {
|
||||||
|
options {
|
||||||
|
timeout(time: 1, unit: 'HOURS')
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
sh 'scripts/dev_cli.sh tests --integration-sgx'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Run SGX integration tests for musl') {
|
||||||
|
options {
|
||||||
|
timeout(time: 1, unit: 'HOURS')
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
sh 'scripts/dev_cli.sh tests --integration-sgx --libc musl'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
post {
|
||||||
|
always {
|
||||||
|
sh "sudo chown -R jenkins.jenkins ${WORKSPACE}"
|
||||||
|
deleteDir()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Worker build - VFIO') {
|
||||||
|
agent { node { label 'jammy-vfio' } }
|
||||||
|
when {
|
||||||
|
beforeAgent true
|
||||||
|
allOf {
|
||||||
|
branch 'main'
|
||||||
|
expression {
|
||||||
|
return runWorkers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stages {
|
||||||
|
stage('Checkout') {
|
||||||
|
steps {
|
||||||
|
checkout scm
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Run VFIO integration tests') {
|
||||||
|
options {
|
||||||
|
timeout(time: 1, unit: 'HOURS')
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
sh 'scripts/dev_cli.sh tests --integration-vfio'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Run VFIO integration tests for musl') {
|
||||||
|
options {
|
||||||
|
timeout(time: 1, unit: 'HOURS')
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
sh 'scripts/dev_cli.sh tests --integration-vfio --libc musl'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
post {
|
||||||
|
always {
|
||||||
|
sh "sudo chown -R jenkins.jenkins ${WORKSPACE}"
|
||||||
|
deleteDir()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
post {
|
||||||
|
regression {
|
||||||
|
script {
|
||||||
|
if (env.BRANCH_NAME == 'main') {
|
||||||
|
slackSend(color: '#ff0000', message: '"main" branch build is now failing', channel: '#jenkins-ci')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fixed {
|
||||||
|
script {
|
||||||
|
if (env.BRANCH_NAME == 'main') {
|
||||||
|
slackSend(color: '#00ff00', message: '"main" branch build is now fixed', channel: '#jenkins-ci')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def cancelPreviousBuilds() {
|
||||||
|
// Check for other instances of this particular build, cancel any that are older than the current one
|
||||||
|
def jobName = env.JOB_NAME
|
||||||
|
def currentBuildNumber = env.BUILD_NUMBER.toInteger()
|
||||||
|
def currentJob = Jenkins.instance.getItemByFullName(jobName)
|
||||||
|
|
||||||
|
// Loop through all instances of this particular job/branch
|
||||||
|
for (def build : currentJob.builds) {
|
||||||
|
if (build.isBuilding() && (build.number.toInteger() < currentBuildNumber)) {
|
||||||
|
echo "Older build still queued. Sending kill signal to build number: ${build.number}"
|
||||||
|
build.doStop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def installAzureCli(distro, arch) {
|
||||||
|
sh 'sudo apt install -y ca-certificates curl apt-transport-https lsb-release gnupg'
|
||||||
|
sh 'curl -sL https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/microsoft.gpg > /dev/null'
|
||||||
|
sh "echo \"deb [arch=${arch}] https://packages.microsoft.com/repos/azure-cli/ ${distro} main\" | sudo tee /etc/apt/sources.list.d/azure-cli.list"
|
||||||
|
sh 'sudo apt update'
|
||||||
|
sh 'sudo apt install -y azure-cli'
|
||||||
|
}
|
||||||
|
|
||||||
|
def boolean skipWorkerBuild() {
|
||||||
|
if (env.CHANGE_TARGET == null) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sh(
|
||||||
|
returnStatus: true,
|
||||||
|
script: "git diff --name-only origin/${env.CHANGE_TARGET}... | grep -v '\\.md'"
|
||||||
|
) != 0) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sh(
|
||||||
|
returnStatus: true,
|
||||||
|
script: "git diff --name-only origin/${env.CHANGE_TARGET}... | grep -v -E 'fuzz/'"
|
||||||
|
) != 0) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sh(
|
||||||
|
returnStatus: true,
|
||||||
|
script: "git diff --name-only origin/${env.CHANGE_TARGET}... | grep -v -E '.github/'"
|
||||||
|
) != 0) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
69
README.md
69
README.md
@@ -59,13 +59,9 @@ based on the [Rust VMM](https://github.com/rust-vmm) crates.
|
|||||||
|
|
||||||
### Architectures
|
### Architectures
|
||||||
|
|
||||||
Cloud Hypervisor supports the `x86-64`, `AArch64` and `riscv64`
|
Cloud Hypervisor supports the `x86-64` and `AArch64` architectures. There are
|
||||||
architectures, with functionality varying across these platforms. The
|
minor differences in functionality between the two architectures
|
||||||
functionality differences between `x86-64` and `AArch64` are documented
|
(see [#1125](https://github.com/cloud-hypervisor/cloud-hypervisor/issues/1125)).
|
||||||
in [#1125](https://github.com/cloud-hypervisor/cloud-hypervisor/issues/1125).
|
|
||||||
The `riscv64` architecture support is experimental and offers limited
|
|
||||||
functionality. For more details and instructions, please refer to [riscv
|
|
||||||
documentation](docs/riscv.md).
|
|
||||||
|
|
||||||
### Guest OS
|
### Guest OS
|
||||||
|
|
||||||
@@ -82,9 +78,9 @@ The following sections describe how to build and run Cloud Hypervisor.
|
|||||||
|
|
||||||
## Host OS
|
## Host OS
|
||||||
|
|
||||||
For required KVM functionality and adequate performance the recommended host
|
For required KVM functionality the minimum host kernel version is 4.11. For
|
||||||
kernel version is 5.13. The majority of the CI currently tests with kernel
|
adequate performance the minimum recommended host kernel version is 5.6. The
|
||||||
version 5.15.
|
majority of the CI currently tests with kernel version 5.15.
|
||||||
|
|
||||||
## Use Pre-built Binaries
|
## Use Pre-built Binaries
|
||||||
|
|
||||||
@@ -112,12 +108,12 @@ do not wish to use the pre-built binaries.
|
|||||||
## Booting Linux
|
## Booting Linux
|
||||||
|
|
||||||
Cloud Hypervisor supports direct kernel boot (the x86-64 kernel requires the kernel
|
Cloud Hypervisor supports direct kernel boot (the x86-64 kernel requires the kernel
|
||||||
built with PVH support or a bzImage) or booting via a firmware (either [Rust Hypervisor
|
built with PVH support) or booting via a firmware (either [Rust Hypervisor
|
||||||
Firmware](https://github.com/cloud-hypervisor/rust-hypervisor-firmware) or an
|
Firmware](https://github.com/cloud-hypervisor/rust-hypervisor-firmware) or an
|
||||||
edk2 UEFI firmware called `CLOUDHV` / `CLOUDHV_EFI`.)
|
edk2 UEFI firmware called `CLOUDHV` / `CLOUDHV_EFI`.)
|
||||||
|
|
||||||
Binary builds of the firmware files are available for the latest release of
|
Binary builds of the firmware files are available for the latest release of
|
||||||
[Rust Hypervisor
|
[Rust Hyperivor
|
||||||
Firmware](https://github.com/cloud-hypervisor/rust-hypervisor-firmware/releases/latest)
|
Firmware](https://github.com/cloud-hypervisor/rust-hypervisor-firmware/releases/latest)
|
||||||
and [our edk2
|
and [our edk2
|
||||||
repository](https://github.com/cloud-hypervisor/edk2/releases/latest)
|
repository](https://github.com/cloud-hypervisor/edk2/releases/latest)
|
||||||
@@ -128,7 +124,7 @@ may be required.
|
|||||||
### Firmware Booting
|
### Firmware Booting
|
||||||
|
|
||||||
Cloud Hypervisor supports booting disk images containing all needed components
|
Cloud Hypervisor supports booting disk images containing all needed components
|
||||||
to run cloud workloads, a.k.a. cloud images.
|
to run cloud workloads, a.k.a. cloud images.
|
||||||
|
|
||||||
The following sample commands will download an Ubuntu Cloud image, converting
|
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
|
it into a format that Cloud Hypervisor can use and a firmware to boot the image
|
||||||
@@ -144,17 +140,14 @@ The Ubuntu cloud images do not ship with a default password so it necessary to
|
|||||||
use a `cloud-init` disk image to customise the image on the first boot. A basic
|
use a `cloud-init` disk image to customise the image on the first boot. A basic
|
||||||
`cloud-init` image is generated by this [script](scripts/create-cloud-init.sh).
|
`cloud-init` image is generated by this [script](scripts/create-cloud-init.sh).
|
||||||
This seeds the image with a default username/password of `cloud/cloud123`. It
|
This seeds the image with a default username/password of `cloud/cloud123`. It
|
||||||
is only necessary to add this disk image on the first boot. Script also assigns
|
is only necessary to add this disk image on the first boot.
|
||||||
default IP address using `test_data/cloud-init/ubuntu/local/network-config` details
|
|
||||||
with `--net "mac=12:34:56:78:90:ab,tap="` option. Then the matching mac address
|
|
||||||
interface will be enabled as per `network-config` details.
|
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
$ sudo setcap cap_net_admin+ep ./cloud-hypervisor
|
$ sudo setcap cap_net_admin+ep ./cloud-hypervisor
|
||||||
$ ./create-cloud-init.sh
|
$ ./create-cloud-init.sh
|
||||||
$ ./cloud-hypervisor \
|
$ ./cloud-hypervisor \
|
||||||
--firmware ./hypervisor-fw \
|
--kernel ./hypervisor-fw \
|
||||||
--disk path=focal-server-cloudimg-amd64.raw path=/tmp/ubuntu-cloudinit.img \
|
--disk path=focal-server-cloudimg-amd64.raw --disk path=/tmp/ubuntu-cloudinit.img \
|
||||||
--cpus boot=4 \
|
--cpus boot=4 \
|
||||||
--memory size=1024M \
|
--memory size=1024M \
|
||||||
--net "tap=,mac=,ip=,mask="
|
--net "tap=,mac=,ip=,mask="
|
||||||
@@ -167,7 +160,7 @@ GRUB) is required then it necessary to switch to the serial console instead of
|
|||||||
```shell
|
```shell
|
||||||
$ ./cloud-hypervisor \
|
$ ./cloud-hypervisor \
|
||||||
--kernel ./hypervisor-fw \
|
--kernel ./hypervisor-fw \
|
||||||
--disk path=focal-server-cloudimg-amd64.raw path=/tmp/ubuntu-cloudinit.img \
|
--disk path=focal-server-cloudimg-amd64.raw --disk path=/tmp/ubuntu-cloudinit.img \
|
||||||
--cpus boot=4 \
|
--cpus boot=4 \
|
||||||
--memory size=1024M \
|
--memory size=1024M \
|
||||||
--net "tap=,mac=,ip=,mask=" \
|
--net "tap=,mac=,ip=,mask=" \
|
||||||
@@ -175,31 +168,24 @@ $ ./cloud-hypervisor \
|
|||||||
--console off
|
--console off
|
||||||
```
|
```
|
||||||
|
|
||||||
## Booting: `--firmware` vs `--kernel`
|
|
||||||
|
|
||||||
The following scenarios are supported by Cloud Hypervisor to bootstrap a VM, i.e.,
|
|
||||||
to load a payload/bootitem(s):
|
|
||||||
|
|
||||||
- Provide firmware
|
|
||||||
- Provide kernel \[+ cmdline\]\ [+ initrd\]
|
|
||||||
|
|
||||||
Please note that our Cloud Hypervisor firmware (`hypervisor-fw`) has a Xen PVH
|
|
||||||
boot entry, therefore it can also be booted via the `--kernel` parameter, as
|
|
||||||
seen in some examples.
|
|
||||||
|
|
||||||
### Custom Kernel and Disk Image
|
### Custom Kernel and Disk Image
|
||||||
|
|
||||||
#### Building your Kernel
|
#### Building your Kernel
|
||||||
|
|
||||||
Cloud Hypervisor also supports direct kernel boot. For x86-64, a `vmlinux` ELF kernel (compiled with PVH support) or a regular bzImage are supported. In order to support development there is a custom branch; however provided the required options are enabled any recent kernel will suffice.
|
Cloud Hypervisor also supports direct kernel boot. For x86-64, a `vmlinux` ELF kernel (compiled with PVH support) is needed. In order to support development there is a custom branch; however provided the required options are enabled any recent kernel will suffice.
|
||||||
|
|
||||||
To build the kernel:
|
To build the kernel:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
# Clone the Cloud Hypervisor Linux branch
|
# Clone the Cloud Hypervisor Linux branch
|
||||||
$ git clone --depth 1 https://github.com/cloud-hypervisor/linux.git -b ch-6.12.8 linux-cloud-hypervisor
|
$ git clone --depth 1 https://github.com/cloud-hypervisor/linux.git -b ch-6.1.6 linux-cloud-hypervisor
|
||||||
$ pushd linux-cloud-hypervisor
|
$ pushd linux-cloud-hypervisor
|
||||||
$ make ch_defconfig
|
# Use the x86-64 cloud-hypervisor kernel config to build your kernel for x86-64
|
||||||
|
$ wget https://raw.githubusercontent.com/cloud-hypervisor/cloud-hypervisor/main/resources/linux-config-x86_64
|
||||||
|
# Use the AArch64 cloud-hypervisor kernel config to build your kernel for AArch64
|
||||||
|
$ wget https://raw.githubusercontent.com/cloud-hypervisor/cloud-hypervisor/main/resources/linux-config-aarch64
|
||||||
|
$ cp linux-config-x86_64 .config # x86-64
|
||||||
|
$ cp linux-config-aarch64 .config # AArch64
|
||||||
# Do native build of the x86-64 kernel
|
# Do native build of the x86-64 kernel
|
||||||
$ KCFLAGS="-Wa,-mx86-used-note=no" make bzImage -j `nproc`
|
$ KCFLAGS="-Wa,-mx86-used-note=no" make bzImage -j `nproc`
|
||||||
# Do native build of the AArch64 kernel
|
# Do native build of the AArch64 kernel
|
||||||
@@ -236,7 +222,7 @@ $ sudo setcap cap_net_admin+ep ./cloud-hypervisor
|
|||||||
$ ./create-cloud-init.sh
|
$ ./create-cloud-init.sh
|
||||||
$ ./cloud-hypervisor \
|
$ ./cloud-hypervisor \
|
||||||
--kernel ./linux-cloud-hypervisor/arch/x86/boot/compressed/vmlinux.bin \
|
--kernel ./linux-cloud-hypervisor/arch/x86/boot/compressed/vmlinux.bin \
|
||||||
--disk path=focal-server-cloudimg-amd64.raw path=/tmp/ubuntu-cloudinit.img \
|
--disk path=focal-server-cloudimg-amd64.raw --disk path=/tmp/ubuntu-cloudinit.img \
|
||||||
--cmdline "console=hvc0 root=/dev/vda1 rw" \
|
--cmdline "console=hvc0 root=/dev/vda1 rw" \
|
||||||
--cpus boot=4 \
|
--cpus boot=4 \
|
||||||
--memory size=1024M \
|
--memory size=1024M \
|
||||||
@@ -250,7 +236,7 @@ $ sudo setcap cap_net_admin+ep ./cloud-hypervisor
|
|||||||
$ ./create-cloud-init.sh
|
$ ./create-cloud-init.sh
|
||||||
$ ./cloud-hypervisor \
|
$ ./cloud-hypervisor \
|
||||||
--kernel ./linux-cloud-hypervisor/arch/arm64/boot/Image \
|
--kernel ./linux-cloud-hypervisor/arch/arm64/boot/Image \
|
||||||
--disk path=focal-server-cloudimg-arm64.raw path=/tmp/ubuntu-cloudinit.img \
|
--disk path=focal-server-cloudimg-arm64.raw --disk path=/tmp/ubuntu-cloudinit.img \
|
||||||
--cmdline "console=hvc0 root=/dev/vda1 rw" \
|
--cmdline "console=hvc0 root=/dev/vda1 rw" \
|
||||||
--cpus boot=4 \
|
--cpus boot=4 \
|
||||||
--memory size=1024M \
|
--memory size=1024M \
|
||||||
@@ -313,9 +299,8 @@ Further details can be found in the [release documentation](docs/releases.md).
|
|||||||
As of 2023-01-03, the following cloud images are supported:
|
As of 2023-01-03, the following cloud images are supported:
|
||||||
|
|
||||||
- [Ubuntu Focal](https://cloud-images.ubuntu.com/focal/current/) (focal-server-cloudimg-{amd64,arm64}.img)
|
- [Ubuntu Focal](https://cloud-images.ubuntu.com/focal/current/) (focal-server-cloudimg-{amd64,arm64}.img)
|
||||||
- [Ubuntu Jammy](https://cloud-images.ubuntu.com/jammy/current/) (jammy-server-cloudimg-{amd64,arm64}.img)
|
- [Ubuntu Jammy](https://cloud-images.ubuntu.com/jammy/current/) (jammy-server-cloudimg-{amd64,arm64}.img )
|
||||||
- [Ubuntu Noble](https://cloud-images.ubuntu.com/noble/current/) (noble-server-cloudimg-{amd64,arm64}.img)
|
- [Fedora 36](https://fedora.mirrorservice.org/fedora/linux/releases/36/Cloud/) ([Fedora-Cloud-Base-36-1.5.x86_64.raw.xz](https://fedora.mirrorservice.org/fedora/linux/releases/36/Cloud/x86_64/images/) / [Fedora-Cloud-Base-36-1.5.aarch64.raw.xz](https://fedora.mirrorservice.org/fedora/linux/releases/36/Cloud/aarch64/images/))
|
||||||
- [Fedora 36](https://archives.fedoraproject.org/pub/archive/fedora/linux/releases/36/Cloud/) ([Fedora-Cloud-Base-36-1.5.x86_64.raw.xz](https://archives.fedoraproject.org/pub/archive/fedora/linux/releases/36/Cloud/x86_64/images/) / [Fedora-Cloud-Base-36-1.5.aarch64.raw.xz](https://archives.fedoraproject.org/pub/archive/fedora/linux/releases/36/Cloud/aarch64/images/))
|
|
||||||
|
|
||||||
Direct kernel boot to userspace should work with a rootfs from most
|
Direct kernel boot to userspace should work with a rootfs from most
|
||||||
distributions although you may need to enable exotic filesystem types in the
|
distributions although you may need to enable exotic filesystem types in the
|
||||||
@@ -387,8 +372,8 @@ are all equal and welcome means of contribution. See the
|
|||||||
|
|
||||||
## Slack
|
## Slack
|
||||||
|
|
||||||
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
|
## Mailing list
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,8 @@
|
|||||||
[package]
|
[package]
|
||||||
authors = ["The Cloud Hypervisor Authors"]
|
|
||||||
edition.workspace = true
|
|
||||||
name = "api_client"
|
name = "api_client"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
authors = ["The Cloud Hypervisor Authors"]
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
thiserror = { workspace = true }
|
vmm-sys-util = "0.11.0"
|
||||||
vmm-sys-util = { workspace = true }
|
|
||||||
|
|
||||||
[lints]
|
|
||||||
workspace = true
|
|
||||||
|
|||||||
@@ -3,32 +3,39 @@
|
|||||||
// 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")]
|
Socket(std::io::Error),
|
||||||
Socket(#[source] std::io::Error),
|
SocketSendFds(vmm_sys_util::errno::Error),
|
||||||
#[error("Error sending file descriptors")]
|
StatusCodeParsing(std::num::ParseIntError),
|
||||||
SocketSendFds(#[source] vmm_sys_util::errno::Error),
|
|
||||||
#[error("Error parsing HTTP status code")]
|
|
||||||
StatusCodeParsing(#[source] std::num::ParseIntError),
|
|
||||||
#[error("HTTP output is missing protocol statement")]
|
|
||||||
MissingProtocol,
|
MissingProtocol,
|
||||||
#[error("Error parsing HTTP Content-Length field")]
|
ContentLengthParsing(std::num::ParseIntError),
|
||||||
ContentLengthParsing(#[source] std::num::ParseIntError),
|
ServerResponse(StatusCode, Option<String>),
|
||||||
#[error("Server responded with error {0:?}: {1:?}")]
|
}
|
||||||
ServerResponse(
|
|
||||||
StatusCode,
|
impl fmt::Display for Error {
|
||||||
// TODO: Move `api` module from `vmm` to dedicated crate and use a common type definition
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
Option<
|
use Error::*;
|
||||||
String, /* Untyped: Currently Vec<String> of error messages from top to root cause */
|
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)]
|
||||||
@@ -38,7 +45,6 @@ pub enum StatusCode {
|
|||||||
NoContent,
|
NoContent,
|
||||||
BadRequest,
|
BadRequest,
|
||||||
NotFound,
|
NotFound,
|
||||||
TooManyRequests,
|
|
||||||
InternalServerError,
|
InternalServerError,
|
||||||
NotImplemented,
|
NotImplemented,
|
||||||
Unknown,
|
Unknown,
|
||||||
@@ -52,7 +58,6 @@ impl StatusCode {
|
|||||||
204 => StatusCode::NoContent,
|
204 => StatusCode::NoContent,
|
||||||
400 => StatusCode::BadRequest,
|
400 => StatusCode::BadRequest,
|
||||||
404 => StatusCode::NotFound,
|
404 => StatusCode::NotFound,
|
||||||
429 => StatusCode::TooManyRequests,
|
|
||||||
500 => StatusCode::InternalServerError,
|
500 => StatusCode::InternalServerError,
|
||||||
501 => StatusCode::NotImplemented,
|
501 => StatusCode::NotImplemented,
|
||||||
_ => StatusCode::Unknown,
|
_ => StatusCode::Unknown,
|
||||||
@@ -118,11 +123,12 @@ fn parse_http_response(socket: &mut dyn Read) -> Result<Option<String>, Error> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(body_offset) = body_offset
|
if let Some(body_offset) = body_offset {
|
||||||
&& let Some(content_length) = content_length
|
if let Some(content_length) = content_length {
|
||||||
&& res.len() >= content_length + body_offset
|
if res.len() >= content_length + body_offset {
|
||||||
{
|
break;
|
||||||
break;
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let body_string = content_length.and(body_offset.map(|o| String::from(&res[o..])));
|
let body_string = content_length.and(body_offset.map(|o| String::from(&res[o..])));
|
||||||
@@ -142,7 +148,7 @@ pub fn simple_api_full_command_with_fds_and_response<T: Read + Write + ScmSocket
|
|||||||
method: &str,
|
method: &str,
|
||||||
full_command: &str,
|
full_command: &str,
|
||||||
request_body: Option<&str>,
|
request_body: Option<&str>,
|
||||||
request_fds: &[RawFd],
|
request_fds: Vec<RawFd>,
|
||||||
) -> Result<Option<String>, Error> {
|
) -> Result<Option<String>, Error> {
|
||||||
socket
|
socket
|
||||||
.send_with_fds(
|
.send_with_fds(
|
||||||
@@ -150,7 +156,7 @@ pub fn simple_api_full_command_with_fds_and_response<T: Read + Write + ScmSocket
|
|||||||
"{method} /api/v1/{full_command} HTTP/1.1\r\nHost: localhost\r\nAccept: */*\r\n"
|
"{method} /api/v1/{full_command} HTTP/1.1\r\nHost: localhost\r\nAccept: */*\r\n"
|
||||||
)
|
)
|
||||||
.as_bytes()],
|
.as_bytes()],
|
||||||
request_fds,
|
&request_fds,
|
||||||
)
|
)
|
||||||
.map_err(Error::SocketSendFds)?;
|
.map_err(Error::SocketSendFds)?;
|
||||||
|
|
||||||
@@ -178,7 +184,7 @@ pub fn simple_api_full_command_with_fds<T: Read + Write + ScmSocket>(
|
|||||||
method: &str,
|
method: &str,
|
||||||
full_command: &str,
|
full_command: &str,
|
||||||
request_body: Option<&str>,
|
request_body: Option<&str>,
|
||||||
request_fds: &[RawFd],
|
request_fds: Vec<RawFd>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let response = simple_api_full_command_with_fds_and_response(
|
let response = simple_api_full_command_with_fds_and_response(
|
||||||
socket,
|
socket,
|
||||||
@@ -188,8 +194,8 @@ pub fn simple_api_full_command_with_fds<T: Read + Write + ScmSocket>(
|
|||||||
request_fds,
|
request_fds,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
if let Some(response) = response {
|
if response.is_some() {
|
||||||
println!("{response}");
|
println!("{}", response.unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -201,7 +207,7 @@ pub fn simple_api_full_command<T: Read + Write + ScmSocket>(
|
|||||||
full_command: &str,
|
full_command: &str,
|
||||||
request_body: Option<&str>,
|
request_body: Option<&str>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
simple_api_full_command_with_fds(socket, method, full_command, request_body, &[])
|
simple_api_full_command_with_fds(socket, method, full_command, request_body, Vec::new())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn simple_api_full_command_and_response<T: Read + Write + ScmSocket>(
|
pub fn simple_api_full_command_and_response<T: Read + Write + ScmSocket>(
|
||||||
@@ -210,7 +216,13 @@ pub fn simple_api_full_command_and_response<T: Read + Write + ScmSocket>(
|
|||||||
full_command: &str,
|
full_command: &str,
|
||||||
request_body: Option<&str>,
|
request_body: Option<&str>,
|
||||||
) -> Result<Option<String>, Error> {
|
) -> Result<Option<String>, Error> {
|
||||||
simple_api_full_command_with_fds_and_response(socket, method, full_command, request_body, &[])
|
simple_api_full_command_with_fds_and_response(
|
||||||
|
socket,
|
||||||
|
method,
|
||||||
|
full_command,
|
||||||
|
request_body,
|
||||||
|
Vec::new(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn simple_api_command_with_fds<T: Read + Write + ScmSocket>(
|
pub fn simple_api_command_with_fds<T: Read + Write + ScmSocket>(
|
||||||
@@ -218,7 +230,7 @@ pub fn simple_api_command_with_fds<T: Read + Write + ScmSocket>(
|
|||||||
method: &str,
|
method: &str,
|
||||||
c: &str,
|
c: &str,
|
||||||
request_body: Option<&str>,
|
request_body: Option<&str>,
|
||||||
request_fds: &[RawFd],
|
request_fds: Vec<RawFd>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
// Create the full VM command. For VMM commands, use
|
// Create the full VM command. For VMM commands, use
|
||||||
// simple_api_full_command().
|
// simple_api_full_command().
|
||||||
@@ -233,5 +245,5 @@ pub fn simple_api_command<T: Read + Write + ScmSocket>(
|
|||||||
c: &str,
|
c: &str,
|
||||||
request_body: Option<&str>,
|
request_body: Option<&str>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
simple_api_command_with_fds(socket, method, c, request_body, &[])
|
simple_api_command_with_fds(socket, method, c, request_body, Vec::new())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,32 +1,29 @@
|
|||||||
[package]
|
[package]
|
||||||
authors = ["The Chromium OS Authors"]
|
|
||||||
edition.workspace = true
|
|
||||||
name = "arch"
|
name = "arch"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
authors = ["The Chromium OS Authors"]
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
fw_cfg = []
|
|
||||||
kvm = ["hypervisor/kvm"]
|
|
||||||
sev_snp = []
|
|
||||||
tdx = []
|
tdx = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = { workspace = true }
|
anyhow = "1.0.69"
|
||||||
byteorder = { workspace = true }
|
byteorder = "1.4.3"
|
||||||
hypervisor = { path = "../hypervisor" }
|
hypervisor = { path = "../hypervisor" }
|
||||||
libc = { workspace = true }
|
libc = "0.2.139"
|
||||||
linux-loader = { workspace = true, features = ["bzimage", "elf", "pe"] }
|
linux-loader = { version = "0.8.1", features = ["elf", "bzimage", "pe"] }
|
||||||
log = { workspace = true }
|
log = "0.4.17"
|
||||||
serde = { workspace = true, features = ["derive", "rc"] }
|
serde = { version = "1.0.151", features = ["rc", "derive"] }
|
||||||
thiserror = { workspace = true }
|
thiserror = "1.0.38"
|
||||||
uuid = { workspace = true }
|
uuid = "1.3.0"
|
||||||
vm-memory = { workspace = true, features = ["backend-bitmap", "backend-mmap"] }
|
versionize = "0.1.9"
|
||||||
vmm-sys-util = { workspace = true, features = ["with-serde"] }
|
versionize_derive = "0.1.4"
|
||||||
|
vm-memory = { version = "0.10.0", features = ["backend-mmap", "backend-bitmap"] }
|
||||||
|
vm-migration = { path = "../vm-migration" }
|
||||||
|
vmm-sys-util = { version = "0.11.0", features = ["with-serde"] }
|
||||||
|
|
||||||
[target.'cfg(any(target_arch = "aarch64", target_arch = "riscv64"))'.dependencies]
|
[target.'cfg(target_arch = "aarch64")'.dependencies]
|
||||||
fdt_parser = { version = "0.1.5", package = "fdt" }
|
fdt_parser = { version = "0.1.4", package = "fdt" }
|
||||||
vm-fdt = { workspace = true }
|
vm-fdt = { git = "https://github.com/rust-vmm/vm-fdt", branch = "main" }
|
||||||
|
|
||||||
[lints]
|
|
||||||
workspace = true
|
|
||||||
|
|||||||
@@ -6,32 +6,27 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the THIRD-PARTY file.
|
// found in the THIRD-PARTY file.
|
||||||
|
|
||||||
|
use crate::{NumaNodes, PciSpaceInfo};
|
||||||
|
use byteorder::{BigEndian, ByteOrder};
|
||||||
|
use hypervisor::arch::aarch64::gic::Vgic;
|
||||||
|
use std::cmp;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::ffi::CStr;
|
use std::ffi::CStr;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::path::Path;
|
use std::result;
|
||||||
|
use std::str;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use std::{cmp, fs, result, str};
|
|
||||||
|
|
||||||
use byteorder::{BigEndian, ByteOrder};
|
use super::super::DeviceType;
|
||||||
use hypervisor::arch::aarch64::gic::Vgic;
|
use super::super::GuestMemoryMmap;
|
||||||
use hypervisor::arch::aarch64::regs::{
|
use super::super::InitramfsConfig;
|
||||||
AARCH64_ARCH_TIMER_HYP_IRQ, AARCH64_ARCH_TIMER_PHYS_NONSECURE_IRQ,
|
use super::layout::{
|
||||||
AARCH64_ARCH_TIMER_PHYS_SECURE_IRQ, AARCH64_ARCH_TIMER_VIRT_IRQ, AARCH64_PMU_IRQ,
|
IRQ_BASE, MEM_32BIT_DEVICES_SIZE, MEM_32BIT_DEVICES_START, MEM_PCI_IO_SIZE, MEM_PCI_IO_START,
|
||||||
|
PCI_HIGH_BASE, PCI_MMIO_CONFIG_SIZE_PER_SEGMENT,
|
||||||
};
|
};
|
||||||
use log::{debug, warn};
|
|
||||||
use thiserror::Error;
|
|
||||||
use vm_fdt::{FdtWriter, FdtWriterResult};
|
use vm_fdt::{FdtWriter, FdtWriterResult};
|
||||||
use vm_memory::{Address, Bytes, GuestMemory, GuestMemoryError, GuestMemoryRegion};
|
use vm_memory::{Address, Bytes, GuestMemory, GuestMemoryError, GuestMemoryRegion};
|
||||||
|
|
||||||
use super::super::{DeviceType, GuestMemoryMmap, InitramfsConfig};
|
|
||||||
use super::layout::{
|
|
||||||
GIC_V2M_COMPATIBLE, GICV2M_SPI_BASE, GICV2M_SPI_NUM, IRQ_BASE, MEM_32BIT_DEVICES_SIZE,
|
|
||||||
MEM_32BIT_DEVICES_START, MEM_PCI_IO_SIZE, MEM_PCI_IO_START, PCI_HIGH_BASE,
|
|
||||||
PCI_MMIO_CONFIG_SIZE_PER_SEGMENT,
|
|
||||||
};
|
|
||||||
use crate::{NumaNodes, PciSpaceInfo};
|
|
||||||
|
|
||||||
// This is a value for uniquely identifying the FDT node declaring the interrupt controller.
|
// This is a value for uniquely identifying the FDT node declaring the interrupt controller.
|
||||||
const GIC_PHANDLE: u32 = 1;
|
const GIC_PHANDLE: u32 = 1;
|
||||||
// This is a value for uniquely identifying the FDT node declaring the MSI controller.
|
// This is a value for uniquely identifying the FDT node declaring the MSI controller.
|
||||||
@@ -45,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;
|
||||||
@@ -65,6 +56,9 @@ const GIC_FDT_IRQ_TYPE_PPI: u32 = 1;
|
|||||||
const IRQ_TYPE_EDGE_RISING: u32 = 1;
|
const IRQ_TYPE_EDGE_RISING: u32 = 1;
|
||||||
const IRQ_TYPE_LEVEL_HI: u32 = 4;
|
const IRQ_TYPE_LEVEL_HI: u32 = 4;
|
||||||
|
|
||||||
|
// PMU PPI interrupt number
|
||||||
|
pub const AARCH64_PMU_IRQ: u32 = 7;
|
||||||
|
|
||||||
// Keys and Buttons
|
// Keys and Buttons
|
||||||
// System Power Down
|
// System Power Down
|
||||||
const KEY_POWER: u32 = 116;
|
const KEY_POWER: u32 = 116;
|
||||||
@@ -80,136 +74,20 @@ 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")]
|
WriteFdtToMemory(GuestMemoryError),
|
||||||
WriteFdtToMemory(#[source] GuestMemoryError),
|
|
||||||
}
|
}
|
||||||
type Result<T> = result::Result<T, Error>;
|
type Result<T> = result::Result<T, Error>;
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
|
||||||
pub enum CacheLevel {
|
|
||||||
/// L1 data cache
|
|
||||||
L1D = 0,
|
|
||||||
/// L1 instruction cache
|
|
||||||
L1I = 1,
|
|
||||||
/// L2 cache
|
|
||||||
L2 = 2,
|
|
||||||
/// L3 cache
|
|
||||||
L3 = 3,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// NOTE: cache size file directory example,
|
|
||||||
/// "/sys/devices/system/cpu/cpu0/cache/index0/size".
|
|
||||||
pub fn get_cache_size(cache_level: CacheLevel) -> u32 {
|
|
||||||
let mut file_directory: String = "/sys/devices/system/cpu/cpu0/cache".to_string();
|
|
||||||
match cache_level {
|
|
||||||
CacheLevel::L1D => file_directory += "/index0/size",
|
|
||||||
CacheLevel::L1I => file_directory += "/index1/size",
|
|
||||||
CacheLevel::L2 => file_directory += "/index2/size",
|
|
||||||
CacheLevel::L3 => file_directory += "/index3/size",
|
|
||||||
}
|
|
||||||
|
|
||||||
let file_path = Path::new(&file_directory);
|
|
||||||
if file_path.exists() {
|
|
||||||
let src = fs::read_to_string(file_directory).expect("File not exists or file corrupted.");
|
|
||||||
// The content of the file is as simple as a size, like: "32K"
|
|
||||||
let src = src.trim();
|
|
||||||
let src_digits: u32 = src[0..src.len() - 1].parse().unwrap();
|
|
||||||
let src_unit = &src[src.len() - 1..];
|
|
||||||
|
|
||||||
src_digits
|
|
||||||
* match src_unit {
|
|
||||||
"K" => 1024,
|
|
||||||
"M" => 1024u32.pow(2),
|
|
||||||
"G" => 1024u32.pow(3),
|
|
||||||
_ => 1,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// NOTE: coherency_line_size file directory example,
|
|
||||||
/// "/sys/devices/system/cpu/cpu0/cache/index0/coherency_line_size".
|
|
||||||
pub fn get_cache_coherency_line_size(cache_level: CacheLevel) -> u32 {
|
|
||||||
let mut file_directory: String = "/sys/devices/system/cpu/cpu0/cache".to_string();
|
|
||||||
match cache_level {
|
|
||||||
CacheLevel::L1D => file_directory += "/index0/coherency_line_size",
|
|
||||||
CacheLevel::L1I => file_directory += "/index1/coherency_line_size",
|
|
||||||
CacheLevel::L2 => file_directory += "/index2/coherency_line_size",
|
|
||||||
CacheLevel::L3 => file_directory += "/index3/coherency_line_size",
|
|
||||||
}
|
|
||||||
|
|
||||||
let file_path = Path::new(&file_directory);
|
|
||||||
if file_path.exists() {
|
|
||||||
let src = fs::read_to_string(file_directory).expect("File not exists or file corrupted.");
|
|
||||||
src.trim().parse::<u32>().unwrap()
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// NOTE: number_of_sets file directory example,
|
|
||||||
/// "/sys/devices/system/cpu/cpu0/cache/index0/number_of_sets".
|
|
||||||
pub fn get_cache_number_of_sets(cache_level: CacheLevel) -> u32 {
|
|
||||||
let mut file_directory: String = "/sys/devices/system/cpu/cpu0/cache".to_string();
|
|
||||||
match cache_level {
|
|
||||||
CacheLevel::L1D => file_directory += "/index0/number_of_sets",
|
|
||||||
CacheLevel::L1I => file_directory += "/index1/number_of_sets",
|
|
||||||
CacheLevel::L2 => file_directory += "/index2/number_of_sets",
|
|
||||||
CacheLevel::L3 => file_directory += "/index3/number_of_sets",
|
|
||||||
}
|
|
||||||
|
|
||||||
let file_path = Path::new(&file_directory);
|
|
||||||
if file_path.exists() {
|
|
||||||
let src = fs::read_to_string(file_directory).expect("File not exists or file corrupted.");
|
|
||||||
src.trim().parse::<u32>().unwrap()
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// NOTE: shared_cpu_list file directory example,
|
|
||||||
/// "/sys/devices/system/cpu/cpu0/cache/index0/shared_cpu_list".
|
|
||||||
pub fn get_cache_shared(cache_level: CacheLevel) -> bool {
|
|
||||||
let mut file_directory: String = "/sys/devices/system/cpu/cpu0/cache".to_string();
|
|
||||||
let mut result = true;
|
|
||||||
|
|
||||||
match cache_level {
|
|
||||||
CacheLevel::L1D | CacheLevel::L1I => result = false,
|
|
||||||
CacheLevel::L2 => file_directory += "/index2/shared_cpu_list",
|
|
||||||
CacheLevel::L3 => file_directory += "/index3/shared_cpu_list",
|
|
||||||
}
|
|
||||||
|
|
||||||
if !result {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let file_path = Path::new(&file_directory);
|
|
||||||
if file_path.exists() {
|
|
||||||
let src = fs::read_to_string(file_directory).expect("File not exists or file corrupted.");
|
|
||||||
let src = src.trim();
|
|
||||||
if src.is_empty() {
|
|
||||||
result = false;
|
|
||||||
} else {
|
|
||||||
result = src.contains('-') || src.contains(',');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
result = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates the flattened device tree for this aarch64 VM.
|
/// 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>(
|
||||||
guest_mem: &GuestMemoryMmap,
|
guest_mem: &GuestMemoryMmap,
|
||||||
cmdline: &str,
|
cmdline: &str,
|
||||||
vcpu_mpidr: &[u64],
|
vcpu_mpidr: Vec<u64>,
|
||||||
vcpu_topology: Option<(u16, u16, u16, u16)>,
|
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: &Arc<Mutex<dyn Vgic>>,
|
||||||
initrd: &Option<InitramfsConfig>,
|
initrd: &Option<InitramfsConfig>,
|
||||||
@@ -222,8 +100,8 @@ pub fn create_fdt<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHash
|
|||||||
let mut fdt = FdtWriter::new().unwrap();
|
let mut fdt = FdtWriter::new().unwrap();
|
||||||
|
|
||||||
// For an explanation why these nodes were introduced in the blob take a look at
|
// For an explanation why these nodes were introduced in the blob take a look at
|
||||||
// the "Device Node Requirements" chapter of the Devicetree Specification.
|
// https://github.com/torvalds/linux/blob/master/Documentation/devicetree/booting-without-of.txt#L845
|
||||||
// https://www.devicetree.org/specifications/
|
// Look for "Required nodes and properties".
|
||||||
|
|
||||||
// Header or the root node as per above mentioned documentation.
|
// Header or the root node as per above mentioned documentation.
|
||||||
let root_node = fdt.begin_node("")?;
|
let root_node = fdt.begin_node("")?;
|
||||||
@@ -235,7 +113,7 @@ pub fn create_fdt<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHash
|
|||||||
// This is not mandatory but we use it to point the root node to the node
|
// This is not mandatory but we use it to point the root node to the node
|
||||||
// containing description of the interrupt controller for this VM.
|
// containing description of the interrupt controller for this VM.
|
||||||
fdt.property_u32("interrupt-parent", GIC_PHANDLE)?;
|
fdt.property_u32("interrupt-parent", GIC_PHANDLE)?;
|
||||||
create_cpu_nodes(&mut fdt, vcpu_mpidr, vcpu_topology, numa_nodes)?;
|
create_cpu_nodes(&mut fdt, &vcpu_mpidr, vcpu_topology, numa_nodes)?;
|
||||||
create_memory_node(&mut fdt, guest_mem, numa_nodes)?;
|
create_memory_node(&mut fdt, guest_mem, numa_nodes)?;
|
||||||
create_chosen_node(&mut fdt, cmdline, initrd)?;
|
create_chosen_node(&mut fdt, cmdline, initrd)?;
|
||||||
create_gic_node(&mut fdt, gic_device)?;
|
create_gic_node(&mut fdt, gic_device)?;
|
||||||
@@ -259,10 +137,10 @@ pub fn create_fdt<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHash
|
|||||||
Ok(fdt_final)
|
Ok(fdt_final)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_fdt_to_memory(fdt_final: &[u8], guest_mem: &GuestMemoryMmap) -> Result<()> {
|
pub fn write_fdt_to_memory(fdt_final: Vec<u8>, guest_mem: &GuestMemoryMmap) -> Result<()> {
|
||||||
// Write FDT to memory.
|
// Write FDT to memory.
|
||||||
guest_mem
|
guest_mem
|
||||||
.write_slice(fdt_final, super::layout::FDT_START)
|
.write_slice(fdt_final.as_slice(), super::layout::FDT_START)
|
||||||
.map_err(Error::WriteFdtToMemory)?;
|
.map_err(Error::WriteFdtToMemory)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -271,7 +149,7 @@ pub fn write_fdt_to_memory(fdt_final: &[u8], guest_mem: &GuestMemoryMmap) -> Res
|
|||||||
fn create_cpu_nodes(
|
fn create_cpu_nodes(
|
||||||
fdt: &mut FdtWriter,
|
fdt: &mut FdtWriter,
|
||||||
vcpu_mpidr: &[u64],
|
vcpu_mpidr: &[u64],
|
||||||
vcpu_topology: Option<(u16, u16, u16, u16)>,
|
vcpu_topology: Option<(u8, u8, u8)>,
|
||||||
numa_nodes: &NumaNodes,
|
numa_nodes: &NumaNodes,
|
||||||
) -> FdtWriterResult<()> {
|
) -> FdtWriterResult<()> {
|
||||||
// See https://github.com/torvalds/linux/blob/master/Documentation/devicetree/bindings/arm/cpus.yaml.
|
// See https://github.com/torvalds/linux/blob/master/Documentation/devicetree/bindings/arm/cpus.yaml.
|
||||||
@@ -280,70 +158,6 @@ fn create_cpu_nodes(
|
|||||||
fdt.property_u32("#size-cells", 0x0)?;
|
fdt.property_u32("#size-cells", 0x0)?;
|
||||||
|
|
||||||
let num_cpus = vcpu_mpidr.len();
|
let num_cpus = vcpu_mpidr.len();
|
||||||
let (threads_per_core, cores_per_die, dies_per_package, packages) =
|
|
||||||
vcpu_topology.unwrap_or((1, 1, 1, 1));
|
|
||||||
let cores_per_package = cores_per_die * dies_per_package;
|
|
||||||
let max_cpus: u32 =
|
|
||||||
threads_per_core as u32 * cores_per_die as u32 * dies_per_package as u32 * packages as u32;
|
|
||||||
|
|
||||||
// Add cache info.
|
|
||||||
// L1 Data Cache Info.
|
|
||||||
let mut l1_d_cache_size: u32 = 0;
|
|
||||||
let mut l1_d_cache_line_size: u32 = 0;
|
|
||||||
let mut l1_d_cache_sets: u32 = 0;
|
|
||||||
|
|
||||||
// L1 Instruction Cache Info.
|
|
||||||
let mut l1_i_cache_size: u32 = 0;
|
|
||||||
let mut l1_i_cache_line_size: u32 = 0;
|
|
||||||
let mut l1_i_cache_sets: u32 = 0;
|
|
||||||
|
|
||||||
// L2 Cache Info.
|
|
||||||
let mut l2_cache_size: u32 = 0;
|
|
||||||
let mut l2_cache_line_size: u32 = 0;
|
|
||||||
let mut l2_cache_sets: u32 = 0;
|
|
||||||
|
|
||||||
// L3 Cache Info.
|
|
||||||
let mut l3_cache_size: u32 = 0;
|
|
||||||
let mut l3_cache_line_size: u32 = 0;
|
|
||||||
let mut l3_cache_sets: u32 = 0;
|
|
||||||
|
|
||||||
// Cache Shared Info.
|
|
||||||
let mut l2_cache_shared: bool = false;
|
|
||||||
let mut l3_cache_shared: bool = false;
|
|
||||||
|
|
||||||
let cache_path = Path::new("/sys/devices/system/cpu/cpu0/cache");
|
|
||||||
let cache_exist: bool = cache_path.exists();
|
|
||||||
if cache_exist {
|
|
||||||
// L1 Data Cache Info.
|
|
||||||
l1_d_cache_size = get_cache_size(CacheLevel::L1D);
|
|
||||||
l1_d_cache_line_size = get_cache_coherency_line_size(CacheLevel::L1D);
|
|
||||||
l1_d_cache_sets = get_cache_number_of_sets(CacheLevel::L1D);
|
|
||||||
|
|
||||||
// L1 Instruction Cache Info.
|
|
||||||
l1_i_cache_size = get_cache_size(CacheLevel::L1I);
|
|
||||||
l1_i_cache_line_size = get_cache_coherency_line_size(CacheLevel::L1I);
|
|
||||||
l1_i_cache_sets = get_cache_number_of_sets(CacheLevel::L1I);
|
|
||||||
|
|
||||||
// L2 Cache Info.
|
|
||||||
l2_cache_size = get_cache_size(CacheLevel::L2);
|
|
||||||
l2_cache_line_size = get_cache_coherency_line_size(CacheLevel::L2);
|
|
||||||
l2_cache_sets = get_cache_number_of_sets(CacheLevel::L2);
|
|
||||||
|
|
||||||
// L3 Cache Info.
|
|
||||||
l3_cache_size = get_cache_size(CacheLevel::L3);
|
|
||||||
l3_cache_line_size = get_cache_coherency_line_size(CacheLevel::L3);
|
|
||||||
l3_cache_sets = get_cache_number_of_sets(CacheLevel::L3);
|
|
||||||
|
|
||||||
// Cache Shared Info.
|
|
||||||
if l2_cache_size != 0 {
|
|
||||||
l2_cache_shared = get_cache_shared(CacheLevel::L2);
|
|
||||||
}
|
|
||||||
if l3_cache_size != 0 {
|
|
||||||
l3_cache_shared = get_cache_shared(CacheLevel::L3);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
warn!("cache sysfs system does not exist.");
|
|
||||||
}
|
|
||||||
|
|
||||||
for (cpu_id, mpidr) in vcpu_mpidr.iter().enumerate().take(num_cpus) {
|
for (cpu_id, mpidr) in vcpu_mpidr.iter().enumerate().take(num_cpus) {
|
||||||
let cpu_name = format!("cpu@{cpu_id:x}");
|
let cpu_name = format!("cpu@{cpu_id:x}");
|
||||||
@@ -363,97 +177,17 @@ fn create_cpu_nodes(
|
|||||||
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));
|
||||||
if numa_node.unwrap().cpus.contains(&(cpu_id as u32)) {
|
if numa_node.unwrap().cpus.contains(&(cpu_id as u8)) {
|
||||||
fdt.property_u32("numa-node-id", numa_node_idx as u32)?;
|
fdt.property_u32("numa-node-id", numa_node_idx as u32)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if cache_exist && l1_d_cache_size != 0 && l1_i_cache_size != 0 {
|
|
||||||
// Add cache info.
|
|
||||||
fdt.property_u32("d-cache-size", l1_d_cache_size)?;
|
|
||||||
fdt.property_u32("d-cache-line-size", l1_d_cache_line_size)?;
|
|
||||||
fdt.property_u32("d-cache-sets", l1_d_cache_sets)?;
|
|
||||||
|
|
||||||
fdt.property_u32("i-cache-size", l1_i_cache_size)?;
|
|
||||||
fdt.property_u32("i-cache-line-size", l1_i_cache_line_size)?;
|
|
||||||
fdt.property_u32("i-cache-sets", l1_i_cache_sets)?;
|
|
||||||
|
|
||||||
if l2_cache_size != 0 && !l2_cache_shared {
|
|
||||||
fdt.property_u32(
|
|
||||||
"next-level-cache",
|
|
||||||
cpu_id as u32 + max_cpus + FIRST_VCPU_PHANDLE + L2_CACHE_PHANDLE,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let l2_cache_name = "l2-cache0";
|
|
||||||
let l2_cache_node = fdt.begin_node(l2_cache_name)?;
|
|
||||||
// PHANDLE is used to mark device node, and PHANDLE is unique. To avoid phandle
|
|
||||||
// conflicts with other device nodes, consider the previous CPU PHANDLE, so the
|
|
||||||
// CPU L2 cache PHANDLE must start from the largest CPU PHANDLE plus 1.
|
|
||||||
fdt.property_u32(
|
|
||||||
"phandle",
|
|
||||||
cpu_id as u32 + max_cpus + FIRST_VCPU_PHANDLE + L2_CACHE_PHANDLE,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
fdt.property_string("compatible", "cache")?;
|
|
||||||
fdt.property_u32("cache-size", l2_cache_size)?;
|
|
||||||
fdt.property_u32("cache-line-size", l2_cache_line_size)?;
|
|
||||||
fdt.property_u32("cache-sets", l2_cache_sets)?;
|
|
||||||
fdt.property_u32("cache-level", 2)?;
|
|
||||||
|
|
||||||
if l3_cache_size != 0 && l3_cache_shared {
|
|
||||||
let package_id: u32 = cpu_id as u32 / cores_per_package as u32;
|
|
||||||
fdt.property_u32(
|
|
||||||
"next-level-cache",
|
|
||||||
package_id
|
|
||||||
+ num_cpus as u32
|
|
||||||
+ max_cpus
|
|
||||||
+ FIRST_VCPU_PHANDLE
|
|
||||||
+ L2_CACHE_PHANDLE
|
|
||||||
+ L3_CACHE_PHANDLE,
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
fdt.end_node(l2_cache_node)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fdt.end_node(cpu_node)?;
|
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_die, dies_per_package, packages) = topology;
|
let (threads_per_core, cores_per_package, packages) = topology;
|
||||||
let cores_per_package = cores_per_die * dies_per_package;
|
|
||||||
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.
|
||||||
@@ -513,7 +247,7 @@ fn create_memory_node(
|
|||||||
let memory_region_size: u64 = memory_region.size() as u64;
|
let memory_region_size: u64 = memory_region.size() as u64;
|
||||||
mem_reg_prop.push(memory_region_start_addr);
|
mem_reg_prop.push(memory_region_start_addr);
|
||||||
mem_reg_prop.push(memory_region_size);
|
mem_reg_prop.push(memory_region_size);
|
||||||
// Set the node address the first non-zero region address
|
// Set the node address the first non-zero regison address
|
||||||
if node_memory_addr == 0 {
|
if node_memory_addr == 0 {
|
||||||
node_memory_addr = memory_region_start_addr;
|
node_memory_addr = memory_region_start_addr;
|
||||||
}
|
}
|
||||||
@@ -526,84 +260,32 @@ fn create_memory_node(
|
|||||||
fdt.end_node(memory_node)?;
|
fdt.end_node(memory_node)?;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Note: memory regions from "GuestMemory" are sorted and non-zero sized.
|
let last_addr = guest_mem.last_addr().raw_value();
|
||||||
let ram_regions = {
|
if last_addr < super::layout::MEM_32BIT_RESERVED_START.raw_value() {
|
||||||
let mut ram_regions = Vec::new();
|
// Case 1: all RAM is under the hole
|
||||||
let mut current_start = guest_mem
|
let mem_size = last_addr - super::layout::RAM_START.raw_value() + 1;
|
||||||
.iter()
|
let mem_reg_prop = [super::layout::RAM_START.raw_value(), mem_size];
|
||||||
.next()
|
let memory_node = fdt.begin_node("memory")?;
|
||||||
.map(GuestMemoryRegion::start_addr)
|
fdt.property_string("device_type", "memory")?;
|
||||||
.expect("GuestMemory must have one memory region at least")
|
fdt.property_array_u64("reg", &mem_reg_prop)?;
|
||||||
.raw_value();
|
fdt.end_node(memory_node)?;
|
||||||
let mut current_end = current_start;
|
} else {
|
||||||
|
// Case 2: RAM is split by the hole
|
||||||
for (start, size) in guest_mem
|
// Region 1: RAM before the hole
|
||||||
.iter()
|
let mem_size = super::layout::MEM_32BIT_RESERVED_START.raw_value()
|
||||||
.map(|m| (m.start_addr().raw_value(), m.len()))
|
- super::layout::RAM_START.raw_value();
|
||||||
{
|
let mem_reg_prop = [super::layout::RAM_START.raw_value(), mem_size];
|
||||||
if current_end == start {
|
let memory_node_name = format!("memory@{:x}", super::layout::RAM_START.raw_value());
|
||||||
// This zone is continuous with the previous one.
|
|
||||||
current_end += size;
|
|
||||||
} else {
|
|
||||||
ram_regions.push((current_start, current_end));
|
|
||||||
|
|
||||||
current_start = start;
|
|
||||||
current_end = start + size;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ram_regions.push((current_start, current_end));
|
|
||||||
|
|
||||||
ram_regions
|
|
||||||
};
|
|
||||||
|
|
||||||
if ram_regions.len() > 2 {
|
|
||||||
panic!(
|
|
||||||
"There should be up to two non-continuous regions, divided by the
|
|
||||||
gap at the end of 32bit address space."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the memory node for memory region before the gap
|
|
||||||
{
|
|
||||||
let (first_region_start, first_region_end) = ram_regions
|
|
||||||
.first()
|
|
||||||
.expect("There should be at last one memory region");
|
|
||||||
let ram_start = super::layout::RAM_START.raw_value();
|
|
||||||
let mem_32bit_reserved_start = super::layout::MEM_32BIT_RESERVED_START.raw_value();
|
|
||||||
|
|
||||||
if !((first_region_start <= &ram_start)
|
|
||||||
&& (first_region_end > &ram_start)
|
|
||||||
&& (first_region_end <= &mem_32bit_reserved_start))
|
|
||||||
{
|
|
||||||
panic!(
|
|
||||||
"Unexpected first memory region layout: (start: 0x{first_region_start:08x}, end: 0x{first_region_end:08x}).
|
|
||||||
ram_start: 0x{ram_start:08x}, mem_32bit_reserved_start: 0x{mem_32bit_reserved_start:08x}"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mem_size = first_region_end - ram_start;
|
|
||||||
let mem_reg_prop = [ram_start, mem_size];
|
|
||||||
let memory_node_name = format!("memory@{ram_start:x}");
|
|
||||||
let memory_node = fdt.begin_node(&memory_node_name)?;
|
let memory_node = fdt.begin_node(&memory_node_name)?;
|
||||||
fdt.property_string("device_type", "memory")?;
|
fdt.property_string("device_type", "memory")?;
|
||||||
fdt.property_array_u64("reg", &mem_reg_prop)?;
|
fdt.property_array_u64("reg", &mem_reg_prop)?;
|
||||||
fdt.end_node(memory_node)?;
|
fdt.end_node(memory_node)?;
|
||||||
}
|
|
||||||
|
|
||||||
// Create the memory map entry for memory region after the gap if any
|
// Region 2: RAM after the hole
|
||||||
if let Some((second_region_start, second_region_end)) = ram_regions.get(1) {
|
let mem_size = last_addr - super::layout::RAM_64BIT_START.raw_value() + 1;
|
||||||
let ram_64bit_start = super::layout::RAM_64BIT_START.raw_value();
|
let mem_reg_prop = [super::layout::RAM_64BIT_START.raw_value(), mem_size];
|
||||||
|
let memory_node_name =
|
||||||
if second_region_start != &ram_64bit_start {
|
format!("memory@{:x}", super::layout::RAM_64BIT_START.raw_value());
|
||||||
panic!(
|
|
||||||
"Unexpected second memory region layout: start: 0x{second_region_start:08x}, ram_64bit_start: 0x{ram_64bit_start:08x}"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mem_size = second_region_end - ram_64bit_start;
|
|
||||||
let mem_reg_prop = [ram_64bit_start, mem_size];
|
|
||||||
let memory_node_name = format!("memory@{ram_64bit_start:x}");
|
|
||||||
let memory_node = fdt.begin_node(&memory_node_name)?;
|
let memory_node = fdt.begin_node(&memory_node_name)?;
|
||||||
fdt.property_string("device_type", "memory")?;
|
fdt.property_string("device_type", "memory")?;
|
||||||
fdt.property_array_u64("reg", &mem_reg_prop)?;
|
fdt.property_array_u64("reg", &mem_reg_prop)?;
|
||||||
@@ -660,19 +342,11 @@ fn create_gic_node(fdt: &mut FdtWriter, gic_device: &Arc<Mutex<dyn Vgic>>) -> Fd
|
|||||||
|
|
||||||
if gic_device.lock().unwrap().msi_compatible() {
|
if gic_device.lock().unwrap().msi_compatible() {
|
||||||
let msic_node = fdt.begin_node("msic")?;
|
let msic_node = fdt.begin_node("msic")?;
|
||||||
let msi_compatibility = gic_device.lock().unwrap().msi_compatibility().to_string();
|
fdt.property_string("compatible", gic_device.lock().unwrap().msi_compatibility())?;
|
||||||
|
|
||||||
fdt.property_string("compatible", msi_compatibility.as_str())?;
|
|
||||||
fdt.property_null("msi-controller")?;
|
fdt.property_null("msi-controller")?;
|
||||||
fdt.property_u32("phandle", MSI_PHANDLE)?;
|
fdt.property_u32("phandle", MSI_PHANDLE)?;
|
||||||
let msi_reg_prop = gic_device.lock().unwrap().msi_properties();
|
let msi_reg_prop = gic_device.lock().unwrap().msi_properties();
|
||||||
fdt.property_array_u64("reg", &msi_reg_prop)?;
|
fdt.property_array_u64("reg", &msi_reg_prop)?;
|
||||||
|
|
||||||
if msi_compatibility == GIC_V2M_COMPATIBLE {
|
|
||||||
fdt.property_u32("arm,msi-base-spi", GICV2M_SPI_BASE)?;
|
|
||||||
fdt.property_u32("arm,msi-num-spis", GICV2M_SPI_NUM)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
fdt.end_node(msic_node)?;
|
fdt.end_node(msic_node)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -699,14 +373,9 @@ fn create_clock_node(fdt: &mut FdtWriter) -> FdtWriterResult<()> {
|
|||||||
|
|
||||||
fn create_timer_node(fdt: &mut FdtWriter) -> FdtWriterResult<()> {
|
fn create_timer_node(fdt: &mut FdtWriter) -> FdtWriterResult<()> {
|
||||||
// See
|
// See
|
||||||
// https://github.com/torvalds/linux/blob/master/Documentation/devicetree/bindings/timer/arm%2Carch_timer.yaml
|
// https://github.com/torvalds/linux/blob/master/Documentation/devicetree/bindings/interrupt-controller/arch_timer.txt
|
||||||
// These are fixed interrupt numbers for the timer device.
|
// These are fixed interrupt numbers for the timer device.
|
||||||
let irqs = [
|
let irqs = [13, 14, 11, 10];
|
||||||
AARCH64_ARCH_TIMER_PHYS_SECURE_IRQ,
|
|
||||||
AARCH64_ARCH_TIMER_PHYS_NONSECURE_IRQ,
|
|
||||||
AARCH64_ARCH_TIMER_VIRT_IRQ,
|
|
||||||
AARCH64_ARCH_TIMER_HYP_IRQ,
|
|
||||||
];
|
|
||||||
let compatible = "arm,armv8-timer";
|
let compatible = "arm,armv8-timer";
|
||||||
|
|
||||||
let mut timer_reg_cells: Vec<u32> = Vec::new();
|
let mut timer_reg_cells: Vec<u32> = Vec::new();
|
||||||
@@ -841,21 +510,6 @@ fn create_gpio_node<T: DeviceInfoForFdt + Clone + Debug>(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://www.kernel.org/doc/Documentation/devicetree/bindings/arm/fw-cfg.txt
|
|
||||||
#[cfg(feature = "fw_cfg")]
|
|
||||||
fn create_fw_cfg_node<T: DeviceInfoForFdt + Clone + Debug>(
|
|
||||||
fdt: &mut FdtWriter,
|
|
||||||
dev_info: &T,
|
|
||||||
) -> FdtWriterResult<()> {
|
|
||||||
// FwCfg node
|
|
||||||
let fw_cfg_node = fdt.begin_node(&format!("fw-cfg@{:x}", dev_info.addr()))?;
|
|
||||||
fdt.property("compatible", b"qemu,fw-cfg-mmio\0")?;
|
|
||||||
fdt.property_array_u64("reg", &[dev_info.addr(), dev_info.length()])?;
|
|
||||||
fdt.end_node(fw_cfg_node)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_devices_node<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHasher>(
|
fn create_devices_node<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHasher>(
|
||||||
fdt: &mut FdtWriter,
|
fdt: &mut FdtWriter,
|
||||||
dev_info: &HashMap<(DeviceType, String), T, S>,
|
dev_info: &HashMap<(DeviceType, String), T, S>,
|
||||||
@@ -871,8 +525,6 @@ fn create_devices_node<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::Buil
|
|||||||
DeviceType::Virtio(_) => {
|
DeviceType::Virtio(_) => {
|
||||||
ordered_virtio_device.push(info);
|
ordered_virtio_device.push(info);
|
||||||
}
|
}
|
||||||
#[cfg(feature = "fw_cfg")]
|
|
||||||
DeviceType::FwCfg => create_fw_cfg_node(fdt, info)?,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -912,7 +564,7 @@ fn create_pci_nodes(
|
|||||||
for pci_device_info_elem in pci_device_info.iter() {
|
for pci_device_info_elem in pci_device_info.iter() {
|
||||||
// EDK2 requires the PCIe high space above 4G address.
|
// EDK2 requires the PCIe high space above 4G address.
|
||||||
// The actual space in CLH follows the RAM. If the RAM space is small, the PCIe high space
|
// The actual space in CLH follows the RAM. If the RAM space is small, the PCIe high space
|
||||||
// could fall below 4G.
|
// could fall bellow 4G.
|
||||||
// Here we cut off PCI device space below 8G in FDT to workaround the EDK2 check.
|
// Here we cut off PCI device space below 8G in FDT to workaround the EDK2 check.
|
||||||
// But the address written in ACPI is not impacted.
|
// But the address written in ACPI is not impacted.
|
||||||
let (pci_device_base_64bit, pci_device_size_64bit) =
|
let (pci_device_base_64bit, pci_device_size_64bit) =
|
||||||
@@ -1002,39 +654,39 @@ fn create_pci_nodes(
|
|||||||
fdt.property_array_u32("msi-map", &msi_map)?;
|
fdt.property_array_u32("msi-map", &msi_map)?;
|
||||||
fdt.property_u32("msi-parent", MSI_PHANDLE)?;
|
fdt.property_u32("msi-parent", MSI_PHANDLE)?;
|
||||||
|
|
||||||
if pci_device_info_elem.pci_segment_id == 0
|
if pci_device_info_elem.pci_segment_id == 0 {
|
||||||
&& let Some(virtio_iommu_bdf) = virtio_iommu_bdf
|
if let Some(virtio_iommu_bdf) = virtio_iommu_bdf {
|
||||||
{
|
// See kernel document Documentation/devicetree/bindings/pci/pci-iommu.txt
|
||||||
// See kernel document Documentation/devicetree/bindings/pci/pci-iommu.txt
|
// for 'iommu-map' attribute setting.
|
||||||
// for 'iommu-map' attribute setting.
|
let iommu_map = [
|
||||||
let iommu_map = [
|
0_u32,
|
||||||
0_u32,
|
VIRTIO_IOMMU_PHANDLE,
|
||||||
VIRTIO_IOMMU_PHANDLE,
|
0_u32,
|
||||||
0_u32,
|
virtio_iommu_bdf,
|
||||||
virtio_iommu_bdf,
|
virtio_iommu_bdf + 1,
|
||||||
virtio_iommu_bdf + 1,
|
VIRTIO_IOMMU_PHANDLE,
|
||||||
VIRTIO_IOMMU_PHANDLE,
|
virtio_iommu_bdf + 1,
|
||||||
virtio_iommu_bdf + 1,
|
0xffff - virtio_iommu_bdf,
|
||||||
0xffff - virtio_iommu_bdf,
|
];
|
||||||
];
|
fdt.property_array_u32("iommu-map", &iommu_map)?;
|
||||||
fdt.property_array_u32("iommu-map", &iommu_map)?;
|
|
||||||
|
|
||||||
// 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@{virtio_iommu_bdf:x}");
|
||||||
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")?;
|
||||||
|
|
||||||
// 'reg' is a five-cell address encoded as
|
// 'reg' is a five-cell address encoded as
|
||||||
// (phys.hi phys.mid phys.lo size.hi size.lo). phys.hi should contain the
|
// (phys.hi phys.mid phys.lo size.hi size.lo). phys.hi should contain the
|
||||||
// device's BDF as 0b00000000 bbbbbbbb dddddfff 00000000. The other cells
|
// device's BDF as 0b00000000 bbbbbbbb dddddfff 00000000. The other cells
|
||||||
// should be zero.
|
// should be zero.
|
||||||
let reg = [virtio_iommu_bdf << 8, 0_u32, 0_u32, 0_u32, 0_u32];
|
let reg = [virtio_iommu_bdf << 8, 0_u32, 0_u32, 0_u32, 0_u32];
|
||||||
fdt.property_array_u32("reg", ®)?;
|
fdt.property_array_u32("reg", ®)?;
|
||||||
fdt.property_u32("phandle", VIRTIO_IOMMU_PHANDLE)?;
|
fdt.property_u32("phandle", VIRTIO_IOMMU_PHANDLE)?;
|
||||||
|
|
||||||
fdt.end_node(virtio_iommu_node)?;
|
fdt.end_node(virtio_iommu_node)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fdt.end_node(pci_node)?;
|
fdt.end_node(pci_node)?;
|
||||||
@@ -1129,7 +781,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,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1152,7 +807,7 @@ fn print_node(node: fdt_parser::node::FdtNode<'_, '_>, n_spaces: usize) {
|
|||||||
array,
|
array,
|
||||||
indent = (n_spaces + 2)
|
indent = (n_spaces + 2)
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print children nodes if there is any
|
// Print children nodes if there is any
|
||||||
|
|||||||
@@ -111,9 +111,8 @@ pub const RAM_64BIT_START: GuestAddress = GuestAddress(0x1_0000_0000);
|
|||||||
pub const CMDLINE_MAX_SIZE: usize = 2048;
|
pub const CMDLINE_MAX_SIZE: usize = 2048;
|
||||||
|
|
||||||
/// FDT is at the beginning of RAM.
|
/// FDT is at the beginning of RAM.
|
||||||
|
/// Maximum size of the device tree blob as specified in https://www.kernel.org/doc/Documentation/arm64/booting.txt.
|
||||||
pub const FDT_START: GuestAddress = RAM_START;
|
pub const FDT_START: GuestAddress = RAM_START;
|
||||||
/// Maximum size of the device tree blob as specified in [the kernel
|
|
||||||
/// documentation](https://www.kernel.org/doc/Documentation/arm64/booting.txt).
|
|
||||||
pub const FDT_MAX_SIZE: u64 = 0x20_0000;
|
pub const FDT_MAX_SIZE: u64 = 0x20_0000;
|
||||||
|
|
||||||
/// Put ACPI table above dtb
|
/// Put ACPI table above dtb
|
||||||
@@ -138,12 +137,3 @@ pub const IRQ_BASE: u32 = 32;
|
|||||||
|
|
||||||
/// Number of supported interrupts
|
/// Number of supported interrupts
|
||||||
pub const IRQ_NUM: u32 = 256;
|
pub const IRQ_NUM: u32 = 256;
|
||||||
|
|
||||||
/// Base SPI interrupt number for the GICv2M MSI frame
|
|
||||||
pub const GICV2M_SPI_BASE: u32 = 128;
|
|
||||||
|
|
||||||
/// Total number of SPIs for the GICv2M MSI frame
|
|
||||||
pub const GICV2M_SPI_NUM: u32 = 64;
|
|
||||||
|
|
||||||
/// GICv2M compatible string
|
|
||||||
pub const GIC_V2M_COMPATIBLE: &str = "arm,gic-v2m-frame";
|
|
||||||
|
|||||||
@@ -6,56 +6,52 @@
|
|||||||
pub mod fdt;
|
pub mod fdt;
|
||||||
/// Layout for this aarch64 system.
|
/// Layout for this aarch64 system.
|
||||||
pub mod layout;
|
pub mod layout;
|
||||||
|
/// Module for system registers definition
|
||||||
|
pub mod regs;
|
||||||
/// Module for loading UEFI binary.
|
/// Module for loading UEFI binary.
|
||||||
pub mod uefi;
|
pub mod uefi;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::fmt::Debug;
|
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
|
|
||||||
use hypervisor::arch::aarch64::gic::Vgic;
|
|
||||||
use hypervisor::arch::aarch64::regs::MPIDR_EL1;
|
|
||||||
use log::{Level, log_enabled};
|
|
||||||
use thiserror::Error;
|
|
||||||
use vm_memory::{Address, GuestAddress, GuestMemory, GuestMemoryAtomic};
|
|
||||||
|
|
||||||
pub use self::fdt::DeviceInfoForFdt;
|
pub use self::fdt::DeviceInfoForFdt;
|
||||||
use crate::{DeviceType, GuestMemoryMmap, NumaNodes, PciSpaceInfo, RegionType};
|
use crate::{DeviceType, GuestMemoryMmap, NumaNodes, PciSpaceInfo, RegionType};
|
||||||
|
use hypervisor::arch::aarch64::gic::Vgic;
|
||||||
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, Mutex};
|
||||||
|
use vm_memory::{Address, GuestAddress, GuestMemory, GuestMemoryAtomic, 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")]
|
WriteFdtToMemory(fdt::Error),
|
||||||
WriteFdtToMemory(#[source] fdt::Error),
|
|
||||||
|
|
||||||
/// Failed to create a GIC.
|
/// Failed to create a GIC.
|
||||||
#[error("Failed to create a GIC")]
|
|
||||||
SetupGic,
|
SetupGic,
|
||||||
|
|
||||||
/// Failed to compute the initramfs address.
|
/// Failed to compute the initramfs address.
|
||||||
#[error("Failed to compute the initramfs address")]
|
|
||||||
InitramfsAddress,
|
InitramfsAddress,
|
||||||
|
|
||||||
/// Error configuring the general purpose registers
|
/// Error configuring the general purpose registers
|
||||||
#[error("Error configuring the general purpose registers")]
|
RegsConfiguration(hypervisor::HypervisorCpuError),
|
||||||
RegsConfiguration(#[source] hypervisor::HypervisorCpuError),
|
|
||||||
|
|
||||||
/// Error configuring the MPIDR register
|
/// Error configuring the MPIDR register
|
||||||
#[error("Error configuring the MPIDR register")]
|
VcpuRegMpidr(hypervisor::HypervisorCpuError),
|
||||||
VcpuRegMpidr(#[source] hypervisor::HypervisorCpuError),
|
|
||||||
|
|
||||||
/// Error initializing PMU for vcpu
|
/// Error initializing PMU for vcpu
|
||||||
#[error("Error initializing PMU for vcpu")]
|
|
||||||
VcpuInitPmu,
|
VcpuInitPmu,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<Error> for super::Error {
|
||||||
|
fn from(e: Error) -> super::Error {
|
||||||
|
super::Error::PlatformSpecific(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
/// Specifies the entry point address where the guest must start
|
/// Specifies the entry point address where the guest must start
|
||||||
/// executing code.
|
/// executing code.
|
||||||
@@ -66,8 +62,8 @@ 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: &dyn hypervisor::Vcpu,
|
vcpu: &Arc<dyn hypervisor::Vcpu>,
|
||||||
id: u32,
|
id: u8,
|
||||||
boot_setup: Option<(EntryPoint, &GuestMemoryAtomic<GuestMemoryMmap>)>,
|
boot_setup: Option<(EntryPoint, &GuestMemoryAtomic<GuestMemoryMmap>)>,
|
||||||
) -> super::Result<u64> {
|
) -> super::Result<u64> {
|
||||||
if let Some((kernel_entry_point, _guest_memory)) = boot_setup {
|
if let Some((kernel_entry_point, _guest_memory)) = boot_setup {
|
||||||
@@ -79,12 +75,14 @@ pub fn configure_vcpu(
|
|||||||
.map_err(Error::RegsConfiguration)?;
|
.map_err(Error::RegsConfiguration)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mpidr = vcpu.get_sys_reg(MPIDR_EL1).map_err(Error::VcpuRegMpidr)?;
|
let mpidr = vcpu
|
||||||
|
.get_sys_reg(regs::MPIDR_EL1)
|
||||||
|
.map_err(Error::VcpuRegMpidr)?;
|
||||||
Ok(mpidr)
|
Ok(mpidr)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn arch_memory_regions() -> Vec<(GuestAddress, usize, RegionType)> {
|
pub fn arch_memory_regions(size: GuestUsize) -> Vec<(GuestAddress, usize, RegionType)> {
|
||||||
vec![
|
let mut regions = vec![
|
||||||
// 0 MiB ~ 256 MiB: UEFI, GIC and legacy devices
|
// 0 MiB ~ 256 MiB: UEFI, GIC and legacy devices
|
||||||
(
|
(
|
||||||
GuestAddress(0),
|
GuestAddress(0),
|
||||||
@@ -103,21 +101,39 @@ 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
|
];
|
||||||
(
|
|
||||||
|
let ram_32bit_space_size =
|
||||||
|
layout::MEM_32BIT_RESERVED_START.unchecked_offset_from(layout::RAM_START);
|
||||||
|
|
||||||
|
// RAM space
|
||||||
|
// Case1: guest memory fits before the gap
|
||||||
|
if size <= ram_32bit_space_size {
|
||||||
|
regions.push((layout::RAM_START, size as usize, RegionType::Ram));
|
||||||
|
// Case2: guest memory extends beyond the gap
|
||||||
|
} else {
|
||||||
|
// Push memory before the gap
|
||||||
|
regions.push((
|
||||||
layout::RAM_START,
|
layout::RAM_START,
|
||||||
layout::MEM_32BIT_RESERVED_START.unchecked_offset_from(layout::RAM_START) as usize,
|
ram_32bit_space_size as usize,
|
||||||
RegionType::Ram,
|
RegionType::Ram,
|
||||||
),
|
));
|
||||||
// 4GiB ~ inf: RAM after the gap
|
// Other memory is placed after 4GiB
|
||||||
(layout::RAM_64BIT_START, usize::MAX, RegionType::Ram),
|
regions.push((
|
||||||
// Add the 32-bit reserved memory hole as a reserved region
|
layout::RAM_64BIT_START,
|
||||||
(
|
(size - ram_32bit_space_size) as usize,
|
||||||
layout::MEM_32BIT_RESERVED_START,
|
RegionType::Ram,
|
||||||
layout::MEM_32BIT_RESERVED_SIZE as usize,
|
));
|
||||||
RegionType::Reserved,
|
}
|
||||||
),
|
|
||||||
]
|
// Add the 32-bit reserved memory hole as a reserved region
|
||||||
|
regions.push((
|
||||||
|
layout::MEM_32BIT_RESERVED_START,
|
||||||
|
layout::MEM_32BIT_RESERVED_SIZE as usize,
|
||||||
|
RegionType::Reserved,
|
||||||
|
));
|
||||||
|
|
||||||
|
regions
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Configures the system and should be called once per vm before starting vcpu threads.
|
/// Configures the system and should be called once per vm before starting vcpu threads.
|
||||||
@@ -125,8 +141,8 @@ pub fn arch_memory_regions() -> Vec<(GuestAddress, usize, RegionType)> {
|
|||||||
pub fn configure_system<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHasher>(
|
pub fn configure_system<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHasher>(
|
||||||
guest_mem: &GuestMemoryMmap,
|
guest_mem: &GuestMemoryMmap,
|
||||||
cmdline: &str,
|
cmdline: &str,
|
||||||
vcpu_mpidr: &[u64],
|
vcpu_mpidr: Vec<u64>,
|
||||||
vcpu_topology: Option<(u16, u16, u16, u16)>,
|
vcpu_topology: Option<(u8, u8, u8)>,
|
||||||
device_info: &HashMap<(DeviceType, String), T, S>,
|
device_info: &HashMap<(DeviceType, String), T, S>,
|
||||||
initrd: &Option<super::InitramfsConfig>,
|
initrd: &Option<super::InitramfsConfig>,
|
||||||
pci_space_info: &[PciSpaceInfo],
|
pci_space_info: &[PciSpaceInfo],
|
||||||
@@ -154,7 +170,7 @@ pub fn configure_system<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::Bui
|
|||||||
fdt::print_fdt(&fdt_final);
|
fdt::print_fdt(&fdt_final);
|
||||||
}
|
}
|
||||||
|
|
||||||
fdt::write_fdt_to_memory(&fdt_final, guest_mem).map_err(Error::WriteFdtToMemory)?;
|
fdt::write_fdt_to_memory(fdt_final, guest_mem).map_err(Error::WriteFdtToMemory)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -180,8 +196,11 @@ pub fn initramfs_load_addr(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_host_cpu_phys_bits(hypervisor: &dyn hypervisor::Hypervisor) -> u8 {
|
pub fn get_host_cpu_phys_bits() -> u8 {
|
||||||
let host_cpu_phys_bits = hypervisor.get_host_ipa_limit().try_into().unwrap();
|
// A dummy hypervisor created only for querying the host IPA size and will
|
||||||
|
// be freed after the query.
|
||||||
|
let hv = hypervisor::new().unwrap();
|
||||||
|
let host_cpu_phys_bits = hv.get_host_ipa_limit().try_into().unwrap();
|
||||||
if host_cpu_phys_bits == 0 {
|
if host_cpu_phys_bits == 0 {
|
||||||
// Host kernel does not support `get_host_ipa_limit`,
|
// Host kernel does not support `get_host_ipa_limit`,
|
||||||
// we return the default value 40 here.
|
// we return the default value 40 here.
|
||||||
@@ -192,16 +211,30 @@ pub fn get_host_cpu_phys_bits(hypervisor: &dyn hypervisor::Hypervisor) -> u8 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod unit_tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_arch_memory_regions_dram() {
|
fn test_arch_memory_regions_dram_2gb() {
|
||||||
let regions = arch_memory_regions();
|
let regions = arch_memory_regions((1usize << 31) as u64); //2GB
|
||||||
|
assert_eq!(5, regions.len());
|
||||||
|
assert_eq!(layout::RAM_START, regions[3].0);
|
||||||
|
assert_eq!((1usize << 31), regions[3].1);
|
||||||
|
assert_eq!(RegionType::Ram, regions[3].2);
|
||||||
|
assert_eq!(RegionType::Reserved, regions[4].2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_arch_memory_regions_dram_4gb() {
|
||||||
|
let regions = arch_memory_regions((1usize << 32) as u64); //4GB
|
||||||
|
let ram_32bit_space_size =
|
||||||
|
layout::MEM_32BIT_RESERVED_START.unchecked_offset_from(layout::RAM_START) as usize;
|
||||||
assert_eq!(6, regions.len());
|
assert_eq!(6, regions.len());
|
||||||
assert_eq!(layout::RAM_START, regions[3].0);
|
assert_eq!(layout::RAM_START, regions[3].0);
|
||||||
|
assert_eq!(ram_32bit_space_size, regions[3].1);
|
||||||
assert_eq!(RegionType::Ram, regions[3].2);
|
assert_eq!(RegionType::Ram, regions[3].2);
|
||||||
assert_eq!(RegionType::Reserved, regions[5].2);
|
assert_eq!(RegionType::Reserved, regions[5].2);
|
||||||
assert_eq!(RegionType::Ram, regions[4].2);
|
assert_eq!(RegionType::Ram, regions[4].2);
|
||||||
|
assert_eq!(((1usize << 32) - ram_32bit_space_size), regions[4].1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
44
arch/src/aarch64/regs.rs
Normal file
44
arch/src/aarch64/regs.rs
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
// Copyright 2022 Arm Limited (or its affiliates). All rights reserved.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
///
|
||||||
|
/// AArch64 system register encoding:
|
||||||
|
/// See https://developer.arm.com/documentation/ddi0487 (chapter D12)
|
||||||
|
///
|
||||||
|
/// 31 22 21 20 19 18 16 15 12 11 8 7 5 4 0
|
||||||
|
/// +----------+---+-----+-----+-----+-----+-----+----+
|
||||||
|
/// |1101010100| L | op0 | op1 | CRn | CRm | op2 | Rt |
|
||||||
|
/// +----------+---+-----+-----+-----+-----+-----+----+
|
||||||
|
///
|
||||||
|
/// Notes:
|
||||||
|
/// - L and Rt are reserved as implementation defined fields, ignored.
|
||||||
|
///
|
||||||
|
const SYSREG_HEAD: u32 = 0b1101010100u32 << 22;
|
||||||
|
const SYSREG_OP0_SHIFT: u32 = 19;
|
||||||
|
const SYSREG_OP0_MASK: u32 = 0b11u32 << 19;
|
||||||
|
const SYSREG_OP1_SHIFT: u32 = 16;
|
||||||
|
const SYSREG_OP1_MASK: u32 = 0b111u32 << 16;
|
||||||
|
const SYSREG_CRN_SHIFT: u32 = 12;
|
||||||
|
const SYSREG_CRN_MASK: u32 = 0b1111u32 << 12;
|
||||||
|
const SYSREG_CRM_SHIFT: u32 = 8;
|
||||||
|
const SYSREG_CRM_MASK: u32 = 0b1111u32 << 8;
|
||||||
|
const SYSREG_OP2_SHIFT: u32 = 5;
|
||||||
|
const SYSREG_OP2_MASK: u32 = 0b111u32 << 5;
|
||||||
|
|
||||||
|
/// Define the ID of system registers
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! arm64_sys_reg {
|
||||||
|
($name: tt, $op0: tt, $op1: tt, $crn: tt, $crm: tt, $op2: tt) => {
|
||||||
|
pub const $name: u32 = SYSREG_HEAD
|
||||||
|
| ((($op0 as u32) << SYSREG_OP0_SHIFT) & SYSREG_OP0_MASK as u32)
|
||||||
|
| ((($op1 as u32) << SYSREG_OP1_SHIFT) & SYSREG_OP1_MASK as u32)
|
||||||
|
| ((($crn as u32) << SYSREG_CRN_SHIFT) & SYSREG_CRN_MASK as u32)
|
||||||
|
| ((($crm as u32) << SYSREG_CRM_SHIFT) & SYSREG_CRM_MASK as u32)
|
||||||
|
| ((($op2 as u32) << SYSREG_OP2_SHIFT) & SYSREG_OP2_MASK as u32);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
arm64_sys_reg!(MPIDR_EL1, 3, 0, 0, 0, 5);
|
||||||
|
arm64_sys_reg!(ID_AA64MMFR0_EL1, 3, 0, 0, 7, 0);
|
||||||
|
arm64_sys_reg!(TTBR1_EL1, 3, 0, 2, 0, 1);
|
||||||
|
arm64_sys_reg!(TCR_EL1, 3, 0, 2, 0, 2);
|
||||||
@@ -1,28 +1,19 @@
|
|||||||
// Copyright 2020 Arm Limited (or its affiliates). All rights reserved.
|
// Copyright 2020 Arm Limited (or its affiliates). All rights reserved.
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
use std::io::{Read, Seek, SeekFrom};
|
use std::io::{Read, Seek, SeekFrom};
|
||||||
use std::os::fd::AsFd;
|
|
||||||
use std::result;
|
use std::result;
|
||||||
|
use vm_memory::{Bytes, GuestAddress, GuestMemory};
|
||||||
use thiserror::Error;
|
|
||||||
use vm_memory::{GuestAddress, GuestMemory};
|
|
||||||
|
|
||||||
/// Errors thrown while loading UEFI binary
|
/// Errors thrown while loading UEFI binary
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
/// Unable to seek to UEFI image start.
|
/// Unable to seek to UEFI image start.
|
||||||
#[error("Unable to seek to UEFI image start")]
|
|
||||||
SeekUefiStart,
|
SeekUefiStart,
|
||||||
/// Unable to seek to UEFI image end.
|
/// Unable to seek to UEFI image end.
|
||||||
#[error("Unable to seek to UEFI image end")]
|
|
||||||
SeekUefiEnd,
|
SeekUefiEnd,
|
||||||
/// UEFI image too big.
|
/// UEFI image too big.
|
||||||
#[error("UEFI image too big")]
|
|
||||||
UefiTooBig,
|
UefiTooBig,
|
||||||
/// Unable to read UEFI image
|
/// Unable to read UEFI image
|
||||||
#[error("Unable to read UEFI image")]
|
|
||||||
ReadUefiImage,
|
ReadUefiImage,
|
||||||
}
|
}
|
||||||
type Result<T> = result::Result<T, Error>;
|
type Result<T> = result::Result<T, Error>;
|
||||||
@@ -33,7 +24,7 @@ pub fn load_uefi<F, M: GuestMemory>(
|
|||||||
uefi_image: &mut F,
|
uefi_image: &mut F,
|
||||||
) -> Result<()>
|
) -> Result<()>
|
||||||
where
|
where
|
||||||
F: Read + Seek + AsFd,
|
F: Read + Seek,
|
||||||
{
|
{
|
||||||
let uefi_size = uefi_image
|
let uefi_size = uefi_image
|
||||||
.seek(SeekFrom::End(0))
|
.seek(SeekFrom::End(0))
|
||||||
@@ -45,6 +36,6 @@ where
|
|||||||
}
|
}
|
||||||
uefi_image.rewind().map_err(|_| Error::SeekUefiStart)?;
|
uefi_image.rewind().map_err(|_| Error::SeekUefiStart)?;
|
||||||
guest_mem
|
guest_mem
|
||||||
.read_exact_volatile_from(guest_addr, &mut uefi_image.as_fd(), uefi_size)
|
.read_exact_from(guest_addr, uefi_image, uefi_size)
|
||||||
.map_err(|_| Error::ReadUefiImage)
|
.map_err(|_| Error::ReadUefiImage)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// Copyright © 2024 Institute of Software, CAS. All rights reserved.
|
|
||||||
// Copyright 2020 Arm Limited (or its affiliates). All rights reserved.
|
// Copyright 2020 Arm Limited (or its affiliates). All rights reserved.
|
||||||
// Copyright © 2020, Oracle and/or its affiliates.
|
// Copyright © 2020, Oracle and/or its affiliates.
|
||||||
//
|
//
|
||||||
@@ -6,14 +5,22 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
//! Implements platform specific functionality.
|
//! Implements platform specific functionality.
|
||||||
//! Supported platforms: x86_64, aarch64, riscv64.
|
//! Supported platforms: x86_64, aarch64.
|
||||||
|
|
||||||
use std::collections::BTreeMap;
|
#[macro_use]
|
||||||
use std::sync::Arc;
|
extern crate log;
|
||||||
use std::{fmt, result};
|
|
||||||
|
|
||||||
|
#[cfg(target_arch = "x86_64")]
|
||||||
|
use crate::x86_64::SgxEpcSection;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
use std::fmt;
|
||||||
|
use std::result;
|
||||||
|
use std::sync::Arc;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
use versionize::{VersionMap, Versionize, VersionizeError, VersionizeResult};
|
||||||
|
use versionize_derive::Versionize;
|
||||||
|
use vm_migration::VersionMapped;
|
||||||
|
|
||||||
type GuestMemoryMmap = vm_memory::GuestMemoryMmap<vm_memory::bitmap::AtomicBitmap>;
|
type GuestMemoryMmap = vm_memory::GuestMemoryMmap<vm_memory::bitmap::AtomicBitmap>;
|
||||||
type GuestRegionMmap = vm_memory::GuestRegionMmap<vm_memory::bitmap::AtomicBitmap>;
|
type GuestRegionMmap = vm_memory::GuestRegionMmap<vm_memory::bitmap::AtomicBitmap>;
|
||||||
@@ -22,14 +29,11 @@ type GuestRegionMmap = vm_memory::GuestRegionMmap<vm_memory::bitmap::AtomicBitma
|
|||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[cfg(target_arch = "x86_64")]
|
#[cfg(target_arch = "x86_64")]
|
||||||
#[error("Platform specific error (x86_64)")]
|
#[error("Platform specific error (x86_64): {0:?}")]
|
||||||
PlatformSpecific(#[from] x86_64::Error),
|
PlatformSpecific(x86_64::Error),
|
||||||
#[cfg(target_arch = "aarch64")]
|
#[cfg(target_arch = "aarch64")]
|
||||||
#[error("Platform specific error (aarch64)")]
|
#[error("Platform specific error (aarch64): {0:?}")]
|
||||||
PlatformSpecific(#[from] aarch64::Error),
|
PlatformSpecific(aarch64::Error),
|
||||||
#[cfg(target_arch = "riscv64")]
|
|
||||||
#[error("Platform specific error (riscv64)")]
|
|
||||||
PlatformSpecific(#[from] riscv64::Error),
|
|
||||||
#[error("The memory map table extends past the end of guest memory")]
|
#[error("The memory map table extends past the end of guest memory")]
|
||||||
MemmapTablePastRamEnd,
|
MemmapTablePastRamEnd,
|
||||||
#[error("Error writing memory map table to guest memory")]
|
#[error("Error writing memory map table to guest memory")]
|
||||||
@@ -40,21 +44,17 @@ pub enum Error {
|
|||||||
StartInfoSetup,
|
StartInfoSetup,
|
||||||
#[error("Failed to compute initramfs address")]
|
#[error("Failed to compute initramfs address")]
|
||||||
InitramfsAddress,
|
InitramfsAddress,
|
||||||
#[error("Error writing module entry to guest memory")]
|
#[error("Error writing module entry to guest memory: {0}")]
|
||||||
ModlistSetup(#[source] vm_memory::GuestMemoryError),
|
ModlistSetup(#[source] vm_memory::GuestMemoryError),
|
||||||
#[error("RSDP extends past the end of guest memory")]
|
#[error("RSDP extends past the end of guest memory")]
|
||||||
RsdpPastRamEnd,
|
RsdpPastRamEnd,
|
||||||
#[error("Failed to setup Zero Page for bzImage")]
|
|
||||||
ZeroPageSetup(#[source] vm_memory::GuestMemoryError),
|
|
||||||
#[error("Zero Page for bzImage past RAM end")]
|
|
||||||
ZeroPagePastRamEnd,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Type for returning public functions outcome.
|
/// Type for returning public functions outcome.
|
||||||
pub type Result<T> = result::Result<T, Error>;
|
pub type Result<T> = result::Result<T, Error>;
|
||||||
|
|
||||||
/// Type for memory region types.
|
/// Type for memory region types.
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize, Versionize)]
|
||||||
pub enum RegionType {
|
pub enum RegionType {
|
||||||
/// RAM type
|
/// RAM type
|
||||||
Ram,
|
Ram,
|
||||||
@@ -72,26 +72,17 @@ 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;
|
||||||
|
|
||||||
#[cfg(target_arch = "aarch64")]
|
#[cfg(target_arch = "aarch64")]
|
||||||
pub use aarch64::{
|
pub use aarch64::{
|
||||||
_NSIG, EntryPoint, arch_memory_regions, configure_system, configure_vcpu,
|
arch_memory_regions, configure_system, configure_vcpu, fdt::DeviceInfoForFdt,
|
||||||
fdt::DeviceInfoForFdt, 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::IRQ_BASE, uefi,
|
layout::IRQ_BASE, uefi, EntryPoint,
|
||||||
};
|
|
||||||
|
|
||||||
/// Module for riscv64 related functionality.
|
|
||||||
#[cfg(target_arch = "riscv64")]
|
|
||||||
pub mod riscv64;
|
|
||||||
|
|
||||||
#[cfg(target_arch = "riscv64")]
|
|
||||||
pub use riscv64::{
|
|
||||||
_NSIG, EntryPoint, arch_memory_regions, configure_system, configure_vcpu,
|
|
||||||
fdt::DeviceInfoForFdt, get_host_cpu_phys_bits, initramfs_load_addr, layout,
|
|
||||||
layout::CMDLINE_MAX_SIZE, layout::IRQ_BASE, uefi,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(target_arch = "x86_64")]
|
#[cfg(target_arch = "x86_64")]
|
||||||
@@ -99,9 +90,9 @@ pub mod x86_64;
|
|||||||
|
|
||||||
#[cfg(target_arch = "x86_64")]
|
#[cfg(target_arch = "x86_64")]
|
||||||
pub use x86_64::{
|
pub use x86_64::{
|
||||||
_NSIG, CpuidConfig, CpuidFeatureEntry, EntryPoint, arch_memory_regions, configure_system,
|
arch_memory_regions, configure_system, configure_vcpu, generate_common_cpuid,
|
||||||
configure_vcpu, generate_common_cpuid, generate_ram_ranges, get_host_cpu_phys_bits,
|
get_host_cpu_phys_bits, initramfs_load_addr, layout, layout::CMDLINE_MAX_SIZE,
|
||||||
initramfs_load_addr, layout, layout::CMDLINE_MAX_SIZE, layout::CMDLINE_START, regs,
|
layout::CMDLINE_START, regs, CpuidFeatureEntry, EntryPoint,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Safe wrapper for `sysconf(_SC_PAGESIZE)`.
|
/// Safe wrapper for `sysconf(_SC_PAGESIZE)`.
|
||||||
@@ -116,10 +107,11 @@ fn pagesize() -> usize {
|
|||||||
pub struct NumaNode {
|
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<u32>,
|
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")]
|
||||||
|
pub sgx_epc_sections: Vec<SgxEpcSection>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type NumaNodes = BTreeMap<u32, NumaNode>;
|
pub type NumaNodes = BTreeMap<u32, NumaNode>;
|
||||||
@@ -138,7 +130,7 @@ pub enum DeviceType {
|
|||||||
/// Device Type: Virtio.
|
/// Device Type: Virtio.
|
||||||
Virtio(u32),
|
Virtio(u32),
|
||||||
/// Device Type: Serial.
|
/// Device Type: Serial.
|
||||||
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
|
#[cfg(target_arch = "aarch64")]
|
||||||
Serial,
|
Serial,
|
||||||
/// Device Type: RTC.
|
/// Device Type: RTC.
|
||||||
#[cfg(target_arch = "aarch64")]
|
#[cfg(target_arch = "aarch64")]
|
||||||
@@ -146,9 +138,6 @@ pub enum DeviceType {
|
|||||||
/// Device Type: GPIO.
|
/// Device Type: GPIO.
|
||||||
#[cfg(target_arch = "aarch64")]
|
#[cfg(target_arch = "aarch64")]
|
||||||
Gpio,
|
Gpio,
|
||||||
/// Device Type: fw_cfg.
|
|
||||||
#[cfg(feature = "fw_cfg")]
|
|
||||||
FwCfg,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Default (smallest) memory page size for the supported architectures.
|
/// Default (smallest) memory page size for the supported architectures.
|
||||||
@@ -162,7 +151,7 @@ impl fmt::Display for DeviceType {
|
|||||||
|
|
||||||
/// Structure to describe MMIO device information
|
/// Structure to describe MMIO device information
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
|
#[cfg(target_arch = "aarch64")]
|
||||||
pub struct MmioDeviceInfo {
|
pub struct MmioDeviceInfo {
|
||||||
pub addr: u64,
|
pub addr: u64,
|
||||||
pub len: u64,
|
pub len: u64,
|
||||||
@@ -171,7 +160,7 @@ pub struct MmioDeviceInfo {
|
|||||||
|
|
||||||
/// Structure to describe PCI space information
|
/// Structure to describe PCI space information
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
|
#[cfg(target_arch = "aarch64")]
|
||||||
pub struct PciSpaceInfo {
|
pub struct PciSpaceInfo {
|
||||||
pub pci_segment_id: u16,
|
pub pci_segment_id: u16,
|
||||||
pub mmio_config_address: u64,
|
pub mmio_config_address: u64,
|
||||||
@@ -179,7 +168,7 @@ pub struct PciSpaceInfo {
|
|||||||
pub pci_device_space_size: u64,
|
pub pci_device_space_size: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
|
#[cfg(target_arch = "aarch64")]
|
||||||
impl DeviceInfoForFdt for MmioDeviceInfo {
|
impl DeviceInfoForFdt for MmioDeviceInfo {
|
||||||
fn addr(&self) -> u64 {
|
fn addr(&self) -> u64 {
|
||||||
self.addr
|
self.addr
|
||||||
|
|||||||
@@ -1,483 +0,0 @@
|
|||||||
// Copyright © 2024 Institute of Software, CAS. All rights reserved.
|
|
||||||
// Copyright 2020 Arm Limited (or its affiliates). All rights reserved.
|
|
||||||
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
//
|
|
||||||
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
|
||||||
// found in the THIRD-PARTY file.
|
|
||||||
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::ffi::CStr;
|
|
||||||
use std::fmt::Debug;
|
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
use std::{cmp, result, str};
|
|
||||||
|
|
||||||
use byteorder::{BigEndian, ByteOrder};
|
|
||||||
use hypervisor::arch::riscv64::aia::Vaia;
|
|
||||||
use log::debug;
|
|
||||||
use thiserror::Error;
|
|
||||||
use vm_fdt::{FdtWriter, FdtWriterResult};
|
|
||||||
use vm_memory::{Address, Bytes, GuestMemory, GuestMemoryError, GuestMemoryRegion};
|
|
||||||
|
|
||||||
use super::super::{DeviceType, GuestMemoryMmap, InitramfsConfig};
|
|
||||||
use super::layout::{
|
|
||||||
IRQ_BASE, MEM_32BIT_DEVICES_SIZE, MEM_32BIT_DEVICES_START, MEM_PCI_IO_SIZE, MEM_PCI_IO_START,
|
|
||||||
PCI_HIGH_BASE, PCI_MMIO_CONFIG_SIZE_PER_SEGMENT,
|
|
||||||
};
|
|
||||||
use crate::PciSpaceInfo;
|
|
||||||
|
|
||||||
const AIA_APLIC_PHANDLE: u32 = 1;
|
|
||||||
const AIA_IMSIC_PHANDLE: u32 = 2;
|
|
||||||
const CPU_INTC_BASE_PHANDLE: u32 = 3;
|
|
||||||
const CPU_BASE_PHANDLE: u32 = 256 + CPU_INTC_BASE_PHANDLE;
|
|
||||||
// Read the documentation specified when appending the root node to the FDT.
|
|
||||||
const ADDRESS_CELLS: u32 = 0x2;
|
|
||||||
const SIZE_CELLS: u32 = 0x2;
|
|
||||||
|
|
||||||
// From https://elixir.bootlin.com/linux/v6.10/source/include/dt-bindings/interrupt-controller/irq.h#L14
|
|
||||||
const _IRQ_TYPE_EDGE_RISING: u32 = 1;
|
|
||||||
const IRQ_TYPE_LEVEL_HI: u32 = 4;
|
|
||||||
|
|
||||||
const S_MODE_EXT_IRQ: u32 = 9;
|
|
||||||
|
|
||||||
/// Trait for devices to be added to the Flattened Device Tree.
|
|
||||||
pub trait DeviceInfoForFdt {
|
|
||||||
/// Returns the address where this device will be loaded.
|
|
||||||
fn addr(&self) -> u64;
|
|
||||||
/// Returns the associated interrupt for this device.
|
|
||||||
fn irq(&self) -> u32;
|
|
||||||
/// Returns the amount of memory that needs to be reserved for this device.
|
|
||||||
fn length(&self) -> u64;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Errors thrown while configuring the Flattened Device Tree for riscv64.
|
|
||||||
#[derive(Debug, Error)]
|
|
||||||
pub enum Error {
|
|
||||||
/// Failure in writing FDT in memory.
|
|
||||||
#[error("Failure in writing FDT in memory")]
|
|
||||||
WriteFdtToMemory(#[source] GuestMemoryError),
|
|
||||||
}
|
|
||||||
type Result<T> = result::Result<T, Error>;
|
|
||||||
|
|
||||||
/// Creates the flattened device tree for this riscv64 VM.
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
|
||||||
pub fn create_fdt<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHasher>(
|
|
||||||
guest_mem: &GuestMemoryMmap,
|
|
||||||
cmdline: &str,
|
|
||||||
num_vcpu: u32,
|
|
||||||
isa_string: &str,
|
|
||||||
device_info: &HashMap<(DeviceType, String), T, S>,
|
|
||||||
aia_device: &Arc<Mutex<dyn Vaia>>,
|
|
||||||
initrd: &Option<InitramfsConfig>,
|
|
||||||
pci_space_info: &[PciSpaceInfo],
|
|
||||||
) -> FdtWriterResult<Vec<u8>> {
|
|
||||||
// Allocate stuff necessary for the holding the blob.
|
|
||||||
let mut fdt = FdtWriter::new()?;
|
|
||||||
|
|
||||||
// For an explanation why these nodes were introduced in the blob take a look at
|
|
||||||
// https://github.com/devicetree-org/devicetree-specification/releases/tag/v0.4
|
|
||||||
// In chapter 3.
|
|
||||||
|
|
||||||
// Header or the root node as per above mentioned documentation.
|
|
||||||
let root_node = fdt.begin_node("")?;
|
|
||||||
fdt.property_string("compatible", "linux,dummy-virt")?;
|
|
||||||
// For info on #address-cells and size-cells resort to Table 3.1 Root Node
|
|
||||||
// Properties
|
|
||||||
fdt.property_u32("#address-cells", ADDRESS_CELLS)?;
|
|
||||||
fdt.property_u32("#size-cells", SIZE_CELLS)?;
|
|
||||||
create_cpu_nodes(&mut fdt, num_vcpu, isa_string)?;
|
|
||||||
create_memory_node(&mut fdt, guest_mem)?;
|
|
||||||
create_chosen_node(&mut fdt, cmdline, initrd)?;
|
|
||||||
create_aia_node(&mut fdt, aia_device)?;
|
|
||||||
create_devices_node(&mut fdt, device_info)?;
|
|
||||||
create_pci_nodes(&mut fdt, pci_space_info)?;
|
|
||||||
|
|
||||||
// End Header node.
|
|
||||||
fdt.end_node(root_node)?;
|
|
||||||
|
|
||||||
let fdt_final = fdt.finish()?;
|
|
||||||
|
|
||||||
Ok(fdt_final)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn write_fdt_to_memory(fdt_final: &[u8], guest_mem: &GuestMemoryMmap) -> Result<()> {
|
|
||||||
// Write FDT to memory.
|
|
||||||
guest_mem
|
|
||||||
.write_slice(fdt_final, super::layout::FDT_START)
|
|
||||||
.map_err(Error::WriteFdtToMemory)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Following are the auxiliary function for creating the different nodes that we append to our FDT.
|
|
||||||
fn create_cpu_nodes(fdt: &mut FdtWriter, num_cpus: u32, isa_string: &str) -> FdtWriterResult<()> {
|
|
||||||
// See https://elixir.bootlin.com/linux/v6.10/source/Documentation/devicetree/bindings/riscv/cpus.yaml
|
|
||||||
let cpus = fdt.begin_node("cpus")?;
|
|
||||||
// As per documentation, on RISC-V 64-bit systems value should be set to 1.
|
|
||||||
fdt.property_u32("#address-cells", 0x01)?;
|
|
||||||
fdt.property_u32("#size-cells", 0x0)?;
|
|
||||||
// TODO: Retrieve CPU frequency from cpu timer regs
|
|
||||||
let timebase_frequency: u32 = 0x989680;
|
|
||||||
fdt.property_u32("timebase-frequency", timebase_frequency)?;
|
|
||||||
|
|
||||||
for cpu_index in 0..num_cpus {
|
|
||||||
let cpu = fdt.begin_node(&format!("cpu@{cpu_index:x}"))?;
|
|
||||||
fdt.property_string("device_type", "cpu")?;
|
|
||||||
fdt.property_string("compatible", "riscv")?;
|
|
||||||
fdt.property_string("mmu-type", "sv48")?;
|
|
||||||
fdt.property_string("riscv,isa", isa_string)?;
|
|
||||||
fdt.property_string("status", "okay")?;
|
|
||||||
fdt.property_u32("reg", cpu_index)?;
|
|
||||||
fdt.property_u32("phandle", CPU_BASE_PHANDLE + cpu_index)?;
|
|
||||||
|
|
||||||
// interrupt controller node
|
|
||||||
let intc_node = fdt.begin_node("interrupt-controller")?;
|
|
||||||
fdt.property_string("compatible", "riscv,cpu-intc")?;
|
|
||||||
fdt.property_u32("#interrupt-cells", 1u32)?;
|
|
||||||
fdt.property_null("interrupt-controller")?;
|
|
||||||
fdt.property_u32("phandle", CPU_INTC_BASE_PHANDLE + cpu_index)?;
|
|
||||||
fdt.end_node(intc_node)?;
|
|
||||||
|
|
||||||
fdt.end_node(cpu)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
fdt.end_node(cpus)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_memory_node(fdt: &mut FdtWriter, guest_mem: &GuestMemoryMmap) -> FdtWriterResult<()> {
|
|
||||||
// Note: memory regions from "GuestMemory" are sorted and non-zero sized.
|
|
||||||
let ram_regions = {
|
|
||||||
let mut ram_regions = Vec::new();
|
|
||||||
let mut current_start = guest_mem
|
|
||||||
.iter()
|
|
||||||
.next()
|
|
||||||
.map(GuestMemoryRegion::start_addr)
|
|
||||||
.expect("GuestMemory must have one memory region at least")
|
|
||||||
.raw_value();
|
|
||||||
let mut current_end = current_start;
|
|
||||||
|
|
||||||
for (start, size) in guest_mem
|
|
||||||
.iter()
|
|
||||||
.map(|m| (m.start_addr().raw_value(), m.len()))
|
|
||||||
{
|
|
||||||
if current_end == start {
|
|
||||||
// This zone is continuous with the previous one.
|
|
||||||
current_end += size;
|
|
||||||
} else {
|
|
||||||
ram_regions.push((current_start, current_end));
|
|
||||||
|
|
||||||
current_start = start;
|
|
||||||
current_end = start + size;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ram_regions.push((current_start, current_end));
|
|
||||||
|
|
||||||
ram_regions
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut mem_reg_property = Vec::new();
|
|
||||||
for region in ram_regions {
|
|
||||||
let mem_size = region.1 - region.0;
|
|
||||||
mem_reg_property.push(region.0);
|
|
||||||
mem_reg_property.push(mem_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
let ram_start = super::layout::RAM_START.raw_value();
|
|
||||||
let memory_node_name = format!("memory@{ram_start:x}");
|
|
||||||
let memory_node = fdt.begin_node(&memory_node_name)?;
|
|
||||||
fdt.property_string("device_type", "memory")?;
|
|
||||||
fdt.property_array_u64("reg", &mem_reg_property)?;
|
|
||||||
fdt.end_node(memory_node)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_chosen_node(
|
|
||||||
fdt: &mut FdtWriter,
|
|
||||||
cmdline: &str,
|
|
||||||
initrd: &Option<InitramfsConfig>,
|
|
||||||
) -> FdtWriterResult<()> {
|
|
||||||
let chosen_node = fdt.begin_node("chosen")?;
|
|
||||||
fdt.property_string("bootargs", cmdline)?;
|
|
||||||
|
|
||||||
if let Some(initrd_config) = initrd {
|
|
||||||
let initrd_start = initrd_config.address.raw_value();
|
|
||||||
let initrd_end = initrd_config.address.raw_value() + initrd_config.size as u64;
|
|
||||||
fdt.property_u64("linux,initrd-start", initrd_start)?;
|
|
||||||
fdt.property_u64("linux,initrd-end", initrd_end)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
fdt.end_node(chosen_node)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_aia_node(fdt: &mut FdtWriter, aia_device: &Arc<Mutex<dyn Vaia>>) -> FdtWriterResult<()> {
|
|
||||||
// IMSIC
|
|
||||||
if aia_device.lock().unwrap().msi_compatible() {
|
|
||||||
use super::layout::IMSIC_START;
|
|
||||||
let imsic_name = format!("imsics@{:x}", IMSIC_START.0);
|
|
||||||
let imsic_node = fdt.begin_node(&imsic_name)?;
|
|
||||||
|
|
||||||
fdt.property_string(
|
|
||||||
"compatible",
|
|
||||||
aia_device.lock().unwrap().imsic_compatibility(),
|
|
||||||
)?;
|
|
||||||
let imsic_reg_prop = aia_device.lock().unwrap().imsic_properties();
|
|
||||||
fdt.property_array_u32("reg", &imsic_reg_prop)?;
|
|
||||||
fdt.property_u32("#interrupt-cells", 0u32)?;
|
|
||||||
fdt.property_null("interrupt-controller")?;
|
|
||||||
fdt.property_null("msi-controller")?;
|
|
||||||
// TODO complete num-ids
|
|
||||||
fdt.property_u32("riscv,num-ids", 2047u32)?;
|
|
||||||
fdt.property_u32("phandle", AIA_IMSIC_PHANDLE)?;
|
|
||||||
|
|
||||||
let mut irq_cells = Vec::new();
|
|
||||||
let num_cpus = aia_device.lock().unwrap().vcpu_count();
|
|
||||||
for i in 0..num_cpus {
|
|
||||||
irq_cells.push(CPU_INTC_BASE_PHANDLE + i);
|
|
||||||
irq_cells.push(S_MODE_EXT_IRQ);
|
|
||||||
}
|
|
||||||
fdt.property_array_u32("interrupts-extended", &irq_cells)?;
|
|
||||||
|
|
||||||
fdt.end_node(imsic_node)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// APLIC
|
|
||||||
use super::layout::APLIC_START;
|
|
||||||
let aplic_name = format!("aplic@{:x}", APLIC_START.0);
|
|
||||||
let aplic_node = fdt.begin_node(&aplic_name)?;
|
|
||||||
|
|
||||||
fdt.property_string(
|
|
||||||
"compatible",
|
|
||||||
aia_device.lock().unwrap().aplic_compatibility(),
|
|
||||||
)?;
|
|
||||||
let reg_cells = aia_device.lock().unwrap().aplic_properties();
|
|
||||||
fdt.property_array_u32("reg", ®_cells)?;
|
|
||||||
fdt.property_u32("#interrupt-cells", 2u32)?;
|
|
||||||
fdt.property_null("interrupt-controller")?;
|
|
||||||
// TODO complete num-srcs
|
|
||||||
fdt.property_u32("riscv,num-sources", 96u32)?;
|
|
||||||
fdt.property_u32("phandle", AIA_APLIC_PHANDLE)?;
|
|
||||||
fdt.property_u32("msi-parent", AIA_IMSIC_PHANDLE)?;
|
|
||||||
|
|
||||||
fdt.end_node(aplic_node)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_serial_node<T: DeviceInfoForFdt + Clone + Debug>(
|
|
||||||
fdt: &mut FdtWriter,
|
|
||||||
dev_info: &T,
|
|
||||||
) -> FdtWriterResult<()> {
|
|
||||||
let serial_reg_prop = [dev_info.addr(), dev_info.length()];
|
|
||||||
let irq = [dev_info.irq() - IRQ_BASE, IRQ_TYPE_LEVEL_HI];
|
|
||||||
|
|
||||||
let serial_node = fdt.begin_node(&format!("serial@{:x}", dev_info.addr()))?;
|
|
||||||
fdt.property_string("compatible", "ns16550a")?;
|
|
||||||
fdt.property_array_u64("reg", &serial_reg_prop)?;
|
|
||||||
fdt.property_u32("clock-frequency", 3686400)?;
|
|
||||||
fdt.property_u32("interrupt-parent", AIA_APLIC_PHANDLE)?;
|
|
||||||
fdt.property_array_u32("interrupts", &irq)?;
|
|
||||||
fdt.end_node(serial_node)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_devices_node<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHasher>(
|
|
||||||
fdt: &mut FdtWriter,
|
|
||||||
dev_info: &HashMap<(DeviceType, String), T, S>,
|
|
||||||
) -> FdtWriterResult<()> {
|
|
||||||
for ((device_type, _device_id), info) in dev_info {
|
|
||||||
match device_type {
|
|
||||||
DeviceType::Serial => create_serial_node(fdt, info)?,
|
|
||||||
DeviceType::Virtio(_) => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_pci_nodes(fdt: &mut FdtWriter, pci_device_info: &[PciSpaceInfo]) -> FdtWriterResult<()> {
|
|
||||||
// Add node for PCIe controller.
|
|
||||||
// See Documentation/devicetree/bindings/pci/host-generic-pci.txt in the kernel
|
|
||||||
// and https://elinux.org/Device_Tree_Usage.
|
|
||||||
// In multiple PCI segments setup, each PCI segment needs a PCI node.
|
|
||||||
for pci_device_info_elem in pci_device_info.iter() {
|
|
||||||
// EDK2 requires the PCIe high space above 4G address.
|
|
||||||
// The actual space in CLH follows the RAM. If the RAM space is small, the PCIe high space
|
|
||||||
// could fall below 4G.
|
|
||||||
// Here we cut off PCI device space below 8G in FDT to workaround the EDK2 check.
|
|
||||||
// But the address written in ACPI is not impacted.
|
|
||||||
let (pci_device_base_64bit, pci_device_size_64bit) =
|
|
||||||
if pci_device_info_elem.pci_device_space_start < PCI_HIGH_BASE.raw_value() {
|
|
||||||
(
|
|
||||||
PCI_HIGH_BASE.raw_value(),
|
|
||||||
pci_device_info_elem.pci_device_space_size
|
|
||||||
- (PCI_HIGH_BASE.raw_value() - pci_device_info_elem.pci_device_space_start),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
(
|
|
||||||
pci_device_info_elem.pci_device_space_start,
|
|
||||||
pci_device_info_elem.pci_device_space_size,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
// There is no specific requirement of the 32bit MMIO range, and
|
|
||||||
// therefore at least we can make these ranges 4K aligned.
|
|
||||||
let pci_device_size_32bit: u64 =
|
|
||||||
MEM_32BIT_DEVICES_SIZE / ((1 << 12) * pci_device_info.len() as u64) * (1 << 12);
|
|
||||||
let pci_device_base_32bit: u64 = MEM_32BIT_DEVICES_START.0
|
|
||||||
+ pci_device_size_32bit * pci_device_info_elem.pci_segment_id as u64;
|
|
||||||
|
|
||||||
let ranges = [
|
|
||||||
// io addresses. Since AArch64 will not use IO address,
|
|
||||||
// we can set the same IO address range for every segment.
|
|
||||||
0x1000000,
|
|
||||||
0_u32,
|
|
||||||
0_u32,
|
|
||||||
(MEM_PCI_IO_START.0 >> 32) as u32,
|
|
||||||
MEM_PCI_IO_START.0 as u32,
|
|
||||||
(MEM_PCI_IO_SIZE >> 32) as u32,
|
|
||||||
MEM_PCI_IO_SIZE as u32,
|
|
||||||
// mmio addresses
|
|
||||||
0x2000000, // (ss = 10: 32-bit memory space)
|
|
||||||
(pci_device_base_32bit >> 32) as u32, // PCI address
|
|
||||||
pci_device_base_32bit as u32,
|
|
||||||
(pci_device_base_32bit >> 32) as u32, // CPU address
|
|
||||||
pci_device_base_32bit as u32,
|
|
||||||
(pci_device_size_32bit >> 32) as u32, // size
|
|
||||||
pci_device_size_32bit as u32,
|
|
||||||
// device addresses
|
|
||||||
0x3000000, // (ss = 11: 64-bit memory space)
|
|
||||||
(pci_device_base_64bit >> 32) as u32, // PCI address
|
|
||||||
pci_device_base_64bit as u32,
|
|
||||||
(pci_device_base_64bit >> 32) as u32, // CPU address
|
|
||||||
pci_device_base_64bit as u32,
|
|
||||||
(pci_device_size_64bit >> 32) as u32, // size
|
|
||||||
pci_device_size_64bit as u32,
|
|
||||||
];
|
|
||||||
let bus_range = [0, 0]; // Only bus 0
|
|
||||||
let reg = [
|
|
||||||
pci_device_info_elem.mmio_config_address,
|
|
||||||
PCI_MMIO_CONFIG_SIZE_PER_SEGMENT,
|
|
||||||
];
|
|
||||||
// See kernel document Documentation/devicetree/bindings/pci/pci-msi.txt
|
|
||||||
let msi_map = [
|
|
||||||
// rid-base: A single cell describing the first RID matched by the entry.
|
|
||||||
0x0,
|
|
||||||
// msi-controller: A single phandle to an MSI controller.
|
|
||||||
AIA_IMSIC_PHANDLE,
|
|
||||||
// msi-base: An msi-specifier describing the msi-specifier produced for the
|
|
||||||
// first RID matched by the entry.
|
|
||||||
(pci_device_info_elem.pci_segment_id as u32) << 8,
|
|
||||||
// length: A single cell describing how many consecutive RIDs are matched
|
|
||||||
// following the rid-base.
|
|
||||||
0x100,
|
|
||||||
];
|
|
||||||
|
|
||||||
let pci_node_name = format!("pci@{:x}", pci_device_info_elem.mmio_config_address);
|
|
||||||
let pci_node = fdt.begin_node(&pci_node_name)?;
|
|
||||||
|
|
||||||
fdt.property_string("compatible", "pci-host-ecam-generic")?;
|
|
||||||
fdt.property_string("device_type", "pci")?;
|
|
||||||
fdt.property_array_u32("ranges", &ranges)?;
|
|
||||||
fdt.property_array_u32("bus-range", &bus_range)?;
|
|
||||||
fdt.property_u32(
|
|
||||||
"linux,pci-domain",
|
|
||||||
pci_device_info_elem.pci_segment_id as u32,
|
|
||||||
)?;
|
|
||||||
fdt.property_u32("#address-cells", 3)?;
|
|
||||||
fdt.property_u32("#size-cells", 2)?;
|
|
||||||
fdt.property_array_u64("reg", ®)?;
|
|
||||||
fdt.property_u32("#interrupt-cells", 1)?;
|
|
||||||
fdt.property_null("interrupt-map")?;
|
|
||||||
fdt.property_null("interrupt-map-mask")?;
|
|
||||||
fdt.property_null("dma-coherent")?;
|
|
||||||
fdt.property_array_u32("msi-map", &msi_map)?;
|
|
||||||
fdt.property_u32("msi-parent", AIA_IMSIC_PHANDLE)?;
|
|
||||||
|
|
||||||
fdt.end_node(pci_node)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse the DTB binary and print for debugging
|
|
||||||
pub fn print_fdt(dtb: &[u8]) {
|
|
||||||
match fdt_parser::Fdt::new(dtb) {
|
|
||||||
Ok(fdt) => {
|
|
||||||
if let Some(root) = fdt.find_node("/") {
|
|
||||||
debug!("Printing the FDT:");
|
|
||||||
print_node(root, 0);
|
|
||||||
} else {
|
|
||||||
debug!("Failed to find root node in FDT for debugging.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(_) => debug!("Failed to parse FDT for debugging."),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn print_node(node: fdt_parser::node::FdtNode<'_, '_>, n_spaces: usize) {
|
|
||||||
debug!("{:indent$}{}/", "", node.name, indent = n_spaces);
|
|
||||||
for property in node.properties() {
|
|
||||||
let name = property.name;
|
|
||||||
|
|
||||||
// If the property is 'compatible', its value requires special handling.
|
|
||||||
// The u8 array could contain multiple null-terminated strings.
|
|
||||||
// We copy the original array and simply replace all 'null' characters with spaces.
|
|
||||||
let value = if name == "compatible" {
|
|
||||||
let mut compatible = vec![0u8; 256];
|
|
||||||
let handled_value = property
|
|
||||||
.value
|
|
||||||
.iter()
|
|
||||||
.map(|&c| if c == 0 { b' ' } else { c })
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
let len = cmp::min(255, handled_value.len());
|
|
||||||
compatible[..len].copy_from_slice(&handled_value[..len]);
|
|
||||||
compatible[..(len + 1)].to_vec()
|
|
||||||
} else {
|
|
||||||
property.value.to_vec()
|
|
||||||
};
|
|
||||||
let value = &value;
|
|
||||||
|
|
||||||
// Now the value can be either:
|
|
||||||
// - A null-terminated C string, or
|
|
||||||
// - Binary data
|
|
||||||
// We follow a very simple logic to present the value:
|
|
||||||
// - At first, try to convert it to CStr and print,
|
|
||||||
// - If failed, print it as u32 array.
|
|
||||||
let value_result = match CStr::from_bytes_with_nul(value) {
|
|
||||||
Ok(value_cstr) => value_cstr.to_str().ok(),
|
|
||||||
Err(_e) => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(value_str) = value_result {
|
|
||||||
debug!(
|
|
||||||
"{:indent$}{} : {:#?}",
|
|
||||||
"",
|
|
||||||
name,
|
|
||||||
value_str,
|
|
||||||
indent = (n_spaces + 2)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
let mut array = Vec::with_capacity(256);
|
|
||||||
array.resize(value.len() / 4, 0u32);
|
|
||||||
BigEndian::read_u32_into(value, &mut array);
|
|
||||||
debug!(
|
|
||||||
"{:indent$}{} : {:X?}",
|
|
||||||
"",
|
|
||||||
name,
|
|
||||||
array,
|
|
||||||
indent = (n_spaces + 2)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print children nodes if there is any
|
|
||||||
for child in node.children() {
|
|
||||||
print_node(child, n_spaces + 2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,117 +0,0 @@
|
|||||||
// Copyright © 2024 Institute of Software, CAS. All rights reserved.
|
|
||||||
// Copyright 2020 Arm Limited (or its affiliates). All rights reserved.
|
|
||||||
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
//
|
|
||||||
// Memory layout of RISC-V 64-bit guest:
|
|
||||||
//
|
|
||||||
// Physical +---------------------------------------------------------------+
|
|
||||||
// address | |
|
|
||||||
// end | |
|
|
||||||
// ~ ~ ~ ~
|
|
||||||
// | |
|
|
||||||
// | Highmem PCI MMIO space |
|
|
||||||
// | |
|
|
||||||
// RAM end +---------------------------------------------------------------+
|
|
||||||
// (dynamic, | |
|
|
||||||
// including | |
|
|
||||||
// hotplug ~ ~ ~ ~
|
|
||||||
// memory) | |
|
|
||||||
// | DRAM |
|
|
||||||
// | |
|
|
||||||
// | |
|
|
||||||
// | |
|
|
||||||
// | |
|
|
||||||
// 1 GB +---------------------------------------------------------------+
|
|
||||||
// | |
|
|
||||||
// | PCI MMCONFIG space |
|
|
||||||
// | |
|
|
||||||
// 768 MB +---------------------------------------------------------------+
|
|
||||||
// | |
|
|
||||||
// | |
|
|
||||||
// | PCI MMIO space |
|
|
||||||
// | |
|
|
||||||
// 256 MB +---------------------------------------------------------------|
|
|
||||||
// | |
|
|
||||||
// | Legacy devices space |
|
|
||||||
// | |
|
|
||||||
// 128 MB +---------------------------------------------------------------|
|
|
||||||
// | |
|
|
||||||
// | IMSICs |
|
|
||||||
// | |
|
|
||||||
// 64 MB +---------------------------------------------------------------+
|
|
||||||
// | |
|
|
||||||
// | APLICs |
|
|
||||||
// | |
|
|
||||||
// 4 MB +---------------------------------------------------------------+
|
|
||||||
// | UEFI flash |
|
|
||||||
// 0 GB +---------------------------------------------------------------+
|
|
||||||
//
|
|
||||||
//
|
|
||||||
|
|
||||||
use vm_memory::GuestAddress;
|
|
||||||
|
|
||||||
/// 0x0 ~ 0x40_0000 (4 MiB) is reserved to UEFI
|
|
||||||
/// UEFI binary size is required less than 3 MiB, reserving 4 MiB is enough.
|
|
||||||
pub const UEFI_START: GuestAddress = GuestAddress(0);
|
|
||||||
pub const UEFI_SIZE: u64 = 0x040_0000;
|
|
||||||
|
|
||||||
/// AIA related devices
|
|
||||||
/// See https://elixir.bootlin.com/linux/v6.10/source/arch/riscv/include/uapi/asm/kvm.h
|
|
||||||
/// 0x40_0000 ~ 0x0400_0000 (64 MiB) resides APLICs
|
|
||||||
pub const APLIC_START: GuestAddress = GuestAddress(0x40_0000);
|
|
||||||
pub const APLIC_SIZE: u64 = 0x4000;
|
|
||||||
|
|
||||||
/// 0x0400_0000 ~ 0x0800_0000 (64 MiB) resides IMSICs
|
|
||||||
pub const IMSIC_START: GuestAddress = GuestAddress(0x0400_0000);
|
|
||||||
pub const IMSIC_SIZE: u64 = 0x1000;
|
|
||||||
|
|
||||||
/// Below this address will reside the AIA, above this address will reside the MMIO devices.
|
|
||||||
const MAPPED_IO_START: GuestAddress = GuestAddress(0x0800_0000);
|
|
||||||
|
|
||||||
/// Space 0x0800_0000 ~ 0x1000_0000 is reserved for legacy devices.
|
|
||||||
pub const LEGACY_SERIAL_MAPPED_IO_START: GuestAddress = MAPPED_IO_START;
|
|
||||||
|
|
||||||
/// Space 0x0905_0000 ~ 0x0906_0000 is reserved for pcie io address
|
|
||||||
pub const MEM_PCI_IO_START: GuestAddress = GuestAddress(0x0905_0000);
|
|
||||||
pub const MEM_PCI_IO_SIZE: u64 = 0x1_0000;
|
|
||||||
|
|
||||||
/// Starting from 0x1000_0000 (256MiB) to 0x3000_0000 (768MiB) is used for PCIE MMIO
|
|
||||||
pub const MEM_32BIT_DEVICES_START: GuestAddress = GuestAddress(0x1000_0000);
|
|
||||||
pub const MEM_32BIT_DEVICES_SIZE: u64 = 0x2000_0000;
|
|
||||||
|
|
||||||
/// PCI MMCONFIG space (start: after the device space at 768MiB, length: 256MiB)
|
|
||||||
pub const PCI_MMCONFIG_START: GuestAddress = GuestAddress(0x3000_0000);
|
|
||||||
pub const PCI_MMCONFIG_SIZE: u64 = 256 << 20;
|
|
||||||
// One bus with potentially 256 devices (32 slots x 8 functions).
|
|
||||||
pub const PCI_MMIO_CONFIG_SIZE_PER_SEGMENT: u64 = 4096 * 256;
|
|
||||||
|
|
||||||
/// Start of RAM.
|
|
||||||
pub const RAM_START: GuestAddress = GuestAddress(0x4000_0000);
|
|
||||||
|
|
||||||
/// Kernel command line maximum size on RISC-V.
|
|
||||||
/// See https://elixir.bootlin.com/linux/v6.10/source/arch/riscv/include/uapi/asm/setup.h
|
|
||||||
pub const CMDLINE_MAX_SIZE: usize = 1024;
|
|
||||||
|
|
||||||
/// FDT is at the beginning of RAM.
|
|
||||||
pub const FDT_START: GuestAddress = RAM_START;
|
|
||||||
pub const FDT_MAX_SIZE: u64 = 0x1_0000;
|
|
||||||
|
|
||||||
/// Put ACPI table above dtb
|
|
||||||
pub const ACPI_START: GuestAddress = GuestAddress(RAM_START.0 + FDT_MAX_SIZE);
|
|
||||||
pub const ACPI_MAX_SIZE: u64 = 0x20_0000;
|
|
||||||
pub const RSDP_POINTER: GuestAddress = ACPI_START;
|
|
||||||
|
|
||||||
/// Kernel start after FDT and ACPI
|
|
||||||
pub const KERNEL_START: GuestAddress = GuestAddress(RAM_START.0 + FDT_MAX_SIZE);
|
|
||||||
|
|
||||||
/// Pci high memory base
|
|
||||||
pub const PCI_HIGH_BASE: GuestAddress = GuestAddress(0x2_0000_0000);
|
|
||||||
|
|
||||||
/// First usable interrupt on riscv64
|
|
||||||
pub const IRQ_BASE: u32 = 0;
|
|
||||||
|
|
||||||
// As per https://elixir.bootlin.com/linux/v6.10/source/arch/riscv/include/asm/kvm_host.h#L31
|
|
||||||
/// Number of supported interrupts
|
|
||||||
pub const IRQ_NUM: u32 = 1023;
|
|
||||||
@@ -1,231 +0,0 @@
|
|||||||
// Copyright © 2024 Institute of Software, CAS. All rights reserved.
|
|
||||||
// Copyright 2020 Arm Limited (or its affiliates). All rights reserved.
|
|
||||||
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
/// Module for the flattened device tree.
|
|
||||||
pub mod fdt;
|
|
||||||
/// Layout for this riscv64 system.
|
|
||||||
pub mod layout;
|
|
||||||
/// Module for loading UEFI binary.
|
|
||||||
pub mod uefi;
|
|
||||||
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::fmt::Debug;
|
|
||||||
use std::fs::File;
|
|
||||||
use std::io::{BufRead, BufReader};
|
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
|
|
||||||
use hypervisor::arch::riscv64::aia::Vaia;
|
|
||||||
use log::{Level, log_enabled};
|
|
||||||
use thiserror::Error;
|
|
||||||
use vm_memory::{Address, GuestAddress, GuestMemory, GuestMemoryAtomic};
|
|
||||||
|
|
||||||
pub use self::fdt::DeviceInfoForFdt;
|
|
||||||
use crate::{DeviceType, GuestMemoryMmap, PciSpaceInfo, RegionType};
|
|
||||||
|
|
||||||
pub const CLOUDHV_IRQCHIP_NUM_MSIS: u16 = 255;
|
|
||||||
pub const CLOUDHV_IRQCHIP_NUM_SOURCES: u8 = 96;
|
|
||||||
pub const CLOUDHV_IRQCHIP_NUM_PRIO_BITS: u8 = 3;
|
|
||||||
pub const CLOUDHV_IRQCHIP_MAX_GUESTS_BITS: u8 = 3;
|
|
||||||
pub const CLOUDHV_IRQCHIP_MAX_GUESTS: u8 = (1 << CLOUDHV_IRQCHIP_MAX_GUESTS_BITS) - 1;
|
|
||||||
pub const _NSIG: i32 = 65;
|
|
||||||
|
|
||||||
/// Errors thrown while configuring riscv64 system.
|
|
||||||
#[derive(Debug, Error)]
|
|
||||||
pub enum Error {
|
|
||||||
/// Failed to create a FDT.
|
|
||||||
#[error("Failed to create a FDT")]
|
|
||||||
SetupFdt,
|
|
||||||
|
|
||||||
/// Failed to write FDT to memory.
|
|
||||||
#[error("Failed to write FDT to memory")]
|
|
||||||
WriteFdtToMemory(#[source] fdt::Error),
|
|
||||||
|
|
||||||
/// Failed to create a AIA.
|
|
||||||
#[error("Failed to create a AIA")]
|
|
||||||
SetupAia,
|
|
||||||
|
|
||||||
/// Failed to compute the initramfs address.
|
|
||||||
#[error("Failed to compute the initramfs address")]
|
|
||||||
InitramfsAddress,
|
|
||||||
|
|
||||||
/// Error configuring the general purpose registers
|
|
||||||
#[error("Error configuring the general purpose registers")]
|
|
||||||
RegsConfiguration(#[source] hypervisor::HypervisorCpuError),
|
|
||||||
|
|
||||||
/// Error opening /proc/cpuinfo
|
|
||||||
#[error("Error opening /proc/cpuinfo")]
|
|
||||||
OpenCpuInfo(#[source] std::io::Error),
|
|
||||||
|
|
||||||
/// Error reading /proc/cpuinfo
|
|
||||||
#[error("Error reading /proc/cpuinfo")]
|
|
||||||
ReadCpuInfo(#[source] std::io::Error),
|
|
||||||
|
|
||||||
/// Invalid ISA string
|
|
||||||
#[error("Invalid ISA string: {0}")]
|
|
||||||
InvalidIsaString(String),
|
|
||||||
|
|
||||||
/// Error parsing /proc/cpuinfo
|
|
||||||
#[error("Error parsing /proc/cpuinfo")]
|
|
||||||
CpuInfoParsing,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
|
||||||
/// Specifies the entry point address where the guest must start
|
|
||||||
/// executing code.
|
|
||||||
pub struct EntryPoint {
|
|
||||||
/// Address in guest memory where the guest must start execution
|
|
||||||
pub entry_addr: GuestAddress,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Configure the specified VCPU, and return its MPIDR.
|
|
||||||
pub fn configure_vcpu(
|
|
||||||
vcpu: &dyn hypervisor::Vcpu,
|
|
||||||
id: u32,
|
|
||||||
boot_setup: Option<(EntryPoint, &GuestMemoryAtomic<GuestMemoryMmap>)>,
|
|
||||||
) -> super::Result<()> {
|
|
||||||
if let Some((kernel_entry_point, _guest_memory)) = boot_setup {
|
|
||||||
vcpu.setup_regs(
|
|
||||||
id,
|
|
||||||
kernel_entry_point.entry_addr.raw_value(),
|
|
||||||
layout::FDT_START.raw_value(),
|
|
||||||
)
|
|
||||||
.map_err(Error::RegsConfiguration)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn arch_memory_regions() -> Vec<(GuestAddress, usize, RegionType)> {
|
|
||||||
vec![
|
|
||||||
// 0 MiB ~ 256 MiB: AIA and legacy devices
|
|
||||||
(
|
|
||||||
GuestAddress(0),
|
|
||||||
layout::MEM_32BIT_DEVICES_START.0 as usize,
|
|
||||||
RegionType::Reserved,
|
|
||||||
),
|
|
||||||
// 256 MiB ~ 768 MiB: MMIO space
|
|
||||||
(
|
|
||||||
layout::MEM_32BIT_DEVICES_START,
|
|
||||||
layout::MEM_32BIT_DEVICES_SIZE as usize,
|
|
||||||
RegionType::SubRegion,
|
|
||||||
),
|
|
||||||
// 768 MiB ~ 1 GiB: reserved. The leading 256M for PCIe MMCONFIG space
|
|
||||||
(
|
|
||||||
layout::PCI_MMCONFIG_START,
|
|
||||||
layout::PCI_MMCONFIG_SIZE as usize,
|
|
||||||
RegionType::Reserved,
|
|
||||||
),
|
|
||||||
// 1GiB ~ inf: RAM
|
|
||||||
(layout::RAM_START, usize::MAX, RegionType::Ram),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read the first "isa" string from /proc/cpuinfo and filter out the H extension,
|
|
||||||
// while correctly preserving multi-letter extensions.
|
|
||||||
fn isa_string_from_host() -> Result<String, Error> {
|
|
||||||
let file = File::open("/proc/cpuinfo").map_err(Error::OpenCpuInfo)?;
|
|
||||||
let reader = BufReader::new(file);
|
|
||||||
|
|
||||||
for line in reader.lines() {
|
|
||||||
let line = line.map_err(Error::ReadCpuInfo)?;
|
|
||||||
let trimmed_line = line.trim();
|
|
||||||
|
|
||||||
if trimmed_line.starts_with("isa") {
|
|
||||||
let parts: Vec<&str> = trimmed_line.split(':').collect();
|
|
||||||
if parts.len() == 2 {
|
|
||||||
let isa_string = parts[1].trim();
|
|
||||||
|
|
||||||
// Split the string by underscores to separate single letter vs long-form
|
|
||||||
// extensions
|
|
||||||
let mut components: Vec<String> =
|
|
||||||
isa_string.split('_').map(|s| s.to_string()).collect();
|
|
||||||
|
|
||||||
if components.is_empty() {
|
|
||||||
return Err(Error::InvalidIsaString(isa_string.to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove H extension if present in single letter extensions
|
|
||||||
let first_component = components[0].chars().filter(|&c| c != 'h').collect();
|
|
||||||
|
|
||||||
components[0] = first_component;
|
|
||||||
|
|
||||||
return Ok(components.join("_"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(Error::CpuInfoParsing)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Configures the system and should be called once per vm before starting vcpu threads.
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
|
||||||
pub fn configure_system<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHasher>(
|
|
||||||
guest_mem: &GuestMemoryMmap,
|
|
||||||
cmdline: &str,
|
|
||||||
num_vcpu: u32,
|
|
||||||
device_info: &HashMap<(DeviceType, String), T, S>,
|
|
||||||
initrd: &Option<super::InitramfsConfig>,
|
|
||||||
pci_space_info: &[PciSpaceInfo],
|
|
||||||
aia_device: &Arc<Mutex<dyn Vaia>>,
|
|
||||||
) -> super::Result<()> {
|
|
||||||
let isa_string = isa_string_from_host()?;
|
|
||||||
let fdt_final = fdt::create_fdt(
|
|
||||||
guest_mem,
|
|
||||||
cmdline,
|
|
||||||
num_vcpu,
|
|
||||||
&isa_string,
|
|
||||||
device_info,
|
|
||||||
aia_device,
|
|
||||||
initrd,
|
|
||||||
pci_space_info,
|
|
||||||
)
|
|
||||||
.map_err(|_| Error::SetupFdt)?;
|
|
||||||
|
|
||||||
if log_enabled!(Level::Debug) {
|
|
||||||
fdt::print_fdt(&fdt_final);
|
|
||||||
}
|
|
||||||
|
|
||||||
fdt::write_fdt_to_memory(&fdt_final, guest_mem).map_err(Error::WriteFdtToMemory)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the memory address where the initramfs could be loaded.
|
|
||||||
pub fn initramfs_load_addr(
|
|
||||||
guest_mem: &GuestMemoryMmap,
|
|
||||||
initramfs_size: usize,
|
|
||||||
) -> super::Result<u64> {
|
|
||||||
let round_to_pagesize = |size| (size + (super::PAGE_SIZE - 1)) & !(super::PAGE_SIZE - 1);
|
|
||||||
match guest_mem
|
|
||||||
.last_addr()
|
|
||||||
.checked_sub(round_to_pagesize(initramfs_size) as u64 - 1)
|
|
||||||
{
|
|
||||||
Some(offset) => {
|
|
||||||
if guest_mem.address_in_range(offset) {
|
|
||||||
Ok(offset.raw_value())
|
|
||||||
} else {
|
|
||||||
Err(super::Error::PlatformSpecific(Error::InitramfsAddress))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => Err(super::Error::PlatformSpecific(Error::InitramfsAddress)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_host_cpu_phys_bits(_hypervisor: &dyn hypervisor::Hypervisor) -> u8 {
|
|
||||||
40
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod unit_tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_arch_memory_regions_dram() {
|
|
||||||
let regions = arch_memory_regions();
|
|
||||||
assert_eq!(4, regions.len());
|
|
||||||
assert_eq!(layout::RAM_START, regions[3].0);
|
|
||||||
assert_eq!(RegionType::Ram, regions[3].2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
// Copyright 2020 Arm Limited (or its affiliates). All rights reserved.
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
use std::io::{Read, Seek, SeekFrom};
|
|
||||||
use std::os::fd::AsFd;
|
|
||||||
use std::result;
|
|
||||||
|
|
||||||
use thiserror::Error;
|
|
||||||
use vm_memory::{GuestAddress, GuestMemory};
|
|
||||||
|
|
||||||
/// Errors thrown while loading UEFI binary
|
|
||||||
#[derive(Debug, Error)]
|
|
||||||
pub enum Error {
|
|
||||||
/// Unable to seek to UEFI image start.
|
|
||||||
#[error("Unable to seek to UEFI image start")]
|
|
||||||
SeekUefiStart,
|
|
||||||
/// Unable to seek to UEFI image end.
|
|
||||||
#[error("Unable to seek to UEFI image end")]
|
|
||||||
SeekUefiEnd,
|
|
||||||
/// UEFI image too big.
|
|
||||||
#[error("UEFI image too big")]
|
|
||||||
UefiTooBig,
|
|
||||||
/// Unable to read UEFI image
|
|
||||||
#[error("Unable to read UEFI image")]
|
|
||||||
ReadUefiImage,
|
|
||||||
}
|
|
||||||
type Result<T> = result::Result<T, Error>;
|
|
||||||
|
|
||||||
pub fn load_uefi<F, M: GuestMemory>(
|
|
||||||
guest_mem: &M,
|
|
||||||
guest_addr: GuestAddress,
|
|
||||||
uefi_image: &mut F,
|
|
||||||
) -> Result<()>
|
|
||||||
where
|
|
||||||
F: Read + Seek + AsFd,
|
|
||||||
{
|
|
||||||
let uefi_size = uefi_image
|
|
||||||
.seek(SeekFrom::End(0))
|
|
||||||
.map_err(|_| Error::SeekUefiEnd)? as usize;
|
|
||||||
|
|
||||||
// edk2 image on virtual platform is smaller than 3M
|
|
||||||
if uefi_size > 0x300000 {
|
|
||||||
return Err(Error::UefiTooBig);
|
|
||||||
}
|
|
||||||
uefi_image.rewind().map_err(|_| Error::SeekUefiStart)?;
|
|
||||||
guest_mem
|
|
||||||
.read_exact_volatile_from(guest_addr, &mut uefi_image.as_fd(), uefi_size)
|
|
||||||
.map_err(|_| Error::ReadUefiImage)
|
|
||||||
}
|
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
// found in the LICENSE-BSD-3-Clause file.
|
// found in the LICENSE-BSD-3-Clause file.
|
||||||
|
|
||||||
use std::result;
|
use std::result;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub type Result<T> = result::Result<T, hypervisor::HypervisorCpuError>;
|
pub type Result<T> = result::Result<T, hypervisor::HypervisorCpuError>;
|
||||||
|
|
||||||
@@ -23,7 +24,7 @@ pub fn set_apic_delivery_mode(reg: u32, mode: u32) -> u32 {
|
|||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
/// * `vcpu` - The VCPU object to configure.
|
/// * `vcpu` - The VCPU object to configure.
|
||||||
pub fn set_lint(vcpu: &dyn hypervisor::Vcpu) -> Result<()> {
|
pub fn set_lint(vcpu: &Arc<dyn hypervisor::Vcpu>) -> Result<()> {
|
||||||
let mut klapic = vcpu.get_lapic()?;
|
let mut klapic = vcpu.get_lapic()?;
|
||||||
|
|
||||||
let lvt_lint0 = klapic.get_klapic_reg(APIC_LVT0);
|
let lvt_lint0 = klapic.get_klapic_reg(APIC_LVT0);
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,10 +1,6 @@
|
|||||||
// Copyright 2017 The Chromium OS Authors. All rights reserved.
|
// 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.
|
||||||
#![allow(non_camel_case_types)]
|
|
||||||
use vm_memory::ByteValued;
|
|
||||||
|
|
||||||
pub const MP_PROCESSOR: ::std::os::raw::c_uint = 0;
|
pub const MP_PROCESSOR: ::std::os::raw::c_uint = 0;
|
||||||
pub const MP_BUS: ::std::os::raw::c_uint = 1;
|
pub const MP_BUS: ::std::os::raw::c_uint = 1;
|
||||||
@@ -19,7 +15,7 @@ pub const MP_IRQDIR_DEFAULT: ::std::os::raw::c_uint = 0;
|
|||||||
#[repr(C)]
|
#[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,
|
||||||
@@ -31,22 +27,15 @@ pub struct mpf_intel {
|
|||||||
pub feature5: ::std::os::raw::c_uchar,
|
pub feature5: ::std::os::raw::c_uchar,
|
||||||
}
|
}
|
||||||
|
|
||||||
const _: () = assert!(::core::mem::size_of::<mpf_intel>() == 16);
|
|
||||||
// SAFETY: all members of this struct are plain integers
|
|
||||||
// and the sum of their sizes is the size of the struct, so
|
|
||||||
// padding and reserved values are not possible as there
|
|
||||||
// would be nowhere for them to exist.
|
|
||||||
unsafe impl ByteValued for mpf_intel {}
|
|
||||||
|
|
||||||
#[repr(C)]
|
#[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,
|
||||||
@@ -54,19 +43,6 @@ pub struct mpc_table {
|
|||||||
pub reserved: ::std::os::raw::c_uint,
|
pub reserved: ::std::os::raw::c_uint,
|
||||||
}
|
}
|
||||||
|
|
||||||
const _: () = {
|
|
||||||
assert!(::core::mem::size_of::<mpc_table>() == 4 + 2 + 1 + 1 + 8 + 12 + 4 + 2 + 2 + 4 + 4);
|
|
||||||
assert!(::core::mem::size_of::<::std::os::raw::c_uint>() == 4);
|
|
||||||
assert!(::core::mem::size_of::<::std::os::raw::c_ushort>() == 2);
|
|
||||||
assert!(::core::mem::size_of::<::std::os::raw::c_uchar>() == 1);
|
|
||||||
};
|
|
||||||
|
|
||||||
// SAFETY: all members of this struct are plain integers
|
|
||||||
// and the sum of their sizes is the size of the struct, so
|
|
||||||
// padding and reserved values are not possible as there
|
|
||||||
// would be nowhere for them to exist.
|
|
||||||
unsafe impl ByteValued for mpc_table {}
|
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Debug, Default, Copy, Clone)]
|
#[derive(Debug, Default, Copy, Clone)]
|
||||||
pub struct mpc_cpu {
|
pub struct mpc_cpu {
|
||||||
@@ -79,13 +55,6 @@ pub struct mpc_cpu {
|
|||||||
pub reserved: [::std::os::raw::c_uint; 2usize],
|
pub reserved: [::std::os::raw::c_uint; 2usize],
|
||||||
}
|
}
|
||||||
|
|
||||||
const _: () = assert!(::core::mem::size_of::<mpc_cpu>() == 20);
|
|
||||||
// SAFETY: all members of this struct are plain integers
|
|
||||||
// and the sum of their sizes is the size of the struct, so
|
|
||||||
// padding and reserved values are not possible as there
|
|
||||||
// would be nowhere for them to exist.
|
|
||||||
unsafe impl ByteValued for mpc_cpu {}
|
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Debug, Default, Copy, Clone)]
|
#[derive(Debug, Default, Copy, Clone)]
|
||||||
pub struct mpc_bus {
|
pub struct mpc_bus {
|
||||||
@@ -94,13 +63,6 @@ pub struct mpc_bus {
|
|||||||
pub bustype: [::std::os::raw::c_uchar; 6usize],
|
pub bustype: [::std::os::raw::c_uchar; 6usize],
|
||||||
}
|
}
|
||||||
|
|
||||||
const _: () = assert!(::core::mem::size_of::<mpc_bus>() == 8);
|
|
||||||
// SAFETY: all members of this struct are plain integers
|
|
||||||
// and the sum of their sizes is the size of the struct, so
|
|
||||||
// padding and reserved values are not possible as there
|
|
||||||
// would be nowhere for them to exist.
|
|
||||||
unsafe impl ByteValued for mpc_bus {}
|
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Debug, Default, Copy, Clone)]
|
#[derive(Debug, Default, Copy, Clone)]
|
||||||
pub struct mpc_ioapic {
|
pub struct mpc_ioapic {
|
||||||
@@ -111,13 +73,6 @@ pub struct mpc_ioapic {
|
|||||||
pub apicaddr: ::std::os::raw::c_uint,
|
pub apicaddr: ::std::os::raw::c_uint,
|
||||||
}
|
}
|
||||||
|
|
||||||
const _: () = assert!(::core::mem::size_of::<mpc_ioapic>() == 8);
|
|
||||||
// SAFETY: all members of this struct are plain integers
|
|
||||||
// and the sum of their sizes is the size of the struct, so
|
|
||||||
// padding and reserved values are not possible as there
|
|
||||||
// would be nowhere for them to exist.
|
|
||||||
unsafe impl ByteValued for mpc_ioapic {}
|
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Debug, Default, Copy, Clone)]
|
#[derive(Debug, Default, Copy, Clone)]
|
||||||
pub struct mpc_intsrc {
|
pub struct mpc_intsrc {
|
||||||
@@ -130,13 +85,6 @@ pub struct mpc_intsrc {
|
|||||||
pub dstirq: ::std::os::raw::c_uchar,
|
pub dstirq: ::std::os::raw::c_uchar,
|
||||||
}
|
}
|
||||||
|
|
||||||
const _: () = assert!(::core::mem::size_of::<mpc_intsrc>() == 8);
|
|
||||||
// SAFETY: all members of this struct are plain integers
|
|
||||||
// and the sum of their sizes is the size of the struct, so
|
|
||||||
// padding and reserved values are not possible as there
|
|
||||||
// would be nowhere for them to exist.
|
|
||||||
unsafe impl ByteValued for mpc_intsrc {}
|
|
||||||
|
|
||||||
pub const MP_IRQ_SOURCE_TYPES_MP_INT: ::std::os::raw::c_uint = 0;
|
pub const MP_IRQ_SOURCE_TYPES_MP_INT: ::std::os::raw::c_uint = 0;
|
||||||
pub const MP_IRQ_SOURCE_TYPES_MP_NMI: ::std::os::raw::c_uint = 1;
|
pub const MP_IRQ_SOURCE_TYPES_MP_NMI: ::std::os::raw::c_uint = 1;
|
||||||
pub const MP_IRQ_SOURCE_TYPES_MP_EXT_INT: ::std::os::raw::c_uint = 3;
|
pub const MP_IRQ_SOURCE_TYPES_MP_EXT_INT: ::std::os::raw::c_uint = 3;
|
||||||
@@ -153,26 +101,12 @@ pub struct mpc_lintsrc {
|
|||||||
pub destapiclint: ::std::os::raw::c_uchar,
|
pub destapiclint: ::std::os::raw::c_uchar,
|
||||||
}
|
}
|
||||||
|
|
||||||
const _: () = assert!(::core::mem::size_of::<mpc_lintsrc>() == 8);
|
|
||||||
// SAFETY: all members of this struct are plain integers
|
|
||||||
// and the sum of their sizes is the size of the struct, so
|
|
||||||
// padding and reserved values are not possible as there
|
|
||||||
// would be nowhere for them to exist.
|
|
||||||
unsafe impl ByteValued for mpc_lintsrc {}
|
|
||||||
|
|
||||||
#[repr(C)]
|
#[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],
|
||||||
}
|
}
|
||||||
|
|
||||||
const _: () = assert!(::core::mem::size_of::<mpc_oemtable>() == 16);
|
|
||||||
// SAFETY: all members of this struct are plain integers
|
|
||||||
// and the sum of their sizes is the size of the struct, so
|
|
||||||
// padding and reserved values are not possible as there
|
|
||||||
// would be nowhere for them to exist.
|
|
||||||
unsafe impl ByteValued for mpc_oemtable {}
|
|
||||||
|
|||||||
@@ -5,17 +5,15 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// 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 log::{info, warn};
|
|
||||||
use thiserror::Error;
|
|
||||||
use vm_memory::{Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryError};
|
|
||||||
|
|
||||||
use super::MAX_SUPPORTED_CPUS_LEGACY;
|
|
||||||
use crate::GuestMemoryMmap;
|
|
||||||
use crate::layout::{APIC_START, HIGH_RAM_START, IOAPIC_START};
|
use crate::layout::{APIC_START, HIGH_RAM_START, IOAPIC_START};
|
||||||
use crate::x86_64::{get_x2apic_id, mpspec};
|
use crate::x86_64::mpspec;
|
||||||
|
use crate::GuestMemoryMmap;
|
||||||
|
use libc::c_char;
|
||||||
|
use std::io;
|
||||||
|
use std::mem;
|
||||||
|
use std::result;
|
||||||
|
use std::slice;
|
||||||
|
use vm_memory::{Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryError};
|
||||||
|
|
||||||
// This is a workaround to the Rust enforcement specifying that any implementation of a foreign
|
// 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:
|
||||||
@@ -52,55 +50,57 @@ unsafe impl ByteValued for MpcLintsrcWrapper {}
|
|||||||
// SAFETY: see above
|
// SAFETY: see above
|
||||||
unsafe impl ByteValued for MpfIntelWrapper {}
|
unsafe impl ByteValued for MpfIntelWrapper {}
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
/// There was too little guest memory to store the entire MP table.
|
/// There was too little guest memory to store the entire MP table.
|
||||||
#[error("There was too little guest memory to store the entire MP table")]
|
|
||||||
NotEnoughMemory,
|
NotEnoughMemory,
|
||||||
/// The MP table has too little address space to be stored.
|
/// The MP table has too little address space to be stored.
|
||||||
#[error("The MP table has too little address space to be stored")]
|
|
||||||
AddressOverflow,
|
AddressOverflow,
|
||||||
/// Failure while zeroing out the memory for the MP table.
|
/// Failure while zeroing out the memory for the MP table.
|
||||||
#[error("Failure while zeroing out the memory for the MP table")]
|
Clear(GuestMemoryError),
|
||||||
Clear(#[source] GuestMemoryError),
|
/// Number of CPUs exceeds the maximum supported CPUs
|
||||||
|
TooManyCpus,
|
||||||
/// Failure to write the MP floating pointer.
|
/// Failure to write the MP floating pointer.
|
||||||
#[error("Failure to write the MP floating pointer")]
|
WriteMpfIntel(GuestMemoryError),
|
||||||
WriteMpfIntel(#[source] GuestMemoryError),
|
|
||||||
/// Failure to write MP CPU entry.
|
/// Failure to write MP CPU entry.
|
||||||
#[error("Failure to write MP CPU entry")]
|
WriteMpcCpu(GuestMemoryError),
|
||||||
WriteMpcCpu(#[source] GuestMemoryError),
|
|
||||||
/// Failure to write MP ioapic entry.
|
/// Failure to write MP ioapic entry.
|
||||||
#[error("Failure to write MP ioapic entry")]
|
WriteMpcIoapic(GuestMemoryError),
|
||||||
WriteMpcIoapic(#[source] GuestMemoryError),
|
|
||||||
/// Failure to write MP bus entry.
|
/// Failure to write MP bus entry.
|
||||||
#[error("Failure to write MP bus entry")]
|
WriteMpcBus(GuestMemoryError),
|
||||||
WriteMpcBus(#[source] GuestMemoryError),
|
|
||||||
/// Failure to write MP interrupt source entry.
|
/// Failure to write MP interrupt source entry.
|
||||||
#[error("Failure to write MP interrupt source entry")]
|
WriteMpcIntsrc(GuestMemoryError),
|
||||||
WriteMpcIntsrc(#[source] GuestMemoryError),
|
|
||||||
/// Failure to write MP local interrupt source entry.
|
/// Failure to write MP local interrupt source entry.
|
||||||
#[error("Failure to write MP local interrupt source entry")]
|
WriteMpcLintsrc(GuestMemoryError),
|
||||||
WriteMpcLintsrc(#[source] GuestMemoryError),
|
|
||||||
/// Failure to write MP table header.
|
/// Failure to write MP table header.
|
||||||
#[error("Failure to write MP table header")]
|
WriteMpcTable(GuestMemoryError),
|
||||||
WriteMpcTable(#[source] GuestMemoryError),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Result<T> = result::Result<T, Error>;
|
pub type Result<T> = result::Result<T, Error>;
|
||||||
|
|
||||||
|
// With APIC/xAPIC, there are only 255 APIC IDs available. And IOAPIC occupies
|
||||||
|
// one APIC ID, so only 254 CPUs at maximum may be supported. Actually it's
|
||||||
|
// a large number for FC usecases.
|
||||||
|
pub const MAX_SUPPORTED_CPUS: u32 = 254;
|
||||||
|
|
||||||
|
// Convenience macro for making arrays of diverse character types.
|
||||||
|
macro_rules! char_array {
|
||||||
|
($t:ty; $( $c:expr ),*) => ( [ $( $c as $t ),* ] )
|
||||||
|
}
|
||||||
|
|
||||||
// Most of these variables are sourced from the Intel MP Spec 1.4.
|
// 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 + ByteValued>(v: &T) -> u8 {
|
fn compute_checksum<T: Copy>(v: &T) -> u8 {
|
||||||
// SAFETY: we are only reading the bytes within the size of the `T` reference `v`.
|
// SAFETY: we are only reading the bytes within the size of the `T` reference `v`.
|
||||||
let v_slice = unsafe { slice::from_raw_parts(v as *const T as *const u8, mem::size_of::<T>()) };
|
let 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;
|
||||||
@@ -115,7 +115,7 @@ fn mpf_intel_compute_checksum(v: &mpspec::mpf_intel) -> u8 {
|
|||||||
(!checksum).wrapping_add(1)
|
(!checksum).wrapping_add(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compute_mp_size(num_cpus: u32) -> usize {
|
fn compute_mp_size(num_cpus: u8) -> usize {
|
||||||
mem::size_of::<MpfIntelWrapper>()
|
mem::size_of::<MpfIntelWrapper>()
|
||||||
+ mem::size_of::<MpcTableWrapper>()
|
+ mem::size_of::<MpcTableWrapper>()
|
||||||
+ mem::size_of::<MpcCpuWrapper>() * (num_cpus as usize)
|
+ mem::size_of::<MpcCpuWrapper>() * (num_cpus as usize)
|
||||||
@@ -126,19 +126,9 @@ fn compute_mp_size(num_cpus: u32) -> usize {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Performs setup of the MP table for the given `num_cpus`.
|
/// 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: u32,
|
|
||||||
topology: Option<(u16, u16, u16, u16)>,
|
|
||||||
) -> Result<()> {
|
|
||||||
if num_cpus > 0 {
|
|
||||||
let cpu_id_max = num_cpus - 1;
|
|
||||||
let x2apic_id_max = get_x2apic_id(cpu_id_max, topology);
|
|
||||||
if x2apic_id_max >= MAX_SUPPORTED_CPUS_LEGACY {
|
|
||||||
info!("Skipping mptable creation due to too many CPUs");
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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.
|
||||||
@@ -152,7 +142,7 @@ pub fn setup_mptable(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut checksum: u8 = 0;
|
let mut checksum: u8 = 0;
|
||||||
let ioapicid: u8 = MAX_SUPPORTED_CPUS_LEGACY as u8 + 1;
|
let ioapicid: u8 = num_cpus + 1;
|
||||||
|
|
||||||
// The checked_add here ensures the all of the following base_mp.unchecked_add's will be without
|
// The checked_add here ensures the all of the following base_mp.unchecked_add's will be without
|
||||||
// overflow.
|
// overflow.
|
||||||
@@ -164,13 +154,13 @@ pub fn setup_mptable(
|
|||||||
return Err(Error::AddressOverflow);
|
return Err(Error::AddressOverflow);
|
||||||
}
|
}
|
||||||
|
|
||||||
mem.read_exact_volatile_from(base_mp, &mut vec![0; mp_size].as_slice(), mp_size)
|
mem.read_exact_from(base_mp, &mut io::repeat(0), mp_size)
|
||||||
.map_err(Error::Clear)?;
|
.map_err(Error::Clear)?;
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut mpf_intel = MpfIntelWrapper(mpspec::mpf_intel::default());
|
let mut mpf_intel = MpfIntelWrapper(mpspec::mpf_intel::default());
|
||||||
let size = mem::size_of::<MpfIntelWrapper>() as u64;
|
let size = mem::size_of::<MpfIntelWrapper>() as u64;
|
||||||
mpf_intel.0.signature = *SMP_MAGIC_IDENT;
|
mpf_intel.0.signature = SMP_MAGIC_IDENT;
|
||||||
mpf_intel.0.length = 1;
|
mpf_intel.0.length = 1;
|
||||||
mpf_intel.0.specification = 4;
|
mpf_intel.0.specification = 4;
|
||||||
mpf_intel.0.physptr = (base_mp.raw_value() + size) as u32;
|
mpf_intel.0.physptr = (base_mp.raw_value() + size) as u32;
|
||||||
@@ -190,7 +180,7 @@ pub fn setup_mptable(
|
|||||||
for cpu_id in 0..num_cpus {
|
for cpu_id in 0..num_cpus {
|
||||||
let mut mpc_cpu = MpcCpuWrapper(mpspec::mpc_cpu::default());
|
let mut mpc_cpu = MpcCpuWrapper(mpspec::mpc_cpu::default());
|
||||||
mpc_cpu.0.type_ = mpspec::MP_PROCESSOR as u8;
|
mpc_cpu.0.type_ = mpspec::MP_PROCESSOR as u8;
|
||||||
mpc_cpu.0.apicid = get_x2apic_id(cpu_id, 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 {
|
||||||
@@ -211,7 +201,7 @@ pub fn setup_mptable(
|
|||||||
let mut mpc_bus = MpcBusWrapper(mpspec::mpc_bus::default());
|
let mut mpc_bus = MpcBusWrapper(mpspec::mpc_bus::default());
|
||||||
mpc_bus.0.type_ = mpspec::MP_BUS as u8;
|
mpc_bus.0.type_ = mpspec::MP_BUS as u8;
|
||||||
mpc_bus.0.busid = 0;
|
mpc_bus.0.busid = 0;
|
||||||
mpc_bus.0.bustype = *BUS_TYPE_ISA;
|
mpc_bus.0.bustype = BUS_TYPE_ISA;
|
||||||
mem.write_obj(mpc_bus, base_mp)
|
mem.write_obj(mpc_bus, base_mp)
|
||||||
.map_err(Error::WriteMpcBus)?;
|
.map_err(Error::WriteMpcBus)?;
|
||||||
base_mp = base_mp.unchecked_add(size as u64);
|
base_mp = base_mp.unchecked_add(size as u64);
|
||||||
@@ -282,14 +272,14 @@ pub fn setup_mptable(
|
|||||||
|
|
||||||
{
|
{
|
||||||
let mut mpc_table = MpcTableWrapper(mpspec::mpc_table::default());
|
let mut mpc_table = MpcTableWrapper(mpspec::mpc_table::default());
|
||||||
mpc_table.0.signature = *MPC_SIGNATURE;
|
mpc_table.0.signature = MPC_SIGNATURE;
|
||||||
mpc_table.0.length = table_end.unchecked_offset_from(table_base) as u16;
|
mpc_table.0.length = table_end.unchecked_offset_from(table_base) as u16;
|
||||||
mpc_table.0.spec = MPC_SPEC;
|
mpc_table.0.spec = MPC_SPEC;
|
||||||
mpc_table.0.oem = *MPC_OEM;
|
mpc_table.0.oem = MPC_OEM;
|
||||||
mpc_table.0.productid = *MPC_PRODUCT_ID;
|
mpc_table.0.productid = MPC_PRODUCT_ID;
|
||||||
mpc_table.0.lapic = APIC_START.0 as u32;
|
mpc_table.0.lapic = APIC_START.0 as u32;
|
||||||
checksum = checksum.wrapping_add(compute_checksum(&mpc_table.0));
|
checksum = checksum.wrapping_add(compute_checksum(&mpc_table.0));
|
||||||
mpc_table.0.checksum = (!checksum).wrapping_add(1);
|
mpc_table.0.checksum = (!checksum).wrapping_add(1) as i8;
|
||||||
mem.write_obj(mpc_table, table_base)
|
mem.write_obj(mpc_table, table_base)
|
||||||
.map_err(Error::WriteMpcTable)?;
|
.map_err(Error::WriteMpcTable)?;
|
||||||
}
|
}
|
||||||
@@ -298,12 +288,10 @@ pub fn setup_mptable(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod unit_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 {
|
||||||
@@ -322,7 +310,7 @@ mod unit_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]
|
||||||
@@ -331,7 +319,7 @@ mod unit_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]
|
||||||
@@ -340,7 +328,7 @@ mod unit_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();
|
||||||
|
|
||||||
@@ -356,31 +344,27 @@ mod unit_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);
|
||||||
}
|
}
|
||||||
@@ -389,12 +373,12 @@ mod unit_tests {
|
|||||||
fn cpu_entry_count() {
|
fn cpu_entry_count() {
|
||||||
let mem = GuestMemoryMmap::from_ranges(&[(
|
let mem = GuestMemoryMmap::from_ranges(&[(
|
||||||
MPTABLE_START,
|
MPTABLE_START,
|
||||||
compute_mp_size(MAX_SUPPORTED_CPUS_LEGACY),
|
compute_mp_size(MAX_SUPPORTED_CPUS as u8),
|
||||||
)])
|
)])
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
for i in 0..MAX_SUPPORTED_CPUS_LEGACY {
|
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);
|
||||||
@@ -423,9 +407,11 @@ mod unit_tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn cpu_entry_count_max() {
|
fn cpu_entry_count_max() {
|
||||||
let cpus = MAX_SUPPORTED_CPUS_LEGACY + 1;
|
let cpus = MAX_SUPPORTED_CPUS + 1;
|
||||||
let mem = GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(cpus))]).unwrap();
|
let mem =
|
||||||
|
GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(cpus as u8))]).unwrap();
|
||||||
|
|
||||||
setup_mptable(MPTABLE_START, &mem, cpus, None).unwrap();
|
let result = setup_mptable(MPTABLE_START, &mem, cpus as u8);
|
||||||
|
assert!(result.is_err());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,57 +6,41 @@
|
|||||||
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE-BSD-3-Clause file.
|
// found in the LICENSE-BSD-3-Clause file.
|
||||||
use std::{mem, result};
|
use crate::layout::{BOOT_GDT_START, BOOT_IDT_START, PVH_INFO_START};
|
||||||
|
use crate::GuestMemoryMmap;
|
||||||
use hypervisor::arch::x86::gdt::{gdt_entry, segment_from_gdt};
|
use hypervisor::arch::x86::gdt::{gdt_entry, segment_from_gdt};
|
||||||
use hypervisor::arch::x86::regs::CR0_PE;
|
use hypervisor::arch::x86::regs::CR0_PE;
|
||||||
use hypervisor::arch::x86::{FpuState, SpecialRegisters};
|
use hypervisor::arch::x86::{FpuState, SpecialRegisters, StandardRegisters};
|
||||||
use thiserror::Error;
|
use std::sync::Arc;
|
||||||
|
use std::{mem, result};
|
||||||
use vm_memory::{Address, Bytes, GuestMemory, GuestMemoryError};
|
use vm_memory::{Address, Bytes, GuestMemory, GuestMemoryError};
|
||||||
|
|
||||||
use crate::layout::{
|
#[derive(Debug)]
|
||||||
BOOT_GDT_START, BOOT_IDT_START, BOOT_STACK_POINTER, PVH_INFO_START, ZERO_PAGE_START,
|
|
||||||
};
|
|
||||||
use crate::{EntryPoint, GuestMemoryMmap};
|
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
/// Failed to get SREGs for this CPU.
|
/// Failed to get SREGs for this CPU.
|
||||||
#[error("Failed to get SREGs for this CPU")]
|
GetStatusRegisters(hypervisor::HypervisorCpuError),
|
||||||
GetStatusRegisters(#[source] hypervisor::HypervisorCpuError),
|
|
||||||
/// Failed to set base registers for this CPU.
|
/// Failed to set base registers for this CPU.
|
||||||
#[error("Failed to set base registers for this CPU")]
|
SetBaseRegisters(hypervisor::HypervisorCpuError),
|
||||||
SetBaseRegisters(#[source] hypervisor::HypervisorCpuError),
|
|
||||||
/// Failed to configure the FPU.
|
/// Failed to configure the FPU.
|
||||||
#[error("Failed to configure the FPU")]
|
SetFpuRegisters(hypervisor::HypervisorCpuError),
|
||||||
SetFpuRegisters(#[source] hypervisor::HypervisorCpuError),
|
|
||||||
/// Setting up MSRs failed.
|
/// Setting up MSRs failed.
|
||||||
#[error("Setting up MSRs failed")]
|
SetModelSpecificRegisters(hypervisor::HypervisorCpuError),
|
||||||
SetModelSpecificRegisters(#[source] hypervisor::HypervisorCpuError),
|
|
||||||
/// Failed to set SREGs for this CPU.
|
/// Failed to set SREGs for this CPU.
|
||||||
#[error("Failed to set SREGs for this CPU")]
|
SetStatusRegisters(hypervisor::HypervisorCpuError),
|
||||||
SetStatusRegisters(#[source] hypervisor::HypervisorCpuError),
|
|
||||||
/// Checking the GDT address failed.
|
/// Checking the GDT address failed.
|
||||||
#[error("Checking the GDT address failed")]
|
|
||||||
CheckGdtAddr,
|
CheckGdtAddr,
|
||||||
/// Writing the GDT to RAM failed.
|
/// Writing the GDT to RAM failed.
|
||||||
#[error("Writing the GDT to RAM failed")]
|
WriteGdt(GuestMemoryError),
|
||||||
WriteGdt(#[source] GuestMemoryError),
|
|
||||||
/// Writing the IDT to RAM failed.
|
/// Writing the IDT to RAM failed.
|
||||||
#[error("Writing the IDT to RAM failed")]
|
WriteIdt(GuestMemoryError),
|
||||||
WriteIdt(#[source] GuestMemoryError),
|
|
||||||
/// Writing PDPTE to RAM failed.
|
/// Writing PDPTE to RAM failed.
|
||||||
#[error("Writing PDPTE to RAM failed")]
|
WritePdpteAddress(GuestMemoryError),
|
||||||
WritePdpteAddress(#[source] GuestMemoryError),
|
|
||||||
/// Writing PDE to RAM failed.
|
/// Writing PDE to RAM failed.
|
||||||
#[error("Writing PDE to RAM failed")]
|
WritePdeAddress(GuestMemoryError),
|
||||||
WritePdeAddress(#[source] GuestMemoryError),
|
|
||||||
/// Writing PML4 to RAM failed.
|
/// Writing PML4 to RAM failed.
|
||||||
#[error("Writing PML4 to RAM failed")]
|
WritePml4Address(GuestMemoryError),
|
||||||
WritePml4Address(#[source] GuestMemoryError),
|
|
||||||
/// Writing PML5 to RAM failed.
|
/// Writing PML5 to RAM failed.
|
||||||
#[error("Writing PML5 to RAM failed")]
|
WritePml5Address(GuestMemoryError),
|
||||||
WritePml5Address(#[source] GuestMemoryError),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Result<T> = result::Result<T, Error>;
|
pub type Result<T> = result::Result<T, Error>;
|
||||||
@@ -66,7 +50,7 @@ pub type Result<T> = result::Result<T, Error>;
|
|||||||
/// # Arguments
|
/// # Arguments
|
||||||
///
|
///
|
||||||
/// * `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_fpu(vcpu: &dyn hypervisor::Vcpu) -> Result<()> {
|
pub fn setup_fpu(vcpu: &Arc<dyn hypervisor::Vcpu>) -> Result<()> {
|
||||||
let fpu: FpuState = FpuState {
|
let fpu: FpuState = FpuState {
|
||||||
fcw: 0x37f,
|
fcw: 0x37f,
|
||||||
mxcsr: 0x1f80,
|
mxcsr: 0x1f80,
|
||||||
@@ -81,8 +65,8 @@ pub fn setup_fpu(vcpu: &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.
|
||||||
pub fn setup_msrs(vcpu: &dyn hypervisor::Vcpu) -> Result<()> {
|
pub fn setup_msrs(vcpu: &Arc<dyn hypervisor::Vcpu>) -> Result<()> {
|
||||||
vcpu.set_msrs(vcpu.boot_msr_entries())
|
vcpu.set_msrs(&vcpu.boot_msr_entries())
|
||||||
.map_err(Error::SetModelSpecificRegisters)?;
|
.map_err(Error::SetModelSpecificRegisters)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -93,22 +77,14 @@ pub fn setup_msrs(vcpu: &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: &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)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,13 +94,9 @@ pub fn setup_regs(vcpu: &dyn hypervisor::Vcpu, entry_point: EntryPoint) -> Resul
|
|||||||
///
|
///
|
||||||
/// * `mem` - The memory that will be passed to the guest.
|
/// * `mem` - The memory that will be passed to the guest.
|
||||||
/// * `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_sregs(
|
pub fn setup_sregs(mem: &GuestMemoryMmap, vcpu: &Arc<dyn hypervisor::Vcpu>) -> Result<()> {
|
||||||
mem: &GuestMemoryMmap,
|
|
||||||
vcpu: &dyn hypervisor::Vcpu,
|
|
||||||
enable_x2_apic_mode: bool,
|
|
||||||
) -> Result<()> {
|
|
||||||
let mut sregs: SpecialRegisters = vcpu.get_sregs().map_err(Error::GetStatusRegisters)?;
|
let mut sregs: SpecialRegisters = vcpu.get_sregs().map_err(Error::GetStatusRegisters)?;
|
||||||
configure_segments_and_sregs(mem, &mut sregs, enable_x2_apic_mode)?;
|
configure_segments_and_sregs(mem, &mut sregs)?;
|
||||||
vcpu.set_sregs(&sregs).map_err(Error::SetStatusRegisters)
|
vcpu.set_sregs(&sregs).map_err(Error::SetStatusRegisters)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,7 +123,6 @@ fn write_idt_value(val: u64, guest_mem: &GuestMemoryMmap) -> Result<()> {
|
|||||||
pub fn configure_segments_and_sregs(
|
pub fn configure_segments_and_sregs(
|
||||||
mem: &GuestMemoryMmap,
|
mem: &GuestMemoryMmap,
|
||||||
sregs: &mut SpecialRegisters,
|
sregs: &mut SpecialRegisters,
|
||||||
enable_x2_apic_mode: bool,
|
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let gdt_table: [u64; BOOT_GDT_MAX] = {
|
let gdt_table: [u64; BOOT_GDT_MAX] = {
|
||||||
// Configure GDT entries as specified by PVH boot protocol
|
// Configure GDT entries as specified by PVH boot protocol
|
||||||
@@ -187,19 +158,14 @@ pub fn configure_segments_and_sregs(
|
|||||||
sregs.cr0 = CR0_PE;
|
sregs.cr0 = CR0_PE;
|
||||||
sregs.cr4 = 0;
|
sregs.cr4 = 0;
|
||||||
|
|
||||||
if enable_x2_apic_mode {
|
|
||||||
const X2APIC_ENABLE_BIT: u64 = 1 << 10;
|
|
||||||
sregs.apic_base |= X2APIC_ENABLE_BIT;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod unit_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()
|
||||||
@@ -213,7 +179,7 @@ mod unit_tests {
|
|||||||
fn segments_and_sregs() {
|
fn segments_and_sregs() {
|
||||||
let mut sregs: SpecialRegisters = Default::default();
|
let mut sregs: SpecialRegisters = Default::default();
|
||||||
let gm = create_guest_mem();
|
let gm = create_guest_mem();
|
||||||
configure_segments_and_sregs(&gm, &mut sregs, false).unwrap();
|
configure_segments_and_sregs(&gm, &mut sregs).unwrap();
|
||||||
assert_eq!(0x0, read_u64(&gm, BOOT_GDT_START));
|
assert_eq!(0x0, read_u64(&gm, BOOT_GDT_START));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
0xcf9b000000ffff,
|
0xcf9b000000ffff,
|
||||||
|
|||||||
@@ -6,35 +6,53 @@
|
|||||||
//
|
//
|
||||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||||
|
|
||||||
use std::{mem, result, slice};
|
|
||||||
|
|
||||||
use thiserror::Error;
|
|
||||||
use uuid::Uuid;
|
|
||||||
use vm_memory::{Address, ByteValued, Bytes, GuestAddress};
|
|
||||||
|
|
||||||
use crate::GuestMemoryMmap;
|
|
||||||
use crate::layout::SMBIOS_START;
|
use crate::layout::SMBIOS_START;
|
||||||
|
use crate::GuestMemoryMmap;
|
||||||
|
use std::fmt::{self, Display};
|
||||||
|
use std::mem;
|
||||||
|
use std::result;
|
||||||
|
use std::slice;
|
||||||
|
use uuid::Uuid;
|
||||||
|
use vm_memory::ByteValued;
|
||||||
|
use vm_memory::{Address, Bytes, GuestAddress};
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
/// There was too little guest memory to store the entire SMBIOS table.
|
/// There was too little guest memory to store the entire SMBIOS table.
|
||||||
#[error("There was too little guest memory to store the SMBIOS table")]
|
|
||||||
NotEnoughMemory,
|
NotEnoughMemory,
|
||||||
/// The SMBIOS table has too little address space to be stored.
|
/// The SMBIOS table has too little address space to be stored.
|
||||||
#[error("The SMBIOS table has too little address space to be stored")]
|
|
||||||
AddressOverflow,
|
AddressOverflow,
|
||||||
/// Failure while zeroing out the memory for the SMBIOS table.
|
/// Failure while zeroing out the memory for the SMBIOS table.
|
||||||
#[error("Failure while zeroing out the memory for the SMBIOS table")]
|
|
||||||
Clear,
|
Clear,
|
||||||
/// Failure to write SMBIOS entrypoint structure
|
/// Failure to write SMBIOS entrypoint structure
|
||||||
#[error("Failure to write SMBIOS entrypoint structure")]
|
|
||||||
WriteSmbiosEp,
|
WriteSmbiosEp,
|
||||||
/// Failure to write additional data to memory
|
/// Failure to write additional data to memory
|
||||||
#[error("Failure to write additional data to memory")]
|
|
||||||
WriteData,
|
WriteData,
|
||||||
/// Failure to parse uuid, uuid format may be error
|
/// Failure to parse uuid, uuid format may be error
|
||||||
#[error("Failure to parse uuid")]
|
ParseUuid(uuid::Error),
|
||||||
ParseUuid(#[source] uuid::Error),
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for Error {}
|
||||||
|
|
||||||
|
impl Display for Error {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
use self::Error::*;
|
||||||
|
|
||||||
|
let description = match self {
|
||||||
|
NotEnoughMemory => {
|
||||||
|
"There was too little guest memory to store the SMBIOS table".to_string()
|
||||||
|
}
|
||||||
|
AddressOverflow => {
|
||||||
|
"The SMBIOS table has too little address space to be stored".to_string()
|
||||||
|
}
|
||||||
|
Clear => "Failure while zeroing out the memory for the SMBIOS table".to_string(),
|
||||||
|
WriteSmbiosEp => "Failure to write SMBIOS entrypoint structure".to_string(),
|
||||||
|
WriteData => "Failure to write additional data to memory".to_string(),
|
||||||
|
ParseUuid(e) => format!("Failure to parse uuid: {e}"),
|
||||||
|
};
|
||||||
|
|
||||||
|
write!(f, "SMBIOS error: {description}")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Result<T> = result::Result<T, Error>;
|
pub type Result<T> = result::Result<T, Error>;
|
||||||
@@ -272,7 +290,7 @@ pub fn setup_smbios(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod unit_tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -1,24 +1,21 @@
|
|||||||
// Copyright © 2021 Intel Corporation
|
// Copyright © 2021 Intel Corporation
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
use crate::GuestMemoryMmap;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{Read, Seek, SeekFrom};
|
use std::io::{Read, Seek, SeekFrom};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use log::{debug, info};
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use vm_memory::{ByteValued, Bytes, GuestAddress, GuestMemoryError};
|
use vm_memory::{ByteValued, Bytes, GuestAddress, GuestMemoryError};
|
||||||
|
|
||||||
use crate::GuestMemoryMmap;
|
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum TdvfError {
|
pub enum TdvfError {
|
||||||
#[error("Failed read TDVF descriptor")]
|
#[error("Failed read TDVF descriptor: {0}")]
|
||||||
ReadDescriptor(#[source] std::io::Error),
|
ReadDescriptor(#[source] std::io::Error),
|
||||||
#[error("Failed read TDVF descriptor offset")]
|
#[error("Failed read TDVF descriptor offset: {0}")]
|
||||||
ReadDescriptorOffset(#[source] std::io::Error),
|
ReadDescriptorOffset(#[source] std::io::Error),
|
||||||
#[error("Failed read GUID table")]
|
#[error("Failed read GUID table: {0}")]
|
||||||
ReadGuidTable(#[source] std::io::Error),
|
ReadGuidTable(#[source] std::io::Error),
|
||||||
#[error("Invalid descriptor signature")]
|
#[error("Invalid descriptor signature")]
|
||||||
InvalidDescriptorSignature,
|
InvalidDescriptorSignature,
|
||||||
@@ -26,9 +23,9 @@ pub enum TdvfError {
|
|||||||
InvalidDescriptorSize,
|
InvalidDescriptorSize,
|
||||||
#[error("Invalid descriptor version")]
|
#[error("Invalid descriptor version")]
|
||||||
InvalidDescriptorVersion,
|
InvalidDescriptorVersion,
|
||||||
#[error("Failed to write HOB details to guest memory")]
|
#[error("Failed to write HOB details to guest memory: {0}")]
|
||||||
GuestMemoryWriteHob(#[source] GuestMemoryError),
|
GuestMemoryWriteHob(#[source] GuestMemoryError),
|
||||||
#[error("Failed to create Uuid")]
|
#[error("Failed to create Uuid: {0}")]
|
||||||
UuidCreation(#[source] uuid::Error),
|
UuidCreation(#[source] uuid::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,7 +33,7 @@ const TABLE_FOOTER_GUID: &str = "96b582de-1fb2-45f7-baea-a366c55a082d";
|
|||||||
const TDVF_METADATA_OFFSET_GUID: &str = "e47a6535-984a-4798-865e-4685a7bf8ec2";
|
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],
|
||||||
@@ -46,7 +43,7 @@ pub struct TdvfDescriptor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TDVF_SECTION
|
// TDVF_SECTION
|
||||||
#[repr(C, packed)]
|
#[repr(packed)]
|
||||||
#[derive(Clone, Copy, Default, Debug)]
|
#[derive(Clone, Copy, Default, Debug)]
|
||||||
pub struct TdvfSection {
|
pub struct TdvfSection {
|
||||||
pub data_offset: u32,
|
pub data_offset: u32,
|
||||||
@@ -101,7 +98,7 @@ fn tdvf_descriptor_offset(file: &mut File) -> Result<(SeekFrom, bool), TdvfError
|
|||||||
// We start after the footer GUID and the table length.
|
// We start after the footer GUID and the table length.
|
||||||
let mut offset = table_size - 18;
|
let mut offset = table_size - 18;
|
||||||
|
|
||||||
debug!("Parsing GUID structure");
|
debug!("Parsing GUIDed structure");
|
||||||
while offset >= 18 {
|
while offset >= 18 {
|
||||||
let entry_uuid = Uuid::from_slice_le(&table[offset - 16..offset])
|
let entry_uuid = Uuid::from_slice_le(&table[offset - 16..offset])
|
||||||
.map_err(TdvfError::UuidCreation)?;
|
.map_err(TdvfError::UuidCreation)?;
|
||||||
@@ -109,7 +106,7 @@ fn tdvf_descriptor_offset(file: &mut File) -> Result<(SeekFrom, bool), TdvfError
|
|||||||
u16::from_le_bytes(table[offset - 18..offset - 16].try_into().unwrap()) as usize;
|
u16::from_le_bytes(table[offset - 18..offset - 16].try_into().unwrap()) as usize;
|
||||||
debug!(
|
debug!(
|
||||||
"Entry GUID = {}, size = {}",
|
"Entry GUID = {}, size = {}",
|
||||||
entry_uuid.hyphenated(),
|
entry_uuid.hyphenated().to_string(),
|
||||||
entry_size
|
entry_size
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -210,7 +207,7 @@ enum HobType {
|
|||||||
EndOfHobList = 0xffff,
|
EndOfHobList = 0xffff,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C, packed)]
|
#[repr(C)]
|
||||||
#[derive(Copy, Clone, Default, Debug)]
|
#[derive(Copy, Clone, Default, Debug)]
|
||||||
struct HobHeader {
|
struct HobHeader {
|
||||||
r#type: HobType,
|
r#type: HobType,
|
||||||
@@ -218,7 +215,7 @@ struct HobHeader {
|
|||||||
reserved: u32,
|
reserved: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C, packed)]
|
#[repr(C)]
|
||||||
#[derive(Copy, Clone, Default, Debug)]
|
#[derive(Copy, Clone, Default, Debug)]
|
||||||
struct HobHandoffInfoTable {
|
struct HobHandoffInfoTable {
|
||||||
header: HobHeader,
|
header: HobHeader,
|
||||||
@@ -231,7 +228,7 @@ struct HobHandoffInfoTable {
|
|||||||
efi_end_of_hob_list: u64,
|
efi_end_of_hob_list: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C, packed)]
|
#[repr(C)]
|
||||||
#[derive(Copy, Clone, Default, Debug)]
|
#[derive(Copy, Clone, Default, Debug)]
|
||||||
struct EfiGuid {
|
struct EfiGuid {
|
||||||
data1: u32,
|
data1: u32,
|
||||||
@@ -240,7 +237,7 @@ struct EfiGuid {
|
|||||||
data4: [u8; 8],
|
data4: [u8; 8],
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C, packed)]
|
#[repr(C)]
|
||||||
#[derive(Copy, Clone, Default, Debug)]
|
#[derive(Copy, Clone, Default, Debug)]
|
||||||
struct HobResourceDescriptor {
|
struct HobResourceDescriptor {
|
||||||
header: HobHeader,
|
header: HobHeader,
|
||||||
@@ -251,7 +248,7 @@ struct HobResourceDescriptor {
|
|||||||
resource_length: u64,
|
resource_length: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C, packed)]
|
#[repr(C)]
|
||||||
#[derive(Copy, Clone, Default, Debug)]
|
#[derive(Copy, Clone, Default, Debug)]
|
||||||
struct HobGuidType {
|
struct HobGuidType {
|
||||||
header: HobHeader,
|
header: HobHeader,
|
||||||
@@ -267,14 +264,14 @@ pub enum PayloadImageType {
|
|||||||
RawVmLinux,
|
RawVmLinux,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C, packed)]
|
#[repr(C)]
|
||||||
#[derive(Copy, Clone, Default, Debug)]
|
#[derive(Copy, Clone, Default, Debug)]
|
||||||
pub struct PayloadInfo {
|
pub struct PayloadInfo {
|
||||||
pub image_type: PayloadImageType,
|
pub image_type: PayloadImageType,
|
||||||
pub entry_point: u64,
|
pub entry_point: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C, packed)]
|
#[repr(C)]
|
||||||
#[derive(Copy, Clone, Default, Debug)]
|
#[derive(Copy, Clone, Default, Debug)]
|
||||||
struct TdPayload {
|
struct TdPayload {
|
||||||
guid_type: HobGuidType,
|
guid_type: HobGuidType,
|
||||||
@@ -300,12 +297,12 @@ 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 {
|
||||||
fn update_offset<T>(&mut self) {
|
fn update_offset<T>(&mut self) {
|
||||||
self.current_offset = align_hob(self.current_offset + std::mem::size_of::<T>() as u64);
|
self.current_offset = align_hob(self.current_offset + std::mem::size_of::<T>() as u64)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start(offset: u64) -> TdHob {
|
pub fn start(offset: u64) -> TdHob {
|
||||||
@@ -520,7 +517,7 @@ impl TdHob {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod unit_tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -529,7 +526,7 @@ mod unit_tests {
|
|||||||
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!("{section:x?}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
[package]
|
|
||||||
authors = ["The Chromium OS Authors", "The Cloud Hypervisor Authors"]
|
|
||||||
edition.workspace = true
|
|
||||||
name = "block"
|
|
||||||
version = "0.1.0"
|
|
||||||
|
|
||||||
[features]
|
|
||||||
default = []
|
|
||||||
io_uring = ["dep:io-uring"]
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
byteorder = { workspace = true }
|
|
||||||
crc-any = "2.5.0"
|
|
||||||
flate2 = "1.0"
|
|
||||||
io-uring = { version = "0.7.11", optional = true }
|
|
||||||
libc = { workspace = true }
|
|
||||||
log = { workspace = true }
|
|
||||||
remain = "0.2.15"
|
|
||||||
serde = { workspace = true, features = ["derive"] }
|
|
||||||
smallvec = "1.15.1"
|
|
||||||
thiserror = { workspace = true }
|
|
||||||
uuid = { workspace = true, features = ["v4"] }
|
|
||||||
virtio-bindings = { workspace = true }
|
|
||||||
virtio-queue = { workspace = true }
|
|
||||||
vm-memory = { workspace = true, features = [
|
|
||||||
"backend-atomic",
|
|
||||||
"backend-bitmap",
|
|
||||||
"backend-mmap",
|
|
||||||
] }
|
|
||||||
vm-virtio = { path = "../vm-virtio" }
|
|
||||||
vmm-sys-util = { workspace = true }
|
|
||||||
zstd = "0.13"
|
|
||||||
|
|
||||||
[lints]
|
|
||||||
workspace = true
|
|
||||||
@@ -1,128 +0,0 @@
|
|||||||
// Copyright © 2021 Intel Corporation
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
|
||||||
|
|
||||||
use std::marker::PhantomData;
|
|
||||||
use std::os::fd::{AsRawFd, OwnedFd, RawFd};
|
|
||||||
|
|
||||||
use thiserror::Error;
|
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
|
||||||
|
|
||||||
use crate::{BatchRequest, DiskTopology};
|
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
|
||||||
pub enum DiskFileError {
|
|
||||||
/// Failed getting disk file size.
|
|
||||||
#[error("Failed getting disk file size")]
|
|
||||||
Size(#[source] std::io::Error),
|
|
||||||
/// Failed creating a new AsyncIo.
|
|
||||||
#[error("Failed creating a new AsyncIo")]
|
|
||||||
NewAsyncIo(#[source] std::io::Error),
|
|
||||||
/// Unsupported operation.
|
|
||||||
#[error("Unsupported operation")]
|
|
||||||
Unsupported,
|
|
||||||
/// Resize failed
|
|
||||||
#[error("Resize failed")]
|
|
||||||
ResizeError(#[source] std::io::Error),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type DiskFileResult<T> = std::result::Result<T, DiskFileError>;
|
|
||||||
|
|
||||||
/// A wrapper for [`RawFd`] capturing the lifetime of a corresponding [`DiskFile`].
|
|
||||||
///
|
|
||||||
/// This fulfills the same role as [`BorrowedFd`] but is tailored to the limitations
|
|
||||||
/// by some implementations of [`DiskFile`], which wrap the effective [`File`]
|
|
||||||
/// in an `Arc<Mutex<T>>`, making the use of [`BorrowedFd`] impossible.
|
|
||||||
///
|
|
||||||
/// [`BorrowedFd`]: std::os::fd::BorrowedFd
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
|
||||||
pub struct BorrowedDiskFd<'fd> {
|
|
||||||
raw_fd: RawFd,
|
|
||||||
_lifetime: PhantomData<&'fd OwnedFd>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BorrowedDiskFd<'_> {
|
|
||||||
pub(super) fn new(raw_fd: RawFd) -> Self {
|
|
||||||
Self {
|
|
||||||
raw_fd,
|
|
||||||
_lifetime: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsRawFd for BorrowedDiskFd<'_> {
|
|
||||||
fn as_raw_fd(&self) -> RawFd {
|
|
||||||
self.raw_fd
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Abstraction over the effective [`File`] backing up a block device,
|
|
||||||
/// with support for synchronous and asynchronous I/O.
|
|
||||||
///
|
|
||||||
/// This allows abstracting over raw image formats as well as structured
|
|
||||||
/// image formats.
|
|
||||||
pub trait DiskFile: Send {
|
|
||||||
/// Returns the logical disk size a guest will see.
|
|
||||||
///
|
|
||||||
/// For raw formats, this is equal to [`Self::physical_size`]. For file formats
|
|
||||||
/// that wrap disk images in a container (e.g. QCOW2), this refers to the
|
|
||||||
/// effective size that the guest will see.
|
|
||||||
fn logical_size(&mut self) -> DiskFileResult<u64>;
|
|
||||||
/// Returns the physical size of the underlying file.
|
|
||||||
fn physical_size(&mut self) -> DiskFileResult<u64>;
|
|
||||||
fn new_async_io(&self, ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>>;
|
|
||||||
fn topology(&mut self) -> DiskTopology {
|
|
||||||
DiskTopology::default()
|
|
||||||
}
|
|
||||||
fn resize(&mut self, _size: u64) -> DiskFileResult<()> {
|
|
||||||
Err(DiskFileError::Unsupported)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the file descriptor of the underlying disk image file.
|
|
||||||
///
|
|
||||||
/// The file descriptor is supposed to be used for `fcntl()` calls but no
|
|
||||||
/// other operation.
|
|
||||||
fn fd(&mut self) -> BorrowedDiskFd<'_>;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
|
||||||
pub enum AsyncIoError {
|
|
||||||
/// Failed vectored reading from file.
|
|
||||||
#[error("Failed vectored reading from file")]
|
|
||||||
ReadVectored(#[source] std::io::Error),
|
|
||||||
/// Failed vectored writing to file.
|
|
||||||
#[error("Failed vectored writing to file")]
|
|
||||||
WriteVectored(#[source] std::io::Error),
|
|
||||||
/// Failed synchronizing file.
|
|
||||||
#[error("Failed synchronizing file")]
|
|
||||||
Fsync(#[source] std::io::Error),
|
|
||||||
/// Failed submitting batch requests.
|
|
||||||
#[error("Failed submitting batch requests")]
|
|
||||||
SubmitBatchRequests(#[source] std::io::Error),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type AsyncIoResult<T> = std::result::Result<T, AsyncIoError>;
|
|
||||||
|
|
||||||
pub trait AsyncIo: Send {
|
|
||||||
fn notifier(&self) -> &EventFd;
|
|
||||||
fn read_vectored(
|
|
||||||
&mut self,
|
|
||||||
offset: libc::off_t,
|
|
||||||
iovecs: &[libc::iovec],
|
|
||||||
user_data: u64,
|
|
||||||
) -> AsyncIoResult<()>;
|
|
||||||
fn write_vectored(
|
|
||||||
&mut self,
|
|
||||||
offset: libc::off_t,
|
|
||||||
iovecs: &[libc::iovec],
|
|
||||||
user_data: u64,
|
|
||||||
) -> AsyncIoResult<()>;
|
|
||||||
fn fsync(&mut self, user_data: Option<u64>) -> AsyncIoResult<()>;
|
|
||||||
fn next_completed_request(&mut self) -> Option<(u64, i32)>;
|
|
||||||
fn batch_requests_enabled(&self) -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
fn submit_batch_requests(&mut self, _batch_request: &[BatchRequest]) -> AsyncIoResult<()> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,222 +0,0 @@
|
|||||||
// Copyright © 2025 Cyberus Technology GmbH
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
//
|
|
||||||
|
|
||||||
//! Helpers for advisory file locking.
|
|
||||||
//!
|
|
||||||
//! Under the hood, the implementation uses OFD locks for the entire file,
|
|
||||||
//! as described in [[0]]. The advantage over `F_SETLKW` (currently used by
|
|
||||||
//! Rust std: `File::try_lock()`) is that only the very last `close()` on a
|
|
||||||
//! file descriptor releases the lock. This prevents mistakes and unexpected
|
|
||||||
//! behavior.
|
|
||||||
//!
|
|
||||||
//! [0]: <https://apenwarr.ca/log/20101213>.
|
|
||||||
|
|
||||||
use std::fmt::Debug;
|
|
||||||
use std::io;
|
|
||||||
use std::os::fd::{AsRawFd, RawFd};
|
|
||||||
|
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
/// Errors that can happen when working with file locks.
|
|
||||||
#[derive(Error, Debug)]
|
|
||||||
pub enum LockError {
|
|
||||||
/// The file is already locked.
|
|
||||||
///
|
|
||||||
/// A call to [`get_lock_state`] can help to identify the reason.
|
|
||||||
#[error("The file is already locked")]
|
|
||||||
AlreadyLocked,
|
|
||||||
/// IO error.
|
|
||||||
#[error("The lock state could not be checked or set")]
|
|
||||||
Io(#[source] io::Error),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Commands for use with [`fcntl`].
|
|
||||||
#[allow(non_camel_case_types)]
|
|
||||||
enum FcntlArg<'a> {
|
|
||||||
/// Set an OFD lock from the given lock description.
|
|
||||||
F_OFD_SETLK(&'a libc::flock),
|
|
||||||
/// Get the first OFD lock for the given lock description.
|
|
||||||
F_OFD_GETLK(&'a mut libc::flock),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Wrapper for [`libc::fcntl`] that properly sets the function arguments.
|
|
||||||
fn fcntl(fd: RawFd, arg: FcntlArg) -> libc::c_int {
|
|
||||||
// SAFETY: We use a valid FD.
|
|
||||||
unsafe {
|
|
||||||
match arg {
|
|
||||||
FcntlArg::F_OFD_SETLK(flock) => libc::fcntl(fd, libc::F_OFD_SETLK, flock),
|
|
||||||
FcntlArg::F_OFD_GETLK(flock) => libc::fcntl(fd, libc::F_OFD_GETLK, flock),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Describes the type of lock you want to set.
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
|
||||||
pub enum LockType {
|
|
||||||
/// Clear a lock.
|
|
||||||
Unlock,
|
|
||||||
/// Set a write lock (exclusive).
|
|
||||||
Write,
|
|
||||||
/// Set a read lock (shared).
|
|
||||||
Read,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LockType {
|
|
||||||
pub const fn to_libc_val(self) -> libc::c_int {
|
|
||||||
match self {
|
|
||||||
Self::Unlock => libc::F_UNLCK as libc::c_int,
|
|
||||||
Self::Write => libc::F_WRLCK as libc::c_int,
|
|
||||||
Self::Read => libc::F_RDLCK as libc::c_int,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Describes the current state of a lock.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum LockState {
|
|
||||||
/// No lock set.
|
|
||||||
Unlocked,
|
|
||||||
/// Locked for reading (non-exclusive).
|
|
||||||
SharedRead,
|
|
||||||
/// Locked for writing (exclusive mode).
|
|
||||||
ExclusiveWrite,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LockState {
|
|
||||||
fn new(value: libc::c_int) -> Self {
|
|
||||||
const F_UNLCK: libc::c_int = libc::F_UNLCK as libc::c_int;
|
|
||||||
const F_WRLCK: libc::c_int = libc::F_WRLCK as libc::c_int;
|
|
||||||
const F_RDLCK: libc::c_int = libc::F_RDLCK as libc::c_int;
|
|
||||||
match value {
|
|
||||||
F_UNLCK => Self::Unlocked,
|
|
||||||
F_WRLCK => Self::ExclusiveWrite,
|
|
||||||
F_RDLCK => Self::SharedRead,
|
|
||||||
// This is so unlikely that we want to avoid the complexity of
|
|
||||||
// coping with this error case. Can only fail if either Linux
|
|
||||||
// is broken or memory is messed up.
|
|
||||||
other => panic!("Unexpected lock state: {other}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The granularity of the advisory lock.
|
|
||||||
///
|
|
||||||
/// The granularity has significant implications in typical cloud deployments
|
|
||||||
/// with network storage. The Linux kernel will sync advisory locks to network
|
|
||||||
/// file systems, but these backends may have different policies and handle
|
|
||||||
/// locks differently. For example, Netapp speaks a NFS API but will treat
|
|
||||||
/// advisory OFD locks for the whole file as mandatory locks, whereas byte-range
|
|
||||||
/// locks for the whole file will remain advisory [0].
|
|
||||||
///
|
|
||||||
/// As it is a valid use case to prevent multiple CHV instances from accessing
|
|
||||||
/// the same disk but disk management software (e.g., Cinder in OpenStack)
|
|
||||||
/// should be able to snapshot disks while VMs are running, we need special
|
|
||||||
/// control over the lock granularity. Therefore, it is a valid use case to lock
|
|
||||||
/// the whole byte range of a disk image without technically locking the whole
|
|
||||||
/// file - to get the best of both worlds.
|
|
||||||
///
|
|
||||||
/// [0] https://kb.netapp.com/on-prem/ontap/da/NAS/NAS-KBs/How_is_Mandatory_Locking_supported_for_NFSv4_on_ONTAP_9
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
|
||||||
pub enum LockGranularity {
|
|
||||||
WholeFile,
|
|
||||||
ByteRange(u64 /* from, inclusive */, u64 /* len */),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LockGranularity {
|
|
||||||
const fn l_start(self) -> u64 {
|
|
||||||
match self {
|
|
||||||
LockGranularity::WholeFile => 0,
|
|
||||||
LockGranularity::ByteRange(start, _) => start,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const fn l_len(self) -> u64 {
|
|
||||||
match self {
|
|
||||||
LockGranularity::WholeFile => 0, /* EOF */
|
|
||||||
LockGranularity::ByteRange(_, len) => len,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a [`struct@libc::flock`] structure for the whole file.
|
|
||||||
const fn get_flock(lock_type: LockType, granularity: LockGranularity) -> libc::flock {
|
|
||||||
libc::flock {
|
|
||||||
l_type: lock_type.to_libc_val() as libc::c_short,
|
|
||||||
l_whence: libc::SEEK_SET as libc::c_short,
|
|
||||||
l_start: granularity.l_start() as libc::c_long,
|
|
||||||
l_len: granularity.l_len() as libc::c_long,
|
|
||||||
l_pid: 0, /* filled by callee */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Tries to acquire a lock using [`fcntl`] with respect to the given
|
|
||||||
/// parameters.
|
|
||||||
///
|
|
||||||
/// Please note that `fcntl()` OFD locks are **advisory locks**, which do not
|
|
||||||
/// prevent to `open()` a file if a lock is already placed.
|
|
||||||
///
|
|
||||||
/// # Parameters
|
|
||||||
/// - `file`: The file to acquire a lock for [`LockType`]. The file's state will
|
|
||||||
/// be logically mutated, but not technically.
|
|
||||||
/// - `lock_type`: The [`LockType`]
|
|
||||||
/// - `granularity`: The [`LockGranularity`].
|
|
||||||
pub fn try_acquire_lock<Fd: AsRawFd>(
|
|
||||||
file: &Fd,
|
|
||||||
lock_type: LockType,
|
|
||||||
granularity: LockGranularity,
|
|
||||||
) -> Result<(), LockError> {
|
|
||||||
let flock = get_flock(lock_type, granularity);
|
|
||||||
|
|
||||||
let res = fcntl(file.as_raw_fd(), FcntlArg::F_OFD_SETLK(&flock));
|
|
||||||
match res {
|
|
||||||
0 => Ok(()),
|
|
||||||
-1 => {
|
|
||||||
let io_error = io::Error::last_os_error();
|
|
||||||
let errno = io_error.raw_os_error().unwrap();
|
|
||||||
match errno {
|
|
||||||
// See man page for error code:
|
|
||||||
// <https://man7.org/linux/man-pages/man2/fcntl.2.html>
|
|
||||||
libc::EAGAIN | libc::EACCES => Err(LockError::AlreadyLocked),
|
|
||||||
_ => Err(LockError::Io(io_error)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val => panic!("Unexpected return value from fcntl(): {val}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Clears a lock.
|
|
||||||
///
|
|
||||||
/// # Parameters
|
|
||||||
/// - `file`: The file to clear all locks for [`LockType`].
|
|
||||||
/// - `granularity`: The [`LockGranularity`].
|
|
||||||
pub fn clear_lock<Fd: AsRawFd>(file: &Fd, granularity: LockGranularity) -> Result<(), LockError> {
|
|
||||||
try_acquire_lock(file, LockType::Unlock, granularity)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the current lock state using [`fcntl`] with respect to the given
|
|
||||||
/// parameters.
|
|
||||||
///
|
|
||||||
/// # Parameters
|
|
||||||
/// - `file`: The file for which to get the lock state.
|
|
||||||
/// - `granularity`: The [`LockGranularity`].
|
|
||||||
pub fn get_lock_state<Fd: AsRawFd>(
|
|
||||||
file: &Fd,
|
|
||||||
granularity: LockGranularity,
|
|
||||||
) -> Result<LockState, LockError> {
|
|
||||||
let mut flock = get_flock(LockType::Write, granularity);
|
|
||||||
let res = fcntl(file.as_raw_fd(), FcntlArg::F_OFD_GETLK(&mut flock));
|
|
||||||
match res {
|
|
||||||
0 => {
|
|
||||||
let state = flock.l_type as libc::c_int;
|
|
||||||
let state = LockState::new(state);
|
|
||||||
Ok(state)
|
|
||||||
}
|
|
||||||
-1 => {
|
|
||||||
let io_error = io::Error::last_os_error();
|
|
||||||
Err(LockError::Io(io_error))
|
|
||||||
}
|
|
||||||
val => panic!("Unexpected return value from fcntl(): {val}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
// Copyright © 2021 Intel Corporation
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
use std::fs::File;
|
|
||||||
use std::io::{Read, Seek, SeekFrom, Write};
|
|
||||||
use std::os::unix::io::{AsRawFd, RawFd};
|
|
||||||
|
|
||||||
use crate::BlockBackend;
|
|
||||||
use crate::vhd::VhdFooter;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct FixedVhd {
|
|
||||||
file: File,
|
|
||||||
size: u64,
|
|
||||||
position: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FixedVhd {
|
|
||||||
pub fn new(mut file: File) -> std::io::Result<Self> {
|
|
||||||
let footer = VhdFooter::new(&mut file)?;
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
file,
|
|
||||||
size: footer.current_size(),
|
|
||||||
position: 0,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsRawFd for FixedVhd {
|
|
||||||
fn as_raw_fd(&self) -> RawFd {
|
|
||||||
self.file.as_raw_fd()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Read for FixedVhd {
|
|
||||||
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
|
||||||
match self.file.read(buf) {
|
|
||||||
Ok(r) => {
|
|
||||||
self.position = self.position.checked_add(r.try_into().unwrap()).unwrap();
|
|
||||||
Ok(r)
|
|
||||||
}
|
|
||||||
Err(e) => Err(e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Write for FixedVhd {
|
|
||||||
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
|
||||||
match self.file.write(buf) {
|
|
||||||
Ok(r) => {
|
|
||||||
self.position = self.position.checked_add(r.try_into().unwrap()).unwrap();
|
|
||||||
Ok(r)
|
|
||||||
}
|
|
||||||
Err(e) => Err(e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn flush(&mut self) -> std::io::Result<()> {
|
|
||||||
self.file.sync_all()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Seek for FixedVhd {
|
|
||||||
fn seek(&mut self, newpos: SeekFrom) -> std::io::Result<u64> {
|
|
||||||
match self.file.seek(newpos) {
|
|
||||||
Ok(pos) => {
|
|
||||||
self.position = pos;
|
|
||||||
Ok(pos)
|
|
||||||
}
|
|
||||||
Err(e) => Err(e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BlockBackend for FixedVhd {
|
|
||||||
fn logical_size(&self) -> Result<u64, crate::Error> {
|
|
||||||
Ok(self.size)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the physical size of the underlying file.
|
|
||||||
fn physical_size(&self) -> Result<u64, crate::Error> {
|
|
||||||
self.file
|
|
||||||
.metadata()
|
|
||||||
.map(|m| m.len())
|
|
||||||
.map_err(crate::Error::GetFileMetadata)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Clone for FixedVhd {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
Self {
|
|
||||||
file: self.file.try_clone().expect("FixedVhd cloning failed"),
|
|
||||||
size: self.size,
|
|
||||||
position: self.position,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
// Copyright 2025 The Cloud Hypervisor Authors. All rights reserved.
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
|
||||||
pub enum Error {
|
|
||||||
#[error("Zlib decompress error")]
|
|
||||||
ZlibDecompress(#[source] flate2::DecompressError),
|
|
||||||
#[error("Zlib unexpected status: {0:?}")]
|
|
||||||
ZlibUnexpectedStatus(flate2::Status),
|
|
||||||
#[error("Zstd decompress error")]
|
|
||||||
ZstdDecompress(#[source] std::io::Error),
|
|
||||||
#[error("Zstd: failed to fill buffer")]
|
|
||||||
ZstdFillBuffer(#[source] std::io::Error),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, Error>;
|
|
||||||
|
|
||||||
/// Generic trait for decoding zlib/zstd formats
|
|
||||||
pub trait Decoder {
|
|
||||||
fn decode(&self, input: &[u8], output: &mut [u8]) -> Result<usize>;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct ZlibDecoder {}
|
|
||||||
|
|
||||||
impl Decoder for ZlibDecoder {
|
|
||||||
fn decode(&self, input: &[u8], output: &mut [u8]) -> Result<usize> {
|
|
||||||
use flate2::{Decompress, FlushDecompress, Status};
|
|
||||||
|
|
||||||
let mut decompressor = Decompress::new(false);
|
|
||||||
let status = decompressor
|
|
||||||
.decompress(input, output, FlushDecompress::Finish)
|
|
||||||
.map_err(Error::ZlibDecompress)?;
|
|
||||||
if status == Status::StreamEnd {
|
|
||||||
Ok(decompressor.total_out() as usize)
|
|
||||||
} else {
|
|
||||||
Err(Error::ZlibUnexpectedStatus(status))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct ZstdDecoder {}
|
|
||||||
|
|
||||||
impl Decoder for ZstdDecoder {
|
|
||||||
fn decode(&self, input: &[u8], output: &mut [u8]) -> Result<usize> {
|
|
||||||
use std::io::Read;
|
|
||||||
|
|
||||||
let mut decoder = zstd::stream::read::Decoder::new(input).map_err(Error::ZstdDecompress)?;
|
|
||||||
let decoded_size = decoder.read(output).map_err(Error::ZstdFillBuffer)?;
|
|
||||||
Ok(decoded_size)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_zlib_decode() {
|
|
||||||
let d = ZlibDecoder::default();
|
|
||||||
let valid_input = vec![99, 96, 100, 98, 6, 0];
|
|
||||||
let mut output1 = vec![0; 4];
|
|
||||||
d.decode(&valid_input, &mut output1).unwrap();
|
|
||||||
assert_eq!(&output1, b"\x00\x01\x02\x03");
|
|
||||||
|
|
||||||
let invalid_input = vec![1, 2, 3, 4];
|
|
||||||
let mut output2 = vec![0; 1024];
|
|
||||||
d.decode(&invalid_input, &mut output2).unwrap_err();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_zstd_decode() {
|
|
||||||
let d = ZstdDecoder::default();
|
|
||||||
let valid_input = vec![40, 181, 47, 253, 32, 2, 17, 0, 0, 1, 254];
|
|
||||||
let mut output1 = vec![0; 2];
|
|
||||||
d.decode(&valid_input, &mut output1).unwrap();
|
|
||||||
assert_eq!(&output1, b"\x01\xfe");
|
|
||||||
|
|
||||||
let invalid_input = vec![1, 2, 3, 4];
|
|
||||||
let mut output2 = vec![0; 1024];
|
|
||||||
d.decode(&invalid_input, &mut output2).unwrap_err();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,141 +0,0 @@
|
|||||||
// Copyright © 2021 Intel Corporation
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
|
||||||
|
|
||||||
use std::collections::VecDeque;
|
|
||||||
use std::fs::File;
|
|
||||||
use std::io::{Seek, SeekFrom};
|
|
||||||
use std::os::fd::AsRawFd;
|
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
|
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
|
||||||
|
|
||||||
use crate::async_io::{
|
|
||||||
AsyncIo, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult,
|
|
||||||
};
|
|
||||||
use crate::qcow::{QcowFile, RawFile, Result as QcowResult};
|
|
||||||
use crate::{AsyncAdaptor, BlockBackend};
|
|
||||||
|
|
||||||
pub struct QcowDiskSync {
|
|
||||||
// FIXME: The Mutex serializes all QCOW2 I/O operations across queues, which
|
|
||||||
// is necessary for correctness but eliminates any parallelism benefit from
|
|
||||||
// multiqueue. QcowFile has internal mutable state (L2 cache, refcounts, file
|
|
||||||
// position) that is not safe to share across threads via Clone.
|
|
||||||
//
|
|
||||||
// A proper fix would require restructuring QcowFile to separate metadata
|
|
||||||
// operations (which need synchronization) from data I/O (which could be
|
|
||||||
// parallelized with per queue file descriptors). See #7560 for details.
|
|
||||||
qcow_file: Arc<Mutex<QcowFile>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl QcowDiskSync {
|
|
||||||
pub fn new(file: File, direct_io: bool, backing_files: bool) -> QcowResult<Self> {
|
|
||||||
if backing_files {
|
|
||||||
Ok(QcowDiskSync {
|
|
||||||
qcow_file: Arc::new(Mutex::new(QcowFile::from(RawFile::new(file, direct_io))?)),
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
Ok(QcowDiskSync {
|
|
||||||
qcow_file: Arc::new(Mutex::new(QcowFile::from_with_nesting_depth(
|
|
||||||
RawFile::new(file, direct_io),
|
|
||||||
0,
|
|
||||||
)?)),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DiskFile for QcowDiskSync {
|
|
||||||
fn logical_size(&mut self) -> DiskFileResult<u64> {
|
|
||||||
self.qcow_file
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.seek(SeekFrom::End(0))
|
|
||||||
.map_err(DiskFileError::Size)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn physical_size(&mut self) -> DiskFileResult<u64> {
|
|
||||||
self.qcow_file.lock().unwrap().physical_size().map_err(|e| {
|
|
||||||
let io_inner = match e {
|
|
||||||
crate::Error::GetFileMetadata(e) => e,
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
DiskFileError::Size(io_inner)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new_async_io(&self, _ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
|
|
||||||
Ok(Box::new(QcowSync::new(Arc::clone(&self.qcow_file))) as Box<dyn AsyncIo>)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fd(&mut self) -> BorrowedDiskFd<'_> {
|
|
||||||
BorrowedDiskFd::new(self.qcow_file.lock().unwrap().as_raw_fd())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct QcowSync {
|
|
||||||
qcow_file: Arc<Mutex<QcowFile>>,
|
|
||||||
eventfd: EventFd,
|
|
||||||
completion_list: VecDeque<(u64, i32)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl QcowSync {
|
|
||||||
pub fn new(qcow_file: Arc<Mutex<QcowFile>>) -> Self {
|
|
||||||
QcowSync {
|
|
||||||
qcow_file,
|
|
||||||
eventfd: EventFd::new(libc::EFD_NONBLOCK)
|
|
||||||
.expect("Failed creating EventFd for QcowSync"),
|
|
||||||
completion_list: VecDeque::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsyncAdaptor for QcowFile {}
|
|
||||||
|
|
||||||
impl AsyncIo for QcowSync {
|
|
||||||
fn notifier(&self) -> &EventFd {
|
|
||||||
&self.eventfd
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_vectored(
|
|
||||||
&mut self,
|
|
||||||
offset: libc::off_t,
|
|
||||||
iovecs: &[libc::iovec],
|
|
||||||
user_data: u64,
|
|
||||||
) -> AsyncIoResult<()> {
|
|
||||||
self.qcow_file.lock().unwrap().read_vectored_sync(
|
|
||||||
offset,
|
|
||||||
iovecs,
|
|
||||||
user_data,
|
|
||||||
&self.eventfd,
|
|
||||||
&mut self.completion_list,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_vectored(
|
|
||||||
&mut self,
|
|
||||||
offset: libc::off_t,
|
|
||||||
iovecs: &[libc::iovec],
|
|
||||||
user_data: u64,
|
|
||||||
) -> AsyncIoResult<()> {
|
|
||||||
self.qcow_file.lock().unwrap().write_vectored_sync(
|
|
||||||
offset,
|
|
||||||
iovecs,
|
|
||||||
user_data,
|
|
||||||
&self.eventfd,
|
|
||||||
&mut self.completion_list,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fsync(&mut self, user_data: Option<u64>) -> AsyncIoResult<()> {
|
|
||||||
self.qcow_file.lock().unwrap().fsync_sync(
|
|
||||||
user_data,
|
|
||||||
&self.eventfd,
|
|
||||||
&mut self.completion_list,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn next_completed_request(&mut self) -> Option<(u64, i32)> {
|
|
||||||
self.completion_list.pop_front()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,164 +0,0 @@
|
|||||||
// Copyright © 2023 Intel Corporation
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
|
||||||
//
|
|
||||||
// Copyright © 2023 Crusoe Energy Systems LLC
|
|
||||||
//
|
|
||||||
|
|
||||||
use std::fs::File;
|
|
||||||
use std::io::{Seek, SeekFrom};
|
|
||||||
use std::os::unix::io::{AsRawFd, RawFd};
|
|
||||||
|
|
||||||
use log::warn;
|
|
||||||
use vmm_sys_util::aio;
|
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
|
||||||
|
|
||||||
use crate::DiskTopology;
|
|
||||||
use crate::async_io::{
|
|
||||||
AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct RawFileDiskAio {
|
|
||||||
file: File,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RawFileDiskAio {
|
|
||||||
pub fn new(file: File) -> Self {
|
|
||||||
RawFileDiskAio { file }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DiskFile for RawFileDiskAio {
|
|
||||||
fn logical_size(&mut self) -> DiskFileResult<u64> {
|
|
||||||
self.file
|
|
||||||
.seek(SeekFrom::End(0))
|
|
||||||
.map_err(DiskFileError::Size)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn physical_size(&mut self) -> DiskFileResult<u64> {
|
|
||||||
self.file
|
|
||||||
.metadata()
|
|
||||||
.map(|m| m.len())
|
|
||||||
.map_err(DiskFileError::Size)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new_async_io(&self, ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
|
|
||||||
Ok(Box::new(
|
|
||||||
RawFileAsyncAio::new(self.file.as_raw_fd(), ring_depth)
|
|
||||||
.map_err(DiskFileError::NewAsyncIo)?,
|
|
||||||
) as Box<dyn AsyncIo>)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn topology(&mut self) -> DiskTopology {
|
|
||||||
if let Ok(topology) = DiskTopology::probe(&self.file) {
|
|
||||||
topology
|
|
||||||
} else {
|
|
||||||
warn!("Unable to get device topology. Using default topology");
|
|
||||||
DiskTopology::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fd(&mut self) -> BorrowedDiskFd<'_> {
|
|
||||||
BorrowedDiskFd::new(self.file.as_raw_fd())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct RawFileAsyncAio {
|
|
||||||
fd: RawFd,
|
|
||||||
ctx: aio::IoContext,
|
|
||||||
eventfd: EventFd,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RawFileAsyncAio {
|
|
||||||
pub fn new(fd: RawFd, queue_depth: u32) -> std::io::Result<Self> {
|
|
||||||
let eventfd = EventFd::new(libc::EFD_NONBLOCK)?;
|
|
||||||
let ctx = aio::IoContext::new(queue_depth)?;
|
|
||||||
|
|
||||||
Ok(RawFileAsyncAio { fd, ctx, eventfd })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsyncIo for RawFileAsyncAio {
|
|
||||||
fn notifier(&self) -> &EventFd {
|
|
||||||
&self.eventfd
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_vectored(
|
|
||||||
&mut self,
|
|
||||||
offset: libc::off_t,
|
|
||||||
iovecs: &[libc::iovec],
|
|
||||||
user_data: u64,
|
|
||||||
) -> AsyncIoResult<()> {
|
|
||||||
let iocbs = [&mut aio::IoControlBlock {
|
|
||||||
aio_fildes: self.fd.as_raw_fd() as u32,
|
|
||||||
aio_lio_opcode: aio::IOCB_CMD_PREADV as u16,
|
|
||||||
aio_buf: iovecs.as_ptr() as u64,
|
|
||||||
aio_nbytes: iovecs.len() as u64,
|
|
||||||
aio_offset: offset,
|
|
||||||
aio_data: user_data,
|
|
||||||
aio_flags: aio::IOCB_FLAG_RESFD,
|
|
||||||
aio_resfd: self.eventfd.as_raw_fd() as u32,
|
|
||||||
..Default::default()
|
|
||||||
}];
|
|
||||||
let _ = self
|
|
||||||
.ctx
|
|
||||||
.submit(&iocbs[..])
|
|
||||||
.map_err(AsyncIoError::ReadVectored)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_vectored(
|
|
||||||
&mut self,
|
|
||||||
offset: libc::off_t,
|
|
||||||
iovecs: &[libc::iovec],
|
|
||||||
user_data: u64,
|
|
||||||
) -> AsyncIoResult<()> {
|
|
||||||
let iocbs = [&mut aio::IoControlBlock {
|
|
||||||
aio_fildes: self.fd.as_raw_fd() as u32,
|
|
||||||
aio_lio_opcode: aio::IOCB_CMD_PWRITEV as u16,
|
|
||||||
aio_buf: iovecs.as_ptr() as u64,
|
|
||||||
aio_nbytes: iovecs.len() as u64,
|
|
||||||
aio_offset: offset,
|
|
||||||
aio_data: user_data,
|
|
||||||
aio_flags: aio::IOCB_FLAG_RESFD,
|
|
||||||
aio_resfd: self.eventfd.as_raw_fd() as u32,
|
|
||||||
..Default::default()
|
|
||||||
}];
|
|
||||||
let _ = self
|
|
||||||
.ctx
|
|
||||||
.submit(&iocbs[..])
|
|
||||||
.map_err(AsyncIoError::WriteVectored)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fsync(&mut self, user_data: Option<u64>) -> AsyncIoResult<()> {
|
|
||||||
if let Some(user_data) = user_data {
|
|
||||||
let iocbs = [&mut aio::IoControlBlock {
|
|
||||||
aio_fildes: self.fd.as_raw_fd() as u32,
|
|
||||||
aio_lio_opcode: aio::IOCB_CMD_FSYNC as u16,
|
|
||||||
aio_data: user_data,
|
|
||||||
aio_flags: aio::IOCB_FLAG_RESFD,
|
|
||||||
aio_resfd: self.eventfd.as_raw_fd() as u32,
|
|
||||||
..Default::default()
|
|
||||||
}];
|
|
||||||
let _ = self.ctx.submit(&iocbs[..]).map_err(AsyncIoError::Fsync)?;
|
|
||||||
} else {
|
|
||||||
// SAFETY: FFI call with a valid fd
|
|
||||||
unsafe { libc::fsync(self.fd) };
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn next_completed_request(&mut self) -> Option<(u64, i32)> {
|
|
||||||
let mut events: [aio::IoEvent; 1] = [aio::IoEvent::default()];
|
|
||||||
let rc = self.ctx.get_events(0, &mut events, None).unwrap();
|
|
||||||
if rc == 0 {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some((events[0].data, events[0].res as i32))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,219 +0,0 @@
|
|||||||
// Copyright © 2021 Intel Corporation
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
use std::fs::File;
|
|
||||||
use std::io::{self, Read, Seek, SeekFrom, Write};
|
|
||||||
|
|
||||||
use remain::sorted;
|
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
use crate::vhdx::vhdx_bat::{self, BatEntry, VhdxBatError};
|
|
||||||
use crate::vhdx::vhdx_metadata::{self, DiskSpec};
|
|
||||||
|
|
||||||
const SECTOR_SIZE: u64 = 512;
|
|
||||||
|
|
||||||
#[sorted]
|
|
||||||
#[derive(Error, Debug)]
|
|
||||||
pub enum VhdxIoError {
|
|
||||||
#[error("Invalid BAT entry state")]
|
|
||||||
InvalidBatEntryState,
|
|
||||||
#[error("Invalid BAT entry count")]
|
|
||||||
InvalidBatIndex,
|
|
||||||
#[error("Invalid disk size")]
|
|
||||||
InvalidDiskSize,
|
|
||||||
#[error("Failed reading sector blocks from file {0}")]
|
|
||||||
ReadSectorBlock(#[source] io::Error),
|
|
||||||
#[error("Failed changing file length {0}")]
|
|
||||||
ResizeFile(#[source] io::Error),
|
|
||||||
#[error("Differencing mode is not supported yet")]
|
|
||||||
UnsupportedMode,
|
|
||||||
#[error("Failed writing BAT to file {0}")]
|
|
||||||
WriteBat(#[source] VhdxBatError),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, VhdxIoError>;
|
|
||||||
|
|
||||||
macro_rules! align {
|
|
||||||
($n:expr, $align:expr) => {{ $n.div_ceil($align) * $align }};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
struct Sector {
|
|
||||||
bat_index: u64,
|
|
||||||
free_sectors: u64,
|
|
||||||
free_bytes: u64,
|
|
||||||
file_offset: u64,
|
|
||||||
block_offset: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Sector {
|
|
||||||
/// Translate sector index and count of data in file to actual offsets and
|
|
||||||
/// BAT index.
|
|
||||||
pub fn new(
|
|
||||||
disk_spec: &DiskSpec,
|
|
||||||
bat: &[BatEntry],
|
|
||||||
sector_index: u64,
|
|
||||||
sector_count: u64,
|
|
||||||
) -> Result<Sector> {
|
|
||||||
let mut sector = Sector::default();
|
|
||||||
|
|
||||||
sector.bat_index = sector_index / disk_spec.sectors_per_block as u64;
|
|
||||||
sector.block_offset = sector_index % disk_spec.sectors_per_block as u64;
|
|
||||||
sector.free_sectors = disk_spec.sectors_per_block as u64 - sector.block_offset;
|
|
||||||
if sector.free_sectors > sector_count {
|
|
||||||
sector.free_sectors = sector_count;
|
|
||||||
}
|
|
||||||
|
|
||||||
sector.free_bytes = sector.free_sectors * disk_spec.logical_sector_size as u64;
|
|
||||||
sector.block_offset *= disk_spec.logical_sector_size as u64;
|
|
||||||
|
|
||||||
let bat_entry = match bat.get(sector.bat_index as usize) {
|
|
||||||
Some(entry) => entry.0,
|
|
||||||
None => {
|
|
||||||
return Err(VhdxIoError::InvalidBatIndex);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
sector.file_offset = bat_entry & vhdx_bat::BAT_FILE_OFF_MASK;
|
|
||||||
if sector.file_offset != 0 {
|
|
||||||
sector.file_offset += sector.block_offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(sector)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// VHDx IO read routine: requires relative sector index and count for the
|
|
||||||
/// requested data.
|
|
||||||
pub fn read(
|
|
||||||
f: &mut File,
|
|
||||||
buf: &mut [u8],
|
|
||||||
disk_spec: &DiskSpec,
|
|
||||||
bat: &[BatEntry],
|
|
||||||
mut sector_index: u64,
|
|
||||||
mut sector_count: u64,
|
|
||||||
) -> Result<usize> {
|
|
||||||
if disk_spec.has_parent {
|
|
||||||
return Err(VhdxIoError::UnsupportedMode);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut read_count: usize = 0;
|
|
||||||
while sector_count > 0 {
|
|
||||||
let sector = Sector::new(disk_spec, bat, sector_index, sector_count)?;
|
|
||||||
|
|
||||||
let bat_entry = match bat.get(sector.bat_index as usize) {
|
|
||||||
Some(entry) => entry.0,
|
|
||||||
None => {
|
|
||||||
return Err(VhdxIoError::InvalidBatIndex);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
match bat_entry & vhdx_bat::BAT_STATE_BIT_MASK {
|
|
||||||
vhdx_bat::PAYLOAD_BLOCK_NOT_PRESENT
|
|
||||||
| vhdx_bat::PAYLOAD_BLOCK_UNDEFINED
|
|
||||||
| vhdx_bat::PAYLOAD_BLOCK_UNMAPPED
|
|
||||||
| vhdx_bat::PAYLOAD_BLOCK_ZERO => {}
|
|
||||||
vhdx_bat::PAYLOAD_BLOCK_FULLY_PRESENT => {
|
|
||||||
f.seek(SeekFrom::Start(sector.file_offset))
|
|
||||||
.map_err(VhdxIoError::ReadSectorBlock)?;
|
|
||||||
f.read_exact(
|
|
||||||
&mut buf
|
|
||||||
[read_count..(read_count + (sector.free_sectors * SECTOR_SIZE) as usize)],
|
|
||||||
)
|
|
||||||
.map_err(VhdxIoError::ReadSectorBlock)?;
|
|
||||||
}
|
|
||||||
vhdx_bat::PAYLOAD_BLOCK_PARTIALLY_PRESENT => {
|
|
||||||
return Err(VhdxIoError::UnsupportedMode);
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
return Err(VhdxIoError::InvalidBatEntryState);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sector_count -= sector.free_sectors;
|
|
||||||
sector_index += sector.free_sectors;
|
|
||||||
read_count += sector.free_bytes as usize;
|
|
||||||
}
|
|
||||||
Ok(read_count)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// VHDx IO write routine: requires relative sector index and count for the
|
|
||||||
/// requested data.
|
|
||||||
pub fn write(
|
|
||||||
f: &mut File,
|
|
||||||
buf: &[u8],
|
|
||||||
disk_spec: &mut DiskSpec,
|
|
||||||
bat_offset: u64,
|
|
||||||
bat: &mut [BatEntry],
|
|
||||||
mut sector_index: u64,
|
|
||||||
mut sector_count: u64,
|
|
||||||
) -> Result<usize> {
|
|
||||||
if disk_spec.has_parent {
|
|
||||||
return Err(VhdxIoError::UnsupportedMode);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut write_count: usize = 0;
|
|
||||||
while sector_count > 0 {
|
|
||||||
let sector = Sector::new(disk_spec, bat, sector_index, sector_count)?;
|
|
||||||
|
|
||||||
let bat_entry = match bat.get(sector.bat_index as usize) {
|
|
||||||
Some(entry) => entry.0,
|
|
||||||
None => {
|
|
||||||
return Err(VhdxIoError::InvalidBatIndex);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
match bat_entry & vhdx_bat::BAT_STATE_BIT_MASK {
|
|
||||||
vhdx_bat::PAYLOAD_BLOCK_NOT_PRESENT
|
|
||||||
| vhdx_bat::PAYLOAD_BLOCK_UNDEFINED
|
|
||||||
| vhdx_bat::PAYLOAD_BLOCK_UNMAPPED
|
|
||||||
| vhdx_bat::PAYLOAD_BLOCK_ZERO => {
|
|
||||||
let file_offset =
|
|
||||||
align!(disk_spec.image_size, vhdx_metadata::BLOCK_SIZE_MIN as u64);
|
|
||||||
let new_size = file_offset
|
|
||||||
.checked_add(disk_spec.block_size as u64)
|
|
||||||
.ok_or(VhdxIoError::InvalidDiskSize)?;
|
|
||||||
|
|
||||||
f.set_len(new_size).map_err(VhdxIoError::ResizeFile)?;
|
|
||||||
disk_spec.image_size = new_size;
|
|
||||||
|
|
||||||
let new_bat_entry = file_offset
|
|
||||||
| (vhdx_bat::PAYLOAD_BLOCK_FULLY_PRESENT & vhdx_bat::BAT_STATE_BIT_MASK);
|
|
||||||
bat[sector.bat_index as usize] = BatEntry(new_bat_entry);
|
|
||||||
BatEntry::write_bat_entries(f, bat_offset, bat).map_err(VhdxIoError::WriteBat)?;
|
|
||||||
|
|
||||||
if file_offset < vhdx_metadata::BLOCK_SIZE_MIN as u64 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
f.seek(SeekFrom::Start(file_offset))
|
|
||||||
.map_err(VhdxIoError::ReadSectorBlock)?;
|
|
||||||
f.write_all(
|
|
||||||
&buf[write_count..(write_count + (sector.free_sectors * SECTOR_SIZE) as usize)],
|
|
||||||
)
|
|
||||||
.map_err(VhdxIoError::ReadSectorBlock)?;
|
|
||||||
}
|
|
||||||
vhdx_bat::PAYLOAD_BLOCK_FULLY_PRESENT => {
|
|
||||||
if sector.file_offset < vhdx_metadata::BLOCK_SIZE_MIN as u64 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
f.seek(SeekFrom::Start(sector.file_offset))
|
|
||||||
.map_err(VhdxIoError::ReadSectorBlock)?;
|
|
||||||
f.write_all(
|
|
||||||
&buf[write_count..(write_count + (sector.free_sectors * SECTOR_SIZE) as usize)],
|
|
||||||
)
|
|
||||||
.map_err(VhdxIoError::ReadSectorBlock)?;
|
|
||||||
}
|
|
||||||
vhdx_bat::PAYLOAD_BLOCK_PARTIALLY_PRESENT => {
|
|
||||||
return Err(VhdxIoError::UnsupportedMode);
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
return Err(VhdxIoError::InvalidBatEntryState);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sector_count -= sector.free_sectors;
|
|
||||||
sector_index += sector.free_sectors;
|
|
||||||
write_count += sector.free_bytes as usize;
|
|
||||||
}
|
|
||||||
Ok(write_count)
|
|
||||||
}
|
|
||||||
25
block_util/Cargo.toml
Normal file
25
block_util/Cargo.toml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
[package]
|
||||||
|
name = "block_util"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["The Cloud Hypervisor Authors"]
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = []
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
io-uring = "0.5.12"
|
||||||
|
libc = "0.2.139"
|
||||||
|
log = "0.4.17"
|
||||||
|
qcow = { path = "../qcow" }
|
||||||
|
smallvec = "1.10.0"
|
||||||
|
thiserror = "1.0.38"
|
||||||
|
versionize = "0.1.9"
|
||||||
|
versionize_derive = "0.1.4"
|
||||||
|
vhdx = { path = "../vhdx" }
|
||||||
|
virtio-bindings = { version = "0.2.0", features = ["virtio-v5_0_0"] }
|
||||||
|
virtio-queue = "0.7.0"
|
||||||
|
vm-memory = { version = "0.10.0", features = ["backend-mmap", "backend-atomic", "backend-bitmap"] }
|
||||||
|
vm-virtio = { path = "../vm-virtio" }
|
||||||
|
vmm-sys-util = "0.11.0"
|
||||||
|
|
||||||
145
block_util/src/async_io.rs
Normal file
145
block_util/src/async_io.rs
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
// Copyright © 2021 Intel Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||||
|
|
||||||
|
use libc::{ioctl, S_IFBLK, S_IFMT};
|
||||||
|
use std::fs::File;
|
||||||
|
use std::os::unix::io::AsRawFd;
|
||||||
|
use thiserror::Error;
|
||||||
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
use vmm_sys_util::{ioctl_io_nr, ioctl_ioc_nr};
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum DiskFileError {
|
||||||
|
/// Failed getting disk file size.
|
||||||
|
#[error("Failed getting disk file size: {0}")]
|
||||||
|
Size(#[source] std::io::Error),
|
||||||
|
/// Failed creating a new AsyncIo.
|
||||||
|
#[error("Failed creating a new AsyncIo: {0}")]
|
||||||
|
NewAsyncIo(#[source] std::io::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct DiskTopology {
|
||||||
|
pub logical_block_size: u64,
|
||||||
|
pub physical_block_size: u64,
|
||||||
|
pub minimum_io_size: u64,
|
||||||
|
pub optimal_io_size: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for DiskTopology {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
logical_block_size: 512,
|
||||||
|
physical_block_size: 512,
|
||||||
|
minimum_io_size: 512,
|
||||||
|
optimal_io_size: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ioctl_io_nr!(BLKSSZGET, 0x12, 104);
|
||||||
|
ioctl_io_nr!(BLKPBSZGET, 0x12, 123);
|
||||||
|
ioctl_io_nr!(BLKIOMIN, 0x12, 120);
|
||||||
|
ioctl_io_nr!(BLKIOOPT, 0x12, 121);
|
||||||
|
|
||||||
|
enum BlockSize {
|
||||||
|
LogicalBlock,
|
||||||
|
PhysicalBlock,
|
||||||
|
MinimumIo,
|
||||||
|
OptimalIo,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DiskTopology {
|
||||||
|
fn is_block_device(f: &mut File) -> std::io::Result<bool> {
|
||||||
|
let mut stat = std::mem::MaybeUninit::<libc::stat>::uninit();
|
||||||
|
// SAFETY: FFI call with a valid fd and buffer
|
||||||
|
let ret = unsafe { libc::fstat(f.as_raw_fd(), stat.as_mut_ptr()) };
|
||||||
|
if ret != 0 {
|
||||||
|
return Err(std::io::Error::last_os_error());
|
||||||
|
}
|
||||||
|
|
||||||
|
// SAFETY: stat is valid at this point
|
||||||
|
let is_block = unsafe { (*stat.as_ptr()).st_mode & S_IFMT == S_IFBLK };
|
||||||
|
Ok(is_block)
|
||||||
|
}
|
||||||
|
|
||||||
|
// libc::ioctl() takes different types on different architectures
|
||||||
|
fn query_block_size(f: &mut File, block_size_type: BlockSize) -> std::io::Result<u64> {
|
||||||
|
let mut block_size = 0;
|
||||||
|
// SAFETY: FFI call with correct arguments
|
||||||
|
let ret = unsafe {
|
||||||
|
ioctl(
|
||||||
|
f.as_raw_fd(),
|
||||||
|
match block_size_type {
|
||||||
|
BlockSize::LogicalBlock => BLKSSZGET(),
|
||||||
|
BlockSize::PhysicalBlock => BLKPBSZGET(),
|
||||||
|
BlockSize::MinimumIo => BLKIOMIN(),
|
||||||
|
BlockSize::OptimalIo => BLKIOOPT(),
|
||||||
|
} as _,
|
||||||
|
&mut block_size,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
if ret != 0 {
|
||||||
|
return Err(std::io::Error::last_os_error());
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(block_size)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn probe(f: &mut File) -> std::io::Result<Self> {
|
||||||
|
if !Self::is_block_device(f)? {
|
||||||
|
return Ok(DiskTopology::default());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(DiskTopology {
|
||||||
|
logical_block_size: Self::query_block_size(f, BlockSize::LogicalBlock)?,
|
||||||
|
physical_block_size: Self::query_block_size(f, BlockSize::PhysicalBlock)?,
|
||||||
|
minimum_io_size: Self::query_block_size(f, BlockSize::MinimumIo)?,
|
||||||
|
optimal_io_size: Self::query_block_size(f, BlockSize::OptimalIo)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type DiskFileResult<T> = std::result::Result<T, DiskFileError>;
|
||||||
|
|
||||||
|
pub trait DiskFile: Send {
|
||||||
|
fn size(&mut self) -> DiskFileResult<u64>;
|
||||||
|
fn new_async_io(&self, ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>>;
|
||||||
|
fn topology(&mut self) -> DiskTopology {
|
||||||
|
DiskTopology::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum AsyncIoError {
|
||||||
|
/// Failed vectored reading from file.
|
||||||
|
#[error("Failed vectored reading from file: {0}")]
|
||||||
|
ReadVectored(#[source] std::io::Error),
|
||||||
|
/// Failed vectored writing to file.
|
||||||
|
#[error("Failed vectored writing to file: {0}")]
|
||||||
|
WriteVectored(#[source] std::io::Error),
|
||||||
|
/// Failed synchronizing file.
|
||||||
|
#[error("Failed synchronizing file: {0}")]
|
||||||
|
Fsync(#[source] std::io::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type AsyncIoResult<T> = std::result::Result<T, AsyncIoError>;
|
||||||
|
|
||||||
|
pub trait AsyncIo: Send {
|
||||||
|
fn notifier(&self) -> &EventFd;
|
||||||
|
fn read_vectored(
|
||||||
|
&mut self,
|
||||||
|
offset: libc::off_t,
|
||||||
|
iovecs: &[libc::iovec],
|
||||||
|
user_data: u64,
|
||||||
|
) -> AsyncIoResult<()>;
|
||||||
|
fn write_vectored(
|
||||||
|
&mut self,
|
||||||
|
offset: libc::off_t,
|
||||||
|
iovecs: &[libc::iovec],
|
||||||
|
user_data: u64,
|
||||||
|
) -> AsyncIoResult<()>;
|
||||||
|
fn fsync(&mut self, user_data: Option<u64>) -> AsyncIoResult<()>;
|
||||||
|
fn next_completed_request(&mut self) -> Option<(u64, i32)>;
|
||||||
|
}
|
||||||
@@ -2,49 +2,42 @@
|
|||||||
//
|
//
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use crate::async_io::{
|
||||||
|
AsyncIo, AsyncIoError, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult,
|
||||||
|
};
|
||||||
|
use crate::raw_async::RawFileAsync;
|
||||||
|
use crate::vhd::VhdFooter;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::os::unix::io::{AsRawFd, RawFd};
|
use std::os::unix::io::{AsRawFd, RawFd};
|
||||||
|
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
use crate::async_io::{
|
pub struct FixedVhdDiskAsync {
|
||||||
AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult,
|
file: File,
|
||||||
};
|
size: u64,
|
||||||
use crate::fixed_vhd::FixedVhd;
|
}
|
||||||
use crate::raw_async::RawFileAsync;
|
|
||||||
use crate::{BatchRequest, BlockBackend};
|
|
||||||
|
|
||||||
pub struct FixedVhdDiskAsync(FixedVhd);
|
|
||||||
|
|
||||||
impl FixedVhdDiskAsync {
|
impl FixedVhdDiskAsync {
|
||||||
pub fn new(file: File) -> std::io::Result<Self> {
|
pub fn new(mut file: File) -> std::io::Result<Self> {
|
||||||
Ok(Self(FixedVhd::new(file)?))
|
let footer = VhdFooter::new(&mut file)?;
|
||||||
|
|
||||||
|
Ok(FixedVhdDiskAsync {
|
||||||
|
file,
|
||||||
|
size: footer.current_size(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DiskFile for FixedVhdDiskAsync {
|
impl DiskFile for FixedVhdDiskAsync {
|
||||||
fn logical_size(&mut self) -> DiskFileResult<u64> {
|
fn size(&mut self) -> DiskFileResult<u64> {
|
||||||
Ok(self.0.logical_size().unwrap())
|
Ok(self.size)
|
||||||
}
|
|
||||||
|
|
||||||
fn physical_size(&mut self) -> DiskFileResult<u64> {
|
|
||||||
Ok(self.0.physical_size().unwrap())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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(
|
FixedVhdAsync::new(self.file.as_raw_fd(), ring_depth, self.size)
|
||||||
self.0.as_raw_fd(),
|
.map_err(DiskFileError::NewAsyncIo)?,
|
||||||
ring_depth,
|
|
||||||
self.0.logical_size().unwrap(),
|
|
||||||
)
|
|
||||||
.map_err(DiskFileError::NewAsyncIo)?,
|
|
||||||
) as Box<dyn AsyncIo>)
|
) as Box<dyn AsyncIo>)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fd(&mut self) -> BorrowedDiskFd<'_> {
|
|
||||||
BorrowedDiskFd::new(self.0.as_raw_fd())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct FixedVhdAsync {
|
pub struct FixedVhdAsync {
|
||||||
@@ -114,12 +107,4 @@ impl AsyncIo for FixedVhdAsync {
|
|||||||
fn next_completed_request(&mut self) -> Option<(u64, i32)> {
|
fn next_completed_request(&mut self) -> Option<(u64, i32)> {
|
||||||
self.raw_file_async.next_completed_request()
|
self.raw_file_async.next_completed_request()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn batch_requests_enabled(&self) -> bool {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
fn submit_batch_requests(&mut self, batch_request: &[BatchRequest]) -> AsyncIoResult<()> {
|
|
||||||
self.raw_file_async.submit_batch_requests(batch_request)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -2,51 +2,42 @@
|
|||||||
//
|
//
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use crate::async_io::{
|
||||||
|
AsyncIo, AsyncIoError, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult,
|
||||||
|
};
|
||||||
|
use crate::raw_sync::RawFileSync;
|
||||||
|
use crate::vhd::VhdFooter;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::os::unix::io::{AsRawFd, RawFd};
|
use std::os::unix::io::{AsRawFd, RawFd};
|
||||||
|
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
use crate::BlockBackend;
|
pub struct FixedVhdDiskSync {
|
||||||
use crate::async_io::{
|
file: File,
|
||||||
AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult,
|
size: u64,
|
||||||
};
|
}
|
||||||
use crate::fixed_vhd::FixedVhd;
|
|
||||||
use crate::raw_sync::RawFileSync;
|
|
||||||
|
|
||||||
pub struct FixedVhdDiskSync(FixedVhd);
|
|
||||||
|
|
||||||
impl FixedVhdDiskSync {
|
impl FixedVhdDiskSync {
|
||||||
pub fn new(file: File) -> std::io::Result<Self> {
|
pub fn new(mut file: File) -> std::io::Result<Self> {
|
||||||
Ok(Self(FixedVhd::new(file)?))
|
let footer = VhdFooter::new(&mut file)?;
|
||||||
|
|
||||||
|
Ok(FixedVhdDiskSync {
|
||||||
|
file,
|
||||||
|
size: footer.current_size(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DiskFile for FixedVhdDiskSync {
|
impl DiskFile for FixedVhdDiskSync {
|
||||||
fn logical_size(&mut self) -> DiskFileResult<u64> {
|
fn size(&mut self) -> DiskFileResult<u64> {
|
||||||
Ok(self.0.logical_size().unwrap())
|
Ok(self.size)
|
||||||
}
|
|
||||||
|
|
||||||
fn physical_size(&mut self) -> DiskFileResult<u64> {
|
|
||||||
self.0.physical_size().map_err(|e| {
|
|
||||||
let io_inner = match e {
|
|
||||||
crate::Error::GetFileMetadata(e) => e,
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
DiskFileError::Size(io_inner)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn 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.logical_size().unwrap())
|
FixedVhdSync::new(self.file.as_raw_fd(), self.size)
|
||||||
.map_err(DiskFileError::NewAsyncIo)?,
|
.map_err(DiskFileError::NewAsyncIo)?,
|
||||||
) as Box<dyn AsyncIo>)
|
) as Box<dyn AsyncIo>)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fd(&mut self) -> BorrowedDiskFd<'_> {
|
|
||||||
BorrowedDiskFd::new(self.0.as_raw_fd())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct FixedVhdSync {
|
pub struct FixedVhdSync {
|
||||||
File diff suppressed because it is too large
Load Diff
104
block_util/src/qcow_sync.rs
Normal file
104
block_util/src/qcow_sync.rs
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
// Copyright © 2021 Intel Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||||
|
|
||||||
|
use crate::async_io::{AsyncIo, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult};
|
||||||
|
use crate::AsyncAdaptor;
|
||||||
|
use qcow::{QcowFile, RawFile, Result as QcowResult};
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::{Seek, SeekFrom};
|
||||||
|
use std::sync::{Arc, Mutex, MutexGuard};
|
||||||
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
|
pub struct QcowDiskSync {
|
||||||
|
qcow_file: Arc<Mutex<QcowFile>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl QcowDiskSync {
|
||||||
|
pub fn new(file: File, direct_io: bool) -> QcowResult<Self> {
|
||||||
|
Ok(QcowDiskSync {
|
||||||
|
qcow_file: Arc::new(Mutex::new(QcowFile::from(RawFile::new(file, direct_io))?)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DiskFile for QcowDiskSync {
|
||||||
|
fn size(&mut self) -> DiskFileResult<u64> {
|
||||||
|
let mut file = self.qcow_file.lock().unwrap();
|
||||||
|
|
||||||
|
file.seek(SeekFrom::End(0)).map_err(DiskFileError::Size)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_async_io(&self, _ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
|
||||||
|
Ok(Box::new(QcowSync::new(self.qcow_file.clone())) as Box<dyn AsyncIo>)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct QcowSync {
|
||||||
|
qcow_file: Arc<Mutex<QcowFile>>,
|
||||||
|
eventfd: EventFd,
|
||||||
|
completion_list: VecDeque<(u64, i32)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl QcowSync {
|
||||||
|
pub fn new(qcow_file: Arc<Mutex<QcowFile>>) -> Self {
|
||||||
|
QcowSync {
|
||||||
|
qcow_file,
|
||||||
|
eventfd: EventFd::new(libc::EFD_NONBLOCK)
|
||||||
|
.expect("Failed creating EventFd for QcowSync"),
|
||||||
|
completion_list: VecDeque::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsyncAdaptor<QcowFile> for Arc<Mutex<QcowFile>> {
|
||||||
|
fn file(&mut self) -> MutexGuard<QcowFile> {
|
||||||
|
self.lock().unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsyncIo for QcowSync {
|
||||||
|
fn notifier(&self) -> &EventFd {
|
||||||
|
&self.eventfd
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_vectored(
|
||||||
|
&mut self,
|
||||||
|
offset: libc::off_t,
|
||||||
|
iovecs: &[libc::iovec],
|
||||||
|
user_data: u64,
|
||||||
|
) -> AsyncIoResult<()> {
|
||||||
|
self.qcow_file.read_vectored_sync(
|
||||||
|
offset,
|
||||||
|
iovecs,
|
||||||
|
user_data,
|
||||||
|
&self.eventfd,
|
||||||
|
&mut self.completion_list,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_vectored(
|
||||||
|
&mut self,
|
||||||
|
offset: libc::off_t,
|
||||||
|
iovecs: &[libc::iovec],
|
||||||
|
user_data: u64,
|
||||||
|
) -> AsyncIoResult<()> {
|
||||||
|
self.qcow_file.write_vectored_sync(
|
||||||
|
offset,
|
||||||
|
iovecs,
|
||||||
|
user_data,
|
||||||
|
&self.eventfd,
|
||||||
|
&mut self.completion_list,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fsync(&mut self, user_data: Option<u64>) -> AsyncIoResult<()> {
|
||||||
|
self.qcow_file
|
||||||
|
.fsync_sync(user_data, &self.eventfd, &mut self.completion_list)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next_completed_request(&mut self) -> Option<(u64, i32)> {
|
||||||
|
self.completion_list.pop_front()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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::fs::File;
|
|
||||||
use std::io::{Error, Seek, SeekFrom};
|
|
||||||
use std::os::unix::io::{AsRawFd, RawFd};
|
|
||||||
|
|
||||||
use io_uring::{IoUring, opcode, types};
|
|
||||||
use log::warn;
|
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
|
||||||
|
|
||||||
use crate::async_io::{
|
use crate::async_io::{
|
||||||
AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult,
|
AsyncIo, AsyncIoError, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult, DiskTopology,
|
||||||
};
|
};
|
||||||
use crate::{BatchRequest, DiskTopology, RequestType};
|
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,19 +22,12 @@ impl RawFileDisk {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl DiskFile for RawFileDisk {
|
impl DiskFile for RawFileDisk {
|
||||||
fn logical_size(&mut self) -> DiskFileResult<u64> {
|
fn size(&mut self) -> DiskFileResult<u64> {
|
||||||
self.file
|
self.file
|
||||||
.seek(SeekFrom::End(0))
|
.seek(SeekFrom::End(0))
|
||||||
.map_err(DiskFileError::Size)
|
.map_err(DiskFileError::Size)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn physical_size(&mut self) -> DiskFileResult<u64> {
|
|
||||||
self.file
|
|
||||||
.metadata()
|
|
||||||
.map(|m| m.len())
|
|
||||||
.map_err(DiskFileError::Size)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new_async_io(&self, ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
|
fn new_async_io(&self, ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
|
||||||
Ok(Box::new(
|
Ok(Box::new(
|
||||||
RawFileAsync::new(self.file.as_raw_fd(), ring_depth)
|
RawFileAsync::new(self.file.as_raw_fd(), ring_depth)
|
||||||
@@ -47,21 +36,13 @@ impl DiskFile for RawFileDisk {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn topology(&mut self) -> DiskTopology {
|
fn topology(&mut self) -> DiskTopology {
|
||||||
if let Ok(topology) = DiskTopology::probe(&self.file) {
|
if let Ok(topology) = DiskTopology::probe(&mut self.file) {
|
||||||
topology
|
topology
|
||||||
} else {
|
} else {
|
||||||
warn!("Unable to get device topology. Using default topology");
|
warn!("Unable to get device topology. Using default topology");
|
||||||
DiskTopology::default()
|
DiskTopology::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resize(&mut self, size: u64) -> DiskFileResult<()> {
|
|
||||||
self.file.set_len(size).map_err(DiskFileError::ResizeError)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fd(&mut self) -> BorrowedDiskFd<'_> {
|
|
||||||
BorrowedDiskFd::new(self.file.as_raw_fd())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RawFileAsync {
|
pub struct RawFileAsync {
|
||||||
@@ -102,14 +83,14 @@ impl AsyncIo for RawFileAsync {
|
|||||||
|
|
||||||
// SAFETY: we know the file descriptor is valid and we
|
// SAFETY: we know the file descriptor is valid and we
|
||||||
// relied on vm-memory to provide the buffer address.
|
// relied on vm-memory to provide the buffer address.
|
||||||
unsafe {
|
let _ = unsafe {
|
||||||
sq.push(
|
sq.push(
|
||||||
&opcode::Readv::new(types::Fd(self.fd), iovecs.as_ptr(), iovecs.len() as u32)
|
&opcode::Readv::new(types::Fd(self.fd), iovecs.as_ptr(), iovecs.len() as u32)
|
||||||
.offset(offset.try_into().unwrap())
|
.offset(offset)
|
||||||
.build()
|
.build()
|
||||||
|
.flags(squeue::Flags::ASYNC)
|
||||||
.user_data(user_data),
|
.user_data(user_data),
|
||||||
)
|
)
|
||||||
.map_err(|_| AsyncIoError::ReadVectored(Error::other("Submission queue is full")))?;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Update the submission queue and submit new operations to the
|
// Update the submission queue and submit new operations to the
|
||||||
@@ -130,14 +111,14 @@ impl AsyncIo for RawFileAsync {
|
|||||||
|
|
||||||
// SAFETY: we know the file descriptor is valid and we
|
// SAFETY: we know the file descriptor is valid and we
|
||||||
// relied on vm-memory to provide the buffer address.
|
// relied on vm-memory to provide the buffer address.
|
||||||
unsafe {
|
let _ = unsafe {
|
||||||
sq.push(
|
sq.push(
|
||||||
&opcode::Writev::new(types::Fd(self.fd), iovecs.as_ptr(), iovecs.len() as u32)
|
&opcode::Writev::new(types::Fd(self.fd), iovecs.as_ptr(), iovecs.len() as u32)
|
||||||
.offset(offset.try_into().unwrap())
|
.offset(offset)
|
||||||
.build()
|
.build()
|
||||||
|
.flags(squeue::Flags::ASYNC)
|
||||||
.user_data(user_data),
|
.user_data(user_data),
|
||||||
)
|
)
|
||||||
.map_err(|_| AsyncIoError::WriteVectored(Error::other("Submission queue is full")))?;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Update the submission queue and submit new operations to the
|
// Update the submission queue and submit new operations to the
|
||||||
@@ -153,13 +134,13 @@ impl AsyncIo for RawFileAsync {
|
|||||||
let (submitter, mut sq, _) = self.io_uring.split();
|
let (submitter, mut sq, _) = self.io_uring.split();
|
||||||
|
|
||||||
// SAFETY: we know the file descriptor is valid.
|
// SAFETY: we know the file descriptor is valid.
|
||||||
unsafe {
|
let _ = unsafe {
|
||||||
sq.push(
|
sq.push(
|
||||||
&opcode::Fsync::new(types::Fd(self.fd))
|
&opcode::Fsync::new(types::Fd(self.fd))
|
||||||
.build()
|
.build()
|
||||||
|
.flags(squeue::Flags::ASYNC)
|
||||||
.user_data(user_data),
|
.user_data(user_data),
|
||||||
)
|
)
|
||||||
.map_err(|_| AsyncIoError::Fsync(Error::other("Submission queue is full")))?;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Update the submission queue and submit new operations to the
|
// Update the submission queue and submit new operations to the
|
||||||
@@ -180,77 +161,4 @@ impl AsyncIo for RawFileAsync {
|
|||||||
.next()
|
.next()
|
||||||
.map(|entry| (entry.user_data(), entry.result()))
|
.map(|entry| (entry.user_data(), entry.result()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn batch_requests_enabled(&self) -> bool {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
fn submit_batch_requests(&mut self, batch_request: &[BatchRequest]) -> AsyncIoResult<()> {
|
|
||||||
if !self.batch_requests_enabled() {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
let (submitter, mut sq, _) = self.io_uring.split();
|
|
||||||
let mut submitted = false;
|
|
||||||
|
|
||||||
for req in batch_request {
|
|
||||||
match req.request_type {
|
|
||||||
RequestType::In => {
|
|
||||||
// SAFETY: we know the file descriptor is valid and we
|
|
||||||
// relied on vm-memory to provide the buffer address.
|
|
||||||
unsafe {
|
|
||||||
sq.push(
|
|
||||||
&opcode::Readv::new(
|
|
||||||
types::Fd(self.fd),
|
|
||||||
req.iovecs.as_ptr(),
|
|
||||||
req.iovecs.len() as u32,
|
|
||||||
)
|
|
||||||
.offset(req.offset as u64)
|
|
||||||
.build()
|
|
||||||
.user_data(req.user_data),
|
|
||||||
)
|
|
||||||
.map_err(|_| {
|
|
||||||
AsyncIoError::ReadVectored(Error::other("Submission queue is full"))
|
|
||||||
})?;
|
|
||||||
};
|
|
||||||
submitted = true;
|
|
||||||
}
|
|
||||||
RequestType::Out => {
|
|
||||||
// SAFETY: we know the file descriptor is valid and we
|
|
||||||
// relied on vm-memory to provide the buffer address.
|
|
||||||
unsafe {
|
|
||||||
sq.push(
|
|
||||||
&opcode::Writev::new(
|
|
||||||
types::Fd(self.fd),
|
|
||||||
req.iovecs.as_ptr(),
|
|
||||||
req.iovecs.len() as u32,
|
|
||||||
)
|
|
||||||
.offset(req.offset as u64)
|
|
||||||
.build()
|
|
||||||
.user_data(req.user_data),
|
|
||||||
)
|
|
||||||
.map_err(|_| {
|
|
||||||
AsyncIoError::WriteVectored(Error::other("Submission queue is full"))
|
|
||||||
})?;
|
|
||||||
};
|
|
||||||
submitted = true;
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
unreachable!("Unexpected batch request type: {:?}", req.request_type)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only submit if we actually queued something
|
|
||||||
if submitted {
|
|
||||||
// Update the submission queue and submit new operations to the
|
|
||||||
// io_uring instance.
|
|
||||||
sq.sync();
|
|
||||||
submitter
|
|
||||||
.submit()
|
|
||||||
.map_err(AsyncIoError::SubmitBatchRequests)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -2,19 +2,15 @@
|
|||||||
//
|
//
|
||||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||||
|
|
||||||
|
use crate::async_io::{
|
||||||
|
AsyncIo, AsyncIoError, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult, DiskTopology,
|
||||||
|
};
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{Seek, SeekFrom};
|
use std::io::{Seek, SeekFrom};
|
||||||
use std::os::unix::io::{AsRawFd, RawFd};
|
use std::os::unix::io::{AsRawFd, RawFd};
|
||||||
|
|
||||||
use log::warn;
|
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
use crate::DiskTopology;
|
|
||||||
use crate::async_io::{
|
|
||||||
AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct RawFileDiskSync {
|
pub struct RawFileDiskSync {
|
||||||
file: File,
|
file: File,
|
||||||
}
|
}
|
||||||
@@ -26,35 +22,24 @@ impl RawFileDiskSync {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl DiskFile for RawFileDiskSync {
|
impl DiskFile for RawFileDiskSync {
|
||||||
fn logical_size(&mut self) -> DiskFileResult<u64> {
|
fn size(&mut self) -> DiskFileResult<u64> {
|
||||||
self.file
|
self.file
|
||||||
.seek(SeekFrom::End(0))
|
.seek(SeekFrom::End(0))
|
||||||
.map_err(DiskFileError::Size)
|
.map_err(DiskFileError::Size)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn physical_size(&mut self) -> DiskFileResult<u64> {
|
|
||||||
self.file
|
|
||||||
.metadata()
|
|
||||||
.map(|m| m.len())
|
|
||||||
.map_err(DiskFileError::Size)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new_async_io(&self, _ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
|
fn new_async_io(&self, _ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
|
||||||
Ok(Box::new(RawFileSync::new(self.file.as_raw_fd())) as Box<dyn AsyncIo>)
|
Ok(Box::new(RawFileSync::new(self.file.as_raw_fd())) as Box<dyn AsyncIo>)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn topology(&mut self) -> DiskTopology {
|
fn topology(&mut self) -> DiskTopology {
|
||||||
if let Ok(topology) = DiskTopology::probe(&self.file) {
|
if let Ok(topology) = DiskTopology::probe(&mut self.file) {
|
||||||
topology
|
topology
|
||||||
} else {
|
} else {
|
||||||
warn!("Unable to get device topology. Using default topology");
|
warn!("Unable to get device topology. Using default topology");
|
||||||
DiskTopology::default()
|
DiskTopology::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fd(&mut self) -> BorrowedDiskFd<'_> {
|
|
||||||
BorrowedDiskFd::new(self.file.as_raw_fd())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RawFileSync {
|
pub struct RawFileSync {
|
||||||
@@ -88,7 +73,7 @@ impl AsyncIo for RawFileSync {
|
|||||||
let result = unsafe {
|
let result = unsafe {
|
||||||
libc::preadv(
|
libc::preadv(
|
||||||
self.fd as libc::c_int,
|
self.fd as libc::c_int,
|
||||||
iovecs.as_ptr(),
|
iovecs.as_ptr() as *const libc::iovec,
|
||||||
iovecs.len() as libc::c_int,
|
iovecs.len() as libc::c_int,
|
||||||
offset,
|
offset,
|
||||||
)
|
)
|
||||||
@@ -113,7 +98,7 @@ impl AsyncIo for RawFileSync {
|
|||||||
let result = unsafe {
|
let result = unsafe {
|
||||||
libc::pwritev(
|
libc::pwritev(
|
||||||
self.fd as libc::c_int,
|
self.fd as libc::c_int,
|
||||||
iovecs.as_ptr(),
|
iovecs.as_ptr() as *const libc::iovec,
|
||||||
iovecs.len() as libc::c_int,
|
iovecs.len() as libc::c_int,
|
||||||
offset,
|
offset,
|
||||||
)
|
)
|
||||||
@@ -2,10 +2,9 @@
|
|||||||
//
|
//
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use std::convert::TryInto;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{Seek, SeekFrom};
|
use std::io::{Read, Seek, SeekFrom};
|
||||||
|
|
||||||
use crate::{DiskTopology, read_aligned_block_size};
|
|
||||||
|
|
||||||
#[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()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,14 +120,12 @@ pub fn is_fixed_vhd(f: &mut File) -> std::io::Result<bool> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod unit_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::{VhdFooter, is_fixed_vhd};
|
|
||||||
|
|
||||||
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,43 +2,29 @@
|
|||||||
//
|
//
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use crate::async_io::{AsyncIo, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult};
|
||||||
|
use crate::AsyncAdaptor;
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::os::fd::AsRawFd;
|
use std::sync::{Arc, Mutex, MutexGuard};
|
||||||
|
use vhdx::vhdx::{Result as VhdxResult, Vhdx};
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
use crate::async_io::{
|
|
||||||
AsyncIo, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult,
|
|
||||||
};
|
|
||||||
use crate::vhdx::{Result as VhdxResult, Vhdx};
|
|
||||||
use crate::{AsyncAdaptor, BlockBackend, Error};
|
|
||||||
|
|
||||||
pub struct VhdxDiskSync {
|
pub struct VhdxDiskSync {
|
||||||
vhdx_file: Vhdx,
|
vhdx_file: Arc<Mutex<Vhdx>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VhdxDiskSync {
|
impl VhdxDiskSync {
|
||||||
pub fn new(f: File) -> VhdxResult<Self> {
|
pub fn new(f: File) -> VhdxResult<Self> {
|
||||||
Ok(VhdxDiskSync {
|
Ok(VhdxDiskSync {
|
||||||
vhdx_file: Vhdx::new(f)?,
|
vhdx_file: Arc::new(Mutex::new(Vhdx::new(f)?)),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DiskFile for VhdxDiskSync {
|
impl DiskFile for VhdxDiskSync {
|
||||||
fn logical_size(&mut self) -> DiskFileResult<u64> {
|
fn size(&mut self) -> DiskFileResult<u64> {
|
||||||
Ok(self.vhdx_file.virtual_disk_size())
|
Ok(self.vhdx_file.lock().unwrap().virtual_disk_size())
|
||||||
}
|
|
||||||
|
|
||||||
fn physical_size(&mut self) -> DiskFileResult<u64> {
|
|
||||||
self.vhdx_file.physical_size().map_err(|e| {
|
|
||||||
let io_inner = match e {
|
|
||||||
Error::GetFileMetadata(e) => e,
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
DiskFileError::Size(io_inner)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_async_io(&self, _ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
|
fn new_async_io(&self, _ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
|
||||||
@@ -47,20 +33,16 @@ impl DiskFile for VhdxDiskSync {
|
|||||||
as Box<dyn AsyncIo>,
|
as Box<dyn AsyncIo>,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fd(&mut self) -> BorrowedDiskFd<'_> {
|
|
||||||
BorrowedDiskFd::new(self.vhdx_file.as_raw_fd())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct VhdxSync {
|
pub struct VhdxSync {
|
||||||
vhdx_file: Vhdx,
|
vhdx_file: Arc<Mutex<Vhdx>>,
|
||||||
eventfd: EventFd,
|
eventfd: EventFd,
|
||||||
completion_list: VecDeque<(u64, i32)>,
|
completion_list: VecDeque<(u64, i32)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VhdxSync {
|
impl VhdxSync {
|
||||||
pub fn new(vhdx_file: Vhdx) -> std::io::Result<Self> {
|
pub fn new(vhdx_file: Arc<Mutex<Vhdx>>) -> std::io::Result<Self> {
|
||||||
Ok(VhdxSync {
|
Ok(VhdxSync {
|
||||||
vhdx_file,
|
vhdx_file,
|
||||||
eventfd: EventFd::new(libc::EFD_NONBLOCK)?,
|
eventfd: EventFd::new(libc::EFD_NONBLOCK)?,
|
||||||
@@ -69,7 +51,11 @@ impl VhdxSync {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsyncAdaptor for Vhdx {}
|
impl AsyncAdaptor<Vhdx> for Arc<Mutex<Vhdx>> {
|
||||||
|
fn file(&mut self) -> MutexGuard<Vhdx> {
|
||||||
|
self.lock().unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl AsyncIo for VhdxSync {
|
impl AsyncIo for VhdxSync {
|
||||||
fn notifier(&self) -> &EventFd {
|
fn notifier(&self) -> &EventFd {
|
||||||
24
build.rs
Normal file
24
build.rs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
// Copyright © 2020 Intel Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
//
|
||||||
|
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut version = "v".to_owned() + env!("CARGO_PKG_VERSION");
|
||||||
|
|
||||||
|
if let Ok(git_out) = Command::new("git").args(["describe", "--dirty"]).output() {
|
||||||
|
if git_out.status.success() {
|
||||||
|
if let Ok(git_out_str) = String::from_utf8(git_out.stdout) {
|
||||||
|
version = git_out_str;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This println!() has a special behavior, as it will set the environment
|
||||||
|
// variable BUILT_VERSION, so that it can be reused from the binary.
|
||||||
|
// Particularly, this is used from src/main.rs to display the exact
|
||||||
|
// version.
|
||||||
|
println!("cargo:rustc-env=BUILT_VERSION={version}");
|
||||||
|
}
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
[package]
|
|
||||||
authors = ["The Cloud Hypervisor Authors"]
|
|
||||||
build = "build.rs"
|
|
||||||
default-run = "cloud-hypervisor"
|
|
||||||
description = "Open source Virtual Machine Monitor (VMM) that runs on top of KVM & MSHV"
|
|
||||||
edition = "2024"
|
|
||||||
homepage = "https://github.com/cloud-hypervisor/cloud-hypervisor"
|
|
||||||
license = "Apache-2.0 AND BSD-3-Clause"
|
|
||||||
name = "cloud-hypervisor"
|
|
||||||
version = "50.2.0"
|
|
||||||
# Minimum buildable version:
|
|
||||||
# Keep in sync with version in .github/workflows/build.yaml
|
|
||||||
# Policy on MSRV (see #4318):
|
|
||||||
# Can only be bumped if satisfying any of the following:
|
|
||||||
# a.) A dependency requires it,
|
|
||||||
# b.) If we want to use a new feature and that MSRV is at least 6 months old,
|
|
||||||
# c.) There is a security issue that is addressed by the toolchain update.
|
|
||||||
rust-version = "1.89.0"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
anyhow = { workspace = true }
|
|
||||||
api_client = { path = "../api_client" }
|
|
||||||
clap = { workspace = true, features = ["string"] }
|
|
||||||
dhat = { workspace = true, optional = true }
|
|
||||||
env_logger = { workspace = true }
|
|
||||||
epoll = { workspace = true }
|
|
||||||
event_monitor = { path = "../event_monitor" }
|
|
||||||
hypervisor = { path = "../hypervisor" }
|
|
||||||
libc = { workspace = true }
|
|
||||||
log = { workspace = true, features = ["std"] }
|
|
||||||
option_parser = { path = "../option_parser" }
|
|
||||||
seccompiler = { workspace = true }
|
|
||||||
serde_json = { workspace = true }
|
|
||||||
signal-hook = { workspace = true }
|
|
||||||
thiserror = { workspace = true }
|
|
||||||
tpm = { path = "../tpm" }
|
|
||||||
tracer = { path = "../tracer" }
|
|
||||||
vm-memory = { workspace = true }
|
|
||||||
vmm = { path = "../vmm" }
|
|
||||||
vmm-sys-util = { workspace = true }
|
|
||||||
zbus = { version = "5.7.1", optional = true }
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
block = { path = "../block" }
|
|
||||||
dirs = { workspace = true }
|
|
||||||
net_util = { path = "../net_util" }
|
|
||||||
serde_json = { workspace = true }
|
|
||||||
test_infra = { path = "../test_infra" }
|
|
||||||
wait-timeout = { workspace = true }
|
|
||||||
|
|
||||||
# Please adjust `vmm::feature_list()` accordingly when changing the
|
|
||||||
# feature list below
|
|
||||||
[features]
|
|
||||||
dbus_api = ["vmm/dbus_api", "zbus"]
|
|
||||||
default = ["io_uring", "kvm"]
|
|
||||||
dhat-heap = ["dhat", "vmm/dhat-heap"] # For heap profiling
|
|
||||||
fw_cfg = ["vmm/fw_cfg"]
|
|
||||||
guest_debug = ["vmm/guest_debug"]
|
|
||||||
igvm = ["mshv", "vmm/igvm"]
|
|
||||||
io_uring = ["vmm/io_uring"]
|
|
||||||
ivshmem = ["vmm/ivshmem"]
|
|
||||||
kvm = ["vmm/kvm"]
|
|
||||||
mshv = ["vmm/mshv"]
|
|
||||||
pvmemcontrol = ["vmm/pvmemcontrol"]
|
|
||||||
sev_snp = ["igvm", "mshv", "vmm/sev_snp"]
|
|
||||||
tdx = ["vmm/tdx"]
|
|
||||||
tracing = ["tracer/tracing", "vmm/tracing"]
|
|
||||||
|
|
||||||
[lints]
|
|
||||||
workspace = true
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
// Copyright © 2020 Intel Corporation
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
//
|
|
||||||
|
|
||||||
use std::env;
|
|
||||||
use std::process::Command;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let mut version = "v".to_owned() + env!("CARGO_PKG_VERSION");
|
|
||||||
|
|
||||||
if let Ok(git_out) = Command::new("git").args(["describe", "--dirty"]).output()
|
|
||||||
&& git_out.status.success()
|
|
||||||
&& let Ok(git_out_str) = String::from_utf8(git_out.stdout)
|
|
||||||
{
|
|
||||||
version = git_out_str;
|
|
||||||
// Pop the trailing newline.
|
|
||||||
version.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Append CH_EXTRA_VERSION to version if it is set.
|
|
||||||
if let Ok(extra_version) = env::var("CH_EXTRA_VERSION") {
|
|
||||||
println!("cargo:rerun-if-env-changed=CH_EXTRA_VERSION");
|
|
||||||
version.push_str(&format!("-{extra_version}"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// This println!() has a special behavior, as it will set the environment
|
|
||||||
// variable BUILD_VERSION, so that it can be reused from the binary.
|
|
||||||
// Particularly, this is used from src/main.rs to display the exact
|
|
||||||
// version.
|
|
||||||
println!("cargo:rustc-env=BUILD_VERSION={version}");
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,46 +0,0 @@
|
|||||||
// Copyright © 2025 Cyberus Technology GmbH
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
use std::error::Error;
|
|
||||||
|
|
||||||
use log::error;
|
|
||||||
|
|
||||||
/// Prints a chain of errors to the user in a consistent manner.
|
|
||||||
/// The user will see a clear chain of errors, followed by debug output
|
|
||||||
/// for opening issues.
|
|
||||||
pub fn cli_print_error_chain<'a>(
|
|
||||||
top_error: &'a (dyn Error + 'static),
|
|
||||||
component: &str,
|
|
||||||
// Function optionally returning the display representation of an error.
|
|
||||||
display_modifier: impl Fn(
|
|
||||||
/* level */ usize,
|
|
||||||
/*indention */ usize,
|
|
||||||
&'a (dyn Error + 'static),
|
|
||||||
) -> Option<String>,
|
|
||||||
) {
|
|
||||||
// Debug info.
|
|
||||||
error!("Fatal error: {top_error:?}");
|
|
||||||
|
|
||||||
eprint!("Error: {component} exited with the following ");
|
|
||||||
if top_error.source().is_none() {
|
|
||||||
eprintln!("error:");
|
|
||||||
eprintln!(" {top_error}");
|
|
||||||
} else {
|
|
||||||
eprintln!("chain of errors:");
|
|
||||||
std::iter::successors(Some(top_error), |sub_error| {
|
|
||||||
// Dereference necessary to mitigate rustc compiler bug.
|
|
||||||
// See <https://github.com/rust-lang/rust/issues/141673>
|
|
||||||
(*sub_error).source()
|
|
||||||
})
|
|
||||||
.enumerate()
|
|
||||||
.for_each(|(level, error)| {
|
|
||||||
// Special case: handling of HTTP Server responses in ch-remote
|
|
||||||
if let Some(message) = display_modifier(level, 2, error) {
|
|
||||||
eprintln!("{message}");
|
|
||||||
} else {
|
|
||||||
eprintln!(" {level}: {error}");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
// Copyright © 2025 Cyberus Technology GmbH
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
//
|
|
||||||
|
|
||||||
//! Test utilities.
|
|
||||||
|
|
||||||
use std::cmp::Ordering;
|
|
||||||
|
|
||||||
use clap::Arg;
|
|
||||||
|
|
||||||
/// Ensures that all [`Arg`]s are sorted alphabetically.
|
|
||||||
pub fn assert_args_sorted<'a, F: Fn() -> R, R: Iterator<Item = &'a Arg>>(get_base_iter: F) {
|
|
||||||
let iter = get_base_iter().zip(get_base_iter().skip(1));
|
|
||||||
for (arg, next) in iter {
|
|
||||||
assert_ne!(
|
|
||||||
arg.get_id().cmp(next.get_id()),
|
|
||||||
Ordering::Greater,
|
|
||||||
"args not alphabetically sorted: arg={}, next={}",
|
|
||||||
arg.get_id(),
|
|
||||||
next.get_id()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,53 +1,29 @@
|
|||||||
[package]
|
[package]
|
||||||
authors = ["The Chromium OS Authors"]
|
|
||||||
edition.workspace = true
|
|
||||||
name = "devices"
|
name = "devices"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
authors = ["The Chromium OS Authors"]
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
acpi_tables = { workspace = true }
|
acpi_tables = { git = "https://github.com/rust-vmm/acpi_tables", branch = "main" }
|
||||||
anyhow = { workspace = true }
|
anyhow = "1.0.69"
|
||||||
arch = { path = "../arch" }
|
arch = { path = "../arch" }
|
||||||
bitfield-struct = { version = "0.12.0", optional = true }
|
bitflags = "1.3.2"
|
||||||
bitflags = { workspace = true }
|
byteorder = "1.4.3"
|
||||||
byteorder = { workspace = true }
|
|
||||||
event_monitor = { path = "../event_monitor" }
|
|
||||||
hypervisor = { path = "../hypervisor" }
|
hypervisor = { path = "../hypervisor" }
|
||||||
libc = { workspace = true }
|
libc = "0.2.139"
|
||||||
linux-loader = { workspace = true, features = [
|
log = "0.4.17"
|
||||||
"bzimage",
|
thiserror = "1.0.38"
|
||||||
"elf",
|
|
||||||
"pe",
|
|
||||||
], optional = true }
|
|
||||||
log = { workspace = true }
|
|
||||||
num_enum = "0.7.5"
|
|
||||||
pci = { path = "../pci" }
|
|
||||||
serde = { workspace = true, features = ["derive"] }
|
|
||||||
thiserror = { workspace = true }
|
|
||||||
tpm = { path = "../tpm" }
|
tpm = { path = "../tpm" }
|
||||||
vm-allocator = { path = "../vm-allocator" }
|
versionize = "0.1.9"
|
||||||
|
versionize_derive = "0.1.4"
|
||||||
vm-device = { path = "../vm-device" }
|
vm-device = { path = "../vm-device" }
|
||||||
vm-memory = { workspace = true, features = [
|
vm-memory = "0.10.0"
|
||||||
"backend-atomic",
|
|
||||||
"backend-bitmap",
|
|
||||||
"backend-mmap",
|
|
||||||
] }
|
|
||||||
vm-migration = { path = "../vm-migration" }
|
vm-migration = { path = "../vm-migration" }
|
||||||
vmm-sys-util = { workspace = true }
|
vmm-sys-util = "0.11.0"
|
||||||
zerocopy = { version = "0.8.31", features = [
|
|
||||||
"alloc",
|
|
||||||
"derive",
|
|
||||||
], optional = true }
|
|
||||||
|
|
||||||
[target.'cfg(any(target_arch = "aarch64", target_arch = "riscv64"))'.dependencies]
|
[target.'cfg(target_arch = "aarch64")'.dependencies]
|
||||||
arch = { path = "../arch" }
|
arch = { path = "../arch" }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
fw_cfg = ["arch/fw_cfg", "bitfield-struct", "linux-loader", "zerocopy"]
|
|
||||||
ivshmem = []
|
|
||||||
kvm = ["arch/kvm"]
|
|
||||||
pvmemcontrol = []
|
|
||||||
|
|
||||||
[lints]
|
|
||||||
workspace = true
|
|
||||||
|
|||||||
@@ -3,40 +3,29 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// 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, AmlSink, aml};
|
|
||||||
use log::{error, info, warn};
|
|
||||||
use vm_device::BusDevice;
|
|
||||||
use vm_device::interrupt::InterruptSourceGroup;
|
use vm_device::interrupt::InterruptSourceGroup;
|
||||||
|
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,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -45,21 +34,14 @@ impl AcpiShutdownDevice {
|
|||||||
impl BusDevice for AcpiShutdownDevice {
|
impl BusDevice for AcpiShutdownDevice {
|
||||||
// Spec has all fields as zero
|
// Spec has all fields as zero
|
||||||
fn read(&mut self, _base: u64, _offset: u64, data: &mut [u8]) {
|
fn read(&mut self, _base: u64, _offset: u64, data: &mut [u8]) {
|
||||||
data.fill(0);
|
data.fill(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write(&mut self, _base: u64, _offset: u64, data: &[u8]) -> Option<Arc<Barrier>> {
|
fn write(&mut self, _base: u64, _offset: u64, data: &[u8]) -> Option<Arc<Barrier>> {
|
||||||
if data[0] == 1 {
|
if data[0] == 1 {
|
||||||
info!("ACPI Reboot signalled");
|
info!("ACPI Reboot signalled");
|
||||||
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
|
||||||
@@ -69,14 +51,7 @@ impl BusDevice for AcpiShutdownDevice {
|
|||||||
if data[0] == (S5_SLEEP_VALUE << SLEEP_VALUE_BIT) | (1 << SLEEP_STATUS_EN_BIT) {
|
if data[0] == (S5_SLEEP_VALUE << SLEEP_VALUE_BIT) | (1 << SLEEP_STATUS_EN_BIT) {
|
||||||
info!("ACPI Shutdown signalled");
|
info!("ACPI Shutdown signalled");
|
||||||
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
|
||||||
@@ -128,32 +103,30 @@ impl BusDevice for AcpiGedDevice {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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_.GEC_".into(),
|
||||||
vec![
|
vec![
|
||||||
&aml::Name::new("_HID".into(), &aml::EISAName::new("PNP0A06")),
|
&aml::Name::new("_HID".into(), &aml::EisaName::new("PNP0A06")),
|
||||||
&aml::Name::new("_UID".into(), &"Generic Event Controller"),
|
&aml::Name::new("_UID".into(), &"Generic Event Controller"),
|
||||||
&aml::Name::new(
|
&aml::Name::new(
|
||||||
"_CRS".into(),
|
"_CRS".into(),
|
||||||
&aml::ResourceTemplate::new(vec![&aml::AddressSpace::new_memory(
|
&aml::ResourceTemplate::new(vec![&aml::AddressSpace::new_memory(
|
||||||
aml::AddressSpaceCacheable::NotCacheable,
|
aml::AddressSpaceCachable::NotCacheable,
|
||||||
true,
|
true,
|
||||||
self.address.0,
|
self.address.0,
|
||||||
self.address.0 + GED_DEVICE_ACPI_SIZE as u64 - 1,
|
self.address.0 + GED_DEVICE_ACPI_SIZE as u64 - 1,
|
||||||
None,
|
|
||||||
)]),
|
)]),
|
||||||
),
|
),
|
||||||
&aml::OpRegion::new(
|
&aml::OpRegion::new(
|
||||||
"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)],
|
||||||
),
|
),
|
||||||
@@ -190,7 +163,7 @@ impl Aml for AcpiGedDevice {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
.to_aml_bytes(sink);
|
.append_aml_bytes(bytes);
|
||||||
aml::Device::new(
|
aml::Device::new(
|
||||||
"_SB_.GED_".into(),
|
"_SB_.GED_".into(),
|
||||||
vec![
|
vec![
|
||||||
@@ -214,7 +187,7 @@ impl Aml for AcpiGedDevice {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
.to_aml_bytes(sink);
|
.append_aml_bytes(bytes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,144 +0,0 @@
|
|||||||
// Copyright © 2024 Institute of Software, CAS. All rights reserved.
|
|
||||||
// Copyright 2020, ARM Limited.
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
|
||||||
|
|
||||||
use std::result;
|
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
|
|
||||||
use arch::layout;
|
|
||||||
use hypervisor::arch::riscv64::aia::{Vaia, VaiaConfig};
|
|
||||||
use hypervisor::{AiaState, CpuState};
|
|
||||||
use vm_device::interrupt::{
|
|
||||||
InterruptIndex, InterruptManager, InterruptSourceConfig, InterruptSourceGroup,
|
|
||||||
LegacyIrqSourceConfig, MsiIrqGroupConfig,
|
|
||||||
};
|
|
||||||
use vm_memory::address::Address;
|
|
||||||
use vm_migration::{Migratable, Pausable, Snapshottable, Transportable};
|
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
|
||||||
|
|
||||||
use super::interrupt_controller::{Error, InterruptController};
|
|
||||||
|
|
||||||
type Result<T> = result::Result<T, Error>;
|
|
||||||
|
|
||||||
// Reserve 32 IRQs for legacy devices.
|
|
||||||
pub const IRQ_LEGACY_BASE: usize = layout::IRQ_BASE as usize;
|
|
||||||
pub const IRQ_LEGACY_COUNT: usize = 32;
|
|
||||||
// TODO: AIA snapshotting is not yet completed.
|
|
||||||
pub const _AIA_SNAPSHOT_ID: &str = "";
|
|
||||||
|
|
||||||
// Aia (Advance Interrupt Architecture) struct provides all the functionality of a
|
|
||||||
// AIA device. It wraps a hypervisor-emulated AIA device (Vaia) provided by the
|
|
||||||
// `hypervisor` crate.
|
|
||||||
// Aia struct also implements InterruptController to provide interrupt delivery
|
|
||||||
// service.
|
|
||||||
pub struct Aia {
|
|
||||||
interrupt_source_group: Arc<dyn InterruptSourceGroup>,
|
|
||||||
// The hypervisor agnostic virtual AIA
|
|
||||||
vaia: Arc<Mutex<dyn Vaia>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Aia {
|
|
||||||
#[allow(clippy::needless_pass_by_value)]
|
|
||||||
pub fn new(
|
|
||||||
vcpu_count: u32,
|
|
||||||
interrupt_manager: Arc<dyn InterruptManager<GroupConfig = MsiIrqGroupConfig>>,
|
|
||||||
vm: Arc<dyn hypervisor::Vm>,
|
|
||||||
) -> Result<Aia> {
|
|
||||||
let interrupt_source_group = interrupt_manager
|
|
||||||
.create_group(MsiIrqGroupConfig {
|
|
||||||
base: IRQ_LEGACY_BASE as InterruptIndex,
|
|
||||||
count: IRQ_LEGACY_COUNT as InterruptIndex,
|
|
||||||
})
|
|
||||||
.map_err(Error::CreateInterruptSourceGroup)?;
|
|
||||||
|
|
||||||
let config = Aia::create_default_config(vcpu_count as u64);
|
|
||||||
let vaia = vm.create_vaia(&config).map_err(Error::CreateAia)?;
|
|
||||||
|
|
||||||
let aia = Aia {
|
|
||||||
interrupt_source_group,
|
|
||||||
vaia,
|
|
||||||
};
|
|
||||||
aia.enable()?;
|
|
||||||
|
|
||||||
Ok(aia)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn restore_vaia(
|
|
||||||
&mut self,
|
|
||||||
state: Option<AiaState>,
|
|
||||||
_saved_vcpu_states: &[CpuState],
|
|
||||||
) -> Result<()> {
|
|
||||||
self.vaia
|
|
||||||
.clone()
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.set_state(&state.unwrap())
|
|
||||||
.map_err(Error::RestoreAia)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn enable(&self) -> Result<()> {
|
|
||||||
// Set irqfd for legacy interrupts
|
|
||||||
self.interrupt_source_group
|
|
||||||
.enable()
|
|
||||||
.map_err(Error::EnableInterrupt)?;
|
|
||||||
|
|
||||||
// Set irq_routing for legacy interrupts.
|
|
||||||
// irqchip: Hardcode to 0 as we support only 1 APLIC
|
|
||||||
// pin: Use irq number as pin
|
|
||||||
for i in IRQ_LEGACY_BASE..(IRQ_LEGACY_BASE + IRQ_LEGACY_COUNT) {
|
|
||||||
let config = LegacyIrqSourceConfig {
|
|
||||||
irqchip: 0,
|
|
||||||
pin: (i - IRQ_LEGACY_BASE) as u32,
|
|
||||||
};
|
|
||||||
self.interrupt_source_group
|
|
||||||
.update(
|
|
||||||
i as InterruptIndex,
|
|
||||||
InterruptSourceConfig::LegacyIrq(config),
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
.map_err(Error::EnableInterrupt)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.interrupt_source_group
|
|
||||||
.set_gsi()
|
|
||||||
.map_err(Error::EnableInterrupt)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Default config implied by arch::layout
|
|
||||||
pub fn create_default_config(vcpu_count: u64) -> VaiaConfig {
|
|
||||||
VaiaConfig {
|
|
||||||
vcpu_count: vcpu_count as u32,
|
|
||||||
aplic_addr: layout::APLIC_START.raw_value(),
|
|
||||||
imsic_addr: layout::IMSIC_START.raw_value(),
|
|
||||||
nr_irqs: layout::IRQ_NUM,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_vaia(&mut self) -> Result<Arc<Mutex<dyn Vaia>>> {
|
|
||||||
Ok(self.vaia.clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InterruptController for Aia {
|
|
||||||
// This should be called anytime an interrupt needs to be injected into the
|
|
||||||
// running guest.
|
|
||||||
fn service_irq(&mut self, irq: usize) -> Result<()> {
|
|
||||||
self.interrupt_source_group
|
|
||||||
.trigger(irq as InterruptIndex)
|
|
||||||
.map_err(Error::TriggerInterrupt)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn notifier(&self, irq: usize) -> Option<EventFd> {
|
|
||||||
self.interrupt_source_group.notifier(irq as InterruptIndex)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Snapshottable for Aia {}
|
|
||||||
impl Pausable for Aia {}
|
|
||||||
impl Transportable for Aia {}
|
|
||||||
impl Migratable for Aia {}
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
// Copyright © 2023 Cyberus Technology
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
//
|
|
||||||
|
|
||||||
//! Module for [`DebugconState`].
|
|
||||||
|
|
||||||
use std::io;
|
|
||||||
use std::io::Write;
|
|
||||||
use std::sync::{Arc, Barrier};
|
|
||||||
|
|
||||||
use log::error;
|
|
||||||
use vm_device::BusDevice;
|
|
||||||
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
|
|
||||||
|
|
||||||
/// I/O-port.
|
|
||||||
pub const DEFAULT_PORT: u64 = 0xe9;
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct DebugconState {}
|
|
||||||
|
|
||||||
/// Emulates a debug console similar to the QEMU debugcon device. This device
|
|
||||||
/// is stateless and only prints the bytes (usually text) that are written to
|
|
||||||
/// it.
|
|
||||||
///
|
|
||||||
/// This device is only available on x86.
|
|
||||||
///
|
|
||||||
/// Reference:
|
|
||||||
/// - https://github.com/qemu/qemu/blob/master/hw/char/debugcon.c
|
|
||||||
/// - https://phip1611.de/blog/how-to-use-qemus-debugcon-feature-and-write-to-a-file/
|
|
||||||
pub struct DebugConsole {
|
|
||||||
id: String,
|
|
||||||
out: Box<dyn io::Write + Send>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DebugConsole {
|
|
||||||
pub fn new(id: String, out: Box<dyn io::Write + Send>) -> Self {
|
|
||||||
Self { id, out }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BusDevice for DebugConsole {
|
|
||||||
fn read(&mut self, _base: u64, _offset: u64, _data: &mut [u8]) {}
|
|
||||||
|
|
||||||
fn write(&mut self, _base: u64, _offset: u64, data: &[u8]) -> Option<Arc<Barrier>> {
|
|
||||||
if let Err(e) = self.out.write_all(data) {
|
|
||||||
// unlikely
|
|
||||||
error!("debug-console: failed writing data: {e:?}");
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Snapshottable for DebugConsole {
|
|
||||||
fn id(&self) -> String {
|
|
||||||
self.id.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn snapshot(&mut self) -> Result<Snapshot, MigratableError> {
|
|
||||||
Snapshot::new_from_state(&())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Pausable for DebugConsole {}
|
|
||||||
impl Transportable for DebugConsole {}
|
|
||||||
impl Migratable for DebugConsole {}
|
|
||||||
@@ -2,13 +2,16 @@
|
|||||||
//
|
//
|
||||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||||
|
|
||||||
use std::result;
|
use super::interrupt_controller::{Error, InterruptController};
|
||||||
use std::sync::{Arc, Mutex};
|
extern crate arch;
|
||||||
|
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use arch::layout;
|
use arch::layout;
|
||||||
use hypervisor::CpuState;
|
use hypervisor::{
|
||||||
use hypervisor::arch::aarch64::gic::{GicState, Vgic, VgicConfig};
|
arch::aarch64::gic::{Vgic, VgicConfig},
|
||||||
|
CpuState, GicState,
|
||||||
|
};
|
||||||
|
use std::result;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
use vm_device::interrupt::{
|
use vm_device::interrupt::{
|
||||||
InterruptIndex, InterruptManager, InterruptSourceConfig, InterruptSourceGroup,
|
InterruptIndex, InterruptManager, InterruptSourceConfig, InterruptSourceGroup,
|
||||||
LegacyIrqSourceConfig, MsiIrqGroupConfig,
|
LegacyIrqSourceConfig, MsiIrqGroupConfig,
|
||||||
@@ -17,8 +20,6 @@ use vm_memory::address::Address;
|
|||||||
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
|
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
|
||||||
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>;
|
||||||
|
|
||||||
// Reserve 32 IRQs for legacy devices.
|
// Reserve 32 IRQs for legacy devices.
|
||||||
@@ -26,7 +27,7 @@ pub const IRQ_LEGACY_BASE: usize = layout::IRQ_BASE as usize;
|
|||||||
pub const IRQ_LEGACY_COUNT: usize = 32;
|
pub const IRQ_LEGACY_COUNT: usize = 32;
|
||||||
pub const GIC_SNAPSHOT_ID: &str = "gic-v3-its";
|
pub const GIC_SNAPSHOT_ID: &str = "gic-v3-its";
|
||||||
|
|
||||||
// Gic (Generic Interrupt Controller) struct provides all the functionality of a
|
// Gic (Generic Interupt Controller) struct provides all the functionality of a
|
||||||
// GIC device. It wraps a hypervisor-emulated GIC device (Vgic) provided by the
|
// GIC device. It wraps a hypervisor-emulated GIC device (Vgic) provided by the
|
||||||
// `hypervisor` crate.
|
// `hypervisor` crate.
|
||||||
// Gic struct also implements InterruptController to provide interrupt delivery
|
// Gic struct also implements InterruptController to provide interrupt delivery
|
||||||
@@ -38,9 +39,8 @@ pub struct Gic {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Gic {
|
impl Gic {
|
||||||
#[allow(clippy::needless_pass_by_value)]
|
|
||||||
pub fn new(
|
pub fn new(
|
||||||
vcpu_count: u32,
|
vcpu_count: u8,
|
||||||
interrupt_manager: Arc<dyn InterruptManager<GroupConfig = MsiIrqGroupConfig>>,
|
interrupt_manager: Arc<dyn InterruptManager<GroupConfig = MsiIrqGroupConfig>>,
|
||||||
vm: Arc<dyn hypervisor::Vm>,
|
vm: Arc<dyn hypervisor::Vm>,
|
||||||
) -> Result<Gic> {
|
) -> Result<Gic> {
|
||||||
@@ -51,8 +51,9 @@ impl Gic {
|
|||||||
})
|
})
|
||||||
.map_err(Error::CreateInterruptSourceGroup)?;
|
.map_err(Error::CreateInterruptSourceGroup)?;
|
||||||
|
|
||||||
let config = Gic::create_default_config(vcpu_count as u64);
|
let vgic = vm
|
||||||
let vgic = vm.create_vgic(&config).map_err(Error::CreateGic)?;
|
.create_vgic(Gic::create_default_config(vcpu_count as u64))
|
||||||
|
.map_err(Error::CreateGic)?;
|
||||||
|
|
||||||
let gic = Gic {
|
let gic = Gic {
|
||||||
interrupt_source_group,
|
interrupt_source_group,
|
||||||
@@ -97,14 +98,9 @@ impl Gic {
|
|||||||
i as InterruptIndex,
|
i as InterruptIndex,
|
||||||
InterruptSourceConfig::LegacyIrq(config),
|
InterruptSourceConfig::LegacyIrq(config),
|
||||||
false,
|
false,
|
||||||
false,
|
|
||||||
)
|
)
|
||||||
.map_err(Error::EnableInterrupt)?;
|
.map_err(Error::EnableInterrupt)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.interrupt_source_group
|
|
||||||
.set_gsi()
|
|
||||||
.map_err(Error::EnableInterrupt)?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,7 +163,10 @@ impl Pausable for Gic {
|
|||||||
// Flush tables to guest RAM
|
// Flush tables to guest RAM
|
||||||
let vgic = self.vgic.as_ref().unwrap().clone();
|
let vgic = self.vgic.as_ref().unwrap().clone();
|
||||||
vgic.lock().unwrap().save_data_tables().map_err(|e| {
|
vgic.lock().unwrap().save_data_tables().map_err(|e| {
|
||||||
MigratableError::Pause(anyhow!("Could not save GICv3ITS GIC pending tables {e:?}",))
|
MigratableError::Pause(anyhow!(
|
||||||
|
"Could not save GICv3ITS GIC pending tables {:?}",
|
||||||
|
e
|
||||||
|
))
|
||||||
})?;
|
})?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,55 +1,35 @@
|
|||||||
// Copyright © 2024 Institute of Software, CAS. All rights reserved.
|
|
||||||
// Copyright 2020, ARM Limited.
|
// Copyright 2020, ARM Limited.
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||||
|
|
||||||
use std::{io, result};
|
use std::io;
|
||||||
|
use std::result;
|
||||||
use thiserror::Error;
|
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
/// Invalid trigger mode.
|
/// Invalid trigger mode.
|
||||||
#[error("Invalid trigger mode")]
|
|
||||||
InvalidTriggerMode,
|
InvalidTriggerMode,
|
||||||
/// Invalid delivery mode.
|
/// Invalid delivery mode.
|
||||||
#[error("Invalid delivery mode")]
|
|
||||||
InvalidDeliveryMode,
|
InvalidDeliveryMode,
|
||||||
/// Failed creating the interrupt source group.
|
/// Failed creating the interrupt source group.
|
||||||
#[error("Failed creating the interrupt source group")]
|
CreateInterruptSourceGroup(io::Error),
|
||||||
CreateInterruptSourceGroup(#[source] io::Error),
|
|
||||||
/// Failed triggering the interrupt.
|
/// Failed triggering the interrupt.
|
||||||
#[error("Failed triggering the interrupt")]
|
TriggerInterrupt(io::Error),
|
||||||
TriggerInterrupt(#[source] io::Error),
|
|
||||||
/// Failed masking the interrupt.
|
/// Failed masking the interrupt.
|
||||||
#[error("Failed masking the interrupt")]
|
MaskInterrupt(io::Error),
|
||||||
MaskInterrupt(#[source] io::Error),
|
|
||||||
/// Failed unmasking the interrupt.
|
/// Failed unmasking the interrupt.
|
||||||
#[error("Failed unmasking the interrupt")]
|
UnmaskInterrupt(io::Error),
|
||||||
UnmaskInterrupt(#[source] io::Error),
|
|
||||||
/// Failed updating the interrupt.
|
/// Failed updating the interrupt.
|
||||||
#[error("Failed updating the interrupt")]
|
UpdateInterrupt(io::Error),
|
||||||
UpdateInterrupt(#[source] io::Error),
|
|
||||||
/// Failed enabling the interrupt.
|
/// Failed enabling the interrupt.
|
||||||
#[error("Failed enabling the interrupt")]
|
EnableInterrupt(io::Error),
|
||||||
EnableInterrupt(#[source] io::Error),
|
|
||||||
#[cfg(target_arch = "aarch64")]
|
#[cfg(target_arch = "aarch64")]
|
||||||
/// Failed creating GIC device.
|
/// Failed creating GIC device.
|
||||||
#[error("Failed creating GIC device")]
|
CreateGic(hypervisor::HypervisorVmError),
|
||||||
CreateGic(#[source] hypervisor::HypervisorVmError),
|
|
||||||
#[cfg(target_arch = "aarch64")]
|
#[cfg(target_arch = "aarch64")]
|
||||||
/// Failed restoring GIC device.
|
/// Failed restoring GIC device.
|
||||||
#[error("Failed restoring GIC device")]
|
RestoreGic(hypervisor::arch::aarch64::gic::Error),
|
||||||
RestoreGic(#[source] hypervisor::arch::aarch64::gic::Error),
|
|
||||||
#[cfg(target_arch = "riscv64")]
|
|
||||||
/// Failed creating AIA device.
|
|
||||||
#[error("Failed creating AIA device")]
|
|
||||||
CreateAia(#[source] hypervisor::HypervisorVmError),
|
|
||||||
#[cfg(target_arch = "riscv64")]
|
|
||||||
/// Failed restoring AIA device.
|
|
||||||
#[error("Failed restoring AIA device")]
|
|
||||||
RestoreAia(#[source] hypervisor::arch::riscv64::aia::Error),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Result<T> = result::Result<T, Error>;
|
type Result<T> = result::Result<T, Error>;
|
||||||
@@ -76,7 +56,7 @@ pub struct MsiMessage {
|
|||||||
// Introduce trait InterruptController to uniform the interrupt
|
// Introduce trait InterruptController to uniform the interrupt
|
||||||
// service provided for devices.
|
// service provided for devices.
|
||||||
// Device manager uses this trait without caring whether it is a
|
// Device manager uses this trait without caring whether it is a
|
||||||
// IOAPIC (X86), GIC (Arm) or AIA (RISC-V).
|
// IOAPIC (X86) or GIC (Arm).
|
||||||
pub trait InterruptController: Send {
|
pub trait InterruptController: Send {
|
||||||
fn service_irq(&mut self, irq: usize) -> Result<()>;
|
fn service_irq(&mut self, irq: usize) -> Result<()>;
|
||||||
#[cfg(target_arch = "x86_64")]
|
#[cfg(target_arch = "x86_64")]
|
||||||
|
|||||||
@@ -9,23 +9,23 @@
|
|||||||
// Implementation of an intel 82093AA Input/Output Advanced Programmable Interrupt Controller
|
// Implementation of an intel 82093AA Input/Output Advanced Programmable Interrupt Controller
|
||||||
// See https://pdos.csail.mit.edu/6.828/2016/readings/ia32/ioapic.pdf for a specification.
|
// See https://pdos.csail.mit.edu/6.828/2016/readings/ia32/ioapic.pdf for a specification.
|
||||||
|
|
||||||
|
use super::interrupt_controller::{Error, InterruptController};
|
||||||
|
use byteorder::{ByteOrder, LittleEndian};
|
||||||
use std::result;
|
use std::result;
|
||||||
use std::sync::{Arc, Barrier};
|
use std::sync::{Arc, Barrier};
|
||||||
|
use versionize::{VersionMap, Versionize, VersionizeResult};
|
||||||
use byteorder::{ByteOrder, LittleEndian};
|
use versionize_derive::Versionize;
|
||||||
use log::{debug, error, trace, warn};
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use vm_device::BusDevice;
|
|
||||||
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_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
|
||||||
@@ -136,7 +136,7 @@ pub struct Ioapic {
|
|||||||
interrupt_source_group: Arc<dyn InterruptSourceGroup>,
|
interrupt_source_group: Arc<dyn InterruptSourceGroup>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Versionize)]
|
||||||
pub struct IoapicState {
|
pub struct IoapicState {
|
||||||
id_reg: u32,
|
id_reg: u32,
|
||||||
reg_sel: u32,
|
reg_sel: u32,
|
||||||
@@ -144,6 +144,7 @@ pub struct IoapicState {
|
|||||||
used_entries: [bool; NUM_IOAPIC_PINS],
|
used_entries: [bool; NUM_IOAPIC_PINS],
|
||||||
apic_address: u64,
|
apic_address: u64,
|
||||||
}
|
}
|
||||||
|
impl VersionMapped for IoapicState {}
|
||||||
|
|
||||||
impl BusDevice for Ioapic {
|
impl BusDevice for Ioapic {
|
||||||
fn read(&mut self, _base: u64, offset: u64, data: &mut [u8]) {
|
fn read(&mut self, _base: u64, offset: u64, data: &mut [u8]) {
|
||||||
@@ -152,13 +153,13 @@ impl BusDevice for Ioapic {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
debug!("IOAPIC_R @ offset 0x{offset:x}");
|
debug!("IOAPIC_R @ offset 0x{:x}", offset);
|
||||||
|
|
||||||
let value: u32 = match offset as u8 {
|
let value: u32 = match offset as u8 {
|
||||||
IOREGSEL_OFF => self.reg_sel,
|
IOREGSEL_OFF => self.reg_sel,
|
||||||
IOWIN_OFF => self.ioapic_read(),
|
IOWIN_OFF => self.ioapic_read(),
|
||||||
_ => {
|
_ => {
|
||||||
error!("IOAPIC: failed reading at offset {offset}");
|
error!("IOAPIC: failed reading at offset {}", offset);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -172,7 +173,7 @@ impl BusDevice for Ioapic {
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
debug!("IOAPIC_W @ offset 0x{offset:x}");
|
debug!("IOAPIC_W @ offset 0x{:x}", offset);
|
||||||
|
|
||||||
let value = LittleEndian::read_u32(data);
|
let value = LittleEndian::read_u32(data);
|
||||||
|
|
||||||
@@ -180,7 +181,7 @@ impl BusDevice for Ioapic {
|
|||||||
IOREGSEL_OFF => self.reg_sel = value,
|
IOREGSEL_OFF => self.reg_sel = value,
|
||||||
IOWIN_OFF => self.ioapic_write(value),
|
IOWIN_OFF => self.ioapic_write(value),
|
||||||
_ => {
|
_ => {
|
||||||
error!("IOAPIC: failed writing at offset {offset}");
|
error!("IOAPIC: failed writing at offset {}", offset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
@@ -191,8 +192,8 @@ impl Ioapic {
|
|||||||
pub fn new(
|
pub fn new(
|
||||||
id: String,
|
id: String,
|
||||||
apic_address: GuestAddress,
|
apic_address: GuestAddress,
|
||||||
interrupt_manager: &dyn InterruptManager<GroupConfig = MsiIrqGroupConfig>,
|
interrupt_manager: Arc<dyn InterruptManager<GroupConfig = MsiIrqGroupConfig>>,
|
||||||
state: Option<&IoapicState>,
|
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 {
|
||||||
@@ -236,14 +237,9 @@ impl Ioapic {
|
|||||||
if state.is_some() {
|
if state.is_some() {
|
||||||
for (irq, entry) in ioapic.used_entries.iter().enumerate() {
|
for (irq, entry) in ioapic.used_entries.iter().enumerate() {
|
||||||
if *entry {
|
if *entry {
|
||||||
ioapic.update_entry(irq, false)?;
|
ioapic.update_entry(irq)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ioapic
|
|
||||||
.interrupt_source_group
|
|
||||||
.set_gsi()
|
|
||||||
.map_err(Error::UpdateInterrupt)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(ioapic)
|
Ok(ioapic)
|
||||||
@@ -267,7 +263,7 @@ impl Ioapic {
|
|||||||
IOWIN_OFF..=REG_MAX_OFFSET => {
|
IOWIN_OFF..=REG_MAX_OFFSET => {
|
||||||
let (index, is_high_bits) = decode_irq_from_selector(self.reg_sel as u8);
|
let (index, is_high_bits) = decode_irq_from_selector(self.reg_sel as u8);
|
||||||
if index > NUM_IOAPIC_PINS {
|
if index > NUM_IOAPIC_PINS {
|
||||||
warn!("IOAPIC index out of range: {index}");
|
warn!("IOAPIC index out of range: {}", index);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if is_high_bits {
|
if is_high_bits {
|
||||||
@@ -282,8 +278,8 @@ 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.
|
||||||
self.used_entries[index] = true;
|
self.used_entries[index] = true;
|
||||||
@@ -304,7 +300,7 @@ impl Ioapic {
|
|||||||
IOWIN_OFF..=REG_MAX_OFFSET => {
|
IOWIN_OFF..=REG_MAX_OFFSET => {
|
||||||
let (index, is_high_bits) = decode_irq_from_selector(self.reg_sel as u8);
|
let (index, is_high_bits) = decode_irq_from_selector(self.reg_sel as u8);
|
||||||
if index > NUM_IOAPIC_PINS {
|
if index > NUM_IOAPIC_PINS {
|
||||||
warn!("IOAPIC index out of range: {index}");
|
warn!("IOAPIC index out of range: {}", index);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
if is_high_bits {
|
if is_high_bits {
|
||||||
@@ -333,7 +329,7 @@ impl Ioapic {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_entry(&self, irq: usize, set_gsi: bool) -> Result<()> {
|
fn update_entry(&self, irq: usize) -> Result<()> {
|
||||||
let entry = self.reg_entries[irq];
|
let entry = self.reg_entries[irq];
|
||||||
|
|
||||||
// Validate Destination Mode value, and retrieve Destination ID
|
// Validate Destination Mode value, and retrieve Destination ID
|
||||||
@@ -347,9 +343,9 @@ impl Ioapic {
|
|||||||
|
|
||||||
// Generate MSI message address
|
// Generate MSI message address
|
||||||
let low_addr: u32 = self.apic_address.0 as u32
|
let low_addr: u32 = self.apic_address.0 as u32
|
||||||
| (u32::from(destination_id) << 12)
|
| u32::from(destination_id) << 12
|
||||||
| (u32::from(redirection_hint) << 3)
|
| u32::from(redirection_hint) << 3
|
||||||
| (u32::from(destination_mode) << 2);
|
| u32::from(destination_mode) << 2;
|
||||||
|
|
||||||
// Validate Trigger Mode value
|
// Validate Trigger Mode value
|
||||||
let trigger_mode = trigger_mode(entry);
|
let trigger_mode = trigger_mode(entry);
|
||||||
@@ -373,9 +369,9 @@ impl Ioapic {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Generate MSI message data
|
// Generate MSI message data
|
||||||
let data: u32 = (u32::from(trigger_mode) << 15)
|
let data: u32 = u32::from(trigger_mode) << 15
|
||||||
| (u32::from(remote_irr(entry)) << 14)
|
| u32::from(remote_irr(entry)) << 14
|
||||||
| (u32::from(delivery_mode) << 8)
|
| u32::from(delivery_mode) << 8
|
||||||
| u32::from(vector(entry));
|
| u32::from(vector(entry));
|
||||||
|
|
||||||
let config = MsiIrqSourceConfig {
|
let config = MsiIrqSourceConfig {
|
||||||
@@ -390,7 +386,6 @@ impl Ioapic {
|
|||||||
irq as InterruptIndex,
|
irq as InterruptIndex,
|
||||||
InterruptSourceConfig::MsiIrq(config),
|
InterruptSourceConfig::MsiIrq(config),
|
||||||
interrupt_mask(entry) == 1,
|
interrupt_mask(entry) == 1,
|
||||||
set_gsi,
|
|
||||||
)
|
)
|
||||||
.map_err(Error::UpdateInterrupt)?;
|
.map_err(Error::UpdateInterrupt)?;
|
||||||
|
|
||||||
@@ -419,7 +414,7 @@ impl InterruptController for Ioapic {
|
|||||||
self.interrupt_source_group
|
self.interrupt_source_group
|
||||||
.trigger(irq as InterruptIndex)
|
.trigger(irq as InterruptIndex)
|
||||||
.map_err(Error::TriggerInterrupt)?;
|
.map_err(Error::TriggerInterrupt)?;
|
||||||
trace!("Interrupt {irq} successfully delivered");
|
debug!("Interrupt successfully delivered");
|
||||||
|
|
||||||
// If trigger mode is level sensitive, set the Remote IRR bit.
|
// If trigger mode is level sensitive, set the Remote IRR bit.
|
||||||
// It will be cleared when the EOI is received.
|
// It will be cleared when the EOI is received.
|
||||||
@@ -443,7 +438,7 @@ impl Snapshottable for Ioapic {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
|
fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
|
||||||
Snapshot::new_from_state(&self.state())
|
Snapshot::new_from_versioned_state(&self.state())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,415 +0,0 @@
|
|||||||
// Copyright © 2024 Tencent Corporation. All rights reserved.
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
//
|
|
||||||
|
|
||||||
use std::any::Any;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::result;
|
|
||||||
use std::sync::atomic::{AtomicU32, Ordering};
|
|
||||||
use std::sync::{Arc, Barrier, Mutex};
|
|
||||||
|
|
||||||
use anyhow::anyhow;
|
|
||||||
use byteorder::{ByteOrder, LittleEndian};
|
|
||||||
use log::{debug, error, warn};
|
|
||||||
use pci::{
|
|
||||||
BarReprogrammingParams, PCI_CONFIGURATION_ID, PciBarConfiguration, PciBarPrefetchable,
|
|
||||||
PciBarRegionType, PciClassCode, PciConfiguration, PciDevice, PciDeviceError, PciHeaderType,
|
|
||||||
PciSubclass,
|
|
||||||
};
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use thiserror::Error;
|
|
||||||
use vm_allocator::{AddressAllocator, SystemAllocator};
|
|
||||||
use vm_device::{BusDevice, Resource, UserspaceMapping};
|
|
||||||
use vm_memory::bitmap::AtomicBitmap;
|
|
||||||
use vm_memory::{Address, GuestAddress};
|
|
||||||
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
|
|
||||||
|
|
||||||
const IVSHMEM_BAR0_IDX: usize = 0;
|
|
||||||
const IVSHMEM_BAR1_IDX: usize = 1;
|
|
||||||
const IVSHMEM_BAR2_IDX: usize = 2;
|
|
||||||
|
|
||||||
const IVSHMEM_VENDOR_ID: u16 = 0x1af4;
|
|
||||||
const IVSHMEM_DEVICE_ID: u16 = 0x1110;
|
|
||||||
|
|
||||||
const IVSHMEM_REG_BAR_SIZE: u64 = 0x100;
|
|
||||||
|
|
||||||
type MmapRegion = vm_memory::MmapRegion<AtomicBitmap>;
|
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
|
||||||
pub enum IvshmemError {
|
|
||||||
#[error("Failed to retrieve PciConfigurationState: {0}")]
|
|
||||||
RetrievePciConfigurationState(#[source] anyhow::Error),
|
|
||||||
#[error("Failed to retrieve IvshmemDeviceState: {0}")]
|
|
||||||
RetrieveIvshmemDeviceStateState(#[source] anyhow::Error),
|
|
||||||
#[error("Failed to remove user memory region")]
|
|
||||||
RemoveUserMemoryRegion,
|
|
||||||
#[error("Failed to create user memory region.")]
|
|
||||||
CreateUserMemoryRegion,
|
|
||||||
#[error("Failed to create userspace mapping.")]
|
|
||||||
CreateUserspaceMapping,
|
|
||||||
#[error("Failed to remove old userspace mapping.")]
|
|
||||||
RemoveUserspaceMapping,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
|
||||||
pub enum IvshmemSubclass {
|
|
||||||
Other = 0x00,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PciSubclass for IvshmemSubclass {
|
|
||||||
fn get_register_value(&self) -> u8 {
|
|
||||||
*self as u8
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait IvshmemOps: Send + Sync {
|
|
||||||
fn map_ram_region(
|
|
||||||
&mut self,
|
|
||||||
start_addr: u64,
|
|
||||||
size: usize,
|
|
||||||
backing_file: Option<PathBuf>,
|
|
||||||
) -> Result<(Arc<MmapRegion>, UserspaceMapping), IvshmemError>;
|
|
||||||
|
|
||||||
fn unmap_ram_region(&mut self, mapping: UserspaceMapping) -> Result<(), IvshmemError>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Inner-Vm Shared Memory Device (Ivshmem device)
|
|
||||||
///
|
|
||||||
/// This device can share memory between host and guest(ivshmem-plain)
|
|
||||||
/// and share memory between guests(ivshmem-doorbell).
|
|
||||||
/// But only ivshmem-plain support now, ivshmem-doorbell doesn't support yet.
|
|
||||||
pub struct IvshmemDevice {
|
|
||||||
id: String,
|
|
||||||
|
|
||||||
// ivshmem device registers
|
|
||||||
// (only used for ivshmem-doorbell, ivshmem-doorbell don't support yet)
|
|
||||||
_interrupt_mask: u32,
|
|
||||||
_interrupt_status: Arc<AtomicU32>,
|
|
||||||
_iv_position: u32,
|
|
||||||
_doorbell: u32,
|
|
||||||
|
|
||||||
// PCI configuration registers.
|
|
||||||
configuration: PciConfiguration,
|
|
||||||
bar_regions: Vec<PciBarConfiguration>,
|
|
||||||
|
|
||||||
region_size: u64,
|
|
||||||
ivshmem_ops: Arc<Mutex<dyn IvshmemOps>>,
|
|
||||||
backend_file: Option<PathBuf>,
|
|
||||||
region: Option<Arc<MmapRegion>>,
|
|
||||||
userspace_mapping: Option<UserspaceMapping>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Default, Clone)]
|
|
||||||
pub struct IvshmemDeviceState {
|
|
||||||
interrupt_mask: u32,
|
|
||||||
interrupt_status: u32,
|
|
||||||
iv_position: u32,
|
|
||||||
doorbell: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IvshmemDevice {
|
|
||||||
pub fn new(
|
|
||||||
id: String,
|
|
||||||
region_size: u64,
|
|
||||||
backend_file: Option<PathBuf>,
|
|
||||||
ivshmem_ops: Arc<Mutex<dyn IvshmemOps>>,
|
|
||||||
snapshot: Option<&Snapshot>,
|
|
||||||
) -> Result<Self, IvshmemError> {
|
|
||||||
let pci_configuration_state = vm_migration::state_from_id(snapshot, PCI_CONFIGURATION_ID)
|
|
||||||
.map_err(|e| {
|
|
||||||
IvshmemError::RetrievePciConfigurationState(anyhow!(
|
|
||||||
"Failed to get PciConfigurationState from Snapshot: {e}",
|
|
||||||
))
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let state: Option<IvshmemDeviceState> = snapshot
|
|
||||||
.as_ref()
|
|
||||||
.map(|s| s.to_state())
|
|
||||||
.transpose()
|
|
||||||
.map_err(|e| {
|
|
||||||
IvshmemError::RetrieveIvshmemDeviceStateState(anyhow!(
|
|
||||||
"Failed to get IvshmemDeviceState from Snapshot: {e}",
|
|
||||||
))
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let configuration = PciConfiguration::new(
|
|
||||||
IVSHMEM_VENDOR_ID,
|
|
||||||
IVSHMEM_DEVICE_ID,
|
|
||||||
0x1,
|
|
||||||
PciClassCode::MemoryController,
|
|
||||||
&IvshmemSubclass::Other,
|
|
||||||
None,
|
|
||||||
PciHeaderType::Device,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
None,
|
|
||||||
pci_configuration_state,
|
|
||||||
);
|
|
||||||
|
|
||||||
let device = if let Some(s) = state {
|
|
||||||
IvshmemDevice {
|
|
||||||
id,
|
|
||||||
configuration,
|
|
||||||
bar_regions: vec![],
|
|
||||||
_interrupt_mask: s.interrupt_mask,
|
|
||||||
_interrupt_status: Arc::new(AtomicU32::new(s.interrupt_status)),
|
|
||||||
_iv_position: s.iv_position,
|
|
||||||
_doorbell: s.doorbell,
|
|
||||||
region_size,
|
|
||||||
ivshmem_ops,
|
|
||||||
region: None,
|
|
||||||
userspace_mapping: None,
|
|
||||||
backend_file,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
IvshmemDevice {
|
|
||||||
id,
|
|
||||||
configuration,
|
|
||||||
bar_regions: vec![],
|
|
||||||
_interrupt_mask: 0,
|
|
||||||
_interrupt_status: Arc::new(AtomicU32::new(0)),
|
|
||||||
_iv_position: 0,
|
|
||||||
_doorbell: 0,
|
|
||||||
region_size,
|
|
||||||
ivshmem_ops,
|
|
||||||
region: None,
|
|
||||||
userspace_mapping: None,
|
|
||||||
backend_file,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Ok(device)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_region(&mut self, region: Arc<MmapRegion>, userspace_mapping: UserspaceMapping) {
|
|
||||||
self.region = Some(region);
|
|
||||||
self.userspace_mapping = Some(userspace_mapping);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn config_bar_addr(&self) -> u64 {
|
|
||||||
self.configuration.get_bar_addr(IVSHMEM_BAR0_IDX)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn data_bar_addr(&self) -> u64 {
|
|
||||||
self.configuration.get_bar_addr(IVSHMEM_BAR2_IDX)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn state(&self) -> IvshmemDeviceState {
|
|
||||||
IvshmemDeviceState {
|
|
||||||
interrupt_mask: self._interrupt_mask,
|
|
||||||
interrupt_status: self._interrupt_status.load(Ordering::SeqCst),
|
|
||||||
iv_position: self._iv_position,
|
|
||||||
doorbell: self._doorbell,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BusDevice for IvshmemDevice {
|
|
||||||
fn read(&mut self, base: u64, offset: u64, data: &mut [u8]) {
|
|
||||||
self.read_bar(base, offset, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write(&mut self, base: u64, offset: u64, data: &[u8]) -> Option<Arc<Barrier>> {
|
|
||||||
self.write_bar(base, offset, data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PciDevice for IvshmemDevice {
|
|
||||||
fn allocate_bars(
|
|
||||||
&mut self,
|
|
||||||
_allocator: &Arc<Mutex<SystemAllocator>>,
|
|
||||||
mmio32_allocator: &mut AddressAllocator,
|
|
||||||
mmio64_allocator: &mut AddressAllocator,
|
|
||||||
resources: Option<Vec<Resource>>,
|
|
||||||
) -> std::result::Result<Vec<PciBarConfiguration>, PciDeviceError> {
|
|
||||||
let mut bars = Vec::new();
|
|
||||||
let mut bar0_addr = None;
|
|
||||||
let mut bar2_addr = None;
|
|
||||||
|
|
||||||
let restoring = resources.is_some();
|
|
||||||
if let Some(resources) = resources {
|
|
||||||
for resource in resources {
|
|
||||||
match resource {
|
|
||||||
Resource::PciBar { index, base, .. } => match index {
|
|
||||||
IVSHMEM_BAR0_IDX => {
|
|
||||||
bar0_addr = Some(GuestAddress(base));
|
|
||||||
}
|
|
||||||
IVSHMEM_BAR1_IDX => {}
|
|
||||||
IVSHMEM_BAR2_IDX => {
|
|
||||||
bar2_addr = Some(GuestAddress(base));
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
error!("Unexpected pci bar index {index}");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
error!("Unexpected resource {resource:?}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if bar0_addr.is_none() || bar2_addr.is_none() {
|
|
||||||
return Err(PciDeviceError::MissingResource);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// BAR0 holds device registers (256 Byte MMIO)
|
|
||||||
let bar0_addr = mmio32_allocator
|
|
||||||
.allocate(bar0_addr, IVSHMEM_REG_BAR_SIZE, None)
|
|
||||||
.ok_or(PciDeviceError::IoAllocationFailed(IVSHMEM_REG_BAR_SIZE))?;
|
|
||||||
debug!("ivshmem bar0 address 0x{:x}", bar0_addr.0);
|
|
||||||
|
|
||||||
let bar0 = PciBarConfiguration::default()
|
|
||||||
.set_index(IVSHMEM_BAR0_IDX)
|
|
||||||
.set_address(bar0_addr.raw_value())
|
|
||||||
.set_size(IVSHMEM_REG_BAR_SIZE)
|
|
||||||
.set_region_type(PciBarRegionType::Memory32BitRegion)
|
|
||||||
.set_prefetchable(PciBarPrefetchable::NotPrefetchable);
|
|
||||||
|
|
||||||
// BAR1 holds MSI-X table and PBA (only ivshmem-doorbell).
|
|
||||||
|
|
||||||
// BAR2 maps the shared memory object
|
|
||||||
let bar2_size = self.region_size;
|
|
||||||
let bar2_addr = mmio64_allocator
|
|
||||||
.allocate(bar2_addr, bar2_size, None)
|
|
||||||
.ok_or(PciDeviceError::IoAllocationFailed(bar2_size))?;
|
|
||||||
debug!("ivshmem bar2 address 0x{:x}", bar2_addr.0);
|
|
||||||
|
|
||||||
let bar2 = PciBarConfiguration::default()
|
|
||||||
.set_index(IVSHMEM_BAR2_IDX)
|
|
||||||
.set_address(bar2_addr.raw_value())
|
|
||||||
.set_size(bar2_size)
|
|
||||||
.set_region_type(PciBarRegionType::Memory64BitRegion)
|
|
||||||
.set_prefetchable(PciBarPrefetchable::Prefetchable);
|
|
||||||
|
|
||||||
if !restoring {
|
|
||||||
self.configuration
|
|
||||||
.add_pci_bar(&bar0)
|
|
||||||
.map_err(|e| PciDeviceError::IoRegistrationFailed(bar0_addr.raw_value(), e))?;
|
|
||||||
self.configuration
|
|
||||||
.add_pci_bar(&bar2)
|
|
||||||
.map_err(|e| PciDeviceError::IoRegistrationFailed(bar2_addr.raw_value(), e))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
bars.push(bar0);
|
|
||||||
bars.push(bar2);
|
|
||||||
self.bar_regions = bars.clone();
|
|
||||||
|
|
||||||
Ok(bars)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn free_bars(
|
|
||||||
&mut self,
|
|
||||||
_allocator: &mut SystemAllocator,
|
|
||||||
_mmio32_allocator: &mut AddressAllocator,
|
|
||||||
_mmio64_allocator: &mut AddressAllocator,
|
|
||||||
) -> std::result::Result<(), PciDeviceError> {
|
|
||||||
unimplemented!("Device hotplug and remove are not supported for ivshmem");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_config_register(
|
|
||||||
&mut self,
|
|
||||||
reg_idx: usize,
|
|
||||||
offset: u64,
|
|
||||||
data: &[u8],
|
|
||||||
) -> (Vec<BarReprogrammingParams>, Option<Arc<Barrier>>) {
|
|
||||||
(
|
|
||||||
self.configuration
|
|
||||||
.write_config_register(reg_idx, offset, data),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_config_register(&mut self, reg_idx: usize) -> u32 {
|
|
||||||
self.configuration.read_reg(reg_idx)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_bar(&mut self, base: u64, offset: u64, data: &mut [u8]) {
|
|
||||||
debug!("read base {base:x} offset {offset}");
|
|
||||||
|
|
||||||
let mut bar_idx = 0;
|
|
||||||
for (idx, bar) in self.bar_regions.iter().enumerate() {
|
|
||||||
if bar.addr() == base {
|
|
||||||
bar_idx = idx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
match bar_idx {
|
|
||||||
// bar 0
|
|
||||||
0 => {
|
|
||||||
// ivshmem don't use interrupt, we return zero now.
|
|
||||||
LittleEndian::write_u32(data, 0);
|
|
||||||
}
|
|
||||||
// bar 2
|
|
||||||
1 => warn!("Unexpected read ivshmem memory idx: {offset}"),
|
|
||||||
_ => {
|
|
||||||
warn!("Invalid bar_idx: {bar_idx}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_bar(&mut self, base: u64, offset: u64, _data: &[u8]) -> Option<Arc<Barrier>> {
|
|
||||||
debug!("write base {base:x} offset {offset}");
|
|
||||||
warn!("Unexpected write ivshmem memory idx: {offset}");
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn move_bar(&mut self, old_base: u64, new_base: u64) -> result::Result<(), std::io::Error> {
|
|
||||||
if new_base == self.data_bar_addr() {
|
|
||||||
if let Some(old_mapping) = self.userspace_mapping.take() {
|
|
||||||
self.ivshmem_ops
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.unmap_ram_region(old_mapping)
|
|
||||||
.map_err(std::io::Error::other)?;
|
|
||||||
}
|
|
||||||
let (region, new_mapping) = self
|
|
||||||
.ivshmem_ops
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.map_ram_region(
|
|
||||||
new_base,
|
|
||||||
self.region_size as usize,
|
|
||||||
self.backend_file.clone(),
|
|
||||||
)
|
|
||||||
.map_err(std::io::Error::other)?;
|
|
||||||
self.set_region(region, new_mapping);
|
|
||||||
}
|
|
||||||
for bar in self.bar_regions.iter_mut() {
|
|
||||||
if bar.addr() == old_base {
|
|
||||||
*bar = bar.set_address(new_base);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn as_any_mut(&mut self) -> &mut dyn Any {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
fn id(&self) -> Option<String> {
|
|
||||||
Some(self.id.clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Pausable for IvshmemDevice {}
|
|
||||||
|
|
||||||
impl Snapshottable for IvshmemDevice {
|
|
||||||
fn id(&self) -> String {
|
|
||||||
self.id.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
// The snapshot/restore (also live migration) support only work for ivshmem-plain mode.
|
|
||||||
// Additional work is needed for supporting ivshmem-doorbell.
|
|
||||||
fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
|
|
||||||
let mut snapshot = Snapshot::new_from_state(&self.state())?;
|
|
||||||
|
|
||||||
// Snapshot PciConfiguration
|
|
||||||
snapshot.add_snapshot(self.configuration.id(), self.configuration.snapshot()?);
|
|
||||||
|
|
||||||
Ok(snapshot)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Transportable for IvshmemDevice {}
|
|
||||||
|
|
||||||
impl Migratable for IvshmemDevice {}
|
|
||||||
@@ -1,21 +1,17 @@
|
|||||||
// Copyright 2017 The Chromium OS Authors. All rights reserved.
|
// Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
|
||||||
|
|
||||||
|
use libc::{clock_gettime, gmtime_r, timespec, tm, CLOCK_REALTIME};
|
||||||
use std::cmp::min;
|
use std::cmp::min;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::mem;
|
||||||
use std::sync::{Arc, Barrier};
|
use std::sync::{Arc, Barrier};
|
||||||
use std::{mem, thread};
|
use vm_device::BusDevice;
|
||||||
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
// https://github.com/rust-lang/libc/issues/1848
|
// https://github.com/rust-lang/libc/issues/1848
|
||||||
#[cfg_attr(target_env = "musl", allow(deprecated))]
|
#[cfg_attr(target_env = "musl", allow(deprecated))]
|
||||||
use libc::time_t;
|
use libc::time_t;
|
||||||
use libc::{CLOCK_REALTIME, clock_gettime, gmtime_r, timespec, tm};
|
|
||||||
use log::{info, warn};
|
|
||||||
use vm_device::BusDevice;
|
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
|
||||||
|
|
||||||
const INDEX_MASK: u8 = 0x7f;
|
const INDEX_MASK: u8 = 0x7f;
|
||||||
const INDEX_OFFSET: u64 = 0x0;
|
const INDEX_OFFSET: u64 = 0x0;
|
||||||
@@ -27,19 +23,13 @@ pub struct Cmos {
|
|||||||
index: u8,
|
index: u8,
|
||||||
data: [u8; DATA_LEN],
|
data: [u8; DATA_LEN],
|
||||||
reset_evt: EventFd,
|
reset_evt: EventFd,
|
||||||
vcpus_kill_signalled: Option<Arc<AtomicBool>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Cmos {
|
impl Cmos {
|
||||||
/// Constructs a CMOS/RTC device with initial data.
|
/// Constructs a CMOS/RTC device with initial data.
|
||||||
/// `mem_below_4g` is the size of memory in bytes below the 32-bit gap.
|
/// `mem_below_4g` is the size of memory in bytes below the 32-bit gap.
|
||||||
/// `mem_above_4g` is the size of memory in bytes above the 32-bit gap.
|
/// `mem_above_4g` is the size of memory in bytes above the 32-bit gap.
|
||||||
pub fn new(
|
pub fn new(mem_below_4g: u64, mem_above_4g: u64, reset_evt: EventFd) -> Cmos {
|
||||||
mem_below_4g: u64,
|
|
||||||
mem_above_4g: u64,
|
|
||||||
reset_evt: EventFd,
|
|
||||||
vcpus_kill_signalled: Option<Arc<AtomicBool>>,
|
|
||||||
) -> Cmos {
|
|
||||||
let mut data = [0u8; DATA_LEN];
|
let mut data = [0u8; DATA_LEN];
|
||||||
|
|
||||||
// Extended memory from 16 MB to 4 GB in units of 64 KB
|
// Extended memory from 16 MB to 4 GB in units of 64 KB
|
||||||
@@ -60,7 +50,6 @@ impl Cmos {
|
|||||||
index: 0,
|
index: 0,
|
||||||
data,
|
data,
|
||||||
reset_evt,
|
reset_evt,
|
||||||
vcpus_kill_signalled,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -78,21 +67,12 @@ impl BusDevice for Cmos {
|
|||||||
if self.index == 0x8f && data[0] == 0 {
|
if self.index == 0x8f && data[0] == 0 {
|
||||||
info!("CMOS reset");
|
info!("CMOS reset");
|
||||||
self.reset_evt.write(1).unwrap();
|
self.reset_evt.write(1).unwrap();
|
||||||
if let Some(vcpus_kill_signalled) = self.vcpus_kill_signalled.take() {
|
|
||||||
// Spin until we are sure the reset_evt has been handled and that when
|
|
||||||
// we return from the KVM_RUN we will exit rather than re-enter the guest.
|
|
||||||
while !vcpus_kill_signalled.load(Ordering::SeqCst) {
|
|
||||||
// This is more effective than thread::yield_now() at
|
|
||||||
// avoiding a priority inversion with the VMM thread
|
|
||||||
thread::sleep(std::time::Duration::from_millis(1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
self.data[(self.index & INDEX_MASK) as usize] = data[0];
|
self.data[(self.index & INDEX_MASK) as usize] = data[0]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
o => warn!("bad write offset on CMOS device: {o}"),
|
o => warn!("bad write offset on CMOS device: {}", o),
|
||||||
}
|
};
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,7 +133,7 @@ impl BusDevice for Cmos {
|
|||||||
0x08 => to_bcd(month as u8),
|
0x08 => to_bcd(month as u8),
|
||||||
0x09 => to_bcd((year % 100) as u8),
|
0x09 => to_bcd((year % 100) as u8),
|
||||||
// Bit 5 for 32kHz clock. Bit 7 for Update in Progress
|
// Bit 5 for 32kHz clock. Bit 7 for Update in Progress
|
||||||
0x0a => (1 << 5) | ((update_in_progress as u8) << 7),
|
0x0a => 1 << 5 | (update_in_progress as u8) << 7,
|
||||||
// Bit 0-6 are reserved and must be 0.
|
// Bit 0-6 are reserved and must be 0.
|
||||||
// Bit 7 must be 1 (CMOS has power)
|
// Bit 7 must be 1 (CMOS has power)
|
||||||
0x0d => 1 << 7,
|
0x0d => 1 << 7,
|
||||||
@@ -165,7 +145,7 @@ impl BusDevice for Cmos {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
o => {
|
o => {
|
||||||
warn!("bad read offset on CMOS device: {o}");
|
warn!("bad read offset on CMOS device: {}", o);
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,6 @@
|
|||||||
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
use log::{error, warn};
|
|
||||||
use vm_device::BusDevice;
|
use vm_device::BusDevice;
|
||||||
|
|
||||||
/// Debug I/O port, see:
|
/// Debug I/O port, see:
|
||||||
@@ -63,7 +61,7 @@ impl DebugPort {
|
|||||||
|
|
||||||
impl BusDevice for DebugPort {
|
impl BusDevice for DebugPort {
|
||||||
fn read(&mut self, _base: u64, _offset: u64, _data: &mut [u8]) {
|
fn read(&mut self, _base: u64, _offset: u64, _data: &mut [u8]) {
|
||||||
error!("Invalid read to debug port");
|
error!("Invalid read to debug port")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write(
|
fn write(
|
||||||
|
|||||||
@@ -1,955 +0,0 @@
|
|||||||
// Copyright 2025 Google LLC.
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
//
|
|
||||||
|
|
||||||
/// Cloud Hypervisor implementation of Qemu's fw_cfg spec
|
|
||||||
/// https://www.qemu.org/docs/master/specs/fw_cfg.html
|
|
||||||
/// Linux kernel fw_cfg driver header
|
|
||||||
/// https://github.com/torvalds/linux/blob/master/include/uapi/linux/qemu_fw_cfg.h
|
|
||||||
/// Uploading files to the guest via fw_cfg is supported for all kernels 4.6+ w/ CONFIG_FW_CFG_SYSFS enabled
|
|
||||||
/// https://cateee.net/lkddb/web-lkddb/FW_CFG_SYSFS.html
|
|
||||||
/// No kernel requirement if above functionality is not required,
|
|
||||||
/// only firmware must implement mechanism to interact with this fw_cfg device
|
|
||||||
use std::{
|
|
||||||
fs::File,
|
|
||||||
io::{ErrorKind, Read, Result, Seek, SeekFrom},
|
|
||||||
mem::offset_of,
|
|
||||||
os::unix::fs::FileExt,
|
|
||||||
sync::{Arc, Barrier},
|
|
||||||
};
|
|
||||||
|
|
||||||
use acpi_tables::rsdp::Rsdp;
|
|
||||||
use arch::RegionType;
|
|
||||||
#[cfg(target_arch = "aarch64")]
|
|
||||||
use arch::aarch64::layout::{
|
|
||||||
MEM_32BIT_DEVICES_START, MEM_32BIT_RESERVED_START, RAM_64BIT_START, RAM_START as HIGH_RAM_START,
|
|
||||||
};
|
|
||||||
#[cfg(target_arch = "x86_64")]
|
|
||||||
use arch::layout::{
|
|
||||||
EBDA_START, HIGH_RAM_START, MEM_32BIT_DEVICES_SIZE, MEM_32BIT_DEVICES_START,
|
|
||||||
MEM_32BIT_RESERVED_START, PCI_MMCONFIG_SIZE, PCI_MMCONFIG_START, RAM_64BIT_START,
|
|
||||||
};
|
|
||||||
use bitfield_struct::bitfield;
|
|
||||||
#[cfg(target_arch = "x86_64")]
|
|
||||||
use linux_loader::bootparam::boot_params;
|
|
||||||
#[cfg(target_arch = "aarch64")]
|
|
||||||
use linux_loader::loader::pe::arm64_image_header as boot_params;
|
|
||||||
use log::{debug, error};
|
|
||||||
use vm_device::BusDevice;
|
|
||||||
use vm_memory::bitmap::AtomicBitmap;
|
|
||||||
use vm_memory::{
|
|
||||||
ByteValued, Bytes, GuestAddress, GuestAddressSpace, GuestMemoryAtomic, GuestMemoryMmap,
|
|
||||||
};
|
|
||||||
use vmm_sys_util::sock_ctrl_msg::IntoIovec;
|
|
||||||
use zerocopy::{FromBytes, FromZeros, Immutable, IntoBytes};
|
|
||||||
|
|
||||||
#[cfg(target_arch = "x86_64")]
|
|
||||||
// https://github.com/project-oak/oak/tree/main/stage0_bin#memory-layout
|
|
||||||
const STAGE0_START_ADDRESS: GuestAddress = GuestAddress(0xfffe_0000);
|
|
||||||
#[cfg(target_arch = "x86_64")]
|
|
||||||
const STAGE0_SIZE: usize = 0x2_0000;
|
|
||||||
const E820_RAM: u32 = 1;
|
|
||||||
const E820_RESERVED: u32 = 2;
|
|
||||||
|
|
||||||
#[cfg(target_arch = "x86_64")]
|
|
||||||
const PORT_FW_CFG_SELECTOR: u64 = 0x510;
|
|
||||||
#[cfg(target_arch = "x86_64")]
|
|
||||||
const PORT_FW_CFG_DATA: u64 = 0x511;
|
|
||||||
#[cfg(target_arch = "x86_64")]
|
|
||||||
const PORT_FW_CFG_DMA_HI: u64 = 0x514;
|
|
||||||
#[cfg(target_arch = "x86_64")]
|
|
||||||
const PORT_FW_CFG_DMA_LO: u64 = 0x518;
|
|
||||||
#[cfg(target_arch = "x86_64")]
|
|
||||||
pub const PORT_FW_CFG_BASE: u64 = 0x510;
|
|
||||||
#[cfg(target_arch = "x86_64")]
|
|
||||||
pub const PORT_FW_CFG_WIDTH: u64 = 0xc;
|
|
||||||
#[cfg(target_arch = "aarch64")]
|
|
||||||
const PORT_FW_CFG_SELECTOR: u64 = 0x9030008;
|
|
||||||
#[cfg(target_arch = "aarch64")]
|
|
||||||
const PORT_FW_CFG_DATA: u64 = 0x9030000;
|
|
||||||
#[cfg(target_arch = "aarch64")]
|
|
||||||
const PORT_FW_CFG_DMA_HI: u64 = 0x9030010;
|
|
||||||
#[cfg(target_arch = "aarch64")]
|
|
||||||
const PORT_FW_CFG_DMA_LO: u64 = 0x9030014;
|
|
||||||
#[cfg(target_arch = "aarch64")]
|
|
||||||
pub const PORT_FW_CFG_BASE: u64 = 0x9030000;
|
|
||||||
#[cfg(target_arch = "aarch64")]
|
|
||||||
pub const PORT_FW_CFG_WIDTH: u64 = 0x10;
|
|
||||||
|
|
||||||
const FW_CFG_SIGNATURE: u16 = 0x00;
|
|
||||||
const FW_CFG_ID: u16 = 0x01;
|
|
||||||
const FW_CFG_KERNEL_SIZE: u16 = 0x08;
|
|
||||||
const FW_CFG_INITRD_SIZE: u16 = 0x0b;
|
|
||||||
const FW_CFG_KERNEL_DATA: u16 = 0x11;
|
|
||||||
const FW_CFG_INITRD_DATA: u16 = 0x12;
|
|
||||||
const FW_CFG_CMDLINE_SIZE: u16 = 0x14;
|
|
||||||
const FW_CFG_CMDLINE_DATA: u16 = 0x15;
|
|
||||||
const FW_CFG_SETUP_SIZE: u16 = 0x17;
|
|
||||||
const FW_CFG_SETUP_DATA: u16 = 0x18;
|
|
||||||
const FW_CFG_FILE_DIR: u16 = 0x19;
|
|
||||||
const FW_CFG_KNOWN_ITEMS: usize = 0x20;
|
|
||||||
|
|
||||||
pub const FW_CFG_FILE_FIRST: u16 = 0x20;
|
|
||||||
pub const FW_CFG_DMA_SIGNATURE: [u8; 8] = *b"QEMU CFG";
|
|
||||||
// https://github.com/torvalds/linux/blob/master/include/uapi/linux/qemu_fw_cfg.h
|
|
||||||
pub const FW_CFG_ACPI_ID: &str = "QEMU0002";
|
|
||||||
// Reserved (must be enabled)
|
|
||||||
const FW_CFG_F_RESERVED: u8 = 1 << 0;
|
|
||||||
// DMA Toggle Bit (enabled by default)
|
|
||||||
const FW_CFG_F_DMA: u8 = 1 << 1;
|
|
||||||
pub const FW_CFG_FEATURE: [u8; 4] = [FW_CFG_F_RESERVED | FW_CFG_F_DMA, 0, 0, 0];
|
|
||||||
|
|
||||||
const COMMAND_ALLOCATE: u32 = 0x1;
|
|
||||||
const COMMAND_ADD_POINTER: u32 = 0x2;
|
|
||||||
const COMMAND_ADD_CHECKSUM: u32 = 0x3;
|
|
||||||
|
|
||||||
const ALLOC_ZONE_HIGH: u8 = 0x1;
|
|
||||||
const ALLOC_ZONE_FSEG: u8 = 0x2;
|
|
||||||
|
|
||||||
const FW_CFG_FILENAME_TABLE_LOADER: &str = "etc/table-loader";
|
|
||||||
const FW_CFG_FILENAME_RSDP: &str = "acpi/rsdp";
|
|
||||||
const FW_CFG_FILENAME_ACPI_TABLES: &str = "acpi/tables";
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum FwCfgContent {
|
|
||||||
Bytes(Vec<u8>),
|
|
||||||
Slice(&'static [u8]),
|
|
||||||
File(u64, File),
|
|
||||||
U32(u32),
|
|
||||||
}
|
|
||||||
|
|
||||||
struct FwCfgContentAccess<'a> {
|
|
||||||
content: &'a FwCfgContent,
|
|
||||||
offset: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Read for FwCfgContentAccess<'_> {
|
|
||||||
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
|
|
||||||
match self.content {
|
|
||||||
FwCfgContent::File(offset, f) => {
|
|
||||||
Seek::seek(&mut (&*f), SeekFrom::Start(offset + self.offset as u64))?;
|
|
||||||
Read::read(&mut (&*f), buf)
|
|
||||||
}
|
|
||||||
FwCfgContent::Bytes(b) => match b.get(self.offset as usize..) {
|
|
||||||
Some(mut s) => s.read(buf),
|
|
||||||
None => Err(ErrorKind::UnexpectedEof)?,
|
|
||||||
},
|
|
||||||
FwCfgContent::Slice(b) => match b.get(self.offset as usize..) {
|
|
||||||
Some(mut s) => s.read(buf),
|
|
||||||
None => Err(ErrorKind::UnexpectedEof)?,
|
|
||||||
},
|
|
||||||
FwCfgContent::U32(n) => match n.to_le_bytes().get(self.offset as usize..) {
|
|
||||||
Some(mut s) => s.read(buf),
|
|
||||||
None => Err(ErrorKind::UnexpectedEof)?,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for FwCfgContent {
|
|
||||||
fn default() -> Self {
|
|
||||||
FwCfgContent::Slice(&[])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FwCfgContent {
|
|
||||||
fn size(&self) -> Result<u32> {
|
|
||||||
let ret = match self {
|
|
||||||
FwCfgContent::Bytes(v) => v.len(),
|
|
||||||
FwCfgContent::File(offset, f) => (f.metadata()?.len() - offset) as usize,
|
|
||||||
FwCfgContent::Slice(s) => s.len(),
|
|
||||||
FwCfgContent::U32(n) => size_of_val(n),
|
|
||||||
};
|
|
||||||
u32::try_from(ret).map_err(|_| std::io::ErrorKind::InvalidInput.into())
|
|
||||||
}
|
|
||||||
fn access(&self, offset: u32) -> FwCfgContentAccess<'_> {
|
|
||||||
FwCfgContentAccess {
|
|
||||||
content: self,
|
|
||||||
offset,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
pub struct FwCfgItem {
|
|
||||||
pub name: String,
|
|
||||||
pub content: FwCfgContent,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// https://www.qemu.org/docs/master/specs/fw_cfg.html
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct FwCfg {
|
|
||||||
selector: u16,
|
|
||||||
data_offset: u32,
|
|
||||||
dma_address: u64,
|
|
||||||
items: Vec<FwCfgItem>, // 0x20 and above
|
|
||||||
known_items: [FwCfgContent; FW_CFG_KNOWN_ITEMS], // 0x0 to 0x19
|
|
||||||
memory: GuestMemoryAtomic<GuestMemoryMmap<AtomicBitmap>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Debug, IntoBytes, FromBytes)]
|
|
||||||
struct FwCfgDmaAccess {
|
|
||||||
control_be: u32,
|
|
||||||
length_be: u32,
|
|
||||||
address_be: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://github.com/torvalds/linux/blob/master/include/uapi/linux/qemu_fw_cfg.h#L67
|
|
||||||
#[bitfield(u32)]
|
|
||||||
struct AccessControl {
|
|
||||||
// FW_CFG_DMA_CTL_ERROR = 0x01
|
|
||||||
error: bool,
|
|
||||||
// FW_CFG_DMA_CTL_READ = 0x02
|
|
||||||
read: bool,
|
|
||||||
#[bits(1)]
|
|
||||||
_unused2: u8,
|
|
||||||
// FW_CFG_DMA_CTL_SKIP = 0x04
|
|
||||||
skip: bool,
|
|
||||||
#[bits(3)]
|
|
||||||
_unused3: u8,
|
|
||||||
// FW_CFG_DMA_CTL_ERROR = 0x08
|
|
||||||
select: bool,
|
|
||||||
#[bits(7)]
|
|
||||||
_unused4: u8,
|
|
||||||
// FW_CFG_DMA_CTL_WRITE = 0x10
|
|
||||||
write: bool,
|
|
||||||
#[bits(16)]
|
|
||||||
_unused: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Debug, IntoBytes, FromBytes)]
|
|
||||||
struct FwCfgFilesHeader {
|
|
||||||
count_be: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const FILE_NAME_SIZE: usize = 56;
|
|
||||||
|
|
||||||
pub fn create_file_name(name: &str) -> [u8; FILE_NAME_SIZE] {
|
|
||||||
let mut c_name = [0u8; FILE_NAME_SIZE];
|
|
||||||
let c_len = std::cmp::min(FILE_NAME_SIZE - 1, name.len());
|
|
||||||
c_name[0..c_len].copy_from_slice(&name.as_bytes()[0..c_len]);
|
|
||||||
c_name
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
#[repr(C, packed)]
|
|
||||||
#[derive(Debug, IntoBytes, FromBytes, Clone, Copy)]
|
|
||||||
struct BootE820Entry {
|
|
||||||
addr: u64,
|
|
||||||
size: u64,
|
|
||||||
type_: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Debug, IntoBytes, FromBytes)]
|
|
||||||
struct FwCfgFile {
|
|
||||||
size_be: u32,
|
|
||||||
select_be: u16,
|
|
||||||
_reserved: u16,
|
|
||||||
name: [u8; FILE_NAME_SIZE],
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(C, align(4))]
|
|
||||||
#[derive(Debug, IntoBytes, Immutable)]
|
|
||||||
struct Allocate {
|
|
||||||
command: u32,
|
|
||||||
file: [u8; FILE_NAME_SIZE],
|
|
||||||
align: u32,
|
|
||||||
zone: u8,
|
|
||||||
_pad: [u8; 63],
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(C, align(4))]
|
|
||||||
#[derive(Debug, IntoBytes, Immutable)]
|
|
||||||
struct AddPointer {
|
|
||||||
command: u32,
|
|
||||||
dst: [u8; FILE_NAME_SIZE],
|
|
||||||
src: [u8; FILE_NAME_SIZE],
|
|
||||||
offset: u32,
|
|
||||||
size: u8,
|
|
||||||
_pad: [u8; 7],
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(C, align(4))]
|
|
||||||
#[derive(Debug, IntoBytes, Immutable)]
|
|
||||||
struct AddChecksum {
|
|
||||||
command: u32,
|
|
||||||
file: [u8; FILE_NAME_SIZE],
|
|
||||||
offset: u32,
|
|
||||||
start: u32,
|
|
||||||
len: u32,
|
|
||||||
_pad: [u8; 56],
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_intra_pointer(name: &str, offset: usize, size: u8) -> AddPointer {
|
|
||||||
AddPointer {
|
|
||||||
command: COMMAND_ADD_POINTER,
|
|
||||||
dst: create_file_name(name),
|
|
||||||
src: create_file_name(name),
|
|
||||||
offset: offset as u32,
|
|
||||||
size,
|
|
||||||
_pad: [0; 7],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_acpi_table_checksum(offset: usize, len: usize) -> AddChecksum {
|
|
||||||
AddChecksum {
|
|
||||||
command: COMMAND_ADD_CHECKSUM,
|
|
||||||
file: create_file_name(FW_CFG_FILENAME_ACPI_TABLES),
|
|
||||||
offset: (offset + offset_of!(AcpiTableHeader, checksum)) as u32,
|
|
||||||
start: offset as u32,
|
|
||||||
len: len as u32,
|
|
||||||
_pad: [0; 56],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(C, align(4))]
|
|
||||||
#[derive(Debug, Clone, Default, FromBytes, IntoBytes)]
|
|
||||||
struct AcpiTableHeader {
|
|
||||||
signature: [u8; 4],
|
|
||||||
length: u32,
|
|
||||||
revision: u8,
|
|
||||||
checksum: u8,
|
|
||||||
oem_id: [u8; 6],
|
|
||||||
oem_table_id: [u8; 8],
|
|
||||||
oem_revision: u32,
|
|
||||||
asl_compiler_id: [u8; 4],
|
|
||||||
asl_compiler_revision: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct AcpiTable {
|
|
||||||
rsdp: Rsdp,
|
|
||||||
tables: Vec<u8>,
|
|
||||||
table_pointers: Vec<usize>,
|
|
||||||
table_checksums: Vec<(usize, usize)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AcpiTable {
|
|
||||||
fn pointers(&self) -> &[usize] {
|
|
||||||
&self.table_pointers
|
|
||||||
}
|
|
||||||
|
|
||||||
fn checksums(&self) -> &[(usize, usize)] {
|
|
||||||
&self.table_checksums
|
|
||||||
}
|
|
||||||
|
|
||||||
fn take(self) -> (Rsdp, Vec<u8>) {
|
|
||||||
(self.rsdp, self.tables)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Creates fw_cfg items used by firmware to load and verify Acpi tables
|
|
||||||
// https://github.com/qemu/qemu/blob/master/hw/acpi/bios-linker-loader.c
|
|
||||||
fn create_acpi_loader(acpi_table: AcpiTable) -> [FwCfgItem; 3] {
|
|
||||||
let mut table_loader_bytes: Vec<u8> = Vec::new();
|
|
||||||
let allocate_rsdp = Allocate {
|
|
||||||
command: COMMAND_ALLOCATE,
|
|
||||||
file: create_file_name(FW_CFG_FILENAME_RSDP),
|
|
||||||
align: 4,
|
|
||||||
zone: ALLOC_ZONE_FSEG,
|
|
||||||
_pad: [0; 63],
|
|
||||||
};
|
|
||||||
table_loader_bytes.extend(allocate_rsdp.as_bytes());
|
|
||||||
|
|
||||||
let allocate_tables = Allocate {
|
|
||||||
command: COMMAND_ALLOCATE,
|
|
||||||
file: create_file_name(FW_CFG_FILENAME_ACPI_TABLES),
|
|
||||||
align: 4,
|
|
||||||
zone: ALLOC_ZONE_HIGH,
|
|
||||||
_pad: [0; 63],
|
|
||||||
};
|
|
||||||
table_loader_bytes.extend(allocate_tables.as_bytes());
|
|
||||||
|
|
||||||
for pointer_offset in acpi_table.pointers().iter() {
|
|
||||||
let pointer = create_intra_pointer(FW_CFG_FILENAME_ACPI_TABLES, *pointer_offset, 8);
|
|
||||||
table_loader_bytes.extend(pointer.as_bytes());
|
|
||||||
}
|
|
||||||
for (offset, len) in acpi_table.checksums().iter() {
|
|
||||||
let checksum = create_acpi_table_checksum(*offset, *len);
|
|
||||||
table_loader_bytes.extend(checksum.as_bytes());
|
|
||||||
}
|
|
||||||
let pointer_rsdp_to_xsdt = AddPointer {
|
|
||||||
command: COMMAND_ADD_POINTER,
|
|
||||||
dst: create_file_name(FW_CFG_FILENAME_RSDP),
|
|
||||||
src: create_file_name(FW_CFG_FILENAME_ACPI_TABLES),
|
|
||||||
offset: offset_of!(Rsdp, xsdt_addr) as u32,
|
|
||||||
size: 8,
|
|
||||||
_pad: [0; 7],
|
|
||||||
};
|
|
||||||
table_loader_bytes.extend(pointer_rsdp_to_xsdt.as_bytes());
|
|
||||||
let checksum_rsdp = AddChecksum {
|
|
||||||
command: COMMAND_ADD_CHECKSUM,
|
|
||||||
file: create_file_name(FW_CFG_FILENAME_RSDP),
|
|
||||||
offset: offset_of!(Rsdp, checksum) as u32,
|
|
||||||
start: 0,
|
|
||||||
len: offset_of!(Rsdp, length) as u32,
|
|
||||||
_pad: [0; 56],
|
|
||||||
};
|
|
||||||
let checksum_rsdp_ext = AddChecksum {
|
|
||||||
command: COMMAND_ADD_CHECKSUM,
|
|
||||||
file: create_file_name(FW_CFG_FILENAME_RSDP),
|
|
||||||
offset: offset_of!(Rsdp, extended_checksum) as u32,
|
|
||||||
start: 0,
|
|
||||||
len: size_of::<Rsdp>() as u32,
|
|
||||||
_pad: [0; 56],
|
|
||||||
};
|
|
||||||
table_loader_bytes.extend(checksum_rsdp.as_bytes());
|
|
||||||
table_loader_bytes.extend(checksum_rsdp_ext.as_bytes());
|
|
||||||
|
|
||||||
let table_loader = FwCfgItem {
|
|
||||||
name: FW_CFG_FILENAME_TABLE_LOADER.to_owned(),
|
|
||||||
content: FwCfgContent::Bytes(table_loader_bytes),
|
|
||||||
};
|
|
||||||
let (rsdp, tables) = acpi_table.take();
|
|
||||||
let acpi_rsdp = FwCfgItem {
|
|
||||||
name: FW_CFG_FILENAME_RSDP.to_owned(),
|
|
||||||
content: FwCfgContent::Bytes(rsdp.as_bytes().to_owned()),
|
|
||||||
};
|
|
||||||
let apci_tables = FwCfgItem {
|
|
||||||
name: FW_CFG_FILENAME_ACPI_TABLES.to_owned(),
|
|
||||||
content: FwCfgContent::Bytes(tables),
|
|
||||||
};
|
|
||||||
[table_loader, acpi_rsdp, apci_tables]
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FwCfg {
|
|
||||||
pub fn new(memory: GuestMemoryAtomic<GuestMemoryMmap<AtomicBitmap>>) -> FwCfg {
|
|
||||||
const DEFAULT_ITEM: FwCfgContent = FwCfgContent::Slice(&[]);
|
|
||||||
let mut known_items = [DEFAULT_ITEM; FW_CFG_KNOWN_ITEMS];
|
|
||||||
known_items[FW_CFG_SIGNATURE as usize] = FwCfgContent::Slice(&FW_CFG_DMA_SIGNATURE);
|
|
||||||
known_items[FW_CFG_ID as usize] = FwCfgContent::Slice(&FW_CFG_FEATURE);
|
|
||||||
let file_buf = Vec::from(FwCfgFilesHeader { count_be: 0 }.as_mut_bytes());
|
|
||||||
known_items[FW_CFG_FILE_DIR as usize] = FwCfgContent::Bytes(file_buf);
|
|
||||||
|
|
||||||
FwCfg {
|
|
||||||
selector: 0,
|
|
||||||
data_offset: 0,
|
|
||||||
dma_address: 0,
|
|
||||||
items: vec![],
|
|
||||||
known_items,
|
|
||||||
memory,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn populate_fw_cfg(
|
|
||||||
&mut self,
|
|
||||||
mem_size: Option<usize>,
|
|
||||||
kernel: Option<File>,
|
|
||||||
initramfs: Option<File>,
|
|
||||||
cmdline: Option<std::ffi::CString>,
|
|
||||||
fw_cfg_item_list: Option<Vec<FwCfgItem>>,
|
|
||||||
) -> Result<()> {
|
|
||||||
if let Some(mem_size) = mem_size {
|
|
||||||
self.add_e820(mem_size)?;
|
|
||||||
}
|
|
||||||
if let Some(kernel) = kernel {
|
|
||||||
self.add_kernel_data(&kernel)?;
|
|
||||||
}
|
|
||||||
if let Some(cmdline) = cmdline {
|
|
||||||
self.add_kernel_cmdline(cmdline);
|
|
||||||
}
|
|
||||||
if let Some(initramfs) = initramfs {
|
|
||||||
self.add_initramfs_data(&initramfs)?;
|
|
||||||
}
|
|
||||||
if let Some(fw_cfg_item_list) = fw_cfg_item_list {
|
|
||||||
for item in fw_cfg_item_list {
|
|
||||||
self.add_item(item)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_e820(&mut self, mem_size: usize) -> Result<()> {
|
|
||||||
#[cfg(target_arch = "x86_64")]
|
|
||||||
let mut mem_regions = vec![
|
|
||||||
(GuestAddress(0), EBDA_START.0 as usize, RegionType::Ram),
|
|
||||||
(
|
|
||||||
MEM_32BIT_DEVICES_START,
|
|
||||||
MEM_32BIT_DEVICES_SIZE as usize,
|
|
||||||
RegionType::Reserved,
|
|
||||||
),
|
|
||||||
(
|
|
||||||
PCI_MMCONFIG_START,
|
|
||||||
PCI_MMCONFIG_SIZE as usize,
|
|
||||||
RegionType::Reserved,
|
|
||||||
),
|
|
||||||
(STAGE0_START_ADDRESS, STAGE0_SIZE, RegionType::Reserved),
|
|
||||||
];
|
|
||||||
#[cfg(target_arch = "aarch64")]
|
|
||||||
let mut mem_regions = arch::aarch64::arch_memory_regions();
|
|
||||||
if mem_size < MEM_32BIT_DEVICES_START.0 as usize {
|
|
||||||
mem_regions.push((
|
|
||||||
HIGH_RAM_START,
|
|
||||||
mem_size - HIGH_RAM_START.0 as usize,
|
|
||||||
RegionType::Ram,
|
|
||||||
));
|
|
||||||
} else {
|
|
||||||
mem_regions.push((
|
|
||||||
HIGH_RAM_START,
|
|
||||||
MEM_32BIT_RESERVED_START.0 as usize - HIGH_RAM_START.0 as usize,
|
|
||||||
RegionType::Ram,
|
|
||||||
));
|
|
||||||
mem_regions.push((
|
|
||||||
RAM_64BIT_START,
|
|
||||||
mem_size - (MEM_32BIT_DEVICES_START.0 as usize),
|
|
||||||
RegionType::Ram,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
let mut bytes = vec![];
|
|
||||||
for (addr, size, region) in mem_regions.iter() {
|
|
||||||
let type_ = match region {
|
|
||||||
RegionType::Ram => E820_RAM,
|
|
||||||
RegionType::Reserved => E820_RESERVED,
|
|
||||||
RegionType::SubRegion => continue,
|
|
||||||
};
|
|
||||||
let mut entry = BootE820Entry {
|
|
||||||
addr: addr.0,
|
|
||||||
size: *size as u64,
|
|
||||||
type_,
|
|
||||||
};
|
|
||||||
bytes.extend_from_slice(entry.as_mut_bytes());
|
|
||||||
}
|
|
||||||
let item = FwCfgItem {
|
|
||||||
name: "etc/e820".to_owned(),
|
|
||||||
content: FwCfgContent::Bytes(bytes),
|
|
||||||
};
|
|
||||||
self.add_item(item)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn file_dir_mut(&mut self) -> &mut Vec<u8> {
|
|
||||||
let FwCfgContent::Bytes(file_buf) = &mut self.known_items[FW_CFG_FILE_DIR as usize] else {
|
|
||||||
unreachable!("fw_cfg: selector {FW_CFG_FILE_DIR:#x} should be FwCfgContent::Byte!")
|
|
||||||
};
|
|
||||||
file_buf
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update_count(&mut self) {
|
|
||||||
let mut header = FwCfgFilesHeader {
|
|
||||||
count_be: (self.items.len() as u32).to_be(),
|
|
||||||
};
|
|
||||||
self.file_dir_mut()[0..4].copy_from_slice(header.as_mut_bytes());
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_item(&mut self, item: FwCfgItem) -> Result<()> {
|
|
||||||
let index = self.items.len();
|
|
||||||
let c_name = create_file_name(&item.name);
|
|
||||||
let size = item.content.size()?;
|
|
||||||
let mut cfg_file = FwCfgFile {
|
|
||||||
size_be: size.to_be(),
|
|
||||||
select_be: (FW_CFG_FILE_FIRST + index as u16).to_be(),
|
|
||||||
_reserved: 0,
|
|
||||||
name: c_name,
|
|
||||||
};
|
|
||||||
self.file_dir_mut()
|
|
||||||
.extend_from_slice(cfg_file.as_mut_bytes());
|
|
||||||
self.items.push(item);
|
|
||||||
self.update_count();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn dma_read_content(
|
|
||||||
&self,
|
|
||||||
content: &FwCfgContent,
|
|
||||||
offset: u32,
|
|
||||||
len: u32,
|
|
||||||
address: u64,
|
|
||||||
) -> Result<u32> {
|
|
||||||
let content_size = content.size()?.saturating_sub(offset);
|
|
||||||
let op_size = std::cmp::min(content_size, len);
|
|
||||||
let mut access = content.access(offset);
|
|
||||||
let mut buf = vec![0u8; op_size as usize];
|
|
||||||
access.read_exact(buf.as_mut_bytes())?;
|
|
||||||
let r = self
|
|
||||||
.memory
|
|
||||||
.memory()
|
|
||||||
.write(buf.as_bytes(), GuestAddress(address));
|
|
||||||
match r {
|
|
||||||
Err(e) => {
|
|
||||||
error!("fw_cfg: dma read error: {e:x?}");
|
|
||||||
Err(ErrorKind::InvalidInput.into())
|
|
||||||
}
|
|
||||||
Ok(size) => Ok(size as u32),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn dma_read(&mut self, selector: u16, len: u32, address: u64) -> Result<()> {
|
|
||||||
let op_size = if let Some(content) = self.known_items.get(selector as usize) {
|
|
||||||
self.dma_read_content(content, self.data_offset, len, address)
|
|
||||||
} else if let Some(item) = self.items.get((selector - FW_CFG_FILE_FIRST) as usize) {
|
|
||||||
self.dma_read_content(&item.content, self.data_offset, len, address)
|
|
||||||
} else {
|
|
||||||
error!("fw_cfg: selector {selector:#x} does not exist.");
|
|
||||||
Err(ErrorKind::NotFound.into())
|
|
||||||
}?;
|
|
||||||
self.data_offset += op_size;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn do_dma(&mut self) {
|
|
||||||
let dma_address = self.dma_address;
|
|
||||||
let mut access = FwCfgDmaAccess::new_zeroed();
|
|
||||||
let dma_access = match self
|
|
||||||
.memory
|
|
||||||
.memory()
|
|
||||||
.read(access.as_mut_bytes(), GuestAddress(dma_address))
|
|
||||||
{
|
|
||||||
Ok(_) => access,
|
|
||||||
Err(e) => {
|
|
||||||
error!("fw_cfg: invalid address of dma access {dma_address:#x}: {e:?}");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let control = AccessControl(u32::from_be(dma_access.control_be));
|
|
||||||
if control.select() {
|
|
||||||
self.selector = control.select() as u16;
|
|
||||||
}
|
|
||||||
let len = u32::from_be(dma_access.length_be);
|
|
||||||
let addr = u64::from_be(dma_access.address_be);
|
|
||||||
let ret = if control.read() {
|
|
||||||
self.dma_read(self.selector, len, addr)
|
|
||||||
} else if control.write() {
|
|
||||||
Err(ErrorKind::InvalidInput.into())
|
|
||||||
} else if control.skip() {
|
|
||||||
self.data_offset += len;
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(ErrorKind::InvalidData.into())
|
|
||||||
};
|
|
||||||
let mut access_resp = AccessControl(0);
|
|
||||||
if let Err(e) = ret {
|
|
||||||
error!("fw_cfg: dma operation {dma_access:x?}: {e:x?}");
|
|
||||||
access_resp.set_error(true);
|
|
||||||
}
|
|
||||||
if let Err(e) = self.memory.memory().write(
|
|
||||||
&access_resp.0.to_be_bytes(),
|
|
||||||
GuestAddress(dma_address + core::mem::offset_of!(FwCfgDmaAccess, control_be) as u64),
|
|
||||||
) {
|
|
||||||
error!("fw_cfg: finishing dma: {e:?}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_kernel_data(&mut self, file: &File) -> Result<()> {
|
|
||||||
let mut buffer = vec![0u8; size_of::<boot_params>()];
|
|
||||||
file.read_exact_at(&mut buffer, 0)?;
|
|
||||||
let bp = boot_params::from_mut_slice(&mut buffer).unwrap();
|
|
||||||
#[cfg(target_arch = "x86_64")]
|
|
||||||
{
|
|
||||||
// must set to 4 for backwards compatibility
|
|
||||||
// https://docs.kernel.org/arch/x86/boot.html#the-real-mode-kernel-header
|
|
||||||
if bp.hdr.setup_sects == 0 {
|
|
||||||
bp.hdr.setup_sects = 4;
|
|
||||||
}
|
|
||||||
// wildcard boot loader type
|
|
||||||
bp.hdr.type_of_loader = 0xff;
|
|
||||||
}
|
|
||||||
#[cfg(target_arch = "aarch64")]
|
|
||||||
let kernel_start = bp.text_offset;
|
|
||||||
#[cfg(target_arch = "x86_64")]
|
|
||||||
let kernel_start = (bp.hdr.setup_sects as usize + 1) * 512;
|
|
||||||
self.known_items[FW_CFG_SETUP_SIZE as usize] = FwCfgContent::U32(buffer.len() as u32);
|
|
||||||
self.known_items[FW_CFG_SETUP_DATA as usize] = FwCfgContent::Bytes(buffer);
|
|
||||||
self.known_items[FW_CFG_KERNEL_SIZE as usize] =
|
|
||||||
FwCfgContent::U32(file.metadata()?.len() as u32 - kernel_start as u32);
|
|
||||||
self.known_items[FW_CFG_KERNEL_DATA as usize] =
|
|
||||||
FwCfgContent::File(kernel_start as u64, file.try_clone()?);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_kernel_cmdline(&mut self, s: std::ffi::CString) {
|
|
||||||
let bytes = s.into_bytes_with_nul();
|
|
||||||
self.known_items[FW_CFG_CMDLINE_SIZE as usize] = FwCfgContent::U32(bytes.len() as u32);
|
|
||||||
self.known_items[FW_CFG_CMDLINE_DATA as usize] = FwCfgContent::Bytes(bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_acpi(
|
|
||||||
&mut self,
|
|
||||||
rsdp: Rsdp,
|
|
||||||
tables: Vec<u8>,
|
|
||||||
table_checksums: Vec<(usize, usize)>,
|
|
||||||
table_pointers: Vec<usize>,
|
|
||||||
) -> Result<()> {
|
|
||||||
let acpi_table = AcpiTable {
|
|
||||||
rsdp,
|
|
||||||
tables,
|
|
||||||
table_checksums,
|
|
||||||
table_pointers,
|
|
||||||
};
|
|
||||||
let [table_loader, acpi_rsdp, apci_tables] = create_acpi_loader(acpi_table);
|
|
||||||
self.add_item(table_loader)?;
|
|
||||||
self.add_item(acpi_rsdp)?;
|
|
||||||
self.add_item(apci_tables)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_initramfs_data(&mut self, file: &File) -> Result<()> {
|
|
||||||
let initramfs_size = file.metadata()?.len();
|
|
||||||
self.known_items[FW_CFG_INITRD_SIZE as usize] = FwCfgContent::U32(initramfs_size as _);
|
|
||||||
self.known_items[FW_CFG_INITRD_DATA as usize] = FwCfgContent::File(0, file.try_clone()?);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_content(content: &FwCfgContent, offset: u32, data: &mut [u8], size: u32) -> Option<u8> {
|
|
||||||
let start = offset as usize;
|
|
||||||
let end = start + size as usize;
|
|
||||||
match content {
|
|
||||||
FwCfgContent::Bytes(b) => {
|
|
||||||
if b.len() >= size as usize {
|
|
||||||
data.copy_from_slice(&b[start..end]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
FwCfgContent::Slice(s) => {
|
|
||||||
if s.len() >= size as usize {
|
|
||||||
data.copy_from_slice(&s[start..end]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
FwCfgContent::File(o, f) => {
|
|
||||||
f.read_exact_at(data, o + offset as u64).ok()?;
|
|
||||||
}
|
|
||||||
FwCfgContent::U32(n) => {
|
|
||||||
let bytes = n.to_le_bytes();
|
|
||||||
data.copy_from_slice(&bytes[start..end]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some(size as u8)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_data(&mut self, data: &mut [u8], size: u32) -> u8 {
|
|
||||||
let ret = if let Some(content) = self.known_items.get(self.selector as usize) {
|
|
||||||
Self::read_content(content, self.data_offset, data, size)
|
|
||||||
} else if let Some(item) = self.items.get((self.selector - FW_CFG_FILE_FIRST) as usize) {
|
|
||||||
Self::read_content(&item.content, self.data_offset, data, size)
|
|
||||||
} else {
|
|
||||||
error!("fw_cfg: selector {:#x} does not exist.", self.selector);
|
|
||||||
None
|
|
||||||
};
|
|
||||||
if let Some(val) = ret {
|
|
||||||
self.data_offset += size;
|
|
||||||
val
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BusDevice for FwCfg {
|
|
||||||
fn read(&mut self, _base: u64, offset: u64, data: &mut [u8]) {
|
|
||||||
let port = offset + PORT_FW_CFG_BASE;
|
|
||||||
let size = data.len();
|
|
||||||
match (port, size) {
|
|
||||||
(PORT_FW_CFG_SELECTOR, _) => {
|
|
||||||
error!("fw_cfg: selector register is write-only.");
|
|
||||||
}
|
|
||||||
(PORT_FW_CFG_DATA, _) => _ = self.read_data(data, size as u32),
|
|
||||||
(PORT_FW_CFG_DMA_HI, 4) => {
|
|
||||||
let addr = self.dma_address;
|
|
||||||
let addr_hi = (addr >> 32) as u32;
|
|
||||||
data.copy_from_slice(&addr_hi.to_be_bytes());
|
|
||||||
}
|
|
||||||
(PORT_FW_CFG_DMA_LO, 4) => {
|
|
||||||
let addr = self.dma_address;
|
|
||||||
let addr_lo = (addr & 0xffff_ffff) as u32;
|
|
||||||
data.copy_from_slice(&addr_lo.to_be_bytes());
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
debug!(
|
|
||||||
"fw_cfg: read from unknown port {port:#x}: {size:#x} bytes and offset {offset:#x}."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write(&mut self, _base: u64, offset: u64, data: &[u8]) -> Option<Arc<Barrier>> {
|
|
||||||
let port = offset + PORT_FW_CFG_BASE;
|
|
||||||
let size = data.size();
|
|
||||||
match (port, size) {
|
|
||||||
(PORT_FW_CFG_SELECTOR, 2) => {
|
|
||||||
let mut buf = [0u8; 2];
|
|
||||||
buf[..size].copy_from_slice(&data[..size]);
|
|
||||||
#[cfg(target_arch = "x86_64")]
|
|
||||||
let val = u16::from_le_bytes(buf);
|
|
||||||
#[cfg(target_arch = "aarch64")]
|
|
||||||
let val = u16::from_be_bytes(buf);
|
|
||||||
self.selector = val;
|
|
||||||
self.data_offset = 0;
|
|
||||||
}
|
|
||||||
(PORT_FW_CFG_DATA, 1) => error!("fw_cfg: data register is read-only."),
|
|
||||||
(PORT_FW_CFG_DMA_HI, 4) => {
|
|
||||||
let mut buf = [0u8; 4];
|
|
||||||
buf[..size].copy_from_slice(&data[..size]);
|
|
||||||
let val = u32::from_be_bytes(buf);
|
|
||||||
self.dma_address &= 0xffff_ffff;
|
|
||||||
self.dma_address |= (val as u64) << 32;
|
|
||||||
}
|
|
||||||
(PORT_FW_CFG_DMA_LO, 4) => {
|
|
||||||
let mut buf = [0u8; 4];
|
|
||||||
buf[..size].copy_from_slice(&data[..size]);
|
|
||||||
let val = u32::from_be_bytes(buf);
|
|
||||||
self.dma_address &= !0xffff_ffff;
|
|
||||||
self.dma_address |= val as u64;
|
|
||||||
self.do_dma();
|
|
||||||
}
|
|
||||||
_ => debug!(
|
|
||||||
"fw_cfg: write to unknown port {port:#x}: {size:#x} bytes and offset {offset:#x} ."
|
|
||||||
),
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod unit_tests {
|
|
||||||
use std::ffi::CString;
|
|
||||||
use std::io::Write;
|
|
||||||
|
|
||||||
use vmm_sys_util::tempfile::TempFile;
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[cfg(target_arch = "x86_64")]
|
|
||||||
const SELECTOR_OFFSET: u64 = 0;
|
|
||||||
#[cfg(target_arch = "aarch64")]
|
|
||||||
const SELECTOR_OFFSET: u64 = 8;
|
|
||||||
#[cfg(target_arch = "x86_64")]
|
|
||||||
const DATA_OFFSET: u64 = 1;
|
|
||||||
#[cfg(target_arch = "aarch64")]
|
|
||||||
const DATA_OFFSET: u64 = 0;
|
|
||||||
#[cfg(target_arch = "x86_64")]
|
|
||||||
const DMA_OFFSET: u64 = 4;
|
|
||||||
#[cfg(target_arch = "aarch64")]
|
|
||||||
const DMA_OFFSET: u64 = 16;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_signature() {
|
|
||||||
let gm = GuestMemoryAtomic::new(
|
|
||||||
GuestMemoryMmap::from_ranges(&[(GuestAddress(0), RAM_64BIT_START.0 as usize)]).unwrap(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut fw_cfg = FwCfg::new(gm);
|
|
||||||
|
|
||||||
let mut data = vec![0u8];
|
|
||||||
|
|
||||||
let mut sig_iter = FW_CFG_DMA_SIGNATURE.into_iter();
|
|
||||||
fw_cfg.write(0, SELECTOR_OFFSET, &[FW_CFG_SIGNATURE as u8, 0]);
|
|
||||||
loop {
|
|
||||||
if let Some(char) = sig_iter.next() {
|
|
||||||
fw_cfg.read(0, DATA_OFFSET, &mut data);
|
|
||||||
assert_eq!(data[0], char);
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[test]
|
|
||||||
fn test_kernel_cmdline() {
|
|
||||||
let gm = GuestMemoryAtomic::new(
|
|
||||||
GuestMemoryMmap::from_ranges(&[(GuestAddress(0), RAM_64BIT_START.0 as usize)]).unwrap(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut fw_cfg = FwCfg::new(gm);
|
|
||||||
|
|
||||||
let cmdline = *b"cmdline\0";
|
|
||||||
|
|
||||||
fw_cfg.add_kernel_cmdline(CString::from_vec_with_nul(cmdline.to_vec()).unwrap());
|
|
||||||
|
|
||||||
let mut data = vec![0u8];
|
|
||||||
|
|
||||||
let mut cmdline_iter = cmdline.into_iter();
|
|
||||||
fw_cfg.write(0, SELECTOR_OFFSET, &[FW_CFG_CMDLINE_DATA as u8, 0]);
|
|
||||||
loop {
|
|
||||||
if let Some(char) = cmdline_iter.next() {
|
|
||||||
fw_cfg.read(0, DATA_OFFSET, &mut data);
|
|
||||||
assert_eq!(data[0], char);
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_initram_fs() {
|
|
||||||
let gm = GuestMemoryAtomic::new(
|
|
||||||
GuestMemoryMmap::from_ranges(&[(GuestAddress(0), RAM_64BIT_START.0 as usize)]).unwrap(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut fw_cfg = FwCfg::new(gm);
|
|
||||||
|
|
||||||
let temp = TempFile::new().unwrap();
|
|
||||||
let mut temp_file = temp.as_file();
|
|
||||||
|
|
||||||
let initram_content = b"this is the initramfs";
|
|
||||||
let written = temp_file.write(initram_content);
|
|
||||||
assert_eq!(written.unwrap(), 21);
|
|
||||||
let _ = fw_cfg.add_initramfs_data(temp_file);
|
|
||||||
|
|
||||||
let mut data = vec![0u8];
|
|
||||||
|
|
||||||
let mut initram_iter = (*initram_content).into_iter();
|
|
||||||
fw_cfg.write(0, SELECTOR_OFFSET, &[FW_CFG_INITRD_DATA as u8, 0]);
|
|
||||||
loop {
|
|
||||||
if let Some(char) = initram_iter.next() {
|
|
||||||
fw_cfg.read(0, DATA_OFFSET, &mut data);
|
|
||||||
assert_eq!(data[0], char);
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_dma() {
|
|
||||||
let code = [
|
|
||||||
0xba, 0xf8, 0x03, 0x00, 0xd8, 0x04, b'0', 0xee, 0xb0, b'\n', 0xee, 0xf4,
|
|
||||||
];
|
|
||||||
|
|
||||||
let content = FwCfgContent::Bytes(code.to_vec());
|
|
||||||
|
|
||||||
let mem_size = 0x1000;
|
|
||||||
let load_addr = GuestAddress(0x1000);
|
|
||||||
let mem: GuestMemoryMmap<AtomicBitmap> =
|
|
||||||
GuestMemoryMmap::from_ranges(&[(load_addr, mem_size)]).unwrap();
|
|
||||||
|
|
||||||
// Note: In firmware we would just allocate FwCfgDmaAccess struct
|
|
||||||
// and use address of struct (&) as dma address
|
|
||||||
let mut access_control = AccessControl(0);
|
|
||||||
// bit 1 = read access
|
|
||||||
access_control.set_read(true);
|
|
||||||
// length of data to access
|
|
||||||
let length_be = (code.len() as u32).to_be();
|
|
||||||
// guest address for data
|
|
||||||
let code_address = 0x1900_u64;
|
|
||||||
let address_be = code_address.to_be();
|
|
||||||
let mut access = FwCfgDmaAccess {
|
|
||||||
control_be: access_control.0.to_be(), // bit(1) = read bit
|
|
||||||
length_be,
|
|
||||||
address_be,
|
|
||||||
};
|
|
||||||
// access address is where to put the code
|
|
||||||
let access_address = GuestAddress(load_addr.0);
|
|
||||||
let address_bytes = access_address.0.to_be_bytes();
|
|
||||||
let dma_lo: [u8; 4] = address_bytes[0..4].try_into().unwrap();
|
|
||||||
let dma_hi: [u8; 4] = address_bytes[4..8].try_into().unwrap();
|
|
||||||
|
|
||||||
// writing the FwCfgDmaAccess to mem (this would just be self.dma_access.as_ref() in guest)
|
|
||||||
let _ = mem.write(access.as_mut_bytes(), access_address);
|
|
||||||
let mem_m = GuestMemoryAtomic::new(mem.clone());
|
|
||||||
let mut fw_cfg = FwCfg::new(mem_m);
|
|
||||||
let cfg_item = FwCfgItem {
|
|
||||||
name: "code".to_string(),
|
|
||||||
content,
|
|
||||||
};
|
|
||||||
let _ = fw_cfg.add_item(cfg_item);
|
|
||||||
|
|
||||||
let mut data = [0u8; 12];
|
|
||||||
|
|
||||||
let _ = mem.read(&mut data, GuestAddress(code_address));
|
|
||||||
assert_ne!(data, code);
|
|
||||||
|
|
||||||
fw_cfg.write(0, SELECTOR_OFFSET, &[FW_CFG_FILE_FIRST as u8, 0]);
|
|
||||||
fw_cfg.write(0, DMA_OFFSET, &dma_lo);
|
|
||||||
fw_cfg.write(0, DMA_OFFSET + 4, &dma_hi);
|
|
||||||
let _ = mem.read(&mut data, GuestAddress(code_address));
|
|
||||||
assert_eq!(data, code);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -8,8 +8,6 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
use std::sync::{Arc, Barrier};
|
use std::sync::{Arc, Barrier};
|
||||||
|
|
||||||
use log::error;
|
|
||||||
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
|
||||||
@@ -27,9 +25,9 @@ impl BusDevice for FwDebugDevice {
|
|||||||
/// Upon read return the magic value to indicate that there is a debug port
|
/// Upon read return the magic value to indicate that there is a debug port
|
||||||
fn read(&mut self, _base: u64, _offset: u64, data: &mut [u8]) {
|
fn read(&mut self, _base: u64, _offset: u64, data: &mut [u8]) {
|
||||||
if data.len() == 1 {
|
if data.len() == 1 {
|
||||||
data[0] = 0xe9;
|
data[0] = 0xe9
|
||||||
} else {
|
} else {
|
||||||
error!("Invalid read size on debug port: {}", data.len());
|
error!("Invalid read size on debug port: {}", data.len())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,7 +35,7 @@ impl BusDevice for FwDebugDevice {
|
|||||||
if data.len() == 1 {
|
if data.len() == 1 {
|
||||||
print!("{}", data[0] as char);
|
print!("{}", data[0] as char);
|
||||||
} else {
|
} else {
|
||||||
error!("Invalid write size on debug port: {}", data.len());
|
error!("Invalid write size on debug port: {}", data.len())
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
None
|
||||||
|
|||||||
@@ -7,17 +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 std::sync::{Arc, Barrier};
|
|
||||||
use std::{io, result};
|
|
||||||
|
|
||||||
use log::warn;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use thiserror::Error;
|
|
||||||
use vm_device::BusDevice;
|
|
||||||
use vm_device::interrupt::InterruptSourceGroup;
|
|
||||||
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
|
|
||||||
|
|
||||||
use crate::{read_le_u32, write_le_u32};
|
use crate::{read_le_u32, write_le_u32};
|
||||||
|
use std::result;
|
||||||
|
use std::sync::{Arc, Barrier};
|
||||||
|
use std::{fmt, io};
|
||||||
|
use versionize::{VersionMap, Versionize, VersionizeResult};
|
||||||
|
use versionize_derive::Versionize;
|
||||||
|
use vm_device::interrupt::InterruptSourceGroup;
|
||||||
|
use vm_device::BusDevice;
|
||||||
|
use vm_migration::{
|
||||||
|
Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable, VersionMapped,
|
||||||
|
};
|
||||||
|
|
||||||
const OFS_DATA: u64 = 0x400; // Data Register
|
const OFS_DATA: u64 = 0x400; // Data Register
|
||||||
const GPIODIR: u64 = 0x400; // Direction Register
|
const GPIODIR: u64 = 0x400; // Direction Register
|
||||||
@@ -29,10 +29,10 @@ const GPIORIE: u64 = 0x414; // Raw Interrupt Status Register
|
|||||||
const GPIOMIS: u64 = 0x418; // Masked Interrupt Status Register
|
const GPIOMIS: u64 = 0x418; // Masked Interrupt Status Register
|
||||||
const GPIOIC: u64 = 0x41c; // Interrupt Clear Register
|
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
|
||||||
const GPIO_ID_LOW: u64 = 0xfe0;
|
const GPIO_ID_LOW: u64 = 0xfe0;
|
||||||
@@ -40,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")]
|
GpioInterruptFailure(io::Error),
|
||||||
GpioInterruptFailure(#[source] io::Error),
|
|
||||||
#[error("Invalid GPIO Input key triggered: {0}")]
|
|
||||||
GpioTriggerKeyFailure(u32),
|
GpioTriggerKeyFailure(u32),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Error {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Error::BadWriteOffset(offset) => write!(f, "Bad Write Offset: {offset}"),
|
||||||
|
Error::GpioInterruptDisabled => write!(f, "GPIO interrupt disabled by guest driver.",),
|
||||||
|
Error::GpioInterruptFailure(ref e) => {
|
||||||
|
write!(f, "Could not trigger GPIO interrupt: {e}.")
|
||||||
|
}
|
||||||
|
Error::GpioTriggerKeyFailure(key) => {
|
||||||
|
write!(f, "Invalid GPIO Input key triggerd: {key}.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type Result<T> = result::Result<T, Error>;
|
type Result<T> = result::Result<T, Error>;
|
||||||
|
|
||||||
/// A GPIO device following the PL061 specification.
|
/// A GPIO device following the PL061 specification.
|
||||||
@@ -78,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,
|
||||||
@@ -91,6 +102,8 @@ pub struct GpioState {
|
|||||||
afsel: u32,
|
afsel: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl VersionMapped for GpioState {}
|
||||||
|
|
||||||
impl Gpio {
|
impl Gpio {
|
||||||
/// Constructs an PL061 GPIO device.
|
/// Constructs an PL061 GPIO device.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
@@ -262,7 +275,7 @@ impl BusDevice for Gpio {
|
|||||||
let index = ((offset - GPIO_ID_LOW) >> 2) as usize;
|
let index = ((offset - GPIO_ID_LOW) >> 2) as usize;
|
||||||
value = u32::from(GPIO_ID[index]);
|
value = u32::from(GPIO_ID[index]);
|
||||||
} else if offset < OFS_DATA {
|
} else if offset < OFS_DATA {
|
||||||
value = self.data & ((offset >> 2) as u32);
|
value = self.data & ((offset >> 2) as u32)
|
||||||
} else {
|
} else {
|
||||||
value = match offset {
|
value = match offset {
|
||||||
GPIODIR => self.dir,
|
GPIODIR => self.dir,
|
||||||
@@ -295,7 +308,7 @@ impl BusDevice for Gpio {
|
|||||||
if data.len() <= 4 {
|
if data.len() <= 4 {
|
||||||
let value = read_le_u32(data);
|
let value = read_le_u32(data);
|
||||||
if let Err(e) = self.handle_write(offset, value) {
|
if let Err(e) = self.handle_write(offset, value) {
|
||||||
warn!("Failed to write to GPIO PL061 device: {e}");
|
warn!("Failed to write to GPIO PL061 device: {}", e);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
warn!(
|
warn!(
|
||||||
@@ -315,7 +328,7 @@ impl Snapshottable for Gpio {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
|
fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
|
||||||
Snapshot::new_from_state(&self.state())
|
Snapshot::new_from_versioned_state(&self.state())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -324,12 +337,13 @@ impl Transportable for Gpio {}
|
|||||||
impl Migratable for Gpio {}
|
impl Migratable for Gpio {}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod unit_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;
|
||||||
|
|
||||||
@@ -347,15 +361,10 @@ mod unit_tests {
|
|||||||
_index: InterruptIndex,
|
_index: InterruptIndex,
|
||||||
_config: InterruptSourceConfig,
|
_config: InterruptSourceConfig,
|
||||||
_masked: bool,
|
_masked: bool,
|
||||||
_set_gsi: bool,
|
|
||||||
) -> result::Result<(), std::io::Error> {
|
) -> result::Result<(), std::io::Error> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_gsi(&self) -> result::Result<(), std::io::Error> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn notifier(&self, _index: InterruptIndex) -> Option<EventFd> {
|
fn notifier(&self, _index: InterruptIndex) -> Option<EventFd> {
|
||||||
Some(self.event_fd.try_clone().unwrap())
|
Some(self.event_fd.try_clone().unwrap())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +1,20 @@
|
|||||||
// Copyright 2017 The Chromium OS Authors. All rights reserved.
|
// Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE-BSD-3-Clause file.
|
// found in the LICENSE-BSD-3-Clause file.
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
|
||||||
|
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
|
||||||
use std::sync::{Arc, Barrier};
|
use std::sync::{Arc, Barrier};
|
||||||
use std::thread;
|
|
||||||
|
|
||||||
use log::{error, info};
|
|
||||||
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,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,14 +36,7 @@ impl BusDevice for I8042Device {
|
|||||||
if data.len() == 1 && data[0] == 0xfe && offset == 3 {
|
if data.len() == 1 && data[0] == 0xfe && offset == 3 {
|
||||||
info!("i8042 reset signalled");
|
info!("i8042 reset signalled");
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user