mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d725400158 | ||
|
|
7026a08569 | ||
|
|
b6945b649a | ||
|
|
4c0052f355 | ||
|
|
758930bc65 | ||
|
|
7b46d06171 | ||
|
|
e16b06f894 | ||
|
|
4e001e2ad5 | ||
|
|
e309ed0a38 | ||
|
|
948e7ca61a | ||
|
|
920564b281 | ||
|
|
014c19e189 | ||
|
|
dbf4891eb2 |
7
.github/workflows/audit.yaml
vendored
7
.github/workflows/audit.yaml
vendored
@@ -4,13 +4,12 @@ on:
|
||||
paths:
|
||||
- '**/Cargo.toml'
|
||||
- '**/Cargo.lock'
|
||||
|
||||
jobs:
|
||||
security_audit:
|
||||
name: Audit
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions-rust-lang/audit@v1
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions-rs/audit-check@v1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
35
.github/workflows/build.yaml
vendored
35
.github/workflows/build.yaml
vendored
@@ -1,11 +1,9 @@
|
||||
name: Cloud Hypervisor Build
|
||||
on: [pull_request, merge_group]
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
on: [pull_request, create]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: github.event_name == 'pull_request'
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
@@ -15,13 +13,13 @@ jobs:
|
||||
- stable
|
||||
- beta
|
||||
- nightly
|
||||
- "1.77.0"
|
||||
- "1.66"
|
||||
target:
|
||||
- x86_64-unknown-linux-gnu
|
||||
- x86_64-unknown-linux-musl
|
||||
steps:
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -29,40 +27,35 @@ jobs:
|
||||
run: sudo apt install -y musl-tools
|
||||
|
||||
- name: Install Rust toolchain (${{ matrix.rust }})
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ matrix.rust }}
|
||||
target: ${{ matrix.target }}
|
||||
override: true
|
||||
|
||||
- name: Build (default features)
|
||||
run: cargo rustc --locked --bin cloud-hypervisor -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
||||
run: cargo rustc --locked --bin cloud-hypervisor -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||
|
||||
- name: Build (kvm)
|
||||
run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
||||
run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||
|
||||
- name: Build (default features + tdx)
|
||||
run: cargo rustc --locked --bin cloud-hypervisor --features "tdx" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
||||
run: cargo rustc --locked --bin cloud-hypervisor --features "tdx" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||
|
||||
- name: Build (default features + dbus_api)
|
||||
run: cargo rustc --locked --bin cloud-hypervisor --features "dbus_api" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
||||
run: cargo rustc --locked --bin cloud-hypervisor --features "dbus_api" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||
|
||||
- name: Build (default features + guest_debug)
|
||||
run: cargo rustc --locked --bin cloud-hypervisor --features "guest_debug" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
||||
|
||||
- name: Build (default features + pvmemcontrol)
|
||||
run: cargo rustc --locked --bin cloud-hypervisor --features "pvmemcontrol" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
||||
run: cargo rustc --locked --bin cloud-hypervisor --features "guest_debug" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||
|
||||
- name: Build (mshv)
|
||||
run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "mshv" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
||||
run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "mshv" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||
|
||||
- name: Build (sev_snp)
|
||||
run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "sev_snp" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
||||
|
||||
- name: Build (igvm)
|
||||
run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "igvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
||||
run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "sev_snp" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||
|
||||
- name: Build (mshv + kvm)
|
||||
run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "mshv,kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
||||
run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "mshv,kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||
|
||||
- name: Release Build (default features)
|
||||
run: cargo build --locked --all --release --target=${{ matrix.target }}
|
||||
|
||||
8
.github/workflows/dco.yaml
vendored
8
.github/workflows/dco.yaml
vendored
@@ -1,18 +1,16 @@
|
||||
name: DCO
|
||||
on: [pull_request, merge_group]
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
jobs:
|
||||
check:
|
||||
name: DCO Check ("Signed-Off-By")
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python 3.x
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: '3.x'
|
||||
- name: Check DCO
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
|
||||
8
.github/workflows/docker-image.yaml
vendored
8
.github/workflows/docker-image.yaml
vendored
@@ -1,13 +1,11 @@
|
||||
name: Cloud Hypervisor's Docker image update
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: main
|
||||
paths: resources/Dockerfile
|
||||
pull_request:
|
||||
paths: resources/Dockerfile
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
@@ -18,7 +16,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
@@ -41,7 +39,7 @@ jobs:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
# generate Docker tags based on the following events/attributes
|
||||
tags: |
|
||||
type=raw,value=20250111-0
|
||||
type=raw,value={{date 'YYYYMMDD'}}-0
|
||||
type=sha
|
||||
|
||||
- name: Build and push
|
||||
|
||||
32
.github/workflows/formatting.yaml
vendored
32
.github/workflows/formatting.yaml
vendored
@@ -1,32 +0,0 @@
|
||||
name: Cloud Hypervisor Code Formatting
|
||||
on: [pull_request, merge_group]
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Code Formatting
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
rust:
|
||||
- nightly
|
||||
target:
|
||||
- x86_64-unknown-linux-gnu
|
||||
- aarch64-unknown-linux-musl
|
||||
env:
|
||||
RUSTFLAGS: -D warnings
|
||||
steps:
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Install Rust toolchain (${{ matrix.rust }})
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ matrix.rust }}
|
||||
target: ${{ matrix.target }}
|
||||
components: rustfmt
|
||||
- name: Formatting (rustfmt)
|
||||
run: cargo fmt --all -- --check
|
||||
- name: Formatting (fuzz) (rustfmt)
|
||||
run: cargo fmt --all --manifest-path fuzz/Cargo.toml -- --check
|
||||
21
.github/workflows/fuzz-build.yaml
vendored
21
.github/workflows/fuzz-build.yaml
vendored
@@ -1,11 +1,9 @@
|
||||
name: Cloud Hypervisor Cargo Fuzz Build
|
||||
on: [pull_request, merge_group]
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
on: [pull_request, create]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: github.event_name == 'pull_request'
|
||||
name: Cargo Fuzz Build
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
@@ -14,19 +12,18 @@ jobs:
|
||||
- nightly
|
||||
target:
|
||||
- x86_64-unknown-linux-gnu
|
||||
env:
|
||||
RUSTFLAGS: -D warnings
|
||||
steps:
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v2
|
||||
- name: Install Rust toolchain (${{ matrix.rust }})
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ matrix.rust }}
|
||||
target: ${{ matrix.target }}
|
||||
override: true
|
||||
- name: Install Cargo fuzz
|
||||
run: cargo install cargo-fuzz
|
||||
- name: Fuzz Build
|
||||
# Temporary fix for cargo-fuzz on latest nightly: https://github.com/rust-fuzz/cargo-fuzz/issues/276
|
||||
#run: cargo install cargo-fuzz
|
||||
run: cargo install --git https://github.com/rust-fuzz/cargo-fuzz --rev b4df3e58f767b5cad8d1aa6753961003f56f3609
|
||||
- name: Cargo Fuzz Build
|
||||
run: cargo fuzz build
|
||||
- name: Fuzz Check
|
||||
run: cargo fuzz check
|
||||
|
||||
25
.github/workflows/gitlint.yaml
vendored
25
.github/workflows/gitlint.yaml
vendored
@@ -1,25 +0,0 @@
|
||||
name: Commit messages check
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
gitlint:
|
||||
name: Check commit messages
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
fetch-depth: 0
|
||||
- name: Set up Python 3.10
|
||||
uses: actions/setup-python@v3
|
||||
with:
|
||||
python-version: "3.10"
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install --upgrade gitlint
|
||||
- name: Lint git commit messages
|
||||
run: |
|
||||
gitlint --commits origin/$GITHUB_BASE_REF..
|
||||
3
.github/workflows/hadolint.yaml
vendored
3
.github/workflows/hadolint.yaml
vendored
@@ -1,4 +1,5 @@
|
||||
name: Lint Dockerfile
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
@@ -13,7 +14,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Lint Dockerfile
|
||||
uses: hadolint/hadolint-action@master
|
||||
|
||||
54
.github/workflows/integration-arm64.yaml
vendored
54
.github/workflows/integration-arm64.yaml
vendored
@@ -1,54 +0,0 @@
|
||||
name: Cloud Hypervisor Tests (ARM64)
|
||||
on: [pull_request, merge_group]
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
timeout-minutes: 120
|
||||
name: Tests (ARM64)
|
||||
runs-on: bookworm-arm64
|
||||
steps:
|
||||
- name: Fix workspace permissions
|
||||
run: sudo chown -R runner:runner ${GITHUB_WORKSPACE}
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Run unit tests (musl)
|
||||
run: scripts/dev_cli.sh tests --unit --libc musl
|
||||
- name: Load openvswitch module
|
||||
run: sudo modprobe openvswitch
|
||||
- name: Run integration tests (musl)
|
||||
timeout-minutes: 60
|
||||
run: scripts/dev_cli.sh tests --integration --libc musl
|
||||
- name: Install Azure CLI
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
run: |
|
||||
sudo apt install -y ca-certificates curl apt-transport-https lsb-release gnupg
|
||||
curl -sL https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/microsoft.gpg > /dev/null
|
||||
echo "deb [arch=arm64] https://packages.microsoft.com/repos/azure-cli/ bookworm main" | sudo tee /etc/apt/sources.list.d/azure-cli.list
|
||||
sudo apt update
|
||||
sudo apt install -y azure-cli
|
||||
- name: Download Windows image
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
shell: bash
|
||||
run: |
|
||||
IMG_BASENAME=windows-11-iot-enterprise-aarch64.raw
|
||||
IMG_PATH=$HOME/workloads/$IMG_BASENAME
|
||||
IMG_GZ_PATH=$HOME/workloads/$IMG_BASENAME.gz
|
||||
IMG_GZ_BLOB_NAME=windows-11-iot-enterprise-aarch64-9-min.raw.gz
|
||||
cp "scripts/$IMG_BASENAME.sha1" "$HOME/workloads/"
|
||||
pushd "$HOME/workloads"
|
||||
if sha1sum "$IMG_BASENAME.sha1" --check; then
|
||||
exit
|
||||
fi
|
||||
popd
|
||||
mkdir -p "$HOME/workloads"
|
||||
az storage blob download --container-name private-images --file "$IMG_GZ_PATH" --name "$IMG_GZ_BLOB_NAME" --connection-string "${{ secrets.CH_PRIVATE_IMAGES }}"
|
||||
gzip -d $IMG_GZ_PATH
|
||||
- name: Run Windows guest integration tests
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
timeout-minutes: 30
|
||||
run: scripts/dev_cli.sh tests --integration-windows --libc musl
|
||||
31
.github/workflows/integration-metrics.yaml
vendored
31
.github/workflows/integration-metrics.yaml
vendored
@@ -1,31 +0,0 @@
|
||||
name: Cloud Hypervisor Tests (Metrics)
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Tests (Metrics)
|
||||
runs-on: garm-jammy-16
|
||||
env:
|
||||
METRICS_PUBLISH_KEY: ${{ secrets.METRICS_PUBLISH_KEY }}
|
||||
steps:
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Install Docker
|
||||
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: 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'
|
||||
35
.github/workflows/integration-rate-limiter.yaml
vendored
35
.github/workflows/integration-rate-limiter.yaml
vendored
@@ -1,35 +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' || 'garm-jammy-16' }}
|
||||
env:
|
||||
AUTH_DOWNLOAD_TOKEN: ${{ secrets.AUTH_DOWNLOAD_TOKEN }}
|
||||
steps:
|
||||
- name: Code checkout
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: 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: Run rate-limiter integration tests
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
timeout-minutes: 20
|
||||
run: scripts/dev_cli.sh tests --integration-rate-limiter
|
||||
- name: Skipping build for PR
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
run: echo "Skipping build for PR"
|
||||
33
.github/workflows/integration-vfio.yaml
vendored
33
.github/workflows/integration-vfio.yaml
vendored
@@ -1,33 +0,0 @@
|
||||
name: Cloud Hypervisor Tests (VFIO)
|
||||
on: [merge_group, pull_request]
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Tests (VFIO)
|
||||
runs-on: ${{ github.event_name == 'pull_request' && 'ubuntu-latest' || 'vfio-nvidia' }}
|
||||
env:
|
||||
AUTH_DOWNLOAD_TOKEN: ${{ secrets.AUTH_DOWNLOAD_TOKEN }}
|
||||
steps:
|
||||
- name: Fix workspace permissions
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
run: sudo chown -R runner:runner ${GITHUB_WORKSPACE}
|
||||
- name: Code checkout
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Run VFIO integration tests
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
timeout-minutes: 15
|
||||
run: scripts/dev_cli.sh tests --integration-vfio
|
||||
# Most tests are failing with musl see #6790
|
||||
# - name: Run VFIO integration tests for musl
|
||||
# if: ${{ github.event_name != 'pull_request' }}
|
||||
# timeout-minutes: 15
|
||||
# run: scripts/dev_cli.sh tests --integration-vfio --libc musl
|
||||
- name: Skipping build for PR
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
run: echo "Skipping build for PR"
|
||||
50
.github/workflows/integration-windows.yaml
vendored
50
.github/workflows/integration-windows.yaml
vendored
@@ -1,50 +0,0 @@
|
||||
name: Cloud Hypervisor Tests (Windows Guest)
|
||||
on: [merge_group, pull_request]
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Tests (Windows Guest)
|
||||
runs-on: ${{ github.event_name == 'pull_request' && 'ubuntu-latest' || 'garm-jammy-16' }}
|
||||
steps:
|
||||
- name: Code checkout
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Install Docker
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get -y install ca-certificates curl gnupg
|
||||
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
|
||||
sudo chmod a+r /usr/share/keyrings/docker-archive-keyring.gpg
|
||||
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
|
||||
sudo apt-get update
|
||||
sudo apt install -y docker-ce docker-ce-cli
|
||||
- name: Install Azure CLI
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
run: |
|
||||
sudo apt install -y ca-certificates curl apt-transport-https lsb-release gnupg
|
||||
curl -sL https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/microsoft.gpg > /dev/null
|
||||
echo "deb [arch=amd64] https://packages.microsoft.com/repos/azure-cli/ jammy main" | sudo tee /etc/apt/sources.list.d/azure-cli.list
|
||||
sudo apt update
|
||||
sudo apt install -y azure-cli
|
||||
- name: Download Windows image
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
run: |
|
||||
mkdir $HOME/workloads
|
||||
az storage blob download --container-name private-images --file "$HOME/workloads/windows-server-2022-amd64-2.raw" --name windows-server-2022-amd64-2.raw --connection-string "${{ secrets.CH_PRIVATE_IMAGES }}"
|
||||
- name: Run Windows guest integration tests
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
timeout-minutes: 15
|
||||
run: scripts/dev_cli.sh tests --integration-windows
|
||||
- name: Run Windows guest integration tests for musl
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
timeout-minutes: 15
|
||||
run: scripts/dev_cli.sh tests --integration-windows --libc musl
|
||||
- name: Skipping build for PR
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
run: echo "Skipping build for PR"
|
||||
52
.github/workflows/integration-x86-64.yaml
vendored
52
.github/workflows/integration-x86-64.yaml
vendored
@@ -1,52 +0,0 @@
|
||||
name: Cloud Hypervisor Tests (x86-64)
|
||||
on: [pull_request, merge_group]
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
timeout-minutes: 60
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
runner: ['garm-jammy', "garm-jammy-amd"]
|
||||
libc: ["musl", 'gnu']
|
||||
name: Tests (x86-64)
|
||||
runs-on: ${{ github.event_name == 'pull_request' && !(matrix.runner == 'garm-jammy' && matrix.libc == 'gnu') && 'ubuntu-latest' || format('{0}-16', matrix.runner) }}
|
||||
steps:
|
||||
- name: Code checkout
|
||||
if: ${{ github.event_name != 'pull_request' || (matrix.runner == 'garm-jammy' && matrix.libc == 'gnu') }}
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Install Docker
|
||||
if: ${{ github.event_name != 'pull_request' || (matrix.runner == 'garm-jammy' && matrix.libc == 'gnu') }}
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get -y install ca-certificates curl gnupg
|
||||
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
|
||||
sudo chmod a+r /usr/share/keyrings/docker-archive-keyring.gpg
|
||||
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
|
||||
sudo apt-get update
|
||||
sudo apt install -y docker-ce docker-ce-cli
|
||||
- name: Prepare for VDPA
|
||||
if: ${{ github.event_name != 'pull_request' || (matrix.runner == 'garm-jammy' && matrix.libc == 'gnu') }}
|
||||
run: scripts/prepare_vdpa.sh
|
||||
- name: Run unit tests
|
||||
if: ${{ github.event_name != 'pull_request' || (matrix.runner == 'garm-jammy' && matrix.libc == 'gnu') }}
|
||||
run: scripts/dev_cli.sh tests --unit --libc ${{ matrix.libc }}
|
||||
- name: Load openvswitch module
|
||||
if: ${{ github.event_name != 'pull_request' || (matrix.runner == 'garm-jammy' && matrix.libc == 'gnu') }}
|
||||
run: sudo modprobe openvswitch
|
||||
- name: Run integration tests
|
||||
if: ${{ github.event_name != 'pull_request' || (matrix.runner == 'garm-jammy' && matrix.libc == 'gnu') }}
|
||||
timeout-minutes: 40
|
||||
run: scripts/dev_cli.sh tests --integration --libc ${{ matrix.libc }}
|
||||
- name: Run live-migration integration tests
|
||||
if: ${{ github.event_name != 'pull_request' || (matrix.runner == 'garm-jammy' && matrix.libc == 'gnu') }}
|
||||
timeout-minutes: 20
|
||||
run: scripts/dev_cli.sh tests --integration-live-migration --libc ${{ matrix.libc }}
|
||||
- name: Skipping build for PR
|
||||
if: ${{ github.event_name == 'pull_request' && matrix.runner != 'garm-jammy' && matrix.libc != 'gnu' }}
|
||||
run: echo "Skipping build for PR"
|
||||
14
.github/workflows/openapi.yaml
vendored
14
.github/workflows/openapi.yaml
vendored
@@ -1,14 +0,0 @@
|
||||
name: Cloud Hypervisor OpenAPI Validation
|
||||
on: [pull_request, merge_group]
|
||||
|
||||
jobs:
|
||||
Validate:
|
||||
runs-on: ubuntu-latest
|
||||
container: openapitools/openapi-generator-cli
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Validate OpenAPI
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
/usr/local/bin/docker-entrypoint.sh validate -i vmm/src/api/openapi/cloud-hypervisor.yaml
|
||||
32
.github/workflows/package-consistency.yaml
vendored
32
.github/workflows/package-consistency.yaml
vendored
@@ -1,32 +0,0 @@
|
||||
name: Cloud Hypervisor Consistency
|
||||
on: [pull_request, merge_group]
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Rust VMM Consistency Check
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install dependencies
|
||||
run: sudo apt install -y python3
|
||||
|
||||
- name: Install Rust toolchain stable
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: stable
|
||||
|
||||
- name: Check Rust VMM Package Consistency of root Workspace
|
||||
run: python3 scripts/package-consistency-check.py github.com/rust-vmm
|
||||
|
||||
- name: Check Rust VMM Package Consistency of fuzz Workspace
|
||||
run: |
|
||||
pushd fuzz
|
||||
python3 ../scripts/package-consistency-check.py github.com/rust-vmm
|
||||
popd
|
||||
39
.github/workflows/preview-riscv64.yaml
vendored
39
.github/workflows/preview-riscv64.yaml
vendored
@@ -1,39 +0,0 @@
|
||||
name: Cloud Hypervisor RISC-V 64-bit Preview
|
||||
on: [pull_request, merge_group]
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Cargo
|
||||
runs-on: riscv64-qemu-host
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
module:
|
||||
- hypervisor
|
||||
- arch
|
||||
- vm-allocator
|
||||
- devices
|
||||
|
||||
steps:
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install Rust toolchain
|
||||
run: /opt/scripts/exec-in-qemu.sh rustup default 1.77.0
|
||||
|
||||
- name: Build ${{ matrix.module }} Module (kvm)
|
||||
run: /opt/scripts/exec-in-qemu.sh cargo rustc --locked -p ${{ matrix.module }} --no-default-features --features "kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
||||
|
||||
- name: Clippy ${{ matrix.module }} Module (kvm)
|
||||
run: /opt/scripts/exec-in-qemu.sh cargo clippy --locked -p ${{ matrix.module }} --no-default-features --features "kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
||||
|
||||
- name: Test ${{ matrix.module }} Module (kvm)
|
||||
run: /opt/scripts/exec-in-qemu.sh cargo test --locked -p ${{ matrix.module }} --no-default-features --features "kvm"
|
||||
|
||||
- name: Check no files were modified
|
||||
run: test -z "$(git status --porcelain)"
|
||||
70
.github/workflows/quality.yaml
vendored
70
.github/workflows/quality.yaml
vendored
@@ -1,19 +1,16 @@
|
||||
name: Cloud Hypervisor Quality Checks
|
||||
on: [pull_request, merge_group]
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
on: [pull_request, create]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Quality (clippy)
|
||||
if: github.event_name == 'pull_request'
|
||||
name: Quality (clippy, rustfmt)
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: ${{ matrix.experimental }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
rust:
|
||||
- beta
|
||||
- stable
|
||||
target:
|
||||
- aarch64-unknown-linux-gnu
|
||||
@@ -21,15 +18,23 @@ jobs:
|
||||
- x86_64-unknown-linux-gnu
|
||||
- x86_64-unknown-linux-musl
|
||||
|
||||
experimental: [false]
|
||||
include:
|
||||
- rust: beta
|
||||
target: aarch64-unknown-linux-gnu
|
||||
experimental: true
|
||||
- rust: beta
|
||||
target: aarch64-unknown-linux-musl
|
||||
experimental: true
|
||||
- rust: beta
|
||||
target: x86_64-unknown-linux-gnu
|
||||
experimental: true
|
||||
- rust: beta
|
||||
target: x86_64-unknown-linux-musl
|
||||
experimental: true
|
||||
- rust: stable
|
||||
experimental: false
|
||||
|
||||
steps:
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -39,85 +44,78 @@ jobs:
|
||||
toolchain: ${{ matrix.rust }}
|
||||
target: ${{ matrix.target }}
|
||||
override: true
|
||||
components: clippy
|
||||
components: rustfmt, clippy
|
||||
|
||||
- name: Bisectability Check (default features)
|
||||
if: ${{ github.event_name == 'pull_request' && matrix.target == 'x86_64-unknown-linux-gnu' }}
|
||||
- name: Debug Check (default features)
|
||||
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
|
||||
run: |
|
||||
set -e
|
||||
commits=$(git rev-list origin/${{ github.base_ref }}..${{ github.sha }})
|
||||
for commit in $commits; do git checkout $commit; cargo check --tests --examples --all --target=${{ matrix.target }}; done
|
||||
git checkout ${{ github.sha }}
|
||||
|
||||
- name: Formatting (rustfmt)
|
||||
run: cargo fmt -- --check
|
||||
|
||||
- name: Clippy (kvm)
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
|
||||
command: clippy
|
||||
args: --target=${{ matrix.target }} --locked --all --all-targets --no-default-features --tests --examples --features "kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
||||
args: --target=${{ matrix.target }} --locked --all --all-targets --no-default-features --tests --examples --features "kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||
|
||||
- name: Clippy (default features)
|
||||
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 -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
||||
args: --target=${{ matrix.target }} --locked --all --all-targets --tests --examples -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||
|
||||
- name: Clippy (default features + guest_debug)
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
|
||||
command: clippy
|
||||
args: --target=${{ matrix.target }} --locked --all --all-targets --tests --examples --features "guest_debug" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
||||
|
||||
- name: Clippy (default features + pvmemcontrol)
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
|
||||
command: clippy
|
||||
args: --target=${{ matrix.target }} --locked --all --all-targets --tests --examples --features "pvmemcontrol" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
||||
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 -W clippy::assertions_on_result_states
|
||||
args: --target=${{ matrix.target }} --locked --all --all-targets --tests --examples --features "tracing" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||
|
||||
- name: Clippy (mshv)
|
||||
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
|
||||
command: clippy
|
||||
args: --target=${{ matrix.target }} --locked --all --all-targets --no-default-features --tests --examples --features "mshv" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
||||
args: --target=${{ matrix.target }} --locked --all --all-targets --no-default-features --tests --examples --features "mshv" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||
|
||||
- name: Clippy (mshv + kvm)
|
||||
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
|
||||
command: clippy
|
||||
args: --target=${{ matrix.target }} --locked --all --all-targets --no-default-features --tests --examples --features "mshv,kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
||||
args: --target=${{ matrix.target }} --locked --all --all-targets --no-default-features --tests --examples --features "mshv,kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||
|
||||
- name: Clippy (sev_snp)
|
||||
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
|
||||
command: clippy
|
||||
args: --target=${{ matrix.target }} --locked --all --all-targets --no-default-features --tests --examples --features "sev_snp" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
||||
|
||||
- name: Clippy (igvm)
|
||||
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: clippy
|
||||
args: --target=${{ matrix.target }} --locked --all --all-targets --no-default-features --tests --examples --features "igvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
||||
args: --target=${{ matrix.target }} --locked --all --all-targets --no-default-features --tests --examples --features "sev_snp" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||
|
||||
- name: Clippy (kvm + tdx)
|
||||
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
|
||||
command: clippy
|
||||
args: --target=${{ matrix.target }} --locked --all --all-targets --no-default-features --tests --examples --features "tdx,kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
||||
args: --target=${{ matrix.target }} --locked --all --all-targets --no-default-features --tests --examples --features "tdx,kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks
|
||||
|
||||
- name: Check build did not modify any files
|
||||
run: test -z "$(git status --porcelain)"
|
||||
@@ -127,6 +125,6 @@ jobs:
|
||||
name: Typos / Spellcheck
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
# Executes "typos ."
|
||||
- uses: crate-ci/typos@v1.16.11
|
||||
|
||||
196
.github/workflows/release.yaml
vendored
196
.github/workflows/release.yaml
vendored
@@ -1,69 +1,134 @@
|
||||
name: Cloud Hypervisor Release
|
||||
on: [create, merge_group]
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}
|
||||
cancel-in-progress: true
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
on: [pull_request, create]
|
||||
|
||||
jobs:
|
||||
release:
|
||||
if: (github.event_name == 'create' && github.event.ref_type == 'tag') || github.event_name == 'merge_group'
|
||||
name: Release ${{ matrix.platform.target }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform:
|
||||
- target: x86_64-unknown-linux-gnu
|
||||
args: --all --release --features mshv
|
||||
name_ch: cloud-hypervisor
|
||||
name_ch_remote: ch-remote
|
||||
- target: x86_64-unknown-linux-musl
|
||||
args: --all --release --features mshv
|
||||
name_ch: cloud-hypervisor-static
|
||||
name_ch_remote: ch-remote-static
|
||||
- target: aarch64-unknown-linux-musl
|
||||
args: --all --release
|
||||
name_ch: cloud-hypervisor-static-aarch64
|
||||
name_ch_remote: ch-remote-static-aarch64
|
||||
if: (github.event_name == 'create' && github.event.ref_type == 'tag') || github.event_name == 'pull_request'
|
||||
name: Release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v2
|
||||
- name: Install musl-gcc
|
||||
if: contains(matrix.platform.target, 'musl')
|
||||
run: sudo apt install -y musl-tools
|
||||
- name: Create release directory
|
||||
if: |
|
||||
github.event_name == 'create' && github.event.ref_type == 'tag' &&
|
||||
matrix.platform.target == 'x86_64-unknown-linux-gnu'
|
||||
run: rsync -rv --exclude=.git . ../cloud-hypervisor-${{ github.event.ref }}
|
||||
- name: Build ${{ matrix.platform.target }}
|
||||
uses: houseabsolute/actions-rust-cross@v0
|
||||
- name: Install Rust toolchain (x86_64-unknown-linux-gnu)
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: "1.67.1"
|
||||
target: x86_64-unknown-linux-gnu
|
||||
- name: Install Rust toolchain (x86_64-unknown-linux-musl)
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: "1.67.1"
|
||||
target: x86_64-unknown-linux-musl
|
||||
- name: Build
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
toolchain: "1.67.1"
|
||||
command: build
|
||||
target: ${{ matrix.platform.target }}
|
||||
args: ${{ matrix.platform.args }}
|
||||
strip: true
|
||||
toolchain: "1.77.0"
|
||||
- name: Copy Release Binaries
|
||||
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
||||
shell: bash
|
||||
run: |
|
||||
cp target/${{ matrix.platform.target }}/release/cloud-hypervisor ./${{ matrix.platform.name_ch }}
|
||||
cp target/${{ matrix.platform.target }}/release/ch-remote ./${{ matrix.platform.name_ch_remote }}
|
||||
- name: Upload Release Artifacts
|
||||
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
||||
uses: actions/upload-artifact@v4
|
||||
args: --all --release --features mshv --target=x86_64-unknown-linux-gnu
|
||||
- name: Static Build
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
name: Artifacts for ${{ matrix.platform.target }}
|
||||
path: |
|
||||
./${{ matrix.platform.name_ch }}
|
||||
./${{ matrix.platform.name_ch_remote }}
|
||||
toolchain: "1.67.1"
|
||||
command: build
|
||||
args: --all --release --features mshv --target=x86_64-unknown-linux-musl
|
||||
- name: Install Rust toolchain (aarch64-unknown-linux-musl)
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: "1.67.1"
|
||||
target: aarch64-unknown-linux-musl
|
||||
override: true
|
||||
- name: Create Release
|
||||
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
||||
id: create_release
|
||||
uses: actions/create-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ github.ref }}
|
||||
release_name: ${{ github.ref }}
|
||||
draft: true
|
||||
prerelease: true
|
||||
- name: Upload cloud-hypervisor
|
||||
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
||||
id: upload-release-cloud-hypervisor
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: target/x86_64-unknown-linux-gnu/release/cloud-hypervisor
|
||||
asset_name: cloud-hypervisor
|
||||
asset_content_type: application/octet-stream
|
||||
- name: Upload static cloud-hypervisor
|
||||
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
||||
id: upload-release-static-cloud-hypervisor
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: target/x86_64-unknown-linux-musl/release/cloud-hypervisor
|
||||
asset_name: cloud-hypervisor-static
|
||||
asset_content_type: application/octet-stream
|
||||
- name: Upload ch-remote
|
||||
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
||||
id: upload-release-ch-remote
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: target/x86_64-unknown-linux-gnu/release/ch-remote
|
||||
asset_name: ch-remote
|
||||
asset_content_type: application/octet-stream
|
||||
- name: Upload static-ch-remote
|
||||
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
||||
id: upload-release-static-ch-remote
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: target/x86_64-unknown-linux-musl/release/ch-remote
|
||||
asset_name: ch-remote-static
|
||||
asset_content_type: application/octet-stream
|
||||
- name: Clean build tree ahead of cross build
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: clean
|
||||
- name: Static Build (AArch64)
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
use-cross: true
|
||||
command: build
|
||||
args: --all --release --target=aarch64-unknown-linux-musl
|
||||
- name: Upload static AArch64 cloud-hypervisor
|
||||
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
||||
id: upload-release-static-aarch64-cloud-hypervisor
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: target/aarch64-unknown-linux-musl/release/cloud-hypervisor
|
||||
asset_name: cloud-hypervisor-static-aarch64
|
||||
asset_content_type: application/octet-stream
|
||||
- name: Upload static AArch64 ch-remote
|
||||
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
||||
id: upload-release-static-aarch64-ch-remote
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: target/aarch64-unknown-linux-musl/release/ch-remote
|
||||
asset_name: ch-remote-static-aarch64
|
||||
asset_content_type: application/octet-stream
|
||||
- name: Vendor
|
||||
if: |
|
||||
github.event_name == 'create' && github.event.ref_type == 'tag' &&
|
||||
matrix.platform.target == 'x86_64-unknown-linux-gnu'
|
||||
working-directory: ../cloud-hypervisor-${{ github.event.ref }}
|
||||
run: |
|
||||
mkdir ../vendor-cargo-home
|
||||
@@ -71,25 +136,16 @@ jobs:
|
||||
mkdir .cargo
|
||||
cargo vendor > .cargo/config.toml
|
||||
- name: Create vendored source archive
|
||||
if: |
|
||||
github.event_name == 'create' && github.event.ref_type == 'tag' &&
|
||||
matrix.platform.target == 'x86_64-unknown-linux-gnu'
|
||||
run: tar cJf cloud-hypervisor-${{ github.event.ref }}.tar.xz ../cloud-hypervisor-${{ github.event.ref }}
|
||||
working-directory: ../
|
||||
run: tar cJf cloud-hypervisor-${{ github.event.ref }}.tar.xz cloud-hypervisor-${{ github.event.ref }}
|
||||
- name: Upload cloud-hypervisor vendored source archive
|
||||
if: |
|
||||
github.event_name == 'create' && github.event.ref_type == 'tag' &&
|
||||
matrix.platform.target == 'x86_64-unknown-linux-gnu'
|
||||
id: upload-release-cloud-hypervisor-vendored-sources
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
path: cloud-hypervisor-${{ github.event.ref }}.tar.xz
|
||||
name: cloud-hypervisor-${{ github.event.ref }}.tar.xz
|
||||
- name: Create GitHub Release
|
||||
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
||||
uses: softprops/action-gh-release@v1
|
||||
id: upload-release-cloud-hypervisor-vendored-sources
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
draft: true
|
||||
files: |
|
||||
./${{ matrix.platform.name_ch }}
|
||||
./${{ matrix.platform.name_ch_remote }}
|
||||
./cloud-hypervisor-${{ github.event.ref }}.tar.xz
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ../cloud-hypervisor-${{ github.event.ref }}.tar.xz
|
||||
asset_name: cloud-hypervisor-${{ github.event.ref }}.tar.xz
|
||||
asset_content_type: application/x-xz
|
||||
|
||||
12
.github/workflows/reuse.yaml
vendored
12
.github/workflows/reuse.yaml
vendored
@@ -1,12 +0,0 @@
|
||||
name: REUSE Compliance Check
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
reuse:
|
||||
name: REUSE Compliance Check
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: REUSE Compliance Check
|
||||
uses: fsfe/reuse-action@v3
|
||||
20
.github/workflows/shlint.yaml
vendored
20
.github/workflows/shlint.yaml
vendored
@@ -1,20 +0,0 @@
|
||||
name: Shell scripts check
|
||||
on:
|
||||
pull_request:
|
||||
merge_group:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
sh-checker:
|
||||
name: Check shell scripts
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
- name: Run the shell script checkers
|
||||
uses: luizm/action-sh-checker@master
|
||||
env:
|
||||
SHFMT_OPTS: -i 4 -d
|
||||
SHELLCHECK_OPTS: -x --source-path scripts
|
||||
21
.github/workflows/taplo.yaml
vendored
21
.github/workflows/taplo.yaml
vendored
@@ -1,21 +0,0 @@
|
||||
name: Cargo.toml Formatting (taplo)
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- '**/Cargo.toml'
|
||||
|
||||
jobs:
|
||||
cargo_toml_format:
|
||||
name: Cargo.toml Formatting
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Install Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
- name: Install build dependencies
|
||||
run: sudo apt-get update && sudo apt-get -yqq install build-essential libssl-dev
|
||||
- name: Install taplo
|
||||
run: cargo install taplo-cli --locked
|
||||
- name: Check formatting
|
||||
run: taplo fmt --check
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -6,4 +6,3 @@
|
||||
**/rusty-tags.vi
|
||||
/rpm/SOURCES
|
||||
/.vscode
|
||||
/vendor
|
||||
|
||||
15
.gitlint
15
.gitlint
@@ -1,15 +0,0 @@
|
||||
[general]
|
||||
extra-path=scripts/gitlint/rules.py
|
||||
regex-style-search=true
|
||||
|
||||
[ignore-by-author-name]
|
||||
regex=dependabot
|
||||
ignore=all
|
||||
|
||||
# default 72
|
||||
[title-max-length]
|
||||
line-length=72
|
||||
|
||||
# default 80
|
||||
[body-max-line-length]
|
||||
line-length=72
|
||||
12
.reuse/dep5
12
.reuse/dep5
@@ -1,12 +0,0 @@
|
||||
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
Upstream-Name: cloud-hypervisor
|
||||
Upstream-Contact: <>
|
||||
Source: https://www.cloudhypervisor.org
|
||||
|
||||
Files: docs/*.md *.md
|
||||
Copyright: 2024
|
||||
License: CC-BY-4.0
|
||||
|
||||
Files: scripts/* test_data/* *.toml .git* fuzz/Cargo.lock fuzz/.gitignore resources/linux-config-* vmm/src/api/openapi/cloud-hypervisor.yaml CODEOWNERS Cargo.lock
|
||||
Copyright: 2024
|
||||
License: Apache-2.0
|
||||
@@ -1,4 +1 @@
|
||||
edition = "2021"
|
||||
group_imports="StdExternalCrate"
|
||||
imports_granularity="Module"
|
||||
|
||||
edition = "2021"
|
||||
@@ -1,5 +0,0 @@
|
||||
include = ["**/Cargo.toml"]
|
||||
|
||||
[formatting]
|
||||
reorder_arrays = true
|
||||
reorder_keys = true
|
||||
@@ -112,5 +112,5 @@ Fixes #88
|
||||
Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
|
||||
```
|
||||
|
||||
Then, after the corresponding PR is merged, GitHub will automatically close that issue when parsing the
|
||||
Then, after the corresponding PR is merged, Github will automatically close that issue when parsing the
|
||||
[commit message](https://help.github.com/articles/closing-issues-via-commit-messages/).
|
||||
|
||||
1482
Cargo.lock
generated
1482
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
135
Cargo.toml
135
Cargo.toml
@@ -1,13 +1,13 @@
|
||||
[package]
|
||||
authors = ["The Cloud Hypervisor Authors"]
|
||||
build = "build.rs"
|
||||
default-run = "cloud-hypervisor"
|
||||
description = "Open source Virtual Machine Monitor (VMM) that runs on top of KVM & MSHV"
|
||||
edition = "2021"
|
||||
homepage = "https://github.com/cloud-hypervisor/cloud-hypervisor"
|
||||
license = "Apache-2.0 AND BSD-3-Clause"
|
||||
name = "cloud-hypervisor"
|
||||
version = "44.0.0"
|
||||
version = "36.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):
|
||||
@@ -15,107 +15,92 @@ version = "44.0.0"
|
||||
# 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.77.0"
|
||||
rust-version = "1.66"
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
opt-level = "s"
|
||||
strip = true
|
||||
|
||||
[profile.profiling]
|
||||
debug = true
|
||||
inherits = "release"
|
||||
strip = false
|
||||
debug = true
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.94"
|
||||
anyhow = "1.0.75"
|
||||
api_client = { path = "api_client" }
|
||||
clap = { version = "4.5.13", features = ["string"] }
|
||||
dhat = { version = "0.3.3", optional = true }
|
||||
clap = { version = "4.3.11", features = ["string"] }
|
||||
dhat = { version = "0.3.2", optional = true }
|
||||
epoll = "4.3.3"
|
||||
event_monitor = { path = "event_monitor" }
|
||||
hypervisor = { path = "hypervisor" }
|
||||
libc = "0.2.167"
|
||||
log = { version = "0.4.22", features = ["std"] }
|
||||
libc = "0.2.147"
|
||||
log = { version = "0.4.17", features = ["std"] }
|
||||
option_parser = { path = "option_parser" }
|
||||
seccompiler = { workspace = true }
|
||||
serde_json = "1.0.120"
|
||||
seccompiler = "0.4.0"
|
||||
serde_json = "1.0.107"
|
||||
signal-hook = "0.3.17"
|
||||
thiserror = "2.0.6"
|
||||
tpm = { path = "tpm" }
|
||||
thiserror = "1.0.40"
|
||||
tpm = { path = "tpm"}
|
||||
tracer = { path = "tracer" }
|
||||
vm-memory = { workspace = true }
|
||||
vmm = { path = "vmm" }
|
||||
vmm-sys-util = { workspace = true }
|
||||
zbus = { version = "4.4.0", optional = true }
|
||||
vmm-sys-util = "0.11.0"
|
||||
vm-memory = "0.12.2"
|
||||
zbus = { version = "3.11.1", optional = true }
|
||||
|
||||
# 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 = "5.0.1"
|
||||
dirs = "5.0.0"
|
||||
net_util = { path = "net_util" }
|
||||
once_cell = "1.20.2"
|
||||
serde_json = "1.0.120"
|
||||
once_cell = "1.18.0"
|
||||
serde_json = "1.0.107"
|
||||
test_infra = { path = "test_infra" }
|
||||
wait-timeout = "0.2.0"
|
||||
|
||||
# Please adjust `vmm::feature_list()` accordingly when changing the
|
||||
# feature list below
|
||||
[features]
|
||||
dbus_api = ["vmm/dbus_api", "zbus"]
|
||||
default = ["io_uring", "kvm"]
|
||||
dhat-heap = ["dhat", "vmm/dhat-heap"] # For heap profiling
|
||||
default = ["kvm", "io_uring"]
|
||||
dbus_api = ["zbus", "vmm/dbus_api"]
|
||||
dhat-heap = ["dhat"] # For heap profiling
|
||||
guest_debug = ["vmm/guest_debug"]
|
||||
igvm = ["mshv", "vmm/igvm"]
|
||||
io_uring = ["vmm/io_uring"]
|
||||
kvm = ["vmm/kvm"]
|
||||
mshv = ["vmm/mshv"]
|
||||
pvmemcontrol = ["vmm/pvmemcontrol"]
|
||||
sev_snp = ["igvm", "mshv", "vmm/sev_snp"]
|
||||
sev_snp = ["vmm/sev_snp", "mshv"]
|
||||
tdx = ["vmm/tdx"]
|
||||
tracing = ["tracer/tracing", "vmm/tracing"]
|
||||
tracing = ["vmm/tracing", "tracer/tracing"]
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
"api_client",
|
||||
"arch",
|
||||
"block",
|
||||
"devices",
|
||||
"event_monitor",
|
||||
"hypervisor",
|
||||
"net_gen",
|
||||
"net_util",
|
||||
"option_parser",
|
||||
"pci",
|
||||
"performance-metrics",
|
||||
"rate_limiter",
|
||||
"serial_buffer",
|
||||
"test_infra",
|
||||
"tracer",
|
||||
"vhost_user_block",
|
||||
"vhost_user_net",
|
||||
"virtio-devices",
|
||||
"vm-allocator",
|
||||
"vm-device",
|
||||
"vm-migration",
|
||||
"vm-virtio",
|
||||
"vmm",
|
||||
"api_client",
|
||||
"arch",
|
||||
"block",
|
||||
"devices",
|
||||
"event_monitor",
|
||||
"hypervisor",
|
||||
"net_gen",
|
||||
"net_util",
|
||||
"option_parser",
|
||||
"pci",
|
||||
"performance-metrics",
|
||||
"rate_limiter",
|
||||
"serial_buffer",
|
||||
"test_infra",
|
||||
"tracer",
|
||||
"vhost_user_block",
|
||||
"vhost_user_net",
|
||||
"virtio-devices",
|
||||
"vmm",
|
||||
"vm-allocator",
|
||||
"vm-device",
|
||||
"vm-migration",
|
||||
"vm-virtio"
|
||||
]
|
||||
|
||||
[workspace.dependencies]
|
||||
acpi_tables = { git = "https://github.com/rust-vmm/acpi_tables", branch = "main" }
|
||||
kvm-bindings = "0.10.0"
|
||||
kvm-ioctls = "0.19.1"
|
||||
linux-loader = "0.13.0"
|
||||
mshv-bindings = "0.3.2"
|
||||
mshv-ioctls = "0.3.2"
|
||||
seccompiler = "0.4.0"
|
||||
vfio-bindings = { git = "https://github.com/rust-vmm/vfio", branch = "main" }
|
||||
vfio-ioctls = { git = "https://github.com/rust-vmm/vfio", branch = "main", default-features = false }
|
||||
vfio_user = { git = "https://github.com/rust-vmm/vfio-user", branch = "main" }
|
||||
vhost = { git = "https://github.com/rust-vmm/vhost", rev = "d983ae0" }
|
||||
vhost-user-backend = { git = "https://github.com/rust-vmm/vhost", rev = "d983ae0" }
|
||||
virtio-bindings = "0.2.4"
|
||||
virtio-queue = "0.14.0"
|
||||
vm-fdt = { git = "https://github.com/rust-vmm/vm-fdt", branch = "main" }
|
||||
vm-memory = "0.16.1"
|
||||
vmm-sys-util = "0.12.1"
|
||||
|
||||
509
Jenkinsfile
vendored
Normal file
509
Jenkinsfile
vendored
Normal file
@@ -0,0 +1,509 @@
|
||||
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 requiring 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('Worker build - AMD') {
|
||||
agent { node { label 'jammy-amd' } }
|
||||
when {
|
||||
beforeAgent true
|
||||
expression {
|
||||
return runWorkers
|
||||
}
|
||||
}
|
||||
stages {
|
||||
stage('Checkout') {
|
||||
steps {
|
||||
checkout scm
|
||||
}
|
||||
}
|
||||
stage('Prepare environment') {
|
||||
steps {
|
||||
sh 'scripts/prepare_vdpa.sh'
|
||||
}
|
||||
}
|
||||
stage('Run integration tests') {
|
||||
options {
|
||||
timeout(time: 1, unit: 'HOURS')
|
||||
}
|
||||
steps {
|
||||
sh 'sudo modprobe openvswitch'
|
||||
sh 'scripts/dev_cli.sh tests --integration -- -- --skip common_parallel::test_vfio'
|
||||
}
|
||||
}
|
||||
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 integration tests for musl') {
|
||||
options {
|
||||
timeout(time: 1, unit: 'HOURS')
|
||||
}
|
||||
steps {
|
||||
sh 'sudo modprobe openvswitch'
|
||||
sh 'scripts/dev_cli.sh tests --integration --libc musl -- -- --skip common_parallel::test_vfio'
|
||||
}
|
||||
}
|
||||
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('focal', '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
|
||||
}
|
||||
13
README.md
13
README.md
@@ -108,7 +108,7 @@ do not wish to use the pre-built binaries.
|
||||
## Booting Linux
|
||||
|
||||
Cloud Hypervisor supports direct kernel boot (the x86-64 kernel requires the kernel
|
||||
built with PVH support or a bzImage) or booting via a firmware (either [Rust Hypervisor
|
||||
built with PVH support) or booting via a firmware (either [Rust Hypervisor
|
||||
Firmware](https://github.com/cloud-hypervisor/rust-hypervisor-firmware) or an
|
||||
edk2 UEFI firmware called `CLOUDHV` / `CLOUDHV_EFI`.)
|
||||
|
||||
@@ -175,15 +175,20 @@ $ ./cloud-hypervisor \
|
||||
|
||||
#### Building your Kernel
|
||||
|
||||
Cloud Hypervisor also supports direct kernel boot. For x86-64, a `vmlinux` ELF kernel (compiled with PVH support) or a regular bzImage are supported. In order to support development there is a custom branch; however provided the required options are enabled any recent kernel will suffice.
|
||||
Cloud Hypervisor also supports direct kernel boot. For x86-64, a `vmlinux` ELF kernel (compiled with PVH support) is needed. In order to support development there is a custom branch; however provided the required options are enabled any recent kernel will suffice.
|
||||
|
||||
To build the kernel:
|
||||
|
||||
```shell
|
||||
# Clone the Cloud Hypervisor Linux branch
|
||||
$ git clone --depth 1 https://github.com/cloud-hypervisor/linux.git -b ch-6.12.8 linux-cloud-hypervisor
|
||||
$ git clone --depth 1 https://github.com/cloud-hypervisor/linux.git -b ch-6.2 linux-cloud-hypervisor
|
||||
$ pushd linux-cloud-hypervisor
|
||||
$ make ch_defconfig
|
||||
# Use the x86-64 cloud-hypervisor kernel config to build your kernel for x86-64
|
||||
$ wget https://raw.githubusercontent.com/cloud-hypervisor/cloud-hypervisor/main/resources/linux-config-x86_64
|
||||
# Use the AArch64 cloud-hypervisor kernel config to build your kernel for AArch64
|
||||
$ wget https://raw.githubusercontent.com/cloud-hypervisor/cloud-hypervisor/main/resources/linux-config-aarch64
|
||||
$ cp linux-config-x86_64 .config # x86-64
|
||||
$ cp linux-config-aarch64 .config # AArch64
|
||||
# Do native build of the x86-64 kernel
|
||||
$ KCFLAGS="-Wa,-mx86-used-note=no" make bzImage -j `nproc`
|
||||
# Do native build of the AArch64 kernel
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
[package]
|
||||
authors = ["The Cloud Hypervisor Authors"]
|
||||
edition = "2021"
|
||||
name = "api_client"
|
||||
version = "0.1.0"
|
||||
authors = ["The Cloud Hypervisor Authors"]
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
thiserror = "2.0.6"
|
||||
vmm-sys-util = { workspace = true }
|
||||
vmm-sys-util = "0.11.0"
|
||||
|
||||
@@ -3,28 +3,41 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
use std::fmt;
|
||||
use std::io::{Read, Write};
|
||||
use std::os::unix::io::RawFd;
|
||||
|
||||
use thiserror::Error;
|
||||
use vmm_sys_util::sock_ctrl_msg::ScmSocket;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
#[error("Error writing to or reading from HTTP socket: {0}")]
|
||||
Socket(std::io::Error),
|
||||
#[error("Error sending file descriptors: {0}")]
|
||||
SocketSendFds(vmm_sys_util::errno::Error),
|
||||
#[error("Error parsing HTTP status code: {0}")]
|
||||
StatusCodeParsing(std::num::ParseIntError),
|
||||
#[error("HTTP output is missing protocol statement")]
|
||||
MissingProtocol,
|
||||
#[error("Error parsing HTTP Content-Length field: {0}")]
|
||||
ContentLengthParsing(std::num::ParseIntError),
|
||||
#[error("Server responded with an error: {0:?}: {1:?}")]
|
||||
ServerResponse(StatusCode, Option<String>),
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
use Error::*;
|
||||
match self {
|
||||
Socket(e) => write!(f, "Error writing to or reading from HTTP socket: {e}"),
|
||||
SocketSendFds(e) => write!(f, "Error writing to or reading from HTTP socket: {e}"),
|
||||
StatusCodeParsing(e) => write!(f, "Error parsing HTTP status code: {e}"),
|
||||
MissingProtocol => write!(f, "HTTP output is missing protocol statement"),
|
||||
ContentLengthParsing(e) => write!(f, "Error parsing HTTP Content-Length field: {e}"),
|
||||
ServerResponse(s, o) => {
|
||||
if let Some(o) = o {
|
||||
write!(f, "Server responded with an error: {s:?}: {o}")
|
||||
} else {
|
||||
write!(f, "Server responded with an error: {s:?}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum StatusCode {
|
||||
Continue,
|
||||
|
||||
@@ -1,29 +1,30 @@
|
||||
[package]
|
||||
authors = ["The Chromium OS Authors"]
|
||||
edition = "2021"
|
||||
name = "arch"
|
||||
version = "0.1.0"
|
||||
authors = ["The Chromium OS Authors"]
|
||||
edition = "2021"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
kvm = ["hypervisor/kvm"]
|
||||
sev_snp = []
|
||||
tdx = []
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.94"
|
||||
byteorder = "1.5.0"
|
||||
anyhow = "1.0.75"
|
||||
byteorder = "1.4.3"
|
||||
hypervisor = { path = "../hypervisor" }
|
||||
libc = "0.2.167"
|
||||
linux-loader = { workspace = true, features = ["bzimage", "elf", "pe"] }
|
||||
log = "0.4.22"
|
||||
serde = { version = "1.0.208", features = ["derive", "rc"] }
|
||||
thiserror = "2.0.6"
|
||||
uuid = "1.12.1"
|
||||
vm-memory = { workspace = true, features = ["backend-bitmap", "backend-mmap"] }
|
||||
libc = "0.2.147"
|
||||
linux-loader = { version = "0.9.1", features = ["elf", "bzimage", "pe"] }
|
||||
log = "0.4.17"
|
||||
serde = { version = "1.0.168", features = ["rc", "derive"] }
|
||||
thiserror = "1.0.40"
|
||||
uuid = "1.3.4"
|
||||
versionize = "0.1.10"
|
||||
versionize_derive = "0.1.4"
|
||||
vm-memory = { version = "0.12.2", features = ["backend-mmap", "backend-bitmap"] }
|
||||
vm-migration = { path = "../vm-migration" }
|
||||
vmm-sys-util = { workspace = true, features = ["with-serde"] }
|
||||
vmm-sys-util = { version = "0.11.0", features = ["with-serde"] }
|
||||
|
||||
[target.'cfg(any(target_arch = "aarch64", target_arch = "riscv64"))'.dependencies]
|
||||
fdt_parser = { version = "0.1.5", package = "fdt" }
|
||||
vm-fdt = { workspace = true }
|
||||
[target.'cfg(target_arch = "aarch64")'.dependencies]
|
||||
fdt_parser = { version = "0.1.4", package = "fdt" }
|
||||
vm-fdt = { git = "https://github.com/rust-vmm/vm-fdt", branch = "main" }
|
||||
|
||||
@@ -6,25 +6,28 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the THIRD-PARTY file.
|
||||
|
||||
use crate::{NumaNodes, PciSpaceInfo};
|
||||
use byteorder::{BigEndian, ByteOrder};
|
||||
use hypervisor::arch::aarch64::gic::Vgic;
|
||||
use std::cmp;
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::CStr;
|
||||
use std::fmt::Debug;
|
||||
use std::path::Path;
|
||||
use std::result;
|
||||
use std::str;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::{cmp, fs, result, str};
|
||||
|
||||
use byteorder::{BigEndian, ByteOrder};
|
||||
use hypervisor::arch::aarch64::gic::Vgic;
|
||||
use thiserror::Error;
|
||||
use vm_fdt::{FdtWriter, FdtWriterResult};
|
||||
use vm_memory::{Address, Bytes, GuestMemory, GuestMemoryError, GuestMemoryRegion};
|
||||
|
||||
use super::super::{DeviceType, GuestMemoryMmap, InitramfsConfig};
|
||||
use super::super::DeviceType;
|
||||
use super::super::GuestMemoryMmap;
|
||||
use super::super::InitramfsConfig;
|
||||
use super::layout::{
|
||||
IRQ_BASE, MEM_32BIT_DEVICES_SIZE, MEM_32BIT_DEVICES_START, MEM_PCI_IO_SIZE, MEM_PCI_IO_START,
|
||||
PCI_HIGH_BASE, PCI_MMIO_CONFIG_SIZE_PER_SEGMENT,
|
||||
};
|
||||
use crate::{NumaNodes, PciSpaceInfo};
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
use vm_fdt::{FdtWriter, FdtWriterResult};
|
||||
use vm_memory::{Address, Bytes, GuestMemory, GuestMemoryError, GuestMemoryRegion};
|
||||
|
||||
// This is a value for uniquely identifying the FDT node declaring the interrupt controller.
|
||||
const GIC_PHANDLE: u32 = 1;
|
||||
@@ -77,10 +80,9 @@ pub trait DeviceInfoForFdt {
|
||||
}
|
||||
|
||||
/// Errors thrown while configuring the Flattened Device Tree for aarch64.
|
||||
#[derive(Debug, Error)]
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Failure in writing FDT in memory.
|
||||
#[error("Failure in writing FDT in memory: {0}")]
|
||||
WriteFdtToMemory(GuestMemoryError),
|
||||
}
|
||||
type Result<T> = result::Result<T, Error>;
|
||||
@@ -521,7 +523,7 @@ fn create_memory_node(
|
||||
let memory_region_size: u64 = memory_region.size() as u64;
|
||||
mem_reg_prop.push(memory_region_start_addr);
|
||||
mem_reg_prop.push(memory_region_size);
|
||||
// Set the node address the first non-zero region address
|
||||
// Set the node address the first non-zero regison address
|
||||
if node_memory_addr == 0 {
|
||||
node_memory_addr = memory_region_start_addr;
|
||||
}
|
||||
@@ -567,7 +569,7 @@ fn create_memory_node(
|
||||
|
||||
if ram_regions.len() > 2 {
|
||||
panic!(
|
||||
"There should be up to two non-continuous regions, divided by the
|
||||
"There should be up to two non-continuous regions, devidided by the
|
||||
gap at the end of 32bit address space."
|
||||
);
|
||||
}
|
||||
@@ -892,7 +894,7 @@ fn create_pci_nodes(
|
||||
for pci_device_info_elem in pci_device_info.iter() {
|
||||
// EDK2 requires the PCIe high space above 4G address.
|
||||
// The actual space in CLH follows the RAM. If the RAM space is small, the PCIe high space
|
||||
// could fall below 4G.
|
||||
// could fall bellow 4G.
|
||||
// Here we cut off PCI device space below 8G in FDT to workaround the EDK2 check.
|
||||
// But the address written in ACPI is not impacted.
|
||||
let (pci_device_base_64bit, pci_device_size_64bit) =
|
||||
|
||||
@@ -111,9 +111,8 @@ pub const RAM_64BIT_START: GuestAddress = GuestAddress(0x1_0000_0000);
|
||||
pub const CMDLINE_MAX_SIZE: usize = 2048;
|
||||
|
||||
/// FDT is at the beginning of RAM.
|
||||
/// Maximum size of the device tree blob as specified in https://www.kernel.org/doc/Documentation/arm64/booting.txt.
|
||||
pub const FDT_START: GuestAddress = RAM_START;
|
||||
/// Maximum size of the device tree blob as specified in [the kernel
|
||||
/// documentation](https://www.kernel.org/doc/Documentation/arm64/booting.txt).
|
||||
pub const FDT_MAX_SIZE: u64 = 0x20_0000;
|
||||
|
||||
/// Put ACPI table above dtb
|
||||
|
||||
@@ -11,49 +11,40 @@ pub mod regs;
|
||||
/// Module for loading UEFI binary.
|
||||
pub mod uefi;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Debug;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use hypervisor::arch::aarch64::gic::Vgic;
|
||||
use log::{log_enabled, Level};
|
||||
use thiserror::Error;
|
||||
use vm_memory::{Address, GuestAddress, GuestMemory, GuestMemoryAtomic};
|
||||
|
||||
pub use self::fdt::DeviceInfoForFdt;
|
||||
use crate::{DeviceType, GuestMemoryMmap, NumaNodes, PciSpaceInfo, RegionType};
|
||||
use hypervisor::arch::aarch64::gic::Vgic;
|
||||
use log::{log_enabled, Level};
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryInto;
|
||||
use std::fmt::Debug;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use vm_memory::{Address, GuestAddress, GuestMemory, GuestMemoryAtomic};
|
||||
|
||||
pub const _NSIG: i32 = 65;
|
||||
|
||||
/// Errors thrown while configuring aarch64 system.
|
||||
#[derive(Debug, Error)]
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Failed to create a FDT.
|
||||
#[error("Failed to create a FDT")]
|
||||
SetupFdt,
|
||||
|
||||
/// Failed to write FDT to memory.
|
||||
#[error("Failed to write FDT to memory: {0}")]
|
||||
WriteFdtToMemory(fdt::Error),
|
||||
|
||||
/// Failed to create a GIC.
|
||||
#[error("Failed to create a GIC")]
|
||||
SetupGic,
|
||||
|
||||
/// Failed to compute the initramfs address.
|
||||
#[error("Failed to compute the initramfs address")]
|
||||
InitramfsAddress,
|
||||
|
||||
/// Error configuring the general purpose registers
|
||||
#[error("Error configuring the general purpose registers: {0}")]
|
||||
RegsConfiguration(hypervisor::HypervisorCpuError),
|
||||
|
||||
/// Error configuring the MPIDR register
|
||||
#[error("Error configuring the MPIDR register: {0}")]
|
||||
VcpuRegMpidr(hypervisor::HypervisorCpuError),
|
||||
|
||||
/// Error initializing PMU for vcpu
|
||||
#[error("Error initializing PMU for vcpu")]
|
||||
VcpuInitPmu,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,28 +1,19 @@
|
||||
// Copyright 2020 Arm Limited (or its affiliates). All rights reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
use std::os::fd::AsFd;
|
||||
use std::result;
|
||||
|
||||
use thiserror::Error;
|
||||
use vm_memory::{GuestAddress, GuestMemory};
|
||||
use vm_memory::{Bytes, GuestAddress, GuestMemory};
|
||||
|
||||
/// Errors thrown while loading UEFI binary
|
||||
#[derive(Debug, Error)]
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Unable to seek to UEFI image start.
|
||||
#[error("Unable to seek to UEFI image start")]
|
||||
SeekUefiStart,
|
||||
/// Unable to seek to UEFI image end.
|
||||
#[error("Unable to seek to UEFI image end")]
|
||||
SeekUefiEnd,
|
||||
/// UEFI image too big.
|
||||
#[error("UEFI image too big")]
|
||||
UefiTooBig,
|
||||
/// Unable to read UEFI image
|
||||
#[error("Unable to read UEFI image")]
|
||||
ReadUefiImage,
|
||||
}
|
||||
type Result<T> = result::Result<T, Error>;
|
||||
@@ -33,7 +24,7 @@ pub fn load_uefi<F, M: GuestMemory>(
|
||||
uefi_image: &mut F,
|
||||
) -> Result<()>
|
||||
where
|
||||
F: Read + Seek + AsFd,
|
||||
F: Read + Seek,
|
||||
{
|
||||
let uefi_size = uefi_image
|
||||
.seek(SeekFrom::End(0))
|
||||
@@ -45,6 +36,6 @@ where
|
||||
}
|
||||
uefi_image.rewind().map_err(|_| Error::SeekUefiStart)?;
|
||||
guest_mem
|
||||
.read_exact_volatile_from(guest_addr, &mut uefi_image.as_fd(), uefi_size)
|
||||
.read_exact_from(guest_addr, uefi_image, uefi_size)
|
||||
.map_err(|_| Error::ReadUefiImage)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// Copyright © 2024 Institute of Software, CAS. All rights reserved.
|
||||
// Copyright 2020 Arm Limited (or its affiliates). All rights reserved.
|
||||
// Copyright © 2020, Oracle and/or its affiliates.
|
||||
//
|
||||
@@ -6,20 +5,22 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//! Implements platform specific functionality.
|
||||
//! Supported platforms: x86_64, aarch64, riscv64.
|
||||
//! Supported platforms: x86_64, aarch64.
|
||||
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::sync::Arc;
|
||||
use std::{fmt, result};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use crate::x86_64::SgxEpcSection;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::BTreeMap;
|
||||
use std::fmt;
|
||||
use std::result;
|
||||
use std::sync::Arc;
|
||||
use thiserror::Error;
|
||||
use versionize::{VersionMap, Versionize, VersionizeError, VersionizeResult};
|
||||
use versionize_derive::Versionize;
|
||||
use vm_migration::VersionMapped;
|
||||
|
||||
type GuestMemoryMmap = vm_memory::GuestMemoryMmap<vm_memory::bitmap::AtomicBitmap>;
|
||||
type GuestRegionMmap = vm_memory::GuestRegionMmap<vm_memory::bitmap::AtomicBitmap>;
|
||||
@@ -33,9 +34,6 @@ pub enum Error {
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
#[error("Platform specific error (aarch64): {0:?}")]
|
||||
PlatformSpecific(aarch64::Error),
|
||||
#[cfg(target_arch = "riscv64")]
|
||||
#[error("Platform specific error (riscv64): {0:?}")]
|
||||
PlatformSpecific(riscv64::Error),
|
||||
#[error("The memory map table extends past the end of guest memory")]
|
||||
MemmapTablePastRamEnd,
|
||||
#[error("Error writing memory map table to guest memory")]
|
||||
@@ -50,17 +48,13 @@ pub enum Error {
|
||||
ModlistSetup(#[source] vm_memory::GuestMemoryError),
|
||||
#[error("RSDP extends past the end of guest memory")]
|
||||
RsdpPastRamEnd,
|
||||
#[error("Failed to setup Zero Page for bzImage")]
|
||||
ZeroPageSetup(#[source] vm_memory::GuestMemoryError),
|
||||
#[error("Zero Page for bzImage past RAM end")]
|
||||
ZeroPagePastRamEnd,
|
||||
}
|
||||
|
||||
/// Type for returning public functions outcome.
|
||||
pub type Result<T> = result::Result<T, Error>;
|
||||
|
||||
/// Type for memory region types.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize)]
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize, Versionize)]
|
||||
pub enum RegionType {
|
||||
/// RAM type
|
||||
Ram,
|
||||
@@ -78,6 +72,8 @@ pub enum RegionType {
|
||||
Reserved,
|
||||
}
|
||||
|
||||
impl VersionMapped for RegionType {}
|
||||
|
||||
/// Module for aarch64 related functionality.
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub mod aarch64;
|
||||
@@ -89,26 +85,14 @@ pub use aarch64::{
|
||||
layout::IRQ_BASE, uefi, EntryPoint, _NSIG,
|
||||
};
|
||||
|
||||
/// Module for riscv64 related functionality.
|
||||
#[cfg(target_arch = "riscv64")]
|
||||
pub mod riscv64;
|
||||
|
||||
#[cfg(target_arch = "riscv64")]
|
||||
pub use riscv64::{
|
||||
arch_memory_regions, configure_system, configure_vcpu, fdt::DeviceInfoForFdt,
|
||||
get_host_cpu_phys_bits, initramfs_load_addr, layout, layout::CMDLINE_MAX_SIZE,
|
||||
layout::IRQ_BASE, EntryPoint, _NSIG,
|
||||
};
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub mod x86_64;
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub use x86_64::{
|
||||
arch_memory_regions, configure_system, configure_vcpu, generate_common_cpuid,
|
||||
generate_ram_ranges, get_host_cpu_phys_bits, initramfs_load_addr, layout,
|
||||
layout::CMDLINE_MAX_SIZE, layout::CMDLINE_START, regs, CpuidConfig, CpuidFeatureEntry,
|
||||
EntryPoint, _NSIG,
|
||||
get_host_cpu_phys_bits, initramfs_load_addr, layout, layout::CMDLINE_MAX_SIZE,
|
||||
layout::CMDLINE_START, regs, CpuidConfig, CpuidFeatureEntry, EntryPoint, _NSIG,
|
||||
};
|
||||
|
||||
/// Safe wrapper for `sysconf(_SC_PAGESIZE)`.
|
||||
@@ -147,7 +131,7 @@ pub enum DeviceType {
|
||||
/// Device Type: Virtio.
|
||||
Virtio(u32),
|
||||
/// Device Type: Serial.
|
||||
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
Serial,
|
||||
/// Device Type: RTC.
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
@@ -168,7 +152,7 @@ impl fmt::Display for DeviceType {
|
||||
|
||||
/// Structure to describe MMIO device information
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub struct MmioDeviceInfo {
|
||||
pub addr: u64,
|
||||
pub len: u64,
|
||||
@@ -177,7 +161,7 @@ pub struct MmioDeviceInfo {
|
||||
|
||||
/// Structure to describe PCI space information
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub struct PciSpaceInfo {
|
||||
pub pci_segment_id: u16,
|
||||
pub mmio_config_address: u64,
|
||||
@@ -185,7 +169,7 @@ pub struct PciSpaceInfo {
|
||||
pub pci_device_space_size: u64,
|
||||
}
|
||||
|
||||
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
impl DeviceInfoForFdt for MmioDeviceInfo {
|
||||
fn addr(&self) -> u64 {
|
||||
self.addr
|
||||
|
||||
@@ -1,484 +0,0 @@
|
||||
// Copyright © 2024 Institute of Software, CAS. All rights reserved.
|
||||
// Copyright 2020 Arm Limited (or its affiliates). All rights reserved.
|
||||
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the THIRD-PARTY file.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::CStr;
|
||||
use std::fmt::Debug;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::{cmp, result, str};
|
||||
|
||||
use byteorder::{BigEndian, ByteOrder};
|
||||
use hypervisor::arch::riscv64::aia::Vaia;
|
||||
use thiserror::Error;
|
||||
use vm_fdt::{FdtWriter, FdtWriterResult};
|
||||
use vm_memory::{Address, Bytes, GuestMemory, GuestMemoryError, GuestMemoryRegion};
|
||||
|
||||
use super::super::{DeviceType, GuestMemoryMmap, InitramfsConfig};
|
||||
use super::layout::{
|
||||
IRQ_BASE, MEM_32BIT_DEVICES_SIZE, MEM_32BIT_DEVICES_START, MEM_PCI_IO_SIZE, MEM_PCI_IO_START,
|
||||
PCI_HIGH_BASE, PCI_MMIO_CONFIG_SIZE_PER_SEGMENT,
|
||||
};
|
||||
use crate::PciSpaceInfo;
|
||||
|
||||
const AIA_APLIC_PHANDLE: u32 = 1;
|
||||
const AIA_IMSIC_PHANDLE: u32 = 2;
|
||||
const CPU_INTC_BASE_PHANDLE: u32 = 3;
|
||||
const CPU_BASE_PHANDLE: u32 = 256 + CPU_INTC_BASE_PHANDLE;
|
||||
// Read the documentation specified when appending the root node to the FDT.
|
||||
const ADDRESS_CELLS: u32 = 0x2;
|
||||
const SIZE_CELLS: u32 = 0x2;
|
||||
|
||||
// From https://elixir.bootlin.com/linux/v6.10/source/include/dt-bindings/interrupt-controller/irq.h#L14
|
||||
const _IRQ_TYPE_EDGE_RISING: u32 = 1;
|
||||
const IRQ_TYPE_LEVEL_HI: u32 = 4;
|
||||
|
||||
const S_MODE_EXT_IRQ: u32 = 9;
|
||||
|
||||
/// Trait for devices to be added to the Flattened Device Tree.
|
||||
pub trait DeviceInfoForFdt {
|
||||
/// Returns the address where this device will be loaded.
|
||||
fn addr(&self) -> u64;
|
||||
/// Returns the associated interrupt for this device.
|
||||
fn irq(&self) -> u32;
|
||||
/// Returns the amount of memory that needs to be reserved for this device.
|
||||
fn length(&self) -> u64;
|
||||
}
|
||||
|
||||
/// Errors thrown while configuring the Flattened Device Tree for riscv64.
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
/// Failure in writing FDT in memory.
|
||||
#[error("Failure in writing FDT in memory: {0}")]
|
||||
WriteFdtToMemory(GuestMemoryError),
|
||||
}
|
||||
type Result<T> = result::Result<T, Error>;
|
||||
|
||||
/// Creates the flattened device tree for this riscv64 VM.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn create_fdt<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHasher>(
|
||||
guest_mem: &GuestMemoryMmap,
|
||||
cmdline: &str,
|
||||
num_vcpu: u32,
|
||||
device_info: &HashMap<(DeviceType, String), T, S>,
|
||||
aia_device: &Arc<Mutex<dyn Vaia>>,
|
||||
initrd: &Option<InitramfsConfig>,
|
||||
pci_space_info: &[PciSpaceInfo],
|
||||
) -> FdtWriterResult<Vec<u8>> {
|
||||
// Allocate stuff necessary for the holding the blob.
|
||||
let mut fdt = FdtWriter::new()?;
|
||||
|
||||
// For an explanation why these nodes were introduced in the blob take a look at
|
||||
// https://github.com/devicetree-org/devicetree-specification/releases/tag/v0.4
|
||||
// In chapter 3.
|
||||
|
||||
// Header or the root node as per above mentioned documentation.
|
||||
let root_node = fdt.begin_node("")?;
|
||||
fdt.property_string("compatible", "linux,dummy-virt")?;
|
||||
// For info on #address-cells and size-cells resort to Table 3.1 Root Node
|
||||
// Properties
|
||||
fdt.property_u32("#address-cells", ADDRESS_CELLS)?;
|
||||
fdt.property_u32("#size-cells", SIZE_CELLS)?;
|
||||
create_cpu_nodes(&mut fdt, num_vcpu)?;
|
||||
create_memory_node(&mut fdt, guest_mem)?;
|
||||
create_chosen_node(&mut fdt, cmdline, initrd)?;
|
||||
create_aia_node(&mut fdt, aia_device)?;
|
||||
create_devices_node(&mut fdt, device_info)?;
|
||||
create_pci_nodes(&mut fdt, pci_space_info)?;
|
||||
|
||||
// End Header node.
|
||||
fdt.end_node(root_node)?;
|
||||
|
||||
let fdt_final = fdt.finish()?;
|
||||
|
||||
Ok(fdt_final)
|
||||
}
|
||||
|
||||
pub fn write_fdt_to_memory(fdt_final: Vec<u8>, guest_mem: &GuestMemoryMmap) -> Result<()> {
|
||||
// Write FDT to memory.
|
||||
guest_mem
|
||||
.write_slice(fdt_final.as_slice(), super::layout::FDT_START)
|
||||
.map_err(Error::WriteFdtToMemory)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Following are the auxiliary function for creating the different nodes that we append to our FDT.
|
||||
fn create_cpu_nodes(fdt: &mut FdtWriter, num_cpus: u32) -> FdtWriterResult<()> {
|
||||
// See https://elixir.bootlin.com/linux/v6.10/source/Documentation/devicetree/bindings/riscv/cpus.yaml
|
||||
let cpus = fdt.begin_node("cpus")?;
|
||||
// As per documentation, on RISC-V 64-bit systems value should be set to 1.
|
||||
fdt.property_u32("#address-cells", 0x01)?;
|
||||
fdt.property_u32("#size-cells", 0x0)?;
|
||||
// TODO: Retrieve CPU frequency from cpu timer regs
|
||||
let timebase_frequency: u32 = 0x989680;
|
||||
fdt.property_u32("timebase-frequency", timebase_frequency)?;
|
||||
|
||||
for cpu_index in 0..num_cpus {
|
||||
let cpu = fdt.begin_node(&format!("cpu@{:x}", cpu_index))?;
|
||||
fdt.property_string("device_type", "cpu")?;
|
||||
fdt.property_string("compatible", "riscv")?;
|
||||
fdt.property_string("mmu-type", "sv48")?;
|
||||
fdt.property_string("riscv,isa", "rv64imafdc_smaia_ssaia")?;
|
||||
fdt.property_string("status", "okay")?;
|
||||
fdt.property_u32("reg", cpu_index)?;
|
||||
fdt.property_u32("phandle", CPU_BASE_PHANDLE + cpu_index)?;
|
||||
|
||||
// interrupt controller node
|
||||
let intc_node = fdt.begin_node("interrupt-controller")?;
|
||||
fdt.property_string("compatible", "riscv,cpu-intc")?;
|
||||
fdt.property_u32("#interrupt-cells", 1u32)?;
|
||||
fdt.property_null("interrupt-controller")?;
|
||||
fdt.property_u32("phandle", CPU_INTC_BASE_PHANDLE + cpu_index)?;
|
||||
fdt.end_node(intc_node)?;
|
||||
|
||||
fdt.end_node(cpu)?;
|
||||
}
|
||||
|
||||
fdt.end_node(cpus)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_memory_node(fdt: &mut FdtWriter, guest_mem: &GuestMemoryMmap) -> FdtWriterResult<()> {
|
||||
// Note: memory regions from "GuestMemory" are sorted and non-zero sized.
|
||||
let ram_regions = {
|
||||
let mut ram_regions = Vec::new();
|
||||
let mut current_start = guest_mem
|
||||
.iter()
|
||||
.next()
|
||||
.map(GuestMemoryRegion::start_addr)
|
||||
.expect("GuestMemory must have one memory region at least")
|
||||
.raw_value();
|
||||
let mut current_end = current_start;
|
||||
|
||||
for (start, size) in guest_mem
|
||||
.iter()
|
||||
.map(|m| (m.start_addr().raw_value(), m.len()))
|
||||
{
|
||||
if current_end == start {
|
||||
// This zone is continuous with the previous one.
|
||||
current_end += size;
|
||||
} else {
|
||||
ram_regions.push((current_start, current_end));
|
||||
|
||||
current_start = start;
|
||||
current_end = start + size;
|
||||
}
|
||||
}
|
||||
|
||||
ram_regions.push((current_start, current_end));
|
||||
|
||||
ram_regions
|
||||
};
|
||||
|
||||
let mut mem_reg_property = Vec::new();
|
||||
for region in ram_regions {
|
||||
let mem_size = region.1 - region.0;
|
||||
mem_reg_property.push(region.0);
|
||||
mem_reg_property.push(mem_size);
|
||||
}
|
||||
|
||||
let ram_start = super::layout::RAM_START.raw_value();
|
||||
let memory_node_name = format!("memory@{:x}", ram_start);
|
||||
let memory_node = fdt.begin_node(&memory_node_name)?;
|
||||
fdt.property_string("device_type", "memory")?;
|
||||
fdt.property_array_u64("reg", &mem_reg_property)?;
|
||||
fdt.end_node(memory_node)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_chosen_node(
|
||||
fdt: &mut FdtWriter,
|
||||
cmdline: &str,
|
||||
initrd: &Option<InitramfsConfig>,
|
||||
) -> FdtWriterResult<()> {
|
||||
let chosen_node = fdt.begin_node("chosen")?;
|
||||
fdt.property_string("bootargs", cmdline)?;
|
||||
|
||||
if let Some(initrd_config) = initrd {
|
||||
let initrd_start = initrd_config.address.raw_value();
|
||||
let initrd_end = initrd_config.address.raw_value() + initrd_config.size as u64;
|
||||
fdt.property_u64("linux,initrd-start", initrd_start)?;
|
||||
fdt.property_u64("linux,initrd-end", initrd_end)?;
|
||||
}
|
||||
|
||||
fdt.end_node(chosen_node)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_aia_node(fdt: &mut FdtWriter, aia_device: &Arc<Mutex<dyn Vaia>>) -> FdtWriterResult<()> {
|
||||
// IMSIC
|
||||
if aia_device.lock().unwrap().msi_compatible() {
|
||||
use super::layout::IMSIC_START;
|
||||
let imsic_name = format!("imsics@{:x}", IMSIC_START.0);
|
||||
let imsic_node = fdt.begin_node(&imsic_name)?;
|
||||
|
||||
fdt.property_string(
|
||||
"compatible",
|
||||
aia_device.lock().unwrap().imsic_compatibility(),
|
||||
)?;
|
||||
let imsic_reg_prop = aia_device.lock().unwrap().imsic_properties();
|
||||
fdt.property_array_u32("reg", &imsic_reg_prop)?;
|
||||
fdt.property_u32("#interrupt-cells", 0u32)?;
|
||||
fdt.property_null("interrupt-controller")?;
|
||||
fdt.property_null("msi-controller")?;
|
||||
// TODO complete num-ids
|
||||
fdt.property_u32("riscv,num-ids", 2047u32)?;
|
||||
fdt.property_u32("phandle", AIA_IMSIC_PHANDLE)?;
|
||||
|
||||
let mut irq_cells = Vec::new();
|
||||
let num_cpus = aia_device.lock().unwrap().vcpu_count();
|
||||
for i in 0..num_cpus {
|
||||
irq_cells.push(CPU_INTC_BASE_PHANDLE + i);
|
||||
irq_cells.push(S_MODE_EXT_IRQ);
|
||||
}
|
||||
fdt.property_array_u32("interrupts-extended", &irq_cells)?;
|
||||
|
||||
fdt.end_node(imsic_node)?;
|
||||
}
|
||||
|
||||
// APLIC
|
||||
use super::layout::APLIC_START;
|
||||
let aplic_name = format!("aplic@{:x}", APLIC_START.0);
|
||||
let aplic_node = fdt.begin_node(&aplic_name)?;
|
||||
|
||||
fdt.property_string(
|
||||
"compatible",
|
||||
aia_device.lock().unwrap().aplic_compatibility(),
|
||||
)?;
|
||||
let reg_cells = aia_device.lock().unwrap().aplic_properties();
|
||||
fdt.property_array_u32("reg", ®_cells)?;
|
||||
fdt.property_u32("#interrupt-cells", 2u32)?;
|
||||
fdt.property_null("interrupt-controller")?;
|
||||
// TODO complete num-srcs
|
||||
fdt.property_u32("riscv,num-sources", 96u32)?;
|
||||
fdt.property_u32("phandle", AIA_APLIC_PHANDLE)?;
|
||||
fdt.property_u32("msi-parent", AIA_IMSIC_PHANDLE)?;
|
||||
|
||||
fdt.end_node(aplic_node)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_serial_node<T: DeviceInfoForFdt + Clone + Debug>(
|
||||
fdt: &mut FdtWriter,
|
||||
dev_info: &T,
|
||||
) -> FdtWriterResult<()> {
|
||||
let serial_reg_prop = [dev_info.addr(), dev_info.length()];
|
||||
let irq = [dev_info.irq() - IRQ_BASE, IRQ_TYPE_LEVEL_HI];
|
||||
|
||||
let serial_node = fdt.begin_node(&format!("serial@{:x}", dev_info.addr()))?;
|
||||
fdt.property_string("compatible", "ns16550a")?;
|
||||
fdt.property_array_u64("reg", &serial_reg_prop)?;
|
||||
fdt.property_u32("clock-frequency", 3686400)?;
|
||||
fdt.property_u32("interrupt-parent", AIA_APLIC_PHANDLE)?;
|
||||
fdt.property_array_u32("interrupts", &irq)?;
|
||||
fdt.end_node(serial_node)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_devices_node<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHasher>(
|
||||
fdt: &mut FdtWriter,
|
||||
dev_info: &HashMap<(DeviceType, String), T, S>,
|
||||
) -> FdtWriterResult<()> {
|
||||
for ((device_type, _device_id), info) in dev_info {
|
||||
match device_type {
|
||||
DeviceType::Serial => create_serial_node(fdt, info)?,
|
||||
DeviceType::Virtio(_) => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_pci_nodes(fdt: &mut FdtWriter, pci_device_info: &[PciSpaceInfo]) -> FdtWriterResult<()> {
|
||||
// Add node for PCIe controller.
|
||||
// See Documentation/devicetree/bindings/pci/host-generic-pci.txt in the kernel
|
||||
// and https://elinux.org/Device_Tree_Usage.
|
||||
// In multiple PCI segments setup, each PCI segment needs a PCI node.
|
||||
for pci_device_info_elem in pci_device_info.iter() {
|
||||
// EDK2 requires the PCIe high space above 4G address.
|
||||
// The actual space in CLH follows the RAM. If the RAM space is small, the PCIe high space
|
||||
// could fall below 4G.
|
||||
// Here we cut off PCI device space below 8G in FDT to workaround the EDK2 check.
|
||||
// But the address written in ACPI is not impacted.
|
||||
let (pci_device_base_64bit, pci_device_size_64bit) =
|
||||
if pci_device_info_elem.pci_device_space_start < PCI_HIGH_BASE.raw_value() {
|
||||
(
|
||||
PCI_HIGH_BASE.raw_value(),
|
||||
pci_device_info_elem.pci_device_space_size
|
||||
- (PCI_HIGH_BASE.raw_value() - pci_device_info_elem.pci_device_space_start),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
pci_device_info_elem.pci_device_space_start,
|
||||
pci_device_info_elem.pci_device_space_size,
|
||||
)
|
||||
};
|
||||
// There is no specific requirement of the 32bit MMIO range, and
|
||||
// therefore at least we can make these ranges 4K aligned.
|
||||
let pci_device_size_32bit: u64 =
|
||||
MEM_32BIT_DEVICES_SIZE / ((1 << 12) * pci_device_info.len() as u64) * (1 << 12);
|
||||
let pci_device_base_32bit: u64 = MEM_32BIT_DEVICES_START.0
|
||||
+ pci_device_size_32bit * pci_device_info_elem.pci_segment_id as u64;
|
||||
|
||||
let ranges = [
|
||||
// io addresses. Since AArch64 will not use IO address,
|
||||
// we can set the same IO address range for every segment.
|
||||
0x1000000,
|
||||
0_u32,
|
||||
0_u32,
|
||||
(MEM_PCI_IO_START.0 >> 32) as u32,
|
||||
MEM_PCI_IO_START.0 as u32,
|
||||
(MEM_PCI_IO_SIZE >> 32) as u32,
|
||||
MEM_PCI_IO_SIZE as u32,
|
||||
// mmio addresses
|
||||
0x2000000, // (ss = 10: 32-bit memory space)
|
||||
(pci_device_base_32bit >> 32) as u32, // PCI address
|
||||
pci_device_base_32bit as u32,
|
||||
(pci_device_base_32bit >> 32) as u32, // CPU address
|
||||
pci_device_base_32bit as u32,
|
||||
(pci_device_size_32bit >> 32) as u32, // size
|
||||
pci_device_size_32bit as u32,
|
||||
// device addresses
|
||||
0x3000000, // (ss = 11: 64-bit memory space)
|
||||
(pci_device_base_64bit >> 32) as u32, // PCI address
|
||||
pci_device_base_64bit as u32,
|
||||
(pci_device_base_64bit >> 32) as u32, // CPU address
|
||||
pci_device_base_64bit as u32,
|
||||
(pci_device_size_64bit >> 32) as u32, // size
|
||||
pci_device_size_64bit as u32,
|
||||
];
|
||||
let bus_range = [0, 0]; // Only bus 0
|
||||
let reg = [
|
||||
pci_device_info_elem.mmio_config_address,
|
||||
PCI_MMIO_CONFIG_SIZE_PER_SEGMENT,
|
||||
];
|
||||
// See kernel document Documentation/devicetree/bindings/pci/pci-msi.txt
|
||||
let msi_map = [
|
||||
// rid-base: A single cell describing the first RID matched by the entry.
|
||||
0x0,
|
||||
// msi-controller: A single phandle to an MSI controller.
|
||||
AIA_IMSIC_PHANDLE,
|
||||
// msi-base: An msi-specifier describing the msi-specifier produced for the
|
||||
// first RID matched by the entry.
|
||||
(pci_device_info_elem.pci_segment_id as u32) << 8,
|
||||
// length: A single cell describing how many consecutive RIDs are matched
|
||||
// following the rid-base.
|
||||
0x100,
|
||||
];
|
||||
|
||||
let pci_node_name = format!("pci@{:x}", pci_device_info_elem.mmio_config_address);
|
||||
let pci_node = fdt.begin_node(&pci_node_name)?;
|
||||
|
||||
fdt.property_string("compatible", "pci-host-ecam-generic")?;
|
||||
fdt.property_string("device_type", "pci")?;
|
||||
fdt.property_array_u32("ranges", &ranges)?;
|
||||
fdt.property_array_u32("bus-range", &bus_range)?;
|
||||
fdt.property_u32(
|
||||
"linux,pci-domain",
|
||||
pci_device_info_elem.pci_segment_id as u32,
|
||||
)?;
|
||||
fdt.property_u32("#address-cells", 3)?;
|
||||
fdt.property_u32("#size-cells", 2)?;
|
||||
fdt.property_array_u64("reg", ®)?;
|
||||
fdt.property_u32("#interrupt-cells", 1)?;
|
||||
fdt.property_null("interrupt-map")?;
|
||||
fdt.property_null("interrupt-map-mask")?;
|
||||
fdt.property_null("dma-coherent")?;
|
||||
fdt.property_array_u32("msi-map", &msi_map)?;
|
||||
fdt.property_u32("msi-parent", AIA_IMSIC_PHANDLE)?;
|
||||
|
||||
fdt.end_node(pci_node)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Parse the DTB binary and print for debugging
|
||||
pub fn print_fdt(dtb: &[u8]) {
|
||||
match fdt_parser::Fdt::new(dtb) {
|
||||
Ok(fdt) => {
|
||||
if let Some(root) = fdt.find_node("/") {
|
||||
debug!("Printing the FDT:");
|
||||
print_node(root, 0);
|
||||
} else {
|
||||
debug!("Failed to find root node in FDT for debugging.");
|
||||
}
|
||||
}
|
||||
Err(_) => debug!("Failed to parse FDT for debugging."),
|
||||
}
|
||||
}
|
||||
|
||||
fn print_node(node: fdt_parser::node::FdtNode<'_, '_>, n_spaces: usize) {
|
||||
debug!("{:indent$}{}/", "", node.name, indent = n_spaces);
|
||||
for property in node.properties() {
|
||||
let name = property.name;
|
||||
|
||||
// If the property is 'compatible', its value requires special handling.
|
||||
// The u8 array could contain multiple null-terminated strings.
|
||||
// We copy the original array and simply replace all 'null' characters with spaces.
|
||||
let value = if name == "compatible" {
|
||||
let mut compatible = vec![0u8; 256];
|
||||
let handled_value = property
|
||||
.value
|
||||
.iter()
|
||||
.map(|&c| if c == 0 { b' ' } else { c })
|
||||
.collect::<Vec<_>>();
|
||||
let len = cmp::min(255, handled_value.len());
|
||||
compatible[..len].copy_from_slice(&handled_value[..len]);
|
||||
compatible[..(len + 1)].to_vec()
|
||||
} else {
|
||||
property.value.to_vec()
|
||||
};
|
||||
let value = &value;
|
||||
|
||||
// Now the value can be either:
|
||||
// - A null-terminated C string, or
|
||||
// - Binary data
|
||||
// We follow a very simple logic to present the value:
|
||||
// - At first, try to convert it to CStr and print,
|
||||
// - If failed, print it as u32 array.
|
||||
let value_result = match CStr::from_bytes_with_nul(value) {
|
||||
Ok(value_cstr) => match value_cstr.to_str() {
|
||||
Ok(value_str) => Some(value_str),
|
||||
Err(_e) => None,
|
||||
},
|
||||
Err(_e) => None,
|
||||
};
|
||||
|
||||
if let Some(value_str) = value_result {
|
||||
debug!(
|
||||
"{:indent$}{} : {:#?}",
|
||||
"",
|
||||
name,
|
||||
value_str,
|
||||
indent = (n_spaces + 2)
|
||||
);
|
||||
} else {
|
||||
let mut array = Vec::with_capacity(256);
|
||||
array.resize(value.len() / 4, 0u32);
|
||||
BigEndian::read_u32_into(value, &mut array);
|
||||
debug!(
|
||||
"{:indent$}{} : {:X?}",
|
||||
"",
|
||||
name,
|
||||
array,
|
||||
indent = (n_spaces + 2)
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
// Print children nodes if there is any
|
||||
for child in node.children() {
|
||||
print_node(child, n_spaces + 2);
|
||||
}
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
// Copyright © 2024 Institute of Software, CAS. All rights reserved.
|
||||
// Copyright 2020 Arm Limited (or its affiliates). All rights reserved.
|
||||
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//
|
||||
// Memory layout of RISC-V 64-bit guest:
|
||||
//
|
||||
// Physical +---------------------------------------------------------------+
|
||||
// address | |
|
||||
// end | |
|
||||
// ~ ~ ~ ~
|
||||
// | |
|
||||
// | Highmem PCI MMIO space |
|
||||
// | |
|
||||
// RAM end +---------------------------------------------------------------+
|
||||
// (dynamic, | |
|
||||
// including | |
|
||||
// hotplug ~ ~ ~ ~
|
||||
// memory) | |
|
||||
// | DRAM |
|
||||
// | |
|
||||
// | |
|
||||
// | |
|
||||
// | |
|
||||
// 1 GB +---------------------------------------------------------------+
|
||||
// | |
|
||||
// | PCI MMCONFIG space |
|
||||
// | |
|
||||
// 768 MB +---------------------------------------------------------------+
|
||||
// | |
|
||||
// | |
|
||||
// | PCI MMIO space |
|
||||
// | |
|
||||
// 256 MB +---------------------------------------------------------------|
|
||||
// | |
|
||||
// | Legacy devices space |
|
||||
// | |
|
||||
// 128 MB +---------------------------------------------------------------|
|
||||
// | |
|
||||
// | IMSICs |
|
||||
// | |
|
||||
// 64 MB +---------------------------------------------------------------+
|
||||
// | |
|
||||
// | APLICs |
|
||||
// | |
|
||||
// 0 GB +---------------------------------------------------------------+
|
||||
//
|
||||
//
|
||||
|
||||
use vm_memory::GuestAddress;
|
||||
|
||||
/// AIA related devices
|
||||
/// See https://elixir.bootlin.com/linux/v6.10/source/arch/riscv/include/uapi/asm/kvm.h
|
||||
/// 0x0 ~ 0x0400_0000 (64 MiB) resides APLICs
|
||||
pub const APLIC_START: GuestAddress = GuestAddress(0);
|
||||
pub const APLIC_SIZE: u64 = 0x4000;
|
||||
|
||||
/// 0x0400_0000 ~ 0x0800_0000 (64 MiB) resides IMSICs
|
||||
pub const IMSIC_START: GuestAddress = GuestAddress(0x0400_0000);
|
||||
pub const IMSIC_SIZE: u64 = 0x1000;
|
||||
|
||||
/// Below this address will reside the AIA, above this address will reside the MMIO devices.
|
||||
const MAPPED_IO_START: GuestAddress = GuestAddress(0x0800_0000);
|
||||
|
||||
/// Space 0x0800_0000 ~ 0x1000_0000 is reserved for legacy devices.
|
||||
pub const LEGACY_SERIAL_MAPPED_IO_START: GuestAddress = MAPPED_IO_START;
|
||||
|
||||
/// Space 0x0905_0000 ~ 0x0906_0000 is reserved for pcie io address
|
||||
pub const MEM_PCI_IO_START: GuestAddress = GuestAddress(0x0905_0000);
|
||||
pub const MEM_PCI_IO_SIZE: u64 = 0x1_0000;
|
||||
|
||||
/// Starting from 0x1000_0000 (256MiB) to 0x3000_0000 (768MiB) is used for PCIE MMIO
|
||||
pub const MEM_32BIT_DEVICES_START: GuestAddress = GuestAddress(0x1000_0000);
|
||||
pub const MEM_32BIT_DEVICES_SIZE: u64 = 0x2000_0000;
|
||||
|
||||
/// PCI MMCONFIG space (start: after the device space at 768MiB, length: 256MiB)
|
||||
pub const PCI_MMCONFIG_START: GuestAddress = GuestAddress(0x3000_0000);
|
||||
pub const PCI_MMCONFIG_SIZE: u64 = 256 << 20;
|
||||
// One bus with potentially 256 devices (32 slots x 8 functions).
|
||||
pub const PCI_MMIO_CONFIG_SIZE_PER_SEGMENT: u64 = 4096 * 256;
|
||||
|
||||
/// Start of RAM.
|
||||
pub const RAM_START: GuestAddress = GuestAddress(0x4000_0000);
|
||||
|
||||
/// Kernel command line maximum size on RISC-V.
|
||||
/// See https://elixir.bootlin.com/linux/v6.10/source/arch/riscv/include/uapi/asm/setup.h
|
||||
pub const CMDLINE_MAX_SIZE: usize = 1024;
|
||||
|
||||
/// FDT is at the beginning of RAM.
|
||||
pub const FDT_START: GuestAddress = RAM_START;
|
||||
pub const FDT_MAX_SIZE: u64 = 0x1_0000;
|
||||
|
||||
/// Kernel start after FDT
|
||||
pub const KERNEL_START: GuestAddress = GuestAddress(RAM_START.0 + FDT_MAX_SIZE);
|
||||
|
||||
/// Pci high memory base
|
||||
pub const PCI_HIGH_BASE: GuestAddress = GuestAddress(0x2_0000_0000);
|
||||
|
||||
/// First usable interrupt on riscv64
|
||||
pub const IRQ_BASE: u32 = 0;
|
||||
|
||||
// As per https://elixir.bootlin.com/linux/v6.10/source/arch/riscv/include/asm/kvm_host.h#L31
|
||||
/// Number of supported interrupts
|
||||
pub const IRQ_NUM: u32 = 1023;
|
||||
@@ -1,173 +0,0 @@
|
||||
// Copyright © 2024 Institute of Software, CAS. All rights reserved.
|
||||
// Copyright 2020 Arm Limited (or its affiliates). All rights reserved.
|
||||
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// Module for the flattened device tree.
|
||||
pub mod fdt;
|
||||
/// Layout for this riscv64 system.
|
||||
pub mod layout;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Debug;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use hypervisor::arch::riscv64::aia::Vaia;
|
||||
use log::{log_enabled, Level};
|
||||
use thiserror::Error;
|
||||
use vm_memory::{Address, GuestAddress, GuestMemory, GuestMemoryAtomic};
|
||||
|
||||
pub use self::fdt::DeviceInfoForFdt;
|
||||
use crate::{DeviceType, GuestMemoryMmap, PciSpaceInfo, RegionType};
|
||||
|
||||
pub const _NSIG: i32 = 65;
|
||||
|
||||
/// Errors thrown while configuring riscv64 system.
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
/// Failed to create a FDT.
|
||||
#[error("Failed to create a FDT")]
|
||||
SetupFdt,
|
||||
|
||||
/// Failed to write FDT to memory.
|
||||
#[error("Failed to write FDT to memory: {0}")]
|
||||
WriteFdtToMemory(fdt::Error),
|
||||
|
||||
/// Failed to create a AIA.
|
||||
#[error("Failed to create a AIA")]
|
||||
SetupAia,
|
||||
|
||||
/// Failed to compute the initramfs address.
|
||||
#[error("Failed to compute the initramfs address")]
|
||||
InitramfsAddress,
|
||||
|
||||
/// Error configuring the general purpose registers
|
||||
#[error("Error configuring the general purpose registers: {0}")]
|
||||
RegsConfiguration(hypervisor::HypervisorCpuError),
|
||||
}
|
||||
|
||||
impl From<Error> for super::Error {
|
||||
fn from(e: Error) -> super::Error {
|
||||
super::Error::PlatformSpecific(e)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
/// Specifies the entry point address where the guest must start
|
||||
/// executing code.
|
||||
pub struct EntryPoint {
|
||||
/// Address in guest memory where the guest must start execution
|
||||
pub entry_addr: GuestAddress,
|
||||
}
|
||||
|
||||
/// Configure the specified VCPU, and return its MPIDR.
|
||||
pub fn configure_vcpu(
|
||||
vcpu: &Arc<dyn hypervisor::Vcpu>,
|
||||
id: u8,
|
||||
boot_setup: Option<(EntryPoint, &GuestMemoryAtomic<GuestMemoryMmap>)>,
|
||||
) -> super::Result<()> {
|
||||
if let Some((kernel_entry_point, _guest_memory)) = boot_setup {
|
||||
vcpu.setup_regs(
|
||||
id,
|
||||
kernel_entry_point.entry_addr.raw_value(),
|
||||
layout::FDT_START.raw_value(),
|
||||
)
|
||||
.map_err(Error::RegsConfiguration)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn arch_memory_regions() -> Vec<(GuestAddress, usize, RegionType)> {
|
||||
vec![
|
||||
// 0 MiB ~ 256 MiB: AIA and legacy devices
|
||||
(
|
||||
GuestAddress(0),
|
||||
layout::MEM_32BIT_DEVICES_START.0 as usize,
|
||||
RegionType::Reserved,
|
||||
),
|
||||
// 256 MiB ~ 768 MiB: MMIO space
|
||||
(
|
||||
layout::MEM_32BIT_DEVICES_START,
|
||||
layout::MEM_32BIT_DEVICES_SIZE as usize,
|
||||
RegionType::SubRegion,
|
||||
),
|
||||
// 768 MiB ~ 1 GiB: reserved. The leading 256M for PCIe MMCONFIG space
|
||||
(
|
||||
layout::PCI_MMCONFIG_START,
|
||||
layout::PCI_MMCONFIG_SIZE as usize,
|
||||
RegionType::Reserved,
|
||||
),
|
||||
// 1GiB ~ inf: RAM
|
||||
(layout::RAM_START, usize::MAX, RegionType::Ram),
|
||||
]
|
||||
}
|
||||
|
||||
/// Configures the system and should be called once per vm before starting vcpu threads.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn configure_system<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHasher>(
|
||||
guest_mem: &GuestMemoryMmap,
|
||||
cmdline: &str,
|
||||
num_vcpu: u32,
|
||||
device_info: &HashMap<(DeviceType, String), T, S>,
|
||||
initrd: &Option<super::InitramfsConfig>,
|
||||
pci_space_info: &[PciSpaceInfo],
|
||||
aia_device: &Arc<Mutex<dyn Vaia>>,
|
||||
) -> super::Result<()> {
|
||||
let fdt_final = fdt::create_fdt(
|
||||
guest_mem,
|
||||
cmdline,
|
||||
num_vcpu,
|
||||
device_info,
|
||||
aia_device,
|
||||
initrd,
|
||||
pci_space_info,
|
||||
)
|
||||
.map_err(|_| Error::SetupFdt)?;
|
||||
|
||||
if log_enabled!(Level::Debug) {
|
||||
fdt::print_fdt(&fdt_final);
|
||||
}
|
||||
|
||||
fdt::write_fdt_to_memory(fdt_final, guest_mem).map_err(Error::WriteFdtToMemory)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns the memory address where the initramfs could be loaded.
|
||||
pub fn initramfs_load_addr(
|
||||
guest_mem: &GuestMemoryMmap,
|
||||
initramfs_size: usize,
|
||||
) -> super::Result<u64> {
|
||||
let round_to_pagesize = |size| (size + (super::PAGE_SIZE - 1)) & !(super::PAGE_SIZE - 1);
|
||||
match guest_mem
|
||||
.last_addr()
|
||||
.checked_sub(round_to_pagesize(initramfs_size) as u64 - 1)
|
||||
{
|
||||
Some(offset) => {
|
||||
if guest_mem.address_in_range(offset) {
|
||||
Ok(offset.raw_value())
|
||||
} else {
|
||||
Err(super::Error::PlatformSpecific(Error::InitramfsAddress))
|
||||
}
|
||||
}
|
||||
None => Err(super::Error::PlatformSpecific(Error::InitramfsAddress)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_host_cpu_phys_bits(_hypervisor: &Arc<dyn hypervisor::Hypervisor>) -> u8 {
|
||||
40
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_arch_memory_regions_dram() {
|
||||
let regions = arch_memory_regions();
|
||||
assert_eq!(4, regions.len());
|
||||
assert_eq!(layout::RAM_START, regions[3].0);
|
||||
assert_eq!(RegionType::Ram, regions[3].2);
|
||||
}
|
||||
}
|
||||
@@ -12,29 +12,27 @@ pub mod layout;
|
||||
mod mpspec;
|
||||
mod mptable;
|
||||
pub mod regs;
|
||||
use std::collections::BTreeMap;
|
||||
use std::mem;
|
||||
|
||||
use crate::GuestMemoryMmap;
|
||||
use crate::InitramfsConfig;
|
||||
use crate::RegionType;
|
||||
use hypervisor::arch::x86::{CpuIdEntry, CPUID_FLAG_VALID_INDEX};
|
||||
use hypervisor::{CpuVendor, HypervisorCpuError, HypervisorError};
|
||||
use linux_loader::loader::bootparam::{boot_params, setup_header};
|
||||
use linux_loader::loader::bootparam::boot_params;
|
||||
use linux_loader::loader::elf::start_info::{
|
||||
hvm_memmap_table_entry, hvm_modlist_entry, hvm_start_info,
|
||||
};
|
||||
use thiserror::Error;
|
||||
use std::collections::BTreeMap;
|
||||
use std::mem;
|
||||
use vm_memory::{
|
||||
Address, Bytes, GuestAddress, GuestAddressSpace, GuestMemory, GuestMemoryAtomic,
|
||||
Address, ByteValued, Bytes, GuestAddress, GuestAddressSpace, GuestMemory, GuestMemoryAtomic,
|
||||
GuestMemoryRegion, GuestUsize,
|
||||
};
|
||||
|
||||
use crate::{GuestMemoryMmap, InitramfsConfig, RegionType};
|
||||
mod smbios;
|
||||
use std::arch::x86_64;
|
||||
#[cfg(feature = "tdx")]
|
||||
pub mod tdx;
|
||||
|
||||
// CPUID feature bits
|
||||
#[cfg(feature = "kvm")]
|
||||
const TSC_DEADLINE_TIMER_ECX_BIT: u8 = 24; // tsc deadline timer ecx bit.
|
||||
const HYPERVISOR_ECX_BIT: u8 = 31; // Hypervisor ecx bit.
|
||||
const MTRR_EDX_BIT: u8 = 12; // Hypervisor ecx bit.
|
||||
@@ -66,8 +64,6 @@ pub const _NSIG: i32 = 65;
|
||||
pub struct EntryPoint {
|
||||
/// Address in guest memory where the guest must start execution
|
||||
pub entry_addr: GuestAddress,
|
||||
/// This field is used for bzImage to fill the zero page
|
||||
pub setup_header: Option<setup_header>,
|
||||
}
|
||||
|
||||
const E820_RAM: u32 = 1;
|
||||
@@ -120,6 +116,38 @@ impl SgxEpcRegion {
|
||||
}
|
||||
}
|
||||
|
||||
// This is a workaround to the Rust enforcement specifying that any implementation of a foreign
|
||||
// trait (in this case `DataInit`) where:
|
||||
// * the type that is implementing the trait is foreign or
|
||||
// * all of the parameters being passed to the trait (if there are any) are also foreign
|
||||
// is prohibited.
|
||||
#[derive(Copy, Clone, Default)]
|
||||
struct StartInfoWrapper(hvm_start_info);
|
||||
|
||||
#[derive(Copy, Clone, Default)]
|
||||
struct MemmapTableEntryWrapper(hvm_memmap_table_entry);
|
||||
|
||||
#[derive(Copy, Clone, Default)]
|
||||
struct ModlistEntryWrapper(hvm_modlist_entry);
|
||||
|
||||
// SAFETY: data structure only contain a series of integers
|
||||
unsafe impl ByteValued for StartInfoWrapper {}
|
||||
// SAFETY: data structure only contain a series of integers
|
||||
unsafe impl ByteValued for MemmapTableEntryWrapper {}
|
||||
// SAFETY: data structure only contain a series of integers
|
||||
unsafe impl ByteValued for ModlistEntryWrapper {}
|
||||
|
||||
// This is a workaround to the Rust enforcement specifying that any implementation of a foreign
|
||||
// trait (in this case `DataInit`) where:
|
||||
// * the type that is implementing the trait is foreign or
|
||||
// * all of the parameters being passed to the trait (if there are any) are also foreign
|
||||
// is prohibited.
|
||||
#[derive(Copy, Clone, Default)]
|
||||
struct BootParamsWrapper(boot_params);
|
||||
|
||||
// SAFETY: BootParamsWrap is a wrapper over `boot_params` (a series of ints).
|
||||
unsafe impl ByteValued for BootParamsWrapper {}
|
||||
|
||||
pub struct CpuidConfig {
|
||||
pub sgx_epc_sections: Option<Vec<SgxEpcSection>>,
|
||||
pub phys_bits: u8,
|
||||
@@ -129,84 +157,62 @@ pub struct CpuidConfig {
|
||||
pub amx: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Error writing MP table to memory.
|
||||
#[error("Error writing MP table to memory: {0}")]
|
||||
MpTableSetup(mptable::Error),
|
||||
|
||||
/// Error configuring the general purpose registers
|
||||
#[error("Error configuring the general purpose registers: {0}")]
|
||||
RegsConfiguration(regs::Error),
|
||||
|
||||
/// Error configuring the special registers
|
||||
#[error("Error configuring the special registers: {0}")]
|
||||
SregsConfiguration(regs::Error),
|
||||
|
||||
/// Error configuring the floating point related registers
|
||||
#[error("Error configuring the floating point related registers: {0}")]
|
||||
FpuConfiguration(regs::Error),
|
||||
|
||||
/// Error configuring the MSR registers
|
||||
#[error("Error configuring the MSR registers: {0}")]
|
||||
MsrsConfiguration(regs::Error),
|
||||
|
||||
/// Failed to set supported CPUs.
|
||||
#[error("Failed to set supported CPUs: {0}")]
|
||||
SetSupportedCpusFailed(anyhow::Error),
|
||||
|
||||
/// Cannot set the local interruption due to bad configuration.
|
||||
#[error("Cannot set the local interruption due to bad configuration: {0}")]
|
||||
LocalIntConfiguration(anyhow::Error),
|
||||
|
||||
/// Error setting up SMBIOS table
|
||||
#[error("Error setting up SMBIOS table: {0}")]
|
||||
SmbiosSetup(smbios::Error),
|
||||
|
||||
/// Could not find any SGX EPC section
|
||||
#[error("Could not find any SGX EPC section")]
|
||||
NoSgxEpcSection,
|
||||
|
||||
/// Missing SGX CPU feature
|
||||
#[error("Missing SGX CPU feature")]
|
||||
MissingSgxFeature,
|
||||
|
||||
/// Missing SGX_LC CPU feature
|
||||
#[error("Missing SGX_LC CPU feature")]
|
||||
MissingSgxLaunchControlFeature,
|
||||
|
||||
/// Error getting supported CPUID through the hypervisor (kvm/mshv) API
|
||||
#[error("Error getting supported CPUID through the hypervisor API: {0}")]
|
||||
CpuidGetSupported(HypervisorError),
|
||||
|
||||
/// Error populating CPUID with KVM HyperV emulation details
|
||||
#[error("Error populating CPUID with KVM HyperV emulation details: {0}")]
|
||||
CpuidKvmHyperV(vmm_sys_util::fam::Error),
|
||||
|
||||
/// Error populating CPUID with CPU identification
|
||||
#[error("Error populating CPUID with CPU identification: {0}")]
|
||||
CpuidIdentification(vmm_sys_util::fam::Error),
|
||||
|
||||
/// Error checking CPUID compatibility
|
||||
#[error("Error checking CPUID compatibility")]
|
||||
CpuidCheckCompatibility,
|
||||
|
||||
// Error writing EBDA address
|
||||
#[error("Error writing EBDA address: {0}")]
|
||||
EbdaSetup(vm_memory::GuestMemoryError),
|
||||
|
||||
// Error getting CPU TSC frequency
|
||||
#[error("Error getting CPU TSC frequency: {0}")]
|
||||
GetTscFrequency(HypervisorCpuError),
|
||||
|
||||
/// Error retrieving TDX capabilities through the hypervisor (kvm/mshv) API
|
||||
#[cfg(feature = "tdx")]
|
||||
#[error("Error retrieving TDX capabilities through the hypervisor API: {0}")]
|
||||
TdxCapabilities(HypervisorError),
|
||||
|
||||
/// Failed to configure E820 map for bzImage
|
||||
#[error("Failed to configure E820 map for bzImage")]
|
||||
E820Configuration,
|
||||
}
|
||||
|
||||
impl From<Error> for super::Error {
|
||||
@@ -215,26 +221,6 @@ impl From<Error> for super::Error {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_x2apic_id(cpu_id: u32, topology: Option<(u8, u8, u8)>) -> u32 {
|
||||
if let Some(t) = topology {
|
||||
let thread_mask_width = u8::BITS - (t.0 - 1).leading_zeros();
|
||||
let core_mask_width = u8::BITS - (t.1 - 1).leading_zeros();
|
||||
let die_mask_width = u8::BITS - (t.2 - 1).leading_zeros();
|
||||
|
||||
let thread_id = cpu_id % (t.0 as u32);
|
||||
let core_id = cpu_id / (t.0 as u32) % (t.1 as u32);
|
||||
let die_id = cpu_id / ((t.0 * t.1) as u32) % (t.2 as u32);
|
||||
let socket_id = cpu_id / ((t.0 * t.1 * t.2) as u32);
|
||||
|
||||
return thread_id
|
||||
| (core_id << thread_mask_width)
|
||||
| (die_id << (thread_mask_width + core_mask_width))
|
||||
| (socket_id << (thread_mask_width + core_mask_width + die_mask_width));
|
||||
}
|
||||
|
||||
cpu_id
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum CpuidReg {
|
||||
EAX,
|
||||
@@ -254,26 +240,6 @@ pub struct CpuidPatch {
|
||||
}
|
||||
|
||||
impl CpuidPatch {
|
||||
pub fn get_cpuid_reg(
|
||||
cpuid: &[CpuIdEntry],
|
||||
function: u32,
|
||||
index: Option<u32>,
|
||||
reg: CpuidReg,
|
||||
) -> Option<u32> {
|
||||
for entry in cpuid.iter() {
|
||||
if entry.function == function && (index.is_none() || index.unwrap() == entry.index) {
|
||||
return match reg {
|
||||
CpuidReg::EAX => Some(entry.eax),
|
||||
CpuidReg::EBX => Some(entry.ebx),
|
||||
CpuidReg::ECX => Some(entry.ecx),
|
||||
CpuidReg::EDX => Some(entry.edx),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn set_cpuid_reg(
|
||||
cpuid: &mut Vec<CpuIdEntry>,
|
||||
function: u32,
|
||||
@@ -578,7 +544,7 @@ impl CpuidFeatureEntry {
|
||||
if !entry_compatible {
|
||||
error!(
|
||||
"Detected incompatible CPUID entry: leaf={:#02x} (subleaf={:#02x}), register='{:?}', \
|
||||
compatible_check='{:?}', source VM feature='{:#04x}', destination VM feature'{:#04x}'.",
|
||||
compatilbe_check='{:?}', source VM feature='{:#04x}', destination VM feature'{:#04x}'.",
|
||||
entry.function, entry.index, entry.feature_reg,
|
||||
entry.compatible_check, src_vm_feature, dest_vm_feature
|
||||
);
|
||||
@@ -601,7 +567,7 @@ pub fn generate_common_cpuid(
|
||||
config: &CpuidConfig,
|
||||
) -> super::Result<Vec<CpuIdEntry>> {
|
||||
// SAFETY: cpuid called with valid leaves
|
||||
if unsafe { x86_64::__cpuid(1) }.ecx & (1 << HYPERVISOR_ECX_BIT) == 1 << HYPERVISOR_ECX_BIT {
|
||||
if unsafe { x86_64::__cpuid(1) }.ecx & 1 << HYPERVISOR_ECX_BIT == 1 << HYPERVISOR_ECX_BIT {
|
||||
// SAFETY: cpuid called with valid leaves
|
||||
let hypervisor_cpuid = unsafe { x86_64::__cpuid(0x4000_0000) };
|
||||
|
||||
@@ -620,8 +586,17 @@ pub fn generate_common_cpuid(
|
||||
"Generating guest CPUID for with physical address size: {}",
|
||||
config.phys_bits
|
||||
);
|
||||
#[allow(unused_mut)]
|
||||
let mut cpuid_patches = vec![
|
||||
let cpuid_patches = vec![
|
||||
// Patch tsc deadline timer bit
|
||||
CpuidPatch {
|
||||
function: 1,
|
||||
index: 0,
|
||||
flags_bit: None,
|
||||
eax_bit: None,
|
||||
ebx_bit: None,
|
||||
ecx_bit: Some(TSC_DEADLINE_TIMER_ECX_BIT),
|
||||
edx_bit: None,
|
||||
},
|
||||
// Patch hypervisor bit
|
||||
CpuidPatch {
|
||||
function: 1,
|
||||
@@ -644,23 +619,6 @@ pub fn generate_common_cpuid(
|
||||
},
|
||||
];
|
||||
|
||||
#[cfg(feature = "kvm")]
|
||||
if matches!(
|
||||
hypervisor.hypervisor_type(),
|
||||
hypervisor::HypervisorType::Kvm
|
||||
) {
|
||||
// Patch tsc deadline timer bit
|
||||
cpuid_patches.push(CpuidPatch {
|
||||
function: 1,
|
||||
index: 0,
|
||||
flags_bit: None,
|
||||
eax_bit: None,
|
||||
ebx_bit: None,
|
||||
ecx_bit: Some(TSC_DEADLINE_TIMER_ECX_BIT),
|
||||
edx_bit: None,
|
||||
});
|
||||
}
|
||||
|
||||
// Supported CPUID
|
||||
let mut cpuid = hypervisor
|
||||
.get_supported_cpuid()
|
||||
@@ -689,7 +647,7 @@ pub fn generate_common_cpuid(
|
||||
// Clear AMX related bits if the AMX feature is not enabled
|
||||
0x7 => {
|
||||
if !config.amx && entry.index == 0 {
|
||||
entry.edx &= !((1 << AMX_BF16) | (1 << AMX_TILE) | (1 << AMX_INT8))
|
||||
entry.edx &= !(1 << AMX_BF16 | 1 << AMX_TILE | 1 << AMX_INT8)
|
||||
}
|
||||
}
|
||||
0xd =>
|
||||
@@ -711,20 +669,6 @@ pub fn generate_common_cpuid(
|
||||
}
|
||||
}
|
||||
}
|
||||
// Copy host L1 cache details if not populated by KVM
|
||||
0x8000_0005 => {
|
||||
if entry.eax == 0 && entry.ebx == 0 && entry.ecx == 0 && entry.edx == 0 {
|
||||
// SAFETY: cpuid called with valid leaves
|
||||
if unsafe { std::arch::x86_64::__cpuid(0x8000_0000).eax } >= 0x8000_0005 {
|
||||
// SAFETY: cpuid called with valid leaves
|
||||
let leaf = unsafe { std::arch::x86_64::__cpuid(0x8000_0005) };
|
||||
entry.eax = leaf.eax;
|
||||
entry.ebx = leaf.ebx;
|
||||
entry.ecx = leaf.ecx;
|
||||
entry.edx = leaf.edx;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Copy host L2 cache details if not populated by KVM
|
||||
0x8000_0006 => {
|
||||
if entry.eax == 0 && entry.ebx == 0 && entry.ecx == 0 && entry.edx == 0 {
|
||||
@@ -801,10 +745,10 @@ pub fn generate_common_cpuid(
|
||||
});
|
||||
cpuid.push(CpuIdEntry {
|
||||
function: 0x4000_0003,
|
||||
eax: (1 << 1) // AccessPartitionReferenceCounter
|
||||
| (1 << 2) // AccessSynicRegs
|
||||
| (1 << 3) // AccessSyntheticTimerRegs
|
||||
| (1 << 9), // AccessPartitionReferenceTsc
|
||||
eax: 1 << 1 // AccessPartitionReferenceCounter
|
||||
| 1 << 2 // AccessSynicRegs
|
||||
| 1 << 3 // AccessSyntheticTimerRegs
|
||||
| 1 << 9, // AccessPartitionReferenceTsc
|
||||
edx: 1 << 3, // CPU dynamic partitioning
|
||||
..Default::default()
|
||||
});
|
||||
@@ -833,32 +777,31 @@ pub fn configure_vcpu(
|
||||
cpu_vendor: CpuVendor,
|
||||
topology: Option<(u8, u8, u8)>,
|
||||
) -> super::Result<()> {
|
||||
let x2apic_id = get_x2apic_id(id as u32, topology);
|
||||
|
||||
// Per vCPU CPUID changes; common are handled via generate_common_cpuid()
|
||||
let mut cpuid = cpuid;
|
||||
CpuidPatch::set_cpuid_reg(&mut cpuid, 0xb, None, CpuidReg::EDX, x2apic_id);
|
||||
CpuidPatch::set_cpuid_reg(&mut cpuid, 0x1f, None, CpuidReg::EDX, x2apic_id);
|
||||
CpuidPatch::set_cpuid_reg(&mut cpuid, 0xb, None, CpuidReg::EDX, u32::from(id));
|
||||
CpuidPatch::set_cpuid_reg(&mut cpuid, 0x1f, None, CpuidReg::EDX, u32::from(id));
|
||||
if matches!(cpu_vendor, CpuVendor::AMD) {
|
||||
CpuidPatch::set_cpuid_reg(&mut cpuid, 0x8000_001e, Some(0), CpuidReg::EAX, x2apic_id);
|
||||
CpuidPatch::set_cpuid_reg(
|
||||
&mut cpuid,
|
||||
0x8000_001e,
|
||||
Some(0),
|
||||
CpuidReg::EAX,
|
||||
u32::from(id),
|
||||
);
|
||||
}
|
||||
|
||||
// Set ApicId in cpuid for each vcpu - found in cpuid ebx when eax = 1
|
||||
let mut apic_id_patched = false;
|
||||
for entry in &mut cpuid {
|
||||
if entry.function == 1 {
|
||||
entry.ebx &= 0xffffff;
|
||||
entry.ebx |= x2apic_id << 24;
|
||||
apic_id_patched = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert!(apic_id_patched);
|
||||
|
||||
if let Some(t) = topology {
|
||||
update_cpuid_topology(&mut cpuid, t.0, t.1, t.2, cpu_vendor, id);
|
||||
}
|
||||
|
||||
// Set ApicId in cpuid for each vcpu
|
||||
// SAFETY: get host cpuid when eax=1
|
||||
let mut cpu_ebx = unsafe { core::arch::x86_64::__cpuid(1) }.ebx;
|
||||
cpu_ebx &= 0xffffff;
|
||||
cpu_ebx |= (id as u32) << 24;
|
||||
CpuidPatch::set_cpuid_reg(&mut cpuid, 0x1, None, CpuidReg::EBX, cpu_ebx);
|
||||
|
||||
// The TSC frequency CPUID leaf should not be included when running with HyperV emulation
|
||||
if !kvm_hyperv {
|
||||
if let Some(tsc_khz) = vcpu.tsc_khz().map_err(Error::GetTscFrequency)? {
|
||||
@@ -887,10 +830,6 @@ pub fn configure_vcpu(
|
||||
}
|
||||
}
|
||||
|
||||
for c in &cpuid {
|
||||
info!("{}", c);
|
||||
}
|
||||
|
||||
vcpu.set_cpuid2(&cpuid)
|
||||
.map_err(|e| Error::SetSupportedCpusFailed(e.into()))?;
|
||||
|
||||
@@ -900,7 +839,8 @@ pub fn configure_vcpu(
|
||||
|
||||
regs::setup_msrs(vcpu).map_err(Error::MsrsConfiguration)?;
|
||||
if let Some((kernel_entry_point, guest_memory)) = boot_setup {
|
||||
regs::setup_regs(vcpu, kernel_entry_point).map_err(Error::RegsConfiguration)?;
|
||||
regs::setup_regs(vcpu, kernel_entry_point.entry_addr.raw_value())
|
||||
.map_err(Error::RegsConfiguration)?;
|
||||
regs::setup_fpu(vcpu).map_err(Error::FpuConfiguration)?;
|
||||
regs::setup_sregs(&guest_memory.memory(), vcpu).map_err(Error::SregsConfiguration)?;
|
||||
}
|
||||
@@ -909,7 +849,6 @@ pub fn configure_vcpu(
|
||||
}
|
||||
|
||||
/// Returns a Vec of the valid memory addresses.
|
||||
///
|
||||
/// These should be used to configure the GuestMemory structure for the platform.
|
||||
/// For x86_64 all addresses are valid from the start of the kernel except a
|
||||
/// carve out at the end of 32bit address space.
|
||||
@@ -950,16 +889,13 @@ pub fn arch_memory_regions() -> Vec<(GuestAddress, usize, RegionType)> {
|
||||
pub fn configure_system(
|
||||
guest_mem: &GuestMemoryMmap,
|
||||
cmdline_addr: GuestAddress,
|
||||
cmdline_size: usize,
|
||||
initramfs: &Option<InitramfsConfig>,
|
||||
_num_cpus: u8,
|
||||
setup_header: Option<setup_header>,
|
||||
rsdp_addr: Option<GuestAddress>,
|
||||
sgx_epc_region: Option<SgxEpcRegion>,
|
||||
serial_number: Option<&str>,
|
||||
uuid: Option<&str>,
|
||||
oem_strings: Option<&[&str]>,
|
||||
topology: Option<(u8, u8, u8)>,
|
||||
) -> super::Result<()> {
|
||||
// Write EBDA address to location where ACPICA expects to find it
|
||||
guest_mem
|
||||
@@ -972,7 +908,7 @@ pub fn configure_system(
|
||||
// Place the MP table after the SMIOS table aligned to 16 bytes
|
||||
let offset = GuestAddress(layout::SMBIOS_START).unchecked_add(size);
|
||||
let offset = GuestAddress((offset.0 + 16) & !0xf);
|
||||
mptable::setup_mptable(offset, guest_mem, _num_cpus, topology).map_err(Error::MpTableSetup)?;
|
||||
mptable::setup_mptable(offset, guest_mem, _num_cpus).map_err(Error::MpTableSetup)?;
|
||||
|
||||
// Check that the RAM is not smaller than the RSDP start address
|
||||
if let Some(rsdp_addr) = rsdp_addr {
|
||||
@@ -981,31 +917,61 @@ pub fn configure_system(
|
||||
}
|
||||
}
|
||||
|
||||
match setup_header {
|
||||
Some(hdr) => configure_32bit_entry(
|
||||
guest_mem,
|
||||
cmdline_addr,
|
||||
cmdline_size,
|
||||
initramfs,
|
||||
hdr,
|
||||
rsdp_addr,
|
||||
sgx_epc_region,
|
||||
),
|
||||
None => configure_pvh(
|
||||
guest_mem,
|
||||
cmdline_addr,
|
||||
initramfs,
|
||||
rsdp_addr,
|
||||
sgx_epc_region,
|
||||
),
|
||||
}
|
||||
configure_pvh(
|
||||
guest_mem,
|
||||
cmdline_addr,
|
||||
initramfs,
|
||||
rsdp_addr,
|
||||
sgx_epc_region,
|
||||
)
|
||||
}
|
||||
|
||||
type RamRange = (u64, u64);
|
||||
fn configure_pvh(
|
||||
guest_mem: &GuestMemoryMmap,
|
||||
cmdline_addr: GuestAddress,
|
||||
initramfs: &Option<InitramfsConfig>,
|
||||
rsdp_addr: Option<GuestAddress>,
|
||||
sgx_epc_region: Option<SgxEpcRegion>,
|
||||
) -> super::Result<()> {
|
||||
const XEN_HVM_START_MAGIC_VALUE: u32 = 0x336ec578;
|
||||
|
||||
let mut start_info: StartInfoWrapper = StartInfoWrapper(hvm_start_info::default());
|
||||
|
||||
start_info.0.magic = XEN_HVM_START_MAGIC_VALUE;
|
||||
start_info.0.version = 1; // pvh has version 1
|
||||
start_info.0.nr_modules = 0;
|
||||
start_info.0.cmdline_paddr = cmdline_addr.raw_value();
|
||||
start_info.0.memmap_paddr = layout::MEMMAP_START.raw_value();
|
||||
|
||||
if let Some(rsdp_addr) = rsdp_addr {
|
||||
start_info.0.rsdp_paddr = rsdp_addr.0;
|
||||
}
|
||||
|
||||
if let Some(initramfs_config) = initramfs {
|
||||
// The initramfs has been written to guest memory already, here we just need to
|
||||
// create the module structure that describes it.
|
||||
let ramdisk_mod: ModlistEntryWrapper = ModlistEntryWrapper(hvm_modlist_entry {
|
||||
paddr: initramfs_config.address.raw_value(),
|
||||
size: initramfs_config.size as u64,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
start_info.0.nr_modules += 1;
|
||||
start_info.0.modlist_paddr = layout::MODLIST_START.raw_value();
|
||||
|
||||
// Write the modlist struct to guest memory.
|
||||
guest_mem
|
||||
.write_obj(ramdisk_mod, layout::MODLIST_START)
|
||||
.map_err(super::Error::ModlistSetup)?;
|
||||
}
|
||||
|
||||
// Vector to hold the memory maps which needs to be written to guest memory
|
||||
// at MEMMAP_START after all of the mappings are recorded.
|
||||
let mut memmap: Vec<hvm_memmap_table_entry> = Vec::new();
|
||||
|
||||
// Create the memory map entries.
|
||||
add_memmap_entry(&mut memmap, 0, layout::EBDA_START.raw_value(), E820_RAM);
|
||||
|
||||
/// Returns usable physical memory ranges for the guest
|
||||
/// These should be used to create e820_RAM memory maps
|
||||
pub fn generate_ram_ranges(guest_mem: &GuestMemoryMmap) -> super::Result<Vec<RamRange>> {
|
||||
// Merge continuous memory regions into one region.
|
||||
// Note: memory regions from "GuestMemory" are sorted and non-zero sized.
|
||||
let ram_regions = {
|
||||
@@ -1038,12 +1004,16 @@ pub fn generate_ram_ranges(guest_mem: &GuestMemoryMmap) -> super::Result<Vec<Ram
|
||||
ram_regions
|
||||
};
|
||||
|
||||
// Create the memory map entry for memory region before the gap
|
||||
let mut ram_ranges = vec![];
|
||||
if ram_regions.len() > 2 {
|
||||
error!(
|
||||
"There should be up to two non-continuous regions, devidided by the
|
||||
gap at the end of 32bit address space (e.g. between 3G and 4G)."
|
||||
);
|
||||
return Err(super::Error::MemmapTableSetup);
|
||||
}
|
||||
|
||||
// Generate the first usable physical memory range before the gap. The e820 map
|
||||
// should only report memory above 1MiB.
|
||||
let first_ram_range = {
|
||||
// Create the memory map entry for memory region before the gap
|
||||
{
|
||||
let (first_region_start, first_region_end) =
|
||||
ram_regions.first().ok_or(super::Error::MemmapTableSetup)?;
|
||||
let high_ram_start = layout::HIGH_RAM_START.raw_value();
|
||||
@@ -1063,87 +1033,39 @@ pub fn generate_ram_ranges(guest_mem: &GuestMemoryMmap) -> super::Result<Vec<Ram
|
||||
}
|
||||
|
||||
info!(
|
||||
"first usable physical memory range, start: 0x{:08x}, end: 0x{:08x}",
|
||||
"create_memmap_entry, start: 0x{:08x}, end: 0x{:08x}",
|
||||
high_ram_start, first_region_end
|
||||
);
|
||||
|
||||
(high_ram_start, *first_region_end)
|
||||
};
|
||||
ram_ranges.push(first_ram_range);
|
||||
|
||||
// Generate additional usable physical memory range after the gap if any.
|
||||
for ram_region in ram_regions.iter().skip(1) {
|
||||
info!(
|
||||
"found usable physical memory range, start: 0x{:08x}, end: 0x{:08x}",
|
||||
ram_region.0, ram_region.1
|
||||
add_memmap_entry(
|
||||
&mut memmap,
|
||||
high_ram_start,
|
||||
first_region_end - high_ram_start,
|
||||
E820_RAM,
|
||||
);
|
||||
|
||||
ram_ranges.push(*ram_region);
|
||||
}
|
||||
|
||||
Ok(ram_ranges)
|
||||
}
|
||||
// Create the memory map entry for memory region after the gap if any
|
||||
if let Some((second_region_start, second_region_end)) = ram_regions.get(1) {
|
||||
let ram_64bit_start = layout::RAM_64BIT_START.raw_value();
|
||||
|
||||
fn configure_pvh(
|
||||
guest_mem: &GuestMemoryMmap,
|
||||
cmdline_addr: GuestAddress,
|
||||
initramfs: &Option<InitramfsConfig>,
|
||||
rsdp_addr: Option<GuestAddress>,
|
||||
sgx_epc_region: Option<SgxEpcRegion>,
|
||||
) -> super::Result<()> {
|
||||
const XEN_HVM_START_MAGIC_VALUE: u32 = 0x336ec578;
|
||||
if second_region_start != &ram_64bit_start {
|
||||
error!(
|
||||
"Unexpected second memory region layout: start: 0x{:08x}, ram_64bit_start: 0x{:08x}",
|
||||
second_region_start, ram_64bit_start
|
||||
);
|
||||
|
||||
let mut start_info = hvm_start_info {
|
||||
magic: XEN_HVM_START_MAGIC_VALUE,
|
||||
version: 1, // pvh has version 1
|
||||
nr_modules: 0,
|
||||
cmdline_paddr: cmdline_addr.raw_value(),
|
||||
memmap_paddr: layout::MEMMAP_START.raw_value(),
|
||||
..Default::default()
|
||||
};
|
||||
return Err(super::Error::MemmapTableSetup);
|
||||
}
|
||||
|
||||
if let Some(rsdp_addr) = rsdp_addr {
|
||||
start_info.rsdp_paddr = rsdp_addr.0;
|
||||
}
|
||||
|
||||
if let Some(initramfs_config) = initramfs {
|
||||
// The initramfs has been written to guest memory already, here we just need to
|
||||
// create the module structure that describes it.
|
||||
let ramdisk_mod = hvm_modlist_entry {
|
||||
paddr: initramfs_config.address.raw_value(),
|
||||
size: initramfs_config.size as u64,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
start_info.nr_modules += 1;
|
||||
start_info.modlist_paddr = layout::MODLIST_START.raw_value();
|
||||
|
||||
// Write the modlist struct to guest memory.
|
||||
guest_mem
|
||||
.write_obj(ramdisk_mod, layout::MODLIST_START)
|
||||
.map_err(super::Error::ModlistSetup)?;
|
||||
}
|
||||
|
||||
// Vector to hold the memory maps which needs to be written to guest memory
|
||||
// at MEMMAP_START after all of the mappings are recorded.
|
||||
let mut memmap: Vec<hvm_memmap_table_entry> = Vec::new();
|
||||
|
||||
// Create the memory map entries.
|
||||
add_memmap_entry(&mut memmap, 0, layout::EBDA_START.raw_value(), E820_RAM);
|
||||
|
||||
// Get usable physical memory ranges
|
||||
let ram_ranges = generate_ram_ranges(guest_mem)?;
|
||||
|
||||
// Create e820 memory map entries
|
||||
for ram_range in ram_ranges {
|
||||
info!(
|
||||
"create_memmap_entry, start: 0x{:08x}, end: 0x{:08x}",
|
||||
ram_range.0, ram_range.1
|
||||
ram_64bit_start, second_region_end
|
||||
);
|
||||
add_memmap_entry(
|
||||
&mut memmap,
|
||||
ram_range.0,
|
||||
ram_range.1 - ram_range.0,
|
||||
ram_64bit_start,
|
||||
second_region_end - ram_64bit_start,
|
||||
E820_RAM,
|
||||
);
|
||||
}
|
||||
@@ -1164,7 +1086,7 @@ fn configure_pvh(
|
||||
);
|
||||
}
|
||||
|
||||
start_info.memmap_entries = memmap.len() as u32;
|
||||
start_info.0.memmap_entries = memmap.len() as u32;
|
||||
|
||||
// Copy the vector with the memmap table to the MEMMAP_START address
|
||||
// which is already saved in the memmap_paddr field of hvm_start_info struct.
|
||||
@@ -1173,14 +1095,17 @@ fn configure_pvh(
|
||||
guest_mem
|
||||
.checked_offset(
|
||||
memmap_start_addr,
|
||||
mem::size_of::<hvm_memmap_table_entry>() * start_info.memmap_entries as usize,
|
||||
mem::size_of::<hvm_memmap_table_entry>() * start_info.0.memmap_entries as usize,
|
||||
)
|
||||
.ok_or(super::Error::MemmapTablePastRamEnd)?;
|
||||
|
||||
// For every entry in the memmap vector, write it to guest memory.
|
||||
// For every entry in the memmap vector, create a MemmapTableEntryWrapper
|
||||
// and write it to guest memory.
|
||||
for memmap_entry in memmap {
|
||||
let map_entry_wrapper: MemmapTableEntryWrapper = MemmapTableEntryWrapper(memmap_entry);
|
||||
|
||||
guest_mem
|
||||
.write_obj(memmap_entry, memmap_start_addr)
|
||||
.write_obj(map_entry_wrapper, memmap_start_addr)
|
||||
.map_err(|_| super::Error::MemmapTableSetup)?;
|
||||
memmap_start_addr =
|
||||
memmap_start_addr.unchecked_add(mem::size_of::<hvm_memmap_table_entry>() as u64);
|
||||
@@ -1203,113 +1128,6 @@ fn configure_pvh(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn configure_32bit_entry(
|
||||
guest_mem: &GuestMemoryMmap,
|
||||
cmdline_addr: GuestAddress,
|
||||
cmdline_size: usize,
|
||||
initramfs: &Option<InitramfsConfig>,
|
||||
setup_hdr: setup_header,
|
||||
rsdp_addr: Option<GuestAddress>,
|
||||
sgx_epc_region: Option<SgxEpcRegion>,
|
||||
) -> super::Result<()> {
|
||||
const KERNEL_LOADER_OTHER: u8 = 0xff;
|
||||
|
||||
// Use the provided setup header
|
||||
let mut params = boot_params {
|
||||
hdr: setup_hdr,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
// Common bootparams settings
|
||||
if params.hdr.type_of_loader == 0 {
|
||||
params.hdr.type_of_loader = KERNEL_LOADER_OTHER;
|
||||
}
|
||||
params.hdr.cmd_line_ptr = cmdline_addr.raw_value() as u32;
|
||||
params.hdr.cmdline_size = cmdline_size as u32;
|
||||
|
||||
if let Some(initramfs_config) = initramfs {
|
||||
params.hdr.ramdisk_image = initramfs_config.address.raw_value() as u32;
|
||||
params.hdr.ramdisk_size = initramfs_config.size as u32;
|
||||
}
|
||||
|
||||
add_e820_entry(&mut params, 0, layout::EBDA_START.raw_value(), E820_RAM)?;
|
||||
|
||||
let mem_end = guest_mem.last_addr();
|
||||
if mem_end < layout::MEM_32BIT_RESERVED_START {
|
||||
add_e820_entry(
|
||||
&mut params,
|
||||
layout::HIGH_RAM_START.raw_value(),
|
||||
mem_end.unchecked_offset_from(layout::HIGH_RAM_START) + 1,
|
||||
E820_RAM,
|
||||
)?;
|
||||
} else {
|
||||
add_e820_entry(
|
||||
&mut params,
|
||||
layout::HIGH_RAM_START.raw_value(),
|
||||
layout::MEM_32BIT_RESERVED_START.unchecked_offset_from(layout::HIGH_RAM_START),
|
||||
E820_RAM,
|
||||
)?;
|
||||
if mem_end > layout::RAM_64BIT_START {
|
||||
add_e820_entry(
|
||||
&mut params,
|
||||
layout::RAM_64BIT_START.raw_value(),
|
||||
mem_end.unchecked_offset_from(layout::RAM_64BIT_START) + 1,
|
||||
E820_RAM,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
add_e820_entry(
|
||||
&mut params,
|
||||
layout::PCI_MMCONFIG_START.0,
|
||||
layout::PCI_MMCONFIG_SIZE,
|
||||
E820_RESERVED,
|
||||
)?;
|
||||
|
||||
if let Some(sgx_epc_region) = sgx_epc_region {
|
||||
add_e820_entry(
|
||||
&mut params,
|
||||
sgx_epc_region.start().raw_value(),
|
||||
sgx_epc_region.size(),
|
||||
E820_RESERVED,
|
||||
)?;
|
||||
}
|
||||
|
||||
if let Some(rsdp_addr) = rsdp_addr {
|
||||
params.acpi_rsdp_addr = rsdp_addr.0;
|
||||
}
|
||||
|
||||
let zero_page_addr = layout::ZERO_PAGE_START;
|
||||
guest_mem
|
||||
.checked_offset(zero_page_addr, mem::size_of::<boot_params>())
|
||||
.ok_or(super::Error::ZeroPagePastRamEnd)?;
|
||||
guest_mem
|
||||
.write_obj(params, zero_page_addr)
|
||||
.map_err(super::Error::ZeroPageSetup)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Add an e820 region to the e820 map.
|
||||
/// Returns Ok(()) if successful, or an error if there is no space left in the map.
|
||||
fn add_e820_entry(
|
||||
params: &mut boot_params,
|
||||
addr: u64,
|
||||
size: u64,
|
||||
mem_type: u32,
|
||||
) -> Result<(), Error> {
|
||||
if params.e820_entries >= params.e820_table.len() as u8 {
|
||||
return Err(Error::E820Configuration);
|
||||
}
|
||||
|
||||
params.e820_table[params.e820_entries as usize].addr = addr;
|
||||
params.e820_table[params.e820_entries as usize].size = size;
|
||||
params.e820_table[params.e820_entries as usize].type_ = mem_type;
|
||||
params.e820_entries += 1;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn add_memmap_entry(memmap: &mut Vec<hvm_memmap_table_entry>, addr: u64, size: u64, mem_type: u32) {
|
||||
// Add the table entry to the vector
|
||||
memmap.push(hvm_memmap_table_entry {
|
||||
@@ -1373,24 +1191,10 @@ fn update_cpuid_topology(
|
||||
cpu_vendor: CpuVendor,
|
||||
id: u8,
|
||||
) {
|
||||
let x2apic_id = get_x2apic_id(
|
||||
id as u32,
|
||||
Some((threads_per_core, cores_per_die, dies_per_package)),
|
||||
);
|
||||
|
||||
let thread_width = 8 - (threads_per_core - 1).leading_zeros();
|
||||
let core_width = (8 - (cores_per_die - 1).leading_zeros()) + thread_width;
|
||||
let die_width = (8 - (dies_per_package - 1).leading_zeros()) + core_width;
|
||||
|
||||
let mut cpu_ebx = CpuidPatch::get_cpuid_reg(cpuid, 0x1, None, CpuidReg::EBX).unwrap_or(0);
|
||||
cpu_ebx |= ((dies_per_package as u32) * (cores_per_die as u32) * (threads_per_core as u32))
|
||||
& (0xff << 16);
|
||||
CpuidPatch::set_cpuid_reg(cpuid, 0x1, None, CpuidReg::EBX, cpu_ebx);
|
||||
|
||||
let mut cpu_edx = CpuidPatch::get_cpuid_reg(cpuid, 0x1, None, CpuidReg::EDX).unwrap_or(0);
|
||||
cpu_edx |= 1 << 28;
|
||||
CpuidPatch::set_cpuid_reg(cpuid, 0x1, None, CpuidReg::EDX, cpu_edx);
|
||||
|
||||
// CPU Topology leaf 0xb
|
||||
CpuidPatch::set_cpuid_reg(cpuid, 0xb, Some(0), CpuidReg::EAX, thread_width);
|
||||
CpuidPatch::set_cpuid_reg(
|
||||
@@ -1449,7 +1253,7 @@ fn update_cpuid_topology(
|
||||
0x8000_001e,
|
||||
Some(0),
|
||||
CpuidReg::EBX,
|
||||
((threads_per_core as u32 - 1) << 8) | (x2apic_id & 0xff),
|
||||
((threads_per_core as u32 - 1) << 8) | (id as u32 & 0xff),
|
||||
);
|
||||
CpuidPatch::set_cpuid_reg(
|
||||
cpuid,
|
||||
@@ -1460,21 +1264,21 @@ fn update_cpuid_topology(
|
||||
);
|
||||
CpuidPatch::set_cpuid_reg(cpuid, 0x8000_001e, Some(0), CpuidReg::EDX, 0);
|
||||
if cores_per_die * threads_per_core > 1 {
|
||||
let ecx =
|
||||
CpuidPatch::get_cpuid_reg(cpuid, 0x8000_0001, Some(0), CpuidReg::ECX).unwrap_or(0);
|
||||
CpuidPatch::set_cpuid_reg(
|
||||
cpuid,
|
||||
0x8000_0001,
|
||||
Some(0),
|
||||
CpuidReg::ECX,
|
||||
ecx | (1u32 << 1) | (1u32 << 22),
|
||||
(1u32 << 1) | (1u32 << 22),
|
||||
);
|
||||
CpuidPatch::set_cpuid_reg(
|
||||
cpuid,
|
||||
0x0000_0001,
|
||||
Some(0),
|
||||
CpuidReg::EBX,
|
||||
(x2apic_id << 24) | (8 << 8) | (((cores_per_die * threads_per_core) as u32) << 16),
|
||||
((id as u32) << 24)
|
||||
| (8 << 8)
|
||||
| (((cores_per_die * threads_per_core) as u32) << 16),
|
||||
);
|
||||
let cpuid_patches = vec![
|
||||
// Patch tsc deadline timer bit
|
||||
@@ -1555,8 +1359,6 @@ fn update_cpuid_sgx(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use linux_loader::loader::bootparam::boot_e820_entry;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
@@ -1574,18 +1376,15 @@ mod tests {
|
||||
let config_err = configure_system(
|
||||
&gm,
|
||||
GuestAddress(0),
|
||||
0,
|
||||
&None,
|
||||
1,
|
||||
None,
|
||||
Some(layout::RSDP_POINTER),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
config_err.unwrap_err();
|
||||
assert!(config_err.is_err());
|
||||
|
||||
// Now assigning some memory that falls before the 32bit memory hole.
|
||||
let arch_mem_regions = arch_memory_regions();
|
||||
@@ -1599,7 +1398,6 @@ mod tests {
|
||||
configure_system(
|
||||
&gm,
|
||||
GuestAddress(0),
|
||||
0,
|
||||
&None,
|
||||
no_vcpus,
|
||||
None,
|
||||
@@ -1607,8 +1405,6 @@ mod tests {
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -1629,7 +1425,6 @@ mod tests {
|
||||
configure_system(
|
||||
&gm,
|
||||
GuestAddress(0),
|
||||
0,
|
||||
&None,
|
||||
no_vcpus,
|
||||
None,
|
||||
@@ -1637,15 +1432,12 @@ mod tests {
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
configure_system(
|
||||
&gm,
|
||||
GuestAddress(0),
|
||||
0,
|
||||
&None,
|
||||
no_vcpus,
|
||||
None,
|
||||
@@ -1653,52 +1445,10 @@ mod tests {
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_e820_entry() {
|
||||
let e820_table = [(boot_e820_entry {
|
||||
addr: 0x1,
|
||||
size: 4,
|
||||
type_: 1,
|
||||
}); 128];
|
||||
|
||||
let expected_params = boot_params {
|
||||
e820_table,
|
||||
e820_entries: 1,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let mut params: boot_params = Default::default();
|
||||
add_e820_entry(
|
||||
&mut params,
|
||||
e820_table[0].addr,
|
||||
e820_table[0].size,
|
||||
e820_table[0].type_,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
format!("{:?}", params.e820_table[0]),
|
||||
format!("{:?}", expected_params.e820_table[0])
|
||||
);
|
||||
assert_eq!(params.e820_entries, expected_params.e820_entries);
|
||||
|
||||
// Exercise the scenario where the field storing the length of the e820 entry table is
|
||||
// is bigger than the allocated memory.
|
||||
params.e820_entries = params.e820_table.len() as u8 + 1;
|
||||
add_e820_entry(
|
||||
&mut params,
|
||||
e820_table[0].addr,
|
||||
e820_table[0].size,
|
||||
e820_table[0].type_,
|
||||
)
|
||||
.unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_memmap_entry() {
|
||||
let mut memmap: Vec<hvm_memmap_table_entry> = Vec::new();
|
||||
@@ -1723,25 +1473,4 @@ mod tests {
|
||||
|
||||
assert_eq!(format!("{memmap:?}"), format!("{expected_memmap:?}"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_x2apic_id() {
|
||||
let x2apic_id = get_x2apic_id(0, Some((2, 3, 1)));
|
||||
assert_eq!(x2apic_id, 0);
|
||||
|
||||
let x2apic_id = get_x2apic_id(1, Some((2, 3, 1)));
|
||||
assert_eq!(x2apic_id, 1);
|
||||
|
||||
let x2apic_id = get_x2apic_id(2, Some((2, 3, 1)));
|
||||
assert_eq!(x2apic_id, 2);
|
||||
|
||||
let x2apic_id = get_x2apic_id(6, Some((2, 3, 1)));
|
||||
assert_eq!(x2apic_id, 8);
|
||||
|
||||
let x2apic_id = get_x2apic_id(7, Some((2, 3, 1)));
|
||||
assert_eq!(x2apic_id, 9);
|
||||
|
||||
let x2apic_id = get_x2apic_id(8, Some((2, 3, 1)));
|
||||
assert_eq!(x2apic_id, 10);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
// Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
|
||||
@@ -17,7 +15,7 @@ pub const MP_IRQDIR_DEFAULT: ::std::os::raw::c_uint = 0;
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone)]
|
||||
pub struct mpf_intel {
|
||||
pub signature: [::std::os::raw::c_uchar; 4usize],
|
||||
pub signature: [::std::os::raw::c_char; 4usize],
|
||||
pub physptr: ::std::os::raw::c_uint,
|
||||
pub length: ::std::os::raw::c_uchar,
|
||||
pub specification: ::std::os::raw::c_uchar,
|
||||
@@ -32,12 +30,12 @@ pub struct mpf_intel {
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone)]
|
||||
pub struct mpc_table {
|
||||
pub signature: [::std::os::raw::c_uchar; 4usize],
|
||||
pub signature: [::std::os::raw::c_char; 4usize],
|
||||
pub length: ::std::os::raw::c_ushort,
|
||||
pub spec: ::std::os::raw::c_uchar,
|
||||
pub checksum: ::std::os::raw::c_uchar,
|
||||
pub oem: [::std::os::raw::c_uchar; 8usize],
|
||||
pub productid: [::std::os::raw::c_uchar; 12usize],
|
||||
pub spec: ::std::os::raw::c_char,
|
||||
pub checksum: ::std::os::raw::c_char,
|
||||
pub oem: [::std::os::raw::c_char; 8usize],
|
||||
pub productid: [::std::os::raw::c_char; 12usize],
|
||||
pub oemptr: ::std::os::raw::c_uint,
|
||||
pub oemsize: ::std::os::raw::c_ushort,
|
||||
pub oemcount: ::std::os::raw::c_ushort,
|
||||
@@ -106,9 +104,9 @@ pub struct mpc_lintsrc {
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone)]
|
||||
pub struct mpc_oemtable {
|
||||
pub signature: [::std::os::raw::c_uchar; 4usize],
|
||||
pub signature: [::std::os::raw::c_char; 4usize],
|
||||
pub length: ::std::os::raw::c_ushort,
|
||||
pub rev: ::std::os::raw::c_uchar,
|
||||
pub checksum: ::std::os::raw::c_uchar,
|
||||
pub mpc: [::std::os::raw::c_uchar; 8usize],
|
||||
pub rev: ::std::os::raw::c_char,
|
||||
pub checksum: ::std::os::raw::c_char,
|
||||
pub mpc: [::std::os::raw::c_char; 8usize],
|
||||
}
|
||||
|
||||
@@ -5,15 +5,15 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
|
||||
use std::{mem, result, slice};
|
||||
|
||||
use libc::c_uchar;
|
||||
use thiserror::Error;
|
||||
use vm_memory::{Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryError};
|
||||
|
||||
use crate::layout::{APIC_START, HIGH_RAM_START, IOAPIC_START};
|
||||
use crate::x86_64::{get_x2apic_id, mpspec};
|
||||
use crate::x86_64::mpspec;
|
||||
use crate::GuestMemoryMmap;
|
||||
use libc::c_char;
|
||||
use std::io;
|
||||
use std::mem;
|
||||
use std::result;
|
||||
use std::slice;
|
||||
use vm_memory::{Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryError};
|
||||
|
||||
// This is a workaround to the Rust enforcement specifying that any implementation of a foreign
|
||||
// trait (in this case `ByteValued`) where:
|
||||
@@ -50,40 +50,29 @@ unsafe impl ByteValued for MpcLintsrcWrapper {}
|
||||
// SAFETY: see above
|
||||
unsafe impl ByteValued for MpfIntelWrapper {}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// There was too little guest memory to store the entire MP table.
|
||||
#[error("There was too little guest memory to store the entire MP table")]
|
||||
NotEnoughMemory,
|
||||
/// The MP table has too little address space to be stored.
|
||||
#[error("The MP table has too little address space to be stored")]
|
||||
AddressOverflow,
|
||||
/// Failure while zeroing out the memory for the MP table.
|
||||
#[error("Failure while zeroing out the memory for the MP table: {0}")]
|
||||
Clear(GuestMemoryError),
|
||||
/// Number of CPUs exceeds the maximum supported CPUs
|
||||
#[error("Number of CPUs exceeds the maximum supported CPUs")]
|
||||
TooManyCpus,
|
||||
/// Failure to write the MP floating pointer.
|
||||
#[error("Failure to write the MP floating pointer: {0}")]
|
||||
WriteMpfIntel(GuestMemoryError),
|
||||
/// Failure to write MP CPU entry.
|
||||
#[error("Failure to write MP CPU entry: {0}")]
|
||||
WriteMpcCpu(GuestMemoryError),
|
||||
/// Failure to write MP ioapic entry.
|
||||
#[error("Failure to write MP ioapic entry: {0}")]
|
||||
WriteMpcIoapic(GuestMemoryError),
|
||||
/// Failure to write MP bus entry.
|
||||
#[error("Failure to write MP bus entry: {0}")]
|
||||
WriteMpcBus(GuestMemoryError),
|
||||
/// Failure to write MP interrupt source entry.
|
||||
#[error("Failure to write MP interrupt source entry: {0}")]
|
||||
WriteMpcIntsrc(GuestMemoryError),
|
||||
/// Failure to write MP local interrupt source entry.
|
||||
#[error("Failure to write MP local interrupt source entry: {0}")]
|
||||
WriteMpcLintsrc(GuestMemoryError),
|
||||
/// Failure to write MP table header.
|
||||
#[error("Failure to write MP table header: {0}")]
|
||||
WriteMpcTable(GuestMemoryError),
|
||||
}
|
||||
|
||||
@@ -94,13 +83,18 @@ pub type Result<T> = result::Result<T, Error>;
|
||||
// a large number for FC usecases.
|
||||
pub const MAX_SUPPORTED_CPUS: u32 = 254;
|
||||
|
||||
// Convenience macro for making arrays of diverse character types.
|
||||
macro_rules! char_array {
|
||||
($t:ty; $( $c:expr ),*) => ( [ $( $c as $t ),* ] )
|
||||
}
|
||||
|
||||
// Most of these variables are sourced from the Intel MP Spec 1.4.
|
||||
const SMP_MAGIC_IDENT: &[c_uchar; 4] = b"_MP_";
|
||||
const MPC_SIGNATURE: &[c_uchar; 4] = b"PCMP";
|
||||
const MPC_SPEC: u8 = 4;
|
||||
const MPC_OEM: &[c_uchar; 8] = b"FC ";
|
||||
const MPC_PRODUCT_ID: &[c_uchar; 12] = &[b'0'; 12];
|
||||
const BUS_TYPE_ISA: &[c_uchar; 6] = b"ISA ";
|
||||
const SMP_MAGIC_IDENT: [c_char; 4] = char_array!(c_char; '_', 'M', 'P', '_');
|
||||
const MPC_SIGNATURE: [c_char; 4] = char_array!(c_char; 'P', 'C', 'M', 'P');
|
||||
const MPC_SPEC: i8 = 4;
|
||||
const MPC_OEM: [c_char; 8] = char_array!(c_char; 'F', 'C', ' ', ' ', ' ', ' ', ' ', ' ');
|
||||
const MPC_PRODUCT_ID: [c_char; 12] = ['0' as c_char; 12];
|
||||
const BUS_TYPE_ISA: [u8; 6] = char_array!(u8; 'I', 'S', 'A', ' ', ' ', ' ');
|
||||
const APIC_VERSION: u8 = 0x14;
|
||||
const CPU_STEPPING: u32 = 0x600;
|
||||
const CPU_FEATURE_APIC: u32 = 0x200;
|
||||
@@ -132,18 +126,9 @@ fn compute_mp_size(num_cpus: u8) -> usize {
|
||||
}
|
||||
|
||||
/// Performs setup of the MP table for the given `num_cpus`.
|
||||
pub fn setup_mptable(
|
||||
offset: GuestAddress,
|
||||
mem: &GuestMemoryMmap,
|
||||
num_cpus: u8,
|
||||
topology: Option<(u8, u8, u8)>,
|
||||
) -> Result<()> {
|
||||
if num_cpus > 0 {
|
||||
let cpu_id_max = num_cpus - 1;
|
||||
let x2apic_id_max = get_x2apic_id(cpu_id_max.into(), topology);
|
||||
if x2apic_id_max >= MAX_SUPPORTED_CPUS {
|
||||
return Err(Error::TooManyCpus);
|
||||
}
|
||||
pub fn setup_mptable(offset: GuestAddress, mem: &GuestMemoryMmap, num_cpus: u8) -> Result<()> {
|
||||
if num_cpus as u32 > MAX_SUPPORTED_CPUS {
|
||||
return Err(Error::TooManyCpus);
|
||||
}
|
||||
|
||||
// Used to keep track of the next base pointer into the MP table.
|
||||
@@ -157,7 +142,7 @@ pub fn setup_mptable(
|
||||
}
|
||||
|
||||
let mut checksum: u8 = 0;
|
||||
let ioapicid: u8 = MAX_SUPPORTED_CPUS as u8 + 1;
|
||||
let ioapicid: u8 = num_cpus + 1;
|
||||
|
||||
// The checked_add here ensures the all of the following base_mp.unchecked_add's will be without
|
||||
// overflow.
|
||||
@@ -169,13 +154,13 @@ pub fn setup_mptable(
|
||||
return Err(Error::AddressOverflow);
|
||||
}
|
||||
|
||||
mem.read_exact_volatile_from(base_mp, &mut vec![0; mp_size].as_slice(), mp_size)
|
||||
mem.read_exact_from(base_mp, &mut io::repeat(0), mp_size)
|
||||
.map_err(Error::Clear)?;
|
||||
|
||||
{
|
||||
let mut mpf_intel = MpfIntelWrapper(mpspec::mpf_intel::default());
|
||||
let size = mem::size_of::<MpfIntelWrapper>() as u64;
|
||||
mpf_intel.0.signature = *SMP_MAGIC_IDENT;
|
||||
mpf_intel.0.signature = SMP_MAGIC_IDENT;
|
||||
mpf_intel.0.length = 1;
|
||||
mpf_intel.0.specification = 4;
|
||||
mpf_intel.0.physptr = (base_mp.raw_value() + size) as u32;
|
||||
@@ -195,7 +180,7 @@ pub fn setup_mptable(
|
||||
for cpu_id in 0..num_cpus {
|
||||
let mut mpc_cpu = MpcCpuWrapper(mpspec::mpc_cpu::default());
|
||||
mpc_cpu.0.type_ = mpspec::MP_PROCESSOR as u8;
|
||||
mpc_cpu.0.apicid = get_x2apic_id(cpu_id as u32, topology) as u8;
|
||||
mpc_cpu.0.apicid = cpu_id;
|
||||
mpc_cpu.0.apicver = APIC_VERSION;
|
||||
mpc_cpu.0.cpuflag = mpspec::CPU_ENABLED as u8
|
||||
| if cpu_id == 0 {
|
||||
@@ -216,7 +201,7 @@ pub fn setup_mptable(
|
||||
let mut mpc_bus = MpcBusWrapper(mpspec::mpc_bus::default());
|
||||
mpc_bus.0.type_ = mpspec::MP_BUS as u8;
|
||||
mpc_bus.0.busid = 0;
|
||||
mpc_bus.0.bustype = *BUS_TYPE_ISA;
|
||||
mpc_bus.0.bustype = BUS_TYPE_ISA;
|
||||
mem.write_obj(mpc_bus, base_mp)
|
||||
.map_err(Error::WriteMpcBus)?;
|
||||
base_mp = base_mp.unchecked_add(size as u64);
|
||||
@@ -287,14 +272,14 @@ pub fn setup_mptable(
|
||||
|
||||
{
|
||||
let mut mpc_table = MpcTableWrapper(mpspec::mpc_table::default());
|
||||
mpc_table.0.signature = *MPC_SIGNATURE;
|
||||
mpc_table.0.signature = MPC_SIGNATURE;
|
||||
mpc_table.0.length = table_end.unchecked_offset_from(table_base) as u16;
|
||||
mpc_table.0.spec = MPC_SPEC;
|
||||
mpc_table.0.oem = *MPC_OEM;
|
||||
mpc_table.0.productid = *MPC_PRODUCT_ID;
|
||||
mpc_table.0.oem = MPC_OEM;
|
||||
mpc_table.0.productid = MPC_PRODUCT_ID;
|
||||
mpc_table.0.lapic = APIC_START.0 as u32;
|
||||
checksum = checksum.wrapping_add(compute_checksum(&mpc_table.0));
|
||||
mpc_table.0.checksum = (!checksum).wrapping_add(1);
|
||||
mpc_table.0.checksum = (!checksum).wrapping_add(1) as i8;
|
||||
mem.write_obj(mpc_table, table_base)
|
||||
.map_err(Error::WriteMpcTable)?;
|
||||
}
|
||||
@@ -304,11 +289,9 @@ pub fn setup_mptable(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use vm_memory::bitmap::BitmapSlice;
|
||||
use vm_memory::{GuestUsize, VolatileMemoryError, VolatileSlice, WriteVolatile};
|
||||
|
||||
use super::*;
|
||||
use crate::layout::MPTABLE_START;
|
||||
use vm_memory::{GuestAddress, GuestUsize};
|
||||
|
||||
fn table_entry_size(type_: u8) -> usize {
|
||||
match type_ as u32 {
|
||||
@@ -327,7 +310,7 @@ mod tests {
|
||||
let mem =
|
||||
GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(num_cpus))]).unwrap();
|
||||
|
||||
setup_mptable(MPTABLE_START, &mem, num_cpus, None).unwrap();
|
||||
setup_mptable(MPTABLE_START, &mem, num_cpus).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -336,7 +319,7 @@ mod tests {
|
||||
let mem = GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(num_cpus) - 1)])
|
||||
.unwrap();
|
||||
|
||||
setup_mptable(MPTABLE_START, &mem, num_cpus, None).unwrap_err();
|
||||
assert!(setup_mptable(MPTABLE_START, &mem, num_cpus).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -345,7 +328,7 @@ mod tests {
|
||||
let mem =
|
||||
GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(num_cpus))]).unwrap();
|
||||
|
||||
setup_mptable(MPTABLE_START, &mem, num_cpus, None).unwrap();
|
||||
setup_mptable(MPTABLE_START, &mem, num_cpus).unwrap();
|
||||
|
||||
let mpf_intel: MpfIntelWrapper = mem.read_obj(MPTABLE_START).unwrap();
|
||||
|
||||
@@ -361,31 +344,27 @@ mod tests {
|
||||
let mem =
|
||||
GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(num_cpus))]).unwrap();
|
||||
|
||||
setup_mptable(MPTABLE_START, &mem, num_cpus, None).unwrap();
|
||||
setup_mptable(MPTABLE_START, &mem, num_cpus).unwrap();
|
||||
|
||||
let mpf_intel: MpfIntelWrapper = mem.read_obj(MPTABLE_START).unwrap();
|
||||
let mpc_offset = GuestAddress(mpf_intel.0.physptr as GuestUsize);
|
||||
let mpc_table: MpcTableWrapper = mem.read_obj(mpc_offset).unwrap();
|
||||
|
||||
struct Sum(u8);
|
||||
impl WriteVolatile for Sum {
|
||||
fn write_volatile<B: BitmapSlice>(
|
||||
&mut self,
|
||||
buf: &VolatileSlice<B>,
|
||||
) -> result::Result<usize, VolatileMemoryError> {
|
||||
let mut tmp = vec![0u8; buf.len()];
|
||||
tmp.write_all_volatile(buf)?;
|
||||
|
||||
for v in tmp.iter() {
|
||||
impl io::Write for Sum {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
for v in buf.iter() {
|
||||
self.0 = self.0.wrapping_add(*v);
|
||||
}
|
||||
|
||||
Ok(buf.len())
|
||||
}
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
let mut sum = Sum(0);
|
||||
mem.write_volatile_to(mpc_offset, &mut sum, mpc_table.0.length as usize)
|
||||
mem.write_to(mpc_offset, &mut sum, mpc_table.0.length as usize)
|
||||
.unwrap();
|
||||
assert_eq!(sum.0, 0);
|
||||
}
|
||||
@@ -399,7 +378,7 @@ mod tests {
|
||||
.unwrap();
|
||||
|
||||
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 mpc_offset = GuestAddress(mpf_intel.0.physptr as GuestUsize);
|
||||
@@ -432,7 +411,7 @@ mod tests {
|
||||
let mem =
|
||||
GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(cpus as u8))]).unwrap();
|
||||
|
||||
let result = setup_mptable(MPTABLE_START, &mem, cpus as u8, None);
|
||||
result.unwrap_err();
|
||||
let result = setup_mptable(MPTABLE_START, &mem, cpus as u8);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,57 +6,40 @@
|
||||
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
use std::sync::Arc;
|
||||
use std::{mem, result};
|
||||
|
||||
use crate::layout::{BOOT_GDT_START, BOOT_IDT_START, PVH_INFO_START};
|
||||
use crate::GuestMemoryMmap;
|
||||
use hypervisor::arch::x86::gdt::{gdt_entry, segment_from_gdt};
|
||||
use hypervisor::arch::x86::regs::CR0_PE;
|
||||
use hypervisor::arch::x86::{FpuState, SpecialRegisters};
|
||||
use thiserror::Error;
|
||||
use hypervisor::arch::x86::{FpuState, SpecialRegisters, StandardRegisters};
|
||||
use std::sync::Arc;
|
||||
use std::{mem, result};
|
||||
use vm_memory::{Address, Bytes, GuestMemory, GuestMemoryError};
|
||||
|
||||
use crate::layout::{
|
||||
BOOT_GDT_START, BOOT_IDT_START, BOOT_STACK_POINTER, PVH_INFO_START, ZERO_PAGE_START,
|
||||
};
|
||||
use crate::{EntryPoint, GuestMemoryMmap};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Failed to get SREGs for this CPU.
|
||||
#[error("Failed to get SREGs for this CPU: {0}")]
|
||||
GetStatusRegisters(hypervisor::HypervisorCpuError),
|
||||
/// Failed to set base registers for this CPU.
|
||||
#[error("Failed to set base registers for this CPU: {0}")]
|
||||
SetBaseRegisters(hypervisor::HypervisorCpuError),
|
||||
/// Failed to configure the FPU.
|
||||
#[error("Failed to configure the FPU: {0}")]
|
||||
SetFpuRegisters(hypervisor::HypervisorCpuError),
|
||||
/// Setting up MSRs failed.
|
||||
#[error("Setting up MSRs failed: {0}")]
|
||||
SetModelSpecificRegisters(hypervisor::HypervisorCpuError),
|
||||
/// Failed to set SREGs for this CPU.
|
||||
#[error("Failed to set SREGs for this CPU: {0}")]
|
||||
SetStatusRegisters(hypervisor::HypervisorCpuError),
|
||||
/// Checking the GDT address failed.
|
||||
#[error("Checking the GDT address failed")]
|
||||
CheckGdtAddr,
|
||||
/// Writing the GDT to RAM failed.
|
||||
#[error("Writing the GDT to RAM failed: {0}")]
|
||||
WriteGdt(GuestMemoryError),
|
||||
/// Writing the IDT to RAM failed.
|
||||
#[error("Writing the IDT to RAM failed: {0}")]
|
||||
WriteIdt(GuestMemoryError),
|
||||
/// Writing PDPTE to RAM failed.
|
||||
#[error("Writing PDPTE to RAM failed: {0}")]
|
||||
WritePdpteAddress(GuestMemoryError),
|
||||
/// Writing PDE to RAM failed.
|
||||
#[error("Writing PDE to RAM failed: {0}")]
|
||||
WritePdeAddress(GuestMemoryError),
|
||||
/// Writing PML4 to RAM failed.
|
||||
#[error("Writing PML4 to RAM failed: {0}")]
|
||||
WritePml4Address(GuestMemoryError),
|
||||
/// Writing PML5 to RAM failed.
|
||||
#[error("Writing PML5 to RAM failed: {0}")]
|
||||
WritePml5Address(GuestMemoryError),
|
||||
}
|
||||
|
||||
@@ -94,21 +77,13 @@ pub fn setup_msrs(vcpu: &Arc<dyn hypervisor::Vcpu>) -> Result<()> {
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `vcpu` - Structure for the VCPU that holds the VCPU's fd.
|
||||
/// * `entry_point` - Description of the boot entry to set up.
|
||||
pub fn setup_regs(vcpu: &Arc<dyn hypervisor::Vcpu>, entry_point: EntryPoint) -> Result<()> {
|
||||
let mut regs = vcpu.create_standard_regs();
|
||||
match entry_point.setup_header {
|
||||
None => {
|
||||
regs.set_rflags(0x0000000000000002u64);
|
||||
regs.set_rip(entry_point.entry_addr.raw_value());
|
||||
regs.set_rbx(PVH_INFO_START.raw_value());
|
||||
}
|
||||
Some(_) => {
|
||||
regs.set_rflags(0x0000000000000002u64);
|
||||
regs.set_rip(entry_point.entry_addr.raw_value());
|
||||
regs.set_rsp(BOOT_STACK_POINTER.raw_value());
|
||||
regs.set_rsi(ZERO_PAGE_START.raw_value());
|
||||
}
|
||||
/// * `boot_ip` - Starting instruction pointer.
|
||||
pub fn setup_regs(vcpu: &Arc<dyn hypervisor::Vcpu>, boot_ip: u64) -> Result<()> {
|
||||
let regs = StandardRegisters {
|
||||
rflags: 0x0000000000000002u64,
|
||||
rbx: PVH_INFO_START.raw_value(),
|
||||
rip: boot_ip,
|
||||
..Default::default()
|
||||
};
|
||||
vcpu.set_regs(®s).map_err(Error::SetBaseRegisters)
|
||||
}
|
||||
@@ -188,9 +163,9 @@ pub fn configure_segments_and_sregs(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use vm_memory::GuestAddress;
|
||||
|
||||
use super::*;
|
||||
use crate::GuestMemoryMmap;
|
||||
use vm_memory::GuestAddress;
|
||||
|
||||
fn create_guest_mem() -> GuestMemoryMmap {
|
||||
GuestMemoryMmap::from_ranges(&[(GuestAddress(0), 0x10000)]).unwrap()
|
||||
|
||||
@@ -6,37 +6,55 @@
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||
|
||||
use std::{mem, result, slice};
|
||||
|
||||
use thiserror::Error;
|
||||
use uuid::Uuid;
|
||||
use vm_memory::{Address, ByteValued, Bytes, GuestAddress};
|
||||
|
||||
use crate::layout::SMBIOS_START;
|
||||
use crate::GuestMemoryMmap;
|
||||
use std::fmt::{self, Display};
|
||||
use std::mem;
|
||||
use std::result;
|
||||
use std::slice;
|
||||
use uuid::Uuid;
|
||||
use vm_memory::ByteValued;
|
||||
use vm_memory::{Address, Bytes, GuestAddress};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// There was too little guest memory to store the entire SMBIOS table.
|
||||
#[error("There was too little guest memory to store the SMBIOS table")]
|
||||
NotEnoughMemory,
|
||||
/// The SMBIOS table has too little address space to be stored.
|
||||
#[error("The SMBIOS table has too little address space to be stored")]
|
||||
AddressOverflow,
|
||||
/// Failure while zeroing out the memory for the SMBIOS table.
|
||||
#[error("Failure while zeroing out the memory for the SMBIOS table")]
|
||||
Clear,
|
||||
/// Failure to write SMBIOS entrypoint structure
|
||||
#[error("Failure to write SMBIOS entrypoint structure")]
|
||||
WriteSmbiosEp,
|
||||
/// Failure to write additional data to memory
|
||||
#[error("Failure to write additional data to memory")]
|
||||
WriteData,
|
||||
/// Failure to parse uuid, uuid format may be error
|
||||
#[error("Failure to parse uuid: {0}")]
|
||||
ParseUuid(uuid::Error),
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {}
|
||||
|
||||
impl Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
use self::Error::*;
|
||||
|
||||
let description = match self {
|
||||
NotEnoughMemory => {
|
||||
"There was too little guest memory to store the SMBIOS table".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>;
|
||||
|
||||
// Constants sourced from SMBIOS Spec 3.2.0.
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
// Copyright © 2021 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
use crate::GuestMemoryMmap;
|
||||
use std::fs::File;
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
use std::str::FromStr;
|
||||
|
||||
use thiserror::Error;
|
||||
use uuid::Uuid;
|
||||
use vm_memory::{ByteValued, Bytes, GuestAddress, GuestMemoryError};
|
||||
|
||||
use crate::GuestMemoryMmap;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum TdvfError {
|
||||
#[error("Failed read TDVF descriptor: {0}")]
|
||||
@@ -35,7 +33,7 @@ const TABLE_FOOTER_GUID: &str = "96b582de-1fb2-45f7-baea-a366c55a082d";
|
||||
const TDVF_METADATA_OFFSET_GUID: &str = "e47a6535-984a-4798-865e-4685a7bf8ec2";
|
||||
|
||||
// TDVF_DESCRIPTOR
|
||||
#[repr(C, packed)]
|
||||
#[repr(packed)]
|
||||
#[derive(Default)]
|
||||
pub struct TdvfDescriptor {
|
||||
signature: [u8; 4],
|
||||
@@ -45,7 +43,7 @@ pub struct TdvfDescriptor {
|
||||
}
|
||||
|
||||
// TDVF_SECTION
|
||||
#[repr(C, packed)]
|
||||
#[repr(packed)]
|
||||
#[derive(Clone, Copy, Default, Debug)]
|
||||
pub struct TdvfSection {
|
||||
pub data_offset: u32,
|
||||
@@ -209,7 +207,7 @@ enum HobType {
|
||||
EndOfHobList = 0xffff,
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Default, Debug)]
|
||||
struct HobHeader {
|
||||
r#type: HobType,
|
||||
@@ -217,7 +215,7 @@ struct HobHeader {
|
||||
reserved: u32,
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Default, Debug)]
|
||||
struct HobHandoffInfoTable {
|
||||
header: HobHeader,
|
||||
@@ -230,7 +228,7 @@ struct HobHandoffInfoTable {
|
||||
efi_end_of_hob_list: u64,
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Default, Debug)]
|
||||
struct EfiGuid {
|
||||
data1: u32,
|
||||
@@ -239,7 +237,7 @@ struct EfiGuid {
|
||||
data4: [u8; 8],
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Default, Debug)]
|
||||
struct HobResourceDescriptor {
|
||||
header: HobHeader,
|
||||
@@ -250,7 +248,7 @@ struct HobResourceDescriptor {
|
||||
resource_length: u64,
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Default, Debug)]
|
||||
struct HobGuidType {
|
||||
header: HobHeader,
|
||||
@@ -266,14 +264,14 @@ pub enum PayloadImageType {
|
||||
RawVmLinux,
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Default, Debug)]
|
||||
pub struct PayloadInfo {
|
||||
pub image_type: PayloadImageType,
|
||||
pub entry_point: u64,
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Default, Debug)]
|
||||
struct TdPayload {
|
||||
guid_type: HobGuidType,
|
||||
|
||||
@@ -1,30 +1,27 @@
|
||||
[package]
|
||||
authors = ["The Chromium OS Authors", "The Cloud Hypervisor Authors"]
|
||||
edition = "2021"
|
||||
name = "block"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
authors = ["The Cloud Hypervisor Authors", "The Chromium OS Authors"]
|
||||
|
||||
[features]
|
||||
default = []
|
||||
io_uring = ["dep:io-uring"]
|
||||
|
||||
[dependencies]
|
||||
byteorder = "1.5.0"
|
||||
crc-any = "2.4.4"
|
||||
io-uring = { version = "0.6.4", optional = true }
|
||||
libc = "0.2.167"
|
||||
log = "0.4.22"
|
||||
remain = "0.2.14"
|
||||
serde = { version = "1.0.208", features = ["derive"] }
|
||||
smallvec = "1.13.2"
|
||||
thiserror = "2.0.6"
|
||||
uuid = { version = "1.12.1", features = ["v4"] }
|
||||
virtio-bindings = { workspace = true, features = ["virtio-v5_0_0"] }
|
||||
virtio-queue = { workspace = true }
|
||||
vm-memory = { workspace = true, features = [
|
||||
"backend-atomic",
|
||||
"backend-bitmap",
|
||||
"backend-mmap",
|
||||
] }
|
||||
byteorder = "1.4.3"
|
||||
crc32c = "0.6.4"
|
||||
io-uring = { version = "0.6.1", optional = true }
|
||||
libc = "0.2.147"
|
||||
log = "0.4.17"
|
||||
remain = "0.2.11"
|
||||
smallvec = "1.11.0"
|
||||
thiserror = "1.0.40"
|
||||
uuid = { version = "1.3.4", features = ["v4"] }
|
||||
versionize = "0.1.10"
|
||||
versionize_derive = "0.1.4"
|
||||
virtio-bindings = { version = "0.2.0", features = ["virtio-v5_0_0"] }
|
||||
virtio-queue = "0.9.0"
|
||||
vm-memory = { version = "0.12.2", features = ["backend-mmap", "backend-atomic", "backend-bitmap"] }
|
||||
vm-virtio = { path = "../vm-virtio" }
|
||||
vmm-sys-util = { workspace = true }
|
||||
vmm-sys-util = "0.11.0"
|
||||
|
||||
@@ -2,11 +2,10 @@
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||
|
||||
use crate::DiskTopology;
|
||||
use thiserror::Error;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
use crate::DiskTopology;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum DiskFileError {
|
||||
/// Failed getting disk file size.
|
||||
|
||||
@@ -2,13 +2,12 @@
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::vhd::VhdFooter;
|
||||
use crate::BlockBackend;
|
||||
use std::fs::File;
|
||||
use std::io::{Read, Seek, SeekFrom, Write};
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
|
||||
use crate::vhd::VhdFooter;
|
||||
use crate::BlockBackend;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FixedVhd {
|
||||
file: File,
|
||||
|
||||
@@ -2,17 +2,15 @@
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::fs::File;
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
use crate::async_io::{
|
||||
AsyncIo, AsyncIoError, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult,
|
||||
};
|
||||
use crate::fixed_vhd::FixedVhd;
|
||||
use crate::raw_async::RawFileAsync;
|
||||
use crate::BlockBackend;
|
||||
use std::fs::File;
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
pub struct FixedVhdDiskAsync(FixedVhd);
|
||||
|
||||
|
||||
@@ -2,17 +2,15 @@
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::fs::File;
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
use crate::async_io::{
|
||||
AsyncIo, AsyncIoError, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult,
|
||||
};
|
||||
use crate::fixed_vhd::FixedVhd;
|
||||
use crate::raw_sync::RawFileSync;
|
||||
use crate::BlockBackend;
|
||||
use std::fs::File;
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
pub struct FixedVhdDiskSync(FixedVhd);
|
||||
|
||||
|
||||
155
block/src/lib.rs
155
block/src/lib.rs
@@ -30,36 +30,43 @@ pub mod vhd;
|
||||
pub mod vhdx;
|
||||
pub mod vhdx_sync;
|
||||
|
||||
use crate::async_io::{AsyncIo, AsyncIoError, AsyncIoResult};
|
||||
use crate::fixed_vhd::FixedVhd;
|
||||
use crate::qcow::{QcowFile, RawFile};
|
||||
use crate::vhdx::{Vhdx, VhdxError};
|
||||
#[cfg(feature = "io_uring")]
|
||||
use io_uring::{opcode, IoUring, Probe};
|
||||
use libc::{ioctl, S_IFBLK, S_IFMT};
|
||||
use smallvec::SmallVec;
|
||||
use std::alloc::{alloc_zeroed, dealloc, Layout};
|
||||
use std::cmp;
|
||||
use std::collections::VecDeque;
|
||||
use std::convert::TryInto;
|
||||
use std::fmt::Debug;
|
||||
use std::fs::File;
|
||||
use std::io::{self, IoSlice, IoSliceMut, Read, Seek, SeekFrom, Write};
|
||||
use std::os::linux::fs::MetadataExt;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::path::Path;
|
||||
use std::sync::{Arc, MutexGuard};
|
||||
use std::result;
|
||||
use std::sync::Arc;
|
||||
use std::sync::MutexGuard;
|
||||
use std::time::Instant;
|
||||
use std::{cmp, result};
|
||||
|
||||
#[cfg(feature = "io_uring")]
|
||||
use io_uring::{opcode, IoUring, Probe};
|
||||
use libc::{ioctl, S_IFBLK, S_IFMT};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use smallvec::SmallVec;
|
||||
use thiserror::Error;
|
||||
use versionize::{VersionMap, Versionize, VersionizeResult};
|
||||
use versionize_derive::Versionize;
|
||||
use virtio_bindings::virtio_blk::*;
|
||||
use virtio_queue::DescriptorChain;
|
||||
use vm_memory::bitmap::Bitmap;
|
||||
use vm_memory::{
|
||||
ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryError, GuestMemoryLoadGuard,
|
||||
bitmap::AtomicBitmap, bitmap::Bitmap, ByteValued, Bytes, GuestAddress, GuestMemory,
|
||||
GuestMemoryError, GuestMemoryLoadGuard,
|
||||
};
|
||||
use vm_virtio::{AccessPlatform, Translatable};
|
||||
use vmm_sys_util::aio;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
use vmm_sys_util::{aio, ioctl_io_nr, ioctl_ioc_nr};
|
||||
use vmm_sys_util::{ioctl_io_nr, ioctl_ioc_nr};
|
||||
|
||||
use crate::async_io::{AsyncIo, AsyncIoError, AsyncIoResult};
|
||||
use crate::vhdx::VhdxError;
|
||||
type GuestMemoryMmap = vm_memory::GuestMemoryMmap<AtomicBitmap>;
|
||||
|
||||
const SECTOR_SHIFT: u8 = 9;
|
||||
pub const SECTOR_SIZE: u64 = 0x01 << SECTOR_SHIFT;
|
||||
@@ -136,14 +143,10 @@ pub enum ExecuteError {
|
||||
Flush(io::Error),
|
||||
#[error("Failed to read: {0}")]
|
||||
Read(GuestMemoryError),
|
||||
#[error("Failed to read_exact: {0}")]
|
||||
ReadExact(io::Error),
|
||||
#[error("Failed to seek: {0}")]
|
||||
Seek(io::Error),
|
||||
#[error("Failed to write: {0}")]
|
||||
Write(GuestMemoryError),
|
||||
#[error("Failed to write_all: {0}")]
|
||||
WriteAll(io::Error),
|
||||
#[error("Unsupported request: {0}")]
|
||||
Unsupported(u32),
|
||||
#[error("Failed to submit io uring: {0}")]
|
||||
@@ -161,15 +164,13 @@ pub enum ExecuteError {
|
||||
}
|
||||
|
||||
impl ExecuteError {
|
||||
pub fn status(&self) -> u8 {
|
||||
let status = match *self {
|
||||
pub fn status(&self) -> u32 {
|
||||
match *self {
|
||||
ExecuteError::BadRequest(_) => VIRTIO_BLK_S_IOERR,
|
||||
ExecuteError::Flush(_) => VIRTIO_BLK_S_IOERR,
|
||||
ExecuteError::Read(_) => VIRTIO_BLK_S_IOERR,
|
||||
ExecuteError::ReadExact(_) => VIRTIO_BLK_S_IOERR,
|
||||
ExecuteError::Seek(_) => VIRTIO_BLK_S_IOERR,
|
||||
ExecuteError::Write(_) => VIRTIO_BLK_S_IOERR,
|
||||
ExecuteError::WriteAll(_) => VIRTIO_BLK_S_IOERR,
|
||||
ExecuteError::Unsupported(_) => VIRTIO_BLK_S_UNSUPP,
|
||||
ExecuteError::SubmitIoUring(_) => VIRTIO_BLK_S_IOERR,
|
||||
ExecuteError::GetHostAddress(_) => VIRTIO_BLK_S_IOERR,
|
||||
@@ -177,8 +178,7 @@ impl ExecuteError {
|
||||
ExecuteError::AsyncWrite(_) => VIRTIO_BLK_S_IOERR,
|
||||
ExecuteError::AsyncFlush(_) => VIRTIO_BLK_S_IOERR,
|
||||
ExecuteError::TemporaryBufferAllocation(_) => VIRTIO_BLK_S_IOERR,
|
||||
};
|
||||
status as u8
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,8 +191,8 @@ pub enum RequestType {
|
||||
Unsupported(u32),
|
||||
}
|
||||
|
||||
pub fn request_type<B: Bitmap + 'static>(
|
||||
mem: &vm_memory::GuestMemoryMmap<B>,
|
||||
pub fn request_type(
|
||||
mem: &GuestMemoryMmap,
|
||||
desc_addr: GuestAddress,
|
||||
) -> result::Result<RequestType, Error> {
|
||||
let type_ = mem.read_obj(desc_addr).map_err(Error::GuestMemory)?;
|
||||
@@ -205,10 +205,7 @@ pub fn request_type<B: Bitmap + 'static>(
|
||||
}
|
||||
}
|
||||
|
||||
fn sector<B: Bitmap + 'static>(
|
||||
mem: &vm_memory::GuestMemoryMmap<B>,
|
||||
desc_addr: GuestAddress,
|
||||
) -> result::Result<u64, Error> {
|
||||
fn sector(mem: &GuestMemoryMmap, desc_addr: GuestAddress) -> result::Result<u64, Error> {
|
||||
const SECTOR_OFFSET: usize = 8;
|
||||
let addr = match mem.checked_offset(desc_addr, SECTOR_OFFSET) {
|
||||
Some(v) => v,
|
||||
@@ -218,8 +215,6 @@ fn sector<B: Bitmap + 'static>(
|
||||
mem.read_obj(addr).map_err(Error::GuestMemory)
|
||||
}
|
||||
|
||||
const DEFAULT_DESCRIPTOR_VEC_SIZE: usize = 32;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AlignedOperation {
|
||||
origin_ptr: u64,
|
||||
@@ -232,23 +227,24 @@ pub struct AlignedOperation {
|
||||
pub struct Request {
|
||||
pub request_type: RequestType,
|
||||
pub sector: u64,
|
||||
pub data_descriptors: SmallVec<[(GuestAddress, u32); DEFAULT_DESCRIPTOR_VEC_SIZE]>,
|
||||
pub data_descriptors: SmallVec<[(GuestAddress, u32); 1]>,
|
||||
pub status_addr: GuestAddress,
|
||||
pub writeback: bool,
|
||||
pub aligned_operations: SmallVec<[AlignedOperation; DEFAULT_DESCRIPTOR_VEC_SIZE]>,
|
||||
pub aligned_operations: SmallVec<[AlignedOperation; 1]>,
|
||||
pub start: Instant,
|
||||
}
|
||||
|
||||
impl Request {
|
||||
pub fn parse<B: Bitmap + 'static>(
|
||||
desc_chain: &mut DescriptorChain<GuestMemoryLoadGuard<vm_memory::GuestMemoryMmap<B>>>,
|
||||
pub fn parse(
|
||||
desc_chain: &mut DescriptorChain<GuestMemoryLoadGuard<GuestMemoryMmap>>,
|
||||
access_platform: Option<&Arc<dyn AccessPlatform>>,
|
||||
) -> result::Result<Request, Error> {
|
||||
let hdr_desc = desc_chain
|
||||
.next()
|
||||
.ok_or(Error::DescriptorChainTooShort)
|
||||
.inspect_err(|_| {
|
||||
.map_err(|e| {
|
||||
error!("Missing head descriptor");
|
||||
e
|
||||
})?;
|
||||
|
||||
// The head contains the request type which MUST be readable.
|
||||
@@ -263,10 +259,10 @@ impl Request {
|
||||
let mut req = Request {
|
||||
request_type: request_type(desc_chain.memory(), hdr_desc_addr)?,
|
||||
sector: sector(desc_chain.memory(), hdr_desc_addr)?,
|
||||
data_descriptors: SmallVec::with_capacity(DEFAULT_DESCRIPTOR_VEC_SIZE),
|
||||
data_descriptors: SmallVec::with_capacity(1),
|
||||
status_addr: GuestAddress(0),
|
||||
writeback: true,
|
||||
aligned_operations: SmallVec::with_capacity(DEFAULT_DESCRIPTOR_VEC_SIZE),
|
||||
aligned_operations: SmallVec::with_capacity(1),
|
||||
start: Instant::now(),
|
||||
};
|
||||
|
||||
@@ -274,8 +270,9 @@ impl Request {
|
||||
let mut desc = desc_chain
|
||||
.next()
|
||||
.ok_or(Error::DescriptorChainTooShort)
|
||||
.inspect_err(|_| {
|
||||
.map_err(|e| {
|
||||
error!("Only head descriptor present: request = {:?}", req);
|
||||
e
|
||||
})?;
|
||||
|
||||
if !desc.has_next() {
|
||||
@@ -306,8 +303,9 @@ impl Request {
|
||||
desc = desc_chain
|
||||
.next()
|
||||
.ok_or(Error::DescriptorChainTooShort)
|
||||
.inspect_err(|_| {
|
||||
.map_err(|e| {
|
||||
error!("DescriptorChain corrupted: request = {:?}", req);
|
||||
e
|
||||
})?;
|
||||
}
|
||||
status_desc = desc;
|
||||
@@ -329,11 +327,11 @@ impl Request {
|
||||
Ok(req)
|
||||
}
|
||||
|
||||
pub fn execute<T: Seek + Read + Write, B: Bitmap + 'static>(
|
||||
pub fn execute<T: Seek + Read + Write>(
|
||||
&self,
|
||||
disk: &mut T,
|
||||
disk_nsectors: u64,
|
||||
mem: &vm_memory::GuestMemoryMmap<B>,
|
||||
mem: &GuestMemoryMmap,
|
||||
serial: &[u8],
|
||||
) -> result::Result<u32, ExecuteError> {
|
||||
disk.seek(SeekFrom::Start(self.sector << SECTOR_SHIFT))
|
||||
@@ -353,21 +351,13 @@ impl Request {
|
||||
|
||||
match self.request_type {
|
||||
RequestType::In => {
|
||||
let mut buf = vec![0u8; *data_len as usize];
|
||||
disk.read_exact(&mut buf).map_err(ExecuteError::ReadExact)?;
|
||||
mem.read_exact_volatile_from(
|
||||
*data_addr,
|
||||
&mut buf.as_slice(),
|
||||
*data_len as usize,
|
||||
)
|
||||
.map_err(ExecuteError::Read)?;
|
||||
mem.read_exact_from(*data_addr, disk, *data_len as usize)
|
||||
.map_err(ExecuteError::Read)?;
|
||||
len += data_len;
|
||||
}
|
||||
RequestType::Out => {
|
||||
let mut buf: Vec<u8> = Vec::new();
|
||||
mem.write_all_volatile_to(*data_addr, &mut buf, *data_len as usize)
|
||||
mem.write_all_to(*data_addr, disk, *data_len as usize)
|
||||
.map_err(ExecuteError::Write)?;
|
||||
disk.write_all(&buf).map_err(ExecuteError::WriteAll)?;
|
||||
if !self.writeback {
|
||||
disk.flush().map_err(ExecuteError::Flush)?;
|
||||
}
|
||||
@@ -386,9 +376,9 @@ impl Request {
|
||||
Ok(len)
|
||||
}
|
||||
|
||||
pub fn execute_async<B: Bitmap + 'static>(
|
||||
pub fn execute_async(
|
||||
&mut self,
|
||||
mem: &vm_memory::GuestMemoryMmap<B>,
|
||||
mem: &GuestMemoryMmap,
|
||||
disk_nsectors: u64,
|
||||
disk_image: &mut dyn AsyncIo,
|
||||
serial: &[u8],
|
||||
@@ -398,7 +388,7 @@ impl Request {
|
||||
let request_type = self.request_type;
|
||||
let offset = (sector << SECTOR_SHIFT) as libc::off_t;
|
||||
|
||||
let mut iovecs: SmallVec<[libc::iovec; DEFAULT_DESCRIPTOR_VEC_SIZE]> =
|
||||
let mut iovecs: SmallVec<[libc::iovec; 1]> =
|
||||
SmallVec::with_capacity(self.data_descriptors.len());
|
||||
for (data_addr, data_len) in &self.data_descriptors {
|
||||
if *data_len == 0 {
|
||||
@@ -541,7 +531,7 @@ impl Request {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize)]
|
||||
#[derive(Copy, Clone, Debug, Default, Versionize)]
|
||||
#[repr(C, packed)]
|
||||
pub struct VirtioBlockConfig {
|
||||
pub capacity: u64,
|
||||
@@ -564,7 +554,7 @@ pub struct VirtioBlockConfig {
|
||||
pub write_zeroes_may_unmap: u8,
|
||||
pub unused1: [u8; 3],
|
||||
}
|
||||
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize)]
|
||||
#[derive(Copy, Clone, Debug, Default, Versionize)]
|
||||
#[repr(C, packed)]
|
||||
pub struct VirtioBlockGeometry {
|
||||
pub cylinders: u16,
|
||||
@@ -653,13 +643,10 @@ where
|
||||
completion_list: &mut VecDeque<(u64, i32)>,
|
||||
) -> AsyncIoResult<()> {
|
||||
// Convert libc::iovec into IoSliceMut
|
||||
let mut slices: SmallVec<[IoSliceMut; DEFAULT_DESCRIPTOR_VEC_SIZE]> =
|
||||
SmallVec::with_capacity(iovecs.len());
|
||||
let mut slices: SmallVec<[IoSliceMut; 1]> = SmallVec::with_capacity(iovecs.len());
|
||||
for iovec in iovecs.iter() {
|
||||
// SAFETY: on Linux IoSliceMut wraps around libc::iovec
|
||||
slices.push(IoSliceMut::new(unsafe {
|
||||
std::mem::transmute::<libc::iovec, &mut [u8]>(*iovec)
|
||||
}));
|
||||
slices.push(IoSliceMut::new(unsafe { std::mem::transmute(*iovec) }));
|
||||
}
|
||||
|
||||
let result = {
|
||||
@@ -669,11 +656,9 @@ where
|
||||
file.seek(SeekFrom::Start(offset as u64))
|
||||
.map_err(AsyncIoError::ReadVectored)?;
|
||||
|
||||
let mut r = 0;
|
||||
for b in slices.iter_mut() {
|
||||
r += file.read(b).map_err(AsyncIoError::ReadVectored)?;
|
||||
}
|
||||
r
|
||||
// Read vectored
|
||||
file.read_vectored(slices.as_mut_slice())
|
||||
.map_err(AsyncIoError::ReadVectored)?
|
||||
};
|
||||
|
||||
completion_list.push_back((user_data, result as i32));
|
||||
@@ -691,13 +676,10 @@ where
|
||||
completion_list: &mut VecDeque<(u64, i32)>,
|
||||
) -> AsyncIoResult<()> {
|
||||
// Convert libc::iovec into IoSlice
|
||||
let mut slices: SmallVec<[IoSlice; DEFAULT_DESCRIPTOR_VEC_SIZE]> =
|
||||
SmallVec::with_capacity(iovecs.len());
|
||||
let mut slices: SmallVec<[IoSlice; 1]> = SmallVec::with_capacity(iovecs.len());
|
||||
for iovec in iovecs.iter() {
|
||||
// SAFETY: on Linux IoSlice wraps around libc::iovec
|
||||
slices.push(IoSlice::new(unsafe {
|
||||
std::mem::transmute::<libc::iovec, &mut [u8]>(*iovec)
|
||||
}));
|
||||
slices.push(IoSlice::new(unsafe { std::mem::transmute(*iovec) }));
|
||||
}
|
||||
|
||||
let result = {
|
||||
@@ -707,11 +689,9 @@ where
|
||||
file.seek(SeekFrom::Start(offset as u64))
|
||||
.map_err(AsyncIoError::WriteVectored)?;
|
||||
|
||||
let mut r = 0;
|
||||
for b in slices.iter() {
|
||||
r += file.write(b).map_err(AsyncIoError::WriteVectored)?;
|
||||
}
|
||||
r
|
||||
// Write vectored
|
||||
file.write_vectored(slices.as_slice())
|
||||
.map_err(AsyncIoError::WriteVectored)?
|
||||
};
|
||||
|
||||
completion_list.push_back((user_data, result as i32));
|
||||
@@ -795,6 +775,25 @@ pub trait BlockBackend: Read + Write + Seek + Send + Debug {
|
||||
fn size(&self) -> Result<u64, Error>;
|
||||
}
|
||||
|
||||
/// Inspect the image file type and create an appropriate disk file to match it.
|
||||
pub fn create_disk_file(mut file: File, direct_io: bool) -> Result<Box<dyn BlockBackend>, Error> {
|
||||
let image_type = detect_image_type(&mut file).map_err(Error::DetectImageType)?;
|
||||
|
||||
Ok(match image_type {
|
||||
ImageType::Qcow2 => {
|
||||
Box::new(QcowFile::from(RawFile::new(file, direct_io)).map_err(Error::QcowError)?)
|
||||
as Box<dyn BlockBackend>
|
||||
}
|
||||
ImageType::FixedVhd => {
|
||||
Box::new(FixedVhd::new(file).map_err(Error::FixedVhdError)?) as Box<dyn BlockBackend>
|
||||
}
|
||||
ImageType::Vhdx => {
|
||||
Box::new(Vhdx::new(file).map_err(Error::VhdxError)?) as Box<dyn BlockBackend>
|
||||
}
|
||||
ImageType::Raw => Box::new(RawFile::new(file, direct_io)) as Box<dyn BlockBackend>,
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DiskTopology {
|
||||
pub logical_block_size: u64,
|
||||
|
||||
@@ -1,126 +1,134 @@
|
||||
// Copyright 2018 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||
|
||||
mod qcow_raw_file;
|
||||
mod raw_file;
|
||||
mod refcount;
|
||||
mod vec_cache;
|
||||
|
||||
use crate::qcow::{
|
||||
qcow_raw_file::QcowRawFile,
|
||||
refcount::RefCount,
|
||||
vec_cache::{CacheMap, Cacheable, VecCache},
|
||||
};
|
||||
use crate::BlockBackend;
|
||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use libc::{EINVAL, ENOSPC, ENOTSUP};
|
||||
use remain::sorted;
|
||||
use std::cmp::{max, min};
|
||||
use std::fmt::{self, Display};
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::{self, Read, Seek, SeekFrom, Write};
|
||||
use std::mem::size_of;
|
||||
use std::str;
|
||||
use vmm_sys_util::{
|
||||
file_traits::FileSetLen, file_traits::FileSync, seek_hole::SeekHole, write_zeroes::PunchHole,
|
||||
write_zeroes::WriteZeroesAt,
|
||||
};
|
||||
|
||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use libc::{EINVAL, ENOSPC, ENOTSUP};
|
||||
use remain::sorted;
|
||||
use thiserror::Error;
|
||||
use vmm_sys_util::file_traits::{FileSetLen, FileSync};
|
||||
use vmm_sys_util::seek_hole::SeekHole;
|
||||
use vmm_sys_util::write_zeroes::{PunchHole, WriteZeroesAt};
|
||||
|
||||
use crate::qcow::qcow_raw_file::QcowRawFile;
|
||||
pub use crate::qcow::raw_file::RawFile;
|
||||
use crate::qcow::refcount::RefCount;
|
||||
use crate::qcow::vec_cache::{CacheMap, Cacheable, VecCache};
|
||||
use crate::BlockBackend;
|
||||
|
||||
/// Nesting depth limit for disk formats that can open other disk files.
|
||||
const MAX_NESTING_DEPTH: u32 = 10;
|
||||
|
||||
#[sorted]
|
||||
#[derive(Debug, Error)]
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
#[error("Backing file io error: {0}")]
|
||||
BackingFileIo(io::Error),
|
||||
#[error("Backing file open error: {0}")]
|
||||
BackingFileOpen(Box<Error>),
|
||||
#[error("Backing file name is too long: {0} bytes over")]
|
||||
BackingFileOpen(Box<crate::Error>),
|
||||
BackingFileTooLong(usize),
|
||||
#[error("Compressed blocks not supported")]
|
||||
CompressedBlocksNotSupported,
|
||||
#[error("Failed to evict cache: {0}")]
|
||||
EvictingCache(io::Error),
|
||||
#[error("File larger than max of {MAX_QCOW_FILE_SIZE}: {0}")]
|
||||
FileTooBig(u64),
|
||||
#[error("Failed to get file size: {0}")]
|
||||
GettingFileSize(io::Error),
|
||||
#[error("Failed to get refcount: {0}")]
|
||||
GettingRefcount(refcount::Error),
|
||||
#[error("Failed to parse filename: {0}")]
|
||||
InvalidBackingFileName(str::Utf8Error),
|
||||
#[error("Invalid cluster index")]
|
||||
InvalidClusterIndex,
|
||||
#[error("Invalid cluster size")]
|
||||
InvalidClusterSize,
|
||||
#[error("Invalid index")]
|
||||
InvalidIndex,
|
||||
#[error("Invalid L1 table offset")]
|
||||
InvalidL1TableOffset,
|
||||
#[error("Invalid L1 table size: {0}")]
|
||||
InvalidL1TableSize(u32),
|
||||
#[error("Invalid magic")]
|
||||
InvalidMagic,
|
||||
#[error("Invalid offset: {0}")]
|
||||
InvalidOffset(u64),
|
||||
#[error("Invalid refcount table offset")]
|
||||
InvalidRefcountTableOffset,
|
||||
#[error("Invalid refcount table size: {0}")]
|
||||
InvalidRefcountTableSize(u64),
|
||||
#[error("Maximum disk nesting depth exceeded")]
|
||||
MaxNestingDepthExceeded,
|
||||
#[error("No free clusters")]
|
||||
NoFreeClusters,
|
||||
#[error("No refcount clusters")]
|
||||
NoRefcountClusters,
|
||||
#[error("Not enough space for refcounts")]
|
||||
NotEnoughSpaceForRefcounts,
|
||||
#[error("Failed to open file {0}")]
|
||||
OpeningFile(io::Error),
|
||||
#[error("Failed to read data: {0}")]
|
||||
ReadingData(io::Error),
|
||||
#[error("Failed to read header: {0}")]
|
||||
ReadingHeader(io::Error),
|
||||
#[error("Failed to read pointers: {0}")]
|
||||
ReadingPointers(io::Error),
|
||||
#[error("Failed to read ref count block: {0}")]
|
||||
ReadingRefCountBlock(refcount::Error),
|
||||
#[error("Failed to read ref counts: {0}")]
|
||||
ReadingRefCounts(io::Error),
|
||||
#[error("Failed to rebuild ref counts: {0}")]
|
||||
RebuildingRefCounts(io::Error),
|
||||
#[error("Refcount table offset past file end")]
|
||||
RefcountTableOffEnd,
|
||||
#[error("Too many clusters specified for refcount")]
|
||||
RefcountTableTooLarge,
|
||||
#[error("Failed to seek file: {0}")]
|
||||
SeekingFile(io::Error),
|
||||
#[error("Failed to set file size: {0}")]
|
||||
SettingFileSize(io::Error),
|
||||
#[error("Failed to set refcount refcount: {0}")]
|
||||
SettingRefcountRefcount(io::Error),
|
||||
#[error("Size too small for number of clusters")]
|
||||
SizeTooSmallForNumberOfClusters,
|
||||
#[error("L1 entry table too large: {0}")]
|
||||
TooManyL1Entries(u64),
|
||||
#[error("Ref count table too large: {0}")]
|
||||
TooManyRefcounts(u64),
|
||||
#[error("Unsupported refcount order")]
|
||||
UnsupportedRefcountOrder,
|
||||
#[error("Unsupported version: {0}")]
|
||||
UnsupportedVersion(u32),
|
||||
#[error("Failed to write data: {0}")]
|
||||
WritingData(io::Error),
|
||||
#[error("Failed to write header: {0}")]
|
||||
WritingHeader(io::Error),
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
impl Display for Error {
|
||||
#[remain::check]
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
use self::Error::*;
|
||||
|
||||
#[sorted]
|
||||
match self {
|
||||
BackingFileIo(e) => write!(f, "backing file io error: {}", e),
|
||||
BackingFileOpen(e) => write!(f, "backing file open error: {}", *e),
|
||||
BackingFileTooLong(len) => {
|
||||
write!(f, "backing file name is too long: {} bytes over", len)
|
||||
}
|
||||
CompressedBlocksNotSupported => write!(f, "compressed blocks not supported"),
|
||||
EvictingCache(e) => write!(f, "failed to evict cache: {e}"),
|
||||
FileTooBig(size) => write!(f, "file larger than max of {MAX_QCOW_FILE_SIZE}: {size}"),
|
||||
GettingFileSize(e) => write!(f, "failed to get file size: {e}"),
|
||||
GettingRefcount(e) => write!(f, "failed to get refcount: {e}"),
|
||||
InvalidBackingFileName(e) => write!(f, "failed to parse filename: {}", e),
|
||||
InvalidClusterIndex => write!(f, "invalid cluster index"),
|
||||
InvalidClusterSize => write!(f, "invalid cluster size"),
|
||||
InvalidIndex => write!(f, "invalid index"),
|
||||
InvalidL1TableOffset => write!(f, "invalid L1 table offset"),
|
||||
InvalidL1TableSize(size) => write!(f, "invalid L1 table size {size}"),
|
||||
InvalidMagic => write!(f, "invalid magic"),
|
||||
InvalidOffset(_) => write!(f, "invalid offset"),
|
||||
InvalidRefcountTableOffset => write!(f, "invalid refcount table offset"),
|
||||
InvalidRefcountTableSize(size) => write!(f, "invalid refcount table size: {size}"),
|
||||
NoFreeClusters => write!(f, "no free clusters"),
|
||||
NoRefcountClusters => write!(f, "no refcount clusters"),
|
||||
NotEnoughSpaceForRefcounts => write!(f, "not enough space for refcounts"),
|
||||
OpeningFile(e) => write!(f, "failed to open file: {e}"),
|
||||
ReadingData(e) => write!(f, "failed to read data: {e}"),
|
||||
ReadingHeader(e) => write!(f, "failed to read header: {e}"),
|
||||
ReadingPointers(e) => write!(f, "failed to read pointers: {e}"),
|
||||
ReadingRefCountBlock(e) => write!(f, "failed to read ref count block: {e}"),
|
||||
ReadingRefCounts(e) => write!(f, "failed to read ref counts: {e}"),
|
||||
RebuildingRefCounts(e) => write!(f, "failed to rebuild ref counts: {e}"),
|
||||
RefcountTableOffEnd => write!(f, "refcount table offset past file end"),
|
||||
RefcountTableTooLarge => write!(f, "too many clusters specified for refcount table"),
|
||||
SeekingFile(e) => write!(f, "failed to seek file: {e}"),
|
||||
SettingFileSize(e) => write!(f, "failed to set file size: {e}"),
|
||||
SettingRefcountRefcount(e) => write!(f, "failed to set refcount refcount: {e}"),
|
||||
SizeTooSmallForNumberOfClusters => write!(f, "size too small for number of clusters"),
|
||||
TooManyL1Entries(count) => write!(f, "l1 entry table too large: {count}"),
|
||||
TooManyRefcounts(count) => write!(f, "ref count table too large: {count}"),
|
||||
UnsupportedRefcountOrder => write!(f, "unsupported refcount order"),
|
||||
UnsupportedVersion(v) => write!(f, "unsupported version: {v}"),
|
||||
WritingData(e) => write!(f, "failed to write data: {e}"),
|
||||
WritingHeader(e) => write!(f, "failed to write header: {e}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum ImageType {
|
||||
Raw,
|
||||
Qcow2,
|
||||
@@ -437,20 +445,12 @@ pub struct QcowFile {
|
||||
// List of unreferenced clusters available to be used. unref clusters become available once the
|
||||
// removal of references to them have been synced to disk.
|
||||
avail_clusters: Vec<u64>,
|
||||
backing_file: Option<Box<Self>>,
|
||||
backing_file: Option<Box<dyn BlockBackend>>,
|
||||
}
|
||||
|
||||
impl QcowFile {
|
||||
/// Creates a QcowFile from `file`. File must be a valid qcow2 image.
|
||||
///
|
||||
/// Additionally, max nesting depth of this qcow2 image will be set to default value 10.
|
||||
pub fn from(file: RawFile) -> Result<QcowFile> {
|
||||
Self::from_with_nesting_depth(file, MAX_NESTING_DEPTH)
|
||||
}
|
||||
|
||||
/// Creates a QcowFile from `file` and with a max nesting depth. File must be a valid qcow2
|
||||
/// image.
|
||||
pub fn from_with_nesting_depth(mut file: RawFile, max_nesting_depth: u32) -> Result<QcowFile> {
|
||||
pub fn from(mut file: RawFile) -> Result<QcowFile> {
|
||||
let header = QcowHeader::new(&mut file)?;
|
||||
|
||||
// Only v2 and v3 files are supported.
|
||||
@@ -477,20 +477,14 @@ impl QcowFile {
|
||||
let direct_io = file.is_direct();
|
||||
|
||||
let backing_file = if let Some(backing_file_path) = header.backing_file_path.as_ref() {
|
||||
if max_nesting_depth == 0 {
|
||||
return Err(Error::MaxNestingDepthExceeded);
|
||||
}
|
||||
let path = backing_file_path.clone();
|
||||
let backing_raw_file = OpenOptions::new()
|
||||
.read(true)
|
||||
.open(path)
|
||||
.map_err(Error::BackingFileIo)?;
|
||||
let backing_file = Self::from_with_nesting_depth(
|
||||
RawFile::new(backing_raw_file, direct_io),
|
||||
max_nesting_depth - 1,
|
||||
)
|
||||
.map_err(|e| Error::BackingFileOpen(Box::new(e)))?;
|
||||
Some(Box::new(backing_file))
|
||||
let backing_file = crate::create_disk_file(backing_raw_file, direct_io)
|
||||
.map_err(|e| Error::BackingFileOpen(Box::new(e)))?;
|
||||
Some(backing_file)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
@@ -638,22 +632,20 @@ impl QcowFile {
|
||||
file: RawFile,
|
||||
version: u32,
|
||||
backing_file_name: &str,
|
||||
backing_file_max_nesting_depth: u32,
|
||||
) -> Result<QcowFile> {
|
||||
let direct_io = file.is_direct();
|
||||
let backing_raw_file = OpenOptions::new()
|
||||
.read(true)
|
||||
.open(backing_file_name)
|
||||
.map_err(Error::BackingFileIo)?;
|
||||
let backing_file = Self::from_with_nesting_depth(
|
||||
RawFile::new(backing_raw_file, direct_io),
|
||||
backing_file_max_nesting_depth,
|
||||
)
|
||||
.map_err(|e| Error::BackingFileOpen(Box::new(e)))?;
|
||||
let size = backing_file.virtual_size();
|
||||
let backing_file = crate::create_disk_file(backing_raw_file, direct_io)
|
||||
.map_err(|e| Error::BackingFileOpen(Box::new(e)))?;
|
||||
let size = backing_file
|
||||
.size()
|
||||
.map_err(|e| Error::BackingFileOpen(Box::new(e)))?;
|
||||
let header = QcowHeader::create_for_size_and_path(version, size, Some(backing_file_name))?;
|
||||
let mut result = QcowFile::new_from_header(file, header)?;
|
||||
result.backing_file = Some(Box::new(backing_file));
|
||||
result.backing_file = Some(backing_file);
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
@@ -681,7 +673,7 @@ impl QcowFile {
|
||||
Ok(qcow)
|
||||
}
|
||||
|
||||
pub fn set_backing_file(&mut self, backing: Option<Box<Self>>) {
|
||||
pub fn set_backing_file(&mut self, backing: Option<Box<dyn BlockBackend>>) {
|
||||
self.backing_file = backing;
|
||||
}
|
||||
|
||||
@@ -1219,7 +1211,7 @@ impl QcowFile {
|
||||
}
|
||||
|
||||
// Allocate and initialize a new data cluster. Returns the offset of the
|
||||
// cluster into the file on success.
|
||||
// cluster in to the file on success.
|
||||
fn append_data_cluster(&mut self, initial_data: Option<Vec<u8>>) -> std::io::Result<u64> {
|
||||
let new_addr: u64 = self.get_new_cluster(initial_data)?;
|
||||
// The cluster refcount starts at one indicating it is used but doesn't need COW.
|
||||
@@ -1637,7 +1629,7 @@ impl PunchHole for QcowFile {
|
||||
let mut remaining = length;
|
||||
let mut offset = offset;
|
||||
while remaining > 0 {
|
||||
let chunk_length = min(remaining, usize::MAX as u64) as usize;
|
||||
let chunk_length = min(remaining, std::usize::MAX as u64) as usize;
|
||||
self.deallocate_bytes(offset, chunk_length)?;
|
||||
remaining -= chunk_length as u64;
|
||||
offset += chunk_length as u64;
|
||||
@@ -1797,17 +1789,11 @@ where
|
||||
/// Copy the contents of a disk image in `src_file` into `dst_file`.
|
||||
/// The type of `src_file` is automatically detected, and the output file type is
|
||||
/// determined by `dst_type`.
|
||||
pub fn convert(
|
||||
mut src_file: RawFile,
|
||||
dst_file: RawFile,
|
||||
dst_type: ImageType,
|
||||
src_max_nesting_depth: u32,
|
||||
) -> Result<()> {
|
||||
pub fn convert(mut src_file: RawFile, dst_file: RawFile, dst_type: ImageType) -> Result<()> {
|
||||
let src_type = detect_image_type(&mut src_file)?;
|
||||
match src_type {
|
||||
ImageType::Qcow2 => {
|
||||
let mut src_reader =
|
||||
QcowFile::from_with_nesting_depth(src_file, src_max_nesting_depth)?;
|
||||
let mut src_reader = QcowFile::from(src_file)?;
|
||||
convert_reader(&mut src_reader, dst_file, dst_type)
|
||||
}
|
||||
ImageType::Raw => {
|
||||
@@ -1835,15 +1821,11 @@ pub fn detect_image_type(file: &mut RawFile) -> Result<ImageType> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::fs::File;
|
||||
use std::path::Path;
|
||||
|
||||
use vmm_sys_util::tempdir::TempDir;
|
||||
use super::*;
|
||||
use std::io::{Read, Seek, SeekFrom, Write};
|
||||
use vmm_sys_util::tempfile::TempFile;
|
||||
use vmm_sys_util::write_zeroes::WriteZeroes;
|
||||
|
||||
use super::*;
|
||||
|
||||
fn valid_header_v3() -> Vec<u8> {
|
||||
vec![
|
||||
0x51u8, 0x46, 0x49, 0xfb, // magic
|
||||
@@ -2040,80 +2022,6 @@ mod tests {
|
||||
assert_eq!(read_header.backing_file_path, header.backing_file_path);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_backing_file() {
|
||||
// `backing_file` is `None`
|
||||
let header = QcowHeader::create_for_size_and_path(3, 0x10_0000, None)
|
||||
.expect("Failed to create header.");
|
||||
let mut disk_file: RawFile = RawFile::new(TempFile::new().unwrap().into_file(), false);
|
||||
header
|
||||
.write_to(&mut disk_file)
|
||||
.expect("Failed to write header to shm.");
|
||||
disk_file.rewind().unwrap();
|
||||
// The maximum nesting depth is 0, which means backing file is not allowed.
|
||||
QcowFile::from_with_nesting_depth(disk_file, 0).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn disable_backing_file() {
|
||||
// `backing_file` is `Some`
|
||||
let header =
|
||||
QcowHeader::create_for_size_and_path(3, 0x10_0000, Some("/path/to/backing/file"))
|
||||
.expect("Failed to create header.");
|
||||
let mut disk_file: RawFile = RawFile::new(TempFile::new().unwrap().into_file(), false);
|
||||
header
|
||||
.write_to(&mut disk_file)
|
||||
.expect("Failed to write header to shm.");
|
||||
disk_file.rewind().unwrap();
|
||||
// The maximum nesting depth is 0, which means backing file is not allowed.
|
||||
let res = QcowFile::from_with_nesting_depth(disk_file, 0);
|
||||
assert!(matches!(res.unwrap_err(), Error::MaxNestingDepthExceeded));
|
||||
}
|
||||
|
||||
/// Create a qcow2 file with itself as its backing file.
|
||||
///
|
||||
/// Without configuration `max_nesting_depth`, this will cause infinite recursion when loading
|
||||
/// the file until stack overflow.
|
||||
fn new_self_referential_qcow(path: &Path) -> Result<()> {
|
||||
let header = QcowHeader::create_for_size_and_path(3, 0x10_0000, path.to_str())?;
|
||||
let mut disk_file = RawFile::new(
|
||||
File::create(path).expect("Failed to create image file."),
|
||||
false,
|
||||
);
|
||||
header.write_to(&mut disk_file)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn max_nesting_backing() {
|
||||
let test_dir = TempDir::new_with_prefix("/tmp/ch").unwrap();
|
||||
let img_path = test_dir.as_path().join("test.img");
|
||||
|
||||
new_self_referential_qcow(img_path.as_path()).unwrap();
|
||||
|
||||
let err = QcowFile::from_with_nesting_depth(
|
||||
RawFile::new(
|
||||
File::open(img_path.as_path()).expect("Failed to open qcow image file"),
|
||||
false,
|
||||
),
|
||||
MAX_NESTING_DEPTH,
|
||||
)
|
||||
.expect_err("Opening qcow file with itself as backing file should fail.");
|
||||
|
||||
// This type of error is complex. For comparing easily, we can check if it contains the
|
||||
// type name after formatting.
|
||||
assert!(format!("{err:?}").contains(&format!("{:?}", Error::MaxNestingDepthExceeded)));
|
||||
// This should recursively call the function ten times before throwing an error, and the
|
||||
// error `BackingFileOpen` should also be repeated ten times.
|
||||
assert_eq!(
|
||||
format!("{err:?}")
|
||||
.matches("BackingFileOpen")
|
||||
.collect::<Vec<_>>()
|
||||
.len() as u32,
|
||||
MAX_NESTING_DEPTH,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_magic() {
|
||||
let invalid_header = vec![0x51u8, 0x46, 0x4a, 0xfb];
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
// Copyright 2018 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||
|
||||
use std::io::{self, BufWriter, Seek, SeekFrom, Write};
|
||||
use std::mem::size_of;
|
||||
|
||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use vmm_sys_util::write_zeroes::WriteZeroes;
|
||||
|
||||
use super::RawFile;
|
||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use std::io::{self, BufWriter, Seek, SeekFrom, Write};
|
||||
use std::mem::size_of;
|
||||
use vmm_sys_util::write_zeroes::WriteZeroes;
|
||||
|
||||
/// A qcow file. Allows reading/writing clusters and appending clusters.
|
||||
#[derive(Debug)]
|
||||
@@ -24,7 +20,7 @@ impl QcowRawFile {
|
||||
/// Creates a `QcowRawFile` from the given `File`, `None` is returned if `cluster_size` is not
|
||||
/// a power of two.
|
||||
pub fn from(file: RawFile, cluster_size: u64) -> Option<Self> {
|
||||
if !cluster_size.is_power_of_two() {
|
||||
if cluster_size.count_ones() != 1 {
|
||||
return None;
|
||||
}
|
||||
Some(QcowRawFile {
|
||||
|
||||
@@ -8,17 +8,15 @@
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||
|
||||
use crate::BlockBackend;
|
||||
use libc::c_void;
|
||||
use std::alloc::{alloc_zeroed, dealloc, Layout};
|
||||
use std::convert::TryInto;
|
||||
use std::fs::{File, Metadata};
|
||||
use std::io::{self, Read, Seek, SeekFrom, Write};
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
use std::slice;
|
||||
|
||||
use libc::c_void;
|
||||
use vmm_sys_util::seek_hole::SeekHole;
|
||||
use vmm_sys_util::write_zeroes::{PunchHole, WriteZeroesAt};
|
||||
|
||||
use crate::BlockBackend;
|
||||
use vmm_sys_util::{seek_hole::SeekHole, write_zeroes::PunchHole, write_zeroes::WriteZeroesAt};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RawFile {
|
||||
@@ -74,7 +72,7 @@ impl RawFile {
|
||||
|
||||
fn round_up(&self, offset: u64) -> u64 {
|
||||
let align: u64 = self.alignment.try_into().unwrap();
|
||||
offset.div_ceil(align) * align
|
||||
((offset + align - 1) / align) * align
|
||||
}
|
||||
|
||||
fn round_down(&self, offset: u64) -> u64 {
|
||||
|
||||
@@ -1,38 +1,51 @@
|
||||
// Copyright 2018 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||
|
||||
use std::fmt::{self, Display};
|
||||
use std::io;
|
||||
|
||||
use libc::EINVAL;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::qcow::qcow_raw_file::QcowRawFile;
|
||||
use crate::qcow::vec_cache::{CacheMap, Cacheable, VecCache};
|
||||
use crate::qcow::{
|
||||
qcow_raw_file::QcowRawFile,
|
||||
vec_cache::{CacheMap, Cacheable, VecCache},
|
||||
};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// `EvictingCache` - Error writing a refblock from the cache to disk.
|
||||
#[error("Failed to write a refblock from the cache to disk: {0}")]
|
||||
EvictingRefCounts(io::Error),
|
||||
/// `InvalidIndex` - Address requested isn't within the range of the disk.
|
||||
#[error("Address requested is not within the range of the disk")]
|
||||
InvalidIndex,
|
||||
/// `NeedCluster` - Handle this error by reading the cluster and calling the function again.
|
||||
#[error("Cluster with addr={0} needs to be read")]
|
||||
NeedCluster(u64),
|
||||
/// `NeedNewCluster` - Handle this error by allocating a cluster and calling the function again.
|
||||
#[error("New cluster needs to be allocated for refcounts")]
|
||||
NeedNewCluster,
|
||||
/// `ReadingRefCounts` - Error reading the file into the refcount cache.
|
||||
#[error("Failed to read the file into the refcount cache: {0}")]
|
||||
/// `ReadingRefCounts` - Error reading the file in to the refcount cache.
|
||||
ReadingRefCounts(io::Error),
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
impl Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
use self::Error::*;
|
||||
|
||||
match self {
|
||||
EvictingRefCounts(e) => {
|
||||
write!(f, "failed to write a refblock from the cache to disk: {e}")
|
||||
}
|
||||
InvalidIndex => write!(f, "address requested is not within the range of the disk"),
|
||||
NeedCluster(addr) => write!(f, "cluster with addr={addr} needs to be read"),
|
||||
NeedNewCluster => write!(f, "new cluster needs to be allocated for refcounts"),
|
||||
ReadingRefCounts(e) => {
|
||||
write!(f, "failed to read the file into the refcount cache: {e}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the refcount entries for an open qcow file.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct RefCount {
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// Copyright 2018 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||
|
||||
use std::collections::hash_map::IterMut;
|
||||
use std::collections::HashMap;
|
||||
@@ -138,7 +136,7 @@ impl<T: Cacheable> CacheMap<T> {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
struct NumCache(());
|
||||
struct NumCache(pub u64);
|
||||
impl Cacheable for NumCache {
|
||||
fn dirty(&self) -> bool {
|
||||
true
|
||||
@@ -150,28 +148,28 @@ mod tests {
|
||||
let mut cache = CacheMap::<NumCache>::new(3);
|
||||
let mut evicted = None;
|
||||
cache
|
||||
.insert(0, NumCache(()), |index, _| {
|
||||
.insert(0, NumCache(5), |index, _| {
|
||||
evicted = Some(index);
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
assert_eq!(evicted, None);
|
||||
cache
|
||||
.insert(1, NumCache(()), |index, _| {
|
||||
.insert(1, NumCache(6), |index, _| {
|
||||
evicted = Some(index);
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
assert_eq!(evicted, None);
|
||||
cache
|
||||
.insert(2, NumCache(()), |index, _| {
|
||||
.insert(2, NumCache(7), |index, _| {
|
||||
evicted = Some(index);
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
assert_eq!(evicted, None);
|
||||
cache
|
||||
.insert(3, NumCache(()), |index, _| {
|
||||
.insert(3, NumCache(8), |index, _| {
|
||||
evicted = Some(index);
|
||||
Ok(())
|
||||
})
|
||||
|
||||
@@ -2,17 +2,15 @@
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||
|
||||
use crate::async_io::{AsyncIo, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult};
|
||||
use crate::qcow::{QcowFile, RawFile, Result as QcowResult};
|
||||
use crate::AsyncAdaptor;
|
||||
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;
|
||||
|
||||
use crate::async_io::{AsyncIo, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult};
|
||||
use crate::qcow::{QcowFile, RawFile, Result as QcowResult};
|
||||
use crate::AsyncAdaptor;
|
||||
|
||||
pub struct QcowDiskSync {
|
||||
qcow_file: Arc<Mutex<QcowFile>>,
|
||||
}
|
||||
|
||||
@@ -2,17 +2,15 @@
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::{Error, Seek, SeekFrom};
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
|
||||
use io_uring::{opcode, types, IoUring};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
use crate::async_io::{
|
||||
AsyncIo, AsyncIoError, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult,
|
||||
};
|
||||
use crate::DiskTopology;
|
||||
use io_uring::{opcode, squeue, types, IoUring};
|
||||
use std::fs::File;
|
||||
use std::io::{Seek, SeekFrom};
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
pub struct RawFileDisk {
|
||||
file: File,
|
||||
@@ -86,14 +84,14 @@ impl AsyncIo for RawFileAsync {
|
||||
|
||||
// SAFETY: we know the file descriptor is valid and we
|
||||
// relied on vm-memory to provide the buffer address.
|
||||
unsafe {
|
||||
let _ = unsafe {
|
||||
sq.push(
|
||||
&opcode::Readv::new(types::Fd(self.fd), iovecs.as_ptr(), iovecs.len() as u32)
|
||||
.offset(offset.try_into().unwrap())
|
||||
.build()
|
||||
.flags(squeue::Flags::ASYNC)
|
||||
.user_data(user_data),
|
||||
)
|
||||
.map_err(|_| AsyncIoError::ReadVectored(Error::other("Submission queue is full")))?
|
||||
};
|
||||
|
||||
// Update the submission queue and submit new operations to the
|
||||
@@ -114,14 +112,14 @@ impl AsyncIo for RawFileAsync {
|
||||
|
||||
// SAFETY: we know the file descriptor is valid and we
|
||||
// relied on vm-memory to provide the buffer address.
|
||||
unsafe {
|
||||
let _ = unsafe {
|
||||
sq.push(
|
||||
&opcode::Writev::new(types::Fd(self.fd), iovecs.as_ptr(), iovecs.len() as u32)
|
||||
.offset(offset.try_into().unwrap())
|
||||
.build()
|
||||
.flags(squeue::Flags::ASYNC)
|
||||
.user_data(user_data),
|
||||
)
|
||||
.map_err(|_| AsyncIoError::WriteVectored(Error::other("Submission queue is full")))?
|
||||
};
|
||||
|
||||
// Update the submission queue and submit new operations to the
|
||||
@@ -137,13 +135,13 @@ impl AsyncIo for RawFileAsync {
|
||||
let (submitter, mut sq, _) = self.io_uring.split();
|
||||
|
||||
// SAFETY: we know the file descriptor is valid.
|
||||
unsafe {
|
||||
let _ = unsafe {
|
||||
sq.push(
|
||||
&opcode::Fsync::new(types::Fd(self.fd))
|
||||
.build()
|
||||
.flags(squeue::Flags::ASYNC)
|
||||
.user_data(user_data),
|
||||
)
|
||||
.map_err(|_| AsyncIoError::Fsync(Error::other("Submission queue is full")))?
|
||||
};
|
||||
|
||||
// Update the submission queue and submit new operations to the
|
||||
|
||||
@@ -5,17 +5,15 @@
|
||||
// Copyright © 2023 Crusoe Energy Systems LLC
|
||||
//
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::{Seek, SeekFrom};
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
|
||||
use vmm_sys_util::aio;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
use crate::async_io::{
|
||||
AsyncIo, AsyncIoError, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult,
|
||||
};
|
||||
use crate::DiskTopology;
|
||||
use std::fs::File;
|
||||
use std::io::{Seek, SeekFrom};
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
use vmm_sys_util::aio;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
pub struct RawFileDiskAio {
|
||||
file: File,
|
||||
|
||||
@@ -2,17 +2,15 @@
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||
|
||||
use std::collections::VecDeque;
|
||||
use std::fs::File;
|
||||
use std::io::{Seek, SeekFrom};
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
use crate::async_io::{
|
||||
AsyncIo, AsyncIoError, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult,
|
||||
};
|
||||
use crate::DiskTopology;
|
||||
use std::collections::VecDeque;
|
||||
use std::fs::File;
|
||||
use std::io::{Seek, SeekFrom};
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
pub struct RawFileDiskSync {
|
||||
file: File,
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::{read_aligned_block_size, DiskTopology};
|
||||
use std::convert::TryInto;
|
||||
use std::fs::File;
|
||||
use std::io::{Seek, SeekFrom};
|
||||
|
||||
use crate::{read_aligned_block_size, DiskTopology};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct VhdFooter {
|
||||
cookie: u64,
|
||||
@@ -118,13 +118,11 @@ pub fn is_fixed_vhd(f: &mut File) -> std::io::Result<bool> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{is_fixed_vhd, VhdFooter};
|
||||
use std::fs::File;
|
||||
use std::io::{Seek, SeekFrom, Write};
|
||||
|
||||
use vmm_sys_util::tempfile::TempFile;
|
||||
|
||||
use super::{is_fixed_vhd, VhdFooter};
|
||||
|
||||
fn valid_fixed_vhd_footer() -> Vec<u8> {
|
||||
vec![
|
||||
0x63, 0x6f, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x78, // cookie
|
||||
|
||||
@@ -2,20 +2,26 @@
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::vhdx::{
|
||||
vhdx_bat::{BatEntry, VhdxBatError},
|
||||
vhdx_header::{RegionInfo, RegionTableEntry, VhdxHeader, VhdxHeaderError},
|
||||
vhdx_io::VhdxIoError,
|
||||
vhdx_metadata::{DiskSpec, VhdxMetadataError},
|
||||
};
|
||||
use crate::BlockBackend;
|
||||
use byteorder::{BigEndian, ByteOrder};
|
||||
use remain::sorted;
|
||||
use std::collections::btree_map::BTreeMap;
|
||||
use std::fs::File;
|
||||
use std::io::{Read, Seek, SeekFrom, Write};
|
||||
|
||||
use byteorder::{BigEndian, ByteOrder};
|
||||
use remain::sorted;
|
||||
use thiserror::Error;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::vhdx::vhdx_bat::{BatEntry, VhdxBatError};
|
||||
use crate::vhdx::vhdx_header::{RegionInfo, RegionTableEntry, VhdxHeader, VhdxHeaderError};
|
||||
use crate::vhdx::vhdx_io::VhdxIoError;
|
||||
use crate::vhdx::vhdx_metadata::{DiskSpec, VhdxMetadataError};
|
||||
use crate::BlockBackend;
|
||||
macro_rules! div_round_up {
|
||||
($n:expr,$d:expr) => {
|
||||
($n + $d - 1) / $d
|
||||
};
|
||||
}
|
||||
|
||||
mod vhdx_bat;
|
||||
mod vhdx_header;
|
||||
@@ -99,10 +105,11 @@ impl Read for Vhdx {
|
||||
/// Wrapper function to satisfy Read trait implementation for VHDx disk.
|
||||
/// Convert the offset to sector index and buffer length to sector count.
|
||||
fn read(&mut self, buf: &mut [u8]) -> std::result::Result<usize, std::io::Error> {
|
||||
let sector_count = (buf.len() as u64).div_ceil(self.disk_spec.logical_sector_size as u64);
|
||||
let sector_count =
|
||||
div_round_up!(buf.len() as u64, self.disk_spec.logical_sector_size as u64);
|
||||
let sector_index = self.current_offset / self.disk_spec.logical_sector_size as u64;
|
||||
|
||||
let result = vhdx_io::read(
|
||||
vhdx_io::read(
|
||||
&mut self.file,
|
||||
buf,
|
||||
&self.disk_spec,
|
||||
@@ -117,11 +124,7 @@ impl Read for Vhdx {
|
||||
"Failed reading {sector_count} sectors from VHDx at index {sector_index}: {e}"
|
||||
),
|
||||
)
|
||||
})?;
|
||||
|
||||
self.current_offset = self.current_offset.checked_add(result as u64).unwrap();
|
||||
|
||||
Ok(result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,7 +136,8 @@ impl Write for Vhdx {
|
||||
/// Wrapper function to satisfy Write trait implementation for VHDx disk.
|
||||
/// Convert the offset to sector index and buffer length to sector count.
|
||||
fn write(&mut self, buf: &[u8]) -> std::result::Result<usize, std::io::Error> {
|
||||
let sector_count = (buf.len() as u64).div_ceil(self.disk_spec.logical_sector_size as u64);
|
||||
let sector_count =
|
||||
div_round_up!(buf.len() as u64, self.disk_spec.logical_sector_size as u64);
|
||||
let sector_index = self.current_offset / self.disk_spec.logical_sector_size as u64;
|
||||
|
||||
if self.first_write {
|
||||
@@ -146,7 +150,7 @@ impl Write for Vhdx {
|
||||
})?;
|
||||
}
|
||||
|
||||
let result = vhdx_io::write(
|
||||
vhdx_io::write(
|
||||
&mut self.file,
|
||||
buf,
|
||||
&mut self.disk_spec,
|
||||
@@ -162,11 +166,7 @@ impl Write for Vhdx {
|
||||
"Failed writing {sector_count} sectors on VHDx at index {sector_index}: {e}"
|
||||
),
|
||||
)
|
||||
})?;
|
||||
|
||||
self.current_offset = self.current_offset.checked_add(result as u64).unwrap();
|
||||
|
||||
Ok(result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,17 +2,14 @@
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::vhdx::{vhdx_header::RegionTableEntry, vhdx_metadata::DiskSpec};
|
||||
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
|
||||
use remain::sorted;
|
||||
use std::fs::File;
|
||||
use std::io::{self, Seek, SeekFrom};
|
||||
use std::mem::size_of;
|
||||
|
||||
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
|
||||
use remain::sorted;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::vhdx::vhdx_header::RegionTableEntry;
|
||||
use crate::vhdx::vhdx_metadata::DiskSpec;
|
||||
|
||||
// Payload BAT Entry States
|
||||
pub const PAYLOAD_BLOCK_NOT_PRESENT: u64 = 0;
|
||||
pub const PAYLOAD_BLOCK_UNDEFINED: u64 = 1;
|
||||
@@ -78,7 +75,7 @@ impl BatEntry {
|
||||
|
||||
// Calculate the number of entries in the BAT
|
||||
fn calculate_entries(block_size: u32, virtual_disk_size: u64, chunk_ratio: u64) -> u64 {
|
||||
let data_blocks_count = virtual_disk_size.div_ceil(block_size as u64);
|
||||
let data_blocks_count = div_round_up!(virtual_disk_size, block_size as u64);
|
||||
data_blocks_count + (data_blocks_count - 1) / chunk_ratio
|
||||
}
|
||||
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
|
||||
extern crate log;
|
||||
|
||||
use byteorder::{ByteOrder, LittleEndian, ReadBytesExt};
|
||||
use remain::sorted;
|
||||
use std::collections::btree_map::BTreeMap;
|
||||
use std::convert::TryInto;
|
||||
use std::fs::File;
|
||||
use std::io::{self, Read, Seek, SeekFrom, Write};
|
||||
use std::mem::size_of;
|
||||
|
||||
use byteorder::{ByteOrder, LittleEndian, ReadBytesExt};
|
||||
use remain::sorted;
|
||||
use thiserror::Error;
|
||||
use uuid::Uuid;
|
||||
|
||||
@@ -91,7 +91,7 @@ pub type Result<T> = std::result::Result<T, VhdxHeaderError>;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct FileTypeIdentifier {
|
||||
pub _signature: u64,
|
||||
pub signature: u64,
|
||||
}
|
||||
|
||||
impl FileTypeIdentifier {
|
||||
@@ -99,18 +99,18 @@ impl FileTypeIdentifier {
|
||||
pub fn new(f: &mut File) -> Result<FileTypeIdentifier> {
|
||||
f.seek(SeekFrom::Start(FILE_START))
|
||||
.map_err(VhdxHeaderError::SeekFileTypeIdentifier)?;
|
||||
let _signature = f
|
||||
let signature = f
|
||||
.read_u64::<LittleEndian>()
|
||||
.map_err(VhdxHeaderError::ReadFileTypeIdentifier)?;
|
||||
if _signature != VHDX_SIGN {
|
||||
if signature != VHDX_SIGN {
|
||||
return Err(VhdxHeaderError::InvalidVHDXSign);
|
||||
}
|
||||
|
||||
Ok(FileTypeIdentifier { _signature })
|
||||
Ok(FileTypeIdentifier { signature })
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[repr(packed)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Header {
|
||||
pub signature: u32,
|
||||
@@ -128,7 +128,7 @@ pub struct Header {
|
||||
impl Header {
|
||||
/// Reads the Header structure from a reference VHDx file
|
||||
pub fn new(f: &mut File, start: u64) -> Result<Header> {
|
||||
// Read the whole header into a buffer. We will need it for
|
||||
// Read the whole header in to a buffer. We will need it for
|
||||
// calculating checksum.
|
||||
let mut buffer = [0; HEADER_SIZE as usize];
|
||||
f.seek(SeekFrom::Start(start))
|
||||
@@ -142,7 +142,7 @@ impl Header {
|
||||
return Err(VhdxHeaderError::InvalidHeaderSign);
|
||||
}
|
||||
|
||||
let new_checksum = calculate_checksum(&mut buffer, size_of::<u32>());
|
||||
let new_checksum = calculate_checksum(&mut buffer, size_of::<u32>())?;
|
||||
if header.checksum != new_checksum {
|
||||
return Err(VhdxHeaderError::InvalidChecksum(String::from("Header")));
|
||||
}
|
||||
@@ -151,7 +151,7 @@ impl Header {
|
||||
}
|
||||
|
||||
/// Converts the header structure into a buffer
|
||||
fn write_to_buffer(&self, buffer: &mut [u8; HEADER_SIZE as usize]) {
|
||||
fn get_header_as_buffer(&self, buffer: &mut [u8; HEADER_SIZE as usize]) {
|
||||
// SAFETY: self is a valid header.
|
||||
let reference = unsafe {
|
||||
std::slice::from_raw_parts(self as *const Header as *const u8, HEADER_SIZE as usize)
|
||||
@@ -160,7 +160,7 @@ impl Header {
|
||||
}
|
||||
|
||||
/// Creates and returns new updated header from the provided current header
|
||||
fn update_header(
|
||||
pub fn update_header(
|
||||
f: &mut File,
|
||||
current_header: &Header,
|
||||
change_data_guid: bool,
|
||||
@@ -191,9 +191,9 @@ impl Header {
|
||||
log_offset: current_header.log_offset,
|
||||
};
|
||||
|
||||
new_header.write_to_buffer(&mut buffer);
|
||||
new_header.checksum = calculate_checksum(&mut buffer, size_of::<u32>());
|
||||
new_header.write_to_buffer(&mut buffer);
|
||||
new_header.get_header_as_buffer(&mut buffer);
|
||||
new_header.checksum = crc32c::crc32c(&buffer);
|
||||
new_header.get_header_as_buffer(&mut buffer);
|
||||
|
||||
f.seek(SeekFrom::Start(start))
|
||||
.map_err(VhdxHeaderError::SeekHeader)?;
|
||||
@@ -203,7 +203,7 @@ impl Header {
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[repr(packed)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
struct RegionTableHeader {
|
||||
pub signature: u32,
|
||||
@@ -229,7 +229,7 @@ impl RegionTableHeader {
|
||||
return Err(VhdxHeaderError::InvalidRegionSign);
|
||||
}
|
||||
|
||||
let new_checksum = calculate_checksum(&mut buffer, size_of::<u32>());
|
||||
let new_checksum = calculate_checksum(&mut buffer, size_of::<u32>())?;
|
||||
if region_table_header.checksum != new_checksum {
|
||||
return Err(VhdxHeaderError::InvalidChecksum(String::from("Region")));
|
||||
}
|
||||
@@ -328,7 +328,7 @@ impl RegionInfo {
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[repr(packed)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct RegionTableEntry {
|
||||
pub guid: Uuid,
|
||||
@@ -351,6 +351,12 @@ impl RegionTableEntry {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct RegionEntry {
|
||||
_start: u64,
|
||||
_end: u64,
|
||||
}
|
||||
|
||||
enum HeaderNo {
|
||||
First,
|
||||
Second,
|
||||
@@ -369,10 +375,22 @@ pub struct VhdxHeader {
|
||||
impl VhdxHeader {
|
||||
/// Creates a VhdxHeader from a reference to a file
|
||||
pub fn new(f: &mut File) -> Result<VhdxHeader> {
|
||||
let _file_type_identifier: FileTypeIdentifier = FileTypeIdentifier::new(f)?;
|
||||
let header_1 = Header::new(f, HEADER_1_START);
|
||||
let header_2 = Header::new(f, HEADER_2_START);
|
||||
|
||||
let mut file_write_guid: u128 = 0;
|
||||
let metadata = f.metadata().map_err(VhdxHeaderError::ReadMetadata)?;
|
||||
if !metadata.permissions().readonly() {
|
||||
file_write_guid = Uuid::new_v4().as_u128();
|
||||
}
|
||||
|
||||
let (header_1, header_2) =
|
||||
VhdxHeader::update_headers(f, header_1, header_2, file_write_guid)?;
|
||||
Ok(VhdxHeader {
|
||||
_file_type_identifier: FileTypeIdentifier::new(f)?,
|
||||
header_1: Header::new(f, HEADER_1_START)?,
|
||||
header_2: Header::new(f, HEADER_2_START)?,
|
||||
_file_type_identifier,
|
||||
header_1,
|
||||
header_2,
|
||||
region_table_1: RegionTableHeader::new(f, REGION_TABLE_1_START)?,
|
||||
_region_table_2: RegionTableHeader::new(f, REGION_TABLE_2_START)?,
|
||||
})
|
||||
@@ -454,18 +472,17 @@ impl VhdxHeader {
|
||||
/// Therefore, before calculating, the existing checksum is retrieved and the
|
||||
/// corresponding field is made zero. After the calculation, the existing checksum
|
||||
/// is put back to the buffer.
|
||||
fn calculate_checksum(buffer: &mut [u8], csum_offset: usize) -> u32 {
|
||||
// Read the original checksum from the buffer
|
||||
let orig_csum = LittleEndian::read_u32(&buffer[csum_offset..csum_offset + 4]);
|
||||
pub fn calculate_checksum(buffer: &mut [u8], csum_offset: usize) -> Result<u32> {
|
||||
// Read the checksum into a mutable slice
|
||||
let csum_buf = &mut buffer[csum_offset..csum_offset + 4];
|
||||
// Convert the checksum chunk in to a u32 integer
|
||||
let orig_csum = LittleEndian::read_u32(csum_buf);
|
||||
// Zero the checksum in the buffer
|
||||
LittleEndian::write_u32(&mut buffer[csum_offset..csum_offset + 4], 0);
|
||||
LittleEndian::write_u32(csum_buf, 0);
|
||||
// Calculate the checksum on the resulting buffer
|
||||
let mut crc = crc_any::CRC::crc32c();
|
||||
crc.digest(&buffer);
|
||||
let new_csum = crc.get_crc() as u32;
|
||||
|
||||
let new_csum = crc32c::crc32c(buffer);
|
||||
// Put back the original checksum in the buffer
|
||||
LittleEndian::write_u32(&mut buffer[csum_offset..csum_offset + 4], orig_csum);
|
||||
|
||||
new_csum
|
||||
Ok(new_csum)
|
||||
}
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::vhdx::{
|
||||
vhdx_bat::{self, BatEntry, VhdxBatError},
|
||||
vhdx_metadata::{self, DiskSpec},
|
||||
};
|
||||
use remain::sorted;
|
||||
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]
|
||||
@@ -36,7 +36,7 @@ pub type Result<T> = std::result::Result<T, VhdxIoError>;
|
||||
|
||||
macro_rules! align {
|
||||
($n:expr, $align:expr) => {{
|
||||
$n.div_ceil($align) * $align
|
||||
(($n + $align - 1) / $align) * $align
|
||||
}};
|
||||
}
|
||||
|
||||
@@ -95,45 +95,46 @@ pub fn read(
|
||||
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)?;
|
||||
if disk_spec.has_parent {
|
||||
return Err(VhdxIoError::UnsupportedMode);
|
||||
} else {
|
||||
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);
|
||||
}
|
||||
};
|
||||
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))
|
||||
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)?;
|
||||
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);
|
||||
}
|
||||
}
|
||||
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;
|
||||
};
|
||||
sector_count -= sector.free_sectors;
|
||||
sector_index += sector.free_sectors;
|
||||
read_count += sector.free_bytes as usize;
|
||||
}
|
||||
Ok(read_count)
|
||||
}
|
||||
@@ -149,73 +150,77 @@ pub fn write(
|
||||
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)?;
|
||||
if disk_spec.has_parent {
|
||||
return Err(VhdxIoError::UnsupportedMode);
|
||||
} else {
|
||||
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;
|
||||
let bat_entry = match bat.get(sector.bat_index as usize) {
|
||||
Some(entry) => entry.0,
|
||||
None => {
|
||||
return Err(VhdxIoError::InvalidBatIndex);
|
||||
}
|
||||
};
|
||||
|
||||
f.seek(SeekFrom::Start(file_offset))
|
||||
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)?;
|
||||
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;
|
||||
}
|
||||
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))
|
||||
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)?;
|
||||
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);
|
||||
}
|
||||
}
|
||||
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;
|
||||
};
|
||||
sector_count -= sector.free_sectors;
|
||||
sector_index += sector.free_sectors;
|
||||
write_count += sector.free_bytes as usize;
|
||||
}
|
||||
Ok(write_count)
|
||||
}
|
||||
|
||||
@@ -2,17 +2,15 @@
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::vhdx::vhdx_header::RegionTableEntry;
|
||||
use byteorder::{LittleEndian, ReadBytesExt};
|
||||
use remain::sorted;
|
||||
use std::fs::File;
|
||||
use std::io::{self, Read, Seek, SeekFrom};
|
||||
use std::mem::size_of;
|
||||
|
||||
use byteorder::{LittleEndian, ReadBytesExt};
|
||||
use remain::sorted;
|
||||
use thiserror::Error;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::vhdx::vhdx_header::RegionTableEntry;
|
||||
|
||||
const METADATA_SIGN: u64 = 0x6174_6164_6174_656D;
|
||||
const METADATA_ENTRY_SIZE: usize = 32;
|
||||
const METADATA_MAX_ENTRIES: u16 = 2047;
|
||||
@@ -55,8 +53,6 @@ const METADATA_LENGTH_MAX: u32 = 1 << 20; // 1 MiB
|
||||
pub enum VhdxMetadataError {
|
||||
#[error("Invalid block size count")]
|
||||
InvalidBlockSize,
|
||||
#[error("Invalid disk size {0}")]
|
||||
InvalidDiskSize(u64),
|
||||
#[error("Invalid metadata entry count")]
|
||||
InvalidEntryCount,
|
||||
#[error("Invalid logical sector size")]
|
||||
@@ -216,14 +212,6 @@ impl DiskSpec {
|
||||
if metadata_presence != METADATA_ALL_PRESENT {
|
||||
return Err(VhdxMetadataError::MissingMetadata);
|
||||
}
|
||||
// Make sure virtual disk size is not zero
|
||||
if (metadata_presence & METADATA_VIRTUAL_DISK_SIZE_PRESENT != 0)
|
||||
&& disk_spec.virtual_disk_size == 0
|
||||
{
|
||||
return Err(VhdxMetadataError::InvalidDiskSize(
|
||||
disk_spec.virtual_disk_size,
|
||||
));
|
||||
}
|
||||
// Check if the virtual disk size is a multiple of the logical sector
|
||||
// size.
|
||||
if ((metadata_presence & METADATA_LOGICAL_SECTOR_SIZE_PRESENT) != 0)
|
||||
@@ -267,7 +255,7 @@ impl DiskSpec {
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[repr(packed)]
|
||||
#[derive(Default, Debug, Clone, Copy)]
|
||||
struct MetadataTableHeader {
|
||||
signature: u64,
|
||||
@@ -298,7 +286,7 @@ impl MetadataTableHeader {
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[repr(packed)]
|
||||
#[derive(Default, Debug, Clone, Copy)]
|
||||
pub struct MetadataTableEntry {
|
||||
item_id: Uuid,
|
||||
|
||||
@@ -2,15 +2,13 @@
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::collections::VecDeque;
|
||||
use std::fs::File;
|
||||
use std::sync::{Arc, Mutex, MutexGuard};
|
||||
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
use crate::async_io::{AsyncIo, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult};
|
||||
use crate::vhdx::{Result as VhdxResult, Vhdx};
|
||||
use crate::AsyncAdaptor;
|
||||
use std::collections::VecDeque;
|
||||
use std::fs::File;
|
||||
use std::sync::{Arc, Mutex, MutexGuard};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
pub struct VhdxDiskSync {
|
||||
vhdx_file: Arc<Mutex<Vhdx>>,
|
||||
|
||||
@@ -1,38 +1,32 @@
|
||||
[package]
|
||||
authors = ["The Chromium OS Authors"]
|
||||
edition = "2021"
|
||||
name = "devices"
|
||||
version = "0.1.0"
|
||||
authors = ["The Chromium OS Authors"]
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
acpi_tables = { workspace = true }
|
||||
anyhow = "1.0.94"
|
||||
acpi_tables = { git = "https://github.com/rust-vmm/acpi_tables", branch = "main" }
|
||||
anyhow = "1.0.75"
|
||||
arch = { path = "../arch" }
|
||||
bitflags = "2.6.0"
|
||||
byteorder = "1.5.0"
|
||||
bitflags = "2.4.1"
|
||||
byteorder = "1.4.3"
|
||||
event_monitor = { path = "../event_monitor" }
|
||||
hypervisor = { path = "../hypervisor" }
|
||||
libc = "0.2.167"
|
||||
log = "0.4.22"
|
||||
num_enum = "0.7.2"
|
||||
libc = "0.2.147"
|
||||
log = "0.4.17"
|
||||
pci = { path = "../pci" }
|
||||
serde = { version = "1.0.208", features = ["derive"] }
|
||||
thiserror = "2.0.6"
|
||||
thiserror = "1.0.40"
|
||||
tpm = { path = "../tpm" }
|
||||
versionize = "0.1.10"
|
||||
versionize_derive = "0.1.4"
|
||||
vm-allocator = { path = "../vm-allocator" }
|
||||
vm-device = { path = "../vm-device" }
|
||||
vm-memory = { workspace = true, features = [
|
||||
"backend-atomic",
|
||||
"backend-bitmap",
|
||||
"backend-mmap",
|
||||
] }
|
||||
vm-memory = "0.12.2"
|
||||
vm-migration = { path = "../vm-migration" }
|
||||
vmm-sys-util = { workspace = true }
|
||||
vmm-sys-util = "0.11.0"
|
||||
|
||||
[target.'cfg(any(target_arch = "aarch64", target_arch = "riscv64"))'.dependencies]
|
||||
[target.'cfg(target_arch = "aarch64")'.dependencies]
|
||||
arch = { path = "../arch" }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
kvm = ["arch/kvm"]
|
||||
pvmemcontrol = []
|
||||
|
||||
@@ -3,19 +3,17 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
use super::AcpiNotificationFlags;
|
||||
use acpi_tables::{aml, Aml, AmlSink};
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{Arc, Barrier};
|
||||
use std::thread;
|
||||
use std::time::Instant;
|
||||
|
||||
use acpi_tables::{aml, Aml, AmlSink};
|
||||
use vm_device::interrupt::InterruptSourceGroup;
|
||||
use vm_device::BusDevice;
|
||||
use vm_memory::GuestAddress;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
use super::AcpiNotificationFlags;
|
||||
|
||||
pub const GED_DEVICE_ACPI_SIZE: usize = 0x1;
|
||||
|
||||
/// A device for handling ACPI shutdown and reboot
|
||||
|
||||
@@ -1,144 +0,0 @@
|
||||
// Copyright © 2024 Institute of Software, CAS. All rights reserved.
|
||||
// Copyright 2020, ARM Limited.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||
|
||||
use super::interrupt_controller::{Error, InterruptController};
|
||||
extern crate arch;
|
||||
use std::result;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use arch::layout;
|
||||
use hypervisor::arch::riscv64::aia::{Vaia, VaiaConfig};
|
||||
use hypervisor::{AiaState, CpuState};
|
||||
use vm_device::interrupt::{
|
||||
InterruptIndex, InterruptManager, InterruptSourceConfig, InterruptSourceGroup,
|
||||
LegacyIrqSourceConfig, MsiIrqGroupConfig,
|
||||
};
|
||||
use vm_memory::address::Address;
|
||||
use vm_migration::{Migratable, Pausable, Snapshottable, Transportable};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
type Result<T> = result::Result<T, Error>;
|
||||
|
||||
// Reserve 32 IRQs for legacy devices.
|
||||
pub const IRQ_LEGACY_BASE: usize = layout::IRQ_BASE as usize;
|
||||
pub const IRQ_LEGACY_COUNT: usize = 32;
|
||||
// TODO: AIA snapshotting is not yet completed.
|
||||
pub const _AIA_SNAPSHOT_ID: &str = "";
|
||||
|
||||
// Aia (Advance Interrupt Architecture) struct provides all the functionality of a
|
||||
// AIA device. It wraps a hypervisor-emulated AIA device (Vaia) provided by the
|
||||
// `hypervisor` crate.
|
||||
// Aia struct also implements InterruptController to provide interrupt delivery
|
||||
// service.
|
||||
pub struct Aia {
|
||||
interrupt_source_group: Arc<dyn InterruptSourceGroup>,
|
||||
// The hypervisor agnostic virtual AIA
|
||||
vaia: Arc<Mutex<dyn Vaia>>,
|
||||
}
|
||||
|
||||
impl Aia {
|
||||
pub fn new(
|
||||
vcpu_count: u8,
|
||||
interrupt_manager: Arc<dyn InterruptManager<GroupConfig = MsiIrqGroupConfig>>,
|
||||
vm: Arc<dyn hypervisor::Vm>,
|
||||
) -> Result<Aia> {
|
||||
let interrupt_source_group = interrupt_manager
|
||||
.create_group(MsiIrqGroupConfig {
|
||||
base: IRQ_LEGACY_BASE as InterruptIndex,
|
||||
count: IRQ_LEGACY_COUNT as InterruptIndex,
|
||||
})
|
||||
.map_err(Error::CreateInterruptSourceGroup)?;
|
||||
|
||||
let vaia = vm
|
||||
.create_vaia(Aia::create_default_config(vcpu_count as u64))
|
||||
.map_err(Error::CreateAia)?;
|
||||
|
||||
let aia = Aia {
|
||||
interrupt_source_group,
|
||||
vaia,
|
||||
};
|
||||
aia.enable()?;
|
||||
|
||||
Ok(aia)
|
||||
}
|
||||
|
||||
pub fn restore_vaia(
|
||||
&mut self,
|
||||
state: Option<AiaState>,
|
||||
_saved_vcpu_states: &[CpuState],
|
||||
) -> Result<()> {
|
||||
self.vaia
|
||||
.clone()
|
||||
.lock()
|
||||
.unwrap()
|
||||
.set_state(&state.unwrap())
|
||||
.map_err(Error::RestoreAia)
|
||||
}
|
||||
|
||||
fn enable(&self) -> Result<()> {
|
||||
// Set irqfd for legacy interrupts
|
||||
self.interrupt_source_group
|
||||
.enable()
|
||||
.map_err(Error::EnableInterrupt)?;
|
||||
|
||||
// Set irq_routing for legacy interrupts.
|
||||
// irqchip: Hardcode to 0 as we support only 1 APLIC
|
||||
// pin: Use irq number as pin
|
||||
for i in IRQ_LEGACY_BASE..(IRQ_LEGACY_BASE + IRQ_LEGACY_COUNT) {
|
||||
let config = LegacyIrqSourceConfig {
|
||||
irqchip: 0,
|
||||
pin: (i - IRQ_LEGACY_BASE) as u32,
|
||||
};
|
||||
self.interrupt_source_group
|
||||
.update(
|
||||
i as InterruptIndex,
|
||||
InterruptSourceConfig::LegacyIrq(config),
|
||||
false,
|
||||
false,
|
||||
)
|
||||
.map_err(Error::EnableInterrupt)?;
|
||||
}
|
||||
|
||||
self.interrupt_source_group
|
||||
.set_gsi()
|
||||
.map_err(Error::EnableInterrupt)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Default config implied by arch::layout
|
||||
pub fn create_default_config(vcpu_count: u64) -> VaiaConfig {
|
||||
VaiaConfig {
|
||||
vcpu_count: vcpu_count as u32,
|
||||
aplic_addr: layout::APLIC_START.raw_value(),
|
||||
imsic_addr: layout::IMSIC_START.raw_value(),
|
||||
nr_irqs: layout::IRQ_NUM,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_vaia(&mut self) -> Result<Arc<Mutex<dyn Vaia>>> {
|
||||
Ok(self.vaia.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl InterruptController for Aia {
|
||||
// This should be called anytime an interrupt needs to be injected into the
|
||||
// running guest.
|
||||
fn service_irq(&mut self, irq: usize) -> Result<()> {
|
||||
self.interrupt_source_group
|
||||
.trigger(irq as InterruptIndex)
|
||||
.map_err(Error::TriggerInterrupt)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn notifier(&self, irq: usize) -> Option<EventFd> {
|
||||
self.interrupt_source_group.notifier(irq as InterruptIndex)
|
||||
}
|
||||
}
|
||||
|
||||
impl Snapshottable for Aia {}
|
||||
impl Pausable for Aia {}
|
||||
impl Transportable for Aia {}
|
||||
impl Migratable for Aia {}
|
||||
@@ -1,65 +0,0 @@
|
||||
// Copyright © 2023 Cyberus Technology
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
//! Module for [`DebugconState`].
|
||||
|
||||
use std::io;
|
||||
use std::io::Write;
|
||||
use std::sync::{Arc, Barrier};
|
||||
|
||||
use vm_device::BusDevice;
|
||||
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
|
||||
|
||||
/// I/O-port.
|
||||
pub const DEFAULT_PORT: u64 = 0xe9;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct DebugconState {}
|
||||
|
||||
/// Emulates a debug console similar to the QEMU debugcon device. This device
|
||||
/// is stateless and only prints the bytes (usually text) that are written to
|
||||
/// it.
|
||||
///
|
||||
/// This device is only available on x86.
|
||||
///
|
||||
/// Reference:
|
||||
/// - https://github.com/qemu/qemu/blob/master/hw/char/debugcon.c
|
||||
/// - https://phip1611.de/blog/how-to-use-qemus-debugcon-feature-and-write-to-a-file/
|
||||
pub struct DebugConsole {
|
||||
id: String,
|
||||
out: Box<dyn io::Write + Send>,
|
||||
}
|
||||
|
||||
impl DebugConsole {
|
||||
pub fn new(id: String, out: Box<dyn io::Write + Send>) -> Self {
|
||||
Self { id, out }
|
||||
}
|
||||
}
|
||||
|
||||
impl BusDevice for DebugConsole {
|
||||
fn read(&mut self, _base: u64, _offset: u64, _data: &mut [u8]) {}
|
||||
|
||||
fn write(&mut self, _base: u64, _offset: u64, data: &[u8]) -> Option<Arc<Barrier>> {
|
||||
if let Err(e) = self.out.write_all(data) {
|
||||
// unlikely
|
||||
error!("debug-console: failed writing data: {e:?}");
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl Snapshottable for DebugConsole {
|
||||
fn id(&self) -> String {
|
||||
self.id.clone()
|
||||
}
|
||||
|
||||
fn snapshot(&mut self) -> Result<Snapshot, MigratableError> {
|
||||
Snapshot::new_from_state(&())
|
||||
}
|
||||
}
|
||||
|
||||
impl Pausable for DebugConsole {}
|
||||
impl Transportable for DebugConsole {}
|
||||
impl Migratable for DebugConsole {}
|
||||
@@ -4,13 +4,14 @@
|
||||
|
||||
use super::interrupt_controller::{Error, InterruptController};
|
||||
extern crate arch;
|
||||
use std::result;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use anyhow::anyhow;
|
||||
use arch::layout;
|
||||
use hypervisor::arch::aarch64::gic::{Vgic, VgicConfig};
|
||||
use hypervisor::{CpuState, GicState};
|
||||
use hypervisor::{
|
||||
arch::aarch64::gic::{Vgic, VgicConfig},
|
||||
CpuState, GicState,
|
||||
};
|
||||
use std::result;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use vm_device::interrupt::{
|
||||
InterruptIndex, InterruptManager, InterruptSourceConfig, InterruptSourceGroup,
|
||||
LegacyIrqSourceConfig, MsiIrqGroupConfig,
|
||||
|
||||
@@ -1,55 +1,35 @@
|
||||
// Copyright © 2024 Institute of Software, CAS. All rights reserved.
|
||||
// Copyright 2020, ARM Limited.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||
|
||||
use std::{io, result};
|
||||
|
||||
use thiserror::Error;
|
||||
use std::io;
|
||||
use std::result;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Invalid trigger mode.
|
||||
#[error("Invalid trigger mode")]
|
||||
InvalidTriggerMode,
|
||||
/// Invalid delivery mode.
|
||||
#[error("Invalid delivery mode")]
|
||||
InvalidDeliveryMode,
|
||||
/// Failed creating the interrupt source group.
|
||||
#[error("Failed creating the interrupt source group: {0}")]
|
||||
CreateInterruptSourceGroup(io::Error),
|
||||
/// Failed triggering the interrupt.
|
||||
#[error("Failed triggering the interrupt: {0}")]
|
||||
TriggerInterrupt(io::Error),
|
||||
/// Failed masking the interrupt.
|
||||
#[error("Failed masking the interrupt: {0}")]
|
||||
MaskInterrupt(io::Error),
|
||||
/// Failed unmasking the interrupt.
|
||||
#[error("Failed unmasking the interrupt: {0}")]
|
||||
UnmaskInterrupt(io::Error),
|
||||
/// Failed updating the interrupt.
|
||||
#[error("Failed updating the interrupt: {0}")]
|
||||
UpdateInterrupt(io::Error),
|
||||
/// Failed enabling the interrupt.
|
||||
#[error("Failed enabling the interrupt: {0}")]
|
||||
EnableInterrupt(io::Error),
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
/// Failed creating GIC device.
|
||||
#[error("Failed creating GIC device: {0}")]
|
||||
CreateGic(hypervisor::HypervisorVmError),
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
/// Failed restoring GIC device.
|
||||
#[error("Failed restoring GIC device: {0}")]
|
||||
RestoreGic(hypervisor::arch::aarch64::gic::Error),
|
||||
#[cfg(target_arch = "riscv64")]
|
||||
/// Failed creating AIA device.
|
||||
#[error("Failed creating AIA device: {0}")]
|
||||
CreateAia(hypervisor::HypervisorVmError),
|
||||
#[cfg(target_arch = "riscv64")]
|
||||
/// Failed restoring AIA device.
|
||||
#[error("Failed restoring AIA device: {0}")]
|
||||
RestoreAia(hypervisor::arch::riscv64::aia::Error),
|
||||
}
|
||||
|
||||
type Result<T> = result::Result<T, Error>;
|
||||
@@ -76,7 +56,7 @@ pub struct MsiMessage {
|
||||
// Introduce trait InterruptController to uniform the interrupt
|
||||
// service provided for devices.
|
||||
// Device manager uses this trait without caring whether it is a
|
||||
// IOAPIC (X86), GIC (Arm) or AIA (RISC-V).
|
||||
// IOAPIC (X86) or GIC (Arm).
|
||||
pub trait InterruptController: Send {
|
||||
fn service_irq(&mut self, irq: usize) -> Result<()>;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
|
||||
@@ -9,22 +9,23 @@
|
||||
// Implementation of an intel 82093AA Input/Output Advanced Programmable Interrupt Controller
|
||||
// See https://pdos.csail.mit.edu/6.828/2016/readings/ia32/ioapic.pdf for a specification.
|
||||
|
||||
use super::interrupt_controller::{Error, InterruptController};
|
||||
use byteorder::{ByteOrder, LittleEndian};
|
||||
use std::result;
|
||||
use std::sync::{Arc, Barrier};
|
||||
|
||||
use byteorder::{ByteOrder, LittleEndian};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use versionize::{VersionMap, Versionize, VersionizeResult};
|
||||
use versionize_derive::Versionize;
|
||||
use vm_device::interrupt::{
|
||||
InterruptIndex, InterruptManager, InterruptSourceConfig, InterruptSourceGroup,
|
||||
MsiIrqGroupConfig, MsiIrqSourceConfig,
|
||||
};
|
||||
use vm_device::BusDevice;
|
||||
use vm_memory::GuestAddress;
|
||||
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
|
||||
use vm_migration::{
|
||||
Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable, VersionMapped,
|
||||
};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
use super::interrupt_controller::{Error, InterruptController};
|
||||
|
||||
type Result<T> = result::Result<T, Error>;
|
||||
|
||||
// I/O REDIRECTION TABLE REGISTER
|
||||
@@ -135,7 +136,7 @@ pub struct Ioapic {
|
||||
interrupt_source_group: Arc<dyn InterruptSourceGroup>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Versionize)]
|
||||
pub struct IoapicState {
|
||||
id_reg: u32,
|
||||
reg_sel: u32,
|
||||
@@ -143,6 +144,7 @@ pub struct IoapicState {
|
||||
used_entries: [bool; NUM_IOAPIC_PINS],
|
||||
apic_address: u64,
|
||||
}
|
||||
impl VersionMapped for IoapicState {}
|
||||
|
||||
impl BusDevice for Ioapic {
|
||||
fn read(&mut self, _base: u64, offset: u64, data: &mut [u8]) {
|
||||
@@ -346,9 +348,9 @@ impl Ioapic {
|
||||
|
||||
// Generate MSI message address
|
||||
let low_addr: u32 = self.apic_address.0 as u32
|
||||
| (u32::from(destination_id) << 12)
|
||||
| (u32::from(redirection_hint) << 3)
|
||||
| (u32::from(destination_mode) << 2);
|
||||
| u32::from(destination_id) << 12
|
||||
| u32::from(redirection_hint) << 3
|
||||
| u32::from(destination_mode) << 2;
|
||||
|
||||
// Validate Trigger Mode value
|
||||
let trigger_mode = trigger_mode(entry);
|
||||
@@ -372,9 +374,9 @@ impl Ioapic {
|
||||
}
|
||||
|
||||
// Generate MSI message data
|
||||
let data: u32 = (u32::from(trigger_mode) << 15)
|
||||
| (u32::from(remote_irr(entry)) << 14)
|
||||
| (u32::from(delivery_mode) << 8)
|
||||
let data: u32 = u32::from(trigger_mode) << 15
|
||||
| u32::from(remote_irr(entry)) << 14
|
||||
| u32::from(delivery_mode) << 8
|
||||
| u32::from(vector(entry));
|
||||
|
||||
let config = MsiIrqSourceConfig {
|
||||
@@ -418,7 +420,7 @@ impl InterruptController for Ioapic {
|
||||
self.interrupt_source_group
|
||||
.trigger(irq as InterruptIndex)
|
||||
.map_err(Error::TriggerInterrupt)?;
|
||||
debug!("Interrupt {irq} successfully delivered");
|
||||
debug!("Interrupt successfully delivered");
|
||||
|
||||
// If trigger mode is level sensitive, set the Remote IRR bit.
|
||||
// It will be cleared when the EOI is received.
|
||||
@@ -442,7 +444,7 @@ impl Snapshottable for Ioapic {
|
||||
}
|
||||
|
||||
fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
|
||||
Snapshot::new_from_state(&self.state())
|
||||
Snapshot::new_from_versioned_state(&self.state())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
// Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||
|
||||
use libc::{clock_gettime, gmtime_r, timespec, tm, CLOCK_REALTIME};
|
||||
use std::cmp::min;
|
||||
use std::mem;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{Arc, Barrier};
|
||||
use std::{mem, thread};
|
||||
use std::thread;
|
||||
use vm_device::BusDevice;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
// https://github.com/rust-lang/libc/issues/1848
|
||||
#[cfg_attr(target_env = "musl", allow(deprecated))]
|
||||
use libc::time_t;
|
||||
use libc::{clock_gettime, gmtime_r, timespec, tm, CLOCK_REALTIME};
|
||||
use vm_device::BusDevice;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
const INDEX_MASK: u8 = 0x7f;
|
||||
const INDEX_OFFSET: u64 = 0x0;
|
||||
@@ -152,7 +151,7 @@ impl BusDevice for Cmos {
|
||||
0x08 => to_bcd(month as u8),
|
||||
0x09 => to_bcd((year % 100) as u8),
|
||||
// Bit 5 for 32kHz clock. Bit 7 for Update in Progress
|
||||
0x0a => (1 << 5) | ((update_in_progress as u8) << 7),
|
||||
0x0a => 1 << 5 | (update_in_progress as u8) << 7,
|
||||
// Bit 0-6 are reserved and must be 0.
|
||||
// Bit 7 must be 1 (CMOS has power)
|
||||
0x0d => 1 << 7,
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
use std::fmt;
|
||||
use std::time::Instant;
|
||||
|
||||
use vm_device::BusDevice;
|
||||
|
||||
/// Debug I/O port, see:
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
//
|
||||
|
||||
use std::sync::{Arc, Barrier};
|
||||
|
||||
use vm_device::BusDevice;
|
||||
|
||||
/// Provides firmware debug output via I/O port controls
|
||||
|
||||
@@ -7,16 +7,17 @@
|
||||
//! This module implements an ARM PrimeCell General Purpose Input/Output(PL061) to support gracefully poweroff microvm from external.
|
||||
//!
|
||||
|
||||
use crate::{read_le_u32, write_le_u32};
|
||||
use std::result;
|
||||
use std::sync::{Arc, Barrier};
|
||||
use std::{io, result};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
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};
|
||||
|
||||
use crate::{read_le_u32, write_le_u32};
|
||||
use vm_migration::{
|
||||
Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable, VersionMapped,
|
||||
};
|
||||
|
||||
const OFS_DATA: u64 = 0x400; // Data Register
|
||||
const GPIODIR: u64 = 0x400; // Direction Register
|
||||
@@ -39,18 +40,29 @@ const GPIO_ID_HIGH: u64 = 0x1000;
|
||||
|
||||
const N_GPIOS: u32 = 8;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
#[error("Bad Write Offset: {0}")]
|
||||
BadWriteOffset(u64),
|
||||
#[error("GPIO interrupt disabled by guest driver.")]
|
||||
GpioInterruptDisabled,
|
||||
#[error("Could not trigger GPIO interrupt: {0}.")]
|
||||
GpioInterruptFailure(io::Error),
|
||||
#[error("Invalid GPIO Input key triggered: {0}.")]
|
||||
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 triggered: {key}.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type Result<T> = result::Result<T, Error>;
|
||||
|
||||
/// A GPIO device following the PL061 specification.
|
||||
@@ -77,7 +89,7 @@ pub struct Gpio {
|
||||
interrupt: Arc<dyn InterruptSourceGroup>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Versionize)]
|
||||
pub struct GpioState {
|
||||
data: u32,
|
||||
old_in_data: u32,
|
||||
@@ -90,6 +102,8 @@ pub struct GpioState {
|
||||
afsel: u32,
|
||||
}
|
||||
|
||||
impl VersionMapped for GpioState {}
|
||||
|
||||
impl Gpio {
|
||||
/// Constructs an PL061 GPIO device.
|
||||
pub fn new(
|
||||
@@ -314,7 +328,7 @@ impl Snapshottable for Gpio {
|
||||
}
|
||||
|
||||
fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
|
||||
Snapshot::new_from_state(&self.state())
|
||||
Snapshot::new_from_versioned_state(&self.state())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -324,11 +338,12 @@ impl Migratable for Gpio {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{read_le_u32, write_le_u32};
|
||||
use std::sync::Arc;
|
||||
use vm_device::interrupt::{InterruptIndex, InterruptSourceConfig};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
use super::*;
|
||||
|
||||
const GPIO_NAME: &str = "gpio";
|
||||
const LEGACY_GPIO_MAPPED_IO_START: u64 = 0x0902_0000;
|
||||
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
// Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{Arc, Barrier};
|
||||
use std::sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc, Barrier,
|
||||
};
|
||||
use std::thread;
|
||||
|
||||
use vm_device::BusDevice;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
|
||||
@@ -24,13 +24,14 @@ pub use self::cmos::Cmos;
|
||||
pub use self::debug_port::DebugPort;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub use self::fwdebug::FwDebugDevice;
|
||||
pub use self::i8042::I8042Device;
|
||||
pub use self::serial::Serial;
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub use self::gpio_pl061::Error as GpioDeviceError;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub use self::gpio_pl061::Gpio;
|
||||
pub use self::i8042::I8042Device;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub use self::rtc_pl031::Rtc;
|
||||
pub use self::serial::Serial;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub use self::uart_pl011::Pl011;
|
||||
|
||||
@@ -8,16 +8,14 @@
|
||||
//! This is achieved by generating an interrupt signal after counting for a programmed number of cycles of
|
||||
//! a real-time clock input.
|
||||
//!
|
||||
use crate::{read_le_u32, write_le_u32};
|
||||
use std::fmt;
|
||||
use std::sync::{Arc, Barrier};
|
||||
use std::time::Instant;
|
||||
use std::{io, result};
|
||||
|
||||
use thiserror::Error;
|
||||
use vm_device::interrupt::InterruptSourceGroup;
|
||||
use vm_device::BusDevice;
|
||||
|
||||
use crate::{read_le_u32, write_le_u32};
|
||||
|
||||
// As you can see in https://static.docs.arm.com/ddi0224/c/real_time_clock_pl031_r1p3_technical_reference_manual_DDI0224C.pdf
|
||||
// at section 3.2 Summary of RTC registers, the total size occupied by this device is 0x000 -> 0xFFC + 4 = 0x1000.
|
||||
// From 0x0 to 0x1C we have following registers:
|
||||
@@ -41,14 +39,21 @@ const AMBA_ID_HIGH: u64 = 0x1000;
|
||||
/// Constant to convert seconds to nanoseconds.
|
||||
pub const NANOS_PER_SECOND: u64 = 1_000_000_000;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
#[error("Bad Write Offset: {0}")]
|
||||
BadWriteOffset(u64),
|
||||
#[error("Failed to trigger interrupt: {0}")]
|
||||
InterruptFailure(io::Error),
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Error::BadWriteOffset(offset) => write!(f, "Bad Write Offset: {offset}"),
|
||||
Error::InterruptFailure(e) => write!(f, "Failed to trigger interrupt: {e}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type Result<T> = result::Result<T, Error>;
|
||||
|
||||
/// Wrapper over `libc::clockid_t` to specify Linux Kernel clock source.
|
||||
@@ -74,6 +79,98 @@ impl From<ClockType> for libc::clockid_t {
|
||||
}
|
||||
}
|
||||
|
||||
/// Structure representing the date in local time with nanosecond precision.
|
||||
pub struct LocalTime {
|
||||
/// Seconds in current minute.
|
||||
sec: i32,
|
||||
/// Minutes in current hour.
|
||||
min: i32,
|
||||
/// Hours in current day, 24H format.
|
||||
hour: i32,
|
||||
/// Days in current month.
|
||||
mday: i32,
|
||||
/// Months in current year.
|
||||
mon: i32,
|
||||
/// Years passed since 1900 BC.
|
||||
year: i32,
|
||||
/// Nanoseconds in current second.
|
||||
nsec: i64,
|
||||
}
|
||||
|
||||
impl LocalTime {
|
||||
/// Returns the [LocalTime](struct.LocalTime.html) structure for the calling moment.
|
||||
#[cfg(test)]
|
||||
pub fn now() -> LocalTime {
|
||||
let mut timespec = libc::timespec {
|
||||
tv_sec: 0,
|
||||
tv_nsec: 0,
|
||||
};
|
||||
let mut tm: libc::tm = libc::tm {
|
||||
tm_sec: 0,
|
||||
tm_min: 0,
|
||||
tm_hour: 0,
|
||||
tm_mday: 0,
|
||||
tm_mon: 0,
|
||||
tm_year: 0,
|
||||
tm_wday: 0,
|
||||
tm_yday: 0,
|
||||
tm_isdst: 0,
|
||||
tm_gmtoff: 0,
|
||||
tm_zone: std::ptr::null(),
|
||||
};
|
||||
|
||||
// SAFETY: the parameters are valid.
|
||||
unsafe {
|
||||
libc::clock_gettime(libc::CLOCK_REALTIME, &mut timespec);
|
||||
libc::localtime_r(×pec.tv_sec, &mut tm);
|
||||
}
|
||||
|
||||
LocalTime {
|
||||
sec: tm.tm_sec,
|
||||
min: tm.tm_min,
|
||||
hour: tm.tm_hour,
|
||||
mday: tm.tm_mday,
|
||||
mon: tm.tm_mon,
|
||||
year: tm.tm_year,
|
||||
nsec: timespec.tv_nsec,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for LocalTime {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}-{:02}-{:02}T{:02}:{:02}:{:02}.{:09}",
|
||||
self.year + 1900,
|
||||
self.mon + 1,
|
||||
self.mday,
|
||||
self.hour,
|
||||
self.min,
|
||||
self.sec,
|
||||
self.nsec
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Holds a micro-second resolution timestamp with both the real time and cpu time.
|
||||
#[derive(Clone)]
|
||||
pub struct TimestampUs {
|
||||
/// Real time in microseconds.
|
||||
pub time_us: u64,
|
||||
/// Cpu time in microseconds.
|
||||
pub cputime_us: u64,
|
||||
}
|
||||
|
||||
impl Default for TimestampUs {
|
||||
fn default() -> TimestampUs {
|
||||
TimestampUs {
|
||||
time_us: get_time(ClockType::Monotonic) / 1000,
|
||||
cputime_us: get_time(ClockType::ProcessCpu) / 1000,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a timestamp in nanoseconds based on the provided clock type.
|
||||
///
|
||||
/// # Arguments
|
||||
@@ -230,81 +327,17 @@ impl BusDevice for Rtc {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
read_be_u16, read_be_u32, read_le_i32, read_le_u16, read_le_u32, read_le_u64, write_be_u16,
|
||||
write_be_u32, write_le_i32, write_le_u16, write_le_u32, write_le_u64,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use vm_device::interrupt::{InterruptIndex, InterruptSourceConfig};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
use super::*;
|
||||
use crate::{
|
||||
read_be_u16, read_be_u32, read_le_i32, read_le_u16, read_le_u64, write_be_u16,
|
||||
write_be_u32, write_le_i32, write_le_u16, write_le_u64,
|
||||
};
|
||||
|
||||
const LEGACY_RTC_MAPPED_IO_START: u64 = 0x0901_0000;
|
||||
|
||||
struct LocalTime {
|
||||
sec: i32,
|
||||
min: i32,
|
||||
hour: i32,
|
||||
mday: i32,
|
||||
mon: i32,
|
||||
year: i32,
|
||||
nsec: i64,
|
||||
}
|
||||
|
||||
impl LocalTime {
|
||||
fn now() -> LocalTime {
|
||||
let mut timespec = libc::timespec {
|
||||
tv_sec: 0,
|
||||
tv_nsec: 0,
|
||||
};
|
||||
let mut tm: libc::tm = libc::tm {
|
||||
tm_sec: 0,
|
||||
tm_min: 0,
|
||||
tm_hour: 0,
|
||||
tm_mday: 0,
|
||||
tm_mon: 0,
|
||||
tm_year: 0,
|
||||
tm_wday: 0,
|
||||
tm_yday: 0,
|
||||
tm_isdst: 0,
|
||||
tm_gmtoff: 0,
|
||||
tm_zone: std::ptr::null(),
|
||||
};
|
||||
|
||||
// SAFETY: the parameters are valid.
|
||||
unsafe {
|
||||
libc::clock_gettime(libc::CLOCK_REALTIME, &mut timespec);
|
||||
libc::localtime_r(×pec.tv_sec, &mut tm);
|
||||
}
|
||||
|
||||
LocalTime {
|
||||
sec: tm.tm_sec,
|
||||
min: tm.tm_min,
|
||||
hour: tm.tm_hour,
|
||||
mday: tm.tm_mday,
|
||||
mon: tm.tm_mon,
|
||||
year: tm.tm_year,
|
||||
nsec: timespec.tv_nsec,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for LocalTime {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}-{:02}-{:02}T{:02}:{:02}:{:02}.{:09}",
|
||||
self.year + 1900,
|
||||
self.mon + 1,
|
||||
self.mday,
|
||||
self.hour,
|
||||
self.min,
|
||||
self.sec,
|
||||
self.nsec
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_time() {
|
||||
for _ in 0..1000 {
|
||||
|
||||
@@ -8,11 +8,13 @@
|
||||
use std::collections::VecDeque;
|
||||
use std::sync::{Arc, Barrier};
|
||||
use std::{io, result};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
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};
|
||||
use vm_migration::{
|
||||
Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable, VersionMapped,
|
||||
};
|
||||
use vmm_sys_util::errno::Result;
|
||||
|
||||
const LOOP_SIZE: usize = 0x40;
|
||||
@@ -72,7 +74,7 @@ pub struct Serial {
|
||||
out: Option<Box<dyn io::Write + Send>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Versionize)]
|
||||
pub struct SerialState {
|
||||
interrupt_enable: u8,
|
||||
interrupt_identification: u8,
|
||||
@@ -84,6 +86,7 @@ pub struct SerialState {
|
||||
baud_divisor: u16,
|
||||
in_buffer: Vec<u8>,
|
||||
}
|
||||
impl VersionMapped for SerialState {}
|
||||
|
||||
impl Serial {
|
||||
pub fn new(
|
||||
@@ -331,7 +334,7 @@ impl Snapshottable for Serial {
|
||||
}
|
||||
|
||||
fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
|
||||
Snapshot::new_from_state(&self.state())
|
||||
Snapshot::new_from_versioned_state(&self.state())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -341,13 +344,12 @@ impl Migratable for Serial {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::sync::Mutex;
|
||||
|
||||
use super::*;
|
||||
use std::io;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use vm_device::interrupt::{InterruptIndex, InterruptSourceConfig};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
use super::*;
|
||||
|
||||
const SERIAL_NAME: &str = "serial";
|
||||
|
||||
struct TestInterrupt {
|
||||
@@ -414,11 +416,14 @@ mod tests {
|
||||
None,
|
||||
);
|
||||
|
||||
serial.write(0, DATA as u64, b"xy");
|
||||
serial.write(0, DATA as u64, b"a");
|
||||
serial.write(0, DATA as u64, b"b");
|
||||
serial.write(0, DATA as u64, b"c");
|
||||
assert_eq!(serial_out.buf.lock().unwrap().as_slice(), b"abc");
|
||||
serial.write(0, DATA as u64, &[b'x', b'y']);
|
||||
serial.write(0, DATA as u64, &[b'a']);
|
||||
serial.write(0, DATA as u64, &[b'b']);
|
||||
serial.write(0, DATA as u64, &[b'c']);
|
||||
assert_eq!(
|
||||
serial_out.buf.lock().unwrap().as_slice(),
|
||||
&[b'a', b'b', b'c']
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -434,9 +439,9 @@ mod tests {
|
||||
|
||||
// write 1 to the interrupt event fd, so that read doesn't block in case the event fd
|
||||
// counter doesn't change (for 0 it blocks)
|
||||
intr_evt.write(1).unwrap();
|
||||
assert!(intr_evt.write(1).is_ok());
|
||||
serial.write(0, IER as u64, &[IER_RECV_BIT]);
|
||||
serial.queue_input_bytes(b"abc").unwrap();
|
||||
serial.queue_input_bytes(&[b'a', b'b', b'c']).unwrap();
|
||||
|
||||
assert_eq!(intr_evt.read().unwrap(), 2);
|
||||
|
||||
@@ -471,9 +476,9 @@ mod tests {
|
||||
|
||||
// write 1 to the interrupt event fd, so that read doesn't block in case the event fd
|
||||
// counter doesn't change (for 0 it blocks)
|
||||
intr_evt.write(1).unwrap();
|
||||
assert!(intr_evt.write(1).is_ok());
|
||||
serial.write(0, IER as u64, &[IER_THR_BIT]);
|
||||
serial.write(0, DATA as u64, b"a");
|
||||
serial.write(0, DATA as u64, &[b'a']);
|
||||
|
||||
assert_eq!(intr_evt.read().unwrap(), 2);
|
||||
let mut data = [0u8];
|
||||
@@ -515,9 +520,9 @@ mod tests {
|
||||
);
|
||||
|
||||
serial.write(0, MCR as u64, &[MCR_LOOP_BIT]);
|
||||
serial.write(0, DATA as u64, b"a");
|
||||
serial.write(0, DATA as u64, b"b");
|
||||
serial.write(0, DATA as u64, b"c");
|
||||
serial.write(0, DATA as u64, &[b'a']);
|
||||
serial.write(0, DATA as u64, &[b'b']);
|
||||
serial.write(0, DATA as u64, &[b'c']);
|
||||
|
||||
let mut data = [0u8];
|
||||
serial.read(0, MSR as u64, &mut data[..]);
|
||||
|
||||
@@ -6,18 +6,19 @@
|
||||
//! This module implements an ARM PrimeCell UART(PL011).
|
||||
//!
|
||||
|
||||
use crate::{read_le_u32, write_le_u32};
|
||||
use std::collections::VecDeque;
|
||||
use std::fmt;
|
||||
use std::sync::{Arc, Barrier};
|
||||
use std::time::Instant;
|
||||
use std::{io, result};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
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};
|
||||
|
||||
use crate::{read_le_u32, write_le_u32};
|
||||
use vm_migration::{
|
||||
Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable, VersionMapped,
|
||||
};
|
||||
|
||||
/* Registers */
|
||||
const UARTDR: u64 = 0;
|
||||
@@ -47,20 +48,27 @@ const PL011_ID: [u8; 8] = [0x11, 0x10, 0x14, 0x00, 0x0d, 0xf0, 0x05, 0xb1];
|
||||
const AMBA_ID_LOW: u64 = 0x3f8;
|
||||
const AMBA_ID_HIGH: u64 = 0x401;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
#[error("pl011_write: Bad Write Offset: {0}")]
|
||||
BadWriteOffset(u64),
|
||||
#[error("pl011: DMA not implemented.")]
|
||||
DmaNotImplemented,
|
||||
#[error("Failed to trigger interrupt: {0}")]
|
||||
InterruptFailure(io::Error),
|
||||
#[error("Failed to write: {0}")]
|
||||
WriteAllFailure(io::Error),
|
||||
#[error("Failed to flush: {0}")]
|
||||
FlushFailure(io::Error),
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Error::BadWriteOffset(offset) => write!(f, "pl011_write: Bad Write Offset: {offset}"),
|
||||
Error::DmaNotImplemented => write!(f, "pl011: DMA not implemented."),
|
||||
Error::InterruptFailure(e) => write!(f, "Failed to trigger interrupt: {e}"),
|
||||
Error::WriteAllFailure(e) => write!(f, "Failed to write: {e}"),
|
||||
Error::FlushFailure(e) => write!(f, "Failed to flush: {e}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type Result<T> = result::Result<T, Error>;
|
||||
|
||||
/// A PL011 device following the PL011 specification.
|
||||
@@ -86,7 +94,7 @@ pub struct Pl011 {
|
||||
timestamp: std::time::Instant,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Versionize)]
|
||||
pub struct Pl011State {
|
||||
flags: u32,
|
||||
lcr: u32,
|
||||
@@ -105,6 +113,8 @@ pub struct Pl011State {
|
||||
read_trigger: u32,
|
||||
}
|
||||
|
||||
impl VersionMapped for Pl011State {}
|
||||
|
||||
impl Pl011 {
|
||||
/// Constructs an AMBA PL011 UART device.
|
||||
pub fn new(
|
||||
@@ -444,7 +454,7 @@ impl Snapshottable for Pl011 {
|
||||
}
|
||||
|
||||
fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
|
||||
Snapshot::new_from_state(&self.state())
|
||||
Snapshot::new_from_versioned_state(&self.state())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -454,13 +464,12 @@ impl Migratable for Pl011 {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::sync::Mutex;
|
||||
|
||||
use super::*;
|
||||
use std::io;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use vm_device::interrupt::{InterruptIndex, InterruptSourceConfig};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
use super::*;
|
||||
|
||||
const SERIAL_NAME: &str = "serial";
|
||||
|
||||
struct TestInterrupt {
|
||||
@@ -528,11 +537,14 @@ mod tests {
|
||||
None,
|
||||
);
|
||||
|
||||
pl011.write(0, UARTDR, b"xy");
|
||||
pl011.write(0, UARTDR, b"a");
|
||||
pl011.write(0, UARTDR, b"b");
|
||||
pl011.write(0, UARTDR, b"c");
|
||||
assert_eq!(pl011_out.buf.lock().unwrap().as_slice(), b"xabc");
|
||||
pl011.write(0, UARTDR, &[b'x', b'y']);
|
||||
pl011.write(0, UARTDR, &[b'a']);
|
||||
pl011.write(0, UARTDR, &[b'b']);
|
||||
pl011.write(0, UARTDR, &[b'c']);
|
||||
assert_eq!(
|
||||
pl011_out.buf.lock().unwrap().as_slice(),
|
||||
&[b'x', b'a', b'b', b'c']
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -549,8 +561,8 @@ mod tests {
|
||||
|
||||
// write 1 to the interrupt event fd, so that read doesn't block in case the event fd
|
||||
// counter doesn't change (for 0 it blocks)
|
||||
intr_evt.write(1).unwrap();
|
||||
pl011.queue_input_bytes(b"abc").unwrap();
|
||||
assert!(intr_evt.write(1).is_ok());
|
||||
pl011.queue_input_bytes(&[b'a', b'b', b'c']).unwrap();
|
||||
|
||||
assert_eq!(intr_evt.read().unwrap(), 2);
|
||||
|
||||
|
||||
@@ -15,21 +15,13 @@ extern crate event_monitor;
|
||||
extern crate log;
|
||||
|
||||
pub mod acpi;
|
||||
#[cfg(target_arch = "riscv64")]
|
||||
pub mod aia;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub mod debug_console;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub mod gic;
|
||||
pub mod interrupt_controller;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub mod ioapic;
|
||||
pub mod legacy;
|
||||
#[cfg(feature = "pvmemcontrol")]
|
||||
pub mod pvmemcontrol;
|
||||
pub mod pvpanic;
|
||||
// TODO: TPM is not yet supported
|
||||
#[cfg(not(target_arch = "riscv64"))]
|
||||
pub mod tpm;
|
||||
|
||||
pub use self::acpi::{AcpiGedDevice, AcpiPmTimerDevice, AcpiShutdownDevice};
|
||||
|
||||
@@ -1,820 +0,0 @@
|
||||
// Copyright © 2024 Google LLC
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::CString;
|
||||
use std::sync::{Arc, Barrier, Mutex, RwLock};
|
||||
use std::{io, result};
|
||||
|
||||
use num_enum::TryFromPrimitive;
|
||||
use pci::{
|
||||
BarReprogrammingParams, PciBarConfiguration, PciBarPrefetchable, PciBarRegionType,
|
||||
PciClassCode, PciConfiguration, PciDevice, PciDeviceError, PciHeaderType, PciSubclass,
|
||||
};
|
||||
use thiserror::Error;
|
||||
use vm_allocator::page_size::get_page_size;
|
||||
use vm_allocator::{AddressAllocator, SystemAllocator};
|
||||
use vm_device::{BusDeviceSync, Resource};
|
||||
use vm_memory::bitmap::AtomicBitmap;
|
||||
use vm_memory::{
|
||||
Address, ByteValued, Bytes, GuestAddress, GuestAddressSpace, GuestMemory, GuestMemoryAtomic,
|
||||
GuestMemoryError, GuestMemoryMmap, Le32, Le64,
|
||||
};
|
||||
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
|
||||
|
||||
const PVMEMCONTROL_VENDOR_ID: u16 = 0x1ae0;
|
||||
const PVMEMCONTROL_DEVICE_ID: u16 = 0x0087;
|
||||
|
||||
const PVMEMCONTROL_SUBSYSTEM_VENDOR_ID: u16 = 0x1ae0;
|
||||
const PVMEMCONTROL_SUBSYSTEM_ID: u16 = 0x011F;
|
||||
|
||||
const MAJOR_VERSION: u64 = 1;
|
||||
const MINOR_VERSION: u64 = 0;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Error {
|
||||
// device errors
|
||||
#[error("Guest gave us bad memory addresses: {0}")]
|
||||
GuestMemory(#[source] GuestMemoryError),
|
||||
#[error("Guest sent us invalid request")]
|
||||
InvalidRequest,
|
||||
|
||||
#[error("Guest sent us invalid command: {0}")]
|
||||
InvalidCommand(u32),
|
||||
#[error("Guest sent us invalid connection: {0}")]
|
||||
InvalidConnection(u32),
|
||||
|
||||
// pvmemcontrol errors
|
||||
#[error("Request contains invalid arguments: {0}")]
|
||||
InvalidArgument(u64),
|
||||
#[error("Unknown function code: {0}")]
|
||||
UnknownFunctionCode(u64),
|
||||
#[error("Libc call fail: {0}")]
|
||||
LibcFail(#[source] std::io::Error),
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
enum PvmemcontrolSubclass {
|
||||
Other = 0x80,
|
||||
}
|
||||
|
||||
impl PciSubclass for PvmemcontrolSubclass {
|
||||
fn get_register_value(&self) -> u8 {
|
||||
*self as u8
|
||||
}
|
||||
}
|
||||
|
||||
/// commands have 0 as the most significant byte
|
||||
#[repr(u32)]
|
||||
#[derive(PartialEq, Eq, Copy, Clone, TryFromPrimitive)]
|
||||
enum PvmemcontrolTransportCommand {
|
||||
Reset = 0x060f_e6d2,
|
||||
Register = 0x0e35_9539,
|
||||
Ready = 0x0ca8_d227,
|
||||
Disconnect = 0x030f_5da0,
|
||||
Ack = 0x03cf_5196,
|
||||
Error = 0x01fb_a249,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
struct PvmemcontrolTransportRegister {
|
||||
buf_phys_addr: Le64,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
struct PvmemcontrolTransportRegisterResponse {
|
||||
command: Le32,
|
||||
_padding: u32,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
union PvmemcontrolTransportUnion {
|
||||
register: PvmemcontrolTransportRegister,
|
||||
register_response: PvmemcontrolTransportRegisterResponse,
|
||||
unit: (),
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
struct PvmemcontrolTransport {
|
||||
payload: PvmemcontrolTransportUnion,
|
||||
command: PvmemcontrolTransportCommand,
|
||||
}
|
||||
|
||||
const PVMEMCONTROL_DEVICE_MMIO_SIZE: u64 = std::mem::size_of::<PvmemcontrolTransport>() as u64;
|
||||
const PVMEMCONTROL_DEVICE_MMIO_ALIGN: u64 = std::mem::align_of::<PvmemcontrolTransport>() as u64;
|
||||
|
||||
impl PvmemcontrolTransport {
|
||||
fn ack() -> Self {
|
||||
PvmemcontrolTransport {
|
||||
payload: PvmemcontrolTransportUnion { unit: () },
|
||||
command: PvmemcontrolTransportCommand::Ack,
|
||||
}
|
||||
}
|
||||
|
||||
fn error() -> Self {
|
||||
PvmemcontrolTransport {
|
||||
payload: PvmemcontrolTransportUnion { unit: () },
|
||||
command: PvmemcontrolTransportCommand::Error,
|
||||
}
|
||||
}
|
||||
|
||||
fn register_response(command: u32) -> Self {
|
||||
PvmemcontrolTransport {
|
||||
payload: PvmemcontrolTransportUnion {
|
||||
register_response: PvmemcontrolTransportRegisterResponse {
|
||||
command: command.into(),
|
||||
_padding: 0,
|
||||
},
|
||||
},
|
||||
command: PvmemcontrolTransportCommand::Ack,
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn as_register(self) -> PvmemcontrolTransportRegister {
|
||||
self.payload.register
|
||||
}
|
||||
}
|
||||
|
||||
// SAFETY: Contains no references and does not have compiler-inserted padding
|
||||
unsafe impl ByteValued for PvmemcontrolTransportUnion {}
|
||||
// SAFETY: Contains no references and does not have compiler-inserted padding
|
||||
unsafe impl ByteValued for PvmemcontrolTransport {}
|
||||
|
||||
#[repr(u64)]
|
||||
#[derive(Copy, Clone, TryFromPrimitive, Debug)]
|
||||
enum FunctionCode {
|
||||
Info = 0,
|
||||
Dontneed = 1,
|
||||
Remove = 2,
|
||||
Free = 3,
|
||||
Pageout = 4,
|
||||
Dontdump = 5,
|
||||
SetVMAAnonName = 6,
|
||||
Mlock = 7,
|
||||
Munlock = 8,
|
||||
MprotectNone = 9,
|
||||
MprotectR = 10,
|
||||
MprotectW = 11,
|
||||
MprotectRW = 12,
|
||||
Mergeable = 13,
|
||||
Unmergeable = 14,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
struct PvmemcontrolReq {
|
||||
func_code: Le64,
|
||||
addr: Le64,
|
||||
length: Le64,
|
||||
arg: Le64,
|
||||
}
|
||||
|
||||
// SAFETY: it only has data and has no implicit padding.
|
||||
unsafe impl ByteValued for PvmemcontrolReq {}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Default)]
|
||||
struct PvmemcontrolResp {
|
||||
ret_errno: Le32,
|
||||
ret_code: Le32,
|
||||
ret_value: Le64,
|
||||
arg0: Le64,
|
||||
arg1: Le64,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for PvmemcontrolResp {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let PvmemcontrolResp {
|
||||
ret_errno,
|
||||
ret_code,
|
||||
..
|
||||
} = self;
|
||||
write!(
|
||||
f,
|
||||
"PvmemcontrolResp {{ ret_errno: {}, ret_code: {}, .. }}",
|
||||
ret_errno.to_native(),
|
||||
ret_code.to_native()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// SAFETY: it only has data and has no implicit padding.
|
||||
unsafe impl ByteValued for PvmemcontrolResp {}
|
||||
|
||||
/// The guest connections start at 0x8000_0000, which has a leading 1 in
|
||||
/// the most significant byte, this ensures it does not conflict with
|
||||
/// any of the transport commands
|
||||
#[derive(Hash, Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub struct GuestConnection {
|
||||
command: u32,
|
||||
}
|
||||
|
||||
impl Default for GuestConnection {
|
||||
fn default() -> Self {
|
||||
GuestConnection::new(0x8000_0000)
|
||||
}
|
||||
}
|
||||
|
||||
impl GuestConnection {
|
||||
fn new(command: u32) -> Self {
|
||||
Self { command }
|
||||
}
|
||||
|
||||
fn next(&self) -> Self {
|
||||
let GuestConnection { command } = *self;
|
||||
|
||||
if command == u32::MAX {
|
||||
GuestConnection::default()
|
||||
} else {
|
||||
GuestConnection::new(command + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<u32> for GuestConnection {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: u32) -> Result<Self, Self::Error> {
|
||||
if (value & 0x8000_0000) != 0 {
|
||||
Ok(GuestConnection::new(value))
|
||||
} else {
|
||||
Err(Error::InvalidConnection(value))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct PercpuInitState {
|
||||
port_buf_map: HashMap<GuestConnection, GuestAddress>,
|
||||
next_conn: GuestConnection,
|
||||
}
|
||||
|
||||
impl PercpuInitState {
|
||||
fn new() -> Self {
|
||||
PercpuInitState {
|
||||
port_buf_map: HashMap::new(),
|
||||
next_conn: GuestConnection::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum PvmemcontrolState {
|
||||
PercpuInit(PercpuInitState),
|
||||
Ready(HashMap<GuestConnection, GuestAddress>),
|
||||
Broken,
|
||||
}
|
||||
|
||||
pub struct PvmemcontrolDevice {
|
||||
transport: PvmemcontrolTransport,
|
||||
state: PvmemcontrolState,
|
||||
}
|
||||
|
||||
impl PvmemcontrolDevice {
|
||||
fn new(transport: PvmemcontrolTransport, state: PvmemcontrolState) -> Self {
|
||||
PvmemcontrolDevice { transport, state }
|
||||
}
|
||||
}
|
||||
|
||||
impl PvmemcontrolDevice {
|
||||
fn register_percpu_buf(
|
||||
guest_memory: &GuestMemoryAtomic<GuestMemoryMmap<AtomicBitmap>>,
|
||||
mut state: PercpuInitState,
|
||||
PvmemcontrolTransportRegister { buf_phys_addr }: PvmemcontrolTransportRegister,
|
||||
) -> Self {
|
||||
// access to this address is checked
|
||||
let buf_phys_addr = GuestAddress(buf_phys_addr.into());
|
||||
if !guest_memory.memory().check_range(
|
||||
buf_phys_addr,
|
||||
std::mem::size_of::<PvmemcontrolResp>().max(std::mem::size_of::<PvmemcontrolReq>()),
|
||||
) {
|
||||
warn!("guest sent invalid phys addr {:#x}", buf_phys_addr.0);
|
||||
return PvmemcontrolDevice::new(
|
||||
PvmemcontrolTransport::error(),
|
||||
PvmemcontrolState::Broken,
|
||||
);
|
||||
}
|
||||
|
||||
let conn = {
|
||||
// find an available port+byte combination, and fail if full
|
||||
let mut next_conn = state.next_conn;
|
||||
while state.port_buf_map.contains_key(&next_conn) {
|
||||
next_conn = next_conn.next();
|
||||
if next_conn == state.next_conn {
|
||||
warn!("connections exhausted");
|
||||
return PvmemcontrolDevice::new(
|
||||
PvmemcontrolTransport::error(),
|
||||
PvmemcontrolState::Broken,
|
||||
);
|
||||
}
|
||||
}
|
||||
next_conn
|
||||
};
|
||||
state.next_conn = conn.next();
|
||||
state.port_buf_map.insert(conn, buf_phys_addr);
|
||||
|
||||
// inform guest of the connection
|
||||
let response = PvmemcontrolTransport::register_response(conn.command);
|
||||
|
||||
PvmemcontrolDevice::new(response, PvmemcontrolState::PercpuInit(state))
|
||||
}
|
||||
|
||||
fn reset() -> Self {
|
||||
PvmemcontrolDevice::new(
|
||||
PvmemcontrolTransport::ack(),
|
||||
PvmemcontrolState::PercpuInit(PercpuInitState::new()),
|
||||
)
|
||||
}
|
||||
|
||||
fn error() -> Self {
|
||||
PvmemcontrolDevice::new(PvmemcontrolTransport::error(), PvmemcontrolState::Broken)
|
||||
}
|
||||
|
||||
fn ready(PercpuInitState { port_buf_map, .. }: PercpuInitState) -> Self {
|
||||
PvmemcontrolDevice::new(
|
||||
PvmemcontrolTransport::ack(),
|
||||
PvmemcontrolState::Ready(port_buf_map),
|
||||
)
|
||||
}
|
||||
|
||||
fn run_command(
|
||||
&mut self,
|
||||
guest_memory: &GuestMemoryAtomic<GuestMemoryMmap<AtomicBitmap>>,
|
||||
command: PvmemcontrolTransportCommand,
|
||||
) {
|
||||
let state = std::mem::replace(&mut self.state, PvmemcontrolState::Broken);
|
||||
|
||||
*self = match command {
|
||||
PvmemcontrolTransportCommand::Reset => Self::reset(),
|
||||
PvmemcontrolTransportCommand::Register => {
|
||||
if let PvmemcontrolState::PercpuInit(state) = state {
|
||||
// SAFETY: By device protocol. If driver is wrong the device
|
||||
// can enter a Broken state, but the behavior is still sound.
|
||||
Self::register_percpu_buf(guest_memory, state, unsafe {
|
||||
self.transport.as_register()
|
||||
})
|
||||
} else {
|
||||
debug!("received register without reset");
|
||||
Self::error()
|
||||
}
|
||||
}
|
||||
PvmemcontrolTransportCommand::Ready => {
|
||||
if let PvmemcontrolState::PercpuInit(state) = state {
|
||||
Self::ready(state)
|
||||
} else {
|
||||
debug!("received ready without reset");
|
||||
Self::error()
|
||||
}
|
||||
}
|
||||
PvmemcontrolTransportCommand::Disconnect => Self::error(),
|
||||
PvmemcontrolTransportCommand::Ack => {
|
||||
debug!("received ack as command");
|
||||
Self::error()
|
||||
}
|
||||
PvmemcontrolTransportCommand::Error => {
|
||||
debug!("received error as command");
|
||||
Self::error()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// read from the transport
|
||||
fn read_transport(&self, offset: u64, data: &mut [u8]) {
|
||||
self.transport
|
||||
.as_slice()
|
||||
.iter()
|
||||
.skip(offset as usize)
|
||||
.zip(data.iter_mut())
|
||||
.for_each(|(src, dest)| *dest = *src)
|
||||
}
|
||||
|
||||
/// can only write to transport payload
|
||||
/// command is a special register that needs separate dispatching
|
||||
fn write_transport(&mut self, offset: u64, data: &[u8]) {
|
||||
self.transport
|
||||
.payload
|
||||
.as_mut_slice()
|
||||
.iter_mut()
|
||||
.skip(offset as usize)
|
||||
.zip(data.iter())
|
||||
.for_each(|(dest, src)| *dest = *src)
|
||||
}
|
||||
|
||||
fn find_connection(&self, conn: GuestConnection) -> Option<GuestAddress> {
|
||||
match &self.state {
|
||||
PvmemcontrolState::Ready(map) => map.get(&conn).copied(),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PvmemcontrolBusDevice {
|
||||
mem: GuestMemoryAtomic<GuestMemoryMmap<AtomicBitmap>>,
|
||||
dev: RwLock<PvmemcontrolDevice>,
|
||||
}
|
||||
|
||||
pub struct PvmemcontrolPciDevice {
|
||||
id: String,
|
||||
configuration: PciConfiguration,
|
||||
bar_regions: Vec<PciBarConfiguration>,
|
||||
}
|
||||
|
||||
impl PvmemcontrolBusDevice {
|
||||
/// f is called with the host address of `range_base` and only when
|
||||
/// [`range_base`, `range_base` + `range_len`) is present in the guest
|
||||
fn operate_on_memory_range<F>(&self, addr: u64, length: u64, f: F) -> result::Result<(), Error>
|
||||
where
|
||||
F: FnOnce(*mut libc::c_void, libc::size_t) -> libc::c_int,
|
||||
{
|
||||
let memory = self.mem.memory();
|
||||
let range_base = GuestAddress(addr);
|
||||
let range_len = usize::try_from(length).map_err(|_| Error::InvalidRequest)?;
|
||||
|
||||
// assume guest memory is not interleaved with vmm memory on the host.
|
||||
if !memory.check_range(range_base, range_len) {
|
||||
return Err(Error::GuestMemory(GuestMemoryError::InvalidGuestAddress(
|
||||
range_base,
|
||||
)));
|
||||
}
|
||||
let hva = memory
|
||||
.get_host_address(range_base)
|
||||
.map_err(Error::GuestMemory)?;
|
||||
let res = f(hva as *mut libc::c_void, range_len as libc::size_t);
|
||||
if res != 0 {
|
||||
return Err(Error::LibcFail(io::Error::last_os_error()));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn madvise(&self, addr: u64, length: u64, advice: libc::c_int) -> result::Result<(), Error> {
|
||||
// SAFETY: [`base`, `base` + `len`) is guest memory
|
||||
self.operate_on_memory_range(addr, length, |base, len| unsafe {
|
||||
libc::madvise(base, len, advice)
|
||||
})
|
||||
}
|
||||
|
||||
fn mlock(&self, addr: u64, length: u64, on_default: bool) -> result::Result<(), Error> {
|
||||
// SAFETY: [`base`, `base` + `len`) is guest memory
|
||||
self.operate_on_memory_range(addr, length, |base, len| unsafe {
|
||||
libc::mlock2(base, len, if on_default { libc::MLOCK_ONFAULT } else { 0 })
|
||||
})
|
||||
}
|
||||
|
||||
fn munlock(&self, addr: u64, length: u64) -> result::Result<(), Error> {
|
||||
// SAFETY: [`base`, `base` + `len`) is guest memory
|
||||
self.operate_on_memory_range(addr, length, |base, len| unsafe {
|
||||
libc::munlock(base, len)
|
||||
})
|
||||
}
|
||||
|
||||
fn mprotect(
|
||||
&self,
|
||||
addr: u64,
|
||||
length: u64,
|
||||
protection: libc::c_int,
|
||||
) -> result::Result<(), Error> {
|
||||
// SAFETY: [`base`, `base` + `len`) is guest memory
|
||||
self.operate_on_memory_range(addr, length, |base, len| unsafe {
|
||||
libc::mprotect(base, len, protection)
|
||||
})
|
||||
}
|
||||
|
||||
fn set_vma_anon_name(&self, addr: u64, length: u64, name: u64) -> result::Result<(), Error> {
|
||||
let name = (name != 0).then(|| CString::new(format!("pvmemcontrol-{}", name)).unwrap());
|
||||
let name_ptr = if let Some(name) = &name {
|
||||
name.as_ptr()
|
||||
} else {
|
||||
std::ptr::null()
|
||||
};
|
||||
debug!("addr {:X} length {} name {:?}", addr, length, name);
|
||||
|
||||
// SAFETY: [`base`, `base` + `len`) is guest memory
|
||||
self.operate_on_memory_range(addr, length, |base, len| unsafe {
|
||||
libc::prctl(
|
||||
libc::PR_SET_VMA,
|
||||
libc::PR_SET_VMA_ANON_NAME,
|
||||
base,
|
||||
len,
|
||||
name_ptr,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn process_request(
|
||||
&self,
|
||||
func_code: FunctionCode,
|
||||
addr: u64,
|
||||
length: u64,
|
||||
arg: u64,
|
||||
) -> Result<PvmemcontrolResp, Error> {
|
||||
let result = match func_code {
|
||||
FunctionCode::Info => {
|
||||
return Ok(PvmemcontrolResp {
|
||||
ret_errno: 0.into(),
|
||||
ret_code: 0.into(),
|
||||
ret_value: get_page_size().into(),
|
||||
arg0: MAJOR_VERSION.into(),
|
||||
arg1: MINOR_VERSION.into(),
|
||||
})
|
||||
}
|
||||
FunctionCode::Dontneed => self.madvise(addr, length, libc::MADV_DONTNEED),
|
||||
FunctionCode::Remove => self.madvise(addr, length, libc::MADV_REMOVE),
|
||||
FunctionCode::Free => self.madvise(addr, length, libc::MADV_FREE),
|
||||
FunctionCode::Pageout => self.madvise(addr, length, libc::MADV_PAGEOUT),
|
||||
FunctionCode::Dontdump => self.madvise(addr, length, libc::MADV_DONTDUMP),
|
||||
FunctionCode::SetVMAAnonName => self.set_vma_anon_name(addr, length, arg),
|
||||
FunctionCode::Mlock => self.mlock(addr, length, false),
|
||||
FunctionCode::Munlock => self.munlock(addr, length),
|
||||
FunctionCode::MprotectNone => self.mprotect(addr, length, libc::PROT_NONE),
|
||||
FunctionCode::MprotectR => self.mprotect(addr, length, libc::PROT_READ),
|
||||
FunctionCode::MprotectW => self.mprotect(addr, length, libc::PROT_WRITE),
|
||||
FunctionCode::MprotectRW => {
|
||||
self.mprotect(addr, length, libc::PROT_READ | libc::PROT_WRITE)
|
||||
}
|
||||
FunctionCode::Mergeable => self.madvise(addr, length, libc::MADV_MERGEABLE),
|
||||
FunctionCode::Unmergeable => self.madvise(addr, length, libc::MADV_UNMERGEABLE),
|
||||
};
|
||||
result.map(|_| PvmemcontrolResp::default())
|
||||
}
|
||||
|
||||
fn handle_request(
|
||||
&self,
|
||||
PvmemcontrolReq {
|
||||
func_code,
|
||||
addr,
|
||||
length,
|
||||
arg,
|
||||
}: PvmemcontrolReq,
|
||||
) -> Result<PvmemcontrolResp, Error> {
|
||||
let (func_code, addr, length, arg) = (
|
||||
func_code.to_native(),
|
||||
addr.to_native(),
|
||||
length.to_native(),
|
||||
arg.to_native(),
|
||||
);
|
||||
|
||||
let resp_or_err = FunctionCode::try_from(func_code)
|
||||
.map_err(|_| Error::UnknownFunctionCode(func_code))
|
||||
.and_then(|func_code| self.process_request(func_code, addr, length, arg));
|
||||
|
||||
let resp = match resp_or_err {
|
||||
Ok(resp) => resp,
|
||||
Err(e) => match e {
|
||||
Error::InvalidArgument(arg) => PvmemcontrolResp {
|
||||
ret_errno: (libc::EINVAL as u32).into(),
|
||||
ret_code: (arg as u32).into(),
|
||||
..Default::default()
|
||||
},
|
||||
Error::LibcFail(err) => PvmemcontrolResp {
|
||||
ret_errno: (err.raw_os_error().unwrap_or(libc::EFAULT) as u32).into(),
|
||||
ret_code: 0u32.into(),
|
||||
..Default::default()
|
||||
},
|
||||
Error::UnknownFunctionCode(func_code) => PvmemcontrolResp {
|
||||
ret_errno: (libc::EOPNOTSUPP as u32).into(),
|
||||
ret_code: (func_code as u32).into(),
|
||||
..Default::default()
|
||||
},
|
||||
Error::GuestMemory(err) => {
|
||||
warn!("{}", err);
|
||||
PvmemcontrolResp {
|
||||
ret_errno: (libc::EINVAL as u32).into(),
|
||||
ret_code: (func_code as u32).into(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
// device error, stop responding
|
||||
other => return Err(other),
|
||||
},
|
||||
};
|
||||
Ok(resp)
|
||||
}
|
||||
|
||||
fn handle_pvmemcontrol_request(&self, guest_addr: GuestAddress) {
|
||||
let request: PvmemcontrolReq = if let Ok(x) = self.mem.memory().read_obj(guest_addr) {
|
||||
x
|
||||
} else {
|
||||
warn!("cannot read from guest address {:#x}", guest_addr.0);
|
||||
return;
|
||||
};
|
||||
|
||||
let response: PvmemcontrolResp = match self.handle_request(request) {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
warn!("cannot process request {:?} with error {}", request, e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if self.mem.memory().write_obj(response, guest_addr).is_err() {
|
||||
warn!("cannot write to guest address {:#x}", guest_addr.0);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_guest_write(&self, offset: u64, data: &[u8]) {
|
||||
if offset as usize != std::mem::offset_of!(PvmemcontrolTransport, command) {
|
||||
if data.len() != 4 && data.len() != 8 {
|
||||
warn!("guest write is not 4 or 8 bytes long");
|
||||
return;
|
||||
}
|
||||
self.dev.write().unwrap().write_transport(offset, data);
|
||||
return;
|
||||
}
|
||||
let data = if data.len() == 4 {
|
||||
let mut d = [0u8; 4];
|
||||
d.iter_mut()
|
||||
.zip(data.iter())
|
||||
.for_each(|(d, data)| *d = *data);
|
||||
d
|
||||
} else {
|
||||
warn!("guest write with non u32 at command register");
|
||||
return;
|
||||
};
|
||||
let data_cmd = u32::from_le_bytes(data);
|
||||
let command = PvmemcontrolTransportCommand::try_from(data_cmd);
|
||||
|
||||
match command {
|
||||
Ok(command) => self.dev.write().unwrap().run_command(&self.mem, command),
|
||||
Err(_) => {
|
||||
GuestConnection::try_from(data_cmd)
|
||||
.and_then(|conn| {
|
||||
self.dev
|
||||
.read()
|
||||
.unwrap()
|
||||
.find_connection(conn)
|
||||
.ok_or(Error::InvalidConnection(conn.command))
|
||||
})
|
||||
.map(|gpa| self.handle_pvmemcontrol_request(gpa))
|
||||
.unwrap_or_else(|err| warn!("{:?}", err));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_guest_read(&self, offset: u64, data: &mut [u8]) {
|
||||
self.dev.read().unwrap().read_transport(offset, data)
|
||||
}
|
||||
}
|
||||
|
||||
impl PvmemcontrolDevice {
|
||||
pub fn make_device(
|
||||
id: String,
|
||||
mem: GuestMemoryAtomic<GuestMemoryMmap<AtomicBitmap>>,
|
||||
) -> (PvmemcontrolPciDevice, PvmemcontrolBusDevice) {
|
||||
let dev = RwLock::new(PvmemcontrolDevice::error());
|
||||
let mut configuration = PciConfiguration::new(
|
||||
PVMEMCONTROL_VENDOR_ID,
|
||||
PVMEMCONTROL_DEVICE_ID,
|
||||
0x1,
|
||||
PciClassCode::BaseSystemPeripheral,
|
||||
&PvmemcontrolSubclass::Other,
|
||||
None,
|
||||
PciHeaderType::Device,
|
||||
PVMEMCONTROL_SUBSYSTEM_VENDOR_ID,
|
||||
PVMEMCONTROL_SUBSYSTEM_ID,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
let command: [u8; 2] = [0x03, 0x01]; // memory, io, SERR#
|
||||
|
||||
configuration.write_config_register(1, 0, &command);
|
||||
(
|
||||
PvmemcontrolPciDevice {
|
||||
id,
|
||||
configuration,
|
||||
bar_regions: Vec::new(),
|
||||
},
|
||||
PvmemcontrolBusDevice { mem, dev },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl PciDevice for PvmemcontrolPciDevice {
|
||||
fn write_config_register(
|
||||
&mut self,
|
||||
reg_idx: usize,
|
||||
offset: u64,
|
||||
data: &[u8],
|
||||
) -> Option<Arc<Barrier>> {
|
||||
self.configuration
|
||||
.write_config_register(reg_idx, offset, data);
|
||||
None
|
||||
}
|
||||
|
||||
fn read_config_register(&mut self, reg_idx: usize) -> u32 {
|
||||
self.configuration.read_config_register(reg_idx)
|
||||
}
|
||||
|
||||
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn id(&self) -> Option<String> {
|
||||
Some(self.id.clone())
|
||||
}
|
||||
|
||||
fn detect_bar_reprogramming(
|
||||
&mut self,
|
||||
reg_idx: usize,
|
||||
data: &[u8],
|
||||
) -> Option<BarReprogrammingParams> {
|
||||
self.configuration.detect_bar_reprogramming(reg_idx, data)
|
||||
}
|
||||
|
||||
fn allocate_bars(
|
||||
&mut self,
|
||||
_allocator: &Arc<Mutex<SystemAllocator>>,
|
||||
mmio32_allocator: &mut AddressAllocator,
|
||||
_mmio64_allocator: &mut AddressAllocator,
|
||||
resources: Option<Vec<Resource>>,
|
||||
) -> Result<Vec<PciBarConfiguration>, PciDeviceError> {
|
||||
let mut bars = Vec::new();
|
||||
let region_type = PciBarRegionType::Memory32BitRegion;
|
||||
let bar_id = 0;
|
||||
let region_size = PVMEMCONTROL_DEVICE_MMIO_SIZE;
|
||||
let restoring = resources.is_some();
|
||||
let bar_addr = mmio32_allocator
|
||||
.allocate(None, region_size, Some(PVMEMCONTROL_DEVICE_MMIO_ALIGN))
|
||||
.ok_or(PciDeviceError::IoAllocationFailed(region_size))?;
|
||||
|
||||
let bar = PciBarConfiguration::default()
|
||||
.set_index(bar_id as usize)
|
||||
.set_address(bar_addr.raw_value())
|
||||
.set_size(region_size)
|
||||
.set_region_type(region_type)
|
||||
.set_prefetchable(PciBarPrefetchable::NotPrefetchable);
|
||||
|
||||
if !restoring {
|
||||
self.configuration
|
||||
.add_pci_bar(&bar)
|
||||
.map_err(|e| PciDeviceError::IoRegistrationFailed(bar_addr.raw_value(), e))?;
|
||||
}
|
||||
|
||||
bars.push(bar);
|
||||
self.bar_regions.clone_from(&bars);
|
||||
Ok(bars)
|
||||
}
|
||||
|
||||
fn free_bars(
|
||||
&mut self,
|
||||
_allocator: &mut SystemAllocator,
|
||||
mmio32_allocator: &mut AddressAllocator,
|
||||
_mmio64_allocator: &mut AddressAllocator,
|
||||
) -> Result<(), PciDeviceError> {
|
||||
for bar in self.bar_regions.drain(..) {
|
||||
mmio32_allocator.free(GuestAddress(bar.addr()), bar.size())
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn move_bar(&mut self, old_base: u64, new_base: u64) -> result::Result<(), io::Error> {
|
||||
for bar in self.bar_regions.iter_mut() {
|
||||
if bar.addr() == old_base {
|
||||
*bar = bar.set_address(new_base);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Pausable for PvmemcontrolPciDevice {
|
||||
fn pause(&mut self) -> std::result::Result<(), MigratableError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn resume(&mut self) -> std::result::Result<(), MigratableError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Snapshottable for PvmemcontrolPciDevice {
|
||||
fn id(&self) -> String {
|
||||
self.id.clone()
|
||||
}
|
||||
|
||||
fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
|
||||
let mut snapshot = Snapshot::new_from_state(&())?;
|
||||
|
||||
// Snapshot PciConfiguration
|
||||
snapshot.add_snapshot(self.configuration.id(), self.configuration.snapshot()?);
|
||||
|
||||
Ok(snapshot)
|
||||
}
|
||||
}
|
||||
|
||||
impl Transportable for PvmemcontrolPciDevice {}
|
||||
impl Migratable for PvmemcontrolPciDevice {}
|
||||
|
||||
impl BusDeviceSync for PvmemcontrolBusDevice {
|
||||
fn read(&self, _base: u64, offset: u64, data: &mut [u8]) {
|
||||
self.handle_guest_read(offset, data)
|
||||
}
|
||||
|
||||
fn write(&self, _base: u64, offset: u64, data: &[u8]) -> Option<Arc<Barrier>> {
|
||||
self.handle_guest_write(offset, data);
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -3,22 +3,24 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
use std::any::Any;
|
||||
use std::result;
|
||||
use std::sync::{Arc, Barrier, Mutex};
|
||||
|
||||
use anyhow::anyhow;
|
||||
use pci::{
|
||||
BarReprogrammingParams, PciBarConfiguration, PciBarPrefetchable, PciBarRegionType,
|
||||
PciClassCode, PciConfiguration, PciDevice, PciDeviceError, PciHeaderType, PciSubclass,
|
||||
PCI_CONFIGURATION_ID,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::any::Any;
|
||||
use std::result;
|
||||
use std::sync::{Arc, Barrier, Mutex};
|
||||
use thiserror::Error;
|
||||
use versionize::{VersionMap, Versionize, VersionizeResult};
|
||||
use versionize_derive::Versionize;
|
||||
use vm_allocator::{AddressAllocator, SystemAllocator};
|
||||
use vm_device::{BusDevice, Resource};
|
||||
use vm_memory::{Address, GuestAddress};
|
||||
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
|
||||
use vm_migration::{
|
||||
Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable, VersionMapped,
|
||||
};
|
||||
|
||||
const PVPANIC_VENDOR_ID: u16 = 0x1b36;
|
||||
const PVPANIC_DEVICE_ID: u16 = 0x0011;
|
||||
@@ -58,20 +60,23 @@ pub struct PvPanicDevice {
|
||||
bar_regions: Vec<PciBarConfiguration>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Versionize)]
|
||||
pub struct PvPanicDeviceState {
|
||||
events: u8,
|
||||
}
|
||||
|
||||
impl VersionMapped for PvPanicDeviceState {}
|
||||
|
||||
impl PvPanicDevice {
|
||||
pub fn new(id: String, snapshot: Option<Snapshot>) -> Result<Self, PvPanicError> {
|
||||
let pci_configuration_state =
|
||||
vm_migration::state_from_id(snapshot.as_ref(), PCI_CONFIGURATION_ID).map_err(|e| {
|
||||
PvPanicError::RetrievePciConfigurationState(anyhow!(
|
||||
"Failed to get PciConfigurationState from Snapshot: {}",
|
||||
e
|
||||
))
|
||||
})?;
|
||||
vm_migration::versioned_state_from_id(snapshot.as_ref(), PCI_CONFIGURATION_ID)
|
||||
.map_err(|e| {
|
||||
PvPanicError::RetrievePciConfigurationState(anyhow!(
|
||||
"Failed to get PciConfigurationState from Snapshot: {}",
|
||||
e
|
||||
))
|
||||
})?;
|
||||
|
||||
let mut configuration = PciConfiguration::new(
|
||||
PVPANIC_VENDOR_ID,
|
||||
@@ -92,7 +97,7 @@ impl PvPanicDevice {
|
||||
|
||||
let state: Option<PvPanicDeviceState> = snapshot
|
||||
.as_ref()
|
||||
.map(|s| s.to_state())
|
||||
.map(|s| s.to_versioned_state())
|
||||
.transpose()
|
||||
.map_err(|e| {
|
||||
PvPanicError::CreatePvPanicDevice(anyhow!(
|
||||
@@ -176,9 +181,8 @@ impl PciDevice for PvPanicDevice {
|
||||
|
||||
fn allocate_bars(
|
||||
&mut self,
|
||||
_allocator: &Arc<Mutex<SystemAllocator>>,
|
||||
mmio32_allocator: &mut AddressAllocator,
|
||||
_mmio64_allocator: &mut AddressAllocator,
|
||||
allocator: &Arc<Mutex<SystemAllocator>>,
|
||||
_mmio_allocator: &mut AddressAllocator,
|
||||
resources: Option<Vec<Resource>>,
|
||||
) -> std::result::Result<Vec<PciBarConfiguration>, PciDeviceError> {
|
||||
let mut bars = Vec::new();
|
||||
@@ -186,8 +190,10 @@ impl PciDevice for PvPanicDevice {
|
||||
let bar_id = 0;
|
||||
let region_size = PVPANIC_DEVICE_MMIO_SIZE;
|
||||
let restoring = resources.is_some();
|
||||
let bar_addr = mmio32_allocator
|
||||
.allocate(None, region_size, Some(PVPANIC_DEVICE_MMIO_ALIGNMENT))
|
||||
let bar_addr = allocator
|
||||
.lock()
|
||||
.unwrap()
|
||||
.allocate_mmio_hole_addresses(None, region_size, Some(PVPANIC_DEVICE_MMIO_ALIGNMENT))
|
||||
.ok_or(PciDeviceError::IoAllocationFailed(region_size))?;
|
||||
|
||||
let bar = PciBarConfiguration::default()
|
||||
@@ -205,19 +211,18 @@ impl PciDevice for PvPanicDevice {
|
||||
}
|
||||
|
||||
bars.push(bar);
|
||||
self.bar_regions.clone_from(&bars);
|
||||
self.bar_regions = bars.clone();
|
||||
|
||||
Ok(bars)
|
||||
}
|
||||
|
||||
fn free_bars(
|
||||
&mut self,
|
||||
_allocator: &mut SystemAllocator,
|
||||
mmio32_allocator: &mut AddressAllocator,
|
||||
_mmio64_allocator: &mut AddressAllocator,
|
||||
allocator: &mut SystemAllocator,
|
||||
_mmio_allocator: &mut AddressAllocator,
|
||||
) -> std::result::Result<(), PciDeviceError> {
|
||||
for bar in self.bar_regions.drain(..) {
|
||||
mmio32_allocator.free(GuestAddress(bar.addr()), bar.size());
|
||||
allocator.free_mmio_hole_addresses(GuestAddress(bar.addr()), bar.size());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -237,7 +242,7 @@ impl PciDevice for PvPanicDevice {
|
||||
data[0] = self.events;
|
||||
}
|
||||
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||
fn as_any(&mut self) -> &mut dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
@@ -254,7 +259,7 @@ impl Snapshottable for PvPanicDevice {
|
||||
}
|
||||
|
||||
fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
|
||||
let mut snapshot = Snapshot::new_from_state(&self.state())?;
|
||||
let mut snapshot = Snapshot::new_from_versioned_state(&self.state())?;
|
||||
|
||||
// Snapshot PciConfiguration
|
||||
snapshot.add_snapshot(self.configuration.id(), self.configuration.snapshot()?);
|
||||
|
||||
@@ -3,14 +3,13 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
use std::cmp;
|
||||
use std::sync::{Arc, Barrier};
|
||||
|
||||
use anyhow::anyhow;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
use arch::aarch64::layout::{TPM_SIZE, TPM_START};
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use arch::x86_64::layout::{TPM_SIZE, TPM_START};
|
||||
use std::cmp;
|
||||
use std::sync::{Arc, Barrier};
|
||||
use thiserror::Error;
|
||||
use tpm::emulator::{BackendCmd, Emulator};
|
||||
use tpm::TPM_CRB_BUFFER_MAX;
|
||||
@@ -451,7 +450,7 @@ impl BusDevice for Tpm {
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
error!("Invalid value passed to CTRL_REQ register");
|
||||
error!("Invalid value passed to CRTL_REQ register");
|
||||
return None;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
All documentations (e.g. files with extension `.md`) in this repository is
|
||||
covered by the following license:
|
||||
The documentation in this directory is covered by the following license:
|
||||
|
||||
Attribution 4.0 International
|
||||
|
||||
@@ -36,5 +36,3 @@ You can run a SEV-SNP VM using the following command:
|
||||
--memory size=1G \
|
||||
--disk path=ubuntu.img
|
||||
```
|
||||
|
||||
For more information related to Microsoft Hypervisor please see [mshv.md](mshv.md)
|
||||
|
||||
28
docs/api.md
28
docs/api.md
@@ -50,12 +50,17 @@ for more details about the API payloads and responses.
|
||||
|
||||
#### REST API Location and availability
|
||||
|
||||
The REST API, if enabled, is available as soon as the Cloud Hypervisor binary is started,
|
||||
through either a local UNIX socket as given in the Cloud Hypervisor option `--api-socket path=...`
|
||||
or a fd with `--api-socket fd=...`.
|
||||
The REST API is available as soon as the Cloud Hypervisor binary is started,
|
||||
through a local UNIX socket.
|
||||
By default, it is located at `/run/user/{user ID}/cloud-hypervisor.{Cloud Hypervisor PID}`.
|
||||
For example, if you launched Cloud Hypervisor as user ID 1000 and its PID is
|
||||
123456, the Cloud Hypervisor REST API will be available at `/run/user/1000/cloud-hypervisor.123456`.
|
||||
|
||||
The REST API default URL can be overridden through the Cloud Hypervisor
|
||||
option `--api-socket`:
|
||||
|
||||
```
|
||||
$ ./target/debug/cloud-hypervisor --api-socket path=/tmp/cloud-hypervisor.sock
|
||||
$ ./target/debug/cloud-hypervisor --api-socket /tmp/cloud-hypervisor.sock
|
||||
Cloud Hypervisor Guest
|
||||
API server: /tmp/cloud-hypervisor.sock
|
||||
vCPUs: 1
|
||||
@@ -105,7 +110,6 @@ The Cloud Hypervisor API exposes the following actions through its endpoints:
|
||||
| Add vsock device to the VM | `/vm.add-vsock` | `/schemas/VsockConfig` | `/schemas/PciDeviceInfo` | The VM is booted |
|
||||
| Remove device from the VM | `/vm.remove-device` | `/schemas/VmRemoveDevice` | N/A | The VM is booted |
|
||||
| Dump the VM counters | `/vm.counters` | N/A | `/schemas/VmCounters` | The VM is booted |
|
||||
| Inject an NMI | `/vm.nmi` | N/A | N/A | The VM is booted |
|
||||
| Prepare to receive a migration | `/vm.receive-migration` | `/schemas/ReceiveMigrationData` | N/A | N/A |
|
||||
| Start to send migration to target | `/vm.send-migration` | `/schemas/SendMigrationData` | N/A | The VM is booted and (shared mem or hugepages enabled) |
|
||||
|
||||
@@ -143,7 +147,7 @@ We want to create a virtual machine with the following characteristics:
|
||||
`/opt/clh/images/focal-server-cloudimg-amd64.raw`
|
||||
|
||||
```shell
|
||||
#!/usr/bin/env bash
|
||||
#!/bin/bash
|
||||
|
||||
curl --unix-socket /tmp/cloud-hypervisor.sock -i \
|
||||
-X PUT 'http://localhost/api/v1/vm.create' \
|
||||
@@ -163,7 +167,7 @@ curl --unix-socket /tmp/cloud-hypervisor.sock -i \
|
||||
Once the VM is created, we can boot it:
|
||||
|
||||
```shell
|
||||
#!/usr/bin/env bash
|
||||
#!/bin/bash
|
||||
|
||||
curl --unix-socket /tmp/cloud-hypervisor.sock -i -X PUT 'http://localhost/api/v1/vm.boot'
|
||||
```
|
||||
@@ -173,7 +177,7 @@ curl --unix-socket /tmp/cloud-hypervisor.sock -i -X PUT 'http://localhost/api/v1
|
||||
We can fetch information about any VM, as soon as it's created:
|
||||
|
||||
```shell
|
||||
#!/usr/bin/env bash
|
||||
#!/bin/bash
|
||||
|
||||
curl --unix-socket /tmp/cloud-hypervisor.sock -i \
|
||||
-X GET 'http://localhost/api/v1/vm.info' \
|
||||
@@ -185,7 +189,7 @@ curl --unix-socket /tmp/cloud-hypervisor.sock -i \
|
||||
We can reboot a VM that's already booted:
|
||||
|
||||
```shell
|
||||
#!/usr/bin/env bash
|
||||
#!/bin/bash
|
||||
|
||||
curl --unix-socket /tmp/cloud-hypervisor.sock -i -X PUT 'http://localhost/api/v1/vm.reboot'
|
||||
```
|
||||
@@ -195,7 +199,7 @@ curl --unix-socket /tmp/cloud-hypervisor.sock -i -X PUT 'http://localhost/api/v1
|
||||
Once booted, we can shut a VM down from the REST API:
|
||||
|
||||
```shell
|
||||
#!/usr/bin/env bash
|
||||
#!/bin/bash
|
||||
|
||||
curl --unix-socket /tmp/cloud-hypervisor.sock -i -X PUT 'http://localhost/api/v1/vm.shutdown'
|
||||
```
|
||||
@@ -265,7 +269,7 @@ definition in XML format:
|
||||
### Command Line Interface
|
||||
|
||||
The Cloud Hypervisor Command Line Interface (CLI) can only be used for launching
|
||||
the Cloud Hypervisor binary, i.e. it cannot be used for controlling the VMM or
|
||||
the Cloud Hypervisor binary, i.e. it can not be used for controlling the VMM or
|
||||
the launched VM once they're up and running.
|
||||
|
||||
If you want to inspect the VMM, or control the VM after launching Cloud
|
||||
@@ -381,7 +385,7 @@ APIs work together, let's look at a complete VM creation flow, from the
|
||||
[REST API](#rest-api) in order to creates a virtual machine:
|
||||
```
|
||||
shell
|
||||
#!/usr/bin/env bash
|
||||
#!/bin/bash
|
||||
|
||||
curl --unix-socket /tmp/cloud-hypervisor.sock -i \
|
||||
-X PUT 'http://localhost/api/v1/vm.create' \
|
||||
|
||||
@@ -65,17 +65,17 @@ the data processing from within the container if you don't want to fight the
|
||||
tool chain.
|
||||
|
||||
```shell
|
||||
# Set env to enable code coverage
|
||||
export RUSTFLAGS="-Cinstrument-coverage"
|
||||
export LLVM_PROFILE_FILE="ch-%p-%m.profraw"
|
||||
# Get a shell
|
||||
./scripts/dev_cli.sh shell
|
||||
|
||||
# Run unit tests
|
||||
scripts/dev_cli.sh tests --unit --libc gnu
|
||||
# Install llvm-tools-preview for llvm-profdata
|
||||
rustup component add llvm-tools-preview
|
||||
# Merge data files by using the following command
|
||||
find . -name '*.profraw' -exec `rustc --print sysroot`/lib/rustlib/x86_64-unknown-linux-gnu/bin/llvm-profdata merge -sparse {} -o coverage.profdata \;
|
||||
|
||||
# Run integration tests
|
||||
scripts/dev_cli.sh tests --integration --libc gnu
|
||||
scripts/dev_cli.sh tests --integration-live-migration --libc gnu
|
||||
|
||||
# Export code coverage report
|
||||
scripts/dev_cli.sh tests --coverage -- -- html
|
||||
# As of writing, the container has Rust 1.67.1. It is too old for grcov.
|
||||
rustup install stable
|
||||
cargo +stable install grcov
|
||||
# Run grcov as usual
|
||||
grcov . --binary-path ./target/x86_64-unknown-linux-gnu/release -s . -t html --branch --ignore-not-existing -o coverage-html-output/
|
||||
```
|
||||
|
||||
@@ -4,17 +4,151 @@ In the context of adding more utilities to the Ubuntu cloud image being used
|
||||
for integration testing, this quick guide details how to achieve the proper
|
||||
modification of an official Ubuntu cloud image.
|
||||
|
||||
## Image generation script
|
||||
## Create the image
|
||||
|
||||
This [script](../scripts/build-custom-image.sh) can be used to generate a custom image (needs to be modified per architecture/distribution image)
|
||||
Let's go through the steps on how to extend an official Ubuntu image. These
|
||||
steps can be applied to other distributions (with a few changes regarding
|
||||
package management).
|
||||
|
||||
### Get latest Ubuntu cloud image
|
||||
|
||||
```bash
|
||||
wget https://cloud-images.ubuntu.com/focal/current/focal-server-cloudimg-amd64.img
|
||||
```
|
||||
|
||||
### Check the file format is QCOW2
|
||||
|
||||
```bash
|
||||
file focal-server-cloudimg-amd64.img
|
||||
focal-server-cloudimg-amd64.img: QEMU QCOW2 Image (v2), 2361393152 bytes
|
||||
```
|
||||
|
||||
### Convert QCOW2 into RAW
|
||||
|
||||
```bash
|
||||
qemu-img convert -p -f qcow2 -O raw focal-server-cloudimg-amd64.img focal-server-cloudimg-amd64.raw
|
||||
```
|
||||
|
||||
### Identify the Linux partition
|
||||
|
||||
The goal is to mount the image rootfs so that it can be modified as needed.
|
||||
That's why we need to identify where the Linux filesystem partition is located
|
||||
in the image.
|
||||
|
||||
```bash
|
||||
sudo fdisk -l focal-server-cloudimg-amd64.raw
|
||||
Disk focal-server-cloudimg-amd64.raw: 2.2 GiB, 2361393152 bytes, 4612096 sectors
|
||||
Units: sectors of 1 * 512 = 512 bytes
|
||||
Sector size (logical/physical): 512 bytes / 512 bytes
|
||||
I/O size (minimum/optimal): 512 bytes / 512 bytes
|
||||
Disklabel type: gpt
|
||||
Disk identifier: A1171ABA-2BEA-4218-A467-1B2B607E5953
|
||||
|
||||
Device Start End Sectors Size Type
|
||||
focal-server-cloudimg-amd64.raw1 227328 4612062 4384735 2.1G Linux filesystem
|
||||
focal-server-cloudimg-amd64.raw14 2048 10239 8192 4M BIOS boot
|
||||
focal-server-cloudimg-amd64.raw15 10240 227327 217088 106M EFI System
|
||||
|
||||
Partition table entries are not in disk order.
|
||||
```
|
||||
|
||||
### Mount the Linux partition
|
||||
|
||||
```bash
|
||||
mkdir -p /mnt
|
||||
sudo mount -o loop,offset=$((227328 * 512)) focal-server-cloudimg-amd64.raw /mnt
|
||||
```
|
||||
|
||||
### Set up DNS
|
||||
|
||||
The next step describes changing the root directory to the rootfs contained by
|
||||
the cloud image. For DNS to work in the root directory, you will need to first bind-mount
|
||||
the host `/etc/resolv.conf` onto the mounted linux partition of the cloud image.
|
||||
|
||||
```bash
|
||||
sudo mount -o bind /etc/resolv.conf /mnt/etc/resolv.conf
|
||||
```
|
||||
|
||||
### Change root directory
|
||||
|
||||
Changing the root directory will allow us to install new packages to the rootfs
|
||||
contained by the cloud image.
|
||||
|
||||
```bash
|
||||
sudo chroot /mnt
|
||||
mount -t proc proc /proc
|
||||
mount -t devpts devpts /dev/pts
|
||||
```
|
||||
|
||||
### Install needed packages
|
||||
|
||||
In the context Cloud Hypervisor's integration tests, we need several utilities.
|
||||
Here is the way to install them for a Ubuntu image. This step is specific to
|
||||
Ubuntu distributions.
|
||||
|
||||
```bash
|
||||
apt update
|
||||
apt install fio iperf iperf3 socat stress cpuid tpm2-tools
|
||||
```
|
||||
|
||||
### Remove counterproductive packages
|
||||
|
||||
* snapd:
|
||||
|
||||
This prevents snapd from trying to mount squashfs filesystem when the kernel
|
||||
might not support it. This might be the case when the image is used with direct
|
||||
kernel boot. This step is specific to Ubuntu distributions.
|
||||
|
||||
* pollinate:
|
||||
|
||||
Remove this package which can fail and lead to the SSH daemon failing to start.
|
||||
See #2113 for details.
|
||||
|
||||
```bash
|
||||
apt remove --purge snapd pollinate
|
||||
```
|
||||
|
||||
|
||||
### Cleanup the image
|
||||
|
||||
Leave no trace in the image before unmounting its content.
|
||||
|
||||
```bash
|
||||
umount /dev/pts
|
||||
umount /proc
|
||||
history -c
|
||||
exit
|
||||
umount /mnt/etc/resolv.conf
|
||||
umount /mnt
|
||||
```
|
||||
|
||||
### Rename the image
|
||||
|
||||
Renaming is important to identify this is a modified image.
|
||||
|
||||
```bash
|
||||
mv focal-server-cloudimg-amd64.raw focal-server-cloudimg-amd64-custom-$(date "+%Y%m%d")-0.raw
|
||||
```
|
||||
|
||||
The `-0` is the revision and is only necessary to change if multiple images are
|
||||
updated on the same day.
|
||||
|
||||
### Create QCOW2 from RAW
|
||||
|
||||
Last step is to create the QCOW2 image back from the modified image.
|
||||
|
||||
```bash
|
||||
qemu-img convert -p -f raw -O qcow2 focal-server-cloudimg-amd64-custom-$(date "+%Y%m%d")-0.raw focal-server-cloudimg-amd64-custom-$(date "+%Y%m%d")-0.qcow2
|
||||
```
|
||||
|
||||
## Switch CI to use the new image
|
||||
|
||||
### Upload to Azure storage
|
||||
|
||||
A command like the following can be used to upload the image:
|
||||
|
||||
`az storage blob upload --account-name cloudhypervisorstorages --container-name '$web' --name jammy-server-cloudimg-amd64-custom-20241017-0.qcow2 --file jammy-server-cloudimg-amd64-custom-20241017-0.qcow2 --sas-token <redacted>`
|
||||
The next step is to update both images (QCOW2 and RAW) stored as part of the
|
||||
Azure storage account, replacing them with the newly created ones. This will
|
||||
make these new images available from the integration tests. This is usually
|
||||
achieved through the web interface.
|
||||
|
||||
### Update integration tests
|
||||
|
||||
@@ -25,6 +159,171 @@ as we might need to update the direct kernel boot command line, replacing
|
||||
|
||||
Update all references to the previous image name to the new one.
|
||||
|
||||
## NVIDIA image for VFIO bare-metal CI
|
||||
## NVIDIA image for VFIO baremetal CI
|
||||
|
||||
Uncomment "VFIO_CUSTOM_IMAGE" in the script listed above to generate the custom image used for the VFIO worker.
|
||||
Here we are going to describe how to create a cloud image that contains the
|
||||
necessary NVIDIA drivers for our VFIO baremetal CI.
|
||||
|
||||
### Download base image
|
||||
|
||||
We usually start from one of the custom cloud image we have previously created
|
||||
but we can use a stock cloud image as well.
|
||||
|
||||
```bash
|
||||
wget https://cloud-hypervisor.azureedge.net/jammy-server-cloudimg-amd64-custom-20230119-0.raw
|
||||
mv jammy-server-cloudimg-amd64-custom-20230119-0.raw jammy-server-cloudimg-amd64-nvidia.raw
|
||||
```
|
||||
|
||||
### Extend the image size
|
||||
|
||||
The NVIDIA drivers consume lots of space, which is why we must resize the image
|
||||
before we proceed any further.
|
||||
|
||||
```bash
|
||||
qemu-img resize jammy-server-cloudimg-amd64-nvidia.raw 5G
|
||||
```
|
||||
|
||||
### Resize the partition
|
||||
|
||||
We use `parted` for fixing the GPT after the image was resized, as well as for
|
||||
resizing the `Linux` partition.
|
||||
|
||||
```bash
|
||||
sudo parted jammy-server-cloudimg-amd64-nvidia.raw
|
||||
|
||||
(parted) print
|
||||
Warning: Not all of the space available to jammy-server-cloudimg-amd64-nvidia.raw
|
||||
appears to be used, you can fix the GPT to use all of the space (an extra 5873664
|
||||
blocks) or continue with the current setting?
|
||||
Fix/Ignore? Fix
|
||||
Model: (file)
|
||||
Disk jammy-server-cloudimg-amd64-nvidia.raw: 5369MB
|
||||
Sector size (logical/physical): 512B/512B
|
||||
Partition Table: gpt
|
||||
Disk Flags:
|
||||
|
||||
Number Start End Size File system Name Flags
|
||||
14 1049kB 5243kB 4194kB bios_grub
|
||||
15 5243kB 116MB 111MB fat32 boot, esp
|
||||
1 116MB 2361MB 2245MB ext4
|
||||
|
||||
(parted) resizepart 1 5369MB
|
||||
(parted) print
|
||||
Model: (file)
|
||||
Disk jammy-server-cloudimg-amd64-nvidia.raw: 5369MB
|
||||
Sector size (logical/physical): 512B/512B
|
||||
Partition Table: gpt
|
||||
Disk Flags:
|
||||
|
||||
Number Start End Size File system Name Flags
|
||||
14 1049kB 5243kB 4194kB bios_grub
|
||||
15 5243kB 116MB 111MB fat32 boot, esp
|
||||
1 116MB 5369MB 5252MB ext4
|
||||
|
||||
(parted) quit
|
||||
```
|
||||
|
||||
### Create a macvtap interface
|
||||
|
||||
Rely on the following [documentation](macvtap-bridge.md) to set up a
|
||||
macvtap interface to provide your VM with proper connectivity.
|
||||
|
||||
### Boot the image
|
||||
|
||||
It is particularly important to boot with a `cloud-init` disk attached to the
|
||||
VM as it will automatically resize the Linux `ext4` filesystem based on the
|
||||
partition that we have previously resized.
|
||||
|
||||
```bash
|
||||
./cloud-hypervisor \
|
||||
--kernel hypervisor-fw \
|
||||
--disk path=focal-server-cloudimg-amd64-nvidia.raw path=/tmp/ubuntu-cloudinit.img \
|
||||
--cpus boot=4 \
|
||||
--memory size=4G \
|
||||
--net fd=3,mac=$mac 3<>$"$tapdevice"
|
||||
```
|
||||
|
||||
### Bring up connectivity
|
||||
|
||||
If your network has a DHCP server, run the following from your VM
|
||||
|
||||
```bash
|
||||
sudo dhclient
|
||||
```
|
||||
|
||||
But if that's not the case, let's give it an IP manually (the IP addresses
|
||||
depend on your actual network) and set the DNS server IP address as well.
|
||||
|
||||
```bash
|
||||
sudo ip addr add 192.168.2.10/24 dev ens4
|
||||
sudo ip link set up dev ens4
|
||||
sudo ip route add default via 192.168.2.1
|
||||
sudo resolvectl dns ens4 8.8.8.8
|
||||
```
|
||||
|
||||
#### Check connectivity and update the image
|
||||
|
||||
```bash
|
||||
sudo apt update
|
||||
sudo apt upgrade
|
||||
```
|
||||
|
||||
### Install NVIDIA drivers
|
||||
|
||||
The following steps and commands are referenced from the
|
||||
[NVIDIA official documentation](https://docs.nvidia.com/datacenter/tesla/tesla-installation-notes/index.html#ubuntu-lts)
|
||||
about Tesla compute cards.
|
||||
|
||||
```bash
|
||||
distribution=$(. /etc/os-release;echo $ID$VERSION_ID | sed -e 's/\.//g')
|
||||
wget https://developer.download.nvidia.com/compute/cuda/repos/$distribution/x86_64/cuda-keyring_1.0-1_all.deb
|
||||
sudo dpkg -i cuda-keyring_1.0-1_all.deb
|
||||
sudo apt-key del 7fa2af80
|
||||
sudo apt update
|
||||
sudo apt -y install cuda-drivers
|
||||
```
|
||||
|
||||
### Check the `nvidia-smi` tool
|
||||
|
||||
Quickly validate that you can find and run the `nvidia-smi` command from your
|
||||
VM. At this point it should fail given no NVIDIA card has been passed through
|
||||
the VM, therefore no NVIDIA driver is loaded.
|
||||
|
||||
### Workaround LA57 reboot issue
|
||||
|
||||
Add `reboot=a` to `GRUB_CMDLINE_LINUX` in `etc/default/grub` so that the VM
|
||||
will be booted with the ACPI reboot type. This resolves a reboot issue when
|
||||
running on 5-level paging systems.
|
||||
|
||||
```bash
|
||||
sudo vim /etc/default/grub
|
||||
sudo update-grub
|
||||
sudo reboot
|
||||
```
|
||||
|
||||
### Remove previous logins
|
||||
|
||||
Since our integration tests rely on past logins to count the number of reboots,
|
||||
we must ensure to clear the list.
|
||||
|
||||
```bash
|
||||
>/var/log/lastlog
|
||||
>/var/log/wtmp
|
||||
>/var/log/btmp
|
||||
```
|
||||
|
||||
### Clear history
|
||||
|
||||
```
|
||||
history -c
|
||||
rm /home/cloud/.bash_history
|
||||
```
|
||||
|
||||
### Reset cloud-init
|
||||
|
||||
This is mandatory as we want `cloud-init` provisioning to work again when a new
|
||||
VM will be booted with this image.
|
||||
|
||||
```
|
||||
sudo cloud-init clean
|
||||
```
|
||||
@@ -1,16 +1,7 @@
|
||||
# `cloud-hypervisor` debug IO ports
|
||||
# `cloud-hypervisor` debug IO port
|
||||
|
||||
When running x86 guests, `cloud-hypervisor` provides different kinds of debug ports:
|
||||
- [`0x80` debug port](https://www.intel.com/content/www/us/en/support/articles/000005500/boards-and-kits.html)
|
||||
- Debug console (by default at `0xe9`).
|
||||
- Firmware debug port at `0x402`.
|
||||
|
||||
All of them can be used to trace user-defined guest events and all of them can
|
||||
be used simultaneously.
|
||||
|
||||
## Debug Ports Overview
|
||||
|
||||
### `0x80` I/O port
|
||||
`cloud-hypervisor` uses the [`0x80`](https://www.intel.com/content/www/us/en/support/articles/000005500/boards-and-kits.html)
|
||||
I/O port to trace user defined guest events.
|
||||
|
||||
Whenever the guest write one byte between `0x0` and `0xF` on this particular
|
||||
I/O port, `cloud-hypervisor` will log and timestamp that event at the `debug`
|
||||
@@ -39,7 +30,7 @@ guest will have `cloud-hypervisor` generate timestamped logs of all those steps.
|
||||
That provides a basic but convenient way of measuring not only the overall guest
|
||||
boot time but all intermediate steps as well.
|
||||
|
||||
#### Logging
|
||||
## Logging
|
||||
|
||||
Assuming parts of the guest software stack have been instrumented to use the
|
||||
`cloud-hypervisor` debug I/O port, we may want to gather the related logs.
|
||||
@@ -68,29 +59,3 @@ $ grep "Debug I/O port" /tmp/ch-fw.log
|
||||
cloud-hypervisor: 19.762449ms: DEBUG:vmm/src/vm.rs:510 -- [Debug I/O port: Firmware code 0x0] 0.019004 seconds
|
||||
cloud-hypervisor: 403.499628ms: DEBUG:vmm/src/vm.rs:510 -- [Debug I/O port: Firmware code 0x1] 0.402744 seconds
|
||||
```
|
||||
|
||||
### Debug console port
|
||||
|
||||
The debug console is inspired by QEMU and Bochs, which have a similar feature.
|
||||
By default, the I/O port `0xe9` is used. This port can be configured like a
|
||||
console. Thus, it can print to a tty, a file, or a pty, for example.
|
||||
|
||||
### Firmware debug port
|
||||
|
||||
The firmware debug port is also a simple port that prints all bytes written to
|
||||
it. The firmware debug port only prints to stdout.
|
||||
|
||||
## When do I need these ports?
|
||||
|
||||
The ports are on the one hand interesting for firmware or kernel developers, as
|
||||
they provide an easy way to print debug information from within a guest.
|
||||
Furthermore, you can patch "normal" software to measure certain events, such as
|
||||
the boot time of a guest.
|
||||
|
||||
## Which port should I choose?
|
||||
|
||||
The `0x80` debug port and the port of the firmware debug device are always
|
||||
available. The debug console must be activated via the command line, but
|
||||
provides more configuration options.
|
||||
|
||||
You can use different ports for different aspect of your logging messages.
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
Cloud Hypervisor uses [cargo-fuzz](https://github.com/rust-fuzz/cargo-fuzz) for fuzzing individual components.
|
||||
|
||||
The fuzzers are in the `fuzz/fuzz_targets` directory
|
||||
The fuzzers are are in the `fuzz/fuzz_targets` directory
|
||||
|
||||
## Preparation
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user