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 | |
|---|---|---|---|
|
|
226d226fef | ||
|
|
293b8fad41 | ||
|
|
54ade15f9e | ||
|
|
03f64d12c3 | ||
|
|
9c3166237d | ||
|
|
3534540e6b | ||
|
|
f444451a04 | ||
|
|
89c25ea00c | ||
|
|
d89ab632e9 | ||
|
|
e32be99d6c | ||
|
|
4a62821e07 | ||
|
|
548ef43ca2 | ||
|
|
27c1b40c83 |
@@ -1,50 +0,0 @@
|
|||||||
[profile.default]
|
|
||||||
# Don't let one individual test run for more than 10 minutes
|
|
||||||
slow-timeout = { period = "60s", terminate-after = 10 }
|
|
||||||
|
|
||||||
[test-groups]
|
|
||||||
windows = { max-threads = 4 }
|
|
||||||
|
|
||||||
[profile.integration]
|
|
||||||
fail-fast = false
|
|
||||||
retries = 3
|
|
||||||
|
|
||||||
[profile.common_tests]
|
|
||||||
inherits = "integration"
|
|
||||||
default-filter = 'test(common_parallel::) | test(common_sequential::) | test(aarch64_acpi::)'
|
|
||||||
junit.path = "/root/workloads/junit/common.xml"
|
|
||||||
|
|
||||||
[[profile.common_tests.overrides]]
|
|
||||||
filter = 'test(common_sequential::)'
|
|
||||||
# use up all the available test threads for each of the sequential tests
|
|
||||||
# i.e. no other test can be running while a sequential test is running.
|
|
||||||
threads-required = 'num-test-threads'
|
|
||||||
|
|
||||||
[profile.dbus]
|
|
||||||
inherits = "integration"
|
|
||||||
default-filter = 'test(dbus_api::)'
|
|
||||||
junit.path = "/root/workloads/junit/dbus.xml"
|
|
||||||
|
|
||||||
[profile.fw_cfg]
|
|
||||||
inherits = "integration"
|
|
||||||
default-filter = 'test(fw_cfg::)'
|
|
||||||
junit.path = "/root/workloads/junit/fw_cfg.xml"
|
|
||||||
|
|
||||||
[profile.ivshmem]
|
|
||||||
inherits = "integration"
|
|
||||||
default-filter = 'test(ivshmem::)'
|
|
||||||
junit.path = "/root/workloads/junit/ivshmem.xml"
|
|
||||||
|
|
||||||
[profile.common_cvm]
|
|
||||||
inherits = "integration"
|
|
||||||
default-filter = 'test(common_cvm::)'
|
|
||||||
junit.path = "/root/workloads/junit/cvm.xml"
|
|
||||||
|
|
||||||
[profile.windows]
|
|
||||||
inherits = "integration"
|
|
||||||
default-filter = 'test(windows::)'
|
|
||||||
junit.path = "/root/workloads/junit/windows.xml"
|
|
||||||
|
|
||||||
[[profile.windows.overrides]]
|
|
||||||
filter = 'test(windows::)'
|
|
||||||
test-group = 'windows'
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
# https://editorconfig.org/
|
|
||||||
#
|
|
||||||
# Hints for editors to assist with correct formatting as you type.
|
|
||||||
root = true
|
|
||||||
|
|
||||||
# Unix-style newlines with a newline ending every file
|
|
||||||
[*]
|
|
||||||
charset = utf-8
|
|
||||||
indent_size = 4
|
|
||||||
end_of_line = lf
|
|
||||||
indent_style = space
|
|
||||||
insert_final_newline = true
|
|
||||||
trim_trailing_whitespace = true
|
|
||||||
# Recommendation, not enforced.
|
|
||||||
max_line_length = 80
|
|
||||||
|
|
||||||
[Makefile]
|
|
||||||
indent_style = tab
|
|
||||||
|
|
||||||
# Inherited as default
|
|
||||||
# [*.sh]
|
|
||||||
# indent_size = 4
|
|
||||||
|
|
||||||
[{Cargo.lock,*.md,*.toml,*.yml,*.yaml}]
|
|
||||||
indent_size = 2
|
|
||||||
34
.github/ISSUE_TEMPLATE/bug_report.md
vendored
34
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,34 +0,0 @@
|
|||||||
---
|
|
||||||
name: Bug report
|
|
||||||
about: File a bug report
|
|
||||||
title: ''
|
|
||||||
labels: ''
|
|
||||||
assignees: ''
|
|
||||||
type: Bug
|
|
||||||
---
|
|
||||||
|
|
||||||
**Describe the bug**
|
|
||||||
A clear and concise description of what the bug is.
|
|
||||||
|
|
||||||
**To Reproduce**
|
|
||||||
Steps to reproduce the behaviour:
|
|
||||||
|
|
||||||
**Version**
|
|
||||||
|
|
||||||
Output of `cloud-hypervisor --version`:
|
|
||||||
|
|
||||||
Did you build from source, if so build command line (e.g. features):
|
|
||||||
|
|
||||||
**VM configuration**
|
|
||||||
|
|
||||||
What command line did you run (or JSON config data):
|
|
||||||
|
|
||||||
Guest OS version details:
|
|
||||||
|
|
||||||
Host OS version details:
|
|
||||||
|
|
||||||
**Logs**
|
|
||||||
|
|
||||||
Output of `cloud-hypervisor -v` from either standard error or via `--log-file`:
|
|
||||||
|
|
||||||
Linux kernel output:
|
|
||||||
36
.github/ISSUE_TEMPLATE/feature_request.md
vendored
36
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,36 +0,0 @@
|
|||||||
---
|
|
||||||
name: Feature request
|
|
||||||
about: Request a feature or enhancement
|
|
||||||
title: ''
|
|
||||||
labels: ''
|
|
||||||
assignees: ''
|
|
||||||
type: Feature
|
|
||||||
---
|
|
||||||
|
|
||||||
**Elevator pitch**
|
|
||||||
A clear and concise description of what the feature (or enhancement) is.
|
|
||||||
|
|
||||||
**Motivation**
|
|
||||||
Why is this feature important to you? What problem does it solve? Why should we
|
|
||||||
carry this feature?
|
|
||||||
|
|
||||||
**Prior art**
|
|
||||||
Examples of similar features in this project or similar (e.g QEMU, Firecracker,
|
|
||||||
Crosvm, etc)
|
|
||||||
|
|
||||||
**API/CLI**
|
|
||||||
Does this feature require any API or CLI changes?
|
|
||||||
|
|
||||||
**Testing**
|
|
||||||
Can it be tested? Any special CI requirements?
|
|
||||||
|
|
||||||
**Interactions**
|
|
||||||
How does this feature interact with existing features (e.g. hotplug, live
|
|
||||||
migration, etc).
|
|
||||||
|
|
||||||
**Implementation**
|
|
||||||
Do you have an Implementation already? If so link to the branch.
|
|
||||||
|
|
||||||
**Full feature description**
|
|
||||||
Please ensure the structured section above is completed and then fill out this
|
|
||||||
section with any additional details you want.
|
|
||||||
77
.github/dependabot.yml
vendored
77
.github/dependabot.yml
vendored
@@ -1,77 +0,0 @@
|
|||||||
version: 2
|
|
||||||
updates:
|
|
||||||
- package-ecosystem: cargo
|
|
||||||
directories:
|
|
||||||
- "/"
|
|
||||||
- "/fuzz"
|
|
||||||
schedule:
|
|
||||||
interval: weekly
|
|
||||||
allow:
|
|
||||||
- dependency-name: "acpi_tables"
|
|
||||||
- dependency-name: "iommufd-ioctls"
|
|
||||||
- dependency-name: "kvm-bindings"
|
|
||||||
- dependency-name: "kvm-ioctls"
|
|
||||||
- dependency-name: "linux-loader"
|
|
||||||
- dependency-name: "micro_http"
|
|
||||||
- dependency-name: "mshv-bindings"
|
|
||||||
- dependency-name: "mshv-ioctls"
|
|
||||||
- dependency-name: "seccompiler"
|
|
||||||
- dependency-name: "vfio-bindings"
|
|
||||||
- dependency-name: "vfio-ioctls"
|
|
||||||
- dependency-name: "vfio_user"
|
|
||||||
- dependency-name: "vhost"
|
|
||||||
- dependency-name: "vhost-user-backend"
|
|
||||||
- dependency-name: "virtio-bindings"
|
|
||||||
- dependency-name: "virtio-queue"
|
|
||||||
- dependency-name: "vm-fdt"
|
|
||||||
- dependency-name: "vm-memory"
|
|
||||||
- dependency-name: "vmm-sys-util"
|
|
||||||
groups:
|
|
||||||
rust-vmm:
|
|
||||||
patterns:
|
|
||||||
- "*"
|
|
||||||
- package-ecosystem: cargo
|
|
||||||
directories:
|
|
||||||
- "/"
|
|
||||||
- "/fuzz"
|
|
||||||
schedule:
|
|
||||||
interval: weekly
|
|
||||||
allow:
|
|
||||||
- dependency-type: all
|
|
||||||
cooldown:
|
|
||||||
default-days: 7
|
|
||||||
semver-major-days: 14
|
|
||||||
semver-minor-days: 7
|
|
||||||
semver-patch-days: 3
|
|
||||||
ignore:
|
|
||||||
- dependency-name: "acpi_tables"
|
|
||||||
- dependency-name: "iommufd-ioctls"
|
|
||||||
- dependency-name: "kvm-bindings"
|
|
||||||
- dependency-name: "kvm-ioctls"
|
|
||||||
- dependency-name: "linux-loader"
|
|
||||||
- dependency-name: "micro_http"
|
|
||||||
- dependency-name: "mshv-bindings"
|
|
||||||
- dependency-name: "mshv-ioctls"
|
|
||||||
- dependency-name: "seccompiler"
|
|
||||||
- dependency-name: "vfio-bindings"
|
|
||||||
- dependency-name: "vfio-ioctls"
|
|
||||||
- dependency-name: "vfio_user"
|
|
||||||
- dependency-name: "vhost"
|
|
||||||
- dependency-name: "vhost-user-backend"
|
|
||||||
- dependency-name: "virtio-bindings"
|
|
||||||
- dependency-name: "virtio-queue"
|
|
||||||
- dependency-name: "vm-fdt"
|
|
||||||
- dependency-name: "vm-memory"
|
|
||||||
- dependency-name: "vmm-sys-util"
|
|
||||||
groups:
|
|
||||||
non-rust-vmm:
|
|
||||||
patterns:
|
|
||||||
- "*"
|
|
||||||
# Makes it possible to have another config for the same directory.
|
|
||||||
# https://github.com/dependabot/dependabot-core/issues/1778#issuecomment-1988140219
|
|
||||||
target-branch: main
|
|
||||||
- package-ecosystem: github-actions
|
|
||||||
directory: "/"
|
|
||||||
schedule:
|
|
||||||
interval: daily
|
|
||||||
open-pull-requests-limit: 1
|
|
||||||
968
.github/workflows/ci.yaml
vendored
968
.github/workflows/ci.yaml
vendored
@@ -1,968 +0,0 @@
|
|||||||
name: CI
|
|
||||||
on: [pull_request, merge_group]
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
pull-requests: read
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ github.event_name }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
jobs:
|
|
||||||
preflight:
|
|
||||||
name: preflight
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
outputs:
|
|
||||||
full: ${{ steps.classify.outputs.full }}
|
|
||||||
rust: ${{ steps.changes.outputs.rust }}
|
|
||||||
cargo: ${{ steps.changes.outputs.cargo }}
|
|
||||||
openapi: ${{ steps.changes.outputs.openapi }}
|
|
||||||
dockerfile: ${{ steps.changes.outputs.dockerfile }}
|
|
||||||
shell: ${{ steps.changes.outputs.shell }}
|
|
||||||
ci: ${{ steps.changes.outputs.ci }}
|
|
||||||
docs: ${{ steps.changes.outputs.docs }}
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v7
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- id: changes
|
|
||||||
uses: dorny/paths-filter@7b450fff21473bca461d4b92ce414b9d0420d706 # v4.0.2
|
|
||||||
with:
|
|
||||||
filters: |
|
|
||||||
rust:
|
|
||||||
- '**/*.rs'
|
|
||||||
- 'build.rs'
|
|
||||||
- '**/Cargo.toml'
|
|
||||||
- '**/Cargo.lock'
|
|
||||||
- 'rust-toolchain.toml'
|
|
||||||
cargo:
|
|
||||||
- '**/Cargo.toml'
|
|
||||||
- '**/Cargo.lock'
|
|
||||||
openapi:
|
|
||||||
- 'vmm/src/api/openapi/**'
|
|
||||||
dockerfile:
|
|
||||||
- 'resources/Dockerfile'
|
|
||||||
shell:
|
|
||||||
- '**/*.sh'
|
|
||||||
- 'scripts/**'
|
|
||||||
ci:
|
|
||||||
- '.github/workflows/**'
|
|
||||||
docs:
|
|
||||||
- 'docs/**'
|
|
||||||
- '**/*.md'
|
|
||||||
- '.github/ISSUE_TEMPLATE/**'
|
|
||||||
- 'LICENSES/**'
|
|
||||||
- 'CODEOWNERS'
|
|
||||||
- id: classify
|
|
||||||
name: Classify changes
|
|
||||||
run: |
|
|
||||||
set -eufo pipefail
|
|
||||||
full=false
|
|
||||||
if [[ "${{ steps.changes.outputs.rust }}" == "true" \
|
|
||||||
|| "${{ steps.changes.outputs.dockerfile }}" == "true" \
|
|
||||||
|| "${{ steps.changes.outputs.shell }}" == "true" \
|
|
||||||
|| "${{ steps.changes.outputs.ci }}" == "true" ]]; then
|
|
||||||
full=true
|
|
||||||
fi
|
|
||||||
echo "full=$full" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "full=$full"
|
|
||||||
dco:
|
|
||||||
name: dco
|
|
||||||
needs: [preflight]
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v7
|
|
||||||
- name: Set up Python 3.x
|
|
||||||
uses: actions/setup-python@v7
|
|
||||||
with:
|
|
||||||
python-version: '3.x'
|
|
||||||
- name: Check DCO
|
|
||||||
if: github.event_name == 'pull_request'
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
run: |
|
|
||||||
set -eufo pipefail
|
|
||||||
pip3 install -U dco-check
|
|
||||||
dco-check -e "49699333+dependabot[bot]@users.noreply.github.com"
|
|
||||||
gitlint:
|
|
||||||
name: gitlint
|
|
||||||
needs: [preflight]
|
|
||||||
# PR-only: gitlint needs GITHUB_BASE_REF, unset on merge_group.
|
|
||||||
if: github.event_name == 'pull_request'
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v7
|
|
||||||
with:
|
|
||||||
# PR head, not the merge ref, so gitlint sees the PR's commits.
|
|
||||||
ref: ${{ github.event.pull_request.head.sha }}
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Set up Python 3.10
|
|
||||||
uses: actions/setup-python@v7
|
|
||||||
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.."
|
|
||||||
lychee:
|
|
||||||
name: lychee
|
|
||||||
needs: [preflight]
|
|
||||||
if: needs.preflight.outputs.docs == 'true' || needs.preflight.outputs.full == 'true'
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Code checkout
|
|
||||||
uses: actions/checkout@v7
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Get changed files in PR
|
|
||||||
id: changed-files
|
|
||||||
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
|
||||||
with:
|
|
||||||
base_sha: ${{ github.event.pull_request.base.sha }}
|
|
||||||
- name: Verify Changed Files
|
|
||||||
run: |
|
|
||||||
set -eufo pipefail
|
|
||||||
echo "--- tj-actions/changed-files Outputs ---"
|
|
||||||
echo "any_changed: ${{ steps.changed-files.outputs.any_changed }}"
|
|
||||||
echo "all_changed_files: ${{ steps.changed-files.outputs.all_changed_files }}"
|
|
||||||
echo "added_files: ${{ steps.changed-files.outputs.added_files }}"
|
|
||||||
echo "modified_files: ${{ steps.changed-files.outputs.modified_files }}"
|
|
||||||
echo "deleted_files: ${{ steps.changed-files.outputs.deleted_files }}"
|
|
||||||
echo "renamed_files: ${{ steps.changed-files.outputs.renamed_files }}"
|
|
||||||
echo "----------------------------------------"
|
|
||||||
if [ -n "${{ steps.changed-files.outputs.all_changed_files }}" ]; then
|
|
||||||
echo "Detected changes: all_changed_files output is NOT empty."
|
|
||||||
else
|
|
||||||
echo "No changes detected: all_changed_files output IS empty."
|
|
||||||
fi
|
|
||||||
- name: Link Availability Check (Diff Only)
|
|
||||||
if: ${{ steps.changed-files.outputs.all_changed_files != '' }}
|
|
||||||
uses: lycheeverse/lychee-action@e7477775783ea5526144ba13e8db5eec57747ce8 # v2.9.0
|
|
||||||
with:
|
|
||||||
args: --verbose --config .lychee.toml ${{ steps.changed-files.outputs.all_changed_files }}
|
|
||||||
failIfEmpty: false
|
|
||||||
fail: true
|
|
||||||
taplo:
|
|
||||||
name: taplo
|
|
||||||
needs: [preflight]
|
|
||||||
if: needs.preflight.outputs.cargo == 'true'
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Code checkout
|
|
||||||
uses: actions/checkout@v7
|
|
||||||
- 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
|
|
||||||
audit:
|
|
||||||
name: audit
|
|
||||||
needs: [preflight]
|
|
||||||
if: needs.preflight.outputs.cargo == 'true'
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v7
|
|
||||||
- uses: actions-rust-lang/audit@v1
|
|
||||||
with:
|
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
shlint:
|
|
||||||
name: shlint
|
|
||||||
needs: [preflight]
|
|
||||||
if: needs.preflight.outputs.shell == 'true' || needs.preflight.outputs.ci == 'true'
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v7
|
|
||||||
- name: Run the shell script checkers
|
|
||||||
uses: luizm/action-sh-checker@883217215b11c1fabbf00eb1a9a041f62d74c744 # v0.10.0
|
|
||||||
env:
|
|
||||||
SHFMT_OPTS: -i 4 -d
|
|
||||||
SHELLCHECK_OPTS: -x --source-path scripts
|
|
||||||
hadolint:
|
|
||||||
name: hadolint
|
|
||||||
needs: [preflight]
|
|
||||||
if: needs.preflight.outputs.dockerfile == 'true'
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v7
|
|
||||||
- name: Lint Dockerfile
|
|
||||||
uses: hadolint/hadolint-action@2a66e89f53d0771bb131a7fa31f3136336094aa6 # v3.4.0
|
|
||||||
with:
|
|
||||||
dockerfile: ./resources/Dockerfile
|
|
||||||
format: tty
|
|
||||||
no-fail: false
|
|
||||||
verbose: true
|
|
||||||
failure-threshold: info
|
|
||||||
reuse:
|
|
||||||
name: reuse
|
|
||||||
needs: [preflight]
|
|
||||||
if: needs.preflight.outputs.full == 'true' || needs.preflight.outputs.cargo == 'true'
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v7
|
|
||||||
- name: REUSE Compliance Check
|
|
||||||
uses: fsfe/reuse-action@v6
|
|
||||||
formatting:
|
|
||||||
name: formatting
|
|
||||||
needs: [preflight]
|
|
||||||
if: needs.preflight.outputs.full == 'true'
|
|
||||||
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@v7
|
|
||||||
- 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
|
|
||||||
package-consistency:
|
|
||||||
name: package-consistency
|
|
||||||
needs: [preflight]
|
|
||||||
if: needs.preflight.outputs.full == 'true'
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Code checkout
|
|
||||||
uses: actions/checkout@v7
|
|
||||||
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: |
|
|
||||||
set -eufo pipefail
|
|
||||||
pushd fuzz
|
|
||||||
python3 ../scripts/package-consistency-check.py github.com/rust-vmm
|
|
||||||
popd
|
|
||||||
fuzz-build:
|
|
||||||
name: fuzz-build
|
|
||||||
needs: [preflight]
|
|
||||||
if: needs.preflight.outputs.full == 'true'
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
rust: [nightly]
|
|
||||||
target: [x86_64-unknown-linux-gnu]
|
|
||||||
env:
|
|
||||||
RUSTFLAGS: -D warnings
|
|
||||||
steps:
|
|
||||||
- name: Code checkout
|
|
||||||
uses: actions/checkout@v7
|
|
||||||
- name: Install Rust toolchain (${{ matrix.rust }})
|
|
||||||
uses: dtolnay/rust-toolchain@stable
|
|
||||||
with:
|
|
||||||
toolchain: ${{ matrix.rust }}
|
|
||||||
target: ${{ matrix.target }}
|
|
||||||
- name: Install Cargo fuzz
|
|
||||||
run: cargo install cargo-fuzz
|
|
||||||
- name: Fuzz Build
|
|
||||||
run: cargo fuzz build
|
|
||||||
- name: Fuzz Check
|
|
||||||
run: cargo fuzz check
|
|
||||||
openapi:
|
|
||||||
name: openapi
|
|
||||||
needs: [preflight]
|
|
||||||
if: needs.preflight.outputs.openapi == 'true'
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
container: openapitools/openapi-generator-cli
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v7
|
|
||||||
- name: Validate OpenAPI
|
|
||||||
run: |
|
|
||||||
/usr/local/bin/docker-entrypoint.sh validate -i vmm/src/api/openapi/cloud-hypervisor.yaml
|
|
||||||
typos:
|
|
||||||
name: typos
|
|
||||||
needs: [preflight]
|
|
||||||
if: github.event_name == 'pull_request'
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v7
|
|
||||||
- uses: crate-ci/typos@bee27e3a4fd1ea2111cf90ab89cd076c870fce14 # v1.48.0
|
|
||||||
quality:
|
|
||||||
name: quality
|
|
||||||
needs: [preflight]
|
|
||||||
if: needs.preflight.outputs.full == 'true'
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
# Beta clippy is non-blocking; continue-on-error below keeps the
|
|
||||||
# aggregated needs.quality.result green when only beta fails.
|
|
||||||
continue-on-error: ${{ matrix.experimental }}
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
rust:
|
|
||||||
- beta
|
|
||||||
- stable
|
|
||||||
target:
|
|
||||||
- aarch64-unknown-linux-gnu
|
|
||||||
- aarch64-unknown-linux-musl
|
|
||||||
- x86_64-unknown-linux-gnu
|
|
||||||
- x86_64-unknown-linux-musl
|
|
||||||
include:
|
|
||||||
- rust: beta
|
|
||||||
experimental: true
|
|
||||||
- rust: stable
|
|
||||||
experimental: false
|
|
||||||
steps:
|
|
||||||
- name: Code checkout
|
|
||||||
uses: actions/checkout@v7
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Install Rust toolchain (${{ matrix.rust }})
|
|
||||||
uses: dtolnay/rust-toolchain@stable
|
|
||||||
with:
|
|
||||||
toolchain: ${{ matrix.rust }}
|
|
||||||
target: ${{ matrix.target }}
|
|
||||||
override: true
|
|
||||||
components: clippy
|
|
||||||
- name: Bisectability Check (default features)
|
|
||||||
if: ${{ github.event_name == 'pull_request' && matrix.target == 'x86_64-unknown-linux-gnu' }}
|
|
||||||
run: |
|
|
||||||
set -eufo pipefail
|
|
||||||
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: Clippy (kvm)
|
|
||||||
uses: houseabsolute/actions-rust-cross@v1
|
|
||||||
with:
|
|
||||||
command: clippy
|
|
||||||
toolchain: ${{ matrix.rust }}
|
|
||||||
target: ${{ matrix.target }}
|
|
||||||
args: --locked --all --all-targets --no-default-features --tests --examples --features "kvm" -- -D warnings
|
|
||||||
- name: Clippy (mshv)
|
|
||||||
uses: houseabsolute/actions-rust-cross@v1
|
|
||||||
with:
|
|
||||||
command: clippy
|
|
||||||
toolchain: ${{ matrix.rust }}
|
|
||||||
target: ${{ matrix.target }}
|
|
||||||
args: --locked --all --all-targets --no-default-features --tests --examples --features "mshv" -- -D warnings
|
|
||||||
- name: Clippy (mshv + kvm)
|
|
||||||
uses: houseabsolute/actions-rust-cross@v1
|
|
||||||
with:
|
|
||||||
command: clippy
|
|
||||||
toolchain: ${{ matrix.rust }}
|
|
||||||
target: ${{ matrix.target }}
|
|
||||||
args: --locked --all --all-targets --no-default-features --tests --examples --features "mshv,kvm" -- -D warnings
|
|
||||||
- name: Clippy (default features)
|
|
||||||
uses: houseabsolute/actions-rust-cross@v1
|
|
||||||
with:
|
|
||||||
command: clippy
|
|
||||||
toolchain: ${{ matrix.rust }}
|
|
||||||
target: ${{ matrix.target }}
|
|
||||||
args: --locked --all --all-targets --tests --examples -- -D warnings
|
|
||||||
- name: Clippy (default features + guest_debug)
|
|
||||||
uses: houseabsolute/actions-rust-cross@v1
|
|
||||||
with:
|
|
||||||
command: clippy
|
|
||||||
toolchain: ${{ matrix.rust }}
|
|
||||||
target: ${{ matrix.target }}
|
|
||||||
args: --locked --all --all-targets --tests --examples --features "guest_debug" -- -D warnings
|
|
||||||
- name: Clippy (default features + pvmemcontrol)
|
|
||||||
uses: houseabsolute/actions-rust-cross@v1
|
|
||||||
with:
|
|
||||||
command: clippy
|
|
||||||
toolchain: ${{ matrix.rust }}
|
|
||||||
target: ${{ matrix.target }}
|
|
||||||
args: --locked --all --all-targets --tests --examples --features "pvmemcontrol" -- -D warnings
|
|
||||||
- name: Clippy (default features + tracing)
|
|
||||||
uses: houseabsolute/actions-rust-cross@v1
|
|
||||||
with:
|
|
||||||
command: clippy
|
|
||||||
toolchain: ${{ matrix.rust }}
|
|
||||||
target: ${{ matrix.target }}
|
|
||||||
args: --locked --all --all-targets --tests --examples --features "tracing" -- -D warnings
|
|
||||||
- name: Clippy (default features + fw_cfg)
|
|
||||||
uses: houseabsolute/actions-rust-cross@v1
|
|
||||||
with:
|
|
||||||
command: clippy
|
|
||||||
toolchain: ${{ matrix.rust }}
|
|
||||||
target: ${{ matrix.target }}
|
|
||||||
args: --target=${{ matrix.target }} --locked --all --all-targets --tests --examples --features "fw_cfg" -- -D warnings
|
|
||||||
- name: Clippy (default features + ivshmem)
|
|
||||||
uses: houseabsolute/actions-rust-cross@v1
|
|
||||||
with:
|
|
||||||
command: clippy
|
|
||||||
toolchain: ${{ matrix.rust }}
|
|
||||||
target: ${{ matrix.target }}
|
|
||||||
args: --locked --all --all-targets --tests --examples --features "ivshmem" -- -D warnings
|
|
||||||
- name: Clippy (kvm + sev_snp)
|
|
||||||
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
|
|
||||||
uses: houseabsolute/actions-rust-cross@v1
|
|
||||||
with:
|
|
||||||
command: clippy
|
|
||||||
toolchain: ${{ matrix.rust }}
|
|
||||||
target: ${{ matrix.target }}
|
|
||||||
args: --locked --all --all-targets --no-default-features --tests --examples --features "kvm,sev_snp" -- -D warnings
|
|
||||||
- name: Clippy (mshv + sev_snp)
|
|
||||||
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
|
|
||||||
uses: houseabsolute/actions-rust-cross@v1
|
|
||||||
with:
|
|
||||||
command: clippy
|
|
||||||
toolchain: ${{ matrix.rust }}
|
|
||||||
target: ${{ matrix.target }}
|
|
||||||
args: --locked --all --all-targets --no-default-features --tests --examples --features "mshv,sev_snp" -- -D warnings
|
|
||||||
- name: Clippy (mshv + igvm + sev_snp)
|
|
||||||
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
|
|
||||||
uses: houseabsolute/actions-rust-cross@v1
|
|
||||||
with:
|
|
||||||
command: clippy
|
|
||||||
toolchain: ${{ matrix.rust }}
|
|
||||||
target: ${{ matrix.target }}
|
|
||||||
args: --locked --all --all-targets --no-default-features --tests --examples --features "mshv,igvm,sev_snp" -- -D warnings
|
|
||||||
- name: Clippy (kvm + igvm)
|
|
||||||
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
|
|
||||||
uses: houseabsolute/actions-rust-cross@v1
|
|
||||||
with:
|
|
||||||
command: clippy
|
|
||||||
toolchain: ${{ matrix.rust }}
|
|
||||||
target: ${{ matrix.target }}
|
|
||||||
args: --locked --all --all-targets --no-default-features --tests --examples --features "kvm,igvm" -- -D warnings
|
|
||||||
- name: Clippy (mshv + igvm)
|
|
||||||
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
|
|
||||||
uses: houseabsolute/actions-rust-cross@v1
|
|
||||||
with:
|
|
||||||
command: clippy
|
|
||||||
toolchain: ${{ matrix.rust }}
|
|
||||||
target: ${{ matrix.target }}
|
|
||||||
args: --locked --all --all-targets --no-default-features --tests --examples --features "mshv,igvm" -- -D warnings
|
|
||||||
- name: Clippy (kvm + igvm + sev_snp + fw_cfg)
|
|
||||||
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
|
|
||||||
uses: houseabsolute/actions-rust-cross@v1
|
|
||||||
with:
|
|
||||||
command: clippy
|
|
||||||
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
|
|
||||||
toolchain: ${{ matrix.rust }}
|
|
||||||
target: ${{ matrix.target }}
|
|
||||||
args: --locked --all --all-targets --no-default-features --tests --examples --features "kvm,igvm,sev_snp,fw_cfg" -- -D warnings
|
|
||||||
- name: Clippy (default features + sev_snp + igvm + fw_cfg)
|
|
||||||
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
|
|
||||||
uses: houseabsolute/actions-rust-cross@v1
|
|
||||||
with:
|
|
||||||
command: clippy
|
|
||||||
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
|
|
||||||
toolchain: ${{ matrix.rust }}
|
|
||||||
target: ${{ matrix.target }}
|
|
||||||
args: --locked --all --all-targets --tests --examples --features "sev_snp,igvm,fw_cfg" -- -D warnings
|
|
||||||
- name: Check build did not modify any files
|
|
||||||
run: test -z "$(git status --porcelain)"
|
|
||||||
build:
|
|
||||||
name: build
|
|
||||||
needs: [preflight]
|
|
||||||
if: needs.preflight.outputs.full == 'true'
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
rust:
|
|
||||||
- stable
|
|
||||||
- beta
|
|
||||||
- nightly
|
|
||||||
- "1.89.0" # MSRV — keep quoted.
|
|
||||||
target:
|
|
||||||
- x86_64-unknown-linux-gnu
|
|
||||||
- x86_64-unknown-linux-musl
|
|
||||||
steps:
|
|
||||||
- name: Code checkout
|
|
||||||
uses: actions/checkout@v7
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Install musl-gcc
|
|
||||||
run: sudo apt install -y musl-tools
|
|
||||||
- name: Install Rust toolchain (${{ matrix.rust }})
|
|
||||||
uses: dtolnay/rust-toolchain@stable
|
|
||||||
with:
|
|
||||||
toolchain: ${{ matrix.rust }}
|
|
||||||
target: ${{ matrix.target }}
|
|
||||||
- name: Build (default features)
|
|
||||||
run: cargo build --locked --bin cloud-hypervisor
|
|
||||||
- name: Build (kvm)
|
|
||||||
run: cargo build --locked --bin cloud-hypervisor --no-default-features --features "kvm"
|
|
||||||
- name: Build (default features + dbus_api)
|
|
||||||
run: cargo build --locked --bin cloud-hypervisor --features "dbus_api"
|
|
||||||
- name: Build (default features + guest_debug)
|
|
||||||
run: cargo build --locked --bin cloud-hypervisor --features "guest_debug"
|
|
||||||
- name: Build (default features + pvmemcontrol)
|
|
||||||
run: cargo build --locked --bin cloud-hypervisor --features "pvmemcontrol"
|
|
||||||
- name: Build (default features + fw_cfg)
|
|
||||||
run: cargo build --locked --bin cloud-hypervisor --features "fw_cfg"
|
|
||||||
- name: Build (default features + ivshmem)
|
|
||||||
run: cargo build --locked --bin cloud-hypervisor --features "ivshmem"
|
|
||||||
- name: Build (mshv)
|
|
||||||
run: cargo build --locked --bin cloud-hypervisor --no-default-features --features "mshv"
|
|
||||||
- name: Build (mshv + igvm)
|
|
||||||
run: cargo build --locked --bin cloud-hypervisor --no-default-features --features "mshv,igvm"
|
|
||||||
- name: Build (mshv + sev_snp)
|
|
||||||
run: cargo build --locked --bin cloud-hypervisor --no-default-features --features "mshv,sev_snp"
|
|
||||||
- name: Build (mshv + igvm + sev_snp)
|
|
||||||
run: cargo build --locked --bin cloud-hypervisor --no-default-features --features "mshv,igvm,sev_snp"
|
|
||||||
- name: Build (kvm + sev_snp)
|
|
||||||
run: cargo build --locked --bin cloud-hypervisor --no-default-features --features "kvm,sev_snp"
|
|
||||||
- name: Build (kvm + igvm + sev_snp + fw_cfg)
|
|
||||||
run: cargo build --locked --bin cloud-hypervisor --no-default-features --features "kvm,igvm,sev_snp,fw_cfg"
|
|
||||||
- name: Build (kvm + igvm)
|
|
||||||
run: cargo build --locked --bin cloud-hypervisor --no-default-features --features "kvm,igvm"
|
|
||||||
- name: Build (mshv + kvm)
|
|
||||||
run: cargo build --locked --bin cloud-hypervisor --no-default-features --features "mshv,kvm"
|
|
||||||
- name: Release Build (default features)
|
|
||||||
run: cargo build --locked --all --release --target=${{ matrix.target }}
|
|
||||||
- name: Check build did not modify any files
|
|
||||||
run: test -z "$(git status --porcelain)"
|
|
||||||
build-riscv64:
|
|
||||||
name: build-riscv64
|
|
||||||
needs: [preflight]
|
|
||||||
if: needs.preflight.outputs.full == 'true'
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
rust:
|
|
||||||
- stable
|
|
||||||
- "1.89.0" # MSRV — keep quoted.
|
|
||||||
env:
|
|
||||||
CARGO_TARGET_RISCV64GC_UNKNOWN_LINUX_GNU_LINKER: riscv64-linux-gnu-gcc
|
|
||||||
steps:
|
|
||||||
- name: Code checkout
|
|
||||||
uses: actions/checkout@v7
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Install riscv64 cross linker
|
|
||||||
run: sudo apt-get update && sudo apt-get install -y gcc-riscv64-linux-gnu
|
|
||||||
- name: Install Rust toolchain (${{ matrix.rust }})
|
|
||||||
uses: dtolnay/rust-toolchain@stable
|
|
||||||
with:
|
|
||||||
toolchain: ${{ matrix.rust }}
|
|
||||||
target: riscv64gc-unknown-linux-gnu
|
|
||||||
- name: Build (kvm)
|
|
||||||
run: cargo build --locked --package cloud-hypervisor --no-default-features --features "kvm" --target riscv64gc-unknown-linux-gnu
|
|
||||||
- name: Check build did not modify any files
|
|
||||||
run: test -z "$(git status --porcelain)"
|
|
||||||
# garm-jammy + gnu: runs on PR and MQ. Other 3 matrix entries are in
|
|
||||||
# integration-x86-64-mq (sibling, MQ-only, runs in parallel).
|
|
||||||
integration-x86-64-pr:
|
|
||||||
name: integration-x86-64-pr
|
|
||||||
needs: [preflight, dco, quality, build]
|
|
||||||
if: >-
|
|
||||||
needs.preflight.outputs.full == 'true' && needs.dco.result == 'success' && needs.quality.result == 'success' && needs.build.result == 'success'
|
|
||||||
timeout-minutes: 80
|
|
||||||
env:
|
|
||||||
# Our runner has 16 cores (nproc).
|
|
||||||
# We limit parallelism only to avoid exhausting disk space and memory
|
|
||||||
# resources, not to save CPU resources.
|
|
||||||
PARALLEL_INTEGRATION_TESTS_NUM: 12
|
|
||||||
runs-on: garm-jammy-16
|
|
||||||
steps:
|
|
||||||
- name: Code checkout
|
|
||||||
uses: actions/checkout@v7
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Install Docker
|
|
||||||
run: |
|
|
||||||
set -eufo pipefail
|
|
||||||
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
|
|
||||||
run: scripts/prepare_vdpa.sh
|
|
||||||
- name: Run unit tests
|
|
||||||
run: scripts/dev_cli.sh tests --unit --libc gnu
|
|
||||||
- name: Load openvswitch module
|
|
||||||
run: sudo modprobe openvswitch
|
|
||||||
- name: Run integration tests
|
|
||||||
timeout-minutes: 60
|
|
||||||
run: scripts/dev_cli.sh tests --integration --libc gnu
|
|
||||||
# MQ-only: the 3 matrix entries that integration-x86-64-pr does not cover.
|
|
||||||
integration-x86-64-mq:
|
|
||||||
name: integration-x86-64-mq
|
|
||||||
needs: [preflight, dco, quality, build]
|
|
||||||
if: >-
|
|
||||||
github.event_name == 'merge_group' && needs.preflight.outputs.full == 'true' && needs.dco.result == 'success' && needs.quality.result == 'success' && needs.build.result == 'success'
|
|
||||||
timeout-minutes: 80
|
|
||||||
env:
|
|
||||||
# Our runner has 16 cores (nproc).
|
|
||||||
# We limit parallelism only to avoid exhausting disk space and memory
|
|
||||||
# resources, not to save CPU resources.
|
|
||||||
PARALLEL_INTEGRATION_TESTS_NUM: 12
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- {runner: garm-jammy, libc: musl}
|
|
||||||
- {runner: garm-jammy-amd, libc: gnu}
|
|
||||||
- {runner: garm-jammy-amd, libc: musl}
|
|
||||||
# format() because `${{ matrix.runner }}-16` is not valid in runs-on.
|
|
||||||
runs-on: ${{ format('{0}-16', matrix.runner) }}
|
|
||||||
steps:
|
|
||||||
- name: Code checkout
|
|
||||||
uses: actions/checkout@v7
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Install Docker
|
|
||||||
run: |
|
|
||||||
set -eufo pipefail
|
|
||||||
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
|
|
||||||
run: scripts/prepare_vdpa.sh
|
|
||||||
- name: Run unit tests
|
|
||||||
run: scripts/dev_cli.sh tests --unit --libc ${{ matrix.libc }}
|
|
||||||
- name: Load openvswitch module
|
|
||||||
run: sudo modprobe openvswitch
|
|
||||||
- name: Run integration tests
|
|
||||||
timeout-minutes: 60
|
|
||||||
run: scripts/dev_cli.sh tests --integration --libc ${{ matrix.libc }}
|
|
||||||
integration-arm64:
|
|
||||||
name: integration-arm64
|
|
||||||
needs: [preflight, dco, quality, build]
|
|
||||||
if: >-
|
|
||||||
github.event_name == 'merge_group' && needs.preflight.outputs.full == 'true' && needs.dco.result == 'success' && needs.quality.result == 'success' && needs.build.result == 'success'
|
|
||||||
timeout-minutes: 120
|
|
||||||
env:
|
|
||||||
# Our runner has 80 cores (nproc).
|
|
||||||
# We limit parallelism only to avoid exhausting disk space and memory
|
|
||||||
# resources, not to save CPU resources.
|
|
||||||
PARALLEL_INTEGRATION_TESTS_NUM: 25
|
|
||||||
runs-on: bookworm-arm64
|
|
||||||
steps:
|
|
||||||
# arm64 runner user is "runner" (vfio's is "github-runner").
|
|
||||||
- name: Fix workspace permissions
|
|
||||||
run: sudo chown -R runner:runner ${GITHUB_WORKSPACE}
|
|
||||||
- name: Code checkout
|
|
||||||
uses: actions/checkout@v7
|
|
||||||
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
|
|
||||||
run: |
|
|
||||||
set -eufo pipefail
|
|
||||||
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
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
set -eufo pipefail
|
|
||||||
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-25h2-6.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"
|
|
||||||
rm -f "$IMG_PATH" "$IMG_GZ_PATH"
|
|
||||||
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
|
|
||||||
timeout-minutes: 30
|
|
||||||
run: scripts/dev_cli.sh tests --integration-windows --libc musl
|
|
||||||
integration-vfio:
|
|
||||||
name: integration-vfio
|
|
||||||
needs: [preflight, dco, quality, build]
|
|
||||||
if: >-
|
|
||||||
github.event_name == 'merge_group' && needs.preflight.outputs.full == 'true' && needs.dco.result == 'success' && needs.quality.result == 'success' && needs.build.result == 'success'
|
|
||||||
runs-on: vfio-nvidia
|
|
||||||
env:
|
|
||||||
AUTH_DOWNLOAD_TOKEN: ${{ secrets.AUTH_DOWNLOAD_TOKEN }}
|
|
||||||
steps:
|
|
||||||
# vfio-nvidia runner user is "github-runner" (not "runner" like arm64).
|
|
||||||
- name: Fix workspace permissions
|
|
||||||
run: sudo chown -R github-runner:github-runner "${GITHUB_WORKSPACE}"
|
|
||||||
- name: Code checkout
|
|
||||||
uses: actions/checkout@v7
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Run VFIO integration tests
|
|
||||||
timeout-minutes: 25
|
|
||||||
run: scripts/dev_cli.sh tests --integration-vfio
|
|
||||||
# Most tests are failing with musl, see #6790
|
|
||||||
# - name: Run VFIO integration tests for musl
|
|
||||||
# timeout-minutes: 25
|
|
||||||
# run: scripts/dev_cli.sh tests --integration-vfio --libc musl
|
|
||||||
integration-windows:
|
|
||||||
name: integration-windows
|
|
||||||
needs: [preflight, dco, quality, build]
|
|
||||||
if: >-
|
|
||||||
github.event_name == 'merge_group' && needs.preflight.outputs.full == 'true' && needs.dco.result == 'success' && needs.quality.result == 'success' && needs.build.result == 'success'
|
|
||||||
runs-on: garm-jammy-16
|
|
||||||
steps:
|
|
||||||
- name: Code checkout
|
|
||||||
uses: actions/checkout@v7
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Install Docker
|
|
||||||
run: |
|
|
||||||
set -eufo pipefail
|
|
||||||
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
|
|
||||||
run: |
|
|
||||||
set -eufo pipefail
|
|
||||||
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
|
|
||||||
run: |
|
|
||||||
set -eufo pipefail
|
|
||||||
mkdir $HOME/workloads
|
|
||||||
az storage blob download --container-name private-images --file "$HOME/workloads/windows-server-2025-amd64-1.raw" --name windows-server-2025-amd64-1.raw --connection-string "${{ secrets.CH_PRIVATE_IMAGES }}"
|
|
||||||
- name: Run Windows guest integration tests
|
|
||||||
timeout-minutes: 15
|
|
||||||
run: scripts/dev_cli.sh tests --integration-windows
|
|
||||||
- name: Run Windows guest integration tests for musl
|
|
||||||
timeout-minutes: 15
|
|
||||||
run: scripts/dev_cli.sh tests --integration-windows --libc musl
|
|
||||||
integration-mshv-x86-64:
|
|
||||||
name: integration-mshv-x86-64
|
|
||||||
needs: [preflight, dco, quality, build]
|
|
||||||
if: >-
|
|
||||||
github.event_name == 'merge_group' && needs.preflight.outputs.full == 'true' && needs.dco.result == 'success' && needs.quality.result == 'success' && needs.build.result == 'success'
|
|
||||||
timeout-minutes: 50
|
|
||||||
runs-on: mshv
|
|
||||||
steps:
|
|
||||||
# mshv runner user is "lsgunner"
|
|
||||||
- name: Fix workspace and Docker socket permissions
|
|
||||||
run: |
|
|
||||||
sudo chown -R lsgrunner:lsgrunner ${GITHUB_WORKSPACE}
|
|
||||||
sudo chmod 666 /var/run/docker.sock
|
|
||||||
- name: Code checkout
|
|
||||||
uses: actions/checkout@v7
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Prepare for VDPA
|
|
||||||
run: scripts/prepare_vdpa.sh
|
|
||||||
- name: Run integration tests
|
|
||||||
timeout-minutes: 45
|
|
||||||
run: scripts/dev_cli.sh tests --integration
|
|
||||||
# Rate-limiter host is not available
|
|
||||||
# integration-rate-limiter:
|
|
||||||
# name: integration-rate-limiter
|
|
||||||
# needs: [preflight, dco, quality, build]
|
|
||||||
# if: >-
|
|
||||||
# github.event_name == 'merge_group' && needs.preflight.outputs.full == 'true' && needs.dco.result == 'success' && needs.quality.result == 'success' && needs.build.result == 'success'
|
|
||||||
# runs-on: bare-metal-9950x
|
|
||||||
# env:
|
|
||||||
# AUTH_DOWNLOAD_TOKEN: ${{ secrets.AUTH_DOWNLOAD_TOKEN }}
|
|
||||||
# steps:
|
|
||||||
# - name: Code checkout
|
|
||||||
# uses: actions/checkout@v7
|
|
||||||
# with:
|
|
||||||
# fetch-depth: 0
|
|
||||||
# - name: Run rate-limiter integration tests
|
|
||||||
# timeout-minutes: 20
|
|
||||||
# run: scripts/dev_cli.sh tests --integration-rate-limiter
|
|
||||||
integration-sev-snp:
|
|
||||||
name: integration-sev-snp
|
|
||||||
needs: [preflight, dco, quality, build]
|
|
||||||
if: >-
|
|
||||||
github.event_name == 'merge_group' && needs.preflight.outputs.full == 'true' && needs.dco.result == 'success' && needs.quality.result == 'success' && needs.build.result == 'success'
|
|
||||||
timeout-minutes: 30
|
|
||||||
runs-on: noble-sevsnp
|
|
||||||
steps:
|
|
||||||
# Self-hosted runners reuse their workdir; a previous privileged
|
|
||||||
# container run can leave root-owned files behind.
|
|
||||||
- name: Fix workspace permissions
|
|
||||||
run: sudo chown -R "$(id -un):$(id -gn)" "${GITHUB_WORKSPACE}"
|
|
||||||
- name: Code checkout
|
|
||||||
uses: actions/checkout@v7
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Sanity-check SEV-SNP prerequisites
|
|
||||||
run: |
|
|
||||||
set -eufo pipefail
|
|
||||||
echo "Checking hypervisor device nodes..."
|
|
||||||
test -e /dev/kvm || { echo "::error::/dev/kvm missing"; exit 1; }
|
|
||||||
test -e /dev/sev || { echo "::error::/dev/sev missing"; exit 1; }
|
|
||||||
echo "Checking staged IGVM/kernel artifacts..."
|
|
||||||
test -d /usr/share/cloud-hypervisor/cvm \
|
|
||||||
|| { echo "::error::/usr/share/cloud-hypervisor/cvm missing"; exit 1; }
|
|
||||||
ls -l /usr/share/cloud-hypervisor/cvm
|
|
||||||
- name: Run CVM (SEV-SNP) integration tests
|
|
||||||
timeout-minutes: 20
|
|
||||||
run: scripts/dev_cli.sh tests --integration-cvm --hypervisor kvm
|
|
||||||
# Rate-limiter host is not available
|
|
||||||
# integration-rate-limiter:
|
|
||||||
# name: integration-rate-limiter
|
|
||||||
# needs: [preflight, dco, quality, build]
|
|
||||||
# if: >-
|
|
||||||
# github.event_name == 'merge_group' && needs.preflight.outputs.full == 'true' && needs.dco.result == 'success' && needs.quality.result == 'success' && needs.build.result == 'success'
|
|
||||||
# runs-on: bare-metal-9950x
|
|
||||||
# env:
|
|
||||||
# AUTH_DOWNLOAD_TOKEN: ${{ secrets.AUTH_DOWNLOAD_TOKEN }}
|
|
||||||
# steps:
|
|
||||||
# - name: Code checkout
|
|
||||||
# uses: actions/checkout@v7
|
|
||||||
# with:
|
|
||||||
# fetch-depth: 0
|
|
||||||
# - name: Run rate-limiter integration tests
|
|
||||||
# timeout-minutes: 20
|
|
||||||
# run: scripts/dev_cli.sh tests --integration-rate-limiter
|
|
||||||
virtio-villain:
|
|
||||||
name: virtio-villain
|
|
||||||
needs: [preflight, dco, quality, build]
|
|
||||||
if: needs.preflight.outputs.full == 'true'
|
|
||||||
timeout-minutes: 60
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
env:
|
|
||||||
VILLAIN_REPO: https://github.com/weltling/virtio-villain.git
|
|
||||||
VILLAIN_REF: v0.6.4
|
|
||||||
steps:
|
|
||||||
- name: Code checkout
|
|
||||||
uses: actions/checkout@v7
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Verify KVM is available
|
|
||||||
run: |
|
|
||||||
set -eufo pipefail
|
|
||||||
test -e /dev/kvm || { echo "::error::/dev/kvm missing on runner"; exit 1; }
|
|
||||||
- name: Install Rust toolchain
|
|
||||||
uses: dtolnay/rust-toolchain@stable
|
|
||||||
- name: Install dependencies
|
|
||||||
run: |
|
|
||||||
set -eufo pipefail
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install -y musl-tools cpio gzip python3
|
|
||||||
sudo apt-get install -y virtiofsd || true
|
|
||||||
- name: Build cloud-hypervisor (kvm)
|
|
||||||
run: cargo build --locked --release --bin cloud-hypervisor --no-default-features --features kvm
|
|
||||||
- name: Clone virtio-villain
|
|
||||||
id: villain-src
|
|
||||||
run: |
|
|
||||||
set -eufo pipefail
|
|
||||||
git clone "$VILLAIN_REPO" virtio-villain
|
|
||||||
git -C virtio-villain checkout "$VILLAIN_REF"
|
|
||||||
echo "sha=$(git -C virtio-villain rev-parse HEAD)" >> "$GITHUB_OUTPUT"
|
|
||||||
- name: Cache virtio-villain build
|
|
||||||
id: villain-cache
|
|
||||||
uses: actions/cache@v6
|
|
||||||
with:
|
|
||||||
path: virtio-villain/target
|
|
||||||
key: virtio-villain-${{ runner.os }}-${{ runner.arch }}-${{ steps.villain-src.outputs.sha }}
|
|
||||||
- name: Build virtio-villain initramfs
|
|
||||||
if: steps.villain-cache.outputs.cache-hit != 'true'
|
|
||||||
run: make -C virtio-villain -j"$(nproc)" initramfs
|
|
||||||
- name: Run virtio-villain suite
|
|
||||||
working-directory: virtio-villain
|
|
||||||
run: |
|
|
||||||
set -eufo pipefail
|
|
||||||
mkdir -p villain-logs
|
|
||||||
sudo ./run \
|
|
||||||
--vmm "${GITHUB_WORKSPACE}/target/release/cloud-hypervisor" \
|
|
||||||
--blk-queues 2 --net-queues 2 --cpus 2 --memory 256M \
|
|
||||||
--order=fast \
|
|
||||||
--jobs 4 --batch 10 --timeout 45 --retries 2 --log-dir villain-logs \
|
|
||||||
--format junit --output villain-logs/results.xml \
|
|
||||||
| tee villain-logs/run.out
|
|
||||||
- name: Publish results to run summary
|
|
||||||
if: always()
|
|
||||||
working-directory: virtio-villain
|
|
||||||
run: |
|
|
||||||
set -eufo pipefail
|
|
||||||
{
|
|
||||||
echo '## virtio-villain'
|
|
||||||
echo '```'
|
|
||||||
if [ -f villain-logs/run.out ]; then
|
|
||||||
sed -n '/tests passed/,$p' villain-logs/run.out
|
|
||||||
else
|
|
||||||
echo 'no results (suite did not produce output)'
|
|
||||||
fi
|
|
||||||
echo '```'
|
|
||||||
} >> "$GITHUB_STEP_SUMMARY"
|
|
||||||
- name: Upload virtio-villain logs
|
|
||||||
if: always()
|
|
||||||
uses: actions/upload-artifact@v7
|
|
||||||
with:
|
|
||||||
name: virtio-villain-logs
|
|
||||||
path: virtio-villain/villain-logs
|
|
||||||
if-no-files-found: ignore
|
|
||||||
# The single required-status check. Branch protection requires this one job.
|
|
||||||
all-green:
|
|
||||||
name: all-green
|
|
||||||
needs:
|
|
||||||
- audit
|
|
||||||
- build
|
|
||||||
- build-riscv64
|
|
||||||
- dco
|
|
||||||
- formatting
|
|
||||||
- fuzz-build
|
|
||||||
- gitlint
|
|
||||||
- hadolint
|
|
||||||
- integration-arm64
|
|
||||||
- integration-sev-snp
|
|
||||||
- integration-vfio
|
|
||||||
- integration-mshv-x86-64
|
|
||||||
- integration-windows
|
|
||||||
- integration-x86-64-mq
|
|
||||||
- integration-x86-64-pr
|
|
||||||
- openapi
|
|
||||||
- package-consistency
|
|
||||||
- preflight
|
|
||||||
- quality
|
|
||||||
- reuse
|
|
||||||
- shlint
|
|
||||||
- taplo
|
|
||||||
- typos
|
|
||||||
if: always()
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Verify all dependencies succeeded or were skipped
|
|
||||||
env:
|
|
||||||
NEEDS_JSON: ${{ toJson(needs) }}
|
|
||||||
run: |
|
|
||||||
set -eufo pipefail
|
|
||||||
echo "$NEEDS_JSON" | jq .
|
|
||||||
# success or skipped = pass; failure or cancelled = red.
|
|
||||||
echo "$NEEDS_JSON" | jq -e '
|
|
||||||
to_entries
|
|
||||||
| map(select(.value.result != "success" and .value.result != "skipped"))
|
|
||||||
| length == 0
|
|
||||||
' >/dev/null
|
|
||||||
119
.github/workflows/docker-image.yaml
vendored
119
.github/workflows/docker-image.yaml
vendored
@@ -1,119 +0,0 @@
|
|||||||
name: Cloud Hypervisor's Docker image update
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: main
|
|
||||||
paths: resources/Dockerfile
|
|
||||||
pull_request:
|
|
||||||
paths: resources/Dockerfile
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ github.event_name }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
env:
|
|
||||||
REGISTRY: ghcr.io
|
|
||||||
IMAGE_NAME: ${{ github.repository }}
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
platform:
|
|
||||||
- linux/amd64
|
|
||||||
- linux/arm64
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Prepare
|
|
||||||
run: |
|
|
||||||
platform=${{ matrix.platform }}
|
|
||||||
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Code checkout
|
|
||||||
uses: actions/checkout@v7
|
|
||||||
|
|
||||||
- name: Docker meta
|
|
||||||
id: meta
|
|
||||||
uses: docker/metadata-action@v6
|
|
||||||
with:
|
|
||||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
|
||||||
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v4
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v4
|
|
||||||
|
|
||||||
- name: Login to ghcr
|
|
||||||
if: ${{ github.event_name == 'push' }}
|
|
||||||
uses: docker/login-action@v4.6.0
|
|
||||||
with:
|
|
||||||
registry: ${{ env.REGISTRY }}
|
|
||||||
username: ${{ github.actor }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Build and push by digest
|
|
||||||
id: build
|
|
||||||
uses: docker/build-push-action@v7
|
|
||||||
with:
|
|
||||||
file: ./resources/Dockerfile
|
|
||||||
platforms: ${{ matrix.platform }}
|
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
|
||||||
outputs: type=image,name=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=${{ github.event_name == 'push' }}
|
|
||||||
|
|
||||||
- name: Export digest
|
|
||||||
if: ${{ github.event_name == 'push' }}
|
|
||||||
run: |
|
|
||||||
mkdir -p /tmp/digests
|
|
||||||
digest="${{ steps.build.outputs.digest }}"
|
|
||||||
touch "/tmp/digests/${digest#sha256:}"
|
|
||||||
|
|
||||||
- name: Upload digest
|
|
||||||
if: ${{ github.event_name == 'push' }}
|
|
||||||
uses: actions/upload-artifact@v7
|
|
||||||
with:
|
|
||||||
name: digests-${{ env.PLATFORM_PAIR }}
|
|
||||||
path: /tmp/digests/*
|
|
||||||
if-no-files-found: error
|
|
||||||
retention-days: 1
|
|
||||||
|
|
||||||
merge:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: build
|
|
||||||
if: ${{ github.event_name == 'push' }}
|
|
||||||
steps:
|
|
||||||
- name: Download digests
|
|
||||||
uses: actions/download-artifact@v8
|
|
||||||
with:
|
|
||||||
path: /tmp/digests
|
|
||||||
pattern: digests-*
|
|
||||||
merge-multiple: true
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v4
|
|
||||||
|
|
||||||
- name: Docker meta
|
|
||||||
id: meta
|
|
||||||
uses: docker/metadata-action@v6
|
|
||||||
with:
|
|
||||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
|
||||||
# generate Docker tags based on the following events/attributes
|
|
||||||
tags: |
|
|
||||||
type=raw,value=20260522-0
|
|
||||||
type=sha
|
|
||||||
|
|
||||||
- name: Login to ghcr
|
|
||||||
uses: docker/login-action@v4.6.0
|
|
||||||
with:
|
|
||||||
registry: ${{ env.REGISTRY }}
|
|
||||||
username: ${{ github.actor }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Create manifest list and push
|
|
||||||
working-directory: /tmp/digests
|
|
||||||
run: |
|
|
||||||
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
|
|
||||||
$(printf '${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *)
|
|
||||||
|
|
||||||
- name: Inspect image
|
|
||||||
run: |
|
|
||||||
docker buildx imagetools inspect ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }}
|
|
||||||
32
.github/workflows/integration-metrics.yaml
vendored
32
.github/workflows/integration-metrics.yaml
vendored
@@ -1,32 +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@v7
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Install Docker
|
|
||||||
run: |
|
|
||||||
set -eufo pipefail
|
|
||||||
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 -- --test-exclude micro_,block_qcow2 -- --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'
|
|
||||||
95
.github/workflows/release.yaml
vendored
95
.github/workflows/release.yaml
vendored
@@ -1,95 +0,0 @@
|
|||||||
name: Cloud Hypervisor Release
|
|
||||||
on: [create, merge_group]
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ github.event_name }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ github.token }}
|
|
||||||
|
|
||||||
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
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Code checkout
|
|
||||||
uses: actions/checkout@v7
|
|
||||||
- name: Install musl-gcc
|
|
||||||
if: contains(matrix.platform.target, 'musl')
|
|
||||||
run: sudo apt install -y musl-tools
|
|
||||||
- name: Create release directory
|
|
||||||
if: |
|
|
||||||
github.event_name == 'create' && github.event.ref_type == 'tag' &&
|
|
||||||
matrix.platform.target == 'x86_64-unknown-linux-gnu'
|
|
||||||
run: rsync -rv --exclude=.git . ../cloud-hypervisor-${{ github.event.ref }}
|
|
||||||
- name: Build ${{ matrix.platform.target }}
|
|
||||||
uses: houseabsolute/actions-rust-cross@v1
|
|
||||||
with:
|
|
||||||
command: build
|
|
||||||
target: ${{ matrix.platform.target }}
|
|
||||||
args: ${{ matrix.platform.args }}
|
|
||||||
strip: true
|
|
||||||
toolchain: "1.89.0"
|
|
||||||
- name: Copy Release Binaries
|
|
||||||
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
cp target/${{ matrix.platform.target }}/release/cloud-hypervisor ./${{ matrix.platform.name_ch }}
|
|
||||||
cp target/${{ matrix.platform.target }}/release/ch-remote ./${{ matrix.platform.name_ch_remote }}
|
|
||||||
- name: Upload Release Artifacts
|
|
||||||
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
|
||||||
uses: actions/upload-artifact@v7
|
|
||||||
with:
|
|
||||||
name: Artifacts for ${{ matrix.platform.target }}
|
|
||||||
path: |
|
|
||||||
./${{ matrix.platform.name_ch }}
|
|
||||||
./${{ matrix.platform.name_ch_remote }}
|
|
||||||
- name: Vendor
|
|
||||||
if: |
|
|
||||||
github.event_name == 'create' && github.event.ref_type == 'tag' &&
|
|
||||||
matrix.platform.target == 'x86_64-unknown-linux-gnu'
|
|
||||||
working-directory: ../cloud-hypervisor-${{ github.event.ref }}
|
|
||||||
run: |
|
|
||||||
mkdir ../vendor-cargo-home
|
|
||||||
export CARGO_HOME=$(realpath ../vendor-cargo-home)
|
|
||||||
mkdir .cargo
|
|
||||||
cargo vendor > .cargo/config.toml
|
|
||||||
- name: Create vendored source archive
|
|
||||||
if: |
|
|
||||||
github.event_name == 'create' && github.event.ref_type == 'tag' &&
|
|
||||||
matrix.platform.target == 'x86_64-unknown-linux-gnu'
|
|
||||||
run: tar cJf cloud-hypervisor-${{ github.event.ref }}.tar.xz ../cloud-hypervisor-${{ github.event.ref }}
|
|
||||||
- name: Upload cloud-hypervisor vendored source archive
|
|
||||||
if: |
|
|
||||||
github.event_name == 'create' && github.event.ref_type == 'tag' &&
|
|
||||||
matrix.platform.target == 'x86_64-unknown-linux-gnu'
|
|
||||||
id: upload-release-cloud-hypervisor-vendored-sources
|
|
||||||
uses: actions/upload-artifact@v7
|
|
||||||
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@v3
|
|
||||||
with:
|
|
||||||
draft: true
|
|
||||||
files: |
|
|
||||||
./${{ matrix.platform.name_ch }}
|
|
||||||
./${{ matrix.platform.name_ch_remote }}
|
|
||||||
./cloud-hypervisor-${{ github.event.ref }}.tar.xz
|
|
||||||
11
.gitignore
vendored
11
.gitignore
vendored
@@ -1,13 +1,4 @@
|
|||||||
|
/target
|
||||||
**/*.rs.bk
|
**/*.rs.bk
|
||||||
**/Cargo.lock
|
**/Cargo.lock
|
||||||
**/rusty-tags.vi
|
**/rusty-tags.vi
|
||||||
/.agents
|
|
||||||
/.cargo
|
|
||||||
/.claude
|
|
||||||
/.codex
|
|
||||||
/.vscode
|
|
||||||
/build
|
|
||||||
/rpm/SOURCES
|
|
||||||
/target
|
|
||||||
/vendor
|
|
||||||
__pycache__
|
|
||||||
|
|||||||
13
.gitlint
13
.gitlint
@@ -1,13 +0,0 @@
|
|||||||
[general]
|
|
||||||
extra-path=scripts/gitlint/rules
|
|
||||||
regex-style-search=true
|
|
||||||
ignore=body-max-line-length,body-hard-tab
|
|
||||||
|
|
||||||
[ignore-by-author-name]
|
|
||||||
regex=dependabot
|
|
||||||
ignore=all
|
|
||||||
|
|
||||||
# default 72
|
|
||||||
[title-max-length]
|
|
||||||
line-length=72
|
|
||||||
|
|
||||||
36
.lychee.toml
36
.lychee.toml
@@ -1,36 +0,0 @@
|
|||||||
verbose = "info"
|
|
||||||
|
|
||||||
exclude_path = [".lychee.toml"]
|
|
||||||
|
|
||||||
exclude = [
|
|
||||||
# Availability of links below should be manually verified.
|
|
||||||
# Page for intel TDX support, returns 403 while querying.
|
|
||||||
'^https://www.intel.com/content/www/us/en/developer/tools/trust-domain-extensions/overview.html',
|
|
||||||
# Page for TPM, returns 403 while querying.
|
|
||||||
'^https://trustedcomputinggroup.org/wp-content/uploads/PC-Client-Specific-Platform-TPM-Profile-for-TPM-2p0-v1p05p_r14_pub.pdf',
|
|
||||||
# GitHub user smibarber referenced in `CREDITS.md` no longer exist
|
|
||||||
'^https://github.com/smibarber',
|
|
||||||
# OSDev has added bot protection and accesses my result in 403 Forbidden.
|
|
||||||
'^https://wiki.osdev.org',
|
|
||||||
# Exclude all pages with $ in the URL since $XXX is a variable
|
|
||||||
"\\$.*",
|
|
||||||
# Exclude local files
|
|
||||||
"file://.*",
|
|
||||||
# ARM documentation returns 403 Forbidden for automated CI checks.
|
|
||||||
'^http://infocenter\.arm\.com',
|
|
||||||
'^https://developer\.arm\.com',
|
|
||||||
# Ignore internal/unsupported protocols seen in logs
|
|
||||||
'^tcp://192\.168\.1\.10',
|
|
||||||
# Slack invite endpoints reject automated GETs and return 403.
|
|
||||||
'^https://join\.slack\.com/t/',
|
|
||||||
# Metrics publish endpoint only answers authenticated PUTs; a plain GET
|
|
||||||
# returns 404.
|
|
||||||
'^https://ch-metrics\.azurewebsites\.net/api/publishmetrics',
|
|
||||||
]
|
|
||||||
|
|
||||||
# Exclude loopback addresses
|
|
||||||
exclude_loopback = true
|
|
||||||
|
|
||||||
max_retries = 3
|
|
||||||
|
|
||||||
retry_wait_time = 5
|
|
||||||
12
.reuse/dep5
12
.reuse/dep5
@@ -1,12 +0,0 @@
|
|||||||
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
|
||||||
Upstream-Name: cloud-hypervisor
|
|
||||||
Upstream-Contact: <>
|
|
||||||
Source: https://www.cloudhypervisor.org
|
|
||||||
|
|
||||||
Files: docs/*.md *.md
|
|
||||||
Copyright: 2024
|
|
||||||
License: CC-BY-4.0
|
|
||||||
|
|
||||||
Files: scripts/* test_data/* *.toml .git* .editorconfig 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 +0,0 @@
|
|||||||
edition = "2024"
|
|
||||||
group_imports="StdExternalCrate"
|
|
||||||
imports_granularity="Module"
|
|
||||||
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
include = ["**/Cargo.toml"]
|
|
||||||
|
|
||||||
[formatting]
|
|
||||||
indent_string = " " # 2 spaces: keep in sync with .editorconfig
|
|
||||||
reorder_arrays = true
|
|
||||||
reorder_keys = true
|
|
||||||
16
.travis.yml
Normal file
16
.travis.yml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
language: rust
|
||||||
|
|
||||||
|
rust:
|
||||||
|
- stable
|
||||||
|
|
||||||
|
script:
|
||||||
|
- cargo build --release
|
||||||
|
|
||||||
|
deploy:
|
||||||
|
provider: releases
|
||||||
|
api_key: $GITHUB_OAUTH_TOKEN
|
||||||
|
file: target/release/cloud-hypervisor
|
||||||
|
skip_cleanup: true
|
||||||
|
draft: true
|
||||||
|
on:
|
||||||
|
tags: true
|
||||||
28
.typos.toml
28
.typos.toml
@@ -1,28 +0,0 @@
|
|||||||
# Configuration for https://github.com/crate-ci/typos
|
|
||||||
|
|
||||||
[files]
|
|
||||||
extend-exclude = [
|
|
||||||
"hypervisor/src/kvm/x86_64/mod.rs",
|
|
||||||
"resources/linux-config-*",
|
|
||||||
]
|
|
||||||
|
|
||||||
[default.extend-words]
|
|
||||||
CLASSE = "CLASSE"
|
|
||||||
Dake = "Dake"
|
|
||||||
EXTINT = "EXTINT"
|
|
||||||
INOUT = "INOUT"
|
|
||||||
MSIS = "MSIS" # MSIs (Message Signaled Interrupt)
|
|
||||||
SME = "SME" # Secure Memory Encryption
|
|
||||||
THR = "THR" # Transmitter Holding Register
|
|
||||||
TRANSLATER = "TRANSLATER"
|
|
||||||
ba = "ba"
|
|
||||||
conectix = "conectix"
|
|
||||||
liness = "liness"
|
|
||||||
outout = "outout"
|
|
||||||
|
|
||||||
[default.extend-identifiers]
|
|
||||||
consts = "consts"
|
|
||||||
fo = "fo"
|
|
||||||
fpr = "fpr"
|
|
||||||
# Public Linux API
|
|
||||||
msg_controllen = "msg_controllen"
|
|
||||||
79
AGENTS.md
79
AGENTS.md
@@ -1,79 +0,0 @@
|
|||||||
## For Humans
|
|
||||||
|
|
||||||
This is a compact [AGENTS.md](https://agents.md/) file for Cloud Hypervisor.
|
|
||||||
It is meant to help automated coding agents make useful changes that stay safe,
|
|
||||||
reviewable, and compatible with the project's normal engineering constraints.
|
|
||||||
|
|
||||||
## For LLMs
|
|
||||||
|
|
||||||
### Project Context
|
|
||||||
|
|
||||||
- Start with `README.md` for the project shape and `CONTRIBUTING.md` for the
|
|
||||||
contribution rules, coding style, commit message guidance, and LLM assistance
|
|
||||||
disclosure policy. Following `CONTRIBUTING.md` is crucial!
|
|
||||||
- Respect `.editorconfig` when editing files, in addition to any
|
|
||||||
language-specific formatter required by `CONTRIBUTING.md`.
|
|
||||||
|
|
||||||
### Change Guidelines
|
|
||||||
|
|
||||||
- Prefer correctness, safety, and readability over micro-optimizations. Keep
|
|
||||||
changes small, reviewable, and aligned with the existing crate/module
|
|
||||||
boundaries. Avoid speculative changes and unrelated refactoring.
|
|
||||||
- For API, config, migration, device model, or hypervisor boundary changes,
|
|
||||||
consider the effect on all architectures and all backends. Changes to one
|
|
||||||
backend can be okay if the other backend still functions properly and could
|
|
||||||
be extended or modified later.
|
|
||||||
- Follow Rust best practices and the style already present in the touched code.
|
|
||||||
- Avoid new dependencies unless the benefit is clear and local alternatives are
|
|
||||||
not enough.
|
|
||||||
- Preserve existing behavior unless the requested change explicitly needs a
|
|
||||||
behavior change; refactors must preserve behavior. Call out compatibility or
|
|
||||||
migration implications.
|
|
||||||
- Do not invent APIs, behavior, or requirements. If something is uncertain,
|
|
||||||
state the uncertainty and proceed only with minimal, explicit assumptions.
|
|
||||||
- For `thiserror`-style errors, start messages with a capital letter and keep
|
|
||||||
the outer `Display` text short. Put all non-`#[source]` attributes in the
|
|
||||||
message to improve helpfulness, but do not repeat a `#[source]` value
|
|
||||||
inline: Cloud Hypervisor prints the full error chain, so only include the
|
|
||||||
concrete failure text directly when there is no source to report.
|
|
||||||
|
|
||||||
### Safety and Domain Notes
|
|
||||||
|
|
||||||
- Prefer safe Rust. If `unsafe` is necessary, keep it narrow, add a `SAFETY:`
|
|
||||||
comment with the invariants, and make sure the surrounding code upholds them.
|
|
||||||
- Assume concurrency matters. Avoid races, unsynchronized shared state, and
|
|
||||||
implicit ordering assumptions; prefer clear ownership and synchronization.
|
|
||||||
|
|
||||||
### Build and Test Notes
|
|
||||||
|
|
||||||
- Some workspace members require the `kvm` feature to build or test correctly.
|
|
||||||
When a default build failure looks feature-related, retry the narrow command
|
|
||||||
with `--features kvm` before widening the diagnosis.
|
|
||||||
- Prefer narrow crate/test commands while iterating, then broaden verification
|
|
||||||
when the touched surface justifies it.
|
|
||||||
- Formatting currently needs nightly-only rustfmt features; use
|
|
||||||
`cargo +nightly fmt --all`.
|
|
||||||
- Add targeted unit tests for bug fixes and non-trivial logic where practical.
|
|
||||||
Keep test scaffolding minimal and focused.
|
|
||||||
- Integration tests live in `./cloud-hypervisor/tests/` and are normally driven
|
|
||||||
by `./scripts/dev_cli.sh` / `./scripts/run_integration_tests_*.sh`. They need
|
|
||||||
host privileges, workloads, and container setup. To build the integration-test
|
|
||||||
code directly without the infrastructure from `./scripts`, set the Rust cfg
|
|
||||||
`devcli_testenv` or simply build through `clippy` which automatically includes
|
|
||||||
these code paths; otherwise the integration-test code is not included.
|
|
||||||
|
|
||||||
### Commit and Patch Formatting
|
|
||||||
|
|
||||||
- Follow the rules in `CONTRIBUTING.md`, including reviewable commit structure,
|
|
||||||
valid component prefixes, 72-column commit messages, and a `Signed-off-by`
|
|
||||||
trailer.
|
|
||||||
- Lines in a commit message that are allowed to exceed the 72-column limit are
|
|
||||||
specified in `./scripts/gitlint/rules`.
|
|
||||||
- For LLM-assisted changes, follow the disclosure guidance in `CONTRIBUTING.md`:
|
|
||||||
use the project's `Assisted-by:` trailer when disclosure is needed, and do not
|
|
||||||
add `Co-authored-by` or similar trailers unless that policy changes. Prefer
|
|
||||||
explicit version numbers, such as `Assisted-by: Claude:Opus-4.7`, rather than
|
|
||||||
`Assisted-by: Claude:Opus-4`.
|
|
||||||
- Temporary allowances such as `#[allow(unused)]` or ignored tests are only
|
|
||||||
acceptable if resolved within the same commit series or paired with a clear
|
|
||||||
TODO referencing a ticket. Ask the developer if in doubt.
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
# Add the list of code owners here (using their GitHub username)
|
|
||||||
* @cloud-hypervisor/cloud-hypervisor-reviewers
|
|
||||||
222
CONTRIBUTING.md
222
CONTRIBUTING.md
@@ -1,109 +1,22 @@
|
|||||||
# Contributing to Cloud Hypervisor
|
# Contributing to Cloud Hypervisor
|
||||||
|
|
||||||
Cloud Hypervisor is an open source project licensed under the [Apache v2
|
Cloud Hypervisor is an open source project licensed under the [Apache v2 License](https://opensource.org/licenses/Apache-2.0) and the [BSD 3 Clause](https://opensource.org/licenses/BSD-3-Clause) license.
|
||||||
License](https://opensource.org/licenses/Apache-2.0) and the [BSD 3
|
|
||||||
Clause](https://opensource.org/licenses/BSD-3-Clause) license. Individual files
|
|
||||||
contain details of their licensing and changes to that file are under the same
|
|
||||||
license unless the contribution changes the license of the file. When importing
|
|
||||||
code from a third party project (e.g. Firecracker or crosvm) please respect the
|
|
||||||
license of those projects.
|
|
||||||
|
|
||||||
New code should be under the [Apache v2
|
## Coding Style
|
||||||
License](https://opensource.org/licenses/Apache-2.0).
|
|
||||||
|
|
||||||
Cloud Hypervisor's main supported architectures are `x86_64` and `aarch64`,
|
We follow the [Rust Style](https://github.com/rust-dev-tools/fmt-rfcs/blob/master/guide/guide.md)
|
||||||
and the main hypervisor backends are KVM and MSHV. `x86_64` with KVM gets the
|
convention and enforce it through the Continuous Integration (CI) process calling into `rustfmt`
|
||||||
most regular exercise, but changes should not make the other supported
|
for each submitted Pull Request (PR).
|
||||||
architecture and backend combinations worse.
|
|
||||||
|
|
||||||
## Coding Style & Code Comments
|
|
||||||
|
|
||||||
We use the [Rust Style] guide and enforce formatting and linting in CI,
|
|
||||||
including `rustfmt`, `clippy`, and other common Rust quality checks, for every
|
|
||||||
pull request. We adapt to best practices, new lints and new tooling as the
|
|
||||||
ecosystem evolves.
|
|
||||||
|
|
||||||
Code should **speak for itself** (for example, by using descriptive identifiers)
|
|
||||||
and be **easy to read and maintain**. Beyond the conventions and tooling
|
|
||||||
described above, contributors have _some_ room to apply their own style and
|
|
||||||
preferred structure. Maintainers may still suggest refactorings where they
|
|
||||||
believe readability, consistency, or maintainability can be improved.
|
|
||||||
|
|
||||||
For new code, add documentation and comments where they **provide additional value**:
|
|
||||||
|
|
||||||
* **Rustdoc** explains the API to its users.
|
|
||||||
* **Inline comments** explain the code the reader, especially *why* it is
|
|
||||||
written that way.
|
|
||||||
* **Commit messages** explain the broader context of a change (for more
|
|
||||||
information on commit messages, see below).
|
|
||||||
|
|
||||||
Comments should be concise and add additional context or information to the code.
|
|
||||||
|
|
||||||
Logging should be minimal and high signal. Use `info!` for important normal
|
|
||||||
state changes that matter in production; use `warn!` or `error!` only for
|
|
||||||
abnormal conditions. Keep `debug!` for focused diagnostics. Please find more
|
|
||||||
information in [`docs/logging.md`](docs/logging.md).
|
|
||||||
|
|
||||||
Error messages should be sentence-style: start with a capital letter and stay
|
|
||||||
concise. For `thiserror`-style errors, put all non-`#[source]` attributes
|
|
||||||
(if they provide clear value) in the outer `Display` text to improve helpfulness,
|
|
||||||
but do not repeat a `#[source]` value there because Cloud Hypervisor prints the
|
|
||||||
full chain elsewhere.
|
|
||||||
|
|
||||||
[Rust Style]: https://github.com/rust-lang/rust/tree/HEAD/src/doc/style-guide/src
|
|
||||||
|
|
||||||
## Basic Checks
|
|
||||||
|
|
||||||
```sh
|
|
||||||
# We currently rely on nightly-only formatting features
|
|
||||||
cargo +nightly fmt --all
|
|
||||||
cargo check --all-targets --tests
|
|
||||||
cargo clippy --all-targets --tests
|
|
||||||
# Please note that this will not execute integration tests.
|
|
||||||
cargo test --all-targets --tests
|
|
||||||
|
|
||||||
# To lint your last three commits
|
|
||||||
gitlint --commits "HEAD~3..HEAD"
|
|
||||||
```
|
|
||||||
|
|
||||||
### \[Optional\] Run Integration Tests
|
|
||||||
|
|
||||||
_Caution: These tests are taking a long time to complete (40+ mins) and need special setup._
|
|
||||||
|
|
||||||
```sh
|
|
||||||
bash ./scripts/dev_cli.sh tests --integration -- --test-filter '<optionally filter test by name pattern>'
|
|
||||||
```
|
|
||||||
|
|
||||||
### Setup Commit Hook
|
|
||||||
|
|
||||||
Please consider creating the following hook as `.git/hooks/pre-commit` in order
|
|
||||||
to ensure basic correctness of your code. You can extend this further if you
|
|
||||||
have specific features that you regularly develop against.
|
|
||||||
|
|
||||||
```sh
|
|
||||||
#!/bin/sh
|
|
||||||
|
|
||||||
cargo +nightly fmt --all -- --check || exit 1
|
|
||||||
cargo check --locked --all-targets --tests || exit 1
|
|
||||||
cargo clippy --locked --all-targets --tests -- -D warnings || exit 1
|
|
||||||
```
|
|
||||||
|
|
||||||
You will need to `chmod +x .git/hooks/pre-commit` to have it run on every
|
|
||||||
commit you make.
|
|
||||||
|
|
||||||
## Certificate of Origin
|
## Certificate of Origin
|
||||||
|
|
||||||
In order to get a clear contribution chain of trust we use the [signed-off-by language](https://www.kernel.org/doc/Documentation/process/submitting-patches.rst)
|
In order to get a clear contribution chain of trust we use the [signed-off-by language](https://01.org/community/signed-process)
|
||||||
used by the Linux kernel project.
|
used by the Linux kernel project.
|
||||||
|
|
||||||
## Patch format & Git Commit Hygiene
|
## Patch format
|
||||||
|
|
||||||
_We use **Patch** as synonym for **Commit**._
|
Beside the signed-off-by footer, we expect each patch to comply with the following format:
|
||||||
|
|
||||||
We require patches to:
|
|
||||||
|
|
||||||
- Have a `Signed-off-by: Name <email>` footer
|
|
||||||
- Follow the pattern: \
|
|
||||||
```
|
```
|
||||||
<component>: Change summary
|
<component>: Change summary
|
||||||
|
|
||||||
@@ -115,12 +28,7 @@ We require patches to:
|
|||||||
Signed-off-by: <contributor@foo.com>
|
Signed-off-by: <contributor@foo.com>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
For example:
|
||||||
Valid components are listed in `TitleStartsWithComponent.py`. In short, each
|
|
||||||
cargo workspace member is a valid component as well as `build`, `ci`, `docs` and
|
|
||||||
`misc`.
|
|
||||||
|
|
||||||
Example patch:
|
|
||||||
|
|
||||||
```
|
```
|
||||||
vm-virtio: Reset underlying device on driver request
|
vm-virtio: Reset underlying device on driver request
|
||||||
@@ -138,48 +46,21 @@ configure it anyway.
|
|||||||
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
|
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
|
||||||
```
|
```
|
||||||
|
|
||||||
### Git Commit History
|
|
||||||
|
|
||||||
We value a clean, **reviewable** commit history. Each commit should represent
|
|
||||||
a self-contained, logical step that guides reviewers clearly from A to B.
|
|
||||||
|
|
||||||
Avoid patterns like `init A -> init B -> fix A` or \
|
|
||||||
`init design A -> revert A -> use design B`. Commits must be independently
|
|
||||||
reviewable - don't leave "fix previous commit" or earlier design attempts in
|
|
||||||
the history.
|
|
||||||
|
|
||||||
Intermediate work-in-progress changes are acceptable only if a subsequent
|
|
||||||
commit in the same series cleans them up (e.g. a temporary `#[allow(unused)]`
|
|
||||||
removed in the next commit).
|
|
||||||
|
|
||||||
## Pull requests
|
## Pull requests
|
||||||
|
|
||||||
> [!IMPORTANT]
|
|
||||||
> Before opening a pull request for a new feature or enhancement request please
|
|
||||||
> create an issue with the "feature request" template and ensure there is
|
|
||||||
> agreement from the maintainers to move ahead with that feature.
|
|
||||||
|
|
||||||
> [!TIP]
|
|
||||||
> When fixing a bug, especially a complex one, please consider opening an issue
|
|
||||||
> with the "bug report" template to make it easier for other users to discover
|
|
||||||
> your fix and aid reviewers. _This is not required for opening a pull request
|
|
||||||
> nor is any agreement required before implementation._
|
|
||||||
|
|
||||||
Cloud Hypervisor uses the “fork-and-pull” development model. Follow these steps if
|
Cloud Hypervisor uses the “fork-and-pull” development model. Follow these steps if
|
||||||
you want to merge your changes to `cloud-hypervisor`:
|
you want to merge your changes to `cloud-hypervisor`:
|
||||||
|
|
||||||
1. Fork the [cloud-hypervisor](https://github.com/cloud-hypervisor/cloud-hypervisor) project
|
1. Fork the [cloud-hypervisor](https://github.com/cloud-hypervisor/cloud-hypervisor) project
|
||||||
into your github organization.
|
into your github organization.
|
||||||
1. Within your fork, create a branch for your contribution.
|
2. Within your fork, create a branch for your contribution.
|
||||||
1. [Create a pull request](https://help.github.com/articles/creating-a-pull-request-from-a-fork/)
|
3. [Create a pull request](https://help.github.com/articles/creating-a-pull-request-from-a-fork/)
|
||||||
against the main branch of the Cloud Hypervisor repository.
|
against the master branch of the Cloud Hypervisor repository.
|
||||||
1. Each commit must comply with the Commit Hygiene guidelines above.
|
4. Add reviewers to your pull request and then work with your reviewers to address
|
||||||
1. A pull request should address a single component or concern to keep review
|
any comments and obtain minimum of 2 [maintainers](MAINTAINERS.md) approvals.
|
||||||
focused and approvals straightforward.
|
To update your pull request amend existing commits whenever applicable and
|
||||||
1. Once the pull request is approved it can be integrated.
|
then push the new changes to your pull request branch.
|
||||||
|
5. Once the pull request is approved, one of the maintainers will merge it.
|
||||||
Please squash any changes done during review already into the corresponding
|
|
||||||
commits instead of pushing `<component>: addressing review for A`-style commits.
|
|
||||||
|
|
||||||
## Issue tracking
|
## Issue tracking
|
||||||
|
|
||||||
@@ -207,72 +88,5 @@ Fixes #88
|
|||||||
Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
|
Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
|
||||||
```
|
```
|
||||||
|
|
||||||
Then, after the corresponding PR is merged, GitHub will automatically close that issue when parsing the
|
Then, after the correspoding PR is merged, Github will automatically close that issue when parsing the
|
||||||
[commit message](https://help.github.com/articles/closing-issues-via-commit-messages/).
|
[commit message](https://help.github.com/articles/closing-issues-via-commit-messages/).
|
||||||
|
|
||||||
## AI/LLM Assistance & Generated Code
|
|
||||||
|
|
||||||
We recommend **a careful and conservative approach** to LLM usage, guided by
|
|
||||||
sound engineering judgment. Please use AI/LLM-assisted tooling thoughtfully and
|
|
||||||
responsibly to ensure efficient use of limited project resources, particularly
|
|
||||||
in code review and long-term maintenance. Our primary goals are to avoid
|
|
||||||
ambiguity in license compliance and to keep contributions clear and easy to
|
|
||||||
review.
|
|
||||||
|
|
||||||
Or in other words: please apply common sense and don't blindly accept LLM
|
|
||||||
suggestions.
|
|
||||||
|
|
||||||
This policy can be revisited as LLMs evolve and mature.
|
|
||||||
|
|
||||||
### Code Review
|
|
||||||
|
|
||||||
We generally recommend doing early coarse-grained reviews using state-of-the-art
|
|
||||||
LLMs. This can help identify rough edges, copy & paste errors, and typos early
|
|
||||||
on. This reduces review cycles for human reviewers.
|
|
||||||
|
|
||||||
Please **do not** use GitHub Copilot directly in PRs to keep discussions clean.
|
|
||||||
Instead, ask an LLM of your choice for a review. A convenient way to do this is
|
|
||||||
|
|
||||||
- appending `.patch` to the GitHub PR URL
|
|
||||||
(e.g., `https://github.com/cloud-hypervisor/cloud-hypervisor/pull/1234.patch`)
|
|
||||||
and pasting it into the LLM of your choice, or
|
|
||||||
- using a local agent in your terminal, such as `codex` or `claude`.
|
|
||||||
|
|
||||||
### Contributions assisted by LLMs
|
|
||||||
|
|
||||||
All contributions **must** be submitted by a human contributor. Automated or
|
|
||||||
bot-driven PRs are not accepted.
|
|
||||||
|
|
||||||
You are responsible for every piece of code you submit, and you must understand
|
|
||||||
both the design and the implementation details. LLMs are useful for prototyping
|
|
||||||
and generating boilerplate code. However, large or complex logic must be
|
|
||||||
authored and fully understood by the contributor - LLM output should not be
|
|
||||||
submitted without careful review and comprehension.
|
|
||||||
|
|
||||||
Please disclose LLM use in your commit message and PR description if it
|
|
||||||
meaningfully contributed to the submitted code. Again, we recommend careful and
|
|
||||||
conservative use of LLMs, guided by common sense.
|
|
||||||
|
|
||||||
Use the following tag to disclose LLM assistance in your commit message:
|
|
||||||
|
|
||||||
```
|
|
||||||
Assisted-by: AGENT_NAME:MODEL_VERSION [TOOL1] [TOOL2]
|
|
||||||
```
|
|
||||||
|
|
||||||
Where:
|
|
||||||
|
|
||||||
- ``AGENT_NAME`` is the name of the AI tool or framework
|
|
||||||
- ``MODEL_VERSION`` is the specific model version used
|
|
||||||
- ``[TOOL1] [TOOL2]`` are optional specialized analysis tools used
|
|
||||||
|
|
||||||
Basic development tools (git, make, editors) should not be listed.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```
|
|
||||||
Assisted-by: Claude:Opus-4.6 CodeQL
|
|
||||||
```
|
|
||||||
|
|
||||||
Maintainers reserve the right to request additional clarification or decline
|
|
||||||
contributions where LLM usage raises concerns. Ultimately, acceptance of any
|
|
||||||
contribution is at the maintainers' discretion.
|
|
||||||
|
|||||||
3393
Cargo.lock
generated
3393
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
189
Cargo.toml
189
Cargo.toml
@@ -1,141 +1,68 @@
|
|||||||
# Cloud Hypervisor Workspace
|
[package]
|
||||||
#
|
name = "cloud-hypervisor"
|
||||||
# The main crate producing the binaries is in `./cloud-hypervisor`.
|
version = "0.5.1"
|
||||||
|
authors = ["The Cloud Hypervisor Authors"]
|
||||||
|
edition = "2018"
|
||||||
|
default-run = "cloud-hypervisor"
|
||||||
|
|
||||||
[profile.release]
|
[dependencies]
|
||||||
codegen-units = 1
|
arc-swap = "0.4.4"
|
||||||
lto = true
|
clap = "2.33.0"
|
||||||
opt-level = "s"
|
epoll = "4.1.0"
|
||||||
strip = true
|
lazy_static = "1.4.0"
|
||||||
|
libc = "0.2.66"
|
||||||
|
log = { version = "0.4.8", features = ["std"] }
|
||||||
|
vhost_user_backend = { path = "vhost_user_backend"}
|
||||||
|
vhost_user_block = { path = "vhost_user_block"}
|
||||||
|
vhost_user_fs = { path = "vhost_user_fs"}
|
||||||
|
vhost_user_net = { path = "vhost_user_net"}
|
||||||
|
virtio-bindings = "0.1.0"
|
||||||
|
vmm = { path = "vmm" }
|
||||||
|
vm-device = { path = "vm-device" }
|
||||||
|
vm-memory = { git = "https://github.com/rust-vmm/vm-memory" }
|
||||||
|
vmm-sys-util = "0.4.0"
|
||||||
|
vm-virtio = { path = "vm-virtio" }
|
||||||
|
|
||||||
[profile.profiling]
|
[dependencies.vhost_rs]
|
||||||
debug = true
|
path = "vhost_rs"
|
||||||
inherits = "release"
|
features = ["vhost-user-slave"]
|
||||||
strip = false
|
|
||||||
|
[dev-dependencies]
|
||||||
|
ssh2 = "0.7.1"
|
||||||
|
dirs = "2.0.2"
|
||||||
|
credibility = "0.1.3"
|
||||||
|
tempdir= "0.3.7"
|
||||||
|
lazy_static= "1.4.0"
|
||||||
|
serde_json = "1.0.48"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["acpi", "pci", "cmos"]
|
||||||
|
acpi = ["vmm/acpi"]
|
||||||
|
pci = ["vmm/pci_support"]
|
||||||
|
mmio = ["vmm/mmio_support"]
|
||||||
|
cmos = ["vmm/cmos"]
|
||||||
|
|
||||||
|
# Integration tests require a special environment to run in
|
||||||
|
integration_tests = []
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
"api_client",
|
|
||||||
"arch",
|
"arch",
|
||||||
"block",
|
|
||||||
"cloud-hypervisor",
|
|
||||||
"devices",
|
"devices",
|
||||||
"event_monitor",
|
"vhost_rs",
|
||||||
"hypervisor",
|
"qcow",
|
||||||
"net_util",
|
|
||||||
"offload_daemon",
|
|
||||||
"option_parser",
|
|
||||||
"pci",
|
"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",
|
"vmm",
|
||||||
|
"vm-virtio",
|
||||||
|
"vm-device",
|
||||||
|
"vhost_user_block",
|
||||||
|
"vhost_user_backend",
|
||||||
|
"vhost_user_fs",
|
||||||
|
"vhost_user_net",
|
||||||
|
"vfio",
|
||||||
|
"net_util",
|
||||||
|
"acpi_tables",
|
||||||
|
"arch_gen",
|
||||||
|
"net_gen",
|
||||||
|
"vm-allocator",
|
||||||
]
|
]
|
||||||
package.edition = "2024"
|
|
||||||
# Minimum buildable version:
|
|
||||||
# Keep in sync with version in .github/workflows/build.yaml
|
|
||||||
# Policy on MSRV (see #4318):
|
|
||||||
# Can only be bumped if satisfying any of the following:
|
|
||||||
# a.) A dependency requires it,
|
|
||||||
# b.) If we want to use a new feature and that MSRV is at least 6 months old,
|
|
||||||
# c.) There is a security issue that is addressed by the toolchain update.
|
|
||||||
package.rust-version = "1.89.0"
|
|
||||||
resolver = "3"
|
|
||||||
|
|
||||||
[workspace.dependencies]
|
|
||||||
# rust-vmm crates
|
|
||||||
acpi_tables = "0.2.1"
|
|
||||||
iommufd-ioctls = "0.2.0"
|
|
||||||
kvm-bindings = "0.14.1"
|
|
||||||
kvm-ioctls = "0.25.0"
|
|
||||||
linux-loader = "0.14.0"
|
|
||||||
mshv-bindings = "0.6.9"
|
|
||||||
mshv-ioctls = "0.6.9"
|
|
||||||
seccompiler = "0.5.0"
|
|
||||||
vfio-bindings = { version = "0.6.2", default-features = false }
|
|
||||||
vfio-ioctls = { version = "0.8.0", default-features = false }
|
|
||||||
vfio_user = { version = "0.1.4", default-features = false }
|
|
||||||
vhost = { version = "0.17.0", default-features = false }
|
|
||||||
vhost-user-backend = { version = "0.23.0", default-features = false }
|
|
||||||
virtio-bindings = "0.2.6"
|
|
||||||
virtio-queue = "0.18.0"
|
|
||||||
vm-fdt = "0.3.0"
|
|
||||||
vm-memory = "0.18.0"
|
|
||||||
vmm-sys-util = "0.15.0"
|
|
||||||
|
|
||||||
# igvm crates
|
|
||||||
igvm = "0.4.0"
|
|
||||||
igvm_defs = "0.4.0"
|
|
||||||
|
|
||||||
# serde crates
|
|
||||||
serde = "1.0.228"
|
|
||||||
serde_json = "1.0.150"
|
|
||||||
serde_with = { version = "3.19.0", default-features = false }
|
|
||||||
|
|
||||||
# other crates
|
|
||||||
anyhow = "1.0.102"
|
|
||||||
base64 = "0.23.0"
|
|
||||||
bitflags = "2.11.1"
|
|
||||||
byteorder = "1.5.0"
|
|
||||||
cfg-if = "1.0.4"
|
|
||||||
clap = "4.6.1"
|
|
||||||
dhat = "0.3.3"
|
|
||||||
dirs = "6.0.0"
|
|
||||||
env_logger = "0.11.10"
|
|
||||||
epoll = "4.4.0"
|
|
||||||
flume = "0.12.0"
|
|
||||||
itertools = "0.15.0"
|
|
||||||
jiff = { version = "0.2", default-features = false, features = ["std"] }
|
|
||||||
libc = "0.2.186"
|
|
||||||
log = "0.4.30"
|
|
||||||
rustls = { version = "0.23.40", default-features = false, features = [
|
|
||||||
"logging",
|
|
||||||
"ring",
|
|
||||||
"std",
|
|
||||||
] }
|
|
||||||
sha2 = "0.11.0"
|
|
||||||
signal-hook = "0.4.4"
|
|
||||||
signal-hook-registry = "1.4.8"
|
|
||||||
smallvec = "1.15.1"
|
|
||||||
thiserror = "2.0.18"
|
|
||||||
uuid = { version = "1.23.2" }
|
|
||||||
wait-timeout = "0.2.1"
|
|
||||||
zerocopy = { version = "0.8.50", default-features = false }
|
|
||||||
|
|
||||||
[workspace.lints.clippy]
|
|
||||||
# Any clippy lint (group) in alphabetical order:
|
|
||||||
# https://rust-lang.github.io/rust-clippy/master/index.html
|
|
||||||
|
|
||||||
# Groups
|
|
||||||
all = "deny" # shorthand for the other groups but here for compleness
|
|
||||||
complexity = "deny"
|
|
||||||
correctness = "deny"
|
|
||||||
perf = "deny"
|
|
||||||
style = "deny"
|
|
||||||
suspicious = "deny"
|
|
||||||
|
|
||||||
# Individual Lints
|
|
||||||
absolute_paths = "deny"
|
|
||||||
assertions_on_result_states = "deny"
|
|
||||||
if_not_else = "deny"
|
|
||||||
manual_string_new = "deny"
|
|
||||||
map_unwrap_or = "deny"
|
|
||||||
needless_pass_by_value = "deny"
|
|
||||||
redundant_else = "deny"
|
|
||||||
semicolon_if_nothing_returned = "deny"
|
|
||||||
undocumented_unsafe_blocks = "deny"
|
|
||||||
uninlined_format_args = "deny"
|
|
||||||
unnecessary_semicolon = "deny"
|
|
||||||
|
|
||||||
[workspace.lints.rust]
|
|
||||||
# `level = warn` is irrelevant here but mandatory for rustc/cargo
|
|
||||||
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(devcli_testenv)'] }
|
|
||||||
|
|||||||
85
Jenkinsfile
vendored
Normal file
85
Jenkinsfile
vendored
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
pipeline{
|
||||||
|
agent none
|
||||||
|
stages {
|
||||||
|
stage ('Master build') {
|
||||||
|
agent { node { label 'master' } }
|
||||||
|
stages {
|
||||||
|
stage ('Check for RFC/WIP builds') {
|
||||||
|
when {
|
||||||
|
changeRequest comparator: 'REGEXP', title: '.*(rfc|RFC|wip|WIP).*'
|
||||||
|
beforeAgent true
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
error("Failing as this is marked as a WIP or RFC PR.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage ('Cancel older builds') {
|
||||||
|
steps {
|
||||||
|
cancelPreviousBuilds()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage ('Worker build') {
|
||||||
|
agent { node { label 'bionic' } }
|
||||||
|
options {
|
||||||
|
timeout(time: 1, unit: 'HOURS')
|
||||||
|
}
|
||||||
|
stages {
|
||||||
|
stage ('Checkout') {
|
||||||
|
steps {
|
||||||
|
checkout scm
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage ('Install system packages') {
|
||||||
|
steps {
|
||||||
|
sh "sudo DEBIAN_FRONTEND=noninteractive apt-get install -yq build-essential mtools libssl-dev pkg-config"
|
||||||
|
sh "sudo apt-get install -yq flex bison libelf-dev qemu-utils qemu-system libglib2.0-dev libpixman-1-dev libseccomp-dev libcap-ng-dev socat"
|
||||||
|
sh "sudo snap install docker"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage ('Install Rust') {
|
||||||
|
steps {
|
||||||
|
sh "nohup curl https://sh.rustup.rs -sSf | sh -s -- -y"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage ('Run Cargo tests') {
|
||||||
|
steps {
|
||||||
|
sh "scripts/run_cargo_tests.sh"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage ('Run OpenAPI tests') {
|
||||||
|
steps {
|
||||||
|
sh "scripts/run_openapi_tests.sh"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage ('Run unit tests') {
|
||||||
|
steps {
|
||||||
|
sh "scripts/run_unit_tests.sh"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage ('Run integration tests') {
|
||||||
|
steps {
|
||||||
|
sh "sudo mount -t tmpfs tmpfs /tmp"
|
||||||
|
sh "scripts/run_integration_tests.sh"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,400 +0,0 @@
|
|||||||
All documentations (e.g. files with extension `.md`) in this repository is
|
|
||||||
covered by the following license:
|
|
||||||
|
|
||||||
Attribution 4.0 International
|
|
||||||
|
|
||||||
=======================================================================
|
|
||||||
|
|
||||||
Creative Commons Corporation ("Creative Commons") is not a law firm and
|
|
||||||
does not provide legal services or legal advice. Distribution of
|
|
||||||
Creative Commons public licenses does not create a lawyer-client or
|
|
||||||
other relationship. Creative Commons makes its licenses and related
|
|
||||||
information available on an "as-is" basis. Creative Commons gives no
|
|
||||||
warranties regarding its licenses, any material licensed under their
|
|
||||||
terms and conditions, or any related information. Creative Commons
|
|
||||||
disclaims all liability for damages resulting from their use to the
|
|
||||||
fullest extent possible.
|
|
||||||
|
|
||||||
Using Creative Commons Public Licenses
|
|
||||||
|
|
||||||
Creative Commons public licenses provide a standard set of terms and
|
|
||||||
conditions that creators and other rights holders may use to share
|
|
||||||
original works of authorship and other material subject to copyright
|
|
||||||
and certain other rights specified in the public license below. The
|
|
||||||
following considerations are for informational purposes only, are not
|
|
||||||
exhaustive, and do not form part of our licenses.
|
|
||||||
|
|
||||||
Considerations for licensors: Our public licenses are
|
|
||||||
intended for use by those authorized to give the public
|
|
||||||
permission to use material in ways otherwise restricted by
|
|
||||||
copyright and certain other rights. Our licenses are
|
|
||||||
irrevocable. Licensors should read and understand the terms
|
|
||||||
and conditions of the license they choose before applying it.
|
|
||||||
Licensors should also secure all rights necessary before
|
|
||||||
applying our licenses so that the public can reuse the
|
|
||||||
material as expected. Licensors should clearly mark any
|
|
||||||
material not subject to the license. This includes other CC-
|
|
||||||
licensed material, or material used under an exception or
|
|
||||||
limitation to copyright. More considerations for licensors:
|
|
||||||
wiki.creativecommons.org/Considerations_for_licensors
|
|
||||||
|
|
||||||
Considerations for the public: By using one of our public
|
|
||||||
licenses, a licensor grants the public permission to use the
|
|
||||||
licensed material under specified terms and conditions. If
|
|
||||||
the licensor's permission is not necessary for any reason--for
|
|
||||||
example, because of any applicable exception or limitation to
|
|
||||||
copyright--then that use is not regulated by the license. Our
|
|
||||||
licenses grant only permissions under copyright and certain
|
|
||||||
other rights that a licensor has authority to grant. Use of
|
|
||||||
the licensed material may still be restricted for other
|
|
||||||
reasons, including because others have copyright or other
|
|
||||||
rights in the material. A licensor may make special requests,
|
|
||||||
such as asking that all changes be marked or described.
|
|
||||||
Although not required by our licenses, you are encouraged to
|
|
||||||
respect those requests where reasonable. More considerations
|
|
||||||
for the public:
|
|
||||||
wiki.creativecommons.org/Considerations_for_licensees
|
|
||||||
|
|
||||||
=======================================================================
|
|
||||||
|
|
||||||
Creative Commons Attribution 4.0 International Public License
|
|
||||||
|
|
||||||
By exercising the Licensed Rights (defined below), You accept and agree
|
|
||||||
to be bound by the terms and conditions of this Creative Commons
|
|
||||||
Attribution 4.0 International Public License ("Public License"). To the
|
|
||||||
extent this Public License may be interpreted as a contract, You are
|
|
||||||
granted the Licensed Rights in consideration of Your acceptance of
|
|
||||||
these terms and conditions, and the Licensor grants You such rights in
|
|
||||||
consideration of benefits the Licensor receives from making the
|
|
||||||
Licensed Material available under these terms and conditions.
|
|
||||||
|
|
||||||
|
|
||||||
Section 1 -- Definitions.
|
|
||||||
|
|
||||||
a. Adapted Material means material subject to Copyright and Similar
|
|
||||||
Rights that is derived from or based upon the Licensed Material
|
|
||||||
and in which the Licensed Material is translated, altered,
|
|
||||||
arranged, transformed, or otherwise modified in a manner requiring
|
|
||||||
permission under the Copyright and Similar Rights held by the
|
|
||||||
Licensor. For purposes of this Public License, where the Licensed
|
|
||||||
Material is a musical work, performance, or sound recording,
|
|
||||||
Adapted Material is always produced where the Licensed Material is
|
|
||||||
synched in timed relation with a moving image.
|
|
||||||
|
|
||||||
b. Adapter's License means the license You apply to Your Copyright
|
|
||||||
and Similar Rights in Your contributions to Adapted Material in
|
|
||||||
accordance with the terms and conditions of this Public License.
|
|
||||||
|
|
||||||
c. Copyright and Similar Rights means copyright and/or similar rights
|
|
||||||
closely related to copyright including, without limitation,
|
|
||||||
performance, broadcast, sound recording, and Sui Generis Database
|
|
||||||
Rights, without regard to how the rights are labeled or
|
|
||||||
categorized. For purposes of this Public License, the rights
|
|
||||||
specified in Section 2(b)(1)-(2) are not Copyright and Similar
|
|
||||||
Rights.
|
|
||||||
|
|
||||||
d. Effective Technological Measures means those measures that, in the
|
|
||||||
absence of proper authority, may not be circumvented under laws
|
|
||||||
fulfilling obligations under Article 11 of the WIPO Copyright
|
|
||||||
Treaty adopted on December 20, 1996, and/or similar international
|
|
||||||
agreements.
|
|
||||||
|
|
||||||
e. Exceptions and Limitations means fair use, fair dealing, and/or
|
|
||||||
any other exception or limitation to Copyright and Similar Rights
|
|
||||||
that applies to Your use of the Licensed Material.
|
|
||||||
|
|
||||||
f. Licensed Material means the artistic or literary work, database,
|
|
||||||
or other material to which the Licensor applied this Public
|
|
||||||
License.
|
|
||||||
|
|
||||||
g. Licensed Rights means the rights granted to You subject to the
|
|
||||||
terms and conditions of this Public License, which are limited to
|
|
||||||
all Copyright and Similar Rights that apply to Your use of the
|
|
||||||
Licensed Material and that the Licensor has authority to license.
|
|
||||||
|
|
||||||
h. Licensor means the individual(s) or entity(ies) granting rights
|
|
||||||
under this Public License.
|
|
||||||
|
|
||||||
i. Share means to provide material to the public by any means or
|
|
||||||
process that requires permission under the Licensed Rights, such
|
|
||||||
as reproduction, public display, public performance, distribution,
|
|
||||||
dissemination, communication, or importation, and to make material
|
|
||||||
available to the public including in ways that members of the
|
|
||||||
public may access the material from a place and at a time
|
|
||||||
individually chosen by them.
|
|
||||||
|
|
||||||
j. Sui Generis Database Rights means rights other than copyright
|
|
||||||
resulting from Directive 96/9/EC of the European Parliament and of
|
|
||||||
the Council of 11 March 1996 on the legal protection of databases,
|
|
||||||
as amended and/or succeeded, as well as other essentially
|
|
||||||
equivalent rights anywhere in the world.
|
|
||||||
|
|
||||||
k. You means the individual or entity exercising the Licensed Rights
|
|
||||||
under this Public License. Your has a corresponding meaning.
|
|
||||||
|
|
||||||
|
|
||||||
Section 2 -- Scope.
|
|
||||||
|
|
||||||
a. License grant.
|
|
||||||
|
|
||||||
1. Subject to the terms and conditions of this Public License,
|
|
||||||
the Licensor hereby grants You a worldwide, royalty-free,
|
|
||||||
non-sublicensable, non-exclusive, irrevocable license to
|
|
||||||
exercise the Licensed Rights in the Licensed Material to:
|
|
||||||
|
|
||||||
a. reproduce and Share the Licensed Material, in whole or
|
|
||||||
in part; and
|
|
||||||
|
|
||||||
b. produce, reproduce, and Share Adapted Material.
|
|
||||||
|
|
||||||
2. Exceptions and Limitations. For the avoidance of doubt, where
|
|
||||||
Exceptions and Limitations apply to Your use, this Public
|
|
||||||
License does not apply, and You do not need to comply with
|
|
||||||
its terms and conditions.
|
|
||||||
|
|
||||||
3. Term. The term of this Public License is specified in Section
|
|
||||||
6(a).
|
|
||||||
|
|
||||||
4. Media and formats; technical modifications allowed. The
|
|
||||||
Licensor authorizes You to exercise the Licensed Rights in
|
|
||||||
all media and formats whether now known or hereafter created,
|
|
||||||
and to make technical modifications necessary to do so. The
|
|
||||||
Licensor waives and/or agrees not to assert any right or
|
|
||||||
authority to forbid You from making technical modifications
|
|
||||||
necessary to exercise the Licensed Rights, including
|
|
||||||
technical modifications necessary to circumvent Effective
|
|
||||||
Technological Measures. For purposes of this Public License,
|
|
||||||
simply making modifications authorized by this Section 2(a)
|
|
||||||
(4) never produces Adapted Material.
|
|
||||||
|
|
||||||
5. Downstream recipients.
|
|
||||||
|
|
||||||
a. Offer from the Licensor -- Licensed Material. Every
|
|
||||||
recipient of the Licensed Material automatically
|
|
||||||
receives an offer from the Licensor to exercise the
|
|
||||||
Licensed Rights under the terms and conditions of this
|
|
||||||
Public License.
|
|
||||||
|
|
||||||
b. No downstream restrictions. You may not offer or impose
|
|
||||||
any additional or different terms or conditions on, or
|
|
||||||
apply any Effective Technological Measures to, the
|
|
||||||
Licensed Material if doing so restricts exercise of the
|
|
||||||
Licensed Rights by any recipient of the Licensed
|
|
||||||
Material.
|
|
||||||
|
|
||||||
6. No endorsement. Nothing in this Public License constitutes or
|
|
||||||
may be construed as permission to assert or imply that You
|
|
||||||
are, or that Your use of the Licensed Material is, connected
|
|
||||||
with, or sponsored, endorsed, or granted official status by,
|
|
||||||
the Licensor or others designated to receive attribution as
|
|
||||||
provided in Section 3(a)(1)(A)(i).
|
|
||||||
|
|
||||||
b. Other rights.
|
|
||||||
|
|
||||||
1. Moral rights, such as the right of integrity, are not
|
|
||||||
licensed under this Public License, nor are publicity,
|
|
||||||
privacy, and/or other similar personality rights; however, to
|
|
||||||
the extent possible, the Licensor waives and/or agrees not to
|
|
||||||
assert any such rights held by the Licensor to the limited
|
|
||||||
extent necessary to allow You to exercise the Licensed
|
|
||||||
Rights, but not otherwise.
|
|
||||||
|
|
||||||
2. Patent and trademark rights are not licensed under this
|
|
||||||
Public License.
|
|
||||||
|
|
||||||
3. To the extent possible, the Licensor waives any right to
|
|
||||||
collect royalties from You for the exercise of the Licensed
|
|
||||||
Rights, whether directly or through a collecting society
|
|
||||||
under any voluntary or waivable statutory or compulsory
|
|
||||||
licensing scheme. In all other cases the Licensor expressly
|
|
||||||
reserves any right to collect such royalties.
|
|
||||||
|
|
||||||
|
|
||||||
Section 3 -- License Conditions.
|
|
||||||
|
|
||||||
Your exercise of the Licensed Rights is expressly made subject to the
|
|
||||||
following conditions.
|
|
||||||
|
|
||||||
a. Attribution.
|
|
||||||
|
|
||||||
1. If You Share the Licensed Material (including in modified
|
|
||||||
form), You must:
|
|
||||||
|
|
||||||
a. retain the following if it is supplied by the Licensor
|
|
||||||
with the Licensed Material:
|
|
||||||
|
|
||||||
i. identification of the creator(s) of the Licensed
|
|
||||||
Material and any others designated to receive
|
|
||||||
attribution, in any reasonable manner requested by
|
|
||||||
the Licensor (including by pseudonym if
|
|
||||||
designated);
|
|
||||||
|
|
||||||
ii. a copyright notice;
|
|
||||||
|
|
||||||
iii. a notice that refers to this Public License;
|
|
||||||
|
|
||||||
iv. a notice that refers to the disclaimer of
|
|
||||||
warranties;
|
|
||||||
|
|
||||||
v. a URI or hyperlink to the Licensed Material to the
|
|
||||||
extent reasonably practicable;
|
|
||||||
|
|
||||||
b. indicate if You modified the Licensed Material and
|
|
||||||
retain an indication of any previous modifications; and
|
|
||||||
|
|
||||||
c. indicate the Licensed Material is licensed under this
|
|
||||||
Public License, and include the text of, or the URI or
|
|
||||||
hyperlink to, this Public License.
|
|
||||||
|
|
||||||
2. You may satisfy the conditions in Section 3(a)(1) in any
|
|
||||||
reasonable manner based on the medium, means, and context in
|
|
||||||
which You Share the Licensed Material. For example, it may be
|
|
||||||
reasonable to satisfy the conditions by providing a URI or
|
|
||||||
hyperlink to a resource that includes the required
|
|
||||||
information.
|
|
||||||
|
|
||||||
3. If requested by the Licensor, You must remove any of the
|
|
||||||
information required by Section 3(a)(1)(A) to the extent
|
|
||||||
reasonably practicable.
|
|
||||||
|
|
||||||
4. If You Share Adapted Material You produce, the Adapter's
|
|
||||||
License You apply must not prevent recipients of the Adapted
|
|
||||||
Material from complying with this Public License.
|
|
||||||
|
|
||||||
|
|
||||||
Section 4 -- Sui Generis Database Rights.
|
|
||||||
|
|
||||||
Where the Licensed Rights include Sui Generis Database Rights that
|
|
||||||
apply to Your use of the Licensed Material:
|
|
||||||
|
|
||||||
a. for the avoidance of doubt, Section 2(a)(1) grants You the right
|
|
||||||
to extract, reuse, reproduce, and Share all or a substantial
|
|
||||||
portion of the contents of the database;
|
|
||||||
|
|
||||||
b. if You include all or a substantial portion of the database
|
|
||||||
contents in a database in which You have Sui Generis Database
|
|
||||||
Rights, then the database in which You have Sui Generis Database
|
|
||||||
Rights (but not its individual contents) is Adapted Material; and
|
|
||||||
|
|
||||||
c. You must comply with the conditions in Section 3(a) if You Share
|
|
||||||
all or a substantial portion of the contents of the database.
|
|
||||||
|
|
||||||
For the avoidance of doubt, this Section 4 supplements and does not
|
|
||||||
replace Your obligations under this Public License where the Licensed
|
|
||||||
Rights include other Copyright and Similar Rights.
|
|
||||||
|
|
||||||
|
|
||||||
Section 5 -- Disclaimer of Warranties and Limitation of Liability.
|
|
||||||
|
|
||||||
a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
|
|
||||||
EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
|
|
||||||
AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
|
|
||||||
ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
|
|
||||||
IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
|
|
||||||
WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
|
||||||
PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
|
|
||||||
ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
|
|
||||||
KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
|
|
||||||
ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
|
|
||||||
|
|
||||||
b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
|
|
||||||
TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
|
|
||||||
NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
|
|
||||||
INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
|
|
||||||
COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
|
|
||||||
USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
|
|
||||||
ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
|
|
||||||
DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
|
|
||||||
IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
|
|
||||||
|
|
||||||
c. The disclaimer of warranties and limitation of liability provided
|
|
||||||
above shall be interpreted in a manner that, to the extent
|
|
||||||
possible, most closely approximates an absolute disclaimer and
|
|
||||||
waiver of all liability.
|
|
||||||
|
|
||||||
|
|
||||||
Section 6 -- Term and Termination.
|
|
||||||
|
|
||||||
a. This Public License applies for the term of the Copyright and
|
|
||||||
Similar Rights licensed here. However, if You fail to comply with
|
|
||||||
this Public License, then Your rights under this Public License
|
|
||||||
terminate automatically.
|
|
||||||
|
|
||||||
b. Where Your right to use the Licensed Material has terminated under
|
|
||||||
Section 6(a), it reinstates:
|
|
||||||
|
|
||||||
1. automatically as of the date the violation is cured, provided
|
|
||||||
it is cured within 30 days of Your discovery of the
|
|
||||||
violation; or
|
|
||||||
|
|
||||||
2. upon express reinstatement by the Licensor.
|
|
||||||
|
|
||||||
For the avoidance of doubt, this Section 6(b) does not affect any
|
|
||||||
right the Licensor may have to seek remedies for Your violations
|
|
||||||
of this Public License.
|
|
||||||
|
|
||||||
c. For the avoidance of doubt, the Licensor may also offer the
|
|
||||||
Licensed Material under separate terms or conditions or stop
|
|
||||||
distributing the Licensed Material at any time; however, doing so
|
|
||||||
will not terminate this Public License.
|
|
||||||
|
|
||||||
d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
|
|
||||||
License.
|
|
||||||
|
|
||||||
|
|
||||||
Section 7 -- Other Terms and Conditions.
|
|
||||||
|
|
||||||
a. The Licensor shall not be bound by any additional or different
|
|
||||||
terms or conditions communicated by You unless expressly agreed.
|
|
||||||
|
|
||||||
b. Any arrangements, understandings, or agreements regarding the
|
|
||||||
Licensed Material not stated herein are separate from and
|
|
||||||
independent of the terms and conditions of this Public License.
|
|
||||||
|
|
||||||
|
|
||||||
Section 8 -- Interpretation.
|
|
||||||
|
|
||||||
a. For the avoidance of doubt, this Public License does not, and
|
|
||||||
shall not be interpreted to, reduce, limit, restrict, or impose
|
|
||||||
conditions on any use of the Licensed Material that could lawfully
|
|
||||||
be made without permission under this Public License.
|
|
||||||
|
|
||||||
b. To the extent possible, if any provision of this Public License is
|
|
||||||
deemed unenforceable, it shall be automatically reformed to the
|
|
||||||
minimum extent necessary to make it enforceable. If the provision
|
|
||||||
cannot be reformed, it shall be severed from this Public License
|
|
||||||
without affecting the enforceability of the remaining terms and
|
|
||||||
conditions.
|
|
||||||
|
|
||||||
c. No term or condition of this Public License will be waived and no
|
|
||||||
failure to comply consented to unless expressly agreed to by the
|
|
||||||
Licensor.
|
|
||||||
|
|
||||||
d. Nothing in this Public License constitutes or may be interpreted
|
|
||||||
as a limitation upon, or waiver of, any privileges and immunities
|
|
||||||
that apply to the Licensor or You, including from the legal
|
|
||||||
processes of any jurisdiction or authority.
|
|
||||||
|
|
||||||
|
|
||||||
=======================================================================
|
|
||||||
|
|
||||||
Creative Commons is not a party to its public
|
|
||||||
licenses. Notwithstanding, Creative Commons may elect to apply one of
|
|
||||||
its public licenses to material it publishes and in those instances
|
|
||||||
will be considered the “Licensor.” The text of the Creative Commons
|
|
||||||
public licenses is dedicated to the public domain under the CC0 Public
|
|
||||||
Domain Dedication. Except for the limited purpose of indicating that
|
|
||||||
material is shared under a Creative Commons public license or as
|
|
||||||
otherwise permitted by the Creative Commons policies published at
|
|
||||||
creativecommons.org/policies, Creative Commons does not authorize the
|
|
||||||
use of the trademark "Creative Commons" or any other trademark or logo
|
|
||||||
of Creative Commons without its prior written consent including,
|
|
||||||
without limitation, in connection with any unauthorized modifications
|
|
||||||
to any of its public licenses or any other arrangements,
|
|
||||||
understandings, or agreements concerning use of licensed material. For
|
|
||||||
the avoidance of doubt, this paragraph does not form part of the
|
|
||||||
public licenses.
|
|
||||||
|
|
||||||
Creative Commons may be contacted at creativecommons.org.
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
# Maintainers
|
# Maintainers
|
||||||
|
|
||||||
- Sebastien Boeuf - @sboeuf
|
- Sebastien Boeuf <sebastien.boeuf@intel.com> @sboeuf
|
||||||
- Robert Bradford - @rbradford
|
- Robert Bradford <robert.bradford@intel.com> @rbradford
|
||||||
- Bo Chen - @likebreath
|
- Samuel Ortiz <sameo@linux.intel.com> @sameo
|
||||||
- Samuel Ortiz - @sameo
|
- Chao P Peng <chao.p.peng@linux.intel.com> @chao-p
|
||||||
- Wei Liu - @liuw
|
|
||||||
- Michael Zhao - @michael2012z
|
|
||||||
|
|||||||
520
README.md
520
README.md
@@ -1,408 +1,324 @@
|
|||||||
- [1. What is Cloud Hypervisor?](#1-what-is-cloud-hypervisor)
|
[](https://travis-ci.com/cloud-hypervisor/cloud-hypervisor)
|
||||||
- [Objectives](#objectives)
|
|
||||||
- [High Level](#high-level)
|
1. [What is Cloud Hypervisor?](#1-what-is-cloud-hypervisor)
|
||||||
- [Architectures](#architectures)
|
* [Requirements](#requirements)
|
||||||
- [Guest OS](#guest-os)
|
+ [High Level](#high-level)
|
||||||
- [2. Getting Started](#2-getting-started)
|
+ [Architectures](#architectures)
|
||||||
- [Host OS](#host-os)
|
+ [Guest OS](#guest-os)
|
||||||
- [Use Pre-built Binaries](#use-pre-built-binaries)
|
2. [Getting Started](#2-getting-started)
|
||||||
- [Packages](#packages)
|
* [Clone and build](#clone-and-build)
|
||||||
- [Building from Source](#building-from-source)
|
* [Run](#run)
|
||||||
- [Booting Linux](#booting-linux)
|
+ [Cloud image](#cloud-image)
|
||||||
- [Firmware Booting](#firmware-booting)
|
+ [Custom kernel and disk image](#custom-kernel-and-disk-image)
|
||||||
- [Custom Kernel and Disk Image](#custom-kernel-and-disk-image)
|
- [Building your kernel](#building-your-kernel)
|
||||||
- [Building your Kernel](#building-your-kernel)
|
|
||||||
- [Disk image](#disk-image)
|
- [Disk image](#disk-image)
|
||||||
- [Booting the guest VM](#booting-the-guest-vm)
|
- [Booting the guest VM](#booting-the-guest-vm)
|
||||||
- [3. Status](#3-status)
|
3. [Status](#2-status)
|
||||||
- [Hot Plug](#hot-plug)
|
* [Device Model](#device-model)
|
||||||
- [Device Model](#device-model)
|
* [TODO](#todo)
|
||||||
- [Roadmap](#roadmap)
|
4. [rust-vmm dependency](#4-rust-vmm-dependency)
|
||||||
- [4. Relationship with _Rust VMM_ Project](#4-relationship-with-rust-vmm-project)
|
* [Firecracker and crosvm](#firecracker-and-crosvm)
|
||||||
- [Differences with Firecracker and crosvm](#differences-with-firecracker-and-crosvm)
|
5. [Community](#5-community)
|
||||||
- [5. Community](#5-community)
|
* [Join us](#join-us)
|
||||||
- [Contribute](#contribute)
|
6. [Security](#6-security)
|
||||||
- [Slack](#slack)
|
|
||||||
- [Mailing list](#mailing-list)
|
|
||||||
- [Security issues](#security-issues)
|
|
||||||
|
|
||||||
# 1. What is Cloud Hypervisor?
|
# 1. What is Cloud Hypervisor?
|
||||||
|
|
||||||
Cloud Hypervisor is an open source Virtual Machine Monitor (VMM) that runs on
|
**This project is an experiment and should not be used with production workloads.**
|
||||||
top of the [KVM](https://www.kernel.org/doc/Documentation/virtual/kvm/api.txt)
|
|
||||||
hypervisor and the Microsoft Hypervisor (MSHV).
|
|
||||||
|
|
||||||
The project focuses on running modern, _Cloud Workloads_, on specific, common,
|
Cloud Hypervisor is an open source Virtual Machine Monitor (VMM) that runs on top of [KVM](https://www.kernel.org/doc/Documentation/virtual/kvm/api.txt).
|
||||||
hardware architectures. In this case _Cloud Workloads_ refers to those that are
|
The project focuses on exclusively running modern, cloud workloads, on top of a limited set of hardware architectures and platforms.
|
||||||
run by customers inside a Cloud Service Provider. This means modern operating
|
Cloud workloads refers to those that are usually run by customers inside a cloud provider. For our purposes this means modern
|
||||||
systems with most I/O handled by
|
Linux* distributions with most I/O handled by paravirtualised devices (i.e. virtio), no requirement for legacy devices and recent CPUs and KVM.
|
||||||
paravirtualised devices (e.g. _virtio_), no requirement for legacy devices, and
|
|
||||||
64-bit CPUs.
|
|
||||||
|
|
||||||
Cloud Hypervisor is implemented in [Rust](https://www.rust-lang.org/) and is
|
Cloud Hypervisor is implemented in [Rust](https://www.rust-lang.org/) and is based on the [rust-vmm](https://github.com/rust-vmm) crates.
|
||||||
based on the [Rust VMM](https://github.com/rust-vmm) crates.
|
|
||||||
|
|
||||||
## Objectives
|
## Objectives
|
||||||
|
|
||||||
### High Level
|
### High Level
|
||||||
|
|
||||||
- Runs on KVM or MSHV
|
* KVM and KVM only based
|
||||||
- Minimal emulation
|
* Minimal emulation
|
||||||
- Low latency
|
* Low latency
|
||||||
- Low memory footprint
|
* Low memory footprint
|
||||||
- Low complexity
|
* Low complexity
|
||||||
- High performance
|
* High performance
|
||||||
- Small attack surface
|
* Small attack surface
|
||||||
- 64-bit support only
|
* 64-bit support only
|
||||||
- CPU, memory, PCI hotplug
|
* Build time configurable CPU, memory, PCI and NVDIMM hotplug
|
||||||
- Machine to machine migration
|
* Machine to machine migration
|
||||||
|
|
||||||
### Architectures
|
### Architectures
|
||||||
|
|
||||||
Cloud Hypervisor's main supported architectures are `x86-64` and `AArch64`,
|
`cloud-hypervisor` only supports the `x86-64` CPU architecture for now.
|
||||||
with functionality varying across these platforms. The functionality
|
|
||||||
differences between `x86-64` and `AArch64` are documented in
|
We're planning to add support for the `AArch64` architecture in the future.
|
||||||
[#1125](https://github.com/cloud-hypervisor/cloud-hypervisor/issues/1125).
|
|
||||||
The `riscv64` architecture support is experimental and offers limited
|
|
||||||
functionality. For more details and instructions, please refer to [riscv
|
|
||||||
documentation](docs/riscv.md).
|
|
||||||
|
|
||||||
### Guest OS
|
### Guest OS
|
||||||
|
* `64-bit Linux`
|
||||||
|
|
||||||
Cloud Hypervisor supports `64-bit Linux` and Windows 10/Windows Server 2019.
|
Support for *modern* 64-bit Windows guest is being evaluated.
|
||||||
|
|
||||||
# 2. Getting Started
|
# 2. Getting Started
|
||||||
|
|
||||||
The following sections describe how to build and run Cloud Hypervisor.
|
We create a folder to build and run `cloud-hypervisor` at `$HOME/cloud-hypervisor`
|
||||||
|
|
||||||
## Prerequisites for AArch64
|
|
||||||
|
|
||||||
- AArch64 servers (recommended) or development boards equipped with the GICv3
|
|
||||||
interrupt controller.
|
|
||||||
|
|
||||||
## Host OS
|
|
||||||
|
|
||||||
For required KVM functionality and adequate performance the recommended host
|
|
||||||
kernel version is 5.13. The majority of the CI currently tests with kernel
|
|
||||||
version 5.15.
|
|
||||||
|
|
||||||
## Use Pre-built Binaries
|
|
||||||
|
|
||||||
The recommended approach to getting started with Cloud Hypervisor is by using a
|
|
||||||
pre-built binary. Binaries are available for the [latest
|
|
||||||
release](https://github.com/cloud-hypervisor/cloud-hypervisor/releases/latest).
|
|
||||||
Use `cloud-hypervisor-static` for `x86-64` or `cloud-hypervisor-static-aarch64`
|
|
||||||
for `AArch64` platform.
|
|
||||||
|
|
||||||
## Packages
|
|
||||||
|
|
||||||
For convenience, packages are also available targeting some popular Linux
|
|
||||||
distributions. This is thanks to the [Open Build
|
|
||||||
Service](https://build.opensuse.org). The [OBS
|
|
||||||
README](https://github.com/cloud-hypervisor/obs-packaging) explains how to
|
|
||||||
enable the repository in a supported Linux distribution and install Cloud Hypervisor
|
|
||||||
and accompanying packages. Please report any packaging issues in the
|
|
||||||
[obs-packaging](https://github.com/cloud-hypervisor/obs-packaging) repository.
|
|
||||||
|
|
||||||
## Building from Source
|
|
||||||
|
|
||||||
Please see the [instructions for building from source](docs/building.md) if you
|
|
||||||
do not wish to use the pre-built binaries.
|
|
||||||
|
|
||||||
## Booting Linux
|
|
||||||
|
|
||||||
Cloud Hypervisor boots guests in one of two ways. The first is direct
|
|
||||||
kernel boot, where a kernel image is passed to `--kernel`. The x86-64
|
|
||||||
kernel must be built with PVH support or be a bzImage. The second is
|
|
||||||
firmware boot, where a firmware image is passed to `--firmware` and
|
|
||||||
brings up the guest's normal boot loader.
|
|
||||||
|
|
||||||
Two firmware options are supported, and which one works best depends
|
|
||||||
on the guest OS. [Rust Hypervisor
|
|
||||||
Firmware](https://github.com/cloud-hypervisor/rust-hypervisor-firmware)
|
|
||||||
is a lightweight Rust-based PVH firmware. The edk2 UEFI firmware is
|
|
||||||
called `CLOUDHV.fd` for x86-64 and `CLOUDHV_EFI.fd` for AArch64.
|
|
||||||
Prebuilt binaries for both are available at their respective releases
|
|
||||||
pages, [Rust Hypervisor
|
|
||||||
Firmware](https://github.com/cloud-hypervisor/rust-hypervisor-firmware/releases/latest)
|
|
||||||
and [our edk2
|
|
||||||
fork](https://github.com/cloud-hypervisor/edk2/releases/latest).
|
|
||||||
The edk2 fork carries customizations required to boot AArch64 guests
|
|
||||||
on cloud-hypervisor. See [docs/uefi.md](docs/uefi.md) for differences
|
|
||||||
with upstream tianocore/edk2.
|
|
||||||
|
|
||||||
### Firmware Booting
|
|
||||||
|
|
||||||
Cloud Hypervisor supports booting disk images containing all needed components
|
|
||||||
to run cloud workloads, a.k.a. cloud images.
|
|
||||||
|
|
||||||
The following sample commands will download an Ubuntu Cloud image, converting
|
|
||||||
it into a format that Cloud Hypervisor can use and a firmware to boot the image
|
|
||||||
with.
|
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
$ wget https://cloud-images.ubuntu.com/focal/current/focal-server-cloudimg-amd64.img
|
$ export CLOUDH=$HOME/cloud-hypervisor
|
||||||
$ qemu-img convert -p -f qcow2 -O raw focal-server-cloudimg-amd64.img focal-server-cloudimg-amd64.raw
|
$ mkdir $CLOUDH
|
||||||
$ wget https://github.com/cloud-hypervisor/rust-hypervisor-firmware/releases/download/0.4.2/hypervisor-fw
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The Ubuntu cloud images do not ship with a default password so it necessary to
|
## Clone and build
|
||||||
use a `cloud-init` disk image to customise the image on the first boot. A basic
|
|
||||||
`cloud-init` image is generated by this [script](scripts/create-cloud-init.sh).
|
First you need to clone and build the cloud-hypervisor repo:
|
||||||
This seeds the image with a default username/password of `cloud/cloud123`. It
|
|
||||||
is only necessary to add this disk image on the first boot. Script also assigns
|
|
||||||
default IP address using `test_data/cloud-init/ubuntu/local/network-config` details
|
|
||||||
with `--net "mac=12:34:56:78:90:ab,tap="` option. Then the matching mac address
|
|
||||||
interface will be enabled as per `network-config` details.
|
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
$ sudo setcap cap_net_admin+ep ./cloud-hypervisor
|
$ pushd $CLOUDH
|
||||||
$ ./create-cloud-init.sh
|
$ git clone https://github.com/cloud-hypervisor/cloud-hypervisor.git
|
||||||
$ ./cloud-hypervisor \
|
$ cd cloud-hypervisor
|
||||||
--firmware ./hypervisor-fw \
|
$ cargo build --release
|
||||||
--disk path=focal-server-cloudimg-amd64.raw path=/tmp/ubuntu-cloudinit.img \
|
|
||||||
--cpus boot=4 \
|
# We need to give the cloud-hypervisor binary the NET_ADMIN capabilities for it to set TAP interfaces up on the host.
|
||||||
--memory size=1024M \
|
$ sudo setcap cap_net_admin+ep ./target/release/cloud-hypervisor
|
||||||
--net "tap=,mac=,ip=,mask="
|
|
||||||
|
$ popd
|
||||||
```
|
```
|
||||||
|
|
||||||
If access to the firmware messages or interaction with the boot loader (e.g.
|
This will build a `cloud-hypervisor` binary under `$CLOUDH/cloud-hypervisor/target/release/cloud-hypervisor`.
|
||||||
GRUB) is required then it necessary to switch to the serial console instead of
|
|
||||||
`virtio-console`.
|
### Containerized builds and tests
|
||||||
|
|
||||||
|
If you want to build and test Cloud Hypervisor without having to install all the
|
||||||
|
required dependencies (The rust toolchain, cargo tools, etc), you can also use
|
||||||
|
Cloud Hypervisor's development script: `dev_cli.sh`. Please note that upon its
|
||||||
|
first invocation, this script will pull a fairly large container image.
|
||||||
|
|
||||||
|
For example, to build the Cloud Hypervisor release binary:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
$ ./cloud-hypervisor \
|
$ pushd $CLOUDH
|
||||||
|
$ cd cloud-hypervisor
|
||||||
|
$ ./scripts/dev_cli.sh build --release
|
||||||
|
```
|
||||||
|
|
||||||
|
With `dev_cli.sh`, one can also run the Cloud Hypervisor CI locally. This can be
|
||||||
|
very convenient for debugging CI errors without having to fully rely on the
|
||||||
|
Cloud Hypervisor CI infrastructure.
|
||||||
|
|
||||||
|
For example, to run the Cloud Hypervisor unit tests:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ ./scripts/dev_cli.sh tests --unit
|
||||||
|
```
|
||||||
|
|
||||||
|
Run the `./scripts/dev_cli.sh --help` command to view all the supported
|
||||||
|
development script commands and their related options.
|
||||||
|
|
||||||
|
## Run
|
||||||
|
|
||||||
|
You can run a guest VM by either using an existing cloud image or booting into your own kernel and disk image.
|
||||||
|
|
||||||
|
### Cloud image
|
||||||
|
|
||||||
|
`cloud-hypervisor` supports booting disk images containing all needed
|
||||||
|
components to run cloud workloads, a.k.a. cloud images. To do that we rely on
|
||||||
|
the [Rust Hypervisor
|
||||||
|
Firmware](https://github.com/cloud-hypervisor/rust-hypervisor-firmware) project to provide
|
||||||
|
an ELF
|
||||||
|
formatted KVM firmware for `cloud-hypervisor` to directly boot into.
|
||||||
|
|
||||||
|
We need to get the latest `rust-hypervisor-firmware` release and also a working cloud image. Here we will use a Clear Linux image:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ pushd $CLOUDH
|
||||||
|
$ wget https://download.clearlinux.org/releases/31890/clear/clear-31890-kvm.img.xz
|
||||||
|
$ unxz clear-31890-kvm.img.xz
|
||||||
|
$ wget https://github.com/cloud-hypervisor/rust-hypervisor-firmware/releases/download/0.2.6/hypervisor-fw
|
||||||
|
$ popd
|
||||||
|
```
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ pushd $CLOUDH
|
||||||
|
$ sudo setcap cap_net_admin+ep ./cloud-hypervisor/target/release/cloud-hypervisor
|
||||||
|
$ ./cloud-hypervisor/target/release/cloud-hypervisor \
|
||||||
--kernel ./hypervisor-fw \
|
--kernel ./hypervisor-fw \
|
||||||
--disk path=focal-server-cloudimg-amd64.raw path=/tmp/ubuntu-cloudinit.img \
|
--disk path=clear-31890-kvm.img \
|
||||||
--cpus boot=4 \
|
--cpus boot=4 \
|
||||||
--memory size=1024M \
|
--memory size=1024M \
|
||||||
--net "tap=,mac=,ip=,mask=" \
|
--net "tap=,mac=,ip=,mask=" \
|
||||||
--serial tty \
|
--rng
|
||||||
--console off
|
$ popd
|
||||||
```
|
```
|
||||||
|
|
||||||
## Booting: `--firmware` vs `--kernel`
|
Multiple arguments can be given to the `--disk` parameter.
|
||||||
|
|
||||||
The following scenarios are supported by Cloud Hypervisor to bootstrap a VM, i.e.,
|
### Custom kernel and disk image
|
||||||
to load a payload/bootitem(s):
|
|
||||||
|
|
||||||
- Provide firmware
|
#### Building your kernel
|
||||||
- Provide kernel \[+ cmdline\]\ [+ initrd\]
|
|
||||||
|
|
||||||
Please note that our Cloud Hypervisor firmware (`hypervisor-fw`) has a Xen PVH
|
`cloud-hypervisor` also supports direct kernel boot into a `vmlinux` ELF kernel
|
||||||
boot entry, therefore it can also be booted via the `--kernel` parameter, as
|
image. In order to support virtio-fs and virtio-iommu we have our own development branch. You are of course able to use your own kernel but these instructions will continue with the version that we develop and test against.
|
||||||
seen in some examples.
|
|
||||||
|
|
||||||
### Custom Kernel and Disk Image
|
|
||||||
|
|
||||||
#### Building your Kernel
|
|
||||||
|
|
||||||
Cloud Hypervisor also supports direct kernel boot. For x86-64, a `vmlinux` ELF kernel (compiled with PVH support) or a regular bzImage are supported. In order to support development there is a custom branch; however provided the required options are enabled any recent kernel will suffice.
|
|
||||||
|
|
||||||
To build the kernel:
|
To build the kernel:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
|
|
||||||
# Clone the Cloud Hypervisor Linux branch
|
# Clone the Cloud Hypervisor Linux branch
|
||||||
$ git clone --depth 1 https://github.com/cloud-hypervisor/linux.git -b ch-6.16.9 linux-cloud-hypervisor
|
$ pushd $CLOUDH
|
||||||
|
$ git clone --depth 1 https://github.com/cloud-hypervisor/linux.git -b virtio-fs-virtio-iommu-5.5-rc1 linux-cloud-hypervisor
|
||||||
$ pushd linux-cloud-hypervisor
|
$ pushd linux-cloud-hypervisor
|
||||||
$ make ch_defconfig
|
|
||||||
# Do native build of the x86-64 kernel
|
# Use the cloud-hypervisor kernel config to build your kernel
|
||||||
$ KCFLAGS="-Wa,-mx86-used-note=no" make bzImage -j `nproc`
|
$ cp $CLOUDH/cloud-hypervisor/resources/linux-virtio-fs-virtio-iommu-config .config
|
||||||
# Do native build of the AArch64 kernel
|
$ make bzImage -j `nproc`
|
||||||
$ make -j `nproc`
|
|
||||||
$ popd
|
$ popd
|
||||||
```
|
```
|
||||||
|
|
||||||
For x86-64, the `vmlinux` kernel image will then be located at
|
The `vmlinux` kernel image will then be located at `linux-cloud-hypervisor/arch/x86/boot/compressed/vmlinux.bin`.
|
||||||
`linux-cloud-hypervisor/arch/x86/boot/compressed/vmlinux.bin`.
|
|
||||||
For AArch64, the `Image` kernel image will then be located at
|
|
||||||
`linux-cloud-hypervisor/arch/arm64/boot/Image`.
|
|
||||||
|
|
||||||
#### Disk image
|
#### Disk image
|
||||||
|
|
||||||
For the disk image the same Ubuntu image as before can be used. This contains
|
For the disk image, we will use a Clear Linux cloud image that contains a root partition:
|
||||||
an `ext4` root filesystem.
|
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
$ wget https://cloud-images.ubuntu.com/focal/current/focal-server-cloudimg-amd64.img # x86-64
|
$ pushd $CLOUDH
|
||||||
$ wget https://cloud-images.ubuntu.com/focal/current/focal-server-cloudimg-arm64.img # AArch64
|
$ wget https://download.clearlinux.org/releases/31890/clear/clear-31890-kvm.img.xz
|
||||||
$ qemu-img convert -p -f qcow2 -O raw focal-server-cloudimg-amd64.img focal-server-cloudimg-amd64.raw # x86-64
|
$ unxz clear-31890-kvm.img.xz
|
||||||
$ qemu-img convert -p -f qcow2 -O raw focal-server-cloudimg-arm64.img focal-server-cloudimg-arm64.raw # AArch64
|
$ popd
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Booting the guest VM
|
#### Booting the guest VM
|
||||||
|
|
||||||
These sample commands boot the disk image using the custom kernel whilst also
|
Now we can directly boot into our custom kernel and make it use the Clear Linux root partition.
|
||||||
supplying the desired kernel command line.
|
If we want to have 4 vCPUs and 512 MBytes of memory:
|
||||||
|
|
||||||
- x86-64
|
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
$ sudo setcap cap_net_admin+ep ./cloud-hypervisor
|
$ pushd $CLOUDH
|
||||||
$ ./create-cloud-init.sh
|
$ sudo setcap cap_net_admin+ep ./cloud-hypervisor/target/release/cloud-hypervisor
|
||||||
$ ./cloud-hypervisor \
|
$ ./cloud-hypervisor/target/release/cloud-hypervisor \
|
||||||
--kernel ./linux-cloud-hypervisor/arch/x86/boot/compressed/vmlinux.bin \
|
--kernel ./linux-cloud-hypervisor/arch/x86/boot/compressed/vmlinux.bin \
|
||||||
--disk path=focal-server-cloudimg-amd64.raw path=/tmp/ubuntu-cloudinit.img \
|
--disk path=clear-31890-kvm.img \
|
||||||
--cmdline "console=hvc0 root=/dev/vda1 rw" \
|
--cmdline "console=hvc0 reboot=k panic=1 nomodules i8042.noaux i8042.nomux i8042.nopnp i8042.dumbkbd root=/dev/vda3" \
|
||||||
--cpus boot=4 \
|
--cpus boot=4 \
|
||||||
--memory size=1024M \
|
--memory size=1024M \
|
||||||
--net "tap=,mac=,ip=,mask="
|
--net "tap=,mac=,ip=,mask=" \
|
||||||
|
--rng
|
||||||
```
|
```
|
||||||
|
|
||||||
- AArch64
|
The above example use the `virtio-console` device as the guest console, and this
|
||||||
|
device may not be enabled soon enough by the guest kernel to get early kernel
|
||||||
|
debug messages.
|
||||||
|
|
||||||
|
When in need for earlier debug messages, using the legacy serial device based
|
||||||
|
console is preferred:
|
||||||
|
|
||||||
```shell
|
|
||||||
$ sudo setcap cap_net_admin+ep ./cloud-hypervisor
|
|
||||||
$ ./create-cloud-init.sh
|
|
||||||
$ ./cloud-hypervisor \
|
|
||||||
--kernel ./linux-cloud-hypervisor/arch/arm64/boot/Image \
|
|
||||||
--disk path=focal-server-cloudimg-arm64.raw path=/tmp/ubuntu-cloudinit.img \
|
|
||||||
--cmdline "console=hvc0 root=/dev/vda1 rw" \
|
|
||||||
--cpus boot=4 \
|
|
||||||
--memory size=1024M \
|
|
||||||
--net "tap=,mac=,ip=,mask="
|
|
||||||
```
|
```
|
||||||
|
$ ./cloud-hypervisor/target/release/cloud-hypervisor \
|
||||||
If earlier kernel messages are required the serial console should be used instead of `virtio-console`.
|
|
||||||
|
|
||||||
- x86-64
|
|
||||||
|
|
||||||
```shell
|
|
||||||
$ ./cloud-hypervisor \
|
|
||||||
--kernel ./linux-cloud-hypervisor/arch/x86/boot/compressed/vmlinux.bin \
|
--kernel ./linux-cloud-hypervisor/arch/x86/boot/compressed/vmlinux.bin \
|
||||||
--console off \
|
--console off \
|
||||||
--serial tty \
|
--serial tty \
|
||||||
--disk path=focal-server-cloudimg-amd64.raw \
|
--disk path=clear-31890-kvm.img \
|
||||||
--cmdline "console=ttyS0 root=/dev/vda1 rw" \
|
--cmdline "console=ttyS0 reboot=k panic=1 nomodules i8042.noaux i8042.nomux i8042.nopnp i8042.dumbkbd root=/dev/vda3" \
|
||||||
--cpus boot=4 \
|
--cpus boot=4 \
|
||||||
--memory size=1024M \
|
--memory size=1024M \
|
||||||
--net "tap=,mac=,ip=,mask="
|
--net "tap=,mac=,ip=,mask=" \
|
||||||
```
|
--rng
|
||||||
|
|
||||||
- AArch64
|
|
||||||
|
|
||||||
```shell
|
|
||||||
$ ./cloud-hypervisor \
|
|
||||||
--kernel ./linux-cloud-hypervisor/arch/arm64/boot/Image \
|
|
||||||
--console off \
|
|
||||||
--serial tty \
|
|
||||||
--disk path=focal-server-cloudimg-arm64.raw \
|
|
||||||
--cmdline "console=ttyAMA0 root=/dev/vda1 rw" \
|
|
||||||
--cpus boot=4 \
|
|
||||||
--memory size=1024M \
|
|
||||||
--net "tap=,mac=,ip=,mask="
|
|
||||||
```
|
```
|
||||||
|
|
||||||
# 3. Status
|
# 3. Status
|
||||||
|
|
||||||
Cloud Hypervisor is under active development. The following stability
|
`cloud-hypervisor` is in a very early, pre-alpha stage. Use at your own risk!
|
||||||
guarantees are currently made:
|
|
||||||
|
|
||||||
* The API (including command line options) will not be removed or changed in a
|
As of 2019-12-12, the following cloud images are supported:
|
||||||
breaking way without a minimum of 2 major releases notice. Where possible
|
* [Clear Linux](https://download.clearlinux.org/current/) (cloudguest and kvm)
|
||||||
warnings will be given about the use of deprecated functionality and the
|
* [Ubuntu Bionic](https://cloud-images.ubuntu.com/bionic/current/) (cloudimg)
|
||||||
deprecations will be documented in the release notes.
|
* [Ubuntu Eoan](https://cloud-images.ubuntu.com/eoan/current/) (cloudimg)
|
||||||
|
|
||||||
* Point releases will be made between individual releases where there are
|
Direct kernel boot to userspace should work with most rootfs.
|
||||||
substantial bug fixes or security issues that need to be fixed. These point
|
|
||||||
releases will only include bug fixes.
|
|
||||||
|
|
||||||
Currently the following items are **not** guaranteed across updates:
|
|
||||||
|
|
||||||
* Snapshot/restore is not supported across different versions
|
|
||||||
* Live migration is not supported across different versions
|
|
||||||
* The following features are considered experimental and may change
|
|
||||||
substantially between releases: TDX, vfio-user, vDPA.
|
|
||||||
|
|
||||||
Further details can be found in the [release documentation](docs/releases.md).
|
|
||||||
|
|
||||||
As of 2023-01-03, the following cloud images are supported:
|
|
||||||
|
|
||||||
- [Ubuntu Focal](https://cloud-images.ubuntu.com/focal/current/) (focal-server-cloudimg-{amd64,arm64}.img)
|
|
||||||
- [Ubuntu Jammy](https://cloud-images.ubuntu.com/jammy/current/) (jammy-server-cloudimg-{amd64,arm64}.img)
|
|
||||||
- [Ubuntu Noble](https://cloud-images.ubuntu.com/noble/current/) (noble-server-cloudimg-{amd64,arm64}.img)
|
|
||||||
- [Fedora 36](https://archives.fedoraproject.org/pub/archive/fedora/linux/releases/36/Cloud/) ([Fedora-Cloud-Base-36-1.5.x86_64.raw.xz](https://archives.fedoraproject.org/pub/archive/fedora/linux/releases/36/Cloud/x86_64/images/) / [Fedora-Cloud-Base-36-1.5.aarch64.raw.xz](https://archives.fedoraproject.org/pub/archive/fedora/linux/releases/36/Cloud/aarch64/images/))
|
|
||||||
|
|
||||||
Direct kernel boot to userspace should work with a rootfs from most
|
|
||||||
distributions although you may need to enable exotic filesystem types in the
|
|
||||||
reference kernel configuration (e.g. XFS or btrfs.)
|
|
||||||
|
|
||||||
## Hot Plug
|
## Hot Plug
|
||||||
|
|
||||||
Cloud Hypervisor supports hotplug of CPUs, passthrough devices (VFIO),
|
This [document](https://github.com/cloud-hypervisor/cloud-hypervisor/blob/master/docs/hotplug.md) details how to add devices to
|
||||||
`virtio-{net,block,pmem,fs,vsock}` and memory resizing. This
|
a running VM. Currently only CPU hot plug is supported.
|
||||||
[document](docs/hotplug.md) details how to add devices to a running VM.
|
|
||||||
|
|
||||||
## Device Model
|
## Device Model
|
||||||
|
|
||||||
Details of the device model can be found in this
|
Follow this [documentation](https://github.com/cloud-hypervisor/cloud-hypervisor/blob/master/docs/device_model.md).
|
||||||
[documentation](docs/device_model.md).
|
|
||||||
|
|
||||||
## Roadmap
|
## TODO
|
||||||
|
|
||||||
The project roadmap is tracked through a [GitHub
|
We are not tracking the `cloud-hypervisor` TODO list from a specific git tracked file but through
|
||||||
project](https://github.com/orgs/cloud-hypervisor/projects/6).
|
[github issues](https://github.com/cloud-hypervisor/cloud-hypervisor/issues/new) instead.
|
||||||
|
|
||||||
# 4. Relationship with _Rust VMM_ Project
|
# 4. `rust-vmm` project dependency
|
||||||
|
|
||||||
In order to satisfy the design goal of having a high-performance,
|
In order to satisfy the design goal of having a high-performance, security-focused hypervisor the decision
|
||||||
security-focused hypervisor the decision was made to use the
|
was made to use the [Rust](https://www.rust-lang.org/) programming language.
|
||||||
[Rust](https://www.rust-lang.org/) programming language. The language's strong
|
The language's strong focus on memory and thread safety makes it an ideal candidate for implementing VMMs
|
||||||
focus on memory and thread safety makes it an ideal candidate for implementing
|
|
||||||
VMMs.
|
|
||||||
|
|
||||||
Instead of implementing the VMM components from scratch, Cloud Hypervisor is
|
Instead of implementing the VMM components from scratch, `cloud-hypervisor` is importing the [rust-vmm](https://github.com/rust-vmm)
|
||||||
importing the [Rust VMM](https://github.com/rust-vmm) crates, and sharing code
|
crates, and sharing code and architecture together with other VMMs like e.g. Amazon's [Firecracker](https://firecracker-microvm.github.io/)
|
||||||
and architecture together with other VMMs like e.g. Amazon's
|
and Google's [crosvm](https://chromium.googlesource.com/chromiumos/platform/crosvm/).
|
||||||
[Firecracker](https://firecracker-microvm.github.io/) and Google's
|
|
||||||
[crosvm](https://chromium.googlesource.com/chromiumos/platform/crosvm/).
|
|
||||||
|
|
||||||
Cloud Hypervisor embraces the _Rust VMM_ project's goals, which is to be able
|
`cloud-hypervisor` embraces the rust-vmm project goals, which is to be able to share and re-use
|
||||||
to share and re-use as many virtualization crates as possible.
|
as many virtualization crates as possible. As such, the `cloud-hypervisor` relationship with the rust-vmm
|
||||||
|
project is twofold:
|
||||||
|
|
||||||
## Differences with Firecracker and crosvm
|
1. It will use as much of the rust-vmm code as possible. Any new rust-vmm crate that's relevant to the project
|
||||||
|
goals will be integrated as soon as possible.
|
||||||
|
2. As it is likely that the rust-vmm project will lack some of the features that `cloud-hypervisor` needs (e.g. ACPI,
|
||||||
|
VFIO, vhost-user, etc), we will be using the `cloud-hypervisor` VMM to implement and test them, and contribute them
|
||||||
|
back to the rust-vmm project.
|
||||||
|
|
||||||
A large part of the Cloud Hypervisor code is based on either the Firecracker or
|
## Firecracker and crosvm
|
||||||
the crosvm project's implementations. Both of these are VMMs written in Rust
|
|
||||||
with a focus on safety and security, like Cloud Hypervisor.
|
|
||||||
|
|
||||||
The goal of the Cloud Hypervisor project differs from the aforementioned
|
A large part of the `cloud-hypervisor` code is based on either the Firecracker or the crosvm projects implementations.
|
||||||
projects in that it aims to be a general purpose VMM for _Cloud Workloads_ and
|
Both of these are VMMs written in Rust with a focus on safety and security, like Cloud Hypervisor.
|
||||||
not limited to container/serverless or client workloads.
|
|
||||||
|
|
||||||
The Cloud Hypervisor community thanks the communities of both the Firecracker
|
However we want to emphasize that the Cloud Hypervisor project is neither a fork nor a reimplementation of any of those
|
||||||
and crosvm projects for their excellent work.
|
projects. The goals and use cases we're trying to meet are different.
|
||||||
|
We're aiming at supporting cloud workloads, i.e. those modern, full Linux distribution images currently being run by
|
||||||
|
Cloud Service Provider (CSP) tenants.
|
||||||
|
|
||||||
|
Our primary target is not to support client or serverless use cases, and as such our code base already diverges
|
||||||
|
from the crosvm and Firecracker ones. As we add more features to support our use cases, we believe that the divergence
|
||||||
|
will increase while at the same time sharing as much of the fundamental virtualization code through the rust-vmm project
|
||||||
|
crates as possible.
|
||||||
|
|
||||||
# 5. Community
|
# 5. Community
|
||||||
|
|
||||||
The Cloud Hypervisor project follows the governance, and community guidelines
|
We are working on building a global, diverse and collaborative community around the Cloud Hypervisor project.
|
||||||
described in the [Community](https://github.com/cloud-hypervisor/community)
|
Anyone who is interested in [contributing](CONTRIBUTING.md) to the project is welcome to participate.
|
||||||
repository.
|
|
||||||
|
|
||||||
## Contribute
|
We believe that contributing to a open source project like Cloud Hypervisor covers a lot more than just sending
|
||||||
|
code. Testing, documentation, pull request reviews, bug reports, feature requests, project improvement suggestions,
|
||||||
|
etc, are all equal and welcome means of contribution. See the [CONTRIBUTING](CONTRIBUTING.md) document for more details.
|
||||||
|
|
||||||
The project strongly believes in building a global, diverse and collaborative
|
## Join us
|
||||||
community around the Cloud Hypervisor project. Anyone who is interested in
|
|
||||||
[contributing](CONTRIBUTING.md) to the project is welcome to participate.
|
|
||||||
|
|
||||||
Contributing to a open source project like Cloud Hypervisor covers a lot more
|
Get an [invite to our Slack channel](https://join.slack.com/t/cloud-hypervisor/shared_invite/enQtNjY3MTE3MDkwNDQ4LWQ1MTA1ZDVmODkwMWQ1MTRhYzk4ZGNlN2UwNTI3ZmFlODU0OTcwOWZjMTkwZDExYWE3YjFmNzgzY2FmNDAyMjI)
|
||||||
than just sending code. Testing, documentation, pull request
|
and [join us on Slack](https://cloud-hypervisor.slack.com/).
|
||||||
reviews, bug reports, feature requests, project improvement suggestions, etc,
|
|
||||||
are all equal and welcome means of contribution. See the
|
|
||||||
[CONTRIBUTING](CONTRIBUTING.md) document for more details.
|
|
||||||
|
|
||||||
## Slack
|
# 6. Security
|
||||||
|
|
||||||
Get an [invite to our Slack channel](https://join.slack.com/t/cloud-hypervisor/shared_invite/enQtNjY3MTE3MDkwNDQ4LWQ1MTA1ZDVmODkwMWQ1MTRhYzk4ZGNlN2UwNTI3ZmFlODU0OTcwOWZjMTkwZDExYWE3YjFmNzgzY2FmNDAyMjI),
|
**Reporting a Potential Security Vulnerability**: If you have discovered
|
||||||
[join us on Slack](https://cloud-hypervisor.slack.com/), and [participate in our community activities](https://cloud-hypervisor.slack.com/archives/C04R5DUQVBN).
|
potential security vulnerability in this project, please send an e-mail to
|
||||||
|
secure@intel.com. For issues related to Intel Products, please visit
|
||||||
|
https://security-center.intel.com.
|
||||||
|
|
||||||
## Mailing list
|
It is important to include the following details:
|
||||||
|
- The projects and versions affected
|
||||||
|
- Detailed description of the vulnerability
|
||||||
|
- Information on known exploits
|
||||||
|
|
||||||
Please report bugs using the [GitHub issue
|
Vulnerability information is extremely sensitive. Please encrypt all security
|
||||||
tracker](https://github.com/cloud-hypervisor/cloud-hypervisor/issues) but for
|
vulnerability reports using our *PGP key*
|
||||||
broader community discussions you may use our [mailing
|
|
||||||
list](https://lists.cloudhypervisor.org/g/dev/).
|
|
||||||
|
|
||||||
## Security issues
|
A member of the Intel Product Security Team will review your e-mail and
|
||||||
|
contact you to to collaborate on resolving the issue. For more information on
|
||||||
|
how Intel works to resolve security issues, see: *Vulnerability Handling
|
||||||
|
Guidelines*
|
||||||
|
|
||||||
Please contact the maintainers listed in the MAINTAINERS.md file with security issues.
|
PGP Key: https://www.intel.com/content/www/us/en/security-center/pgp-public-key.html
|
||||||
|
|
||||||
|
Vulnerability Handling Guidelines: https://www.intel.com/content/www/us/en/security-center/vulnerability-handling-guidelines.html
|
||||||
|
|||||||
68
SECURITY.md
68
SECURITY.md
@@ -1,68 +0,0 @@
|
|||||||
# Cloud Hypervisor Security Policy
|
|
||||||
|
|
||||||
## What Is A Vulnerability?
|
|
||||||
|
|
||||||
Cloud Hypervisor's threat model is in [docs/threat-model.md](docs/threat-model.md).
|
|
||||||
A vulnerability is defined as an entity defined in the threat model as
|
|
||||||
untrusted being able to cause Cloud Hypervisor to do something that the
|
|
||||||
threat model states it should not be able to cause.
|
|
||||||
|
|
||||||
Any known or potential memory corruption is assumed exploitable until
|
|
||||||
and unless proven otherwise. Attackers have shown repeatedly that memory
|
|
||||||
corruption can usually be turned into arbitrary code execution. While
|
|
||||||
doing so may be very difficult, LLMs have made this much easier.
|
|
||||||
|
|
||||||
Mishandling of a memory allocation failure (either user-mode or
|
|
||||||
kernel-mode) is still in scope. While this will typically result in
|
|
||||||
Cloud Hypervisor crashing, Cloud Hypervisor must not corrupt its own
|
|
||||||
memory or otherwise behave insecurely.
|
|
||||||
|
|
||||||
## How To Report A Vulnerability?
|
|
||||||
|
|
||||||
Vulnerabilities should be reported using the GitHub Security Advisory
|
|
||||||
process. Do not file an issue, as that immediately gives malicious
|
|
||||||
actors knowledge of the vulnerability. A proof of concept is strongly
|
|
||||||
preferred but not strictly required. A patch is also greatly
|
|
||||||
appreciated but is also not a requirement.
|
|
||||||
|
|
||||||
Cloud Hypervisor does not currently have any bug bounty program.
|
|
||||||
|
|
||||||
The use of automated tooling to find vulnerabilities is encouraged.
|
|
||||||
This includes large language models and other forms of AI. The tool used
|
|
||||||
should be noted in the report. The human making the report is
|
|
||||||
responsible for its contents and for filtering out false positives.
|
|
||||||
|
|
||||||
It is not expected that every single report will be valid, but reporters
|
|
||||||
must make a good-faith effort to avoid false positives. Striving to achieve a
|
|
||||||
zero false-positive rate will reduce the number of correct reports and is not
|
|
||||||
worthwhile.
|
|
||||||
|
|
||||||
## When A Vulnerability Is Reported
|
|
||||||
|
|
||||||
The Cloud Hypervisor maintainers will triage any reported
|
|
||||||
vulnerabilities. Once patches are ready, an embargo period of up to 14
|
|
||||||
days starts. There will be a public announcement that a vulnerability
|
|
||||||
is under embargo, along with its GHSA number.
|
|
||||||
|
|
||||||
The following organizations will receive full access to embargoed
|
|
||||||
information. They are only permitted to use this information for
|
|
||||||
preparing and deploying patches. Information must be limited to those
|
|
||||||
who need to know. This includes access to both patched source code and
|
|
||||||
patched binaries.
|
|
||||||
|
|
||||||
- Microsoft
|
|
||||||
- Crusoe
|
|
||||||
- Cyberus Technology
|
|
||||||
- Meta
|
|
||||||
- Google
|
|
||||||
- UbiCloud
|
|
||||||
|
|
||||||
This list may be extended by filing a PR. It will only include:
|
|
||||||
|
|
||||||
- Organizations that distribute Cloud Hypervisor to a significant number
|
|
||||||
of users.
|
|
||||||
|
|
||||||
- Organizations that use Cloud Hypervisor to provide a managed service
|
|
||||||
to a significant number of users.
|
|
||||||
|
|
||||||
The list is documented here for the purposes of transparency.
|
|
||||||
9
acpi_tables/Cargo.toml
Normal file
9
acpi_tables/Cargo.toml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
[package]
|
||||||
|
name = "acpi_tables"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["The Cloud Hypervisor Authors"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
vm-memory = { git = "https://github.com/rust-vmm/vm-memory" }
|
||||||
|
|
||||||
1988
acpi_tables/src/aml.rs
Normal file
1988
acpi_tables/src/aml.rs
Normal file
File diff suppressed because it is too large
Load Diff
12
acpi_tables/src/lib.rs
Normal file
12
acpi_tables/src/lib.rs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
// Copyright © 2019 Intel Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
//
|
||||||
|
|
||||||
|
pub mod aml;
|
||||||
|
pub mod rsdp;
|
||||||
|
pub mod sdt;
|
||||||
|
|
||||||
|
fn generate_checksum(data: &[u8]) -> u8 {
|
||||||
|
(255 - data.iter().fold(0u8, |acc, x| acc.wrapping_add(*x))).wrapping_add(1)
|
||||||
|
}
|
||||||
67
acpi_tables/src/rsdp.rs
Normal file
67
acpi_tables/src/rsdp.rs
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
// Copyright © 2019 Intel Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
//
|
||||||
|
|
||||||
|
use vm_memory::ByteValued;
|
||||||
|
|
||||||
|
#[repr(packed)]
|
||||||
|
#[derive(Clone, Copy, Default)]
|
||||||
|
pub struct RSDP {
|
||||||
|
pub signature: [u8; 8],
|
||||||
|
pub checksum: u8,
|
||||||
|
pub oem_id: [u8; 6],
|
||||||
|
pub revision: u8,
|
||||||
|
_rsdt_addr: u32,
|
||||||
|
pub length: u32,
|
||||||
|
pub xsdt_addr: u64,
|
||||||
|
pub extended_checksum: u8,
|
||||||
|
_reserved: [u8; 3],
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl ByteValued for RSDP {}
|
||||||
|
|
||||||
|
impl RSDP {
|
||||||
|
pub fn new(oem_id: [u8; 6], xsdt_addr: u64) -> Self {
|
||||||
|
let mut rsdp = RSDP {
|
||||||
|
signature: *b"RSD PTR ",
|
||||||
|
checksum: 0,
|
||||||
|
oem_id,
|
||||||
|
revision: 2,
|
||||||
|
_rsdt_addr: 0,
|
||||||
|
length: std::mem::size_of::<RSDP>() as u32,
|
||||||
|
xsdt_addr,
|
||||||
|
extended_checksum: 0,
|
||||||
|
_reserved: [0; 3],
|
||||||
|
};
|
||||||
|
|
||||||
|
rsdp.checksum = super::generate_checksum(&rsdp.as_slice()[0..19]);
|
||||||
|
rsdp.extended_checksum = super::generate_checksum(&rsdp.as_slice());
|
||||||
|
rsdp
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn len() -> usize {
|
||||||
|
std::mem::size_of::<RSDP>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::RSDP;
|
||||||
|
use vm_memory::bytes::ByteValued;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_rsdp() {
|
||||||
|
let rsdp = RSDP::new(*b"CHYPER", 0xdead_beef);
|
||||||
|
let sum = rsdp
|
||||||
|
.as_slice()
|
||||||
|
.iter()
|
||||||
|
.fold(0u8, |acc, x| acc.wrapping_add(*x));
|
||||||
|
assert_eq!(sum, 0);
|
||||||
|
let sum: u8 = rsdp
|
||||||
|
.as_slice()
|
||||||
|
.iter()
|
||||||
|
.fold(0u8, |acc, x| acc.wrapping_add(*x));
|
||||||
|
assert_eq!(sum, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
137
acpi_tables/src/sdt.rs
Normal file
137
acpi_tables/src/sdt.rs
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
// Copyright © 2019 Intel Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
//
|
||||||
|
|
||||||
|
#[repr(packed)]
|
||||||
|
pub struct GenericAddress {
|
||||||
|
pub address_space_id: u8,
|
||||||
|
pub register_bit_width: u8,
|
||||||
|
pub register_bit_offset: u8,
|
||||||
|
pub access_size: u8,
|
||||||
|
pub address: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GenericAddress {
|
||||||
|
pub fn io_port_address(address: u16) -> Self {
|
||||||
|
GenericAddress {
|
||||||
|
address_space_id: 1,
|
||||||
|
register_bit_width: 8,
|
||||||
|
register_bit_offset: 0,
|
||||||
|
access_size: 1,
|
||||||
|
address: u64::from(address),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SDT {
|
||||||
|
data: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::len_without_is_empty)]
|
||||||
|
impl SDT {
|
||||||
|
pub fn new(
|
||||||
|
signature: [u8; 4],
|
||||||
|
length: u32,
|
||||||
|
revision: u8,
|
||||||
|
oem_id: [u8; 6],
|
||||||
|
oem_table: [u8; 8],
|
||||||
|
oem_revision: u32,
|
||||||
|
) -> Self {
|
||||||
|
assert!(length >= 36);
|
||||||
|
|
||||||
|
let mut data = Vec::with_capacity(length as usize);
|
||||||
|
data.extend_from_slice(&signature);
|
||||||
|
data.extend_from_slice(&length.to_le_bytes());
|
||||||
|
data.push(revision);
|
||||||
|
data.push(0); // checksum
|
||||||
|
data.extend_from_slice(&oem_id);
|
||||||
|
data.extend_from_slice(&oem_table);
|
||||||
|
data.extend_from_slice(&oem_revision.to_le_bytes());
|
||||||
|
data.extend_from_slice(b"CLDH");
|
||||||
|
data.extend_from_slice(&0u32.to_le_bytes());
|
||||||
|
assert_eq!(data.len(), 36);
|
||||||
|
|
||||||
|
data.resize(length as usize, 0);
|
||||||
|
let mut sdt = SDT { data };
|
||||||
|
|
||||||
|
sdt.update_checksum();
|
||||||
|
sdt
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_checksum(&mut self) {
|
||||||
|
self.data[9] = 0;
|
||||||
|
let checksum = super::generate_checksum(self.data.as_slice());
|
||||||
|
self.data[9] = checksum
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_slice(&self) -> &[u8] {
|
||||||
|
&self.data.as_slice()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn append<T>(&mut self, value: T) {
|
||||||
|
let orig_length = self.data.len();
|
||||||
|
let new_length = orig_length + std::mem::size_of::<T>();
|
||||||
|
self.data.resize(new_length, 0);
|
||||||
|
self.write_u32(4, new_length as u32);
|
||||||
|
self.write(orig_length, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn append_slice(&mut self, data: &[u8]) {
|
||||||
|
let orig_length = self.data.len();
|
||||||
|
let new_length = orig_length + data.len();
|
||||||
|
self.write_u32(4, new_length as u32);
|
||||||
|
self.data.extend_from_slice(data);
|
||||||
|
self.update_checksum();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write a value at the given offset
|
||||||
|
pub fn write<T>(&mut self, offset: usize, value: T) {
|
||||||
|
assert!((offset + (std::mem::size_of::<T>() - 1)) < self.data.len());
|
||||||
|
unsafe {
|
||||||
|
*(((self.data.as_mut_ptr() as usize) + offset) as *mut T) = value;
|
||||||
|
}
|
||||||
|
self.update_checksum();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_u8(&mut self, offset: usize, val: u8) {
|
||||||
|
self.write(offset, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_u16(&mut self, offset: usize, val: u16) {
|
||||||
|
self.write(offset, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_u32(&mut self, offset: usize, val: u32) {
|
||||||
|
self.write(offset, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_u64(&mut self, offset: usize, val: u64) {
|
||||||
|
self.write(offset, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.data.len()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::SDT;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_sdt() {
|
||||||
|
let mut sdt = SDT::new(*b"TEST", 40, 1, *b"CLOUDH", *b"TESTTEST", 1);
|
||||||
|
let sum: u8 = sdt
|
||||||
|
.as_slice()
|
||||||
|
.iter()
|
||||||
|
.fold(0u8, |acc, x| acc.wrapping_add(*x));
|
||||||
|
assert_eq!(sum, 0);
|
||||||
|
sdt.write_u32(36, 0x12345678);
|
||||||
|
let sum: u8 = sdt
|
||||||
|
.as_slice()
|
||||||
|
.iter()
|
||||||
|
.fold(0u8, |acc, x| acc.wrapping_add(*x));
|
||||||
|
assert_eq!(sum, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
[package]
|
|
||||||
authors = ["The Cloud Hypervisor Authors"]
|
|
||||||
edition.workspace = true
|
|
||||||
license = "Apache-2.0"
|
|
||||||
name = "api_client"
|
|
||||||
rust-version.workspace = true
|
|
||||||
version = "0.1.0"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
thiserror = { workspace = true }
|
|
||||||
vmm-sys-util = { workspace = true }
|
|
||||||
|
|
||||||
[lints]
|
|
||||||
workspace = true
|
|
||||||
@@ -1,239 +0,0 @@
|
|||||||
// Copyright © 2020 Intel Corporation
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
//
|
|
||||||
|
|
||||||
use std::io::{self, Read, Write};
|
|
||||||
use std::os::unix::io::RawFd;
|
|
||||||
use std::{num, str};
|
|
||||||
|
|
||||||
use thiserror::Error;
|
|
||||||
use vmm_sys_util::errno;
|
|
||||||
use vmm_sys_util::sock_ctrl_msg::ScmSocket;
|
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
|
||||||
pub enum Error {
|
|
||||||
#[error("Error writing to or reading from HTTP socket")]
|
|
||||||
Socket(#[source] io::Error),
|
|
||||||
#[error("Error sending file descriptors")]
|
|
||||||
SocketSendFds(#[source] errno::Error),
|
|
||||||
#[error("Error parsing HTTP status code")]
|
|
||||||
StatusCodeParsing(#[source] num::ParseIntError),
|
|
||||||
#[error("HTTP output is missing protocol statement")]
|
|
||||||
MissingProtocol,
|
|
||||||
#[error("Error parsing HTTP Content-Length field")]
|
|
||||||
ContentLengthParsing(#[source] num::ParseIntError),
|
|
||||||
#[error("Server responded with error {0:?}: {1:?}")]
|
|
||||||
ServerResponse(
|
|
||||||
StatusCode,
|
|
||||||
// TODO: Move `api` module from `vmm` to dedicated crate and use a common type definition
|
|
||||||
Option<
|
|
||||||
String, /* Untyped: Currently Vec<String> of error messages from top to root cause */
|
|
||||||
>,
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
|
||||||
pub enum StatusCode {
|
|
||||||
Continue,
|
|
||||||
Ok,
|
|
||||||
NoContent,
|
|
||||||
BadRequest,
|
|
||||||
NotFound,
|
|
||||||
TooManyRequests,
|
|
||||||
InternalServerError,
|
|
||||||
NotImplemented,
|
|
||||||
Unknown,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StatusCode {
|
|
||||||
fn from_raw(code: usize) -> StatusCode {
|
|
||||||
match code {
|
|
||||||
100 => StatusCode::Continue,
|
|
||||||
200 => StatusCode::Ok,
|
|
||||||
204 => StatusCode::NoContent,
|
|
||||||
400 => StatusCode::BadRequest,
|
|
||||||
404 => StatusCode::NotFound,
|
|
||||||
429 => StatusCode::TooManyRequests,
|
|
||||||
500 => StatusCode::InternalServerError,
|
|
||||||
501 => StatusCode::NotImplemented,
|
|
||||||
_ => StatusCode::Unknown,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse(code: &str) -> Result<StatusCode, Error> {
|
|
||||||
Ok(StatusCode::from_raw(
|
|
||||||
code.trim().parse().map_err(Error::StatusCodeParsing)?,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_server_error(self) -> bool {
|
|
||||||
!matches!(
|
|
||||||
self,
|
|
||||||
StatusCode::Ok | StatusCode::Continue | StatusCode::NoContent
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_header<'a>(res: &'a str, header: &'a str) -> Option<&'a str> {
|
|
||||||
let header_str = format!("{header}: ");
|
|
||||||
res.find(&header_str)
|
|
||||||
.map(|o| &res[o + header_str.len()..o + res[o..].find('\r').unwrap()])
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_status_code(res: &str) -> Result<StatusCode, Error> {
|
|
||||||
if let Some(o) = res.find("HTTP/1.1") {
|
|
||||||
Ok(StatusCode::parse(
|
|
||||||
&res[o + "HTTP/1.1 ".len()..res[o..].find('\r').unwrap()],
|
|
||||||
)?)
|
|
||||||
} else {
|
|
||||||
Err(Error::MissingProtocol)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_http_response(socket: &mut dyn Read) -> Result<Option<String>, Error> {
|
|
||||||
let mut res = String::new();
|
|
||||||
let mut body_offset = None;
|
|
||||||
let mut content_length: Option<usize> = None;
|
|
||||||
loop {
|
|
||||||
let mut bytes = vec![0; 256];
|
|
||||||
let count = socket.read(&mut bytes).map_err(Error::Socket)?;
|
|
||||||
// If the return value is 0, the peer has performed an orderly shutdown.
|
|
||||||
if count == 0 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
res.push_str(str::from_utf8(&bytes[0..count]).unwrap());
|
|
||||||
|
|
||||||
// End of headers
|
|
||||||
if let Some(o) = res.find("\r\n\r\n") {
|
|
||||||
body_offset = Some(o + "\r\n\r\n".len());
|
|
||||||
|
|
||||||
// With all headers available we can see if there is any body
|
|
||||||
content_length = if let Some(length) = get_header(&res, "Content-Length") {
|
|
||||||
Some(length.trim().parse().map_err(Error::ContentLengthParsing)?)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
if content_length.is_none() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(body_offset) = body_offset
|
|
||||||
&& let Some(content_length) = content_length
|
|
||||||
&& res.len() >= content_length + body_offset
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let body_string = content_length.and(body_offset.map(|o| String::from(&res[o..])));
|
|
||||||
let status_code = get_status_code(&res)?;
|
|
||||||
|
|
||||||
if status_code.is_server_error() {
|
|
||||||
Err(Error::ServerResponse(status_code, body_string))
|
|
||||||
} else {
|
|
||||||
Ok(body_string)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Make an API request using the fully qualified command name.
|
|
||||||
/// For example, full_command could be "vm.create" or "vmm.ping".
|
|
||||||
pub fn simple_api_full_command_with_fds_and_response<T: Read + Write + ScmSocket>(
|
|
||||||
socket: &mut T,
|
|
||||||
method: &str,
|
|
||||||
full_command: &str,
|
|
||||||
request_body: Option<&str>,
|
|
||||||
request_fds: &[RawFd],
|
|
||||||
) -> Result<Option<String>, Error> {
|
|
||||||
socket
|
|
||||||
.send_with_fds(
|
|
||||||
&[format!(
|
|
||||||
"{method} /api/v1/{full_command} HTTP/1.1\r\nHost: localhost\r\nAccept: */*\r\n"
|
|
||||||
)
|
|
||||||
.as_bytes()],
|
|
||||||
request_fds,
|
|
||||||
)
|
|
||||||
.map_err(Error::SocketSendFds)?;
|
|
||||||
|
|
||||||
if let Some(request_body) = request_body {
|
|
||||||
socket
|
|
||||||
.write_all(format!("Content-Length: {}\r\n", request_body.len()).as_bytes())
|
|
||||||
.map_err(Error::Socket)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
socket.write_all(b"\r\n").map_err(Error::Socket)?;
|
|
||||||
|
|
||||||
if let Some(request_body) = request_body {
|
|
||||||
socket
|
|
||||||
.write_all(request_body.as_bytes())
|
|
||||||
.map_err(Error::Socket)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
socket.flush().map_err(Error::Socket)?;
|
|
||||||
|
|
||||||
parse_http_response(socket)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn simple_api_full_command_with_fds<T: Read + Write + ScmSocket>(
|
|
||||||
socket: &mut T,
|
|
||||||
method: &str,
|
|
||||||
full_command: &str,
|
|
||||||
request_body: Option<&str>,
|
|
||||||
request_fds: &[RawFd],
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
let response = simple_api_full_command_with_fds_and_response(
|
|
||||||
socket,
|
|
||||||
method,
|
|
||||||
full_command,
|
|
||||||
request_body,
|
|
||||||
request_fds,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
if let Some(response) = response {
|
|
||||||
println!("{response}");
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn simple_api_full_command<T: Read + Write + ScmSocket>(
|
|
||||||
socket: &mut T,
|
|
||||||
method: &str,
|
|
||||||
full_command: &str,
|
|
||||||
request_body: Option<&str>,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
simple_api_full_command_with_fds(socket, method, full_command, request_body, &[])
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn simple_api_full_command_and_response<T: Read + Write + ScmSocket>(
|
|
||||||
socket: &mut T,
|
|
||||||
method: &str,
|
|
||||||
full_command: &str,
|
|
||||||
request_body: Option<&str>,
|
|
||||||
) -> Result<Option<String>, Error> {
|
|
||||||
simple_api_full_command_with_fds_and_response(socket, method, full_command, request_body, &[])
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn simple_api_command_with_fds<T: Read + Write + ScmSocket>(
|
|
||||||
socket: &mut T,
|
|
||||||
method: &str,
|
|
||||||
c: &str,
|
|
||||||
request_body: Option<&str>,
|
|
||||||
request_fds: &[RawFd],
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
// Create the full VM command. For VMM commands, use
|
|
||||||
// simple_api_full_command().
|
|
||||||
let full_command = format!("vm.{c}");
|
|
||||||
|
|
||||||
simple_api_full_command_with_fds(socket, method, &full_command, request_body, request_fds)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn simple_api_command<T: Read + Write + ScmSocket>(
|
|
||||||
socket: &mut T,
|
|
||||||
method: &str,
|
|
||||||
c: &str,
|
|
||||||
request_body: Option<&str>,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
simple_api_command_with_fds(socket, method, c, request_body, &[])
|
|
||||||
}
|
|
||||||
@@ -1,37 +1,27 @@
|
|||||||
[package]
|
[package]
|
||||||
authors = ["The Chromium OS Authors"]
|
|
||||||
edition.workspace = true
|
|
||||||
name = "arch"
|
name = "arch"
|
||||||
rust-version.workspace = true
|
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
authors = ["The Chromium OS Authors"]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
fw_cfg = []
|
|
||||||
kvm = ["hypervisor/kvm"]
|
|
||||||
sev_snp = []
|
|
||||||
tdx = []
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = { workspace = true }
|
byteorder = "1.3.4"
|
||||||
byteorder = { workspace = true }
|
kvm-bindings = "0.2.0"
|
||||||
hypervisor = { path = "../hypervisor" }
|
kvm-ioctls = "0.5.0"
|
||||||
libc = { workspace = true }
|
libc = "0.2.66"
|
||||||
linux-loader = { workspace = true, features = ["bzimage", "elf", "pe"] }
|
|
||||||
log = { workspace = true }
|
acpi_tables = { path = "../acpi_tables", optional = true }
|
||||||
serde = { workspace = true, features = ["derive", "rc"] }
|
arch_gen = { path = "../arch_gen" }
|
||||||
thiserror = { workspace = true }
|
|
||||||
uuid = { workspace = true }
|
[dependencies.vm-memory]
|
||||||
vm-memory = { workspace = true, features = ["backend-bitmap", "backend-mmap"] }
|
git = "https://github.com/rust-vmm/vm-memory"
|
||||||
vmm-sys-util = { workspace = true, features = ["with-serde"] }
|
features = ["backend-mmap"]
|
||||||
|
|
||||||
|
[dependencies.linux-loader]
|
||||||
|
git = "https://github.com/rust-vmm/linux-loader"
|
||||||
|
features = ["elf", "bzimage"]
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
proptest = "1.0.0"
|
rand = "0.7.3"
|
||||||
serde_json = { workspace = true }
|
|
||||||
|
|
||||||
[target.'cfg(any(target_arch = "aarch64", target_arch = "riscv64"))'.dependencies]
|
|
||||||
fdt_parser = { version = "0.1.5", package = "fdt" }
|
|
||||||
vm-fdt = { workspace = true }
|
|
||||||
|
|
||||||
[lints]
|
|
||||||
workspace = true
|
|
||||||
|
|||||||
@@ -1,188 +0,0 @@
|
|||||||
// 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::fs;
|
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
use log::warn;
|
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
|
||||||
pub enum CacheLevel {
|
|
||||||
/// L1 data cache
|
|
||||||
L1D = 0,
|
|
||||||
/// L1 instruction cache
|
|
||||||
L1I = 1,
|
|
||||||
/// L2 cache
|
|
||||||
L2 = 2,
|
|
||||||
/// L3 cache
|
|
||||||
L3 = 3,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// NOTE: cache size file directory example,
|
|
||||||
/// "/sys/devices/system/cpu/cpu0/cache/index0/size".
|
|
||||||
pub fn get_cache_size(cache_level: CacheLevel) -> u32 {
|
|
||||||
let mut file_directory: String = "/sys/devices/system/cpu/cpu0/cache".to_string();
|
|
||||||
match cache_level {
|
|
||||||
CacheLevel::L1D => file_directory += "/index0/size",
|
|
||||||
CacheLevel::L1I => file_directory += "/index1/size",
|
|
||||||
CacheLevel::L2 => file_directory += "/index2/size",
|
|
||||||
CacheLevel::L3 => file_directory += "/index3/size",
|
|
||||||
}
|
|
||||||
|
|
||||||
let file_path = Path::new(&file_directory);
|
|
||||||
if file_path.exists() {
|
|
||||||
let src = fs::read_to_string(file_directory).expect("File not exists or file corrupted.");
|
|
||||||
// The content of the file is as simple as a size, like: "32K"
|
|
||||||
let src = src.trim();
|
|
||||||
let src_digits: u32 = src[0..src.len() - 1].parse().unwrap();
|
|
||||||
let src_unit = &src[src.len() - 1..];
|
|
||||||
|
|
||||||
src_digits
|
|
||||||
* match src_unit {
|
|
||||||
"K" => 1u32 << 10,
|
|
||||||
"M" => 1u32 << 20,
|
|
||||||
"G" => 1u32 << 30,
|
|
||||||
_ => 1,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// NOTE: coherency_line_size file directory example,
|
|
||||||
/// "/sys/devices/system/cpu/cpu0/cache/index0/coherency_line_size".
|
|
||||||
pub fn get_cache_coherency_line_size(cache_level: CacheLevel) -> u32 {
|
|
||||||
let mut file_directory: String = "/sys/devices/system/cpu/cpu0/cache".to_string();
|
|
||||||
match cache_level {
|
|
||||||
CacheLevel::L1D => file_directory += "/index0/coherency_line_size",
|
|
||||||
CacheLevel::L1I => file_directory += "/index1/coherency_line_size",
|
|
||||||
CacheLevel::L2 => file_directory += "/index2/coherency_line_size",
|
|
||||||
CacheLevel::L3 => file_directory += "/index3/coherency_line_size",
|
|
||||||
}
|
|
||||||
|
|
||||||
let file_path = Path::new(&file_directory);
|
|
||||||
if file_path.exists() {
|
|
||||||
let src = fs::read_to_string(file_directory).expect("File not exists or file corrupted.");
|
|
||||||
src.trim().parse::<u32>().unwrap()
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// NOTE: number_of_sets file directory example,
|
|
||||||
/// "/sys/devices/system/cpu/cpu0/cache/index0/number_of_sets".
|
|
||||||
pub fn get_cache_number_of_sets(cache_level: CacheLevel) -> u32 {
|
|
||||||
let mut file_directory: String = "/sys/devices/system/cpu/cpu0/cache".to_string();
|
|
||||||
match cache_level {
|
|
||||||
CacheLevel::L1D => file_directory += "/index0/number_of_sets",
|
|
||||||
CacheLevel::L1I => file_directory += "/index1/number_of_sets",
|
|
||||||
CacheLevel::L2 => file_directory += "/index2/number_of_sets",
|
|
||||||
CacheLevel::L3 => file_directory += "/index3/number_of_sets",
|
|
||||||
}
|
|
||||||
|
|
||||||
let file_path = Path::new(&file_directory);
|
|
||||||
if file_path.exists() {
|
|
||||||
let src = fs::read_to_string(file_directory).expect("File not exists or file corrupted.");
|
|
||||||
src.trim().parse::<u32>().unwrap()
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// NOTE: shared_cpu_list file directory example,
|
|
||||||
/// "/sys/devices/system/cpu/cpu0/cache/index0/shared_cpu_list".
|
|
||||||
pub fn get_cache_shared(cache_level: CacheLevel) -> bool {
|
|
||||||
let mut file_directory: String = "/sys/devices/system/cpu/cpu0/cache".to_string();
|
|
||||||
let mut result = true;
|
|
||||||
|
|
||||||
match cache_level {
|
|
||||||
CacheLevel::L1D | CacheLevel::L1I => result = false,
|
|
||||||
CacheLevel::L2 => file_directory += "/index2/shared_cpu_list",
|
|
||||||
CacheLevel::L3 => file_directory += "/index3/shared_cpu_list",
|
|
||||||
}
|
|
||||||
|
|
||||||
if !result {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let file_path = Path::new(&file_directory);
|
|
||||||
if file_path.exists() {
|
|
||||||
let src = fs::read_to_string(file_directory).expect("File not exists or file corrupted.");
|
|
||||||
let src = src.trim();
|
|
||||||
if src.is_empty() {
|
|
||||||
result = false;
|
|
||||||
} else {
|
|
||||||
result = src.contains('-') || src.contains(',');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
result = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default, Copy, Clone, Debug)]
|
|
||||||
pub struct CacheTopologyInfo {
|
|
||||||
pub l1_d_cache_size: u32,
|
|
||||||
pub l1_d_cache_line_size: u32,
|
|
||||||
pub l1_d_cache_sets: u32,
|
|
||||||
|
|
||||||
pub l1_i_cache_size: u32,
|
|
||||||
pub l1_i_cache_line_size: u32,
|
|
||||||
pub l1_i_cache_sets: u32,
|
|
||||||
|
|
||||||
pub l2_cache_size: u32,
|
|
||||||
pub l2_cache_line_size: u32,
|
|
||||||
pub l2_cache_sets: u32,
|
|
||||||
|
|
||||||
pub l3_cache_size: u32,
|
|
||||||
pub l3_cache_line_size: u32,
|
|
||||||
pub l3_cache_sets: u32,
|
|
||||||
|
|
||||||
pub l2_cache_shared: bool,
|
|
||||||
pub l3_cache_shared: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reads cache topology information from sysfs for cpu0.
|
|
||||||
pub fn read_cache_topology() -> Option<CacheTopologyInfo> {
|
|
||||||
let cache_path = Path::new("/sys/devices/system/cpu/cpu0/cache");
|
|
||||||
if !cache_path.exists() {
|
|
||||||
warn!("Cache topology information is not available in sysfs.");
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut info = CacheTopologyInfo {
|
|
||||||
l1_d_cache_size: get_cache_size(CacheLevel::L1D),
|
|
||||||
l1_d_cache_line_size: get_cache_coherency_line_size(CacheLevel::L1D),
|
|
||||||
l1_d_cache_sets: get_cache_number_of_sets(CacheLevel::L1D),
|
|
||||||
|
|
||||||
l1_i_cache_size: get_cache_size(CacheLevel::L1I),
|
|
||||||
l1_i_cache_line_size: get_cache_coherency_line_size(CacheLevel::L1I),
|
|
||||||
l1_i_cache_sets: get_cache_number_of_sets(CacheLevel::L1I),
|
|
||||||
|
|
||||||
l2_cache_size: get_cache_size(CacheLevel::L2),
|
|
||||||
l2_cache_line_size: get_cache_coherency_line_size(CacheLevel::L2),
|
|
||||||
l2_cache_sets: get_cache_number_of_sets(CacheLevel::L2),
|
|
||||||
|
|
||||||
l3_cache_size: get_cache_size(CacheLevel::L3),
|
|
||||||
l3_cache_line_size: get_cache_coherency_line_size(CacheLevel::L3),
|
|
||||||
l3_cache_sets: get_cache_number_of_sets(CacheLevel::L3),
|
|
||||||
|
|
||||||
l2_cache_shared: false,
|
|
||||||
l3_cache_shared: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
if info.l2_cache_size != 0 {
|
|
||||||
info.l2_cache_shared = get_cache_shared(CacheLevel::L2);
|
|
||||||
}
|
|
||||||
if info.l3_cache_size != 0 {
|
|
||||||
info.l3_cache_shared = get_cache_shared(CacheLevel::L3);
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(info)
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,149 +1,7 @@
|
|||||||
// Copyright 2020 Arm Limited (or its affiliates). All rights reserved.
|
|
||||||
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
//
|
/// Kernel command line start address.
|
||||||
// Memory layout of AArch64 guest:
|
pub const CMDLINE_START: usize = 0x0;
|
||||||
//
|
/// Kernel command line start address maximum size.
|
||||||
// Physical +---------------------------------------------------------------+
|
pub const CMDLINE_MAX_SIZE: usize = 0x0;
|
||||||
// address | |
|
|
||||||
// end | |
|
|
||||||
// ~ ~ ~ ~
|
|
||||||
// | |
|
|
||||||
// | Highmem PCI MMIO space |
|
|
||||||
// | |
|
|
||||||
// RAM end +---------------------------------------------------------------+
|
|
||||||
// (dynamic, | |
|
|
||||||
// including | |
|
|
||||||
// hotplug ~ ~ ~ ~
|
|
||||||
// memory) | |
|
|
||||||
// | DRAM |
|
|
||||||
// | |
|
|
||||||
// | |
|
|
||||||
// 4GB +---------------------------------------------------------------+
|
|
||||||
// | 32-bit devices hole |
|
|
||||||
// 4GB-64M +---------------------------------------------------------------+
|
|
||||||
// | |
|
|
||||||
// | |
|
|
||||||
// | DRAM |
|
|
||||||
// | |
|
|
||||||
// | |
|
|
||||||
// 1GB +---------------------------------------------------------------+
|
|
||||||
// | |
|
|
||||||
// | PCI MMCONFIG space |
|
|
||||||
// | |
|
|
||||||
// 768 M +---------------------------------------------------------------+
|
|
||||||
// | |
|
|
||||||
// | |
|
|
||||||
// | PCI MMIO space |
|
|
||||||
// | |
|
|
||||||
// 256 M +---------------------------------------------------------------|
|
|
||||||
// | |
|
|
||||||
// | Legacy devices space |
|
|
||||||
// | |
|
|
||||||
// 144 M +---------------------------------------------------------------|
|
|
||||||
// | |
|
|
||||||
// | Reserved (now GIC is here) |
|
|
||||||
// | |
|
|
||||||
// 4 M +---------------------------------------------------------------+
|
|
||||||
// | UEFI flash |
|
|
||||||
// 0GB +---------------------------------------------------------------+
|
|
||||||
//
|
|
||||||
//
|
|
||||||
|
|
||||||
use vm_memory::GuestAddress;
|
|
||||||
|
|
||||||
/// 0x0 ~ 0x40_0000 (4 MiB) is reserved to UEFI
|
|
||||||
/// UEFI binary size is required less than 3 MiB, reserving 4 MiB is enough.
|
|
||||||
pub const UEFI_START: GuestAddress = GuestAddress(0);
|
|
||||||
pub const UEFI_SIZE: u64 = 0x040_0000;
|
|
||||||
|
|
||||||
/// Below this address will reside the GIC, above this address will reside the MMIO devices.
|
|
||||||
const MAPPED_IO_START: GuestAddress = GuestAddress(0x0900_0000);
|
|
||||||
|
|
||||||
/// See kernel file arch/arm64/include/uapi/asm/kvm.h for the GIC related definitions.
|
|
||||||
/// 0x08ff_0000 ~ 0x0900_0000 is reserved for GICv3 Distributor
|
|
||||||
pub const GIC_V3_DIST_SIZE: u64 = 0x01_0000;
|
|
||||||
pub const GIC_V3_DIST_START: GuestAddress = GuestAddress(MAPPED_IO_START.0 - GIC_V3_DIST_SIZE);
|
|
||||||
/// Below 0x08ff_0000 is reserved for GICv3 Redistributor.
|
|
||||||
/// The size defined here is for each vcpu.
|
|
||||||
/// The total size is 'number_of_vcpu * GIC_V3_REDIST_SIZE'
|
|
||||||
pub const GIC_V3_REDIST_SIZE: u64 = 0x02_0000;
|
|
||||||
/// Below Redistributor area is GICv3 ITS
|
|
||||||
pub const GIC_V3_ITS_SIZE: u64 = 0x02_0000;
|
|
||||||
|
|
||||||
/// Space 0x0900_0000 ~ 0x0905_0000 is reserved for legacy devices.
|
|
||||||
pub const LEGACY_SERIAL_MAPPED_IO_START: GuestAddress = MAPPED_IO_START;
|
|
||||||
pub const LEGACY_RTC_MAPPED_IO_START: GuestAddress = GuestAddress(0x0901_0000);
|
|
||||||
pub const LEGACY_GPIO_MAPPED_IO_START: GuestAddress = GuestAddress(0x0902_0000);
|
|
||||||
|
|
||||||
/// Space 0x0905_0000 ~ 0x0906_0000 is reserved for pcie io address
|
|
||||||
pub const MEM_PCI_IO_START: GuestAddress = GuestAddress(0x0905_0000);
|
|
||||||
pub const MEM_PCI_IO_SIZE: u64 = 0x10000;
|
|
||||||
|
|
||||||
/// Starting from 0x1000_0000 (256MiB) to 0x3000_0000 (768MiB) is used for PCIE MMIO
|
|
||||||
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 1 GiB, 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);
|
|
||||||
|
|
||||||
/// 32-bit reserved area: 64MiB before 4GiB
|
|
||||||
pub const MEM_32BIT_RESERVED_START: GuestAddress = GuestAddress(0xfc00_0000);
|
|
||||||
pub const MEM_32BIT_RESERVED_SIZE: u64 = 0x0400_0000;
|
|
||||||
|
|
||||||
/// TPM Address Range
|
|
||||||
/// This Address range is specific to CRB Interface
|
|
||||||
pub const TPM_START: GuestAddress = GuestAddress(0xfed4_0000);
|
|
||||||
pub const TPM_SIZE: u64 = 0x1000;
|
|
||||||
|
|
||||||
/// Start of 64-bit RAM.
|
|
||||||
pub const RAM_64BIT_START: GuestAddress = GuestAddress(0x1_0000_0000);
|
|
||||||
|
|
||||||
/// Kernel command line maximum size.
|
|
||||||
/// As per `arch/arm64/include/uapi/asm/setup.h`.
|
|
||||||
pub const CMDLINE_MAX_SIZE: usize = 2048;
|
|
||||||
|
|
||||||
/// FDT is at the beginning of RAM.
|
|
||||||
pub const FDT_START: GuestAddress = RAM_START;
|
|
||||||
/// Maximum size of the device tree blob as specified in [the kernel
|
|
||||||
/// documentation](https://www.kernel.org/doc/Documentation/arm64/booting.txt).
|
|
||||||
pub const FDT_MAX_SIZE: u64 = 0x20_0000;
|
|
||||||
|
|
||||||
/// Put ACPI table above dtb
|
|
||||||
pub const ACPI_START: GuestAddress = GuestAddress(RAM_START.0 + FDT_MAX_SIZE);
|
|
||||||
pub const ACPI_MAX_SIZE: u64 = 0x20_0000;
|
|
||||||
pub const RSDP_POINTER: GuestAddress = ACPI_START;
|
|
||||||
|
|
||||||
/// Kernel start after FDT and ACPI
|
|
||||||
pub const KERNEL_START: GuestAddress = GuestAddress(ACPI_START.0 + ACPI_MAX_SIZE);
|
|
||||||
|
|
||||||
/// Pci high memory base
|
|
||||||
pub const PCI_HIGH_BASE: GuestAddress = GuestAddress(0x2_0000_0000);
|
|
||||||
|
|
||||||
// As per virt/kvm/arm/vgic/vgic-kvm-device.c we need
|
|
||||||
// the number of interrupts our GIC will support to be:
|
|
||||||
// * bigger than 32
|
|
||||||
// * less than 1023 and
|
|
||||||
// * a multiple of 32.
|
|
||||||
// We are setting up our interrupt controller to support a maximum of 256 interrupts.
|
|
||||||
/// First usable interrupt on aarch64
|
|
||||||
pub const IRQ_BASE: u32 = 32;
|
|
||||||
|
|
||||||
/// Number of supported interrupts
|
|
||||||
pub const IRQ_NUM: u32 = 256;
|
|
||||||
|
|
||||||
/// Base SPI interrupt number for the GICv2M MSI frame
|
|
||||||
pub const GICV2M_SPI_BASE: u32 = 128;
|
|
||||||
|
|
||||||
/// Total number of SPIs for the GICv2M MSI frame
|
|
||||||
pub const GICV2M_SPI_NUM: u32 = 64;
|
|
||||||
|
|
||||||
/// GICv2M compatible string
|
|
||||||
pub const GIC_V2M_COMPATIBLE: &str = "arm,gic-v2m-frame";
|
|
||||||
|
|||||||
@@ -1,210 +1,27 @@
|
|||||||
// Copyright 2020 Arm Limited (or its affiliates). All rights reserved.
|
|
||||||
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
/// Module for cache info.
|
|
||||||
pub mod cache;
|
|
||||||
/// Module for the flattened device tree.
|
|
||||||
pub mod fdt;
|
|
||||||
/// Layout for this aarch64 system.
|
|
||||||
pub mod layout;
|
pub mod layout;
|
||||||
/// Module for loading UEFI binary.
|
|
||||||
pub mod uefi;
|
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use memory_model::{GuestAddress, GuestMemory};
|
||||||
use std::fmt::Debug;
|
|
||||||
use std::hash::BuildHasher;
|
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
|
|
||||||
use hypervisor::arch::aarch64::gic::Vgic;
|
/// Stub function that needs to be implemented when aarch64 functionality is added.
|
||||||
use hypervisor::arch::aarch64::regs::MPIDR_EL1;
|
pub fn arch_memory_regions(size: usize) -> Vec<(GuestAddress, usize, RegionType)> {
|
||||||
use log::{Level, log_enabled};
|
vec![(GuestAddress(0), size, RegionType::Ram)]
|
||||||
use thiserror::Error;
|
|
||||||
use vm_memory::{Address, GuestAddress, GuestMemoryAtomic, GuestMemoryBackend};
|
|
||||||
|
|
||||||
pub use self::fdt::DeviceInfoForFdt;
|
|
||||||
use crate::{DeviceType, GuestMemoryMmap, NumaNodes, PciSpaceInfo, RegionType};
|
|
||||||
|
|
||||||
pub const _NSIG: i32 = 65;
|
|
||||||
|
|
||||||
/// Errors thrown while configuring aarch64 system.
|
|
||||||
#[derive(Debug, Error)]
|
|
||||||
pub enum Error {
|
|
||||||
/// Failed to create a FDT.
|
|
||||||
#[error("Failed to create a FDT")]
|
|
||||||
SetupFdt,
|
|
||||||
|
|
||||||
/// Failed to write FDT to memory.
|
|
||||||
#[error("Failed to write FDT to memory")]
|
|
||||||
WriteFdtToMemory(#[source] fdt::Error),
|
|
||||||
|
|
||||||
/// Failed to create a GIC.
|
|
||||||
#[error("Failed to create a GIC")]
|
|
||||||
SetupGic,
|
|
||||||
|
|
||||||
/// Failed to compute the initramfs address.
|
|
||||||
#[error("Failed to compute the initramfs address")]
|
|
||||||
InitramfsAddress,
|
|
||||||
|
|
||||||
/// Error configuring the general purpose registers
|
|
||||||
#[error("Error configuring the general purpose registers")]
|
|
||||||
RegsConfiguration(#[source] hypervisor::HypervisorCpuError),
|
|
||||||
|
|
||||||
/// Error configuring the MPIDR register
|
|
||||||
#[error("Error configuring the MPIDR register")]
|
|
||||||
VcpuRegMpidr(#[source] hypervisor::HypervisorCpuError),
|
|
||||||
|
|
||||||
/// Error initializing PMU for vcpu
|
|
||||||
#[error("Error initializing PMU for vcpu")]
|
|
||||||
VcpuInitPmu,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
/// Stub function that needs to be implemented when aarch64 functionality is added.
|
||||||
/// Specifies the entry point address where the guest must start
|
pub fn configure_system(
|
||||||
/// executing code.
|
_guest_mem: &GuestMemory,
|
||||||
pub struct EntryPoint {
|
_cmdline_addr: GuestAddress,
|
||||||
/// Address in guest memory where the guest must start execution
|
_cmdline_size: usize,
|
||||||
pub entry_addr: GuestAddress,
|
_num_cpus: u8,
|
||||||
}
|
_rsdp_addr: Option<GuestAddress>,
|
||||||
|
|
||||||
/// Configure the specified VCPU, and return its MPIDR.
|
|
||||||
pub fn configure_vcpu(
|
|
||||||
vcpu: &dyn hypervisor::Vcpu,
|
|
||||||
id: u32,
|
|
||||||
boot_setup: Option<(EntryPoint, &GuestMemoryAtomic<GuestMemoryMmap>)>,
|
|
||||||
) -> super::Result<u64> {
|
|
||||||
if let Some((kernel_entry_point, _guest_memory)) = boot_setup {
|
|
||||||
vcpu.setup_regs(
|
|
||||||
id,
|
|
||||||
kernel_entry_point.entry_addr.raw_value(),
|
|
||||||
super::layout::FDT_START.raw_value(),
|
|
||||||
)
|
|
||||||
.map_err(Error::RegsConfiguration)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mpidr = vcpu.get_sys_reg(MPIDR_EL1).map_err(Error::VcpuRegMpidr)?;
|
|
||||||
Ok(mpidr)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn arch_memory_regions() -> Vec<(GuestAddress, usize, RegionType)> {
|
|
||||||
vec![
|
|
||||||
// 0 MiB ~ 256 MiB: UEFI, GIC 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 ~ 4032 MiB: RAM before the gap
|
|
||||||
(
|
|
||||||
layout::RAM_START,
|
|
||||||
layout::MEM_32BIT_RESERVED_START.unchecked_offset_from(layout::RAM_START) as usize,
|
|
||||||
RegionType::Ram,
|
|
||||||
),
|
|
||||||
// 4GiB ~ inf: RAM after the gap
|
|
||||||
(layout::RAM_64BIT_START, usize::MAX, RegionType::Ram),
|
|
||||||
// Add the 32-bit reserved memory hole as a reserved region
|
|
||||||
(
|
|
||||||
layout::MEM_32BIT_RESERVED_START,
|
|
||||||
layout::MEM_32BIT_RESERVED_SIZE as usize,
|
|
||||||
RegionType::Reserved,
|
|
||||||
),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Configures the system and should be called once per vm before starting vcpu threads.
|
|
||||||
#[expect(clippy::too_many_arguments)]
|
|
||||||
pub fn configure_system<T: DeviceInfoForFdt + Clone + Debug, S: BuildHasher>(
|
|
||||||
guest_mem: &GuestMemoryMmap,
|
|
||||||
cmdline: &str,
|
|
||||||
vcpu_mpidr: &[u64],
|
|
||||||
vcpu_topology: Option<(u16, u16, u16, u16)>,
|
|
||||||
device_info: &HashMap<(DeviceType, String), T, S>,
|
|
||||||
initrd: &Option<super::InitramfsConfig>,
|
|
||||||
pci_space_info: &[PciSpaceInfo],
|
|
||||||
virtio_iommu_bdf: Option<u32>,
|
|
||||||
gic_device: &Arc<Mutex<dyn Vgic>>,
|
|
||||||
numa_nodes: &NumaNodes,
|
|
||||||
pmu_supported: bool,
|
|
||||||
) -> super::Result<()> {
|
) -> super::Result<()> {
|
||||||
let fdt_final = fdt::create_fdt(
|
|
||||||
guest_mem,
|
|
||||||
cmdline,
|
|
||||||
vcpu_mpidr,
|
|
||||||
vcpu_topology,
|
|
||||||
device_info,
|
|
||||||
gic_device,
|
|
||||||
initrd,
|
|
||||||
pci_space_info,
|
|
||||||
numa_nodes,
|
|
||||||
virtio_iommu_bdf,
|
|
||||||
pmu_supported,
|
|
||||||
)
|
|
||||||
.map_err(|_| Error::SetupFdt)?;
|
|
||||||
|
|
||||||
if log_enabled!(Level::Debug) {
|
|
||||||
fdt::print_fdt(&fdt_final);
|
|
||||||
}
|
|
||||||
|
|
||||||
fdt::write_fdt_to_memory(&fdt_final, guest_mem).map_err(Error::WriteFdtToMemory)?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the memory address where the initramfs could be loaded.
|
/// Stub function that needs to be implemented when aarch64 functionality is added.
|
||||||
pub fn initramfs_load_addr(
|
pub fn get_reserved_mem_addr() -> usize {
|
||||||
guest_mem: &GuestMemoryMmap,
|
0
|
||||||
initramfs_size: usize,
|
|
||||||
) -> super::Result<u64> {
|
|
||||||
let round_to_pagesize = |size| (size + (super::PAGE_SIZE - 1)) & !(super::PAGE_SIZE - 1);
|
|
||||||
match guest_mem
|
|
||||||
.last_addr()
|
|
||||||
.checked_sub(round_to_pagesize(initramfs_size) as u64 - 1)
|
|
||||||
{
|
|
||||||
Some(offset) => {
|
|
||||||
if guest_mem.address_in_range(offset) {
|
|
||||||
Ok(offset.raw_value())
|
|
||||||
} else {
|
|
||||||
Err(super::Error::PlatformSpecific(Error::InitramfsAddress))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => Err(super::Error::PlatformSpecific(Error::InitramfsAddress)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_host_cpu_phys_bits(hypervisor: &dyn hypervisor::Hypervisor) -> u8 {
|
|
||||||
let host_cpu_phys_bits = hypervisor.get_host_ipa_limit().try_into().unwrap();
|
|
||||||
if host_cpu_phys_bits == 0 {
|
|
||||||
// Host kernel does not support `get_host_ipa_limit`,
|
|
||||||
// we return the default value 40 here.
|
|
||||||
40
|
|
||||||
} else {
|
|
||||||
host_cpu_phys_bits
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod unit_tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_arch_memory_regions_dram() {
|
|
||||||
let regions = arch_memory_regions();
|
|
||||||
assert_eq!(6, regions.len());
|
|
||||||
assert_eq!(layout::RAM_START, regions[3].0);
|
|
||||||
assert_eq!(RegionType::Ram, regions[3].2);
|
|
||||||
assert_eq!(RegionType::Reserved, regions[5].2);
|
|
||||||
assert_eq!(RegionType::Ram, regions[4].2);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,50 +0,0 @@
|
|||||||
// Copyright 2020 Arm Limited (or its affiliates). All rights reserved.
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
use std::io::{Read, Seek, SeekFrom};
|
|
||||||
use std::os::fd::AsFd;
|
|
||||||
use std::result;
|
|
||||||
|
|
||||||
use thiserror::Error;
|
|
||||||
use vm_memory::{Bytes, GuestAddress, GuestMemory};
|
|
||||||
|
|
||||||
/// Errors thrown while loading UEFI binary
|
|
||||||
#[derive(Debug, Error)]
|
|
||||||
pub enum Error {
|
|
||||||
/// Unable to seek to UEFI image start.
|
|
||||||
#[error("Unable to seek to UEFI image start")]
|
|
||||||
SeekUefiStart,
|
|
||||||
/// Unable to seek to UEFI image end.
|
|
||||||
#[error("Unable to seek to UEFI image end")]
|
|
||||||
SeekUefiEnd,
|
|
||||||
/// UEFI image too big.
|
|
||||||
#[error("UEFI image too big")]
|
|
||||||
UefiTooBig,
|
|
||||||
/// Unable to read UEFI image
|
|
||||||
#[error("Unable to read UEFI image")]
|
|
||||||
ReadUefiImage,
|
|
||||||
}
|
|
||||||
type Result<T> = result::Result<T, Error>;
|
|
||||||
|
|
||||||
pub fn load_uefi<F, M: GuestMemory>(
|
|
||||||
guest_mem: &M,
|
|
||||||
guest_addr: GuestAddress,
|
|
||||||
uefi_image: &mut F,
|
|
||||||
) -> Result<()>
|
|
||||||
where
|
|
||||||
F: Read + Seek + AsFd,
|
|
||||||
{
|
|
||||||
let uefi_size = uefi_image
|
|
||||||
.seek(SeekFrom::End(0))
|
|
||||||
.map_err(|_| Error::SeekUefiEnd)? as usize;
|
|
||||||
|
|
||||||
// edk2 image on virtual platform is smaller than 3M
|
|
||||||
if uefi_size > 0x300000 {
|
|
||||||
return Err(Error::UefiTooBig);
|
|
||||||
}
|
|
||||||
uefi_image.rewind().map_err(|_| Error::SeekUefiStart)?;
|
|
||||||
guest_mem
|
|
||||||
.read_exact_volatile_from(guest_addr, &mut uefi_image.as_fd(), uefi_size)
|
|
||||||
.map_err(|_| Error::ReadUefiImage)
|
|
||||||
}
|
|
||||||
204
arch/src/lib.rs
204
arch/src/lib.rs
@@ -1,85 +1,40 @@
|
|||||||
// 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.
|
|
||||||
//
|
|
||||||
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
//! Implements platform specific functionality.
|
#![allow(
|
||||||
//! Supported platforms: x86_64, aarch64, riscv64.
|
clippy::unreadable_literal,
|
||||||
|
clippy::redundant_static_lifetimes,
|
||||||
|
clippy::cast_lossless,
|
||||||
|
clippy::transmute_ptr_to_ptr,
|
||||||
|
clippy::cast_ptr_alignment
|
||||||
|
)]
|
||||||
|
|
||||||
use std::collections::BTreeMap;
|
extern crate byteorder;
|
||||||
use std::str::FromStr;
|
extern crate kvm_bindings;
|
||||||
use std::sync::Arc;
|
extern crate libc;
|
||||||
use std::{fmt, result};
|
|
||||||
|
|
||||||
use serde::de::{IntoDeserializer, value};
|
#[cfg(feature = "acpi")]
|
||||||
use serde::{Deserialize, Serialize};
|
extern crate acpi_tables;
|
||||||
use thiserror::Error;
|
extern crate arch_gen;
|
||||||
use vm_memory::bitmap::AtomicBitmap;
|
extern crate kvm_ioctls;
|
||||||
|
extern crate linux_loader;
|
||||||
|
extern crate vm_memory;
|
||||||
|
|
||||||
type GuestMemoryMmap = vm_memory::GuestMemoryMmap<AtomicBitmap>;
|
use std::result;
|
||||||
type GuestRegionMmap = vm_memory::GuestRegionMmap<AtomicBitmap>;
|
|
||||||
|
|
||||||
/// Type for returning error code.
|
#[derive(Debug)]
|
||||||
#[derive(Debug, Error)]
|
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[cfg(target_arch = "x86_64")]
|
#[cfg(target_arch = "x86_64")]
|
||||||
#[error("Platform specific error (x86_64)")]
|
/// X86_64 specific error triggered during system configuration.
|
||||||
PlatformSpecific(#[from] x86_64::Error),
|
X86_64Setup(x86_64::Error),
|
||||||
#[cfg(target_arch = "aarch64")]
|
/// The zero page extends past the end of guest_mem.
|
||||||
#[error("Platform specific error (aarch64)")]
|
|
||||||
PlatformSpecific(#[from] aarch64::Error),
|
|
||||||
#[cfg(target_arch = "riscv64")]
|
|
||||||
#[error("Platform specific error (riscv64)")]
|
|
||||||
PlatformSpecific(#[from] riscv64::Error),
|
|
||||||
#[error("The memory map table extends past the end of guest memory")]
|
|
||||||
MemmapTablePastRamEnd,
|
|
||||||
#[error("Error writing memory map table to guest memory")]
|
|
||||||
MemmapTableSetup(#[source] vm_memory::GuestMemoryError),
|
|
||||||
#[error("Error generating memory map table")]
|
|
||||||
MemmapTableGeneration,
|
|
||||||
#[error("The hvm_start_info structure extends past the end of guest memory")]
|
|
||||||
StartInfoPastRamEnd,
|
|
||||||
#[error("Error writing hvm_start_info to guest memory")]
|
|
||||||
StartInfoSetup(#[source] vm_memory::GuestMemoryError),
|
|
||||||
#[error("Failed to compute initramfs address")]
|
|
||||||
InitramfsAddress,
|
|
||||||
#[error("Error writing module entry to guest memory")]
|
|
||||||
ModlistSetup(#[source] vm_memory::GuestMemoryError),
|
|
||||||
#[error("RSDP extends past the end of guest memory")]
|
|
||||||
RsdpPastRamEnd,
|
|
||||||
#[error("Failed to setup Zero Page for bzImage")]
|
|
||||||
ZeroPageSetup(#[source] vm_memory::GuestMemoryError),
|
|
||||||
#[error("Zero Page for bzImage past RAM end")]
|
|
||||||
ZeroPagePastRamEnd,
|
ZeroPagePastRamEnd,
|
||||||
|
/// Error writing the zero page of guest memory.
|
||||||
|
ZeroPageSetup(vm_memory::GuestMemoryError),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Type for returning public functions outcome.
|
|
||||||
pub type Result<T> = result::Result<T, Error>;
|
pub type Result<T> = result::Result<T, Error>;
|
||||||
|
|
||||||
// If the target_arch is x86_64 we import CpuProfile from the x86_64 module, otherwise we
|
#[derive(PartialEq)]
|
||||||
// declare it here with only "host" as a selectable CPU profile. This trick is useful to prevent
|
|
||||||
// excessive conditional compilation throughout the codebase.
|
|
||||||
#[cfg(not(target_arch = "x86_64"))]
|
|
||||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
|
||||||
/// A [`CpuProfile`] is a mechanism for ensuring live migration compatibility
|
|
||||||
/// between host's with potentially different CPU models.
|
|
||||||
pub enum CpuProfile {
|
|
||||||
#[default]
|
|
||||||
Host,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note that this trait impl is architecture agnostic and may thus reside here.
|
|
||||||
impl FromStr for CpuProfile {
|
|
||||||
type Err = value::Error;
|
|
||||||
fn from_str(s: &str) -> result::Result<Self, Self::Err> {
|
|
||||||
Self::deserialize(s.into_deserializer())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Type for memory region types.
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize)]
|
|
||||||
pub enum RegionType {
|
pub enum RegionType {
|
||||||
/// RAM type
|
/// RAM type
|
||||||
Ram,
|
Ram,
|
||||||
@@ -97,26 +52,13 @@ pub enum RegionType {
|
|||||||
Reserved,
|
Reserved,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Module for aarch64 related functionality.
|
|
||||||
#[cfg(target_arch = "aarch64")]
|
#[cfg(target_arch = "aarch64")]
|
||||||
pub mod aarch64;
|
pub mod aarch64;
|
||||||
|
|
||||||
#[cfg(target_arch = "aarch64")]
|
#[cfg(target_arch = "aarch64")]
|
||||||
pub use aarch64::{
|
pub use aarch64::{
|
||||||
_NSIG, EntryPoint, arch_memory_regions, configure_system, configure_vcpu,
|
arch_memory_regions, configure_system, get_reserved_mem_addr, layout::CMDLINE_MAX_SIZE,
|
||||||
fdt::DeviceInfoForFdt, get_host_cpu_phys_bits, initramfs_load_addr, layout,
|
layout::CMDLINE_START,
|
||||||
layout::CMDLINE_MAX_SIZE, layout::IRQ_BASE, uefi,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Module for riscv64 related functionality.
|
|
||||||
#[cfg(target_arch = "riscv64")]
|
|
||||||
pub mod riscv64;
|
|
||||||
|
|
||||||
#[cfg(target_arch = "riscv64")]
|
|
||||||
pub use riscv64::{
|
|
||||||
_NSIG, EntryPoint, arch_memory_regions, configure_system, configure_vcpu,
|
|
||||||
fdt::DeviceInfoForFdt, get_host_cpu_phys_bits, initramfs_load_addr, layout,
|
|
||||||
layout::CMDLINE_MAX_SIZE, layout::IRQ_BASE, uefi,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(target_arch = "x86_64")]
|
#[cfg(target_arch = "x86_64")]
|
||||||
@@ -124,97 +66,5 @@ pub mod x86_64;
|
|||||||
|
|
||||||
#[cfg(target_arch = "x86_64")]
|
#[cfg(target_arch = "x86_64")]
|
||||||
pub use x86_64::{
|
pub use x86_64::{
|
||||||
_NSIG, CpuidConfig, CpuidFeatureEntry, EntryPoint, arch_memory_regions, configure_system,
|
arch_memory_regions, configure_system, layout, layout::CMDLINE_MAX_SIZE, layout::CMDLINE_START,
|
||||||
configure_vcpu, cpu_profile::CpuProfile, generate_common_cpuid, generate_ram_ranges,
|
|
||||||
get_host_cpu_phys_bits, initramfs_load_addr, layout, layout::CMDLINE_MAX_SIZE,
|
|
||||||
layout::CMDLINE_START, regs,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Safe wrapper for `sysconf(_SC_PAGESIZE)`.
|
|
||||||
#[cfg(target_arch = "x86_64")]
|
|
||||||
#[inline(always)]
|
|
||||||
fn pagesize() -> usize {
|
|
||||||
// SAFETY: Trivially safe
|
|
||||||
unsafe { libc::sysconf(libc::_SC_PAGESIZE) as usize }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Default)]
|
|
||||||
pub struct NumaNode {
|
|
||||||
pub memory_regions: Vec<Arc<GuestRegionMmap>>,
|
|
||||||
pub hotplug_regions: Vec<Arc<GuestRegionMmap>>,
|
|
||||||
pub cpus: Vec<u32>,
|
|
||||||
pub pci_segments: Vec<u16>,
|
|
||||||
pub distances: BTreeMap<u32, u8>,
|
|
||||||
pub memory_zones: Vec<String>,
|
|
||||||
pub device_id: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type NumaNodes = BTreeMap<u32, NumaNode>;
|
|
||||||
|
|
||||||
/// Type for passing information about the initramfs in the guest memory.
|
|
||||||
pub struct InitramfsConfig {
|
|
||||||
/// Load address of initramfs in guest memory
|
|
||||||
pub address: vm_memory::GuestAddress,
|
|
||||||
/// Size of initramfs in guest memory
|
|
||||||
pub size: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Types of devices that can get attached to this platform.
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, Copy)]
|
|
||||||
pub enum DeviceType {
|
|
||||||
/// Device Type: Virtio.
|
|
||||||
Virtio(u32),
|
|
||||||
/// Device Type: Serial.
|
|
||||||
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
|
|
||||||
Serial,
|
|
||||||
/// Device Type: RTC.
|
|
||||||
#[cfg(target_arch = "aarch64")]
|
|
||||||
Rtc,
|
|
||||||
/// Device Type: GPIO.
|
|
||||||
#[cfg(target_arch = "aarch64")]
|
|
||||||
Gpio,
|
|
||||||
/// Device Type: fw_cfg.
|
|
||||||
#[cfg(feature = "fw_cfg")]
|
|
||||||
FwCfg,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Default (smallest) memory page size for the supported architectures.
|
|
||||||
pub const PAGE_SIZE: usize = 4096;
|
|
||||||
|
|
||||||
impl fmt::Display for DeviceType {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
write!(f, "{self:?}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Structure to describe MMIO device information
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
|
|
||||||
pub struct MmioDeviceInfo {
|
|
||||||
pub addr: u64,
|
|
||||||
pub len: u64,
|
|
||||||
pub irq: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Structure to describe PCI space information
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
|
|
||||||
pub struct PciSpaceInfo {
|
|
||||||
pub pci_segment_id: u16,
|
|
||||||
pub mmio_config_address: u64,
|
|
||||||
pub pci_device_space_start: u64,
|
|
||||||
pub pci_device_space_size: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
|
|
||||||
impl DeviceInfoForFdt for MmioDeviceInfo {
|
|
||||||
fn addr(&self) -> u64 {
|
|
||||||
self.addr
|
|
||||||
}
|
|
||||||
fn irq(&self) -> u32 {
|
|
||||||
self.irq
|
|
||||||
}
|
|
||||||
fn length(&self) -> u64 {
|
|
||||||
self.len
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,487 +0,0 @@
|
|||||||
// Copyright © 2024 Institute of Software, CAS. All rights reserved.
|
|
||||||
// Copyright 2020 Arm Limited (or its affiliates). All rights reserved.
|
|
||||||
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
//
|
|
||||||
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
|
||||||
// found in the THIRD-PARTY file.
|
|
||||||
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::ffi::CStr;
|
|
||||||
use std::fmt::Debug;
|
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
use std::{cmp, result, str};
|
|
||||||
|
|
||||||
use byteorder::{BigEndian, ByteOrder};
|
|
||||||
use hypervisor::arch::riscv64::aia::Vaia;
|
|
||||||
use log::debug;
|
|
||||||
use thiserror::Error;
|
|
||||||
use vm_fdt::{FdtWriter, FdtWriterResult};
|
|
||||||
use vm_memory::{Address, Bytes, GuestMemoryBackend, GuestMemoryError, GuestMemoryRegion};
|
|
||||||
|
|
||||||
use super::super::{DeviceType, GuestMemoryMmap, InitramfsConfig};
|
|
||||||
use super::layout::{
|
|
||||||
IRQ_BASE, MEM_32BIT_DEVICES_SIZE, MEM_32BIT_DEVICES_START, MEM_PCI_IO_SIZE, MEM_PCI_IO_START,
|
|
||||||
PCI_HIGH_BASE, PCI_MMIO_CONFIG_SIZE_PER_SEGMENT,
|
|
||||||
};
|
|
||||||
use crate::PciSpaceInfo;
|
|
||||||
|
|
||||||
const AIA_APLIC_PHANDLE: u32 = 1;
|
|
||||||
const AIA_IMSIC_PHANDLE: u32 = 2;
|
|
||||||
const CPU_INTC_BASE_PHANDLE: u32 = 3;
|
|
||||||
const CPU_BASE_PHANDLE: u32 = 256 + CPU_INTC_BASE_PHANDLE;
|
|
||||||
// Read the documentation specified when appending the root node to the FDT.
|
|
||||||
const ADDRESS_CELLS: u32 = 0x2;
|
|
||||||
const SIZE_CELLS: u32 = 0x2;
|
|
||||||
|
|
||||||
// From https://elixir.bootlin.com/linux/v6.10/source/include/dt-bindings/interrupt-controller/irq.h#L14
|
|
||||||
const _IRQ_TYPE_EDGE_RISING: u32 = 1;
|
|
||||||
const IRQ_TYPE_LEVEL_HI: u32 = 4;
|
|
||||||
|
|
||||||
const S_MODE_EXT_IRQ: u32 = 9;
|
|
||||||
|
|
||||||
/// Trait for devices to be added to the Flattened Device Tree.
|
|
||||||
pub trait DeviceInfoForFdt {
|
|
||||||
/// Returns the address where this device will be loaded.
|
|
||||||
fn addr(&self) -> u64;
|
|
||||||
/// Returns the associated interrupt for this device.
|
|
||||||
fn irq(&self) -> u32;
|
|
||||||
/// Returns the amount of memory that needs to be reserved for this device.
|
|
||||||
fn length(&self) -> u64;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Errors thrown while configuring the Flattened Device Tree for riscv64.
|
|
||||||
#[derive(Debug, Error)]
|
|
||||||
pub enum Error {
|
|
||||||
/// Failure in writing FDT in memory.
|
|
||||||
#[error("Failure in writing FDT in memory")]
|
|
||||||
WriteFdtToMemory(#[source] GuestMemoryError),
|
|
||||||
}
|
|
||||||
type Result<T> = result::Result<T, Error>;
|
|
||||||
|
|
||||||
/// Creates the flattened device tree for this riscv64 VM.
|
|
||||||
#[expect(clippy::too_many_arguments)]
|
|
||||||
pub fn create_fdt<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHasher>(
|
|
||||||
guest_mem: &GuestMemoryMmap,
|
|
||||||
cmdline: &str,
|
|
||||||
num_vcpu: u32,
|
|
||||||
isa_string: &str,
|
|
||||||
device_info: &HashMap<(DeviceType, String), T, S>,
|
|
||||||
aia_device: &Arc<Mutex<dyn Vaia>>,
|
|
||||||
initrd: &Option<InitramfsConfig>,
|
|
||||||
pci_space_info: &[PciSpaceInfo],
|
|
||||||
timebase_frequency: u32,
|
|
||||||
) -> FdtWriterResult<Vec<u8>> {
|
|
||||||
// Allocate stuff necessary for the holding the blob.
|
|
||||||
let mut fdt = FdtWriter::new()?;
|
|
||||||
|
|
||||||
// For an explanation why these nodes were introduced in the blob take a look at
|
|
||||||
// https://github.com/devicetree-org/devicetree-specification/releases/tag/v0.4
|
|
||||||
// In chapter 3.
|
|
||||||
|
|
||||||
// Header or the root node as per above mentioned documentation.
|
|
||||||
let root_node = fdt.begin_node("")?;
|
|
||||||
fdt.property_string("compatible", "linux,dummy-virt")?;
|
|
||||||
// For info on #address-cells and size-cells resort to Table 3.1 Root Node
|
|
||||||
// Properties
|
|
||||||
fdt.property_u32("#address-cells", ADDRESS_CELLS)?;
|
|
||||||
fdt.property_u32("#size-cells", SIZE_CELLS)?;
|
|
||||||
create_cpu_nodes(&mut fdt, num_vcpu, isa_string, timebase_frequency)?;
|
|
||||||
create_memory_node(&mut fdt, guest_mem)?;
|
|
||||||
create_chosen_node(&mut fdt, cmdline, initrd)?;
|
|
||||||
create_aia_node(&mut fdt, aia_device)?;
|
|
||||||
create_devices_node(&mut fdt, device_info)?;
|
|
||||||
create_pci_nodes(&mut fdt, pci_space_info)?;
|
|
||||||
|
|
||||||
// End Header node.
|
|
||||||
fdt.end_node(root_node)?;
|
|
||||||
|
|
||||||
let fdt_final = fdt.finish()?;
|
|
||||||
|
|
||||||
Ok(fdt_final)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn write_fdt_to_memory(fdt_final: &[u8], guest_mem: &GuestMemoryMmap) -> Result<()> {
|
|
||||||
// Write FDT to memory.
|
|
||||||
guest_mem
|
|
||||||
.write_slice(fdt_final, super::layout::FDT_START)
|
|
||||||
.map_err(Error::WriteFdtToMemory)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Following are the auxiliary function for creating the different nodes that we append to our FDT.
|
|
||||||
fn create_cpu_nodes(
|
|
||||||
fdt: &mut FdtWriter,
|
|
||||||
num_cpus: u32,
|
|
||||||
isa_string: &str,
|
|
||||||
timebase_frequency: 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)?;
|
|
||||||
fdt.property_u32("timebase-frequency", timebase_frequency)?;
|
|
||||||
|
|
||||||
for cpu_index in 0..num_cpus {
|
|
||||||
let cpu = fdt.begin_node(&format!("cpu@{cpu_index:x}"))?;
|
|
||||||
fdt.property_string("device_type", "cpu")?;
|
|
||||||
fdt.property_string("compatible", "riscv")?;
|
|
||||||
fdt.property_string("mmu-type", "sv48")?;
|
|
||||||
fdt.property_string("riscv,isa", isa_string)?;
|
|
||||||
fdt.property_string("status", "okay")?;
|
|
||||||
fdt.property_u32("reg", cpu_index)?;
|
|
||||||
fdt.property_u32("phandle", CPU_BASE_PHANDLE + cpu_index)?;
|
|
||||||
|
|
||||||
// interrupt controller node
|
|
||||||
let intc_node = fdt.begin_node("interrupt-controller")?;
|
|
||||||
fdt.property_string("compatible", "riscv,cpu-intc")?;
|
|
||||||
fdt.property_u32("#interrupt-cells", 1u32)?;
|
|
||||||
fdt.property_null("interrupt-controller")?;
|
|
||||||
fdt.property_u32("phandle", CPU_INTC_BASE_PHANDLE + cpu_index)?;
|
|
||||||
fdt.end_node(intc_node)?;
|
|
||||||
|
|
||||||
fdt.end_node(cpu)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
fdt.end_node(cpus)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_memory_node(fdt: &mut FdtWriter, guest_mem: &GuestMemoryMmap) -> FdtWriterResult<()> {
|
|
||||||
// Note: memory regions from "GuestMemoryBackend" 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("GuestMemoryBackend must have one memory region at least")
|
|
||||||
.raw_value();
|
|
||||||
let mut current_end = current_start;
|
|
||||||
|
|
||||||
for (start, size) in guest_mem
|
|
||||||
.iter()
|
|
||||||
.map(|m| (m.start_addr().raw_value(), m.len()))
|
|
||||||
{
|
|
||||||
if current_end == start {
|
|
||||||
// This zone is continuous with the previous one.
|
|
||||||
current_end += size;
|
|
||||||
} else {
|
|
||||||
ram_regions.push((current_start, current_end));
|
|
||||||
|
|
||||||
current_start = start;
|
|
||||||
current_end = start + size;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ram_regions.push((current_start, current_end));
|
|
||||||
|
|
||||||
ram_regions
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut mem_reg_property = Vec::new();
|
|
||||||
for region in ram_regions {
|
|
||||||
let mem_size = region.1 - region.0;
|
|
||||||
mem_reg_property.push(region.0);
|
|
||||||
mem_reg_property.push(mem_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
let ram_start = super::layout::RAM_START.raw_value();
|
|
||||||
let memory_node_name = format!("memory@{ram_start:x}");
|
|
||||||
let memory_node = fdt.begin_node(&memory_node_name)?;
|
|
||||||
fdt.property_string("device_type", "memory")?;
|
|
||||||
fdt.property_array_u64("reg", &mem_reg_property)?;
|
|
||||||
fdt.end_node(memory_node)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_chosen_node(
|
|
||||||
fdt: &mut FdtWriter,
|
|
||||||
cmdline: &str,
|
|
||||||
initrd: &Option<InitramfsConfig>,
|
|
||||||
) -> FdtWriterResult<()> {
|
|
||||||
let chosen_node = fdt.begin_node("chosen")?;
|
|
||||||
fdt.property_string("bootargs", cmdline)?;
|
|
||||||
|
|
||||||
if let Some(initrd_config) = initrd {
|
|
||||||
let initrd_start = initrd_config.address.raw_value();
|
|
||||||
let initrd_end = initrd_config.address.raw_value() + initrd_config.size as u64;
|
|
||||||
fdt.property_u64("linux,initrd-start", initrd_start)?;
|
|
||||||
fdt.property_u64("linux,initrd-end", initrd_end)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
fdt.end_node(chosen_node)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_aia_node(fdt: &mut FdtWriter, aia_device: &Arc<Mutex<dyn Vaia>>) -> FdtWriterResult<()> {
|
|
||||||
// IMSIC
|
|
||||||
if aia_device.lock().unwrap().msi_compatible() {
|
|
||||||
use super::layout::IMSIC_START;
|
|
||||||
let imsic_name = format!("imsics@{:x}", IMSIC_START.0);
|
|
||||||
let imsic_node = fdt.begin_node(&imsic_name)?;
|
|
||||||
|
|
||||||
fdt.property_string(
|
|
||||||
"compatible",
|
|
||||||
aia_device.lock().unwrap().imsic_compatibility(),
|
|
||||||
)?;
|
|
||||||
let imsic_reg_prop = aia_device.lock().unwrap().imsic_properties();
|
|
||||||
fdt.property_array_u32("reg", &imsic_reg_prop)?;
|
|
||||||
fdt.property_u32("#interrupt-cells", 0u32)?;
|
|
||||||
fdt.property_null("interrupt-controller")?;
|
|
||||||
fdt.property_null("msi-controller")?;
|
|
||||||
let imsic_num_ids = aia_device.lock().unwrap().imsic_num_ids();
|
|
||||||
fdt.property_u32("riscv,num-ids", imsic_num_ids)?;
|
|
||||||
fdt.property_u32("phandle", AIA_IMSIC_PHANDLE)?;
|
|
||||||
|
|
||||||
let mut irq_cells = Vec::new();
|
|
||||||
let num_cpus = aia_device.lock().unwrap().vcpu_count();
|
|
||||||
for i in 0..num_cpus {
|
|
||||||
irq_cells.push(CPU_INTC_BASE_PHANDLE + i);
|
|
||||||
irq_cells.push(S_MODE_EXT_IRQ);
|
|
||||||
}
|
|
||||||
fdt.property_array_u32("interrupts-extended", &irq_cells)?;
|
|
||||||
|
|
||||||
fdt.end_node(imsic_node)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// APLIC
|
|
||||||
use super::layout::APLIC_START;
|
|
||||||
let aplic_name = format!("aplic@{:x}", APLIC_START.0);
|
|
||||||
let aplic_node = fdt.begin_node(&aplic_name)?;
|
|
||||||
|
|
||||||
fdt.property_string(
|
|
||||||
"compatible",
|
|
||||||
aia_device.lock().unwrap().aplic_compatibility(),
|
|
||||||
)?;
|
|
||||||
let reg_cells = aia_device.lock().unwrap().aplic_properties();
|
|
||||||
fdt.property_array_u32("reg", ®_cells)?;
|
|
||||||
fdt.property_u32("#interrupt-cells", 2u32)?;
|
|
||||||
fdt.property_null("interrupt-controller")?;
|
|
||||||
// TODO complete num-srcs
|
|
||||||
fdt.property_u32("riscv,num-sources", 96u32)?;
|
|
||||||
fdt.property_u32("phandle", AIA_APLIC_PHANDLE)?;
|
|
||||||
fdt.property_u32("msi-parent", AIA_IMSIC_PHANDLE)?;
|
|
||||||
|
|
||||||
fdt.end_node(aplic_node)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_serial_node<T: DeviceInfoForFdt + Clone + Debug>(
|
|
||||||
fdt: &mut FdtWriter,
|
|
||||||
dev_info: &T,
|
|
||||||
) -> FdtWriterResult<()> {
|
|
||||||
let serial_reg_prop = [dev_info.addr(), dev_info.length()];
|
|
||||||
let irq = [dev_info.irq() - IRQ_BASE, IRQ_TYPE_LEVEL_HI];
|
|
||||||
|
|
||||||
let serial_node = fdt.begin_node(&format!("serial@{:x}", dev_info.addr()))?;
|
|
||||||
fdt.property_string("compatible", "ns16550a")?;
|
|
||||||
fdt.property_array_u64("reg", &serial_reg_prop)?;
|
|
||||||
fdt.property_u32("clock-frequency", 3686400)?;
|
|
||||||
fdt.property_u32("interrupt-parent", AIA_APLIC_PHANDLE)?;
|
|
||||||
fdt.property_array_u32("interrupts", &irq)?;
|
|
||||||
fdt.end_node(serial_node)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_devices_node<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHasher>(
|
|
||||||
fdt: &mut FdtWriter,
|
|
||||||
dev_info: &HashMap<(DeviceType, String), T, S>,
|
|
||||||
) -> FdtWriterResult<()> {
|
|
||||||
for ((device_type, _device_id), info) in dev_info {
|
|
||||||
match device_type {
|
|
||||||
DeviceType::Serial => create_serial_node(fdt, info)?,
|
|
||||||
DeviceType::Virtio(_) => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_pci_nodes(fdt: &mut FdtWriter, pci_device_info: &[PciSpaceInfo]) -> FdtWriterResult<()> {
|
|
||||||
// Add node for PCIe controller.
|
|
||||||
// See Documentation/devicetree/bindings/pci/host-generic-pci.txt in the kernel
|
|
||||||
// and https://elinux.org/Device_Tree_Usage.
|
|
||||||
// In multiple PCI segments setup, each PCI segment needs a PCI node.
|
|
||||||
for pci_device_info_elem in pci_device_info.iter() {
|
|
||||||
// EDK2 requires the PCIe high space above 4G address.
|
|
||||||
// The actual space in CLH follows the RAM. If the RAM space is small, the PCIe high space
|
|
||||||
// could fall below 4G.
|
|
||||||
// Here we cut off PCI device space below 8G in FDT to workaround the EDK2 check.
|
|
||||||
// But the address written in ACPI is not impacted.
|
|
||||||
let (pci_device_base_64bit, pci_device_size_64bit) =
|
|
||||||
if pci_device_info_elem.pci_device_space_start < PCI_HIGH_BASE.raw_value() {
|
|
||||||
(
|
|
||||||
PCI_HIGH_BASE.raw_value(),
|
|
||||||
pci_device_info_elem.pci_device_space_size
|
|
||||||
- (PCI_HIGH_BASE.raw_value() - pci_device_info_elem.pci_device_space_start),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
(
|
|
||||||
pci_device_info_elem.pci_device_space_start,
|
|
||||||
pci_device_info_elem.pci_device_space_size,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
// There is no specific requirement of the 32bit MMIO range, and
|
|
||||||
// therefore at least we can make these ranges 4K aligned.
|
|
||||||
let pci_device_size_32bit: u64 =
|
|
||||||
MEM_32BIT_DEVICES_SIZE / ((1 << 12) * pci_device_info.len() as u64) * (1 << 12);
|
|
||||||
let pci_device_base_32bit: u64 = MEM_32BIT_DEVICES_START.0
|
|
||||||
+ pci_device_size_32bit * pci_device_info_elem.pci_segment_id as u64;
|
|
||||||
|
|
||||||
let ranges = [
|
|
||||||
// io addresses. Since AArch64 will not use IO address,
|
|
||||||
// we can set the same IO address range for every segment.
|
|
||||||
0x1000000,
|
|
||||||
0_u32,
|
|
||||||
0_u32,
|
|
||||||
(MEM_PCI_IO_START.0 >> 32) as u32,
|
|
||||||
MEM_PCI_IO_START.0 as u32,
|
|
||||||
(MEM_PCI_IO_SIZE >> 32) as u32,
|
|
||||||
MEM_PCI_IO_SIZE as u32,
|
|
||||||
// mmio addresses
|
|
||||||
0x2000000, // (ss = 10: 32-bit memory space)
|
|
||||||
(pci_device_base_32bit >> 32) as u32, // PCI address
|
|
||||||
pci_device_base_32bit as u32,
|
|
||||||
(pci_device_base_32bit >> 32) as u32, // CPU address
|
|
||||||
pci_device_base_32bit as u32,
|
|
||||||
(pci_device_size_32bit >> 32) as u32, // size
|
|
||||||
pci_device_size_32bit as u32,
|
|
||||||
// device addresses
|
|
||||||
0x3000000, // (ss = 11: 64-bit memory space)
|
|
||||||
(pci_device_base_64bit >> 32) as u32, // PCI address
|
|
||||||
pci_device_base_64bit as u32,
|
|
||||||
(pci_device_base_64bit >> 32) as u32, // CPU address
|
|
||||||
pci_device_base_64bit as u32,
|
|
||||||
(pci_device_size_64bit >> 32) as u32, // size
|
|
||||||
pci_device_size_64bit as u32,
|
|
||||||
];
|
|
||||||
let bus_range = [0, 0]; // Only bus 0
|
|
||||||
let reg = [
|
|
||||||
pci_device_info_elem.mmio_config_address,
|
|
||||||
PCI_MMIO_CONFIG_SIZE_PER_SEGMENT,
|
|
||||||
];
|
|
||||||
// See kernel document Documentation/devicetree/bindings/pci/pci-msi.txt
|
|
||||||
let msi_map = [
|
|
||||||
// rid-base: A single cell describing the first RID matched by the entry.
|
|
||||||
0x0,
|
|
||||||
// msi-controller: A single phandle to an MSI controller.
|
|
||||||
AIA_IMSIC_PHANDLE,
|
|
||||||
// msi-base: An msi-specifier describing the msi-specifier produced for the
|
|
||||||
// first RID matched by the entry.
|
|
||||||
(pci_device_info_elem.pci_segment_id as u32) << 8,
|
|
||||||
// length: A single cell describing how many consecutive RIDs are matched
|
|
||||||
// following the rid-base.
|
|
||||||
0x100,
|
|
||||||
];
|
|
||||||
|
|
||||||
let pci_node_name = format!("pci@{:x}", pci_device_info_elem.mmio_config_address);
|
|
||||||
let pci_node = fdt.begin_node(&pci_node_name)?;
|
|
||||||
|
|
||||||
fdt.property_string("compatible", "pci-host-ecam-generic")?;
|
|
||||||
fdt.property_string("device_type", "pci")?;
|
|
||||||
fdt.property_array_u32("ranges", &ranges)?;
|
|
||||||
fdt.property_array_u32("bus-range", &bus_range)?;
|
|
||||||
fdt.property_u32(
|
|
||||||
"linux,pci-domain",
|
|
||||||
pci_device_info_elem.pci_segment_id as u32,
|
|
||||||
)?;
|
|
||||||
fdt.property_u32("#address-cells", 3)?;
|
|
||||||
fdt.property_u32("#size-cells", 2)?;
|
|
||||||
fdt.property_array_u64("reg", ®)?;
|
|
||||||
fdt.property_u32("#interrupt-cells", 1)?;
|
|
||||||
fdt.property_null("interrupt-map")?;
|
|
||||||
fdt.property_null("interrupt-map-mask")?;
|
|
||||||
fdt.property_null("dma-coherent")?;
|
|
||||||
fdt.property_array_u32("msi-map", &msi_map)?;
|
|
||||||
fdt.property_u32("msi-parent", AIA_IMSIC_PHANDLE)?;
|
|
||||||
|
|
||||||
fdt.end_node(pci_node)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse the DTB binary and print for debugging
|
|
||||||
pub fn print_fdt(dtb: &[u8]) {
|
|
||||||
match fdt_parser::Fdt::new(dtb) {
|
|
||||||
Ok(fdt) => {
|
|
||||||
if let Some(root) = fdt.find_node("/") {
|
|
||||||
debug!("Printing the FDT:");
|
|
||||||
print_node(root, 0);
|
|
||||||
} else {
|
|
||||||
debug!("Failed to find root node in FDT for debugging.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(_) => debug!("Failed to parse FDT for debugging."),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn print_node(node: fdt_parser::node::FdtNode<'_, '_>, n_spaces: usize) {
|
|
||||||
debug!("{:indent$}{}/", "", node.name, indent = n_spaces);
|
|
||||||
for property in node.properties() {
|
|
||||||
let name = property.name;
|
|
||||||
|
|
||||||
// If the property is 'compatible', its value requires special handling.
|
|
||||||
// The u8 array could contain multiple null-terminated strings.
|
|
||||||
// We copy the original array and simply replace all 'null' characters with spaces.
|
|
||||||
let value = if name == "compatible" {
|
|
||||||
let mut compatible = vec![0u8; 256];
|
|
||||||
let handled_value = property
|
|
||||||
.value
|
|
||||||
.iter()
|
|
||||||
.map(|&c| if c == 0 { b' ' } else { c })
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
let len = cmp::min(255, handled_value.len());
|
|
||||||
compatible[..len].copy_from_slice(&handled_value[..len]);
|
|
||||||
compatible[..(len + 1)].to_vec()
|
|
||||||
} else {
|
|
||||||
property.value.to_vec()
|
|
||||||
};
|
|
||||||
let value = &value;
|
|
||||||
|
|
||||||
// Now the value can be either:
|
|
||||||
// - A null-terminated C string, or
|
|
||||||
// - Binary data
|
|
||||||
// We follow a very simple logic to present the value:
|
|
||||||
// - At first, try to convert it to CStr and print,
|
|
||||||
// - If failed, print it as u32 array.
|
|
||||||
let value_result = match CStr::from_bytes_with_nul(value) {
|
|
||||||
Ok(value_cstr) => value_cstr.to_str().ok(),
|
|
||||||
Err(_e) => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(value_str) = value_result {
|
|
||||||
debug!(
|
|
||||||
"{:indent$}{} : {:#?}",
|
|
||||||
"",
|
|
||||||
name,
|
|
||||||
value_str,
|
|
||||||
indent = (n_spaces + 2)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
let mut array = Vec::with_capacity(256);
|
|
||||||
array.resize(value.len() / 4, 0u32);
|
|
||||||
BigEndian::read_u32_into(value, &mut array);
|
|
||||||
debug!(
|
|
||||||
"{:indent$}{} : {:X?}",
|
|
||||||
"",
|
|
||||||
name,
|
|
||||||
array,
|
|
||||||
indent = (n_spaces + 2)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print children nodes if there is any
|
|
||||||
for child in node.children() {
|
|
||||||
print_node(child, n_spaces + 2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,117 +0,0 @@
|
|||||||
// Copyright © 2024 Institute of Software, CAS. All rights reserved.
|
|
||||||
// Copyright 2020 Arm Limited (or its affiliates). All rights reserved.
|
|
||||||
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
//
|
|
||||||
// Memory layout of RISC-V 64-bit guest:
|
|
||||||
//
|
|
||||||
// Physical +---------------------------------------------------------------+
|
|
||||||
// address | |
|
|
||||||
// end | |
|
|
||||||
// ~ ~ ~ ~
|
|
||||||
// | |
|
|
||||||
// | Highmem PCI MMIO space |
|
|
||||||
// | |
|
|
||||||
// RAM end +---------------------------------------------------------------+
|
|
||||||
// (dynamic, | |
|
|
||||||
// including | |
|
|
||||||
// hotplug ~ ~ ~ ~
|
|
||||||
// memory) | |
|
|
||||||
// | DRAM |
|
|
||||||
// | |
|
|
||||||
// | |
|
|
||||||
// | |
|
|
||||||
// | |
|
|
||||||
// 1 GB +---------------------------------------------------------------+
|
|
||||||
// | |
|
|
||||||
// | PCI MMCONFIG space |
|
|
||||||
// | |
|
|
||||||
// 768 MB +---------------------------------------------------------------+
|
|
||||||
// | |
|
|
||||||
// | |
|
|
||||||
// | PCI MMIO space |
|
|
||||||
// | |
|
|
||||||
// 256 MB +---------------------------------------------------------------|
|
|
||||||
// | |
|
|
||||||
// | Legacy devices space |
|
|
||||||
// | |
|
|
||||||
// 128 MB +---------------------------------------------------------------|
|
|
||||||
// | |
|
|
||||||
// | IMSICs |
|
|
||||||
// | |
|
|
||||||
// 64 MB +---------------------------------------------------------------+
|
|
||||||
// | |
|
|
||||||
// | APLICs |
|
|
||||||
// | |
|
|
||||||
// 4 MB +---------------------------------------------------------------+
|
|
||||||
// | UEFI flash |
|
|
||||||
// 0 GB +---------------------------------------------------------------+
|
|
||||||
//
|
|
||||||
//
|
|
||||||
|
|
||||||
use vm_memory::GuestAddress;
|
|
||||||
|
|
||||||
/// 0x0 ~ 0x40_0000 (4 MiB) is reserved to UEFI
|
|
||||||
/// UEFI binary size is required less than 3 MiB, reserving 4 MiB is enough.
|
|
||||||
pub const UEFI_START: GuestAddress = GuestAddress(0);
|
|
||||||
pub const UEFI_SIZE: u64 = 0x040_0000;
|
|
||||||
|
|
||||||
/// AIA related devices
|
|
||||||
/// See https://elixir.bootlin.com/linux/v6.10/source/arch/riscv/include/uapi/asm/kvm.h
|
|
||||||
/// 0x40_0000 ~ 0x0400_0000 (64 MiB) resides APLICs
|
|
||||||
pub const APLIC_START: GuestAddress = GuestAddress(0x40_0000);
|
|
||||||
pub const APLIC_SIZE: u64 = 0x4000;
|
|
||||||
|
|
||||||
/// 0x0400_0000 ~ 0x0800_0000 (64 MiB) resides IMSICs
|
|
||||||
pub const IMSIC_START: GuestAddress = GuestAddress(0x0400_0000);
|
|
||||||
pub const IMSIC_SIZE: u64 = 0x1000;
|
|
||||||
|
|
||||||
/// Below this address will reside the AIA, above this address will reside the MMIO devices.
|
|
||||||
const MAPPED_IO_START: GuestAddress = GuestAddress(0x0800_0000);
|
|
||||||
|
|
||||||
/// Space 0x0800_0000 ~ 0x1000_0000 is reserved for legacy devices.
|
|
||||||
pub const LEGACY_SERIAL_MAPPED_IO_START: GuestAddress = MAPPED_IO_START;
|
|
||||||
|
|
||||||
/// Space 0x0905_0000 ~ 0x0906_0000 is reserved for pcie io address
|
|
||||||
pub const MEM_PCI_IO_START: GuestAddress = GuestAddress(0x0905_0000);
|
|
||||||
pub const MEM_PCI_IO_SIZE: u64 = 0x1_0000;
|
|
||||||
|
|
||||||
/// Starting from 0x1000_0000 (256MiB) to 0x3000_0000 (768MiB) is used for PCIE MMIO
|
|
||||||
pub const MEM_32BIT_DEVICES_START: GuestAddress = GuestAddress(0x1000_0000);
|
|
||||||
pub const MEM_32BIT_DEVICES_SIZE: u64 = 0x2000_0000;
|
|
||||||
|
|
||||||
/// PCI MMCONFIG space (start: after the device space at 768MiB, length: 256MiB)
|
|
||||||
pub const PCI_MMCONFIG_START: GuestAddress = GuestAddress(0x3000_0000);
|
|
||||||
pub const PCI_MMCONFIG_SIZE: u64 = 256 << 20;
|
|
||||||
// One bus with potentially 256 devices (32 slots x 8 functions).
|
|
||||||
pub const PCI_MMIO_CONFIG_SIZE_PER_SEGMENT: u64 = 4096 * 256;
|
|
||||||
|
|
||||||
/// Start of RAM.
|
|
||||||
pub const RAM_START: GuestAddress = GuestAddress(0x4000_0000);
|
|
||||||
|
|
||||||
/// Kernel command line maximum size on RISC-V.
|
|
||||||
/// See https://elixir.bootlin.com/linux/v6.10/source/arch/riscv/include/uapi/asm/setup.h
|
|
||||||
pub const CMDLINE_MAX_SIZE: usize = 1024;
|
|
||||||
|
|
||||||
/// FDT is at the beginning of RAM.
|
|
||||||
pub const FDT_START: GuestAddress = RAM_START;
|
|
||||||
pub const FDT_MAX_SIZE: u64 = 0x1_0000;
|
|
||||||
|
|
||||||
/// Put ACPI table above dtb
|
|
||||||
pub const ACPI_START: GuestAddress = GuestAddress(RAM_START.0 + FDT_MAX_SIZE);
|
|
||||||
pub const ACPI_MAX_SIZE: u64 = 0x20_0000;
|
|
||||||
pub const RSDP_POINTER: GuestAddress = ACPI_START;
|
|
||||||
|
|
||||||
/// Kernel start after FDT and ACPI
|
|
||||||
pub const KERNEL_START: GuestAddress = GuestAddress(RAM_START.0 + FDT_MAX_SIZE);
|
|
||||||
|
|
||||||
/// Pci high memory base
|
|
||||||
pub const PCI_HIGH_BASE: GuestAddress = GuestAddress(0x2_0000_0000);
|
|
||||||
|
|
||||||
/// First usable interrupt on riscv64
|
|
||||||
pub const IRQ_BASE: u32 = 0;
|
|
||||||
|
|
||||||
// As per https://elixir.bootlin.com/linux/v6.10/source/arch/riscv/include/asm/kvm_host.h#L31
|
|
||||||
/// Number of supported interrupts
|
|
||||||
pub const IRQ_NUM: u32 = 1023;
|
|
||||||
@@ -1,232 +0,0 @@
|
|||||||
// Copyright © 2024 Institute of Software, CAS. All rights reserved.
|
|
||||||
// Copyright 2020 Arm Limited (or its affiliates). All rights reserved.
|
|
||||||
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
/// Module for the flattened device tree.
|
|
||||||
pub mod fdt;
|
|
||||||
/// Layout for this riscv64 system.
|
|
||||||
pub mod layout;
|
|
||||||
/// Module for loading UEFI binary.
|
|
||||||
pub mod uefi;
|
|
||||||
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::fmt::Debug;
|
|
||||||
use std::fs::File;
|
|
||||||
use std::io::{BufRead, BufReader};
|
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
|
|
||||||
use hypervisor::arch::riscv64::aia::Vaia;
|
|
||||||
use log::{Level, log_enabled};
|
|
||||||
use thiserror::Error;
|
|
||||||
use vm_memory::{Address, GuestAddress, GuestMemoryAtomic, GuestMemoryBackend};
|
|
||||||
|
|
||||||
pub use self::fdt::DeviceInfoForFdt;
|
|
||||||
use crate::{DeviceType, GuestMemoryMmap, PciSpaceInfo, RegionType};
|
|
||||||
|
|
||||||
pub const CLOUDHV_IRQCHIP_NUM_MSIS: u16 = 255;
|
|
||||||
pub const CLOUDHV_IRQCHIP_NUM_SOURCES: u8 = 96;
|
|
||||||
pub const CLOUDHV_IRQCHIP_NUM_PRIO_BITS: u8 = 3;
|
|
||||||
pub const CLOUDHV_IRQCHIP_MAX_GUESTS_BITS: u8 = 3;
|
|
||||||
pub const CLOUDHV_IRQCHIP_MAX_GUESTS: u8 = (1 << CLOUDHV_IRQCHIP_MAX_GUESTS_BITS) - 1;
|
|
||||||
pub const _NSIG: i32 = 65;
|
|
||||||
|
|
||||||
/// Errors thrown while configuring riscv64 system.
|
|
||||||
#[derive(Debug, Error)]
|
|
||||||
pub enum Error {
|
|
||||||
/// Failed to create a FDT.
|
|
||||||
#[error("Failed to create a FDT")]
|
|
||||||
SetupFdt,
|
|
||||||
|
|
||||||
/// Failed to write FDT to memory.
|
|
||||||
#[error("Failed to write FDT to memory")]
|
|
||||||
WriteFdtToMemory(#[source] fdt::Error),
|
|
||||||
|
|
||||||
/// Failed to create a AIA.
|
|
||||||
#[error("Failed to create a AIA")]
|
|
||||||
SetupAia,
|
|
||||||
|
|
||||||
/// Failed to compute the initramfs address.
|
|
||||||
#[error("Failed to compute the initramfs address")]
|
|
||||||
InitramfsAddress,
|
|
||||||
|
|
||||||
/// Error configuring the general purpose registers
|
|
||||||
#[error("Error configuring the general purpose registers")]
|
|
||||||
RegsConfiguration(#[source] hypervisor::HypervisorCpuError),
|
|
||||||
|
|
||||||
/// Error opening /proc/cpuinfo
|
|
||||||
#[error("Error opening /proc/cpuinfo")]
|
|
||||||
OpenCpuInfo(#[source] std::io::Error),
|
|
||||||
|
|
||||||
/// Error reading /proc/cpuinfo
|
|
||||||
#[error("Error reading /proc/cpuinfo")]
|
|
||||||
ReadCpuInfo(#[source] std::io::Error),
|
|
||||||
|
|
||||||
/// Invalid ISA string
|
|
||||||
#[error("Invalid ISA string: {0}")]
|
|
||||||
InvalidIsaString(String),
|
|
||||||
|
|
||||||
/// Error parsing /proc/cpuinfo
|
|
||||||
#[error("Error parsing /proc/cpuinfo")]
|
|
||||||
CpuInfoParsing,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
|
||||||
/// Specifies the entry point address where the guest must start
|
|
||||||
/// executing code.
|
|
||||||
pub struct EntryPoint {
|
|
||||||
/// Address in guest memory where the guest must start execution
|
|
||||||
pub entry_addr: GuestAddress,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Configure the specified VCPU, and return its MPIDR.
|
|
||||||
pub fn configure_vcpu(
|
|
||||||
vcpu: &dyn hypervisor::Vcpu,
|
|
||||||
id: u32,
|
|
||||||
boot_setup: Option<(EntryPoint, &GuestMemoryAtomic<GuestMemoryMmap>)>,
|
|
||||||
) -> super::Result<()> {
|
|
||||||
if let Some((kernel_entry_point, _guest_memory)) = boot_setup {
|
|
||||||
vcpu.setup_regs(
|
|
||||||
id,
|
|
||||||
kernel_entry_point.entry_addr.raw_value(),
|
|
||||||
layout::FDT_START.raw_value(),
|
|
||||||
)
|
|
||||||
.map_err(Error::RegsConfiguration)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn arch_memory_regions() -> Vec<(GuestAddress, usize, RegionType)> {
|
|
||||||
vec![
|
|
||||||
// 0 MiB ~ 256 MiB: AIA and legacy devices
|
|
||||||
(
|
|
||||||
GuestAddress(0),
|
|
||||||
layout::MEM_32BIT_DEVICES_START.0 as usize,
|
|
||||||
RegionType::Reserved,
|
|
||||||
),
|
|
||||||
// 256 MiB ~ 768 MiB: MMIO space
|
|
||||||
(
|
|
||||||
layout::MEM_32BIT_DEVICES_START,
|
|
||||||
layout::MEM_32BIT_DEVICES_SIZE as usize,
|
|
||||||
RegionType::SubRegion,
|
|
||||||
),
|
|
||||||
// 768 MiB ~ 1 GiB: reserved. The leading 256M for PCIe MMCONFIG space
|
|
||||||
(
|
|
||||||
layout::PCI_MMCONFIG_START,
|
|
||||||
layout::PCI_MMCONFIG_SIZE as usize,
|
|
||||||
RegionType::Reserved,
|
|
||||||
),
|
|
||||||
// 1GiB ~ inf: RAM
|
|
||||||
(layout::RAM_START, usize::MAX, RegionType::Ram),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read the first "isa" string from /proc/cpuinfo and filter out the H extension,
|
|
||||||
// while correctly preserving multi-letter extensions.
|
|
||||||
fn isa_string_from_host() -> Result<String, Error> {
|
|
||||||
let file = File::open("/proc/cpuinfo").map_err(Error::OpenCpuInfo)?;
|
|
||||||
let reader = BufReader::new(file);
|
|
||||||
|
|
||||||
for line in reader.lines() {
|
|
||||||
let line = line.map_err(Error::ReadCpuInfo)?;
|
|
||||||
let trimmed_line = line.trim();
|
|
||||||
|
|
||||||
if trimmed_line.starts_with("isa") {
|
|
||||||
let parts: Vec<&str> = trimmed_line.split(':').collect();
|
|
||||||
if parts.len() == 2 {
|
|
||||||
let isa_string = parts[1].trim();
|
|
||||||
|
|
||||||
// Split the string by underscores to separate single letter vs long-form
|
|
||||||
// extensions
|
|
||||||
let mut components: Vec<String> =
|
|
||||||
isa_string.split('_').map(|s| s.to_string()).collect();
|
|
||||||
|
|
||||||
if components.is_empty() {
|
|
||||||
return Err(Error::InvalidIsaString(isa_string.to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove H extension if present in single letter extensions
|
|
||||||
let first_component = components[0].chars().filter(|&c| c != 'h').collect();
|
|
||||||
|
|
||||||
components[0] = first_component;
|
|
||||||
|
|
||||||
return Ok(components.join("_"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(Error::CpuInfoParsing)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Configures the system and should be called once per vm before starting vcpu threads.
|
|
||||||
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>>,
|
|
||||||
timebase_frequency: u32,
|
|
||||||
) -> super::Result<()> {
|
|
||||||
let isa_string = isa_string_from_host()?;
|
|
||||||
let fdt_final = fdt::create_fdt(
|
|
||||||
guest_mem,
|
|
||||||
cmdline,
|
|
||||||
num_vcpu,
|
|
||||||
&isa_string,
|
|
||||||
device_info,
|
|
||||||
aia_device,
|
|
||||||
initrd,
|
|
||||||
pci_space_info,
|
|
||||||
timebase_frequency,
|
|
||||||
)
|
|
||||||
.map_err(|_| Error::SetupFdt)?;
|
|
||||||
|
|
||||||
if log_enabled!(Level::Debug) {
|
|
||||||
fdt::print_fdt(&fdt_final);
|
|
||||||
}
|
|
||||||
|
|
||||||
fdt::write_fdt_to_memory(&fdt_final, guest_mem).map_err(Error::WriteFdtToMemory)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the memory address where the initramfs could be loaded.
|
|
||||||
pub fn initramfs_load_addr(
|
|
||||||
guest_mem: &GuestMemoryMmap,
|
|
||||||
initramfs_size: usize,
|
|
||||||
) -> super::Result<u64> {
|
|
||||||
let round_to_pagesize = |size| (size + (super::PAGE_SIZE - 1)) & !(super::PAGE_SIZE - 1);
|
|
||||||
match guest_mem
|
|
||||||
.last_addr()
|
|
||||||
.checked_sub(round_to_pagesize(initramfs_size) as u64 - 1)
|
|
||||||
{
|
|
||||||
Some(offset) => {
|
|
||||||
if guest_mem.address_in_range(offset) {
|
|
||||||
Ok(offset.raw_value())
|
|
||||||
} else {
|
|
||||||
Err(super::Error::PlatformSpecific(Error::InitramfsAddress))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => Err(super::Error::PlatformSpecific(Error::InitramfsAddress)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_host_cpu_phys_bits(_hypervisor: &dyn hypervisor::Hypervisor) -> u8 {
|
|
||||||
40
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod unit_tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_arch_memory_regions_dram() {
|
|
||||||
let regions = arch_memory_regions();
|
|
||||||
assert_eq!(4, regions.len());
|
|
||||||
assert_eq!(layout::RAM_START, regions[3].0);
|
|
||||||
assert_eq!(RegionType::Ram, regions[3].2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
// Copyright 2020 Arm Limited (or its affiliates). All rights reserved.
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
use std::io::{Read, Seek, SeekFrom};
|
|
||||||
use std::os::fd::AsFd;
|
|
||||||
use std::result;
|
|
||||||
|
|
||||||
use thiserror::Error;
|
|
||||||
use vm_memory::{Bytes, GuestAddress, GuestMemory};
|
|
||||||
|
|
||||||
/// Errors thrown while loading UEFI binary
|
|
||||||
#[derive(Debug, Error)]
|
|
||||||
pub enum Error {
|
|
||||||
/// Unable to seek to UEFI image start.
|
|
||||||
#[error("Unable to seek to UEFI image start")]
|
|
||||||
SeekUefiStart,
|
|
||||||
/// Unable to seek to UEFI image end.
|
|
||||||
#[error("Unable to seek to UEFI image end")]
|
|
||||||
SeekUefiEnd,
|
|
||||||
/// UEFI image too big.
|
|
||||||
#[error("UEFI image too big")]
|
|
||||||
UefiTooBig,
|
|
||||||
/// Unable to read UEFI image
|
|
||||||
#[error("Unable to read UEFI image")]
|
|
||||||
ReadUefiImage,
|
|
||||||
}
|
|
||||||
type Result<T> = result::Result<T, Error>;
|
|
||||||
|
|
||||||
pub fn load_uefi<F, M: GuestMemory>(
|
|
||||||
guest_mem: &M,
|
|
||||||
guest_addr: GuestAddress,
|
|
||||||
uefi_image: &mut F,
|
|
||||||
) -> Result<()>
|
|
||||||
where
|
|
||||||
F: Read + Seek + AsFd,
|
|
||||||
{
|
|
||||||
let uefi_size = uefi_image
|
|
||||||
.seek(SeekFrom::End(0))
|
|
||||||
.map_err(|_| Error::SeekUefiEnd)? as usize;
|
|
||||||
|
|
||||||
// edk2 image on virtual platform is smaller than 3M
|
|
||||||
if uefi_size > 0x300000 {
|
|
||||||
return Err(Error::UefiTooBig);
|
|
||||||
}
|
|
||||||
uefi_image.rewind().map_err(|_| Error::SeekUefiStart)?;
|
|
||||||
guest_mem
|
|
||||||
.read_exact_volatile_from(guest_addr, &mut uefi_image.as_fd(), uefi_size)
|
|
||||||
.map_err(|_| Error::ReadUefiImage)
|
|
||||||
}
|
|
||||||
@@ -1,272 +0,0 @@
|
|||||||
// Copyright © 2026 Cyberus Technology GmbH
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
//
|
|
||||||
|
|
||||||
use hypervisor::arch::x86::{MsrEntry, VcpuMsrConfigUpdate};
|
|
||||||
use log::{debug, error};
|
|
||||||
|
|
||||||
use crate::x86_64::Error;
|
|
||||||
|
|
||||||
/// The register address of the IA32_ARCH_CAPABILITIES MSR
|
|
||||||
const IA32_ARCH_CAPABILITIES: u32 = 0x10a;
|
|
||||||
|
|
||||||
/// Check that the MSR updates required by the CPU profile are compatible with the
|
|
||||||
/// host's feature MSRs.
|
|
||||||
pub(crate) fn valid_required_arch_capabilities_update(
|
|
||||||
required_updates: &VcpuMsrConfigUpdate,
|
|
||||||
host_feature_msrs: &[MsrEntry],
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
let find_arch_capabilities = |msrs: &[MsrEntry]| {
|
|
||||||
msrs.iter()
|
|
||||||
.find(|msr| msr.index == IA32_ARCH_CAPABILITIES)
|
|
||||||
.map(|entry| entry.data)
|
|
||||||
};
|
|
||||||
|
|
||||||
let Some(required_arch_capabilities_msr) =
|
|
||||||
find_arch_capabilities(&required_updates.feature_msrs)
|
|
||||||
else {
|
|
||||||
return Ok(());
|
|
||||||
};
|
|
||||||
|
|
||||||
let Some(host_arch_capabilities) = find_arch_capabilities(host_feature_msrs) else {
|
|
||||||
error!("Unable to find MSR IA32_ARCH_CAPABILITIES, but it is required by the CPU profile");
|
|
||||||
return Err(Error::CpuProfileMissingMsr);
|
|
||||||
};
|
|
||||||
|
|
||||||
if arch_capabilities_compatible(
|
|
||||||
required_arch_capabilities_msr,
|
|
||||||
host_arch_capabilities,
|
|
||||||
"CPU Profile",
|
|
||||||
"Host",
|
|
||||||
) {
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(Error::CpuProfileMsrIncompatibility)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// If `src_val` and `dest_val` are two different possible values of IA32_ARCH_CAPABILITIES, then
|
|
||||||
/// this returns `true` when `src_val` is considered compatible with `dest_val`.
|
|
||||||
///
|
|
||||||
/// If this check fails then programs that work when the value is `src_val`, may possibly
|
|
||||||
/// no longer work if the value is `dest_val`.
|
|
||||||
///
|
|
||||||
/// The `src_id` and `dest_id` parameters are used to identify where `src_val` and `dest_val`
|
|
||||||
/// originate from (e.g. CPU profile, Host) when logging the detected incompatibility.
|
|
||||||
fn arch_capabilities_compatible(src_val: u64, dest_val: u64, src_id: &str, dest_id: &str) -> bool {
|
|
||||||
const RSBA_MASK: u64 = 1 << 2;
|
|
||||||
const RRSBA_MASK: u64 = 1 << 19;
|
|
||||||
// We consider it unsafe to migrate from a machine without RSBA or RRSBA to one that advertises this behavior.
|
|
||||||
// We consider the converse safe: Return stack buffer underflow mitigations can still be applied even if they
|
|
||||||
// may no longer be necessary after migrating. This of course assumes that the destination is capable of applying
|
|
||||||
// said mitigations, but that should be ensured by other CPUID and/or MSR value checks.
|
|
||||||
const SUPERSET_MASK: u64 = RSBA_MASK | RRSBA_MASK;
|
|
||||||
|
|
||||||
// Bits 31 and 33..=61 are (currently) reserved
|
|
||||||
const RESERVED_MASK: u64 = {
|
|
||||||
let bits_0_to_61 = (1_u64 << 62) - 1;
|
|
||||||
let bits_0_to_32 = (1_u64 << 33) - 1;
|
|
||||||
(bits_0_to_61 ^ bits_0_to_32) | (1 << 31)
|
|
||||||
};
|
|
||||||
|
|
||||||
const SUBSET_MASK: u64 = !(SUPERSET_MASK | RESERVED_MASK);
|
|
||||||
|
|
||||||
const MDS_NO_MASK: u64 = 1 << 5;
|
|
||||||
const TAA_NO_MASK: u64 = 1 << 8;
|
|
||||||
const SBDR_SSDP_NO_MASK: u64 = 1 << 13;
|
|
||||||
const FBSDP_NO_MASK: u64 = 1 << 14;
|
|
||||||
const PSDP_NO_MASK: u64 = 1 << 15;
|
|
||||||
const FB_CLEAR_MASK: u64 = 1 << 17;
|
|
||||||
const TOLERATE_MISSING_FB_CLEAR_MASK: u64 =
|
|
||||||
MDS_NO_MASK | TAA_NO_MASK | SBDR_SSDP_NO_MASK | FBSDP_NO_MASK | PSDP_NO_MASK;
|
|
||||||
|
|
||||||
// For safety reasons we will require equality on the reserved bits for now: If/when they become unreserved then we can adjust the checks
|
|
||||||
// accordingly.
|
|
||||||
let reserved_eq_check = {
|
|
||||||
let src_reserved = src_val & RESERVED_MASK;
|
|
||||||
let dest_reserved = dest_val & RESERVED_MASK;
|
|
||||||
if src_reserved == dest_reserved {
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
let only_in_src = src_reserved & (src_reserved ^ dest_reserved);
|
|
||||||
let only_in_dest = dest_reserved & (dest_reserved ^ src_reserved);
|
|
||||||
debug_log_features_only_in(only_in_src, src_id);
|
|
||||||
debug_log_features_only_in(only_in_dest, dest_id);
|
|
||||||
false
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut subset_check = true;
|
|
||||||
if let Err(only_in_src) = check_subset(src_val & SUBSET_MASK, dest_val & SUBSET_MASK) {
|
|
||||||
// If the only bit that is only in source is 17 (FB_CLEAR) and dest_val has
|
|
||||||
// certain mitigation bits set, then src_val is actually compatible with
|
|
||||||
// dest_val. QEMU does in fact always artificially set bit 17 in that case: See
|
|
||||||
// https://github.com/qemu/qemu/blob/v11.0.1/target/i386/kvm/kvm.c#L679-L685
|
|
||||||
//
|
|
||||||
// TODO: Perhaps we should also rather make Hypervisor::get_msr_based_features() adjust bit
|
|
||||||
// 17? With CPU profiles this doesn't seem necessary though.
|
|
||||||
if !(((dest_val & TOLERATE_MISSING_FB_CLEAR_MASK) == TOLERATE_MISSING_FB_CLEAR_MASK)
|
|
||||||
&& (only_in_src == FB_CLEAR_MASK))
|
|
||||||
{
|
|
||||||
subset_check = false;
|
|
||||||
debug_log_features_only_in(only_in_src, src_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let superset_check = {
|
|
||||||
if let Err(only_in_dest) = check_subset(dest_val & SUPERSET_MASK, src_val & SUPERSET_MASK) {
|
|
||||||
debug_log_features_only_in(only_in_dest, dest_id);
|
|
||||||
false
|
|
||||||
} else {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let is_err = !(reserved_eq_check && subset_check && superset_check);
|
|
||||||
|
|
||||||
if is_err {
|
|
||||||
error!(
|
|
||||||
"IA32_ARCH_CAPABILITIES compatibility check failed: {src_id} value={src_val:#x}, {dest_id} value={dest_val:#x}"
|
|
||||||
);
|
|
||||||
|
|
||||||
false
|
|
||||||
} else {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check that no bits are only in `a`.
|
|
||||||
///
|
|
||||||
/// Upon error a bitset is returned with the bits that are only available in
|
|
||||||
/// `a`.
|
|
||||||
fn check_subset(a: u64, b: u64) -> Result<(), u64> {
|
|
||||||
let only_in_a = a & (a ^ b);
|
|
||||||
if only_in_a != 0 {
|
|
||||||
Err(only_in_a)
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn debug_log_features_only_in(mut only_in: u64, id: &str) {
|
|
||||||
while only_in != 0 {
|
|
||||||
// Obtain the lowest set bit
|
|
||||||
let bit_pos = only_in.trailing_zeros();
|
|
||||||
debug!(
|
|
||||||
"IA32_ARCH_CAPABILITIES compatibility check failed: bit={bit_pos} is only set for {id}"
|
|
||||||
);
|
|
||||||
// Unset the lowest set bit
|
|
||||||
only_in &= only_in - 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod unit_tests {
|
|
||||||
use super::arch_capabilities_compatible;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn check_arch_compatibilities_cascade_lake_sapphire_rapids() {
|
|
||||||
// Value of IA32_ARCH_CAPABILITIES on Intel Cascade Lake obtained from KVM (kernel version 6.12.60)
|
|
||||||
let cascade_lake_msr_value: u64 = 0xc0aa0eb;
|
|
||||||
|
|
||||||
// Value of IA32_ARCH_CAPABILITIES on Sapphire Rapids obtained from KVM (kernel version 6.18.33)
|
|
||||||
let sapphire_rapids_msr_value: u64 = 0x400000000c08e1eb;
|
|
||||||
|
|
||||||
// Live migration from Intel Cascade Lake to Sapphire Rapids should work as far as IA32_ARCH_CAPABILITIES
|
|
||||||
// is concerned.
|
|
||||||
// NOTE: The Cascade Lake has the FB_CLEAR bit set (bit 17), but this is not the case for Sapphire Rapids.
|
|
||||||
// This means that the code path for the fallback compatibility check must necessarily get exercised.
|
|
||||||
assert!(arch_capabilities_compatible(
|
|
||||||
cascade_lake_msr_value,
|
|
||||||
sapphire_rapids_msr_value,
|
|
||||||
"Cascade Lake",
|
|
||||||
"Sapphire Rapids",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn check_arch_capabilities_sapphire_rapids_granite_rapids() {
|
|
||||||
// Value of IA32_ARCH_CAPABILITIES on Sapphire Rapids (obtained from KVM with kernel version 6.18.33)
|
|
||||||
let sapphire_rapids_msr_value: u64 = 0x400000000c08e1eb;
|
|
||||||
// Value of IA32_ARCH_CAPABILITIES on Granite Rapids (obtained from KVM with kernel version 6.12.91)
|
|
||||||
// TODO: Consider extracting the values from KVM with the same Linux Kernel versions, but we do not
|
|
||||||
// expect this to change the values of this MSR though.
|
|
||||||
let granite_rapids_msr_value: u64 = 0x400000000d08e1eb;
|
|
||||||
|
|
||||||
// Migration from sapphire rapids to granite rapids without a CPU profile should work
|
|
||||||
assert!(arch_capabilities_compatible(
|
|
||||||
sapphire_rapids_msr_value,
|
|
||||||
granite_rapids_msr_value,
|
|
||||||
"Sapphire Rapids",
|
|
||||||
"Granite Rapids",
|
|
||||||
));
|
|
||||||
|
|
||||||
// On the other hand it should NOT be possible to migrate from the
|
|
||||||
// Granite Rapids machine (without applying a CPU profile) to the
|
|
||||||
// Sapphire Rapids, because PRBS_NO (IA32_ARCH_CAPABILITIES[24]) is set
|
|
||||||
// on the former, but not the latter.
|
|
||||||
assert!(!arch_capabilities_compatible(
|
|
||||||
granite_rapids_msr_value,
|
|
||||||
sapphire_rapids_msr_value,
|
|
||||||
"Granite Rapids",
|
|
||||||
"Sapphire Rapids",
|
|
||||||
));
|
|
||||||
|
|
||||||
// The value extracted from the Sapphire Rapids machine, but with the
|
|
||||||
// TSX CTRL bit unset. All CPU profiles apart from host will adapt
|
|
||||||
// CPUID to indicate that TSX is not available because that feature is
|
|
||||||
// riddled with CVEs and we expect operators to disable it globally (at
|
|
||||||
// the kernel level).
|
|
||||||
let restricted_sapphire_rapids_msr_value: u64 = 0x400000000c08e16b;
|
|
||||||
|
|
||||||
// It must be possible to apply the Sapphire Rapids CPU profile on
|
|
||||||
// the host that the profile is based on
|
|
||||||
assert!(arch_capabilities_compatible(
|
|
||||||
restricted_sapphire_rapids_msr_value,
|
|
||||||
sapphire_rapids_msr_value,
|
|
||||||
"Sapphire Rapids profile",
|
|
||||||
"Sapphire Rapids host",
|
|
||||||
));
|
|
||||||
|
|
||||||
// It should also be possible to apply the Sapphire Rapids profile on
|
|
||||||
// the Granite Rapids machine
|
|
||||||
assert!(arch_capabilities_compatible(
|
|
||||||
restricted_sapphire_rapids_msr_value,
|
|
||||||
granite_rapids_msr_value,
|
|
||||||
"Sapphire Rapids profile",
|
|
||||||
"Granite Rapids host",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check that if reserved bits are different then we get an error.
|
|
||||||
//
|
|
||||||
// This test is somewhat contrived and simplistic. Reserved bits in
|
|
||||||
// IA32_ARCH_CAPABILITIES will be 0 in practice. We do however want to be
|
|
||||||
// safe if/when bits are no longer reserved on future hardware generations,
|
|
||||||
// hence we add a simple test as a reality check that differing reserved
|
|
||||||
// bits is not allowed.
|
|
||||||
#[test]
|
|
||||||
fn check_arch_capabilities_compatibility_reserved_bits() {
|
|
||||||
const RESERVED_ONE: u64 = 1 << 31;
|
|
||||||
const RESERVED_TWO: u64 = 1 << 42;
|
|
||||||
const RESERVED_THREE: u64 = 1 << 61;
|
|
||||||
let sapphire_rapids_msr_value: u64 = 0x400000000c08e1eb;
|
|
||||||
let with_reserved_bits = sapphire_rapids_msr_value | RESERVED_ONE | RESERVED_THREE;
|
|
||||||
let with_other_reserved_bits = sapphire_rapids_msr_value | RESERVED_TWO;
|
|
||||||
|
|
||||||
assert!(!arch_capabilities_compatible(
|
|
||||||
with_reserved_bits,
|
|
||||||
with_other_reserved_bits,
|
|
||||||
"Reserved 1",
|
|
||||||
"Reserved 2",
|
|
||||||
));
|
|
||||||
|
|
||||||
assert!(!arch_capabilities_compatible(
|
|
||||||
with_other_reserved_bits,
|
|
||||||
with_reserved_bits,
|
|
||||||
"Reserved 2",
|
|
||||||
"Reserved 1",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,190 +0,0 @@
|
|||||||
// Copyright © 2026 Cyberus Technology GmbH
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
//
|
|
||||||
|
|
||||||
//! This module contains types associated with adjusting CPUID entries according
|
|
||||||
//! to a selected CPU profile.
|
|
||||||
|
|
||||||
use std::ops::RangeInclusive;
|
|
||||||
|
|
||||||
use hypervisor::arch::x86::CpuIdEntry;
|
|
||||||
use log::error;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
use crate::x86_64::{CpuidReg, deserialize_u32_hex, serialize_u32_hex};
|
|
||||||
|
|
||||||
/// Parameters for inspecting CPUID definitions.
|
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
|
|
||||||
pub struct CpuidParameters {
|
|
||||||
/// The leaf (EAX) parameter used with the CPUID instruction
|
|
||||||
#[serde(
|
|
||||||
serialize_with = "serialize_u32_hex",
|
|
||||||
deserialize_with = "deserialize_u32_hex"
|
|
||||||
)]
|
|
||||||
pub leaf: u32,
|
|
||||||
/// The sub-leaf (ECX) parameter used with the CPUID instruction
|
|
||||||
#[serde(
|
|
||||||
serialize_with = "serialize_range_hex",
|
|
||||||
deserialize_with = "deserialize_range_hex"
|
|
||||||
)]
|
|
||||||
pub sub_leaf: RangeInclusive<u32>,
|
|
||||||
/// The register we are interested in inspecting which gets filled by the CPUID instruction
|
|
||||||
pub register: CpuidReg,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only used for (de-)serialization
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
struct ProvisionalRangeInclusive {
|
|
||||||
#[serde(
|
|
||||||
serialize_with = "serialize_u32_hex",
|
|
||||||
deserialize_with = "deserialize_u32_hex"
|
|
||||||
)]
|
|
||||||
start: u32,
|
|
||||||
#[serde(
|
|
||||||
serialize_with = "serialize_u32_hex",
|
|
||||||
deserialize_with = "deserialize_u32_hex"
|
|
||||||
)]
|
|
||||||
end: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn serialize_range_hex<S: serde::Serializer>(
|
|
||||||
input: &RangeInclusive<u32>,
|
|
||||||
serializer: S,
|
|
||||||
) -> Result<S::Ok, S::Error> {
|
|
||||||
let provisional = ProvisionalRangeInclusive {
|
|
||||||
start: *input.start(),
|
|
||||||
end: *input.end(),
|
|
||||||
};
|
|
||||||
provisional.serialize(serializer)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deserialize_range_hex<'de, D: serde::Deserializer<'de>>(
|
|
||||||
deserializer: D,
|
|
||||||
) -> Result<RangeInclusive<u32>, D::Error> {
|
|
||||||
let ProvisionalRangeInclusive { start, end } =
|
|
||||||
ProvisionalRangeInclusive::deserialize(deserializer)?;
|
|
||||||
Ok(start..=end)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Used for adjusting an entire cpuid output register (EAX, EBX, ECX or EDX).
|
|
||||||
///
|
|
||||||
/// Instances of this struct typically adjust CPUID according to the following
|
|
||||||
/// formula: `cpuid_reg_value = (self.mask & cpuid_reg_value) | self.replacements`.
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
|
||||||
pub struct CpuidOutputRegisterAdjustments {
|
|
||||||
/// Packs values to be placed into the given CPUID output register.
|
|
||||||
#[serde(
|
|
||||||
serialize_with = "serialize_u32_hex",
|
|
||||||
deserialize_with = "deserialize_u32_hex"
|
|
||||||
)]
|
|
||||||
pub replacements: u32,
|
|
||||||
/// Used to zero out the area `replacements` occupy. This mask is not necessarily !replacements, as replacements
|
|
||||||
/// may pack values of different types that occupy varying ranges of bits.
|
|
||||||
///
|
|
||||||
/// Bit ranges within a CPUID output register that are **not** supposed to be replaced/overwritten should be set in
|
|
||||||
/// this mask.
|
|
||||||
#[serde(
|
|
||||||
serialize_with = "serialize_u32_hex",
|
|
||||||
deserialize_with = "deserialize_u32_hex"
|
|
||||||
)]
|
|
||||||
pub mask: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Error type indicating that expected CPUID entries could not be found.
|
|
||||||
///
|
|
||||||
/// This type does not record which entries could not be found as we do not
|
|
||||||
/// expect this to be actionable at runtime. Instead we encourage logging such
|
|
||||||
/// violations when and where they are detected.
|
|
||||||
#[derive(Debug, Error)]
|
|
||||||
#[error("Required CPUID entries not found")]
|
|
||||||
pub struct MissingCpuidEntriesError;
|
|
||||||
|
|
||||||
impl CpuidOutputRegisterAdjustments {
|
|
||||||
/// Adjust the given `cpuid_output_register` by retaining and replacing values according to `self`.
|
|
||||||
fn adjust(self, cpuid_output_register: &mut u32) {
|
|
||||||
*cpuid_output_register &= self.mask;
|
|
||||||
*cpuid_output_register |= self.replacements;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Adjust `cpuid` according to the given `adjustments`.
|
|
||||||
///
|
|
||||||
/// The returned vector of cpuid entries covers the same CPUID (sub-) leaves as the given `cpuid` input,
|
|
||||||
/// but values without matching [`CpuidParameters`] are zeroed out.
|
|
||||||
///
|
|
||||||
/// # Errors
|
|
||||||
///
|
|
||||||
/// An error is returned if an entry cannot be found for an adjustment describing non-zero replacements.
|
|
||||||
pub(super) fn adjust_cpuid_entries(
|
|
||||||
mut cpuid: Vec<CpuIdEntry>,
|
|
||||||
adjustments: &[(CpuidParameters, Self)],
|
|
||||||
) -> Result<Vec<CpuIdEntry>, MissingCpuidEntriesError> {
|
|
||||||
for entry in &mut cpuid {
|
|
||||||
for (reg, reg_value) in [
|
|
||||||
(CpuidReg::EAX, &mut entry.eax),
|
|
||||||
(CpuidReg::EBX, &mut entry.ebx),
|
|
||||||
(CpuidReg::ECX, &mut entry.ecx),
|
|
||||||
(CpuidReg::EDX, &mut entry.edx),
|
|
||||||
] {
|
|
||||||
// Lookup the adjustment corresponding to the entry's function/leaf and index/sub-leaf for each of the register.
|
|
||||||
let register_adjustments: Option<CpuidOutputRegisterAdjustments> =
|
|
||||||
adjustments.iter().find_map(|(param, adjustment)| {
|
|
||||||
((param.leaf == entry.function)
|
|
||||||
&& param.sub_leaf.contains(&entry.index)
|
|
||||||
&& (param.register == reg))
|
|
||||||
.then_some(*adjustment)
|
|
||||||
});
|
|
||||||
|
|
||||||
match register_adjustments {
|
|
||||||
Some(adjustment) => adjustment.adjust(reg_value),
|
|
||||||
None => {
|
|
||||||
// No matching cpuid parameters were found. We thus set the value of the register to 0.
|
|
||||||
*reg_value = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Self::expected_entries_found(&cpuid, adjustments)?;
|
|
||||||
Ok(cpuid)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check that we found every value that was supposed to be replaced with something else than 0
|
|
||||||
///
|
|
||||||
/// IMPORTANT: This function assumes that the given `cpuid` has already been adjusted with the
|
|
||||||
/// provided `adjustments`.
|
|
||||||
fn expected_entries_found(
|
|
||||||
cpuid: &[CpuIdEntry],
|
|
||||||
adjustments: &[(CpuidParameters, Self)],
|
|
||||||
) -> Result<(), MissingCpuidEntriesError> {
|
|
||||||
let mut missing_entry = false;
|
|
||||||
|
|
||||||
for (param, adjustment) in adjustments {
|
|
||||||
if adjustment.replacements == 0 {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if !cpuid.iter().any(|entry| {
|
|
||||||
(entry.function == param.leaf) && (param.sub_leaf.contains(&entry.index))
|
|
||||||
}) {
|
|
||||||
error!(
|
|
||||||
"cannot adjust CPU profile. No entry found matching the required parameters: {param:?}"
|
|
||||||
);
|
|
||||||
missing_entry = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if missing_entry {
|
|
||||||
Err(MissingCpuidEntriesError)
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Data describing CPUID adjustments related to a CPU Profile.
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct CpuidProfileData {
|
|
||||||
/// Adjustments necessary to become compatible with the desired target.
|
|
||||||
pub adjustments: Vec<(CpuidParameters, CpuidOutputRegisterAdjustments)>,
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,107 +0,0 @@
|
|||||||
// Copyright © 2026 Cyberus Technology GmbH
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
//
|
|
||||||
|
|
||||||
use hypervisor::arch::x86::MsrEntry;
|
|
||||||
use log::{debug, error};
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
use crate::x86_64::Error;
|
|
||||||
use crate::x86_64::helpers::{
|
|
||||||
deserialize_u32_hex, deserialize_u64_hex, serialize_u32_hex, serialize_u64_hex,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// The register address of an MSR
|
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)]
|
|
||||||
pub struct RegisterAddress(
|
|
||||||
#[serde(
|
|
||||||
serialize_with = "serialize_u32_hex",
|
|
||||||
deserialize_with = "deserialize_u32_hex"
|
|
||||||
)]
|
|
||||||
pub u32,
|
|
||||||
);
|
|
||||||
|
|
||||||
/// Used to adjust the value of a Feature MSR.
|
|
||||||
///
|
|
||||||
/// Instances of this struct typically adjust MSR values according to the
|
|
||||||
/// following formula: `msr_value = (self.mask & msr_value) | self.replacements`.
|
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
|
|
||||||
pub struct FeatureMsrAdjustment {
|
|
||||||
/// Packs values to be placed into the given feature MSR value.
|
|
||||||
#[serde(
|
|
||||||
serialize_with = "serialize_u64_hex",
|
|
||||||
deserialize_with = "deserialize_u64_hex"
|
|
||||||
)]
|
|
||||||
pub replacements: u64,
|
|
||||||
|
|
||||||
/// Used to zero out the area `replacements` occupy. This mask is not necessarily !replacements, as replacements
|
|
||||||
/// may pack values of different types that occupy varying ranges of bits.
|
|
||||||
///
|
|
||||||
/// Bit ranges within a feature MSR value that are **not** supposed to be replaced/overwritten should be set in
|
|
||||||
/// this mask.
|
|
||||||
#[serde(
|
|
||||||
serialize_with = "serialize_u64_hex",
|
|
||||||
deserialize_with = "deserialize_u64_hex"
|
|
||||||
)]
|
|
||||||
pub mask: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FeatureMsrAdjustment {
|
|
||||||
/// Adjusts the given `feature_msrs` according to `adjustments`.
|
|
||||||
///
|
|
||||||
/// An error is returned if there exists an MSR register address in
|
|
||||||
/// `adjustments` without a matching entry in `feature_msrs`.
|
|
||||||
pub(super) fn adjust_feature_msrs(
|
|
||||||
feature_msrs: &[MsrEntry],
|
|
||||||
adjustments: &[(RegisterAddress, FeatureMsrAdjustment)],
|
|
||||||
) -> Result<Vec<MsrEntry>, Error> {
|
|
||||||
let mut missing_msr = false;
|
|
||||||
let mut output_feature_msrs = Vec::with_capacity(adjustments.len());
|
|
||||||
for (reg_address, adjustment) in adjustments {
|
|
||||||
let Some(entry) = feature_msrs
|
|
||||||
.iter()
|
|
||||||
.find(|entry| entry.index == reg_address.0)
|
|
||||||
else {
|
|
||||||
missing_msr = true;
|
|
||||||
error!(
|
|
||||||
"Did not find feature based MSR entry for MSR {:#x}",
|
|
||||||
reg_address.0
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut entry = *entry;
|
|
||||||
let data = entry.data;
|
|
||||||
entry.data = (adjustment.mask & data) | adjustment.replacements;
|
|
||||||
|
|
||||||
debug!(
|
|
||||||
"Prepared adjusted MSR feature: register address={:#x} value={:#x}, previous value={data:#x}",
|
|
||||||
entry.index, entry.data
|
|
||||||
);
|
|
||||||
output_feature_msrs.push(entry);
|
|
||||||
}
|
|
||||||
|
|
||||||
if missing_msr {
|
|
||||||
Err(Error::CpuProfileMissingMsr)
|
|
||||||
} else {
|
|
||||||
Ok(output_feature_msrs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Data describing MSR adjustments related to a CPU profile.
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
|
|
||||||
pub struct MsrProfileData {
|
|
||||||
/// Describes feature MSR adjustments necessary to become compatible with
|
|
||||||
/// the desired target.
|
|
||||||
pub adjustments: Vec<(RegisterAddress, FeatureMsrAdjustment)>,
|
|
||||||
/// List of the MSRs that the CPU profile requires.
|
|
||||||
///
|
|
||||||
/// When applying a CPU profile then the union of the sets of MSRs obtained
|
|
||||||
/// from `Hypervisor::get_feature_msrs` and `Hypervisor::get_msr_index_list`
|
|
||||||
/// must necessarily contain all MSRs listed here. Otherwise the host is
|
|
||||||
/// considered incompatible with the CPU profile. Exceptions are made for
|
|
||||||
/// missing Hyper-V MSRs when `kvm_hyperv=off`.
|
|
||||||
pub required_msrs: Vec<RegisterAddress>,
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
// Copyright © 2020, Oracle and/or its affiliates.
|
|
||||||
//
|
|
||||||
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
//
|
//
|
||||||
@@ -8,51 +6,26 @@
|
|||||||
// found in the LICENSE-BSD-3-Clause file.
|
// found in the LICENSE-BSD-3-Clause file.
|
||||||
|
|
||||||
// For GDT details see arch/x86/include/asm/segment.h
|
// For GDT details see arch/x86/include/asm/segment.h
|
||||||
use crate::arch::x86::SegmentRegister;
|
|
||||||
|
use kvm_bindings::kvm_segment;
|
||||||
|
|
||||||
/// Constructor for a conventional segment GDT (or LDT) entry. Derived from the kernel's segment.h.
|
/// Constructor for a conventional segment GDT (or LDT) entry. Derived from the kernel's segment.h.
|
||||||
pub fn gdt_entry(flags: u16, base: u32, limit: u32) -> u64 {
|
pub fn gdt_entry(flags: u16, base: u32, limit: u32) -> u64 {
|
||||||
(((base as u64) & 0xff000000u64) << (56 - 24))
|
((((base as u64) & 0xff000000u64) << (56 - 24))
|
||||||
| (((flags as u64) & 0x0000f0ffu64) << 40)
|
| (((flags as u64) & 0x0000f0ffu64) << 40)
|
||||||
| (((limit as u64) & 0x000f0000u64) << (48 - 16))
|
| (((limit as u64) & 0x000f0000u64) << (48 - 16))
|
||||||
| (((base as u64) & 0x00ffffffu64) << 16)
|
| (((base as u64) & 0x00ffffffu64) << 16)
|
||||||
| ((limit as u64) & 0x0000ffffu64)
|
| ((limit as u64) & 0x0000ffffu64))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_base(entry: u64) -> u64 {
|
fn get_base(entry: u64) -> u64 {
|
||||||
(((entry) & 0xFF00000000000000) >> 32)
|
((((entry) & 0xFF00000000000000) >> 32)
|
||||||
| (((entry) & 0x000000FF00000000) >> 16)
|
| (((entry) & 0x000000FF00000000) >> 16)
|
||||||
| (((entry) & 0x00000000FFFF0000) >> 16)
|
| (((entry) & 0x00000000FFFF0000) >> 16))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract the segment limit from the GDT segment descriptor.
|
|
||||||
//
|
|
||||||
// In a segment descriptor, the limit field is 20 bits, so it can directly describe
|
|
||||||
// a range from 0 to 0xFFFFF (1MByte). When G flag is set (4-KByte page granularity) it
|
|
||||||
// scales the value in the limit field by a factor of 2^12 (4Kbytes), making the effective
|
|
||||||
// limit range from 0xFFF (4 KBytes) to 0xFFFF_FFFF (4 GBytes).
|
|
||||||
//
|
|
||||||
// However, the limit field in the VMCS definition is a 32 bit field, and the limit value is not
|
|
||||||
// automatically scaled using the G flag. This means that for a desired range of 4GB for a
|
|
||||||
// given segment, its limit must be specified as 0xFFFF_FFFF. Therefore the method of obtaining
|
|
||||||
// the limit from the GDT entry is not sufficient, since it only provides 20 bits when 32 bits
|
|
||||||
// are necessary. Fortunately, we can check if the G flag is set when extracting the limit since
|
|
||||||
// the full GDT entry is passed as an argument, and perform the scaling of the limit value to
|
|
||||||
// return the full 32 bit value.
|
|
||||||
//
|
|
||||||
// The scaling mentioned above is required when using PVH boot, since the guest boots in protected
|
|
||||||
// (32-bit) mode and must be able to access the entire 32-bit address space. It does not cause issues
|
|
||||||
// for the case of direct boot to 64-bit (long) mode, since in 64-bit mode the processor does not
|
|
||||||
// perform runtime limit checking on code or data segments.
|
|
||||||
fn get_limit(entry: u64) -> u32 {
|
fn get_limit(entry: u64) -> u32 {
|
||||||
let limit: u32 =
|
((((entry) & 0x000F000000000000) >> 32) | ((entry) & 0x000000000000FFFF)) as u32
|
||||||
((((entry) & 0x000F000000000000) >> 32) | ((entry) & 0x000000000000FFFF)) as u32;
|
|
||||||
|
|
||||||
// Perform manual limit scaling if G flag is set
|
|
||||||
match get_g(entry) {
|
|
||||||
0 => limit,
|
|
||||||
_ => (limit << 12) | 0xFFF, // G flag is either 0 or 1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_g(entry: u64) -> u8 {
|
fn get_g(entry: u64) -> u8 {
|
||||||
@@ -87,14 +60,14 @@ fn get_type(entry: u64) -> u8 {
|
|||||||
((entry & 0x00000F0000000000) >> 40) as u8
|
((entry & 0x00000F0000000000) >> 40) as u8
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Automatically build the struct for SET_SREGS from the kernel bit fields.
|
/// Automatically build the kvm struct for SET_SREGS from the kernel bit fields.
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
///
|
///
|
||||||
/// * `entry` - The gdt entry.
|
/// * `entry` - The gdt entry.
|
||||||
/// * `table_index` - Index of the entry in the gdt table.
|
/// * `table_index` - Index of the entry in the gdt table.
|
||||||
pub fn segment_from_gdt(entry: u64, table_index: u8) -> SegmentRegister {
|
pub fn kvm_segment_from_gdt(entry: u64, table_index: u8) -> kvm_segment {
|
||||||
SegmentRegister {
|
kvm_segment {
|
||||||
base: get_base(entry),
|
base: get_base(entry),
|
||||||
limit: get_limit(entry),
|
limit: get_limit(entry),
|
||||||
selector: (table_index * 8) as u16,
|
selector: (table_index * 8) as u16,
|
||||||
@@ -106,6 +79,7 @@ pub fn segment_from_gdt(entry: u64, table_index: u8) -> SegmentRegister {
|
|||||||
l: get_l(entry),
|
l: get_l(entry),
|
||||||
g: get_g(entry),
|
g: get_g(entry),
|
||||||
avl: get_avl(entry),
|
avl: get_avl(entry),
|
||||||
|
padding: 0,
|
||||||
unusable: match get_p(entry) {
|
unusable: match get_p(entry) {
|
||||||
0 => 1,
|
0 => 1,
|
||||||
_ => 0,
|
_ => 0,
|
||||||
@@ -114,13 +88,13 @@ pub fn segment_from_gdt(entry: u64, table_index: u8) -> SegmentRegister {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod unit_tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn field_parse() {
|
fn field_parse() {
|
||||||
let gdt = gdt_entry(0xA09B, 0x100000, 0xfffff);
|
let gdt = gdt_entry(0xA09B, 0x100000, 0xfffff);
|
||||||
let seg = segment_from_gdt(gdt, 0);
|
let seg = kvm_segment_from_gdt(gdt, 0);
|
||||||
// 0xA09B
|
// 0xA09B
|
||||||
// 'A'
|
// 'A'
|
||||||
assert_eq!(0x1, seg.g);
|
assert_eq!(0x1, seg.g);
|
||||||
@@ -135,7 +109,7 @@ mod unit_tests {
|
|||||||
assert_eq!(0xB, seg.type_);
|
assert_eq!(0xB, seg.type_);
|
||||||
// base and limit
|
// base and limit
|
||||||
assert_eq!(0x100000, seg.base);
|
assert_eq!(0x100000, seg.base);
|
||||||
assert_eq!(0xffffffff, seg.limit);
|
assert_eq!(0xfffff, seg.limit);
|
||||||
assert_eq!(0x0, seg.unusable);
|
assert_eq!(0x0, seg.unusable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,129 +0,0 @@
|
|||||||
// Copyright © 2026 Cyberus Technology GmbH
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
//
|
|
||||||
|
|
||||||
use serde::{Deserialize, Deserializer, Serializer, de};
|
|
||||||
|
|
||||||
/// Serializes the given `input` as a hex string (starting with "0x").
|
|
||||||
///
|
|
||||||
/// As an example if `input:=5` then this function will feed the given
|
|
||||||
/// `serializer` the string "0x5".
|
|
||||||
pub(crate) fn serialize_u32_hex<S: Serializer>(
|
|
||||||
input: &u32,
|
|
||||||
serializer: S,
|
|
||||||
) -> Result<S::Ok, S::Error> {
|
|
||||||
serializer.serialize_str(&format!("{input:#x}"))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Deserializes a u32 from a hex string representation.
|
|
||||||
pub(crate) fn deserialize_u32_hex<'de, D: Deserializer<'de>>(
|
|
||||||
deserializer: D,
|
|
||||||
) -> Result<u32, D::Error> {
|
|
||||||
let hex: &str = <&str>::deserialize(deserializer)?;
|
|
||||||
u32::from_str_radix(hex.strip_prefix("0x").unwrap_or(""), 16).map_err(|_| {
|
|
||||||
<D::Error as de::Error>::custom(format!("{hex} is not a hex encoded 32 bit integer"))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 64-bit version of `serialize_u32_hex`
|
|
||||||
pub(crate) fn serialize_u64_hex<S: Serializer>(
|
|
||||||
input: &u64,
|
|
||||||
serializer: S,
|
|
||||||
) -> Result<S::Ok, S::Error> {
|
|
||||||
serializer.serialize_str(&format!("{input:#x}"))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 64-bit version of `deserialize_u32_hex`
|
|
||||||
pub(crate) fn deserialize_u64_hex<'de, D: Deserializer<'de>>(
|
|
||||||
deserializer: D,
|
|
||||||
) -> Result<u64, D::Error> {
|
|
||||||
let hex: &str = <&str>::deserialize(deserializer)?;
|
|
||||||
u64::from_str_radix(hex.strip_prefix("0x").unwrap_or(""), 16).map_err(|_| {
|
|
||||||
<D::Error as de::Error>::custom(format!("{hex} is not a hex encoded 64 bit integer"))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod unit_tests {
|
|
||||||
use std::fmt::Debug;
|
|
||||||
|
|
||||||
use proptest::prelude::*;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
|
|
||||||
struct TestStruct {
|
|
||||||
#[serde(
|
|
||||||
serialize_with = "serialize_u32_hex",
|
|
||||||
deserialize_with = "deserialize_u32_hex"
|
|
||||||
)]
|
|
||||||
foo: u32,
|
|
||||||
#[serde(
|
|
||||||
serialize_with = "serialize_u32_hex",
|
|
||||||
deserialize_with = "deserialize_u32_hex"
|
|
||||||
)]
|
|
||||||
bar: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
|
|
||||||
struct TestStruct64 {
|
|
||||||
#[serde(
|
|
||||||
serialize_with = "serialize_u64_hex",
|
|
||||||
deserialize_with = "deserialize_u64_hex"
|
|
||||||
)]
|
|
||||||
foo: u64,
|
|
||||||
#[serde(
|
|
||||||
serialize_with = "serialize_u64_hex",
|
|
||||||
deserialize_with = "deserialize_u64_hex"
|
|
||||||
)]
|
|
||||||
bar: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check that our hex serializers satisfy the two following invariants
|
|
||||||
// 1. Serialization followed by deserialization is the identity.
|
|
||||||
// 2. Values of type u32/u64 are serialized to strings starting with "0x" and then
|
|
||||||
// a sub-string where all characters are ascii hex digits (with the letters [a-f] always in lowercase).
|
|
||||||
fn test_hex_serialization<T>(t: T) -> Result<(), TestCaseError>
|
|
||||||
where
|
|
||||||
T: Serialize + Debug + Eq + Copy,
|
|
||||||
for<'de> T: Deserialize<'de>,
|
|
||||||
{
|
|
||||||
let t_string = serde_json::to_string(&t).unwrap();
|
|
||||||
let t_deserialized = serde_json::from_str(&t_string).unwrap();
|
|
||||||
prop_assert_eq!(t, t_deserialized);
|
|
||||||
|
|
||||||
let t_json = serde_json::to_value(t).unwrap();
|
|
||||||
|
|
||||||
let check_str_invariants = |value: &str| {
|
|
||||||
prop_assert!(value.starts_with("0x"));
|
|
||||||
prop_assert!(value.as_bytes()[2..].iter().all(u8::is_ascii_hexdigit));
|
|
||||||
prop_assert!(!value.as_bytes()[2..].iter().any(u8::is_ascii_uppercase));
|
|
||||||
Ok(())
|
|
||||||
};
|
|
||||||
|
|
||||||
let foo_str = t_json.get("foo").unwrap().as_str().unwrap();
|
|
||||||
let bar_str = t_json.get("bar").unwrap().as_str().unwrap();
|
|
||||||
|
|
||||||
check_str_invariants(foo_str)?;
|
|
||||||
check_str_invariants(bar_str)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
proptest! {
|
|
||||||
#[test]
|
|
||||||
fn hex_serialization_works_32(foo in any::<u32>(), bar in any::<u32>()) {
|
|
||||||
let t = TestStruct { foo , bar };
|
|
||||||
test_hex_serialization(t)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
proptest! {
|
|
||||||
#[test]
|
|
||||||
fn hex_serialization_works_64(foo in any::<u64>(), bar in any::<u64>()) {
|
|
||||||
let t = TestStruct64 { foo , bar };
|
|
||||||
test_hex_serialization(t)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,127 +0,0 @@
|
|||||||
// Copyright © 2026 Cyberus Technology GmbH
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
//
|
|
||||||
|
|
||||||
const HV_X64_MSR_GUEST_OS_ID: u32 = 0x40000000;
|
|
||||||
const HV_X64_MSR_HYPERCALL: u32 = 0x40000001;
|
|
||||||
const HV_X64_MSR_VP_INDEX: u32 = 0x40000002;
|
|
||||||
const HV_X64_MSR_RESET: u32 = 0x40000003;
|
|
||||||
const HV_X64_MSR_VP_RUNTIME: u32 = 0x40000010;
|
|
||||||
const HV_X64_MSR_TIME_REF_COUNT: u32 = 0x40000020;
|
|
||||||
const HV_X64_MSR_REFERENCE_TSC: u32 = 0x40000021;
|
|
||||||
const HV_X64_MSR_TSC_FREQUENCY: u32 = 0x40000022;
|
|
||||||
const HV_X64_MSR_APIC_FREQUENCY: u32 = 0x40000023;
|
|
||||||
const HV_X64_MSR_EOI: u32 = 0x40000070;
|
|
||||||
const HV_X64_MSR_ICR: u32 = 0x40000071;
|
|
||||||
const HV_X64_MSR_TPR: u32 = 0x40000072;
|
|
||||||
const HV_X64_MSR_VP_ASSIST_PAGE: u32 = 0x40000073;
|
|
||||||
const HV_X64_MSR_SCONTROL: u32 = 0x40000080;
|
|
||||||
const HV_X64_MSR_SVERSION: u32 = 0x40000081;
|
|
||||||
const HV_X64_MSR_SIEFP: u32 = 0x40000082;
|
|
||||||
const HV_X64_MSR_SIMP: u32 = 0x40000083;
|
|
||||||
const HV_X64_MSR_EOM: u32 = 0x40000084;
|
|
||||||
const HV_X64_MSR_SINT0: u32 = 0x40000090;
|
|
||||||
const HV_X64_MSR_SINT1: u32 = 0x40000091;
|
|
||||||
const HV_X64_MSR_SINT2: u32 = 0x40000092;
|
|
||||||
const HV_X64_MSR_SINT3: u32 = 0x40000093;
|
|
||||||
const HV_X64_MSR_SINT4: u32 = 0x40000094;
|
|
||||||
const HV_X64_MSR_SINT5: u32 = 0x40000095;
|
|
||||||
const HV_X64_MSR_SINT6: u32 = 0x40000096;
|
|
||||||
const HV_X64_MSR_SINT7: u32 = 0x40000097;
|
|
||||||
const HV_X64_MSR_SINT8: u32 = 0x40000098;
|
|
||||||
const HV_X64_MSR_SINT9: u32 = 0x40000099;
|
|
||||||
const HV_X64_MSR_SINT10: u32 = 0x4000009A;
|
|
||||||
const HV_X64_MSR_SINT11: u32 = 0x4000009B;
|
|
||||||
const HV_X64_MSR_SINT12: u32 = 0x4000009C;
|
|
||||||
const HV_X64_MSR_SINT13: u32 = 0x4000009D;
|
|
||||||
const HV_X64_MSR_SINT14: u32 = 0x4000009E;
|
|
||||||
const HV_X64_MSR_SINT15: u32 = 0x4000009F;
|
|
||||||
const HV_X64_MSR_STIMER0_CONFIG: u32 = 0x400000B0;
|
|
||||||
const HV_X64_MSR_STIMER0_COUNT: u32 = 0x400000B1;
|
|
||||||
const HV_X64_MSR_STIMER1_CONFIG: u32 = 0x400000B2;
|
|
||||||
const HV_X64_MSR_STIMER1_COUNT: u32 = 0x400000B3;
|
|
||||||
const HV_X64_MSR_STIMER2_CONFIG: u32 = 0x400000B4;
|
|
||||||
const HV_X64_MSR_STIMER2_COUNT: u32 = 0x400000B5;
|
|
||||||
const HV_X64_MSR_STIMER3_CONFIG: u32 = 0x400000B6;
|
|
||||||
const HV_X64_MSR_STIMER3_COUNT: u32 = 0x400000B7;
|
|
||||||
const HV_X64_MSR_GUEST_IDLE: u32 = 0x400000F0;
|
|
||||||
const HV_X64_MSR_CRASH_P0: u32 = 0x40000100;
|
|
||||||
const HV_X64_MSR_CRASH_P1: u32 = 0x40000101;
|
|
||||||
const HV_X64_MSR_CRASH_P2: u32 = 0x40000102;
|
|
||||||
const HV_X64_MSR_CRASH_P3: u32 = 0x40000103;
|
|
||||||
const HV_X64_MSR_CRASH_P4: u32 = 0x40000104;
|
|
||||||
const HV_X64_MSR_CRASH_CTL: u32 = 0x40000105;
|
|
||||||
const HV_X64_MSR_REENLIGHTENMENT_CONTROL: u32 = 0x40000106;
|
|
||||||
const HV_X64_MSR_TSC_EMULATION_CONTROL: u32 = 0x40000107;
|
|
||||||
const HV_X64_MSR_TSC_EMULATION_STATUS: u32 = 0x40000108;
|
|
||||||
const HV_X64_MSR_TSC_INVARIANT_CONTROL: u32 = 0x40000118;
|
|
||||||
const HV_X64_MSR_SYNDBG_CONTROL: u32 = 0x400000F1;
|
|
||||||
const HV_X64_MSR_SYNDBG_STATUS: u32 = 0x400000F2;
|
|
||||||
const HV_X64_MSR_SYNDBG_SEND_BUFFER: u32 = 0x400000F3;
|
|
||||||
const HV_X64_MSR_SYNDBG_RECV_BUFFER: u32 = 0x400000F4;
|
|
||||||
const HV_X64_MSR_SYNDBG_PENDING_BUFFER: u32 = 0x400000F5;
|
|
||||||
const HV_X64_MSR_SYNDBG_OPTIONS: u32 = 0x400000FF;
|
|
||||||
|
|
||||||
// All Hyper-V MSRs extracted from https://elixir.bootlin.com/linux/v7.1.1/source/tools/testing/selftests/kvm/include/x86/hyperv.h#L23
|
|
||||||
pub const HYPERV_MSRS: [u32; 59] = [
|
|
||||||
HV_X64_MSR_GUEST_OS_ID,
|
|
||||||
HV_X64_MSR_HYPERCALL,
|
|
||||||
HV_X64_MSR_VP_INDEX,
|
|
||||||
HV_X64_MSR_RESET,
|
|
||||||
HV_X64_MSR_VP_RUNTIME,
|
|
||||||
HV_X64_MSR_TIME_REF_COUNT,
|
|
||||||
HV_X64_MSR_REFERENCE_TSC,
|
|
||||||
HV_X64_MSR_TSC_FREQUENCY,
|
|
||||||
HV_X64_MSR_APIC_FREQUENCY,
|
|
||||||
HV_X64_MSR_EOI,
|
|
||||||
HV_X64_MSR_ICR,
|
|
||||||
HV_X64_MSR_TPR,
|
|
||||||
HV_X64_MSR_VP_ASSIST_PAGE,
|
|
||||||
HV_X64_MSR_SCONTROL,
|
|
||||||
HV_X64_MSR_SVERSION,
|
|
||||||
HV_X64_MSR_SIEFP,
|
|
||||||
HV_X64_MSR_SIMP,
|
|
||||||
HV_X64_MSR_EOM,
|
|
||||||
HV_X64_MSR_SINT0,
|
|
||||||
HV_X64_MSR_SINT1,
|
|
||||||
HV_X64_MSR_SINT2,
|
|
||||||
HV_X64_MSR_SINT3,
|
|
||||||
HV_X64_MSR_SINT4,
|
|
||||||
HV_X64_MSR_SINT5,
|
|
||||||
HV_X64_MSR_SINT6,
|
|
||||||
HV_X64_MSR_SINT7,
|
|
||||||
HV_X64_MSR_SINT8,
|
|
||||||
HV_X64_MSR_SINT9,
|
|
||||||
HV_X64_MSR_SINT10,
|
|
||||||
HV_X64_MSR_SINT11,
|
|
||||||
HV_X64_MSR_SINT12,
|
|
||||||
HV_X64_MSR_SINT13,
|
|
||||||
HV_X64_MSR_SINT14,
|
|
||||||
HV_X64_MSR_SINT15,
|
|
||||||
HV_X64_MSR_STIMER0_CONFIG,
|
|
||||||
HV_X64_MSR_STIMER0_COUNT,
|
|
||||||
HV_X64_MSR_STIMER1_CONFIG,
|
|
||||||
HV_X64_MSR_STIMER1_COUNT,
|
|
||||||
HV_X64_MSR_STIMER2_CONFIG,
|
|
||||||
HV_X64_MSR_STIMER2_COUNT,
|
|
||||||
HV_X64_MSR_STIMER3_CONFIG,
|
|
||||||
HV_X64_MSR_STIMER3_COUNT,
|
|
||||||
HV_X64_MSR_GUEST_IDLE,
|
|
||||||
HV_X64_MSR_CRASH_P0,
|
|
||||||
HV_X64_MSR_CRASH_P1,
|
|
||||||
HV_X64_MSR_CRASH_P2,
|
|
||||||
HV_X64_MSR_CRASH_P3,
|
|
||||||
HV_X64_MSR_CRASH_P4,
|
|
||||||
HV_X64_MSR_CRASH_CTL,
|
|
||||||
HV_X64_MSR_REENLIGHTENMENT_CONTROL,
|
|
||||||
HV_X64_MSR_TSC_EMULATION_CONTROL,
|
|
||||||
HV_X64_MSR_TSC_EMULATION_STATUS,
|
|
||||||
HV_X64_MSR_TSC_INVARIANT_CONTROL,
|
|
||||||
HV_X64_MSR_SYNDBG_CONTROL,
|
|
||||||
HV_X64_MSR_SYNDBG_STATUS,
|
|
||||||
HV_X64_MSR_SYNDBG_SEND_BUFFER,
|
|
||||||
HV_X64_MSR_SYNDBG_RECV_BUFFER,
|
|
||||||
HV_X64_MSR_SYNDBG_PENDING_BUFFER,
|
|
||||||
HV_X64_MSR_SYNDBG_OPTIONS,
|
|
||||||
];
|
|
||||||
@@ -5,34 +5,154 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE-BSD-3-Clause file.
|
// found in the LICENSE-BSD-3-Clause file.
|
||||||
|
|
||||||
|
use std::io::Cursor;
|
||||||
|
use std::mem;
|
||||||
use std::result;
|
use std::result;
|
||||||
|
|
||||||
pub type Result<T> = result::Result<T, hypervisor::HypervisorCpuError>;
|
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
|
||||||
|
|
||||||
|
use kvm_bindings::kvm_lapic_state;
|
||||||
|
use kvm_ioctls;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Error {
|
||||||
|
GetLapic(kvm_ioctls::Error),
|
||||||
|
SetLapic(kvm_ioctls::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type Result<T> = result::Result<T, Error>;
|
||||||
|
|
||||||
// Defines poached from apicdef.h kernel header.
|
// Defines poached from apicdef.h kernel header.
|
||||||
pub const APIC_LVT0: usize = 0x350;
|
const APIC_LVT0: usize = 0x350;
|
||||||
pub const APIC_LVT1: usize = 0x360;
|
const APIC_LVT1: usize = 0x360;
|
||||||
pub const APIC_MODE_NMI: u32 = 0x4;
|
const APIC_MODE_NMI: u32 = 0x4;
|
||||||
pub const APIC_MODE_EXTINT: u32 = 0x7;
|
const APIC_MODE_EXTINT: u32 = 0x7;
|
||||||
|
|
||||||
pub fn set_apic_delivery_mode(reg: u32, mode: u32) -> u32 {
|
fn get_klapic_reg(klapic: &kvm_lapic_state, reg_offset: usize) -> u32 {
|
||||||
((reg) & !0x700) | ((mode) << 8)
|
let sliceu8 = unsafe {
|
||||||
|
// This array is only accessed as parts of a u32 word, so interpret it as a u8 array.
|
||||||
|
// Cursors are only readable on arrays of u8, not i8(c_char).
|
||||||
|
mem::transmute::<&[i8], &[u8]>(&klapic.regs[reg_offset..])
|
||||||
|
};
|
||||||
|
let mut reader = Cursor::new(sliceu8);
|
||||||
|
// Following call can't fail if the offsets defined above are correct.
|
||||||
|
reader
|
||||||
|
.read_u32::<LittleEndian>()
|
||||||
|
.expect("Failed to read klapic register")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_klapic_reg(klapic: &mut kvm_lapic_state, reg_offset: usize, value: u32) {
|
||||||
|
let sliceu8 = unsafe {
|
||||||
|
// This array is only accessed as parts of a u32 word, so interpret it as a u8 array.
|
||||||
|
// Cursors are only readable on arrays of u8, not i8(c_char).
|
||||||
|
mem::transmute::<&mut [i8], &mut [u8]>(&mut klapic.regs[reg_offset..])
|
||||||
|
};
|
||||||
|
let mut writer = Cursor::new(sliceu8);
|
||||||
|
// Following call can't fail if the offsets defined above are correct.
|
||||||
|
writer
|
||||||
|
.write_u32::<LittleEndian>(value)
|
||||||
|
.expect("Failed to write klapic register")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_apic_delivery_mode(reg: u32, mode: u32) -> u32 {
|
||||||
|
(((reg) & !0x700) | ((mode) << 8))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Configures LAPICs. LAPIC0 is set for external interrupts, LAPIC1 is set for NMI.
|
/// Configures LAPICs. LAPIC0 is set for external interrupts, LAPIC1 is set for NMI.
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
/// * `vcpu` - The VCPU object to configure.
|
/// * `vcpu` - The VCPU object to configure.
|
||||||
pub fn set_lint(vcpu: &dyn hypervisor::Vcpu) -> Result<()> {
|
pub fn set_lint(vcpu: &kvm_ioctls::VcpuFd) -> Result<()> {
|
||||||
let mut klapic = vcpu.get_lapic()?;
|
let mut klapic = vcpu.get_lapic().map_err(Error::GetLapic)?;
|
||||||
|
|
||||||
let lvt_lint0 = klapic.get_klapic_reg(APIC_LVT0);
|
let lvt_lint0 = get_klapic_reg(&klapic, APIC_LVT0);
|
||||||
klapic.set_klapic_reg(
|
set_klapic_reg(
|
||||||
|
&mut klapic,
|
||||||
APIC_LVT0,
|
APIC_LVT0,
|
||||||
set_apic_delivery_mode(lvt_lint0, APIC_MODE_EXTINT),
|
set_apic_delivery_mode(lvt_lint0, APIC_MODE_EXTINT),
|
||||||
);
|
);
|
||||||
let lvt_lint1 = klapic.get_klapic_reg(APIC_LVT1);
|
let lvt_lint1 = get_klapic_reg(&klapic, APIC_LVT1);
|
||||||
klapic.set_klapic_reg(APIC_LVT1, set_apic_delivery_mode(lvt_lint1, APIC_MODE_NMI));
|
set_klapic_reg(
|
||||||
|
&mut klapic,
|
||||||
|
APIC_LVT1,
|
||||||
|
set_apic_delivery_mode(lvt_lint1, APIC_MODE_NMI),
|
||||||
|
);
|
||||||
|
|
||||||
vcpu.set_lapic(&klapic)
|
vcpu.set_lapic(&klapic).map_err(Error::SetLapic)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
extern crate kvm_ioctls;
|
||||||
|
extern crate rand;
|
||||||
|
use self::rand::Rng;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use kvm_ioctls::Kvm;
|
||||||
|
|
||||||
|
const KVM_APIC_REG_SIZE: usize = 0x400;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_set_and_get_klapic_reg() {
|
||||||
|
let reg_offset = 0x340;
|
||||||
|
let mut klapic = kvm_lapic_state::default();
|
||||||
|
set_klapic_reg(&mut klapic, reg_offset, 3);
|
||||||
|
let value = get_klapic_reg(&klapic, reg_offset);
|
||||||
|
assert_eq!(value, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic]
|
||||||
|
fn test_set_and_get_klapic_out_of_bounds() {
|
||||||
|
let reg_offset = KVM_APIC_REG_SIZE + 10;
|
||||||
|
let mut klapic = kvm_lapic_state::default();
|
||||||
|
set_klapic_reg(&mut klapic, reg_offset, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_apic_delivery_mode() {
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
let mut v: Vec<u32> = (0..20).map(|_| rng.gen::<u32>()).collect();
|
||||||
|
|
||||||
|
v.iter_mut()
|
||||||
|
.for_each(|x| *x = set_apic_delivery_mode(*x, 2));
|
||||||
|
let after: Vec<u32> = v.iter().map(|x| ((*x & !0x700) | ((2) << 8))).collect();
|
||||||
|
assert_eq!(v, after);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_setlint() {
|
||||||
|
let kvm = kvm_ioctls::Kvm::new().unwrap();
|
||||||
|
assert!(kvm.check_extension(kvm_ioctls::Cap::Irqchip));
|
||||||
|
let vm = kvm.create_vm().unwrap();
|
||||||
|
//the get_lapic ioctl will fail if there is no irqchip created beforehand.
|
||||||
|
assert!(vm.create_irq_chip().is_ok());
|
||||||
|
let vcpu = vm.create_vcpu(0).unwrap();
|
||||||
|
let klapic_before: kvm_lapic_state = vcpu.get_lapic().unwrap();
|
||||||
|
|
||||||
|
// Compute the value that is expected to represent LVT0 and LVT1.
|
||||||
|
let lint0 = get_klapic_reg(&klapic_before, APIC_LVT0);
|
||||||
|
let lint1 = get_klapic_reg(&klapic_before, APIC_LVT1);
|
||||||
|
let lint0_mode_expected = set_apic_delivery_mode(lint0, APIC_MODE_EXTINT);
|
||||||
|
let lint1_mode_expected = set_apic_delivery_mode(lint1, APIC_MODE_NMI);
|
||||||
|
|
||||||
|
set_lint(&vcpu).unwrap();
|
||||||
|
|
||||||
|
// Compute the value that represents LVT0 and LVT1 after set_lint.
|
||||||
|
let klapic_actual: kvm_lapic_state = vcpu.get_lapic().unwrap();
|
||||||
|
let lint0_mode_actual = get_klapic_reg(&klapic_actual, APIC_LVT0);
|
||||||
|
let lint1_mode_actual = get_klapic_reg(&klapic_actual, APIC_LVT1);
|
||||||
|
assert_eq!(lint0_mode_expected, lint0_mode_actual);
|
||||||
|
assert_eq!(lint1_mode_expected, lint1_mode_actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_setlint_fails() {
|
||||||
|
let kvm = Kvm::new().unwrap();
|
||||||
|
let vm = kvm.create_vm().unwrap();
|
||||||
|
let vcpu = vm.create_vcpu(0).unwrap();
|
||||||
|
// 'get_lapic' ioctl triggered by the 'set_lint' function will fail if there is no
|
||||||
|
// irqchip created beforehand.
|
||||||
|
assert!(set_lint(&vcpu).is_err());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
// Copyright © 2020, Oracle and/or its affiliates.
|
|
||||||
//
|
|
||||||
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
//
|
//
|
||||||
@@ -7,7 +5,7 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE-BSD-3-Clause file.
|
// found in the LICENSE-BSD-3-Clause file.
|
||||||
|
|
||||||
use vm_memory::GuestAddress;
|
use vm_memory::{GuestAddress, GuestUsize};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
||||||
@@ -24,24 +22,10 @@ pub const LOW_RAM_START: GuestAddress = GuestAddress(0x0);
|
|||||||
|
|
||||||
// == Fixed addresses within the "Low RAM" range: ==
|
// == Fixed addresses within the "Low RAM" range: ==
|
||||||
|
|
||||||
// Location of EBDA address
|
|
||||||
pub const EBDA_POINTER: GuestAddress = GuestAddress(0x40e);
|
|
||||||
|
|
||||||
// Initial GDT/IDT needed to boot kernel
|
// Initial GDT/IDT needed to boot kernel
|
||||||
pub const BOOT_GDT_START: GuestAddress = GuestAddress(0x500);
|
pub const BOOT_GDT_START: GuestAddress = GuestAddress(0x500);
|
||||||
pub const BOOT_IDT_START: GuestAddress = GuestAddress(0x520);
|
pub const BOOT_IDT_START: GuestAddress = GuestAddress(0x520);
|
||||||
|
|
||||||
/// Address for the hvm_start_info struct used in PVH boot
|
|
||||||
pub const PVH_INFO_START: GuestAddress = GuestAddress(0x6000);
|
|
||||||
|
|
||||||
/// Starting address of array of modules of hvm_modlist_entry type.
|
|
||||||
/// Used to enable initrd support using the PVH boot ABI.
|
|
||||||
pub const MODLIST_START: GuestAddress = GuestAddress(0x6040);
|
|
||||||
|
|
||||||
/// Address of memory map table used in PVH boot. Can overlap
|
|
||||||
/// with the zero page address since they are mutually exclusive.
|
|
||||||
pub const MEMMAP_START: GuestAddress = GuestAddress(0x7000);
|
|
||||||
|
|
||||||
/// The 'zero page', a.k.a linux kernel bootparams.
|
/// The 'zero page', a.k.a linux kernel bootparams.
|
||||||
pub const ZERO_PAGE_START: GuestAddress = GuestAddress(0x7000);
|
pub const ZERO_PAGE_START: GuestAddress = GuestAddress(0x7000);
|
||||||
|
|
||||||
@@ -50,10 +34,9 @@ pub const BOOT_STACK_START: GuestAddress = GuestAddress(0x8000);
|
|||||||
pub const BOOT_STACK_POINTER: GuestAddress = GuestAddress(0x8ff0);
|
pub const BOOT_STACK_POINTER: GuestAddress = GuestAddress(0x8ff0);
|
||||||
|
|
||||||
// Initial pagetables.
|
// Initial pagetables.
|
||||||
pub const PML5_START: GuestAddress = GuestAddress(0x9000);
|
pub const PML4_START: GuestAddress = GuestAddress(0x9000);
|
||||||
pub const PML4_START: GuestAddress = GuestAddress(0xa000);
|
pub const PDPTE_START: GuestAddress = GuestAddress(0xa000);
|
||||||
pub const PDPTE_START: GuestAddress = GuestAddress(0xb000);
|
pub const PDE_START: GuestAddress = GuestAddress(0xb000);
|
||||||
pub const PDE_START: GuestAddress = GuestAddress(0xc000);
|
|
||||||
|
|
||||||
/// Kernel command line start address.
|
/// Kernel command line start address.
|
||||||
pub const CMDLINE_START: GuestAddress = GuestAddress(0x20000);
|
pub const CMDLINE_START: GuestAddress = GuestAddress(0x20000);
|
||||||
@@ -73,8 +56,6 @@ pub const EBDA_START: GuestAddress = GuestAddress(0xa0000);
|
|||||||
// ACPI RSDP table
|
// ACPI RSDP table
|
||||||
pub const RSDP_POINTER: GuestAddress = EBDA_START;
|
pub const RSDP_POINTER: GuestAddress = EBDA_START;
|
||||||
|
|
||||||
pub const SMBIOS_START: u64 = 0xf0000; // First possible location per the spec.
|
|
||||||
|
|
||||||
// == End of "EBDA" range ==
|
// == End of "EBDA" range ==
|
||||||
|
|
||||||
// ** High RAM (start: 1MiB, length: 3071MiB) **
|
// ** High RAM (start: 1MiB, length: 3071MiB) **
|
||||||
@@ -82,43 +63,31 @@ pub const HIGH_RAM_START: GuestAddress = GuestAddress(0x100000);
|
|||||||
|
|
||||||
// == No fixed addresses in the "High RAM" range ==
|
// == No fixed addresses in the "High RAM" range ==
|
||||||
|
|
||||||
// ** 32-bit reserved area (start: 3GiB, length: 896MiB) **
|
// ** 32-bit reserved area (start: 3GiB, length: 1GiB) **
|
||||||
pub const MEM_32BIT_RESERVED_START: GuestAddress = GuestAddress(0xc000_0000);
|
pub const MEM_32BIT_RESERVED_START: GuestAddress = GuestAddress(0xc000_0000);
|
||||||
pub const MEM_32BIT_RESERVED_SIZE: u64 = PCI_MMCONFIG_SIZE + MEM_32BIT_DEVICES_SIZE;
|
pub const MEM_32BIT_RESERVED_SIZE: GuestUsize = (1024 << 20);
|
||||||
|
|
||||||
// == Fixed constants within the "32-bit reserved" range ==
|
// == Fixed constants within the "32-bit reserved" range ==
|
||||||
|
|
||||||
// Sub range: 32-bit PCI devices (start: 3GiB, length: 640Mib)
|
// Sub range: 32-bit PCI devices (start: 3GiB, length: 640Mib)
|
||||||
pub const MEM_32BIT_DEVICES_START: GuestAddress = MEM_32BIT_RESERVED_START;
|
pub const MEM_32BIT_DEVICES_START: GuestAddress = MEM_32BIT_RESERVED_START;
|
||||||
pub const MEM_32BIT_DEVICES_SIZE: u64 = 640 << 20;
|
pub const MEM_32BIT_DEVICES_SIZE: GuestUsize = (640 << 20);
|
||||||
|
|
||||||
// PCI MMCONFIG space (start: after the device space, length: 256MiB)
|
// PCI MMCONFIG space (start: after the device space, length: 256MiB)
|
||||||
pub const PCI_MMCONFIG_START: GuestAddress =
|
pub const PCI_MMCONFIG_START: GuestAddress =
|
||||||
GuestAddress(MEM_32BIT_DEVICES_START.0 + MEM_32BIT_DEVICES_SIZE);
|
GuestAddress(MEM_32BIT_DEVICES_START.0 + MEM_32BIT_DEVICES_SIZE);
|
||||||
pub const PCI_MMCONFIG_SIZE: u64 = 256 << 20;
|
pub const PCI_MMCONFIG_SIZE: GuestUsize = (256 << 20);
|
||||||
// One bus with potentially 256 devices (32 slots x 8 functions).
|
|
||||||
pub const PCI_MMIO_CONFIG_SIZE_PER_SEGMENT: u64 = 4096 * 256;
|
|
||||||
|
|
||||||
// TSS is 3 pages after the PCI MMCONFIG space
|
|
||||||
pub const KVM_TSS_START: GuestAddress = GuestAddress(PCI_MMCONFIG_START.0 + PCI_MMCONFIG_SIZE);
|
|
||||||
pub const KVM_TSS_SIZE: u64 = (3 * 4) << 10;
|
|
||||||
|
|
||||||
// Identity map is a one page region after the TSS
|
|
||||||
pub const KVM_IDENTITY_MAP_START: GuestAddress = GuestAddress(KVM_TSS_START.0 + KVM_TSS_SIZE);
|
|
||||||
pub const KVM_IDENTITY_MAP_SIZE: u64 = 4 << 10;
|
|
||||||
|
|
||||||
/// TPM Address Range
|
|
||||||
/// This Address range is specific to CRB Interface
|
|
||||||
pub const TPM_START: GuestAddress = GuestAddress(0xfed4_0000);
|
|
||||||
pub const TPM_SIZE: u64 = 0x1000;
|
|
||||||
|
|
||||||
// IOAPIC
|
// IOAPIC
|
||||||
pub const IOAPIC_START: GuestAddress = GuestAddress(0xfec0_0000);
|
pub const IOAPIC_START: GuestAddress = GuestAddress(0xfec0_0000);
|
||||||
pub const IOAPIC_SIZE: u64 = 0x20;
|
pub const IOAPIC_SIZE: GuestUsize = 0x20;
|
||||||
|
|
||||||
// APIC
|
// APIC
|
||||||
pub const APIC_START: GuestAddress = GuestAddress(0xfee0_0000);
|
pub const APIC_START: GuestAddress = GuestAddress(0xfee0_0000);
|
||||||
|
|
||||||
|
/// Address for the TSS setup.
|
||||||
|
pub const KVM_TSS_ADDRESS: GuestAddress = GuestAddress(0xfffb_d000);
|
||||||
|
|
||||||
// == End of "32-bit reserved" range. ==
|
// == End of "32-bit reserved" range. ==
|
||||||
|
|
||||||
// ** 64-bit RAM start (start: 4GiB, length: varies) **
|
// ** 64-bit RAM start (start: 4GiB, length: varies) **
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,179 +0,0 @@
|
|||||||
// Copyright 2017 The Chromium OS Authors. All rights reserved.
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
|
||||||
// found in the LICENSE-BSD-3-Clause file.
|
|
||||||
use std::os::raw;
|
|
||||||
|
|
||||||
use vm_memory::ByteValued;
|
|
||||||
|
|
||||||
pub const MP_PROCESSOR: raw::c_uint = 0;
|
|
||||||
pub const MP_BUS: raw::c_uint = 1;
|
|
||||||
pub const MP_IOAPIC: raw::c_uint = 2;
|
|
||||||
pub const MP_INTSRC: raw::c_uint = 3;
|
|
||||||
pub const MP_LINTSRC: raw::c_uint = 4;
|
|
||||||
pub const CPU_ENABLED: raw::c_uint = 1;
|
|
||||||
pub const CPU_BOOTPROCESSOR: raw::c_uint = 2;
|
|
||||||
pub const MPC_APIC_USABLE: raw::c_uint = 1;
|
|
||||||
pub const MP_IRQDIR_DEFAULT: raw::c_uint = 0;
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Debug, Default, Copy, Clone)]
|
|
||||||
pub struct mpf_intel {
|
|
||||||
pub signature: [raw::c_uchar; 4usize],
|
|
||||||
pub physptr: raw::c_uint,
|
|
||||||
pub length: raw::c_uchar,
|
|
||||||
pub specification: raw::c_uchar,
|
|
||||||
pub checksum: raw::c_uchar,
|
|
||||||
pub feature1: raw::c_uchar,
|
|
||||||
pub feature2: raw::c_uchar,
|
|
||||||
pub feature3: raw::c_uchar,
|
|
||||||
pub feature4: raw::c_uchar,
|
|
||||||
pub feature5: raw::c_uchar,
|
|
||||||
}
|
|
||||||
|
|
||||||
const _: () = assert!(size_of::<mpf_intel>() == 16);
|
|
||||||
// SAFETY: all members of this struct are plain integers
|
|
||||||
// and the sum of their sizes is the size of the struct, so
|
|
||||||
// padding and reserved values are not possible as there
|
|
||||||
// would be nowhere for them to exist.
|
|
||||||
unsafe impl ByteValued for mpf_intel {}
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Debug, Default, Copy, Clone)]
|
|
||||||
pub struct mpc_table {
|
|
||||||
pub signature: [raw::c_uchar; 4usize],
|
|
||||||
pub length: raw::c_ushort,
|
|
||||||
pub spec: raw::c_uchar,
|
|
||||||
pub checksum: raw::c_uchar,
|
|
||||||
pub oem: [raw::c_uchar; 8usize],
|
|
||||||
pub productid: [raw::c_uchar; 12usize],
|
|
||||||
pub oemptr: raw::c_uint,
|
|
||||||
pub oemsize: raw::c_ushort,
|
|
||||||
pub oemcount: raw::c_ushort,
|
|
||||||
pub lapic: raw::c_uint,
|
|
||||||
pub reserved: raw::c_uint,
|
|
||||||
}
|
|
||||||
|
|
||||||
const _: () = {
|
|
||||||
assert!(size_of::<mpc_table>() == 4 + 2 + 1 + 1 + 8 + 12 + 4 + 2 + 2 + 4 + 4);
|
|
||||||
assert!(size_of::<raw::c_uint>() == 4);
|
|
||||||
assert!(size_of::<raw::c_ushort>() == 2);
|
|
||||||
assert!(size_of::<raw::c_uchar>() == 1);
|
|
||||||
};
|
|
||||||
|
|
||||||
// SAFETY: all members of this struct are plain integers
|
|
||||||
// and the sum of their sizes is the size of the struct, so
|
|
||||||
// padding and reserved values are not possible as there
|
|
||||||
// would be nowhere for them to exist.
|
|
||||||
unsafe impl ByteValued for mpc_table {}
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Debug, Default, Copy, Clone)]
|
|
||||||
pub struct mpc_cpu {
|
|
||||||
pub type_: raw::c_uchar,
|
|
||||||
pub apicid: raw::c_uchar,
|
|
||||||
pub apicver: raw::c_uchar,
|
|
||||||
pub cpuflag: raw::c_uchar,
|
|
||||||
pub cpufeature: raw::c_uint,
|
|
||||||
pub featureflag: raw::c_uint,
|
|
||||||
pub reserved: [raw::c_uint; 2usize],
|
|
||||||
}
|
|
||||||
|
|
||||||
const _: () = assert!(size_of::<mpc_cpu>() == 20);
|
|
||||||
// SAFETY: all members of this struct are plain integers
|
|
||||||
// and the sum of their sizes is the size of the struct, so
|
|
||||||
// padding and reserved values are not possible as there
|
|
||||||
// would be nowhere for them to exist.
|
|
||||||
unsafe impl ByteValued for mpc_cpu {}
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Debug, Default, Copy, Clone)]
|
|
||||||
pub struct mpc_bus {
|
|
||||||
pub type_: raw::c_uchar,
|
|
||||||
pub busid: raw::c_uchar,
|
|
||||||
pub bustype: [raw::c_uchar; 6usize],
|
|
||||||
}
|
|
||||||
|
|
||||||
const _: () = assert!(size_of::<mpc_bus>() == 8);
|
|
||||||
// SAFETY: all members of this struct are plain integers
|
|
||||||
// and the sum of their sizes is the size of the struct, so
|
|
||||||
// padding and reserved values are not possible as there
|
|
||||||
// would be nowhere for them to exist.
|
|
||||||
unsafe impl ByteValued for mpc_bus {}
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Debug, Default, Copy, Clone)]
|
|
||||||
pub struct mpc_ioapic {
|
|
||||||
pub type_: raw::c_uchar,
|
|
||||||
pub apicid: raw::c_uchar,
|
|
||||||
pub apicver: raw::c_uchar,
|
|
||||||
pub flags: raw::c_uchar,
|
|
||||||
pub apicaddr: raw::c_uint,
|
|
||||||
}
|
|
||||||
|
|
||||||
const _: () = assert!(size_of::<mpc_ioapic>() == 8);
|
|
||||||
// SAFETY: all members of this struct are plain integers
|
|
||||||
// and the sum of their sizes is the size of the struct, so
|
|
||||||
// padding and reserved values are not possible as there
|
|
||||||
// would be nowhere for them to exist.
|
|
||||||
unsafe impl ByteValued for mpc_ioapic {}
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Debug, Default, Copy, Clone)]
|
|
||||||
pub struct mpc_intsrc {
|
|
||||||
pub type_: raw::c_uchar,
|
|
||||||
pub irqtype: raw::c_uchar,
|
|
||||||
pub irqflag: raw::c_ushort,
|
|
||||||
pub srcbus: raw::c_uchar,
|
|
||||||
pub srcbusirq: raw::c_uchar,
|
|
||||||
pub dstapic: raw::c_uchar,
|
|
||||||
pub dstirq: raw::c_uchar,
|
|
||||||
}
|
|
||||||
|
|
||||||
const _: () = assert!(size_of::<mpc_intsrc>() == 8);
|
|
||||||
// SAFETY: all members of this struct are plain integers
|
|
||||||
// and the sum of their sizes is the size of the struct, so
|
|
||||||
// padding and reserved values are not possible as there
|
|
||||||
// would be nowhere for them to exist.
|
|
||||||
unsafe impl ByteValued for mpc_intsrc {}
|
|
||||||
|
|
||||||
pub const MP_IRQ_SOURCE_TYPES_MP_INT: raw::c_uint = 0;
|
|
||||||
pub const MP_IRQ_SOURCE_TYPES_MP_NMI: raw::c_uint = 1;
|
|
||||||
pub const MP_IRQ_SOURCE_TYPES_MP_EXT_INT: raw::c_uint = 3;
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Debug, Default, Copy, Clone)]
|
|
||||||
pub struct mpc_lintsrc {
|
|
||||||
pub type_: raw::c_uchar,
|
|
||||||
pub irqtype: raw::c_uchar,
|
|
||||||
pub irqflag: raw::c_ushort,
|
|
||||||
pub srcbusid: raw::c_uchar,
|
|
||||||
pub srcbusirq: raw::c_uchar,
|
|
||||||
pub destapic: raw::c_uchar,
|
|
||||||
pub destapiclint: raw::c_uchar,
|
|
||||||
}
|
|
||||||
|
|
||||||
const _: () = assert!(size_of::<mpc_lintsrc>() == 8);
|
|
||||||
// SAFETY: all members of this struct are plain integers
|
|
||||||
// and the sum of their sizes is the size of the struct, so
|
|
||||||
// padding and reserved values are not possible as there
|
|
||||||
// would be nowhere for them to exist.
|
|
||||||
unsafe impl ByteValued for mpc_lintsrc {}
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Debug, Default, Copy, Clone)]
|
|
||||||
pub struct mpc_oemtable {
|
|
||||||
pub signature: [raw::c_uchar; 4usize],
|
|
||||||
pub length: raw::c_ushort,
|
|
||||||
pub rev: raw::c_uchar,
|
|
||||||
pub checksum: raw::c_uchar,
|
|
||||||
pub mpc: [raw::c_uchar; 8usize],
|
|
||||||
}
|
|
||||||
|
|
||||||
const _: () = assert!(size_of::<mpc_oemtable>() == 16);
|
|
||||||
// SAFETY: all members of this struct are plain integers
|
|
||||||
// and the sum of their sizes is the size of the struct, so
|
|
||||||
// padding and reserved values are not possible as there
|
|
||||||
// would be nowhere for them to exist.
|
|
||||||
unsafe impl ByteValued for mpc_oemtable {}
|
|
||||||
@@ -5,17 +5,16 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE-BSD-3-Clause file.
|
// found in the LICENSE-BSD-3-Clause file.
|
||||||
|
|
||||||
|
use std::io;
|
||||||
|
use std::mem;
|
||||||
use std::result;
|
use std::result;
|
||||||
|
use std::slice;
|
||||||
|
|
||||||
use libc::c_uchar;
|
use libc::c_char;
|
||||||
use log::{info, warn};
|
|
||||||
use thiserror::Error;
|
|
||||||
use vm_memory::{Address, ByteValued, Bytes, GuestAddress, GuestMemoryBackend, GuestMemoryError};
|
|
||||||
|
|
||||||
use super::MAX_SUPPORTED_CPUS_LEGACY;
|
use arch_gen::x86::mpspec;
|
||||||
use crate::GuestMemoryMmap;
|
use layout::{APIC_START, IOAPIC_START, MPTABLE_START};
|
||||||
use crate::layout::{APIC_START, HIGH_RAM_START, IOAPIC_START};
|
use vm_memory::{Address, ByteValued, Bytes, GuestMemory, GuestMemoryError, GuestMemoryMmap};
|
||||||
use crate::x86_64::{get_x2apic_id, mpspec};
|
|
||||||
|
|
||||||
// This is a workaround to the Rust enforcement specifying that any implementation of a foreign
|
// This is a workaround to the Rust enforcement specifying that any implementation of a foreign
|
||||||
// trait (in this case `ByteValued`) where:
|
// trait (in this case `ByteValued`) where:
|
||||||
@@ -37,72 +36,70 @@ struct MpcLintsrcWrapper(mpspec::mpc_lintsrc);
|
|||||||
#[derive(Copy, Clone, Default)]
|
#[derive(Copy, Clone, Default)]
|
||||||
struct MpfIntelWrapper(mpspec::mpf_intel);
|
struct MpfIntelWrapper(mpspec::mpf_intel);
|
||||||
|
|
||||||
// SAFETY: These `mpspec` wrapper types are only data, reading them from data is a safe initialization.
|
// These `mpspec` wrapper types are only data, reading them from data is a safe initialization.
|
||||||
unsafe impl ByteValued for MpcBusWrapper {}
|
unsafe impl ByteValued for MpcBusWrapper {}
|
||||||
// SAFETY: see above
|
|
||||||
unsafe impl ByteValued for MpcCpuWrapper {}
|
unsafe impl ByteValued for MpcCpuWrapper {}
|
||||||
// SAFETY: see above
|
|
||||||
unsafe impl ByteValued for MpcIntsrcWrapper {}
|
unsafe impl ByteValued for MpcIntsrcWrapper {}
|
||||||
// SAFETY: see above
|
|
||||||
unsafe impl ByteValued for MpcIoapicWrapper {}
|
unsafe impl ByteValued for MpcIoapicWrapper {}
|
||||||
// SAFETY: see above
|
|
||||||
unsafe impl ByteValued for MpcTableWrapper {}
|
unsafe impl ByteValued for MpcTableWrapper {}
|
||||||
// SAFETY: see above
|
|
||||||
unsafe impl ByteValued for MpcLintsrcWrapper {}
|
unsafe impl ByteValued for MpcLintsrcWrapper {}
|
||||||
// SAFETY: see above
|
|
||||||
unsafe impl ByteValued for MpfIntelWrapper {}
|
unsafe impl ByteValued for MpfIntelWrapper {}
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
/// There was too little guest memory to store the entire MP table.
|
/// There was too little guest memory to store the entire MP table.
|
||||||
#[error("There was too little guest memory to store the entire MP table")]
|
|
||||||
NotEnoughMemory,
|
NotEnoughMemory,
|
||||||
/// The MP table has too little address space to be stored.
|
/// The MP table has too little address space to be stored.
|
||||||
#[error("The MP table has too little address space to be stored")]
|
|
||||||
AddressOverflow,
|
AddressOverflow,
|
||||||
/// Failure while zeroing out the memory for the MP table.
|
/// Failure while zeroing out the memory for the MP table.
|
||||||
#[error("Failure while zeroing out the memory for the MP table")]
|
Clear(GuestMemoryError),
|
||||||
Clear(#[source] GuestMemoryError),
|
/// Number of CPUs exceeds the maximum supported CPUs
|
||||||
|
TooManyCpus,
|
||||||
/// Failure to write the MP floating pointer.
|
/// Failure to write the MP floating pointer.
|
||||||
#[error("Failure to write the MP floating pointer")]
|
WriteMpfIntel(GuestMemoryError),
|
||||||
WriteMpfIntel(#[source] GuestMemoryError),
|
|
||||||
/// Failure to write MP CPU entry.
|
/// Failure to write MP CPU entry.
|
||||||
#[error("Failure to write MP CPU entry")]
|
WriteMpcCpu(GuestMemoryError),
|
||||||
WriteMpcCpu(#[source] GuestMemoryError),
|
|
||||||
/// Failure to write MP ioapic entry.
|
/// Failure to write MP ioapic entry.
|
||||||
#[error("Failure to write MP ioapic entry")]
|
WriteMpcIoapic(GuestMemoryError),
|
||||||
WriteMpcIoapic(#[source] GuestMemoryError),
|
|
||||||
/// Failure to write MP bus entry.
|
/// Failure to write MP bus entry.
|
||||||
#[error("Failure to write MP bus entry")]
|
WriteMpcBus(GuestMemoryError),
|
||||||
WriteMpcBus(#[source] GuestMemoryError),
|
|
||||||
/// Failure to write MP interrupt source entry.
|
/// Failure to write MP interrupt source entry.
|
||||||
#[error("Failure to write MP interrupt source entry")]
|
WriteMpcIntsrc(GuestMemoryError),
|
||||||
WriteMpcIntsrc(#[source] GuestMemoryError),
|
|
||||||
/// Failure to write MP local interrupt source entry.
|
/// Failure to write MP local interrupt source entry.
|
||||||
#[error("Failure to write MP local interrupt source entry")]
|
WriteMpcLintsrc(GuestMemoryError),
|
||||||
WriteMpcLintsrc(#[source] GuestMemoryError),
|
|
||||||
/// Failure to write MP table header.
|
/// Failure to write MP table header.
|
||||||
#[error("Failure to write MP table header")]
|
WriteMpcTable(GuestMemoryError),
|
||||||
WriteMpcTable(#[source] GuestMemoryError),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Result<T> = result::Result<T, Error>;
|
pub type Result<T> = result::Result<T, Error>;
|
||||||
|
|
||||||
|
// With APIC/xAPIC, there are only 255 APIC IDs available. And IOAPIC occupies
|
||||||
|
// one APIC ID, so only 254 CPUs at maximum may be supported. Actually it's
|
||||||
|
// a large number for FC usecases.
|
||||||
|
pub const MAX_SUPPORTED_CPUS: u32 = 254;
|
||||||
|
|
||||||
|
// Convenience macro for making arrays of diverse character types.
|
||||||
|
macro_rules! char_array {
|
||||||
|
($t:ty; $( $c:expr ),*) => ( [ $( $c as $t ),* ] )
|
||||||
|
}
|
||||||
|
|
||||||
// Most of these variables are sourced from the Intel MP Spec 1.4.
|
// Most of these variables are sourced from the Intel MP Spec 1.4.
|
||||||
const SMP_MAGIC_IDENT: &[c_uchar; 4] = b"_MP_";
|
const SMP_MAGIC_IDENT: [c_char; 4] = char_array!(c_char; '_', 'M', 'P', '_');
|
||||||
const MPC_SIGNATURE: &[c_uchar; 4] = b"PCMP";
|
const MPC_SIGNATURE: [c_char; 4] = char_array!(c_char; 'P', 'C', 'M', 'P');
|
||||||
const MPC_SPEC: u8 = 4;
|
const MPC_SPEC: i8 = 4;
|
||||||
const MPC_OEM: &[c_uchar; 8] = b"FC ";
|
const MPC_OEM: [c_char; 8] = char_array!(c_char; 'F', 'C', ' ', ' ', ' ', ' ', ' ', ' ');
|
||||||
const MPC_PRODUCT_ID: &[c_uchar; 12] = &[b'0'; 12];
|
const MPC_PRODUCT_ID: [c_char; 12] = ['0' as c_char; 12];
|
||||||
const BUS_TYPE_ISA: &[c_uchar; 6] = b"ISA ";
|
const BUS_TYPE_ISA: [u8; 6] = char_array!(u8; 'I', 'S', 'A', ' ', ' ', ' ');
|
||||||
const APIC_VERSION: u8 = 0x14;
|
const APIC_VERSION: u8 = 0x14;
|
||||||
const CPU_STEPPING: u32 = 0x600;
|
const CPU_STEPPING: u32 = 0x600;
|
||||||
const CPU_FEATURE_APIC: u32 = 0x200;
|
const CPU_FEATURE_APIC: u32 = 0x200;
|
||||||
const CPU_FEATURE_FPU: u32 = 0x001;
|
const CPU_FEATURE_FPU: u32 = 0x001;
|
||||||
|
|
||||||
fn compute_checksum<T: Copy + ByteValued>(v: &T) -> u8 {
|
fn compute_checksum<T: Copy>(v: &T) -> u8 {
|
||||||
|
// Safe because we are only reading the bytes within the size of the `T` reference `v`.
|
||||||
|
let v_slice = unsafe { slice::from_raw_parts(v as *const T as *const u8, mem::size_of::<T>()) };
|
||||||
let mut checksum: u8 = 0;
|
let mut checksum: u8 = 0;
|
||||||
for i in v.as_slice().iter() {
|
for i in v_slice.iter() {
|
||||||
checksum = checksum.wrapping_add(*i);
|
checksum = checksum.wrapping_add(*i);
|
||||||
}
|
}
|
||||||
checksum
|
checksum
|
||||||
@@ -113,44 +110,29 @@ fn mpf_intel_compute_checksum(v: &mpspec::mpf_intel) -> u8 {
|
|||||||
(!checksum).wrapping_add(1)
|
(!checksum).wrapping_add(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compute_mp_size(num_cpus: u32) -> usize {
|
fn compute_mp_size(num_cpus: u8) -> usize {
|
||||||
size_of::<MpfIntelWrapper>()
|
mem::size_of::<MpfIntelWrapper>()
|
||||||
+ size_of::<MpcTableWrapper>()
|
+ mem::size_of::<MpcTableWrapper>()
|
||||||
+ size_of::<MpcCpuWrapper>() * (num_cpus as usize)
|
+ mem::size_of::<MpcCpuWrapper>() * (num_cpus as usize)
|
||||||
+ size_of::<MpcIoapicWrapper>()
|
+ mem::size_of::<MpcIoapicWrapper>()
|
||||||
+ size_of::<MpcBusWrapper>()
|
+ mem::size_of::<MpcBusWrapper>()
|
||||||
+ size_of::<MpcIntsrcWrapper>() * 16
|
+ mem::size_of::<MpcIntsrcWrapper>() * 16
|
||||||
+ size_of::<MpcLintsrcWrapper>() * 2
|
+ mem::size_of::<MpcLintsrcWrapper>() * 2
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Performs setup of the MP table for the given `num_cpus`.
|
/// Performs setup of the MP table for the given `num_cpus`.
|
||||||
pub fn setup_mptable(
|
pub fn setup_mptable(mem: &GuestMemoryMmap, num_cpus: u8) -> Result<()> {
|
||||||
offset: GuestAddress,
|
if num_cpus as u32 > MAX_SUPPORTED_CPUS {
|
||||||
mem: &GuestMemoryMmap,
|
return Err(Error::TooManyCpus);
|
||||||
num_cpus: u32,
|
|
||||||
topology: Option<(u16, u16, u16, u16)>,
|
|
||||||
) -> Result<()> {
|
|
||||||
if num_cpus > 0 {
|
|
||||||
let cpu_id_max = num_cpus - 1;
|
|
||||||
let x2apic_id_max = get_x2apic_id(cpu_id_max, topology);
|
|
||||||
if x2apic_id_max >= MAX_SUPPORTED_CPUS_LEGACY {
|
|
||||||
info!("Skipping mptable creation due to too many CPUs");
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Used to keep track of the next base pointer into the MP table.
|
// Used to keep track of the next base pointer into the MP table.
|
||||||
let mut base_mp = offset;
|
let mut base_mp = MPTABLE_START;
|
||||||
|
|
||||||
let mp_size = compute_mp_size(num_cpus);
|
let mp_size = compute_mp_size(num_cpus);
|
||||||
|
|
||||||
if offset.unchecked_add(mp_size as u64) >= HIGH_RAM_START {
|
|
||||||
warn!("Skipping mptable creation due to insufficient space");
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut checksum: u8 = 0;
|
let mut checksum: u8 = 0;
|
||||||
let ioapicid: u8 = MAX_SUPPORTED_CPUS_LEGACY as u8 + 1;
|
let ioapicid: u8 = num_cpus + 1;
|
||||||
|
|
||||||
// The checked_add here ensures the all of the following base_mp.unchecked_add's will be without
|
// The checked_add here ensures the all of the following base_mp.unchecked_add's will be without
|
||||||
// overflow.
|
// overflow.
|
||||||
@@ -162,13 +144,13 @@ pub fn setup_mptable(
|
|||||||
return Err(Error::AddressOverflow);
|
return Err(Error::AddressOverflow);
|
||||||
}
|
}
|
||||||
|
|
||||||
mem.read_exact_volatile_from(base_mp, &mut vec![0; mp_size].as_slice(), mp_size)
|
mem.read_exact_from(base_mp, &mut io::repeat(0), mp_size)
|
||||||
.map_err(Error::Clear)?;
|
.map_err(Error::Clear)?;
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut mpf_intel = MpfIntelWrapper(mpspec::mpf_intel::default());
|
let mut mpf_intel = MpfIntelWrapper(mpspec::mpf_intel::default());
|
||||||
let size = size_of::<MpfIntelWrapper>() as u64;
|
let size = mem::size_of::<MpfIntelWrapper>() as u64;
|
||||||
mpf_intel.0.signature = *SMP_MAGIC_IDENT;
|
mpf_intel.0.signature = SMP_MAGIC_IDENT;
|
||||||
mpf_intel.0.length = 1;
|
mpf_intel.0.length = 1;
|
||||||
mpf_intel.0.specification = 4;
|
mpf_intel.0.specification = 4;
|
||||||
mpf_intel.0.physptr = (base_mp.raw_value() + size) as u32;
|
mpf_intel.0.physptr = (base_mp.raw_value() + size) as u32;
|
||||||
@@ -181,14 +163,14 @@ pub fn setup_mptable(
|
|||||||
// We set the location of the mpc_table here but we can't fill it out until we have the length
|
// We set the location of the mpc_table here but we can't fill it out until we have the length
|
||||||
// of the entire table later.
|
// of the entire table later.
|
||||||
let table_base = base_mp;
|
let table_base = base_mp;
|
||||||
base_mp = base_mp.unchecked_add(size_of::<MpcTableWrapper>() as u64);
|
base_mp = base_mp.unchecked_add(mem::size_of::<MpcTableWrapper>() as u64);
|
||||||
|
|
||||||
{
|
{
|
||||||
let size = size_of::<MpcCpuWrapper>();
|
let size = mem::size_of::<MpcCpuWrapper>();
|
||||||
for cpu_id in 0..num_cpus {
|
for cpu_id in 0..num_cpus {
|
||||||
let mut mpc_cpu = MpcCpuWrapper(mpspec::mpc_cpu::default());
|
let mut mpc_cpu = MpcCpuWrapper(mpspec::mpc_cpu::default());
|
||||||
mpc_cpu.0.type_ = mpspec::MP_PROCESSOR as u8;
|
mpc_cpu.0.type_ = mpspec::MP_PROCESSOR as u8;
|
||||||
mpc_cpu.0.apicid = get_x2apic_id(cpu_id, topology) as u8;
|
mpc_cpu.0.apicid = cpu_id;
|
||||||
mpc_cpu.0.apicver = APIC_VERSION;
|
mpc_cpu.0.apicver = APIC_VERSION;
|
||||||
mpc_cpu.0.cpuflag = mpspec::CPU_ENABLED as u8
|
mpc_cpu.0.cpuflag = mpspec::CPU_ENABLED as u8
|
||||||
| if cpu_id == 0 {
|
| if cpu_id == 0 {
|
||||||
@@ -205,18 +187,18 @@ pub fn setup_mptable(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
let size = size_of::<MpcBusWrapper>();
|
let size = mem::size_of::<MpcBusWrapper>();
|
||||||
let mut mpc_bus = MpcBusWrapper(mpspec::mpc_bus::default());
|
let mut mpc_bus = MpcBusWrapper(mpspec::mpc_bus::default());
|
||||||
mpc_bus.0.type_ = mpspec::MP_BUS as u8;
|
mpc_bus.0.type_ = mpspec::MP_BUS as u8;
|
||||||
mpc_bus.0.busid = 0;
|
mpc_bus.0.busid = 0;
|
||||||
mpc_bus.0.bustype = *BUS_TYPE_ISA;
|
mpc_bus.0.bustype = BUS_TYPE_ISA;
|
||||||
mem.write_obj(mpc_bus, base_mp)
|
mem.write_obj(mpc_bus, base_mp)
|
||||||
.map_err(Error::WriteMpcBus)?;
|
.map_err(Error::WriteMpcBus)?;
|
||||||
base_mp = base_mp.unchecked_add(size as u64);
|
base_mp = base_mp.unchecked_add(size as u64);
|
||||||
checksum = checksum.wrapping_add(compute_checksum(&mpc_bus.0));
|
checksum = checksum.wrapping_add(compute_checksum(&mpc_bus.0));
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
let size = size_of::<MpcIoapicWrapper>();
|
let size = mem::size_of::<MpcIoapicWrapper>();
|
||||||
let mut mpc_ioapic = MpcIoapicWrapper(mpspec::mpc_ioapic::default());
|
let mut mpc_ioapic = MpcIoapicWrapper(mpspec::mpc_ioapic::default());
|
||||||
mpc_ioapic.0.type_ = mpspec::MP_IOAPIC as u8;
|
mpc_ioapic.0.type_ = mpspec::MP_IOAPIC as u8;
|
||||||
mpc_ioapic.0.apicid = ioapicid;
|
mpc_ioapic.0.apicid = ioapicid;
|
||||||
@@ -230,10 +212,10 @@ pub fn setup_mptable(
|
|||||||
}
|
}
|
||||||
// Per kvm_setup_default_irq_routing() in kernel
|
// Per kvm_setup_default_irq_routing() in kernel
|
||||||
for i in 0..16 {
|
for i in 0..16 {
|
||||||
let size = size_of::<MpcIntsrcWrapper>();
|
let size = mem::size_of::<MpcIntsrcWrapper>();
|
||||||
let mut mpc_intsrc = MpcIntsrcWrapper(mpspec::mpc_intsrc::default());
|
let mut mpc_intsrc = MpcIntsrcWrapper(mpspec::mpc_intsrc::default());
|
||||||
mpc_intsrc.0.type_ = mpspec::MP_INTSRC as u8;
|
mpc_intsrc.0.type_ = mpspec::MP_INTSRC as u8;
|
||||||
mpc_intsrc.0.irqtype = mpspec::MP_IRQ_SOURCE_TYPES_MP_INT as u8;
|
mpc_intsrc.0.irqtype = mpspec::mp_irq_source_types_mp_INT as u8;
|
||||||
mpc_intsrc.0.irqflag = mpspec::MP_IRQDIR_DEFAULT as u16;
|
mpc_intsrc.0.irqflag = mpspec::MP_IRQDIR_DEFAULT as u16;
|
||||||
mpc_intsrc.0.srcbus = 0;
|
mpc_intsrc.0.srcbus = 0;
|
||||||
mpc_intsrc.0.srcbusirq = i;
|
mpc_intsrc.0.srcbusirq = i;
|
||||||
@@ -245,10 +227,10 @@ pub fn setup_mptable(
|
|||||||
checksum = checksum.wrapping_add(compute_checksum(&mpc_intsrc.0));
|
checksum = checksum.wrapping_add(compute_checksum(&mpc_intsrc.0));
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
let size = size_of::<MpcLintsrcWrapper>();
|
let size = mem::size_of::<MpcLintsrcWrapper>();
|
||||||
let mut mpc_lintsrc = MpcLintsrcWrapper(mpspec::mpc_lintsrc::default());
|
let mut mpc_lintsrc = MpcLintsrcWrapper(mpspec::mpc_lintsrc::default());
|
||||||
mpc_lintsrc.0.type_ = mpspec::MP_LINTSRC as u8;
|
mpc_lintsrc.0.type_ = mpspec::MP_LINTSRC as u8;
|
||||||
mpc_lintsrc.0.irqtype = mpspec::MP_IRQ_SOURCE_TYPES_MP_EXT_INT as u8;
|
mpc_lintsrc.0.irqtype = mpspec::mp_irq_source_types_mp_ExtINT as u8;
|
||||||
mpc_lintsrc.0.irqflag = mpspec::MP_IRQDIR_DEFAULT as u16;
|
mpc_lintsrc.0.irqflag = mpspec::MP_IRQDIR_DEFAULT as u16;
|
||||||
mpc_lintsrc.0.srcbusid = 0;
|
mpc_lintsrc.0.srcbusid = 0;
|
||||||
mpc_lintsrc.0.srcbusirq = 0;
|
mpc_lintsrc.0.srcbusirq = 0;
|
||||||
@@ -260,10 +242,10 @@ pub fn setup_mptable(
|
|||||||
checksum = checksum.wrapping_add(compute_checksum(&mpc_lintsrc.0));
|
checksum = checksum.wrapping_add(compute_checksum(&mpc_lintsrc.0));
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
let size = size_of::<MpcLintsrcWrapper>();
|
let size = mem::size_of::<MpcLintsrcWrapper>();
|
||||||
let mut mpc_lintsrc = MpcLintsrcWrapper(mpspec::mpc_lintsrc::default());
|
let mut mpc_lintsrc = MpcLintsrcWrapper(mpspec::mpc_lintsrc::default());
|
||||||
mpc_lintsrc.0.type_ = mpspec::MP_LINTSRC as u8;
|
mpc_lintsrc.0.type_ = mpspec::MP_LINTSRC as u8;
|
||||||
mpc_lintsrc.0.irqtype = mpspec::MP_IRQ_SOURCE_TYPES_MP_NMI as u8;
|
mpc_lintsrc.0.irqtype = mpspec::mp_irq_source_types_mp_NMI as u8;
|
||||||
mpc_lintsrc.0.irqflag = mpspec::MP_IRQDIR_DEFAULT as u16;
|
mpc_lintsrc.0.irqflag = mpspec::MP_IRQDIR_DEFAULT as u16;
|
||||||
mpc_lintsrc.0.srcbusid = 0;
|
mpc_lintsrc.0.srcbusid = 0;
|
||||||
mpc_lintsrc.0.srcbusirq = 0;
|
mpc_lintsrc.0.srcbusirq = 0;
|
||||||
@@ -280,14 +262,14 @@ pub fn setup_mptable(
|
|||||||
|
|
||||||
{
|
{
|
||||||
let mut mpc_table = MpcTableWrapper(mpspec::mpc_table::default());
|
let mut mpc_table = MpcTableWrapper(mpspec::mpc_table::default());
|
||||||
mpc_table.0.signature = *MPC_SIGNATURE;
|
mpc_table.0.signature = MPC_SIGNATURE;
|
||||||
mpc_table.0.length = table_end.unchecked_offset_from(table_base) as u16;
|
mpc_table.0.length = table_end.unchecked_offset_from(table_base) as u16;
|
||||||
mpc_table.0.spec = MPC_SPEC;
|
mpc_table.0.spec = MPC_SPEC;
|
||||||
mpc_table.0.oem = *MPC_OEM;
|
mpc_table.0.oem = MPC_OEM;
|
||||||
mpc_table.0.productid = *MPC_PRODUCT_ID;
|
mpc_table.0.productid = MPC_PRODUCT_ID;
|
||||||
mpc_table.0.lapic = APIC_START.0 as u32;
|
mpc_table.0.lapic = APIC_START.0 as u32;
|
||||||
checksum = checksum.wrapping_add(compute_checksum(&mpc_table.0));
|
checksum = checksum.wrapping_add(compute_checksum(&mpc_table.0));
|
||||||
mpc_table.0.checksum = (!checksum).wrapping_add(1);
|
mpc_table.0.checksum = (!checksum).wrapping_add(1) as i8;
|
||||||
mem.write_obj(mpc_table, table_base)
|
mem.write_obj(mpc_table, table_base)
|
||||||
.map_err(Error::WriteMpcTable)?;
|
.map_err(Error::WriteMpcTable)?;
|
||||||
}
|
}
|
||||||
@@ -296,21 +278,18 @@ pub fn setup_mptable(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod unit_tests {
|
mod tests {
|
||||||
use vm_memory::bitmap::BitmapSlice;
|
|
||||||
use vm_memory::{GuestUsize, VolatileMemoryError, VolatileSlice, WriteVolatile};
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::layout::MPTABLE_START;
|
use vm_memory::{GuestAddress, GuestUsize};
|
||||||
|
|
||||||
fn table_entry_size(type_: u8) -> usize {
|
fn table_entry_size(type_: u8) -> usize {
|
||||||
match type_ as u32 {
|
match type_ as u32 {
|
||||||
mpspec::MP_PROCESSOR => size_of::<MpcCpuWrapper>(),
|
mpspec::MP_PROCESSOR => mem::size_of::<MpcCpuWrapper>(),
|
||||||
mpspec::MP_BUS => size_of::<MpcBusWrapper>(),
|
mpspec::MP_BUS => mem::size_of::<MpcBusWrapper>(),
|
||||||
mpspec::MP_IOAPIC => size_of::<MpcIoapicWrapper>(),
|
mpspec::MP_IOAPIC => mem::size_of::<MpcIoapicWrapper>(),
|
||||||
mpspec::MP_INTSRC => size_of::<MpcIntsrcWrapper>(),
|
mpspec::MP_INTSRC => mem::size_of::<MpcIntsrcWrapper>(),
|
||||||
mpspec::MP_LINTSRC => size_of::<MpcLintsrcWrapper>(),
|
mpspec::MP_LINTSRC => mem::size_of::<MpcLintsrcWrapper>(),
|
||||||
_ => panic!("unrecognized mpc table entry type: {type_}"),
|
_ => panic!("unrecognized mpc table entry type: {}", type_),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -320,7 +299,7 @@ mod unit_tests {
|
|||||||
let mem =
|
let mem =
|
||||||
GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(num_cpus))]).unwrap();
|
GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(num_cpus))]).unwrap();
|
||||||
|
|
||||||
setup_mptable(MPTABLE_START, &mem, num_cpus, None).unwrap();
|
setup_mptable(&mem, num_cpus).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -329,7 +308,7 @@ mod unit_tests {
|
|||||||
let mem = GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(num_cpus) - 1)])
|
let mem = GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(num_cpus) - 1)])
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
setup_mptable(MPTABLE_START, &mem, num_cpus, None).unwrap_err();
|
assert!(setup_mptable(&mem, num_cpus).is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -338,7 +317,7 @@ mod unit_tests {
|
|||||||
let mem =
|
let mem =
|
||||||
GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(num_cpus))]).unwrap();
|
GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(num_cpus))]).unwrap();
|
||||||
|
|
||||||
setup_mptable(MPTABLE_START, &mem, num_cpus, None).unwrap();
|
setup_mptable(&mem, num_cpus).unwrap();
|
||||||
|
|
||||||
let mpf_intel: MpfIntelWrapper = mem.read_obj(MPTABLE_START).unwrap();
|
let mpf_intel: MpfIntelWrapper = mem.read_obj(MPTABLE_START).unwrap();
|
||||||
|
|
||||||
@@ -354,31 +333,27 @@ mod unit_tests {
|
|||||||
let mem =
|
let mem =
|
||||||
GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(num_cpus))]).unwrap();
|
GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(num_cpus))]).unwrap();
|
||||||
|
|
||||||
setup_mptable(MPTABLE_START, &mem, num_cpus, None).unwrap();
|
setup_mptable(&mem, num_cpus).unwrap();
|
||||||
|
|
||||||
let mpf_intel: MpfIntelWrapper = mem.read_obj(MPTABLE_START).unwrap();
|
let mpf_intel: MpfIntelWrapper = mem.read_obj(MPTABLE_START).unwrap();
|
||||||
let mpc_offset = GuestAddress(mpf_intel.0.physptr as GuestUsize);
|
let mpc_offset = GuestAddress(mpf_intel.0.physptr as GuestUsize);
|
||||||
let mpc_table: MpcTableWrapper = mem.read_obj(mpc_offset).unwrap();
|
let mpc_table: MpcTableWrapper = mem.read_obj(mpc_offset).unwrap();
|
||||||
|
|
||||||
struct Sum(u8);
|
struct Sum(u8);
|
||||||
impl WriteVolatile for Sum {
|
impl io::Write for Sum {
|
||||||
fn write_volatile<B: BitmapSlice>(
|
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||||
&mut self,
|
for v in buf.iter() {
|
||||||
buf: &VolatileSlice<B>,
|
|
||||||
) -> result::Result<usize, VolatileMemoryError> {
|
|
||||||
let mut tmp = vec![0u8; buf.len()];
|
|
||||||
tmp.write_all_volatile(buf)?;
|
|
||||||
|
|
||||||
for v in tmp.iter() {
|
|
||||||
self.0 = self.0.wrapping_add(*v);
|
self.0 = self.0.wrapping_add(*v);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(buf.len())
|
Ok(buf.len())
|
||||||
}
|
}
|
||||||
|
fn flush(&mut self) -> io::Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut sum = Sum(0);
|
let mut sum = Sum(0);
|
||||||
mem.write_volatile_to(mpc_offset, &mut sum, mpc_table.0.length as usize)
|
mem.write_to(mpc_offset, &mut sum, mpc_table.0.length as usize)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(sum.0, 0);
|
assert_eq!(sum.0, 0);
|
||||||
}
|
}
|
||||||
@@ -387,12 +362,12 @@ mod unit_tests {
|
|||||||
fn cpu_entry_count() {
|
fn cpu_entry_count() {
|
||||||
let mem = GuestMemoryMmap::from_ranges(&[(
|
let mem = GuestMemoryMmap::from_ranges(&[(
|
||||||
MPTABLE_START,
|
MPTABLE_START,
|
||||||
compute_mp_size(MAX_SUPPORTED_CPUS_LEGACY),
|
compute_mp_size(MAX_SUPPORTED_CPUS as u8),
|
||||||
)])
|
)])
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
for i in 0..MAX_SUPPORTED_CPUS_LEGACY {
|
for i in 0..MAX_SUPPORTED_CPUS as u8 {
|
||||||
setup_mptable(MPTABLE_START, &mem, i, None).unwrap();
|
setup_mptable(&mem, i).unwrap();
|
||||||
|
|
||||||
let mpf_intel: MpfIntelWrapper = mem.read_obj(MPTABLE_START).unwrap();
|
let mpf_intel: MpfIntelWrapper = mem.read_obj(MPTABLE_START).unwrap();
|
||||||
let mpc_offset = GuestAddress(mpf_intel.0.physptr as GuestUsize);
|
let mpc_offset = GuestAddress(mpf_intel.0.physptr as GuestUsize);
|
||||||
@@ -402,7 +377,7 @@ mod unit_tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let mut entry_offset = mpc_offset
|
let mut entry_offset = mpc_offset
|
||||||
.checked_add(size_of::<MpcTableWrapper>() as GuestUsize)
|
.checked_add(mem::size_of::<MpcTableWrapper>() as GuestUsize)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let mut cpu_count = 0;
|
let mut cpu_count = 0;
|
||||||
while entry_offset < mpc_end {
|
while entry_offset < mpc_end {
|
||||||
@@ -421,9 +396,11 @@ mod unit_tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn cpu_entry_count_max() {
|
fn cpu_entry_count_max() {
|
||||||
let cpus = MAX_SUPPORTED_CPUS_LEGACY + 1;
|
let cpus = MAX_SUPPORTED_CPUS + 1;
|
||||||
let mem = GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(cpus))]).unwrap();
|
let mem =
|
||||||
|
GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(cpus as u8))]).unwrap();
|
||||||
|
|
||||||
setup_mptable(MPTABLE_START, &mem, cpus, None).unwrap();
|
let result = setup_mptable(&mem, cpus as u8);
|
||||||
|
assert!(result.is_err());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,67 +1,47 @@
|
|||||||
// Copyright © 2020, Oracle and/or its affiliates.
|
|
||||||
//
|
|
||||||
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
//
|
//
|
||||||
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE-BSD-3-Clause file.
|
// found in the LICENSE-BSD-3-Clause file.
|
||||||
use std::result;
|
|
||||||
|
|
||||||
use hypervisor::arch::x86::gdt::{gdt_entry, segment_from_gdt};
|
use std::{mem, result};
|
||||||
use hypervisor::arch::x86::regs::CR0_PE;
|
|
||||||
use hypervisor::arch::x86::{FpuState, SpecialRegisters};
|
|
||||||
#[cfg(all(feature = "kvm", not(feature = "sev_snp")))]
|
|
||||||
use log::error;
|
|
||||||
use thiserror::Error;
|
|
||||||
use vm_memory::{Address, Bytes, GuestMemoryBackend, GuestMemoryError};
|
|
||||||
|
|
||||||
use crate::layout::{
|
use super::gdt::{gdt_entry, kvm_segment_from_gdt};
|
||||||
BOOT_GDT_START, BOOT_IDT_START, BOOT_STACK_POINTER, PVH_INFO_START, ZERO_PAGE_START,
|
use arch_gen::x86::msr_index;
|
||||||
};
|
use kvm_bindings::{kvm_fpu, kvm_msr_entry, kvm_regs, kvm_sregs, Msrs};
|
||||||
use crate::{EntryPoint, GuestMemoryMmap};
|
use kvm_ioctls::VcpuFd;
|
||||||
|
use layout::{BOOT_GDT_START, BOOT_IDT_START, PDE_START, PDPTE_START, PML4_START};
|
||||||
|
use vm_memory::{Address, Bytes, GuestMemory, GuestMemoryError, GuestMemoryMmap};
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
// MTRR constants
|
||||||
|
const MTRR_ENABLE: u64 = 0x800; // IA32_MTRR_DEF_TYPE MSR: E (MTRRs enabled) flag, bit 11
|
||||||
|
const MTRR_MEM_TYPE_WB: u64 = 0x6;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
/// Failed to get SREGs for this CPU.
|
/// Failed to get SREGs for this CPU.
|
||||||
#[error("Failed to get SREGs for this CPU")]
|
GetStatusRegisters(kvm_ioctls::Error),
|
||||||
GetStatusRegisters(#[source] hypervisor::HypervisorCpuError),
|
|
||||||
/// Failed to set base registers for this CPU.
|
/// Failed to set base registers for this CPU.
|
||||||
#[error("Failed to set base registers for this CPU")]
|
SetBaseRegisters(kvm_ioctls::Error),
|
||||||
SetBaseRegisters(#[source] hypervisor::HypervisorCpuError),
|
|
||||||
/// Failed to configure the FPU.
|
/// Failed to configure the FPU.
|
||||||
#[error("Failed to configure the FPU")]
|
SetFPURegisters(kvm_ioctls::Error),
|
||||||
SetFpuRegisters(#[source] hypervisor::HypervisorCpuError),
|
|
||||||
/// Setting up MSRs failed.
|
/// Setting up MSRs failed.
|
||||||
#[error("Setting up MSRs failed")]
|
SetModelSpecificRegisters(kvm_ioctls::Error),
|
||||||
SetModelSpecificRegisters(#[source] hypervisor::HypervisorCpuError),
|
|
||||||
/// Setting up MSRs failed because not all setup entries were set.
|
|
||||||
#[error("Some MSRs could not be set")]
|
|
||||||
SetModelSpecificRegistersAll,
|
|
||||||
/// Failed to set SREGs for this CPU.
|
/// Failed to set SREGs for this CPU.
|
||||||
#[error("Failed to set SREGs for this CPU")]
|
SetStatusRegisters(kvm_ioctls::Error),
|
||||||
SetStatusRegisters(#[source] hypervisor::HypervisorCpuError),
|
|
||||||
/// Checking the GDT address failed.
|
/// Checking the GDT address failed.
|
||||||
#[error("Checking the GDT address failed")]
|
CheckGDTAddr,
|
||||||
CheckGdtAddr,
|
|
||||||
/// Writing the GDT to RAM failed.
|
/// Writing the GDT to RAM failed.
|
||||||
#[error("Writing the GDT to RAM failed")]
|
WriteGDT(GuestMemoryError),
|
||||||
WriteGdt(#[source] GuestMemoryError),
|
|
||||||
/// Writing the IDT to RAM failed.
|
/// Writing the IDT to RAM failed.
|
||||||
#[error("Writing the IDT to RAM failed")]
|
WriteIDT(GuestMemoryError),
|
||||||
WriteIdt(#[source] GuestMemoryError),
|
|
||||||
/// Writing PDPTE to RAM failed.
|
/// Writing PDPTE to RAM failed.
|
||||||
#[error("Writing PDPTE to RAM failed")]
|
WritePDPTEAddress(GuestMemoryError),
|
||||||
WritePdpteAddress(#[source] GuestMemoryError),
|
|
||||||
/// Writing PDE to RAM failed.
|
/// Writing PDE to RAM failed.
|
||||||
#[error("Writing PDE to RAM failed")]
|
WritePDEAddress(GuestMemoryError),
|
||||||
WritePdeAddress(#[source] GuestMemoryError),
|
|
||||||
/// Writing PML4 to RAM failed.
|
/// Writing PML4 to RAM failed.
|
||||||
#[error("Writing PML4 to RAM failed")]
|
WritePML4Address(GuestMemoryError),
|
||||||
WritePml4Address(#[source] GuestMemoryError),
|
|
||||||
/// Writing PML5 to RAM failed.
|
|
||||||
#[error("Writing PML5 to RAM failed")]
|
|
||||||
WritePml5Address(#[source] GuestMemoryError),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Result<T> = result::Result<T, Error>;
|
pub type Result<T> = result::Result<T, Error>;
|
||||||
@@ -71,14 +51,14 @@ pub type Result<T> = result::Result<T, Error>;
|
|||||||
/// # Arguments
|
/// # Arguments
|
||||||
///
|
///
|
||||||
/// * `vcpu` - Structure for the VCPU that holds the VCPU's fd.
|
/// * `vcpu` - Structure for the VCPU that holds the VCPU's fd.
|
||||||
pub fn setup_fpu(vcpu: &dyn hypervisor::Vcpu) -> Result<()> {
|
pub fn setup_fpu(vcpu: &VcpuFd) -> Result<()> {
|
||||||
let fpu: FpuState = FpuState {
|
let fpu: kvm_fpu = kvm_fpu {
|
||||||
fcw: 0x37f,
|
fcw: 0x37f,
|
||||||
mxcsr: 0x1f80,
|
mxcsr: 0x1f80,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
vcpu.set_fpu(&fpu).map_err(Error::SetFpuRegisters)
|
vcpu.set_fpu(&fpu).map_err(Error::SetFPURegisters)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Configure Model Specific Registers (MSRs) for a given CPU.
|
/// Configure Model Specific Registers (MSRs) for a given CPU.
|
||||||
@@ -86,31 +66,10 @@ pub fn setup_fpu(vcpu: &dyn hypervisor::Vcpu) -> Result<()> {
|
|||||||
/// # Arguments
|
/// # Arguments
|
||||||
///
|
///
|
||||||
/// * `vcpu` - Structure for the VCPU that holds the VCPU's fd.
|
/// * `vcpu` - Structure for the VCPU that holds the VCPU's fd.
|
||||||
#[cfg_attr(
|
pub fn setup_msrs(vcpu: &VcpuFd) -> Result<()> {
|
||||||
any(not(feature = "kvm"), feature = "sev_snp"),
|
vcpu.set_msrs(&create_msr_entries())
|
||||||
allow(unused_variables)
|
|
||||||
)]
|
|
||||||
pub fn setup_msrs(vcpu: &dyn hypervisor::Vcpu) -> Result<()> {
|
|
||||||
let setup_entries = vcpu.boot_msr_entries();
|
|
||||||
let num_msrs_set = vcpu
|
|
||||||
.set_msrs(&setup_entries)
|
|
||||||
.map_err(Error::SetModelSpecificRegisters)?;
|
.map_err(Error::SetModelSpecificRegisters)?;
|
||||||
|
|
||||||
// Check that all setup entries were set. We can only do this for KVM
|
|
||||||
// (when SEV-SNP is not enabled) as MSHV always returns Ok(0) on success.
|
|
||||||
#[cfg(all(feature = "kvm", not(feature = "sev_snp")))]
|
|
||||||
if matches!(vcpu.hypervisor_type(), hypervisor::HypervisorType::Kvm)
|
|
||||||
&& num_msrs_set != setup_entries.len()
|
|
||||||
{
|
|
||||||
for msr in &setup_entries[num_msrs_set..] {
|
|
||||||
error!(
|
|
||||||
"Could not set MSR with register address={:#x} and value={:#x}",
|
|
||||||
msr.index, msr.data
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Err(Error::SetModelSpecificRegistersAll);
|
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,22 +78,19 @@ pub fn setup_msrs(vcpu: &dyn hypervisor::Vcpu) -> Result<()> {
|
|||||||
/// # Arguments
|
/// # Arguments
|
||||||
///
|
///
|
||||||
/// * `vcpu` - Structure for the VCPU that holds the VCPU's fd.
|
/// * `vcpu` - Structure for the VCPU that holds the VCPU's fd.
|
||||||
/// * `entry_point` - Description of the boot entry to set up.
|
/// * `boot_ip` - Starting instruction pointer.
|
||||||
pub fn setup_regs(vcpu: &dyn hypervisor::Vcpu, entry_point: EntryPoint) -> Result<()> {
|
/// * `boot_sp` - Starting stack pointer.
|
||||||
let mut regs = vcpu.create_standard_regs();
|
/// * `boot_si` - Must point to zero page address per Linux ABI.
|
||||||
match entry_point.setup_header {
|
pub fn setup_regs(vcpu: &VcpuFd, boot_ip: u64, boot_sp: u64, boot_si: u64) -> Result<()> {
|
||||||
None => {
|
let regs: kvm_regs = kvm_regs {
|
||||||
regs.set_rflags(0x0000000000000002u64);
|
rflags: 0x0000000000000002u64,
|
||||||
regs.set_rip(entry_point.entry_addr.raw_value());
|
rip: boot_ip,
|
||||||
regs.set_rbx(PVH_INFO_START.raw_value());
|
rsp: boot_sp,
|
||||||
}
|
rbp: boot_sp,
|
||||||
Some(_) => {
|
rsi: boot_si,
|
||||||
regs.set_rflags(0x0000000000000002u64);
|
..Default::default()
|
||||||
regs.set_rip(entry_point.entry_addr.raw_value());
|
};
|
||||||
regs.set_rsp(BOOT_STACK_POINTER.raw_value());
|
|
||||||
regs.set_rsi(ZERO_PAGE_START.raw_value());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
vcpu.set_regs(®s).map_err(Error::SetBaseRegisters)
|
vcpu.set_regs(®s).map_err(Error::SetBaseRegisters)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,25 +100,31 @@ pub fn setup_regs(vcpu: &dyn hypervisor::Vcpu, entry_point: EntryPoint) -> Resul
|
|||||||
///
|
///
|
||||||
/// * `mem` - The memory that will be passed to the guest.
|
/// * `mem` - The memory that will be passed to the guest.
|
||||||
/// * `vcpu` - Structure for the VCPU that holds the VCPU's fd.
|
/// * `vcpu` - Structure for the VCPU that holds the VCPU's fd.
|
||||||
pub fn setup_sregs(
|
pub fn setup_sregs(mem: &GuestMemoryMmap, vcpu: &VcpuFd) -> Result<()> {
|
||||||
mem: &GuestMemoryMmap,
|
let mut sregs: kvm_sregs = vcpu.get_sregs().map_err(Error::GetStatusRegisters)?;
|
||||||
vcpu: &dyn hypervisor::Vcpu,
|
|
||||||
enable_x2_apic_mode: bool,
|
configure_segments_and_sregs(mem, &mut sregs)?;
|
||||||
) -> Result<()> {
|
setup_page_tables(mem, &mut sregs)?; // TODO(dgreid) - Can this be done once per system instead?
|
||||||
let mut sregs: SpecialRegisters = vcpu.get_sregs().map_err(Error::GetStatusRegisters)?;
|
|
||||||
configure_segments_and_sregs(mem, &mut sregs, enable_x2_apic_mode)?;
|
|
||||||
vcpu.set_sregs(&sregs).map_err(Error::SetStatusRegisters)
|
vcpu.set_sregs(&sregs).map_err(Error::SetStatusRegisters)
|
||||||
}
|
}
|
||||||
|
|
||||||
const BOOT_GDT_MAX: usize = 4;
|
const BOOT_GDT_MAX: usize = 4;
|
||||||
|
|
||||||
|
const EFER_LMA: u64 = 0x400;
|
||||||
|
const EFER_LME: u64 = 0x100;
|
||||||
|
|
||||||
|
const X86_CR0_PE: u64 = 0x1;
|
||||||
|
const X86_CR0_PG: u64 = 0x80000000;
|
||||||
|
const X86_CR4_PAE: u64 = 0x20;
|
||||||
|
|
||||||
fn write_gdt_table(table: &[u64], guest_mem: &GuestMemoryMmap) -> Result<()> {
|
fn write_gdt_table(table: &[u64], guest_mem: &GuestMemoryMmap) -> Result<()> {
|
||||||
let boot_gdt_addr = BOOT_GDT_START;
|
let boot_gdt_addr = BOOT_GDT_START;
|
||||||
for (index, entry) in table.iter().enumerate() {
|
for (index, entry) in table.iter().enumerate() {
|
||||||
let addr = guest_mem
|
let addr = guest_mem
|
||||||
.checked_offset(boot_gdt_addr, index * size_of::<u64>())
|
.checked_offset(boot_gdt_addr, index * mem::size_of::<u64>())
|
||||||
.ok_or(Error::CheckGdtAddr)?;
|
.ok_or(Error::CheckGDTAddr)?;
|
||||||
guest_mem.write_obj(*entry, addr).map_err(Error::WriteGdt)?;
|
guest_mem.write_obj(*entry, addr).map_err(Error::WriteGDT)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -171,36 +133,29 @@ fn write_idt_value(val: u64, guest_mem: &GuestMemoryMmap) -> Result<()> {
|
|||||||
let boot_idt_addr = BOOT_IDT_START;
|
let boot_idt_addr = BOOT_IDT_START;
|
||||||
guest_mem
|
guest_mem
|
||||||
.write_obj(val, boot_idt_addr)
|
.write_obj(val, boot_idt_addr)
|
||||||
.map_err(Error::WriteIdt)
|
.map_err(Error::WriteIDT)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn configure_segments_and_sregs(
|
fn configure_segments_and_sregs(mem: &GuestMemoryMmap, sregs: &mut kvm_sregs) -> Result<()> {
|
||||||
mem: &GuestMemoryMmap,
|
let gdt_table: [u64; BOOT_GDT_MAX as usize] = [
|
||||||
sregs: &mut SpecialRegisters,
|
|
||||||
enable_x2_apic_mode: bool,
|
|
||||||
) -> Result<()> {
|
|
||||||
let gdt_table: [u64; BOOT_GDT_MAX] = {
|
|
||||||
// Configure GDT entries as specified by PVH boot protocol
|
|
||||||
[
|
|
||||||
gdt_entry(0, 0, 0), // NULL
|
gdt_entry(0, 0, 0), // NULL
|
||||||
gdt_entry(0xc09b, 0, 0xffffffff), // CODE
|
gdt_entry(0xa09b, 0, 0xfffff), // CODE
|
||||||
gdt_entry(0xc093, 0, 0xffffffff), // DATA
|
gdt_entry(0xc093, 0, 0xfffff), // DATA
|
||||||
gdt_entry(0x008b, 0, 0x67), // TSS
|
gdt_entry(0x808b, 0, 0xfffff), // TSS
|
||||||
]
|
];
|
||||||
};
|
|
||||||
|
|
||||||
let code_seg = segment_from_gdt(gdt_table[1], 1);
|
let code_seg = kvm_segment_from_gdt(gdt_table[1], 1);
|
||||||
let data_seg = segment_from_gdt(gdt_table[2], 2);
|
let data_seg = kvm_segment_from_gdt(gdt_table[2], 2);
|
||||||
let tss_seg = segment_from_gdt(gdt_table[3], 3);
|
let tss_seg = kvm_segment_from_gdt(gdt_table[3], 3);
|
||||||
|
|
||||||
// Write segments
|
// Write segments
|
||||||
write_gdt_table(&gdt_table[..], mem)?;
|
write_gdt_table(&gdt_table[..], mem)?;
|
||||||
sregs.gdt.base = BOOT_GDT_START.raw_value();
|
sregs.gdt.base = BOOT_GDT_START.raw_value();
|
||||||
sregs.gdt.limit = size_of_val(&gdt_table) as u16 - 1;
|
sregs.gdt.limit = mem::size_of_val(&gdt_table) as u16 - 1;
|
||||||
|
|
||||||
write_idt_value(0, mem)?;
|
write_idt_value(0, mem)?;
|
||||||
sregs.idt.base = BOOT_IDT_START.raw_value();
|
sregs.idt.base = BOOT_IDT_START.raw_value();
|
||||||
sregs.idt.limit = size_of::<u64>() as u16 - 1;
|
sregs.idt.limit = mem::size_of::<u64>() as u16 - 1;
|
||||||
|
|
||||||
sregs.cs = code_seg;
|
sregs.cs = code_seg;
|
||||||
sregs.ds = data_seg;
|
sregs.ds = data_seg;
|
||||||
@@ -210,25 +165,111 @@ pub fn configure_segments_and_sregs(
|
|||||||
sregs.ss = data_seg;
|
sregs.ss = data_seg;
|
||||||
sregs.tr = tss_seg;
|
sregs.tr = tss_seg;
|
||||||
|
|
||||||
sregs.cr0 = CR0_PE;
|
/* 64-bit protected mode */
|
||||||
sregs.cr4 = 0;
|
sregs.cr0 |= X86_CR0_PE;
|
||||||
|
sregs.efer |= EFER_LME | EFER_LMA;
|
||||||
if enable_x2_apic_mode {
|
|
||||||
const X2APIC_ENABLE_BIT: u64 = 1 << 10;
|
|
||||||
sregs.apic_base |= X2APIC_ENABLE_BIT;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn setup_page_tables(mem: &GuestMemoryMmap, sregs: &mut kvm_sregs) -> Result<()> {
|
||||||
|
// Puts PML4 right after zero page but aligned to 4k.
|
||||||
|
|
||||||
|
// Entry covering VA [0..512GB)
|
||||||
|
mem.write_obj(PDPTE_START.raw_value() | 0x03, PML4_START)
|
||||||
|
.map_err(Error::WritePML4Address)?;
|
||||||
|
|
||||||
|
// Entry covering VA [0..1GB)
|
||||||
|
mem.write_obj(PDE_START.raw_value() | 0x03, PDPTE_START)
|
||||||
|
.map_err(Error::WritePDPTEAddress)?;
|
||||||
|
// 512 2MB entries together covering VA [0..1GB). Note we are assuming
|
||||||
|
// CPU supports 2MB pages (/proc/cpuinfo has 'pse'). All modern CPUs do.
|
||||||
|
for i in 0..512 {
|
||||||
|
mem.write_obj((i << 21) + 0x83u64, PDE_START.unchecked_add(i * 8))
|
||||||
|
.map_err(Error::WritePDEAddress)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
sregs.cr3 = PML4_START.raw_value();
|
||||||
|
sregs.cr4 |= X86_CR4_PAE;
|
||||||
|
sregs.cr0 |= X86_CR0_PG;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_msr_entries() -> Msrs {
|
||||||
|
let mut entries = Vec::<kvm_msr_entry>::new();
|
||||||
|
|
||||||
|
entries.push(kvm_msr_entry {
|
||||||
|
index: msr_index::MSR_IA32_SYSENTER_CS,
|
||||||
|
data: 0x0,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
entries.push(kvm_msr_entry {
|
||||||
|
index: msr_index::MSR_IA32_SYSENTER_ESP,
|
||||||
|
data: 0x0,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
entries.push(kvm_msr_entry {
|
||||||
|
index: msr_index::MSR_IA32_SYSENTER_EIP,
|
||||||
|
data: 0x0,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
// x86_64 specific msrs, we only run on x86_64 not x86.
|
||||||
|
entries.push(kvm_msr_entry {
|
||||||
|
index: msr_index::MSR_STAR,
|
||||||
|
data: 0x0,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
entries.push(kvm_msr_entry {
|
||||||
|
index: msr_index::MSR_CSTAR,
|
||||||
|
data: 0x0,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
entries.push(kvm_msr_entry {
|
||||||
|
index: msr_index::MSR_KERNEL_GS_BASE,
|
||||||
|
data: 0x0,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
entries.push(kvm_msr_entry {
|
||||||
|
index: msr_index::MSR_SYSCALL_MASK,
|
||||||
|
data: 0x0,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
entries.push(kvm_msr_entry {
|
||||||
|
index: msr_index::MSR_LSTAR,
|
||||||
|
data: 0x0,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
// end of x86_64 specific code
|
||||||
|
entries.push(kvm_msr_entry {
|
||||||
|
index: msr_index::MSR_IA32_TSC,
|
||||||
|
data: 0x0,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
entries.push(kvm_msr_entry {
|
||||||
|
index: msr_index::MSR_IA32_MISC_ENABLE,
|
||||||
|
data: msr_index::MSR_IA32_MISC_ENABLE_FAST_STRING as u64,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
entries.push(kvm_msr_entry {
|
||||||
|
index: msr_index::MSR_MTRRdefType,
|
||||||
|
data: MTRR_ENABLE | MTRR_MEM_TYPE_WB,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
Msrs::from_entries(&entries)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod unit_tests {
|
mod tests {
|
||||||
use vm_memory::GuestAddress;
|
extern crate kvm_ioctls;
|
||||||
|
extern crate vm_memory;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use kvm_ioctls::Kvm;
|
||||||
|
use vm_memory::{GuestAddress, GuestMemoryMmap};
|
||||||
|
|
||||||
fn create_guest_mem() -> GuestMemoryMmap {
|
fn create_guest_mem() -> GuestMemoryMmap {
|
||||||
GuestMemoryMmap::from_ranges(&[(GuestAddress(0), 0x10000)]).unwrap()
|
GuestMemoryMmap::from_ranges(&vec![(GuestAddress(0), 0x10000)]).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_u64(gm: &GuestMemoryMmap, offset: GuestAddress) -> u64 {
|
fn read_u64(gm: &GuestMemoryMmap, offset: GuestAddress) -> u64 {
|
||||||
@@ -237,12 +278,13 @@ mod unit_tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn segments_and_sregs() {
|
fn segments_and_sregs() {
|
||||||
let mut sregs: SpecialRegisters = Default::default();
|
let mut sregs: kvm_sregs = Default::default();
|
||||||
let gm = create_guest_mem();
|
let gm = create_guest_mem();
|
||||||
configure_segments_and_sregs(&gm, &mut sregs, false).unwrap();
|
configure_segments_and_sregs(&gm, &mut sregs).unwrap();
|
||||||
|
|
||||||
assert_eq!(0x0, read_u64(&gm, BOOT_GDT_START));
|
assert_eq!(0x0, read_u64(&gm, BOOT_GDT_START));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
0xcf9b000000ffff,
|
0xaf9b000000ffff,
|
||||||
read_u64(&gm, BOOT_GDT_START.unchecked_add(8))
|
read_u64(&gm, BOOT_GDT_START.unchecked_add(8))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -250,23 +292,132 @@ mod unit_tests {
|
|||||||
read_u64(&gm, BOOT_GDT_START.unchecked_add(16))
|
read_u64(&gm, BOOT_GDT_START.unchecked_add(16))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
0x8b0000000067,
|
0x8f8b000000ffff,
|
||||||
read_u64(&gm, BOOT_GDT_START.unchecked_add(24))
|
read_u64(&gm, BOOT_GDT_START.unchecked_add(24))
|
||||||
);
|
);
|
||||||
assert_eq!(0x0, read_u64(&gm, BOOT_IDT_START));
|
assert_eq!(0x0, read_u64(&gm, BOOT_IDT_START));
|
||||||
|
|
||||||
assert_eq!(0, sregs.cs.base);
|
assert_eq!(0, sregs.cs.base);
|
||||||
assert_eq!(0xffffffff, sregs.ds.limit);
|
assert_eq!(0xfffff, sregs.ds.limit);
|
||||||
assert_eq!(0x10, sregs.es.selector);
|
assert_eq!(0x10, sregs.es.selector);
|
||||||
assert_eq!(1, sregs.fs.present);
|
assert_eq!(1, sregs.fs.present);
|
||||||
assert_eq!(1, sregs.gs.g);
|
assert_eq!(1, sregs.gs.g);
|
||||||
assert_eq!(0, sregs.ss.avl);
|
assert_eq!(0, sregs.ss.avl);
|
||||||
assert_eq!(0, sregs.tr.base);
|
assert_eq!(0, sregs.tr.base);
|
||||||
assert_eq!(0, sregs.tr.g);
|
assert_eq!(0xfffff, sregs.tr.limit);
|
||||||
assert_eq!(0x67, sregs.tr.limit);
|
|
||||||
assert_eq!(0xb, sregs.tr.type_);
|
|
||||||
assert_eq!(0, sregs.tr.avl);
|
assert_eq!(0, sregs.tr.avl);
|
||||||
assert_eq!(CR0_PE, sregs.cr0);
|
assert_eq!(X86_CR0_PE, sregs.cr0);
|
||||||
assert_eq!(0, sregs.cr4);
|
assert_eq!(EFER_LME | EFER_LMA, sregs.efer);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn page_tables() {
|
||||||
|
let mut sregs: kvm_sregs = Default::default();
|
||||||
|
let gm = create_guest_mem();
|
||||||
|
setup_page_tables(&gm, &mut sregs).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(0xa003, read_u64(&gm, PML4_START));
|
||||||
|
assert_eq!(0xb003, read_u64(&gm, PDPTE_START));
|
||||||
|
for i in 0..512 {
|
||||||
|
assert_eq!(
|
||||||
|
(i << 21) + 0x83u64,
|
||||||
|
read_u64(&gm, PDE_START.unchecked_add(i * 8))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(PML4_START.raw_value(), sregs.cr3);
|
||||||
|
assert_eq!(X86_CR4_PAE, sregs.cr4);
|
||||||
|
assert_eq!(X86_CR0_PG, sregs.cr0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_setup_fpu() {
|
||||||
|
let kvm = Kvm::new().unwrap();
|
||||||
|
let vm = kvm.create_vm().unwrap();
|
||||||
|
let vcpu = vm.create_vcpu(0).unwrap();
|
||||||
|
setup_fpu(&vcpu).unwrap();
|
||||||
|
|
||||||
|
let expected_fpu: kvm_fpu = kvm_fpu {
|
||||||
|
fcw: 0x37f,
|
||||||
|
mxcsr: 0x1f80,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let actual_fpu: kvm_fpu = vcpu.get_fpu().unwrap();
|
||||||
|
// TODO: auto-generate kvm related structures with PartialEq on.
|
||||||
|
assert_eq!(expected_fpu.fcw, actual_fpu.fcw);
|
||||||
|
// Setting the mxcsr register from kvm_fpu inside setup_fpu does not influence anything.
|
||||||
|
// See 'kvm_arch_vcpu_ioctl_set_fpu' from arch/x86/kvm/x86.c.
|
||||||
|
// The mxcsr will stay 0 and the assert below fails. Decide whether or not we should
|
||||||
|
// remove it at all.
|
||||||
|
// assert!(expected_fpu.mxcsr == actual_fpu.mxcsr);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_setup_msrs() {
|
||||||
|
let kvm = Kvm::new().unwrap();
|
||||||
|
let vm = kvm.create_vm().unwrap();
|
||||||
|
let vcpu = vm.create_vcpu(0).unwrap();
|
||||||
|
setup_msrs(&vcpu).unwrap();
|
||||||
|
|
||||||
|
// This test will check against the last MSR entry configured (the tenth one).
|
||||||
|
// See create_msr_entries for details.
|
||||||
|
let mut msrs = Msrs::from_entries(&[kvm_msr_entry {
|
||||||
|
index: msr_index::MSR_IA32_MISC_ENABLE,
|
||||||
|
..Default::default()
|
||||||
|
}]);
|
||||||
|
|
||||||
|
// get_msrs returns the number of msrs that it succeed in reading. We only want to read 1
|
||||||
|
// in this test case scenario.
|
||||||
|
let read_msrs = vcpu.get_msrs(&mut msrs).unwrap();
|
||||||
|
assert_eq!(read_msrs, 1);
|
||||||
|
|
||||||
|
// Official entries that were setup when we did setup_msrs. We need to assert that the
|
||||||
|
// tenth one (i.e the one with index msr_index::MSR_IA32_MISC_ENABLE has the data we
|
||||||
|
// expect.
|
||||||
|
let entry_vec = create_msr_entries();
|
||||||
|
assert_eq!(entry_vec.as_slice()[9], msrs.as_slice()[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_setup_regs() {
|
||||||
|
let kvm = Kvm::new().unwrap();
|
||||||
|
let vm = kvm.create_vm().unwrap();
|
||||||
|
let vcpu = vm.create_vcpu(0).unwrap();
|
||||||
|
|
||||||
|
let expected_regs: kvm_regs = kvm_regs {
|
||||||
|
rflags: 0x0000000000000002u64,
|
||||||
|
rip: 1,
|
||||||
|
rsp: 2,
|
||||||
|
rbp: 2,
|
||||||
|
rsi: 3,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
setup_regs(
|
||||||
|
&vcpu,
|
||||||
|
expected_regs.rip,
|
||||||
|
expected_regs.rsp,
|
||||||
|
expected_regs.rsi,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let actual_regs: kvm_regs = vcpu.get_regs().unwrap();
|
||||||
|
assert_eq!(actual_regs, expected_regs);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_setup_sregs() {
|
||||||
|
let kvm = Kvm::new().unwrap();
|
||||||
|
let vm = kvm.create_vm().unwrap();
|
||||||
|
let vcpu = vm.create_vcpu(0).unwrap();
|
||||||
|
|
||||||
|
let mut expected_sregs: kvm_sregs = vcpu.get_sregs().unwrap();
|
||||||
|
let gm = create_guest_mem();
|
||||||
|
configure_segments_and_sregs(&gm, &mut expected_sregs).unwrap();
|
||||||
|
setup_page_tables(&gm, &mut expected_sregs).unwrap();
|
||||||
|
|
||||||
|
setup_sregs(&gm, &vcpu).unwrap();
|
||||||
|
let actual_sregs: kvm_sregs = vcpu.get_sregs().unwrap();
|
||||||
|
assert_eq!(expected_sregs, actual_sregs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,706 +0,0 @@
|
|||||||
// Copyright © 2020 Intel Corporation
|
|
||||||
//
|
|
||||||
// Copyright 2019 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 std::result;
|
|
||||||
|
|
||||||
use thiserror::Error;
|
|
||||||
use uuid::Uuid;
|
|
||||||
use vm_memory::{Address, ByteValued, Bytes, GuestAddress};
|
|
||||||
|
|
||||||
use crate::GuestMemoryMmap;
|
|
||||||
use crate::layout::SMBIOS_START;
|
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
|
||||||
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(#[source] vm_memory::GuestMemoryError),
|
|
||||||
/// Failure to write additional data to memory
|
|
||||||
#[error("Failure to write additional data to memory")]
|
|
||||||
WriteData(#[source] vm_memory::GuestMemoryError),
|
|
||||||
/// Failure to parse uuid, uuid format may be error
|
|
||||||
#[error("Failure to parse uuid: {1}")]
|
|
||||||
ParseUuid(#[source] uuid::Error, String),
|
|
||||||
/// SMBIOS string index overflow (u8 limit reached).
|
|
||||||
#[error("SMBIOS string index overflow (u8 limit reached: {})", u8::MAX)]
|
|
||||||
TooManyStrings,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type Result<T> = result::Result<T, Error>;
|
|
||||||
|
|
||||||
// Constants sourced from SMBIOS Spec 3.9.0.
|
|
||||||
const SM3_MAGIC_IDENT: &[u8; 5usize] = b"_SM3_";
|
|
||||||
const BIOS_INFORMATION: u8 = 0;
|
|
||||||
const SYSTEM_INFORMATION: u8 = 1;
|
|
||||||
const OEM_STRINGS: u8 = 11;
|
|
||||||
const SYSTEM_ENCLOSURE: u8 = 3;
|
|
||||||
const END_OF_TABLE: u8 = 127;
|
|
||||||
const SYSTEM_WAKE_UP_TYPE_UNKNOWN: u8 = 0x02;
|
|
||||||
const CHASSIS_TYPE_UNKNOWN: u8 = 0x02;
|
|
||||||
const CHASSIS_STATE_UNKNOWN: u8 = 0x02;
|
|
||||||
const CHASSIS_SECURITY_STATUS_NONE: u8 = 0x03;
|
|
||||||
const PCI_SUPPORTED: u64 = 1 << 7;
|
|
||||||
const IS_VIRTUAL_MACHINE: u8 = 1 << 4;
|
|
||||||
pub const DEFAULT_SYSTEM_MANUFACTURER: &str = "Cloud Hypervisor";
|
|
||||||
pub const DEFAULT_SYSTEM_PRODUCT_NAME: &str = "cloud-hypervisor";
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
|
||||||
pub struct SmbiosConfig {
|
|
||||||
pub system: Option<SmbiosSystem>,
|
|
||||||
pub chassis: Option<SmbiosChassisConfig>,
|
|
||||||
pub oem_strings: Box<[String]>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
|
||||||
pub struct SmbiosSystem {
|
|
||||||
pub manufacturer: Option<String>,
|
|
||||||
pub product_name: Option<String>,
|
|
||||||
pub version: Option<String>,
|
|
||||||
pub serial_number: Option<String>,
|
|
||||||
pub uuid: Option<String>,
|
|
||||||
pub sku_number: Option<String>,
|
|
||||||
pub family: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
|
||||||
pub struct SmbiosChassisConfig {
|
|
||||||
pub asset_tag: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SmbiosConfig {
|
|
||||||
pub fn is_empty(&self) -> bool {
|
|
||||||
*self == Self::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn compute_checksum<T: Copy + ByteValued>(v: &T) -> u8 {
|
|
||||||
let mut checksum: u8 = 0;
|
|
||||||
for i in v.as_slice().iter() {
|
|
||||||
checksum = checksum.wrapping_add(*i);
|
|
||||||
}
|
|
||||||
(!checksum).wrapping_add(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(C, packed)]
|
|
||||||
#[derive(Default, Copy, Clone)]
|
|
||||||
struct Smbios30Entrypoint {
|
|
||||||
signature: [u8; 5usize],
|
|
||||||
checksum: u8,
|
|
||||||
length: u8,
|
|
||||||
majorver: u8,
|
|
||||||
minorver: u8,
|
|
||||||
docrev: u8,
|
|
||||||
revision: u8,
|
|
||||||
reserved: u8,
|
|
||||||
max_size: u32,
|
|
||||||
physptr: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(C, packed)]
|
|
||||||
#[derive(Default, Copy, Clone)]
|
|
||||||
struct SmbiosBiosInfo {
|
|
||||||
r#type: u8,
|
|
||||||
length: u8,
|
|
||||||
handle: u16,
|
|
||||||
vendor: u8,
|
|
||||||
version: u8,
|
|
||||||
start_addr: u16,
|
|
||||||
release_date: u8,
|
|
||||||
rom_size: u8,
|
|
||||||
characteristics: u64,
|
|
||||||
characteristics_ext1: u8,
|
|
||||||
characteristics_ext2: u8,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(C, packed)]
|
|
||||||
#[derive(Default, Copy, Clone)]
|
|
||||||
struct SmbiosSysInfo {
|
|
||||||
r#type: u8,
|
|
||||||
length: u8,
|
|
||||||
handle: u16,
|
|
||||||
manufacturer: u8,
|
|
||||||
product_name: u8,
|
|
||||||
version: u8,
|
|
||||||
serial_number: u8,
|
|
||||||
uuid: [u8; 16usize],
|
|
||||||
wake_up_type: u8,
|
|
||||||
sku: u8,
|
|
||||||
family: u8,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(C, packed)]
|
|
||||||
#[derive(Default, Copy, Clone)]
|
|
||||||
struct SmbiosOemStrings {
|
|
||||||
r#type: u8,
|
|
||||||
length: u8,
|
|
||||||
handle: u16,
|
|
||||||
count: u8,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// SMBIOS Chassis Table (Type 3) as defined in DMTF SMBIOS 3.9.0:
|
|
||||||
/// https://www.dmtf.org/sites/default/files/standards/documents/DSP0134_3.9.0.pdf
|
|
||||||
/// Note: trailing fields are omitted, so this structure is not complete.
|
|
||||||
#[repr(C, packed)]
|
|
||||||
#[derive(Default, Copy, Clone)]
|
|
||||||
struct SmbiosChassis {
|
|
||||||
r#type: u8,
|
|
||||||
length: u8,
|
|
||||||
handle: u16,
|
|
||||||
manufacturer: u8,
|
|
||||||
chassis_type: u8,
|
|
||||||
version: u8,
|
|
||||||
serial_number: u8,
|
|
||||||
asset_tag: u8,
|
|
||||||
bootup_state: u8,
|
|
||||||
power_supply_state: u8,
|
|
||||||
thermal_state: u8,
|
|
||||||
security_status: u8,
|
|
||||||
oem_defined: u32,
|
|
||||||
height: u8,
|
|
||||||
number_of_power_cords: u8,
|
|
||||||
contained_element_count: u8,
|
|
||||||
contained_element_record_length: u8,
|
|
||||||
// followed by contained element records (optional, variable-length)
|
|
||||||
// followed by sku_number: u8, rack_type: u8, rack_height: u8
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(C, packed)]
|
|
||||||
#[derive(Default, Copy, Clone)]
|
|
||||||
struct SmbiosEndOfTable {
|
|
||||||
r#type: u8,
|
|
||||||
length: u8,
|
|
||||||
handle: u16,
|
|
||||||
}
|
|
||||||
|
|
||||||
// SAFETY: data structure only contain a series of integers
|
|
||||||
unsafe impl ByteValued for Smbios30Entrypoint {}
|
|
||||||
// SAFETY: data structure only contain a series of integers
|
|
||||||
unsafe impl ByteValued for SmbiosBiosInfo {}
|
|
||||||
// SAFETY: data structure only contain a series of integers
|
|
||||||
unsafe impl ByteValued for SmbiosSysInfo {}
|
|
||||||
// SAFETY: data structure only contain a series of integers
|
|
||||||
unsafe impl ByteValued for SmbiosOemStrings {}
|
|
||||||
// SAFETY: data structure only contain a series of integers
|
|
||||||
unsafe impl ByteValued for SmbiosChassis {}
|
|
||||||
// SAFETY: data structure only contain a series of integers
|
|
||||||
unsafe impl ByteValued for SmbiosEndOfTable {}
|
|
||||||
|
|
||||||
fn write_and_incr<T: ByteValued>(
|
|
||||||
mem: &GuestMemoryMmap,
|
|
||||||
val: T,
|
|
||||||
mut curptr: GuestAddress,
|
|
||||||
) -> Result<GuestAddress> {
|
|
||||||
mem.write_obj(val, curptr).map_err(Error::WriteData)?;
|
|
||||||
curptr = curptr
|
|
||||||
.checked_add(size_of::<T>() as u64)
|
|
||||||
.ok_or(Error::NotEnoughMemory)?;
|
|
||||||
Ok(curptr)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_string(
|
|
||||||
mem: &GuestMemoryMmap,
|
|
||||||
val: &str,
|
|
||||||
mut curptr: GuestAddress,
|
|
||||||
) -> Result<GuestAddress> {
|
|
||||||
for c in val.as_bytes().iter() {
|
|
||||||
curptr = write_and_incr(mem, *c, curptr)?;
|
|
||||||
}
|
|
||||||
curptr = write_and_incr(mem, 0u8, curptr)?;
|
|
||||||
Ok(curptr)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_opt_string(
|
|
||||||
mem: &GuestMemoryMmap,
|
|
||||||
s: Option<&str>,
|
|
||||||
cur: GuestAddress,
|
|
||||||
) -> Result<GuestAddress> {
|
|
||||||
if let Some(v) = s {
|
|
||||||
write_string(mem, v, cur)
|
|
||||||
} else {
|
|
||||||
Ok(cur)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_string_terminator(
|
|
||||||
mem: &GuestMemoryMmap,
|
|
||||||
cur: GuestAddress,
|
|
||||||
has_strings: bool,
|
|
||||||
) -> Result<GuestAddress> {
|
|
||||||
// SMBIOS DSP0134 §6.1.3: if all string-reference fields are 0, follow the
|
|
||||||
// formatted section with two null bytes (empty string-set).
|
|
||||||
if has_strings {
|
|
||||||
write_and_incr(mem, 0u8, cur)
|
|
||||||
} else {
|
|
||||||
let cur = write_and_incr(mem, 0u8, cur)?;
|
|
||||||
write_and_incr(mem, 0u8, cur)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Allocate the next string index for an SMBIOS string-set.
|
|
||||||
///
|
|
||||||
/// Per SMBIOS DSP0134, index `0` means "no string", so valid indices run from
|
|
||||||
/// `1` to `255`. Returns `0` when `present` is `false`. Otherwise returns the
|
|
||||||
/// current value of `*next` and advances it by one. Fails with
|
|
||||||
/// [`Error::TooManyStrings`] once all 255 indices have been used: `next`
|
|
||||||
/// starts at `1`, so it can only be `0` here after wrapping past `255`.
|
|
||||||
fn alloc_index(next: &mut u8, present: bool) -> Result<u8> {
|
|
||||||
if !present {
|
|
||||||
return Ok(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
let idx = *next;
|
|
||||||
if idx == 0 {
|
|
||||||
return Err(Error::TooManyStrings);
|
|
||||||
}
|
|
||||||
|
|
||||||
*next = next.wrapping_add(1);
|
|
||||||
Ok(idx)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_type1_system(
|
|
||||||
mem: &GuestMemoryMmap,
|
|
||||||
curptr: &mut GuestAddress,
|
|
||||||
handle: &mut u16,
|
|
||||||
system: Option<&SmbiosSystem>,
|
|
||||||
) -> Result<()> {
|
|
||||||
*handle += 1;
|
|
||||||
|
|
||||||
let manufacturer = system
|
|
||||||
.and_then(|s| s.manufacturer.as_deref())
|
|
||||||
.unwrap_or(DEFAULT_SYSTEM_MANUFACTURER);
|
|
||||||
let product = system
|
|
||||||
.and_then(|s| s.product_name.as_deref())
|
|
||||||
.unwrap_or(DEFAULT_SYSTEM_PRODUCT_NAME);
|
|
||||||
let version = system.and_then(|s| s.version.as_deref());
|
|
||||||
let serial = system.and_then(|s| s.serial_number.as_deref());
|
|
||||||
let uuid = system.and_then(|s| s.uuid.as_deref());
|
|
||||||
let sku = system.and_then(|s| s.sku_number.as_deref());
|
|
||||||
let family = system.and_then(|s| s.family.as_deref());
|
|
||||||
|
|
||||||
let uuid_number = uuid
|
|
||||||
.map(Uuid::parse_str)
|
|
||||||
.transpose()
|
|
||||||
.map_err(|e| Error::ParseUuid(e, uuid.unwrap().to_string()))?
|
|
||||||
.unwrap_or(Uuid::nil());
|
|
||||||
|
|
||||||
let mut next = 1u8;
|
|
||||||
let manufacturer_idx = alloc_index(&mut next, true)?;
|
|
||||||
let product_idx = alloc_index(&mut next, true)?;
|
|
||||||
let version_idx = alloc_index(&mut next, version.is_some())?;
|
|
||||||
let serial_idx = alloc_index(&mut next, serial.is_some())?;
|
|
||||||
let sku_idx = alloc_index(&mut next, sku.is_some())?;
|
|
||||||
let family_idx = alloc_index(&mut next, family.is_some())?;
|
|
||||||
|
|
||||||
let sys = SmbiosSysInfo {
|
|
||||||
r#type: SYSTEM_INFORMATION,
|
|
||||||
length: size_of::<SmbiosSysInfo>() as u8,
|
|
||||||
handle: *handle,
|
|
||||||
manufacturer: manufacturer_idx,
|
|
||||||
product_name: product_idx,
|
|
||||||
version: version_idx,
|
|
||||||
serial_number: serial_idx,
|
|
||||||
uuid: uuid_number.to_bytes_le(),
|
|
||||||
wake_up_type: SYSTEM_WAKE_UP_TYPE_UNKNOWN,
|
|
||||||
sku: sku_idx,
|
|
||||||
family: family_idx,
|
|
||||||
};
|
|
||||||
|
|
||||||
*curptr = write_and_incr(mem, sys, *curptr)?;
|
|
||||||
*curptr = write_string(mem, manufacturer, *curptr)?;
|
|
||||||
*curptr = write_string(mem, product, *curptr)?;
|
|
||||||
*curptr = write_opt_string(mem, version, *curptr)?;
|
|
||||||
*curptr = write_opt_string(mem, serial, *curptr)?;
|
|
||||||
*curptr = write_opt_string(mem, sku, *curptr)?;
|
|
||||||
*curptr = write_opt_string(mem, family, *curptr)?;
|
|
||||||
*curptr = write_and_incr(mem, 0u8, *curptr)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_type3_chassis(
|
|
||||||
mem: &GuestMemoryMmap,
|
|
||||||
curptr: &mut GuestAddress,
|
|
||||||
handle: &mut u16,
|
|
||||||
chassis: &SmbiosChassisConfig,
|
|
||||||
) -> Result<()> {
|
|
||||||
*handle += 1;
|
|
||||||
|
|
||||||
let asset_tag = chassis.asset_tag.as_deref();
|
|
||||||
let mut next = 1u8;
|
|
||||||
let asset_idx = alloc_index(&mut next, asset_tag.is_some())?;
|
|
||||||
|
|
||||||
let ch = SmbiosChassis {
|
|
||||||
r#type: SYSTEM_ENCLOSURE,
|
|
||||||
length: size_of::<SmbiosChassis>() as u8,
|
|
||||||
handle: *handle,
|
|
||||||
manufacturer: 0,
|
|
||||||
chassis_type: CHASSIS_TYPE_UNKNOWN,
|
|
||||||
version: 0,
|
|
||||||
serial_number: 0,
|
|
||||||
asset_tag: asset_idx,
|
|
||||||
bootup_state: CHASSIS_STATE_UNKNOWN,
|
|
||||||
power_supply_state: CHASSIS_STATE_UNKNOWN,
|
|
||||||
thermal_state: CHASSIS_STATE_UNKNOWN,
|
|
||||||
security_status: CHASSIS_SECURITY_STATUS_NONE,
|
|
||||||
contained_element_count: 0,
|
|
||||||
contained_element_record_length: 0,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
*curptr = write_and_incr(mem, ch, *curptr)?;
|
|
||||||
*curptr = write_opt_string(mem, asset_tag, *curptr)?;
|
|
||||||
*curptr = write_string_terminator(mem, *curptr, asset_tag.is_some())?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn setup_smbios(mem: &GuestMemoryMmap, smbios: Option<&SmbiosConfig>) -> Result<u64> {
|
|
||||||
let system = smbios.and_then(|cfg| cfg.system.as_ref());
|
|
||||||
let chassis = smbios.and_then(|cfg| cfg.chassis.as_ref());
|
|
||||||
let oem_strings: &[String] = smbios.map_or(&[], |cfg| &cfg.oem_strings);
|
|
||||||
let physptr = GuestAddress(SMBIOS_START)
|
|
||||||
.checked_add(size_of::<Smbios30Entrypoint>() as u64)
|
|
||||||
.ok_or(Error::NotEnoughMemory)?;
|
|
||||||
let mut curptr = physptr;
|
|
||||||
let mut handle = 0;
|
|
||||||
|
|
||||||
{
|
|
||||||
handle += 1;
|
|
||||||
let smbios_biosinfo = SmbiosBiosInfo {
|
|
||||||
r#type: BIOS_INFORMATION,
|
|
||||||
length: size_of::<SmbiosBiosInfo>() as u8,
|
|
||||||
handle,
|
|
||||||
vendor: 1, // First string written in this section
|
|
||||||
version: 2, // Second string written in this section
|
|
||||||
characteristics: PCI_SUPPORTED,
|
|
||||||
characteristics_ext2: IS_VIRTUAL_MACHINE,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
curptr = write_and_incr(mem, smbios_biosinfo, curptr)?;
|
|
||||||
curptr = write_string(mem, "cloud-hypervisor", curptr)?;
|
|
||||||
curptr = write_string(mem, "0", curptr)?;
|
|
||||||
curptr = write_and_incr(mem, 0u8, curptr)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
write_type1_system(mem, &mut curptr, &mut handle, system)?;
|
|
||||||
|
|
||||||
if let Some(chassis) = chassis {
|
|
||||||
write_type3_chassis(mem, &mut curptr, &mut handle, chassis)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if !oem_strings.is_empty() {
|
|
||||||
handle += 1;
|
|
||||||
|
|
||||||
let smbios_oemstrings = SmbiosOemStrings {
|
|
||||||
r#type: OEM_STRINGS,
|
|
||||||
length: size_of::<SmbiosOemStrings>() as u8,
|
|
||||||
handle,
|
|
||||||
count: oem_strings.len() as u8,
|
|
||||||
};
|
|
||||||
|
|
||||||
curptr = write_and_incr(mem, smbios_oemstrings, curptr)?;
|
|
||||||
|
|
||||||
for s in oem_strings {
|
|
||||||
curptr = write_string(mem, s, curptr)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
curptr = write_string_terminator(mem, curptr, true)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
handle += 1;
|
|
||||||
let smbios_end = SmbiosEndOfTable {
|
|
||||||
r#type: END_OF_TABLE,
|
|
||||||
length: size_of::<SmbiosEndOfTable>() as u8,
|
|
||||||
handle,
|
|
||||||
};
|
|
||||||
curptr = write_and_incr(mem, smbios_end, curptr)?;
|
|
||||||
curptr = write_and_incr(mem, 0u8, curptr)?;
|
|
||||||
curptr = write_and_incr(mem, 0u8, curptr)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
let mut smbios_ep = Smbios30Entrypoint {
|
|
||||||
signature: *SM3_MAGIC_IDENT,
|
|
||||||
length: size_of::<Smbios30Entrypoint>() as u8,
|
|
||||||
// SMBIOS rev 3.2.0
|
|
||||||
majorver: 0x03,
|
|
||||||
minorver: 0x02,
|
|
||||||
docrev: 0x00,
|
|
||||||
revision: 0x01, // SMBIOS 3.0
|
|
||||||
max_size: curptr.unchecked_offset_from(physptr) as u32,
|
|
||||||
physptr: physptr.0,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
smbios_ep.checksum = compute_checksum(&smbios_ep);
|
|
||||||
mem.write_obj(smbios_ep, GuestAddress(SMBIOS_START))
|
|
||||||
.map_err(Error::WriteSmbiosEp)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(curptr.unchecked_offset_from(physptr) + size_of::<Smbios30Entrypoint>() as u64)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod unit_tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
/// Collects all strings after a SMBIOS structure, stopping at the double-NUL terminator and returns next addr.
|
|
||||||
fn read_string_set(mem: &GuestMemoryMmap, addr: GuestAddress) -> (Vec<String>, GuestAddress) {
|
|
||||||
let mut cur = addr;
|
|
||||||
let read_byte = |addr: GuestAddress| -> u8 { mem.read_obj(addr).unwrap() };
|
|
||||||
|
|
||||||
// SMBIOS string-set: NUL-terminated strings, terminated by an extra NUL.
|
|
||||||
// Empty string-set is exactly "\0\0".
|
|
||||||
if read_byte(cur) == 0 {
|
|
||||||
let next = cur.checked_add(1).unwrap();
|
|
||||||
assert_eq!(read_byte(next), 0);
|
|
||||||
return (Vec::new(), next.checked_add(1).unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut strings = Vec::new();
|
|
||||||
loop {
|
|
||||||
let mut bytes = Vec::new();
|
|
||||||
loop {
|
|
||||||
let b = read_byte(cur);
|
|
||||||
cur = cur.checked_add(1).unwrap();
|
|
||||||
if b == 0 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
bytes.push(b);
|
|
||||||
}
|
|
||||||
strings.push(String::from_utf8(bytes).unwrap());
|
|
||||||
|
|
||||||
// If the next byte is NUL, that's the extra terminator.
|
|
||||||
if read_byte(cur) == 0 {
|
|
||||||
cur = cur.checked_add(1).unwrap();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(strings, cur)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn entrypoint_checksum() {
|
|
||||||
let mem = GuestMemoryMmap::from_ranges(&[(GuestAddress(SMBIOS_START), 4096)]).unwrap();
|
|
||||||
|
|
||||||
setup_smbios(&mem, None).unwrap();
|
|
||||||
|
|
||||||
let smbios_ep: Smbios30Entrypoint = mem.read_obj(GuestAddress(SMBIOS_START)).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(compute_checksum(&smbios_ep), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn entrypoint_struct_size() {
|
|
||||||
assert_eq!(
|
|
||||||
size_of::<Smbios30Entrypoint>(),
|
|
||||||
0x18usize,
|
|
||||||
concat!("Size of: ", stringify!(Smbios30Entrypoint))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
size_of::<SmbiosBiosInfo>(),
|
|
||||||
0x14usize,
|
|
||||||
concat!("Size of: ", stringify!(SmbiosBiosInfo))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
size_of::<SmbiosSysInfo>(),
|
|
||||||
0x1busize,
|
|
||||||
concat!("Size of: ", stringify!(SmbiosSysInfo))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn smbios_chassis_empty_string_set_has_double_null() {
|
|
||||||
let mem = GuestMemoryMmap::from_ranges(&[(GuestAddress(SMBIOS_START), 4096)]).unwrap();
|
|
||||||
let smbios = SmbiosConfig {
|
|
||||||
chassis: Some(SmbiosChassisConfig::default()),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
setup_smbios(&mem, Some(&smbios)).unwrap();
|
|
||||||
|
|
||||||
let smbios_ep: Smbios30Entrypoint = mem.read_obj(GuestAddress(SMBIOS_START)).unwrap();
|
|
||||||
let mut cur = GuestAddress(smbios_ep.physptr);
|
|
||||||
|
|
||||||
let bios: SmbiosBiosInfo = mem.read_obj(cur).unwrap();
|
|
||||||
cur = cur.checked_add(bios.length as u64).unwrap();
|
|
||||||
let (_, next) = read_string_set(&mem, cur);
|
|
||||||
cur = next;
|
|
||||||
|
|
||||||
let sys: SmbiosSysInfo = mem.read_obj(cur).unwrap();
|
|
||||||
cur = cur.checked_add(sys.length as u64).unwrap();
|
|
||||||
let (_, next) = read_string_set(&mem, cur);
|
|
||||||
cur = next;
|
|
||||||
|
|
||||||
let chassis: SmbiosChassis = mem.read_obj(cur).unwrap();
|
|
||||||
cur = cur.checked_add(chassis.length as u64).unwrap();
|
|
||||||
// SMBIOS DSP0134 §6.1.3: empty string-set ends with double NUL.
|
|
||||||
let b0: u8 = mem.read_obj(cur).unwrap();
|
|
||||||
let b1: u8 = mem.read_obj(cur.checked_add(1).unwrap()).unwrap();
|
|
||||||
assert_eq!(b0, 0);
|
|
||||||
assert_eq!(b1, 0);
|
|
||||||
cur = cur.checked_add(2).unwrap();
|
|
||||||
|
|
||||||
let end: SmbiosEndOfTable = mem.read_obj(cur).unwrap();
|
|
||||||
assert_eq!(end.r#type, END_OF_TABLE);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn smbios_chassis_oem_strings_layout() {
|
|
||||||
let mem = GuestMemoryMmap::from_ranges(&[(GuestAddress(SMBIOS_START), 4096)]).unwrap();
|
|
||||||
|
|
||||||
let smbios = SmbiosConfig {
|
|
||||||
chassis: Some(SmbiosChassisConfig {
|
|
||||||
asset_tag: Some("rack1".to_string()),
|
|
||||||
}),
|
|
||||||
oem_strings: ["o1".to_string(), "o2".to_string()].into(),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
setup_smbios(&mem, Some(&smbios)).unwrap();
|
|
||||||
|
|
||||||
let smbios_ep: Smbios30Entrypoint = mem.read_obj(GuestAddress(SMBIOS_START)).unwrap();
|
|
||||||
let mut cur = GuestAddress(smbios_ep.physptr);
|
|
||||||
|
|
||||||
let bios: SmbiosBiosInfo = mem.read_obj(cur).unwrap();
|
|
||||||
cur = cur.checked_add(bios.length as u64).unwrap();
|
|
||||||
let (_, next) = read_string_set(&mem, cur);
|
|
||||||
cur = next;
|
|
||||||
|
|
||||||
let sys: SmbiosSysInfo = mem.read_obj(cur).unwrap();
|
|
||||||
cur = cur.checked_add(sys.length as u64).unwrap();
|
|
||||||
let (_, next) = read_string_set(&mem, cur);
|
|
||||||
cur = next;
|
|
||||||
|
|
||||||
let chassis: SmbiosChassis = mem.read_obj(cur).unwrap();
|
|
||||||
assert_eq!(chassis.r#type, SYSTEM_ENCLOSURE);
|
|
||||||
assert_eq!(chassis.asset_tag, 1);
|
|
||||||
cur = cur.checked_add(chassis.length as u64).unwrap();
|
|
||||||
let (chassis_strings, next) = read_string_set(&mem, cur);
|
|
||||||
assert_eq!(chassis_strings, vec!["rack1"]);
|
|
||||||
cur = next;
|
|
||||||
|
|
||||||
let oem: SmbiosOemStrings = mem.read_obj(cur).unwrap();
|
|
||||||
assert_eq!(oem.r#type, OEM_STRINGS);
|
|
||||||
assert_eq!(oem.count, 2);
|
|
||||||
cur = cur.checked_add(oem.length as u64).unwrap();
|
|
||||||
let (oem_strings, next) = read_string_set(&mem, cur);
|
|
||||||
assert_eq!(oem_strings, vec!["o1", "o2"]);
|
|
||||||
cur = next;
|
|
||||||
|
|
||||||
let end: SmbiosEndOfTable = mem.read_obj(cur).unwrap();
|
|
||||||
assert_eq!(end.r#type, END_OF_TABLE);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn smbios_strings_terminators_default() {
|
|
||||||
let mem = GuestMemoryMmap::from_ranges(&[(GuestAddress(SMBIOS_START), 4096)]).unwrap();
|
|
||||||
|
|
||||||
setup_smbios(&mem, None).unwrap();
|
|
||||||
|
|
||||||
let smbios_ep: Smbios30Entrypoint = mem.read_obj(GuestAddress(SMBIOS_START)).unwrap();
|
|
||||||
let mut cur = GuestAddress(smbios_ep.physptr);
|
|
||||||
|
|
||||||
let bios: SmbiosBiosInfo = mem.read_obj(cur).unwrap();
|
|
||||||
assert_eq!(bios.r#type, BIOS_INFORMATION);
|
|
||||||
cur = cur.checked_add(bios.length as u64).unwrap();
|
|
||||||
let (bios_strings, next) = read_string_set(&mem, cur);
|
|
||||||
assert_eq!(bios_strings, vec!["cloud-hypervisor", "0"]);
|
|
||||||
cur = next;
|
|
||||||
|
|
||||||
let sys: SmbiosSysInfo = mem.read_obj(cur).unwrap();
|
|
||||||
assert_eq!(sys.r#type, SYSTEM_INFORMATION);
|
|
||||||
assert_eq!(sys.manufacturer, 1);
|
|
||||||
assert_eq!(sys.product_name, 2);
|
|
||||||
assert_eq!(sys.version, 0);
|
|
||||||
assert_eq!(sys.serial_number, 0);
|
|
||||||
assert_eq!(sys.sku, 0);
|
|
||||||
assert_eq!(sys.family, 0);
|
|
||||||
cur = cur.checked_add(sys.length as u64).unwrap();
|
|
||||||
let (sys_strings, next) = read_string_set(&mem, cur);
|
|
||||||
assert_eq!(
|
|
||||||
sys_strings,
|
|
||||||
vec![DEFAULT_SYSTEM_MANUFACTURER, DEFAULT_SYSTEM_PRODUCT_NAME]
|
|
||||||
);
|
|
||||||
cur = next;
|
|
||||||
|
|
||||||
let end: SmbiosEndOfTable = mem.read_obj(cur).unwrap();
|
|
||||||
assert_eq!(end.r#type, END_OF_TABLE);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn smbios_strings_too_many() {
|
|
||||||
let mut next = 1u8;
|
|
||||||
for _ in 0..255 {
|
|
||||||
alloc_index(&mut next, true).unwrap();
|
|
||||||
}
|
|
||||||
let err = alloc_index(&mut next, true).unwrap_err();
|
|
||||||
assert!(matches!(err, Error::TooManyStrings));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn smbios_uuid_invalid_rejected() {
|
|
||||||
let mem = GuestMemoryMmap::from_ranges(&[(GuestAddress(SMBIOS_START), 4096)]).unwrap();
|
|
||||||
let smbios = SmbiosConfig {
|
|
||||||
system: Some(SmbiosSystem {
|
|
||||||
uuid: Some("not-a-uuid".to_string()),
|
|
||||||
..Default::default()
|
|
||||||
}),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
let err = setup_smbios(&mem, Some(&smbios)).unwrap_err();
|
|
||||||
assert!(matches!(err, Error::ParseUuid(_, _)));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn smbios_uuid_written_le() {
|
|
||||||
let mem = GuestMemoryMmap::from_ranges(&[(GuestAddress(SMBIOS_START), 4096)]).unwrap();
|
|
||||||
let uuid_str = "00112233-4455-6677-8899-aabbccddeeff";
|
|
||||||
let smbios = SmbiosConfig {
|
|
||||||
system: Some(SmbiosSystem {
|
|
||||||
uuid: Some(uuid_str.to_string()),
|
|
||||||
..Default::default()
|
|
||||||
}),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
setup_smbios(&mem, Some(&smbios)).unwrap();
|
|
||||||
|
|
||||||
let smbios_ep: Smbios30Entrypoint = mem.read_obj(GuestAddress(SMBIOS_START)).unwrap();
|
|
||||||
let mut cur = GuestAddress(smbios_ep.physptr);
|
|
||||||
|
|
||||||
let bios: SmbiosBiosInfo = mem.read_obj(cur).unwrap();
|
|
||||||
cur = cur.checked_add(bios.length as u64).unwrap();
|
|
||||||
let (_, next) = read_string_set(&mem, cur);
|
|
||||||
cur = next;
|
|
||||||
|
|
||||||
let sys: SmbiosSysInfo = mem.read_obj(cur).unwrap();
|
|
||||||
assert_eq!(sys.uuid, Uuid::parse_str(uuid_str).unwrap().to_bytes_le());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn smbios_write_fails_with_too_small_memory() {
|
|
||||||
let mem = GuestMemoryMmap::from_ranges(&[(
|
|
||||||
GuestAddress(SMBIOS_START),
|
|
||||||
size_of::<Smbios30Entrypoint>(),
|
|
||||||
)])
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let err = setup_smbios(&mem, None).unwrap_err();
|
|
||||||
assert!(matches!(err, Error::WriteData(_)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,531 +0,0 @@
|
|||||||
// Copyright © 2021 Intel Corporation
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
use std::fs::File;
|
|
||||||
use std::io::{self, Read, Seek, SeekFrom};
|
|
||||||
use std::slice;
|
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
use log::{debug, info};
|
|
||||||
use thiserror::Error;
|
|
||||||
use uuid::Uuid;
|
|
||||||
use vm_memory::{ByteValued, Bytes, GuestAddress, GuestMemoryError};
|
|
||||||
|
|
||||||
use crate::GuestMemoryMmap;
|
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
|
||||||
pub enum TdvfError {
|
|
||||||
#[error("Failed read TDVF descriptor")]
|
|
||||||
ReadDescriptor(#[source] io::Error),
|
|
||||||
#[error("Failed read TDVF descriptor offset")]
|
|
||||||
ReadDescriptorOffset(#[source] io::Error),
|
|
||||||
#[error("Failed read GUID table")]
|
|
||||||
ReadGuidTable(#[source] io::Error),
|
|
||||||
#[error("Invalid descriptor signature")]
|
|
||||||
InvalidDescriptorSignature,
|
|
||||||
#[error("Invalid descriptor size")]
|
|
||||||
InvalidDescriptorSize,
|
|
||||||
#[error("Invalid descriptor version")]
|
|
||||||
InvalidDescriptorVersion,
|
|
||||||
#[error("Failed to write HOB details to guest memory")]
|
|
||||||
GuestMemoryWriteHob(#[source] GuestMemoryError),
|
|
||||||
#[error("Failed to create Uuid")]
|
|
||||||
UuidCreation(#[source] uuid::Error),
|
|
||||||
}
|
|
||||||
|
|
||||||
const TABLE_FOOTER_GUID: &str = "96b582de-1fb2-45f7-baea-a366c55a082d";
|
|
||||||
const TDVF_METADATA_OFFSET_GUID: &str = "e47a6535-984a-4798-865e-4685a7bf8ec2";
|
|
||||||
|
|
||||||
// TDVF_DESCRIPTOR
|
|
||||||
#[repr(C, packed)]
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct TdvfDescriptor {
|
|
||||||
signature: [u8; 4],
|
|
||||||
length: u32,
|
|
||||||
version: u32,
|
|
||||||
num_sections: u32, // NumberOfSectionEntry
|
|
||||||
}
|
|
||||||
|
|
||||||
// TDVF_SECTION
|
|
||||||
#[repr(C, packed)]
|
|
||||||
#[derive(Clone, Copy, Default, Debug)]
|
|
||||||
pub struct TdvfSection {
|
|
||||||
pub data_offset: u32,
|
|
||||||
pub data_size: u32, // RawDataSize
|
|
||||||
pub address: u64, // MemoryAddress
|
|
||||||
pub size: u64, // MemoryDataSize
|
|
||||||
pub r#type: TdvfSectionType,
|
|
||||||
pub attributes: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(u32)]
|
|
||||||
#[derive(Clone, Copy, Debug, Default)]
|
|
||||||
pub enum TdvfSectionType {
|
|
||||||
Bfv,
|
|
||||||
Cfv,
|
|
||||||
TdHob,
|
|
||||||
TempMem,
|
|
||||||
PermMem,
|
|
||||||
Payload,
|
|
||||||
PayloadParam,
|
|
||||||
#[default]
|
|
||||||
Reserved = 0xffffffff,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn tdvf_descriptor_offset(file: &mut File) -> Result<(SeekFrom, bool), TdvfError> {
|
|
||||||
// Let's first try to identify the presence of the table footer GUID
|
|
||||||
file.seek(SeekFrom::End(-0x30))
|
|
||||||
.map_err(TdvfError::ReadGuidTable)?;
|
|
||||||
let mut table_footer_guid: [u8; 16] = [0; 16];
|
|
||||||
file.read_exact(&mut table_footer_guid)
|
|
||||||
.map_err(TdvfError::ReadGuidTable)?;
|
|
||||||
let uuid =
|
|
||||||
Uuid::from_slice_le(table_footer_guid.as_slice()).map_err(TdvfError::UuidCreation)?;
|
|
||||||
let expected_uuid = Uuid::from_str(TABLE_FOOTER_GUID).map_err(TdvfError::UuidCreation)?;
|
|
||||||
if uuid == expected_uuid {
|
|
||||||
// Retrieve the table size
|
|
||||||
file.seek(SeekFrom::End(-0x32))
|
|
||||||
.map_err(TdvfError::ReadGuidTable)?;
|
|
||||||
let mut table_size: [u8; 2] = [0; 2];
|
|
||||||
file.read_exact(&mut table_size)
|
|
||||||
.map_err(TdvfError::ReadGuidTable)?;
|
|
||||||
let table_size = u16::from_le_bytes(table_size) as usize;
|
|
||||||
let mut table: Vec<u8> = vec![0; table_size];
|
|
||||||
|
|
||||||
// Read the entire table
|
|
||||||
file.seek(SeekFrom::End(-(table_size as i64 + 0x20)))
|
|
||||||
.map_err(TdvfError::ReadGuidTable)?;
|
|
||||||
file.read_exact(table.as_mut_slice())
|
|
||||||
.map_err(TdvfError::ReadGuidTable)?;
|
|
||||||
|
|
||||||
// Let's start from the top and go backward down the table.
|
|
||||||
// We start after the footer GUID and the table length.
|
|
||||||
let mut offset = table_size - 18;
|
|
||||||
|
|
||||||
debug!("Parsing GUID structure");
|
|
||||||
while offset >= 18 {
|
|
||||||
let entry_uuid = Uuid::from_slice_le(&table[offset - 16..offset])
|
|
||||||
.map_err(TdvfError::UuidCreation)?;
|
|
||||||
let entry_size =
|
|
||||||
u16::from_le_bytes(table[offset - 18..offset - 16].try_into().unwrap()) as usize;
|
|
||||||
debug!(
|
|
||||||
"Entry GUID = {}, size = {}",
|
|
||||||
entry_uuid.hyphenated(),
|
|
||||||
entry_size
|
|
||||||
);
|
|
||||||
|
|
||||||
// Avoid going through an infinite loop if the entry size is 0
|
|
||||||
if entry_size == 0 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
offset -= entry_size;
|
|
||||||
|
|
||||||
let expected_uuid =
|
|
||||||
Uuid::from_str(TDVF_METADATA_OFFSET_GUID).map_err(TdvfError::UuidCreation)?;
|
|
||||||
if entry_uuid == expected_uuid && entry_size == 22 {
|
|
||||||
return Ok((
|
|
||||||
SeekFrom::End(
|
|
||||||
-(u32::from_le_bytes(table[offset..offset + 4].try_into().unwrap()) as i64),
|
|
||||||
),
|
|
||||||
true,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we end up here, this means the firmware doesn't support the new way
|
|
||||||
// of exposing the TDVF descriptor offset through the table of GUIDs.
|
|
||||||
// That's why we fallback onto the deprecated method.
|
|
||||||
|
|
||||||
// The 32-bit offset to the TDVF metadata is located 32 bytes from
|
|
||||||
// the end of the file.
|
|
||||||
// See "TDVF Metadata Pointer" in "TDX Virtual Firmware Design Guide
|
|
||||||
file.seek(SeekFrom::End(-0x20))
|
|
||||||
.map_err(TdvfError::ReadDescriptorOffset)?;
|
|
||||||
|
|
||||||
let mut descriptor_offset: [u8; 4] = [0; 4];
|
|
||||||
file.read_exact(&mut descriptor_offset)
|
|
||||||
.map_err(TdvfError::ReadDescriptorOffset)?;
|
|
||||||
|
|
||||||
Ok((
|
|
||||||
SeekFrom::Start(u32::from_le_bytes(descriptor_offset) as u64),
|
|
||||||
false,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse_tdvf_sections(file: &mut File) -> Result<(Vec<TdvfSection>, bool), TdvfError> {
|
|
||||||
let (descriptor_offset, guid_found) = tdvf_descriptor_offset(file)?;
|
|
||||||
|
|
||||||
file.seek(descriptor_offset)
|
|
||||||
.map_err(TdvfError::ReadDescriptor)?;
|
|
||||||
|
|
||||||
let mut descriptor: TdvfDescriptor = Default::default();
|
|
||||||
// SAFETY: we read exactly the size of the descriptor header
|
|
||||||
file.read_exact(unsafe {
|
|
||||||
slice::from_raw_parts_mut((&raw mut descriptor).cast(), size_of::<TdvfDescriptor>())
|
|
||||||
})
|
|
||||||
.map_err(TdvfError::ReadDescriptor)?;
|
|
||||||
|
|
||||||
if &descriptor.signature != b"TDVF" {
|
|
||||||
return Err(TdvfError::InvalidDescriptorSignature);
|
|
||||||
}
|
|
||||||
|
|
||||||
if descriptor.length as usize
|
|
||||||
!= size_of::<TdvfDescriptor>() + size_of::<TdvfSection>() * descriptor.num_sections as usize
|
|
||||||
{
|
|
||||||
return Err(TdvfError::InvalidDescriptorSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
if descriptor.version != 1 {
|
|
||||||
return Err(TdvfError::InvalidDescriptorVersion);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut sections = Vec::new();
|
|
||||||
sections.resize_with(descriptor.num_sections as usize, TdvfSection::default);
|
|
||||||
|
|
||||||
// SAFETY: we read exactly the advertised sections
|
|
||||||
file.read_exact(unsafe {
|
|
||||||
slice::from_raw_parts_mut(
|
|
||||||
sections.as_mut_ptr().cast(),
|
|
||||||
descriptor.num_sections as usize * size_of::<TdvfSection>(),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.map_err(TdvfError::ReadDescriptor)?;
|
|
||||||
|
|
||||||
Ok((sections, guid_found))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(u16)]
|
|
||||||
#[derive(Copy, Clone, Debug, Default)]
|
|
||||||
enum HobType {
|
|
||||||
Handoff = 0x1,
|
|
||||||
ResourceDescriptor = 0x3,
|
|
||||||
GuidExtension = 0x4,
|
|
||||||
#[default]
|
|
||||||
Unused = 0xfffe,
|
|
||||||
EndOfHobList = 0xffff,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(C, packed)]
|
|
||||||
#[derive(Copy, Clone, Default, Debug)]
|
|
||||||
struct HobHeader {
|
|
||||||
r#type: HobType,
|
|
||||||
length: u16,
|
|
||||||
reserved: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(C, packed)]
|
|
||||||
#[derive(Copy, Clone, Default, Debug)]
|
|
||||||
struct HobHandoffInfoTable {
|
|
||||||
header: HobHeader,
|
|
||||||
version: u32,
|
|
||||||
boot_mode: u32,
|
|
||||||
efi_memory_top: u64,
|
|
||||||
efi_memory_bottom: u64,
|
|
||||||
efi_free_memory_top: u64,
|
|
||||||
efi_free_memory_bottom: u64,
|
|
||||||
efi_end_of_hob_list: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(C, packed)]
|
|
||||||
#[derive(Copy, Clone, Default, Debug)]
|
|
||||||
struct EfiGuid {
|
|
||||||
data1: u32,
|
|
||||||
data2: u16,
|
|
||||||
data3: u16,
|
|
||||||
data4: [u8; 8],
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(C, packed)]
|
|
||||||
#[derive(Copy, Clone, Default, Debug)]
|
|
||||||
struct HobResourceDescriptor {
|
|
||||||
header: HobHeader,
|
|
||||||
owner: EfiGuid,
|
|
||||||
resource_type: u32,
|
|
||||||
resource_attribute: u32,
|
|
||||||
physical_start: u64,
|
|
||||||
resource_length: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(C, packed)]
|
|
||||||
#[derive(Copy, Clone, Default, Debug)]
|
|
||||||
struct HobGuidType {
|
|
||||||
header: HobHeader,
|
|
||||||
name: EfiGuid,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(u32)]
|
|
||||||
#[derive(Clone, Copy, Debug, Default)]
|
|
||||||
pub enum PayloadImageType {
|
|
||||||
#[default]
|
|
||||||
ExecutablePayload,
|
|
||||||
BzImage,
|
|
||||||
RawVmLinux,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(C, packed)]
|
|
||||||
#[derive(Copy, Clone, Default, Debug)]
|
|
||||||
pub struct PayloadInfo {
|
|
||||||
pub image_type: PayloadImageType,
|
|
||||||
pub entry_point: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(C, packed)]
|
|
||||||
#[derive(Copy, Clone, Default, Debug)]
|
|
||||||
struct TdPayload {
|
|
||||||
guid_type: HobGuidType,
|
|
||||||
payload_info: PayloadInfo,
|
|
||||||
}
|
|
||||||
|
|
||||||
// SAFETY: data structure only contain a series of integers
|
|
||||||
unsafe impl ByteValued for HobHeader {}
|
|
||||||
// SAFETY: data structure only contain a series of integers
|
|
||||||
unsafe impl ByteValued for HobHandoffInfoTable {}
|
|
||||||
// SAFETY: data structure only contain a series of integers
|
|
||||||
unsafe impl ByteValued for HobResourceDescriptor {}
|
|
||||||
// SAFETY: data structure only contain a series of integers
|
|
||||||
unsafe impl ByteValued for HobGuidType {}
|
|
||||||
// SAFETY: data structure only contain a series of integers
|
|
||||||
unsafe impl ByteValued for PayloadInfo {}
|
|
||||||
// SAFETY: data structure only contain a series of integers
|
|
||||||
unsafe impl ByteValued for TdPayload {}
|
|
||||||
|
|
||||||
pub struct TdHob {
|
|
||||||
start_offset: u64,
|
|
||||||
current_offset: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn align_hob(v: u64) -> u64 {
|
|
||||||
v.div_ceil(8) * 8
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TdHob {
|
|
||||||
fn update_offset<T>(&mut self) {
|
|
||||||
self.current_offset = align_hob(self.current_offset + size_of::<T>() as u64);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn start(offset: u64) -> TdHob {
|
|
||||||
// Leave a gap to place the HandoffTable at the start as it can only be filled in later
|
|
||||||
let mut hob = TdHob {
|
|
||||||
start_offset: offset,
|
|
||||||
current_offset: offset,
|
|
||||||
};
|
|
||||||
hob.update_offset::<HobHandoffInfoTable>();
|
|
||||||
hob
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn finish(&mut self, mem: &GuestMemoryMmap) -> Result<(), TdvfError> {
|
|
||||||
// Write end
|
|
||||||
let end = HobHeader {
|
|
||||||
r#type: HobType::EndOfHobList,
|
|
||||||
length: size_of::<HobHeader>() as u16,
|
|
||||||
reserved: 0,
|
|
||||||
};
|
|
||||||
info!("Writing HOB end {:x} {:x?}", self.current_offset, end);
|
|
||||||
mem.write_obj(end, GuestAddress(self.current_offset))
|
|
||||||
.map_err(TdvfError::GuestMemoryWriteHob)?;
|
|
||||||
self.update_offset::<HobHeader>();
|
|
||||||
|
|
||||||
// Write handoff, delayed as it needs end of HOB list
|
|
||||||
let efi_end_of_hob_list = self.current_offset;
|
|
||||||
let handoff = HobHandoffInfoTable {
|
|
||||||
header: HobHeader {
|
|
||||||
r#type: HobType::Handoff,
|
|
||||||
length: size_of::<HobHandoffInfoTable>() as u16,
|
|
||||||
reserved: 0,
|
|
||||||
},
|
|
||||||
version: 0x9,
|
|
||||||
boot_mode: 0,
|
|
||||||
efi_memory_top: 0,
|
|
||||||
efi_memory_bottom: 0,
|
|
||||||
efi_free_memory_top: 0,
|
|
||||||
efi_free_memory_bottom: 0,
|
|
||||||
efi_end_of_hob_list,
|
|
||||||
};
|
|
||||||
info!("Writing HOB start {:x} {:x?}", self.start_offset, handoff);
|
|
||||||
mem.write_obj(handoff, GuestAddress(self.start_offset))
|
|
||||||
.map_err(TdvfError::GuestMemoryWriteHob)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_resource(
|
|
||||||
&mut self,
|
|
||||||
mem: &GuestMemoryMmap,
|
|
||||||
physical_start: u64,
|
|
||||||
resource_length: u64,
|
|
||||||
resource_type: u32,
|
|
||||||
resource_attribute: u32,
|
|
||||||
) -> Result<(), TdvfError> {
|
|
||||||
let resource_descriptor = HobResourceDescriptor {
|
|
||||||
header: HobHeader {
|
|
||||||
r#type: HobType::ResourceDescriptor,
|
|
||||||
length: size_of::<HobResourceDescriptor>() as u16,
|
|
||||||
reserved: 0,
|
|
||||||
},
|
|
||||||
owner: EfiGuid::default(),
|
|
||||||
resource_type,
|
|
||||||
resource_attribute,
|
|
||||||
physical_start,
|
|
||||||
resource_length,
|
|
||||||
};
|
|
||||||
info!(
|
|
||||||
"Writing HOB resource {:x} {:x?}",
|
|
||||||
self.current_offset, resource_descriptor
|
|
||||||
);
|
|
||||||
mem.write_obj(resource_descriptor, GuestAddress(self.current_offset))
|
|
||||||
.map_err(TdvfError::GuestMemoryWriteHob)?;
|
|
||||||
self.update_offset::<HobResourceDescriptor>();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_memory_resource(
|
|
||||||
&mut self,
|
|
||||||
mem: &GuestMemoryMmap,
|
|
||||||
physical_start: u64,
|
|
||||||
resource_length: u64,
|
|
||||||
ram: bool,
|
|
||||||
guid_found: bool,
|
|
||||||
) -> Result<(), TdvfError> {
|
|
||||||
self.add_resource(
|
|
||||||
mem,
|
|
||||||
physical_start,
|
|
||||||
resource_length,
|
|
||||||
if ram {
|
|
||||||
if guid_found {
|
|
||||||
0x7 /* EFI_RESOURCE_MEMORY_UNACCEPTED */
|
|
||||||
} else {
|
|
||||||
0 /* EFI_RESOURCE_SYSTEM_MEMORY */
|
|
||||||
}
|
|
||||||
} else if guid_found {
|
|
||||||
0 /* EFI_RESOURCE_SYSTEM_MEMORY */
|
|
||||||
} else {
|
|
||||||
0x5 /*EFI_RESOURCE_MEMORY_RESERVED */
|
|
||||||
},
|
|
||||||
/* TODO:
|
|
||||||
* QEMU currently fills it in like this:
|
|
||||||
* EFI_RESOURCE_ATTRIBUTE_PRESENT | EFI_RESOURCE_ATTRIBUTE_INITIALIZED | EFI_RESOURCE_ATTRIBUTE_TESTED
|
|
||||||
* which differs from the spec (due to TDVF implementation issue?)
|
|
||||||
*/
|
|
||||||
0x7,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_mmio_resource(
|
|
||||||
&mut self,
|
|
||||||
mem: &GuestMemoryMmap,
|
|
||||||
physical_start: u64,
|
|
||||||
resource_length: u64,
|
|
||||||
) -> Result<(), TdvfError> {
|
|
||||||
self.add_resource(
|
|
||||||
mem,
|
|
||||||
physical_start,
|
|
||||||
resource_length,
|
|
||||||
0x1, /* EFI_RESOURCE_MEMORY_MAPPED_IO */
|
|
||||||
/*
|
|
||||||
* EFI_RESOURCE_ATTRIBUTE_PRESENT | EFI_RESOURCE_ATTRIBUTE_INITIALIZED | EFI_RESOURCE_ATTRIBUTE_UNCACHEABLE
|
|
||||||
*/
|
|
||||||
0x403,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_acpi_table(
|
|
||||||
&mut self,
|
|
||||||
mem: &GuestMemoryMmap,
|
|
||||||
table_content: &[u8],
|
|
||||||
) -> Result<(), TdvfError> {
|
|
||||||
// We already know the HobGuidType size is 8 bytes multiple, but we
|
|
||||||
// need the total size to be 8 bytes multiple. That is why the ACPI
|
|
||||||
// table size must be 8 bytes multiple as well.
|
|
||||||
let length = size_of::<HobGuidType>() as u16 + align_hob(table_content.len() as u64) as u16;
|
|
||||||
let hob_guid_type = HobGuidType {
|
|
||||||
header: HobHeader {
|
|
||||||
r#type: HobType::GuidExtension,
|
|
||||||
length,
|
|
||||||
reserved: 0,
|
|
||||||
},
|
|
||||||
// ACPI_TABLE_HOB_GUID
|
|
||||||
// 0x6a0c5870, 0xd4ed, 0x44f4, {0xa1, 0x35, 0xdd, 0x23, 0x8b, 0x6f, 0xc, 0x8d }
|
|
||||||
name: EfiGuid {
|
|
||||||
data1: 0x6a0c_5870,
|
|
||||||
data2: 0xd4ed,
|
|
||||||
data3: 0x44f4,
|
|
||||||
data4: [0xa1, 0x35, 0xdd, 0x23, 0x8b, 0x6f, 0xc, 0x8d],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
info!(
|
|
||||||
"Writing HOB ACPI table {:x} {:x?} {:x?}",
|
|
||||||
self.current_offset, hob_guid_type, table_content
|
|
||||||
);
|
|
||||||
mem.write_obj(hob_guid_type, GuestAddress(self.current_offset))
|
|
||||||
.map_err(TdvfError::GuestMemoryWriteHob)?;
|
|
||||||
let current_offset = self.current_offset + size_of::<HobGuidType>() as u64;
|
|
||||||
|
|
||||||
// In case the table is quite large, let's make sure we can handle
|
|
||||||
// retrying until everything has been correctly copied.
|
|
||||||
let mut offset: usize = 0;
|
|
||||||
loop {
|
|
||||||
let bytes_written = mem
|
|
||||||
.write(
|
|
||||||
&table_content[offset..],
|
|
||||||
GuestAddress(current_offset + offset as u64),
|
|
||||||
)
|
|
||||||
.map_err(TdvfError::GuestMemoryWriteHob)?;
|
|
||||||
offset += bytes_written;
|
|
||||||
if offset >= table_content.len() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.current_offset += length as u64;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_payload(
|
|
||||||
&mut self,
|
|
||||||
mem: &GuestMemoryMmap,
|
|
||||||
payload_info: PayloadInfo,
|
|
||||||
) -> Result<(), TdvfError> {
|
|
||||||
let payload = TdPayload {
|
|
||||||
guid_type: HobGuidType {
|
|
||||||
header: HobHeader {
|
|
||||||
r#type: HobType::GuidExtension,
|
|
||||||
length: size_of::<TdPayload>() as u16,
|
|
||||||
reserved: 0,
|
|
||||||
},
|
|
||||||
// HOB_PAYLOAD_INFO_GUID
|
|
||||||
// 0xb96fa412, 0x461f, 0x4be3, {0x8c, 0xd, 0xad, 0x80, 0x5a, 0x49, 0x7a, 0xc0
|
|
||||||
name: EfiGuid {
|
|
||||||
data1: 0xb96f_a412,
|
|
||||||
data2: 0x461f,
|
|
||||||
data3: 0x4be3,
|
|
||||||
data4: [0x8c, 0xd, 0xad, 0x80, 0x5a, 0x49, 0x7a, 0xc0],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
payload_info,
|
|
||||||
};
|
|
||||||
info!(
|
|
||||||
"Writing HOB TD_PAYLOAD {:x} {:x?}",
|
|
||||||
self.current_offset, payload
|
|
||||||
);
|
|
||||||
mem.write_obj(payload, GuestAddress(self.current_offset))
|
|
||||||
.map_err(TdvfError::GuestMemoryWriteHob)?;
|
|
||||||
self.update_offset::<TdPayload>();
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod unit_tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[ignore]
|
|
||||||
fn test_parse_tdvf_sections() {
|
|
||||||
let mut f = File::open("tdvf.fd").unwrap();
|
|
||||||
let (sections, _) = parse_tdvf_sections(&mut f).unwrap();
|
|
||||||
for section in sections {
|
|
||||||
eprintln!("{section:x?}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
6
arch_gen/Cargo.lock
generated
Normal file
6
arch_gen/Cargo.lock
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
[[package]]
|
||||||
|
name = "arch_gen"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
7
arch_gen/Cargo.toml
Normal file
7
arch_gen/Cargo.toml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
[package]
|
||||||
|
name = "arch_gen"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Amazon firecracker team <firecracker-devel@amazon.com>"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
|
||||||
5
arch_gen/src/lib.rs
Normal file
5
arch_gen/src/lib.rs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||||
|
pub mod x86;
|
||||||
25
arch_gen/src/x86/mod.rs
Normal file
25
arch_gen/src/x86/mod.rs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
//
|
||||||
|
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE-BSD-3-Clause file.
|
||||||
|
|
||||||
|
#[allow(non_upper_case_globals)]
|
||||||
|
#[allow(non_camel_case_types)]
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
#[allow(
|
||||||
|
clippy::unreadable_literal,
|
||||||
|
clippy::redundant_static_lifetimes,
|
||||||
|
clippy::trivially_copy_pass_by_ref,
|
||||||
|
clippy::useless_transmute,
|
||||||
|
clippy::should_implement_trait,
|
||||||
|
clippy::transmute_ptr_to_ptr
|
||||||
|
)]
|
||||||
|
#[allow(non_camel_case_types)]
|
||||||
|
#[allow(non_upper_case_globals)]
|
||||||
|
#[allow(clippy::unreadable_literal, clippy::redundant_static_lifetimes)]
|
||||||
|
pub mod mpspec;
|
||||||
|
#[allow(non_upper_case_globals)]
|
||||||
|
#[allow(clippy::unreadable_literal, clippy::redundant_static_lifetimes)]
|
||||||
|
pub mod msr_index;
|
||||||
832
arch_gen/src/x86/mpspec.rs
Normal file
832
arch_gen/src/x86/mpspec.rs
Normal file
@@ -0,0 +1,832 @@
|
|||||||
|
// Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE-BSD-3-Clause file.
|
||||||
|
|
||||||
|
/* automatically generated by rust-bindgen */
|
||||||
|
|
||||||
|
pub const MPC_SIGNATURE: &'static [u8; 5usize] = b"PCMP\x00";
|
||||||
|
pub const MP_PROCESSOR: ::std::os::raw::c_uint = 0;
|
||||||
|
pub const MP_BUS: ::std::os::raw::c_uint = 1;
|
||||||
|
pub const MP_IOAPIC: ::std::os::raw::c_uint = 2;
|
||||||
|
pub const MP_INTSRC: ::std::os::raw::c_uint = 3;
|
||||||
|
pub const MP_LINTSRC: ::std::os::raw::c_uint = 4;
|
||||||
|
pub const MP_TRANSLATION: ::std::os::raw::c_uint = 192;
|
||||||
|
pub const CPU_ENABLED: ::std::os::raw::c_uint = 1;
|
||||||
|
pub const CPU_BOOTPROCESSOR: ::std::os::raw::c_uint = 2;
|
||||||
|
pub const CPU_STEPPING_MASK: ::std::os::raw::c_uint = 15;
|
||||||
|
pub const CPU_MODEL_MASK: ::std::os::raw::c_uint = 240;
|
||||||
|
pub const CPU_FAMILY_MASK: ::std::os::raw::c_uint = 3840;
|
||||||
|
pub const BUSTYPE_EISA: &'static [u8; 5usize] = b"EISA\x00";
|
||||||
|
pub const BUSTYPE_ISA: &'static [u8; 4usize] = b"ISA\x00";
|
||||||
|
pub const BUSTYPE_INTERN: &'static [u8; 7usize] = b"INTERN\x00";
|
||||||
|
pub const BUSTYPE_MCA: &'static [u8; 4usize] = b"MCA\x00";
|
||||||
|
pub const BUSTYPE_VL: &'static [u8; 3usize] = b"VL\x00";
|
||||||
|
pub const BUSTYPE_PCI: &'static [u8; 4usize] = b"PCI\x00";
|
||||||
|
pub const BUSTYPE_PCMCIA: &'static [u8; 7usize] = b"PCMCIA\x00";
|
||||||
|
pub const BUSTYPE_CBUS: &'static [u8; 5usize] = b"CBUS\x00";
|
||||||
|
pub const BUSTYPE_CBUSII: &'static [u8; 7usize] = b"CBUSII\x00";
|
||||||
|
pub const BUSTYPE_FUTURE: &'static [u8; 7usize] = b"FUTURE\x00";
|
||||||
|
pub const BUSTYPE_MBI: &'static [u8; 4usize] = b"MBI\x00";
|
||||||
|
pub const BUSTYPE_MBII: &'static [u8; 5usize] = b"MBII\x00";
|
||||||
|
pub const BUSTYPE_MPI: &'static [u8; 4usize] = b"MPI\x00";
|
||||||
|
pub const BUSTYPE_MPSA: &'static [u8; 5usize] = b"MPSA\x00";
|
||||||
|
pub const BUSTYPE_NUBUS: &'static [u8; 6usize] = b"NUBUS\x00";
|
||||||
|
pub const BUSTYPE_TC: &'static [u8; 3usize] = b"TC\x00";
|
||||||
|
pub const BUSTYPE_VME: &'static [u8; 4usize] = b"VME\x00";
|
||||||
|
pub const BUSTYPE_XPRESS: &'static [u8; 7usize] = b"XPRESS\x00";
|
||||||
|
pub const MPC_APIC_USABLE: ::std::os::raw::c_uint = 1;
|
||||||
|
pub const MP_IRQDIR_DEFAULT: ::std::os::raw::c_uint = 0;
|
||||||
|
pub const MP_IRQDIR_HIGH: ::std::os::raw::c_uint = 1;
|
||||||
|
pub const MP_IRQDIR_LOW: ::std::os::raw::c_uint = 3;
|
||||||
|
pub const MP_APIC_ALL: ::std::os::raw::c_uint = 255;
|
||||||
|
pub const MPC_OEM_SIGNATURE: &'static [u8; 5usize] = b"_OEM\x00";
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Default, Copy)]
|
||||||
|
pub struct mpf_intel {
|
||||||
|
pub signature: [::std::os::raw::c_char; 4usize],
|
||||||
|
pub physptr: ::std::os::raw::c_uint,
|
||||||
|
pub length: ::std::os::raw::c_uchar,
|
||||||
|
pub specification: ::std::os::raw::c_uchar,
|
||||||
|
pub checksum: ::std::os::raw::c_uchar,
|
||||||
|
pub feature1: ::std::os::raw::c_uchar,
|
||||||
|
pub feature2: ::std::os::raw::c_uchar,
|
||||||
|
pub feature3: ::std::os::raw::c_uchar,
|
||||||
|
pub feature4: ::std::os::raw::c_uchar,
|
||||||
|
pub feature5: ::std::os::raw::c_uchar,
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn bindgen_test_layout_mpf_intel() {
|
||||||
|
assert_eq!(
|
||||||
|
::std::mem::size_of::<mpf_intel>(),
|
||||||
|
16usize,
|
||||||
|
concat!("Size of: ", stringify!(mpf_intel))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
::std::mem::align_of::<mpf_intel>(),
|
||||||
|
4usize,
|
||||||
|
concat!("Alignment of ", stringify!(mpf_intel))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(0 as *const mpf_intel)).signature as *const _ as usize },
|
||||||
|
0usize,
|
||||||
|
concat!(
|
||||||
|
"Alignment of field: ",
|
||||||
|
stringify!(mpf_intel),
|
||||||
|
"::",
|
||||||
|
stringify!(signature)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(0 as *const mpf_intel)).physptr as *const _ as usize },
|
||||||
|
4usize,
|
||||||
|
concat!(
|
||||||
|
"Alignment of field: ",
|
||||||
|
stringify!(mpf_intel),
|
||||||
|
"::",
|
||||||
|
stringify!(physptr)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(0 as *const mpf_intel)).length as *const _ as usize },
|
||||||
|
8usize,
|
||||||
|
concat!(
|
||||||
|
"Alignment of field: ",
|
||||||
|
stringify!(mpf_intel),
|
||||||
|
"::",
|
||||||
|
stringify!(length)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(0 as *const mpf_intel)).specification as *const _ as usize },
|
||||||
|
9usize,
|
||||||
|
concat!(
|
||||||
|
"Alignment of field: ",
|
||||||
|
stringify!(mpf_intel),
|
||||||
|
"::",
|
||||||
|
stringify!(specification)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(0 as *const mpf_intel)).checksum as *const _ as usize },
|
||||||
|
10usize,
|
||||||
|
concat!(
|
||||||
|
"Alignment of field: ",
|
||||||
|
stringify!(mpf_intel),
|
||||||
|
"::",
|
||||||
|
stringify!(checksum)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(0 as *const mpf_intel)).feature1 as *const _ as usize },
|
||||||
|
11usize,
|
||||||
|
concat!(
|
||||||
|
"Alignment of field: ",
|
||||||
|
stringify!(mpf_intel),
|
||||||
|
"::",
|
||||||
|
stringify!(feature1)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(0 as *const mpf_intel)).feature2 as *const _ as usize },
|
||||||
|
12usize,
|
||||||
|
concat!(
|
||||||
|
"Alignment of field: ",
|
||||||
|
stringify!(mpf_intel),
|
||||||
|
"::",
|
||||||
|
stringify!(feature2)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(0 as *const mpf_intel)).feature3 as *const _ as usize },
|
||||||
|
13usize,
|
||||||
|
concat!(
|
||||||
|
"Alignment of field: ",
|
||||||
|
stringify!(mpf_intel),
|
||||||
|
"::",
|
||||||
|
stringify!(feature3)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(0 as *const mpf_intel)).feature4 as *const _ as usize },
|
||||||
|
14usize,
|
||||||
|
concat!(
|
||||||
|
"Alignment of field: ",
|
||||||
|
stringify!(mpf_intel),
|
||||||
|
"::",
|
||||||
|
stringify!(feature4)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(0 as *const mpf_intel)).feature5 as *const _ as usize },
|
||||||
|
15usize,
|
||||||
|
concat!(
|
||||||
|
"Alignment of field: ",
|
||||||
|
stringify!(mpf_intel),
|
||||||
|
"::",
|
||||||
|
stringify!(feature5)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
impl Clone for mpf_intel {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
*self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Default, Copy)]
|
||||||
|
pub struct mpc_table {
|
||||||
|
pub signature: [::std::os::raw::c_char; 4usize],
|
||||||
|
pub length: ::std::os::raw::c_ushort,
|
||||||
|
pub spec: ::std::os::raw::c_char,
|
||||||
|
pub checksum: ::std::os::raw::c_char,
|
||||||
|
pub oem: [::std::os::raw::c_char; 8usize],
|
||||||
|
pub productid: [::std::os::raw::c_char; 12usize],
|
||||||
|
pub oemptr: ::std::os::raw::c_uint,
|
||||||
|
pub oemsize: ::std::os::raw::c_ushort,
|
||||||
|
pub oemcount: ::std::os::raw::c_ushort,
|
||||||
|
pub lapic: ::std::os::raw::c_uint,
|
||||||
|
pub reserved: ::std::os::raw::c_uint,
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn bindgen_test_layout_mpc_table() {
|
||||||
|
assert_eq!(
|
||||||
|
::std::mem::size_of::<mpc_table>(),
|
||||||
|
44usize,
|
||||||
|
concat!("Size of: ", stringify!(mpc_table))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
::std::mem::align_of::<mpc_table>(),
|
||||||
|
4usize,
|
||||||
|
concat!("Alignment of ", stringify!(mpc_table))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(0 as *const mpc_table)).signature as *const _ as usize },
|
||||||
|
0usize,
|
||||||
|
concat!(
|
||||||
|
"Alignment of field: ",
|
||||||
|
stringify!(mpc_table),
|
||||||
|
"::",
|
||||||
|
stringify!(signature)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(0 as *const mpc_table)).length as *const _ as usize },
|
||||||
|
4usize,
|
||||||
|
concat!(
|
||||||
|
"Alignment of field: ",
|
||||||
|
stringify!(mpc_table),
|
||||||
|
"::",
|
||||||
|
stringify!(length)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(0 as *const mpc_table)).spec as *const _ as usize },
|
||||||
|
6usize,
|
||||||
|
concat!(
|
||||||
|
"Alignment of field: ",
|
||||||
|
stringify!(mpc_table),
|
||||||
|
"::",
|
||||||
|
stringify!(spec)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(0 as *const mpc_table)).checksum as *const _ as usize },
|
||||||
|
7usize,
|
||||||
|
concat!(
|
||||||
|
"Alignment of field: ",
|
||||||
|
stringify!(mpc_table),
|
||||||
|
"::",
|
||||||
|
stringify!(checksum)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(0 as *const mpc_table)).oem as *const _ as usize },
|
||||||
|
8usize,
|
||||||
|
concat!(
|
||||||
|
"Alignment of field: ",
|
||||||
|
stringify!(mpc_table),
|
||||||
|
"::",
|
||||||
|
stringify!(oem)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(0 as *const mpc_table)).productid as *const _ as usize },
|
||||||
|
16usize,
|
||||||
|
concat!(
|
||||||
|
"Alignment of field: ",
|
||||||
|
stringify!(mpc_table),
|
||||||
|
"::",
|
||||||
|
stringify!(productid)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(0 as *const mpc_table)).oemptr as *const _ as usize },
|
||||||
|
28usize,
|
||||||
|
concat!(
|
||||||
|
"Alignment of field: ",
|
||||||
|
stringify!(mpc_table),
|
||||||
|
"::",
|
||||||
|
stringify!(oemptr)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(0 as *const mpc_table)).oemsize as *const _ as usize },
|
||||||
|
32usize,
|
||||||
|
concat!(
|
||||||
|
"Alignment of field: ",
|
||||||
|
stringify!(mpc_table),
|
||||||
|
"::",
|
||||||
|
stringify!(oemsize)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(0 as *const mpc_table)).oemcount as *const _ as usize },
|
||||||
|
34usize,
|
||||||
|
concat!(
|
||||||
|
"Alignment of field: ",
|
||||||
|
stringify!(mpc_table),
|
||||||
|
"::",
|
||||||
|
stringify!(oemcount)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(0 as *const mpc_table)).lapic as *const _ as usize },
|
||||||
|
36usize,
|
||||||
|
concat!(
|
||||||
|
"Alignment of field: ",
|
||||||
|
stringify!(mpc_table),
|
||||||
|
"::",
|
||||||
|
stringify!(lapic)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(0 as *const mpc_table)).reserved as *const _ as usize },
|
||||||
|
40usize,
|
||||||
|
concat!(
|
||||||
|
"Alignment of field: ",
|
||||||
|
stringify!(mpc_table),
|
||||||
|
"::",
|
||||||
|
stringify!(reserved)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
impl Clone for mpc_table {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
*self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Default, Copy)]
|
||||||
|
pub struct mpc_cpu {
|
||||||
|
pub type_: ::std::os::raw::c_uchar,
|
||||||
|
pub apicid: ::std::os::raw::c_uchar,
|
||||||
|
pub apicver: ::std::os::raw::c_uchar,
|
||||||
|
pub cpuflag: ::std::os::raw::c_uchar,
|
||||||
|
pub cpufeature: ::std::os::raw::c_uint,
|
||||||
|
pub featureflag: ::std::os::raw::c_uint,
|
||||||
|
pub reserved: [::std::os::raw::c_uint; 2usize],
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn bindgen_test_layout_mpc_cpu() {
|
||||||
|
assert_eq!(
|
||||||
|
::std::mem::size_of::<mpc_cpu>(),
|
||||||
|
20usize,
|
||||||
|
concat!("Size of: ", stringify!(mpc_cpu))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
::std::mem::align_of::<mpc_cpu>(),
|
||||||
|
4usize,
|
||||||
|
concat!("Alignment of ", stringify!(mpc_cpu))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(0 as *const mpc_cpu)).type_ as *const _ as usize },
|
||||||
|
0usize,
|
||||||
|
concat!(
|
||||||
|
"Alignment of field: ",
|
||||||
|
stringify!(mpc_cpu),
|
||||||
|
"::",
|
||||||
|
stringify!(type_)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(0 as *const mpc_cpu)).apicid as *const _ as usize },
|
||||||
|
1usize,
|
||||||
|
concat!(
|
||||||
|
"Alignment of field: ",
|
||||||
|
stringify!(mpc_cpu),
|
||||||
|
"::",
|
||||||
|
stringify!(apicid)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(0 as *const mpc_cpu)).apicver as *const _ as usize },
|
||||||
|
2usize,
|
||||||
|
concat!(
|
||||||
|
"Alignment of field: ",
|
||||||
|
stringify!(mpc_cpu),
|
||||||
|
"::",
|
||||||
|
stringify!(apicver)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(0 as *const mpc_cpu)).cpuflag as *const _ as usize },
|
||||||
|
3usize,
|
||||||
|
concat!(
|
||||||
|
"Alignment of field: ",
|
||||||
|
stringify!(mpc_cpu),
|
||||||
|
"::",
|
||||||
|
stringify!(cpuflag)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(0 as *const mpc_cpu)).cpufeature as *const _ as usize },
|
||||||
|
4usize,
|
||||||
|
concat!(
|
||||||
|
"Alignment of field: ",
|
||||||
|
stringify!(mpc_cpu),
|
||||||
|
"::",
|
||||||
|
stringify!(cpufeature)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(0 as *const mpc_cpu)).featureflag as *const _ as usize },
|
||||||
|
8usize,
|
||||||
|
concat!(
|
||||||
|
"Alignment of field: ",
|
||||||
|
stringify!(mpc_cpu),
|
||||||
|
"::",
|
||||||
|
stringify!(featureflag)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(0 as *const mpc_cpu)).reserved as *const _ as usize },
|
||||||
|
12usize,
|
||||||
|
concat!(
|
||||||
|
"Alignment of field: ",
|
||||||
|
stringify!(mpc_cpu),
|
||||||
|
"::",
|
||||||
|
stringify!(reserved)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
impl Clone for mpc_cpu {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
*self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Default, Copy)]
|
||||||
|
pub struct mpc_bus {
|
||||||
|
pub type_: ::std::os::raw::c_uchar,
|
||||||
|
pub busid: ::std::os::raw::c_uchar,
|
||||||
|
pub bustype: [::std::os::raw::c_uchar; 6usize],
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn bindgen_test_layout_mpc_bus() {
|
||||||
|
assert_eq!(
|
||||||
|
::std::mem::size_of::<mpc_bus>(),
|
||||||
|
8usize,
|
||||||
|
concat!("Size of: ", stringify!(mpc_bus))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
::std::mem::align_of::<mpc_bus>(),
|
||||||
|
1usize,
|
||||||
|
concat!("Alignment of ", stringify!(mpc_bus))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(0 as *const mpc_bus)).type_ as *const _ as usize },
|
||||||
|
0usize,
|
||||||
|
concat!(
|
||||||
|
"Alignment of field: ",
|
||||||
|
stringify!(mpc_bus),
|
||||||
|
"::",
|
||||||
|
stringify!(type_)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(0 as *const mpc_bus)).busid as *const _ as usize },
|
||||||
|
1usize,
|
||||||
|
concat!(
|
||||||
|
"Alignment of field: ",
|
||||||
|
stringify!(mpc_bus),
|
||||||
|
"::",
|
||||||
|
stringify!(busid)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(0 as *const mpc_bus)).bustype as *const _ as usize },
|
||||||
|
2usize,
|
||||||
|
concat!(
|
||||||
|
"Alignment of field: ",
|
||||||
|
stringify!(mpc_bus),
|
||||||
|
"::",
|
||||||
|
stringify!(bustype)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
impl Clone for mpc_bus {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
*self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Default, Copy)]
|
||||||
|
pub struct mpc_ioapic {
|
||||||
|
pub type_: ::std::os::raw::c_uchar,
|
||||||
|
pub apicid: ::std::os::raw::c_uchar,
|
||||||
|
pub apicver: ::std::os::raw::c_uchar,
|
||||||
|
pub flags: ::std::os::raw::c_uchar,
|
||||||
|
pub apicaddr: ::std::os::raw::c_uint,
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn bindgen_test_layout_mpc_ioapic() {
|
||||||
|
assert_eq!(
|
||||||
|
::std::mem::size_of::<mpc_ioapic>(),
|
||||||
|
8usize,
|
||||||
|
concat!("Size of: ", stringify!(mpc_ioapic))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
::std::mem::align_of::<mpc_ioapic>(),
|
||||||
|
4usize,
|
||||||
|
concat!("Alignment of ", stringify!(mpc_ioapic))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(0 as *const mpc_ioapic)).type_ as *const _ as usize },
|
||||||
|
0usize,
|
||||||
|
concat!(
|
||||||
|
"Alignment of field: ",
|
||||||
|
stringify!(mpc_ioapic),
|
||||||
|
"::",
|
||||||
|
stringify!(type_)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(0 as *const mpc_ioapic)).apicid as *const _ as usize },
|
||||||
|
1usize,
|
||||||
|
concat!(
|
||||||
|
"Alignment of field: ",
|
||||||
|
stringify!(mpc_ioapic),
|
||||||
|
"::",
|
||||||
|
stringify!(apicid)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(0 as *const mpc_ioapic)).apicver as *const _ as usize },
|
||||||
|
2usize,
|
||||||
|
concat!(
|
||||||
|
"Alignment of field: ",
|
||||||
|
stringify!(mpc_ioapic),
|
||||||
|
"::",
|
||||||
|
stringify!(apicver)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(0 as *const mpc_ioapic)).flags as *const _ as usize },
|
||||||
|
3usize,
|
||||||
|
concat!(
|
||||||
|
"Alignment of field: ",
|
||||||
|
stringify!(mpc_ioapic),
|
||||||
|
"::",
|
||||||
|
stringify!(flags)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(0 as *const mpc_ioapic)).apicaddr as *const _ as usize },
|
||||||
|
4usize,
|
||||||
|
concat!(
|
||||||
|
"Alignment of field: ",
|
||||||
|
stringify!(mpc_ioapic),
|
||||||
|
"::",
|
||||||
|
stringify!(apicaddr)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
impl Clone for mpc_ioapic {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
*self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Default, Copy)]
|
||||||
|
pub struct mpc_intsrc {
|
||||||
|
pub type_: ::std::os::raw::c_uchar,
|
||||||
|
pub irqtype: ::std::os::raw::c_uchar,
|
||||||
|
pub irqflag: ::std::os::raw::c_ushort,
|
||||||
|
pub srcbus: ::std::os::raw::c_uchar,
|
||||||
|
pub srcbusirq: ::std::os::raw::c_uchar,
|
||||||
|
pub dstapic: ::std::os::raw::c_uchar,
|
||||||
|
pub dstirq: ::std::os::raw::c_uchar,
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn bindgen_test_layout_mpc_intsrc() {
|
||||||
|
assert_eq!(
|
||||||
|
::std::mem::size_of::<mpc_intsrc>(),
|
||||||
|
8usize,
|
||||||
|
concat!("Size of: ", stringify!(mpc_intsrc))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
::std::mem::align_of::<mpc_intsrc>(),
|
||||||
|
2usize,
|
||||||
|
concat!("Alignment of ", stringify!(mpc_intsrc))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(0 as *const mpc_intsrc)).type_ as *const _ as usize },
|
||||||
|
0usize,
|
||||||
|
concat!(
|
||||||
|
"Alignment of field: ",
|
||||||
|
stringify!(mpc_intsrc),
|
||||||
|
"::",
|
||||||
|
stringify!(type_)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(0 as *const mpc_intsrc)).irqtype as *const _ as usize },
|
||||||
|
1usize,
|
||||||
|
concat!(
|
||||||
|
"Alignment of field: ",
|
||||||
|
stringify!(mpc_intsrc),
|
||||||
|
"::",
|
||||||
|
stringify!(irqtype)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(0 as *const mpc_intsrc)).irqflag as *const _ as usize },
|
||||||
|
2usize,
|
||||||
|
concat!(
|
||||||
|
"Alignment of field: ",
|
||||||
|
stringify!(mpc_intsrc),
|
||||||
|
"::",
|
||||||
|
stringify!(irqflag)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(0 as *const mpc_intsrc)).srcbus as *const _ as usize },
|
||||||
|
4usize,
|
||||||
|
concat!(
|
||||||
|
"Alignment of field: ",
|
||||||
|
stringify!(mpc_intsrc),
|
||||||
|
"::",
|
||||||
|
stringify!(srcbus)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(0 as *const mpc_intsrc)).srcbusirq as *const _ as usize },
|
||||||
|
5usize,
|
||||||
|
concat!(
|
||||||
|
"Alignment of field: ",
|
||||||
|
stringify!(mpc_intsrc),
|
||||||
|
"::",
|
||||||
|
stringify!(srcbusirq)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(0 as *const mpc_intsrc)).dstapic as *const _ as usize },
|
||||||
|
6usize,
|
||||||
|
concat!(
|
||||||
|
"Alignment of field: ",
|
||||||
|
stringify!(mpc_intsrc),
|
||||||
|
"::",
|
||||||
|
stringify!(dstapic)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(0 as *const mpc_intsrc)).dstirq as *const _ as usize },
|
||||||
|
7usize,
|
||||||
|
concat!(
|
||||||
|
"Alignment of field: ",
|
||||||
|
stringify!(mpc_intsrc),
|
||||||
|
"::",
|
||||||
|
stringify!(dstirq)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
impl Clone for mpc_intsrc {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
*self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub const mp_irq_source_types_mp_INT: mp_irq_source_types = 0;
|
||||||
|
pub const mp_irq_source_types_mp_NMI: mp_irq_source_types = 1;
|
||||||
|
pub const mp_irq_source_types_mp_SMI: mp_irq_source_types = 2;
|
||||||
|
pub const mp_irq_source_types_mp_ExtINT: mp_irq_source_types = 3;
|
||||||
|
pub type mp_irq_source_types = ::std::os::raw::c_uint;
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Default, Copy)]
|
||||||
|
pub struct mpc_lintsrc {
|
||||||
|
pub type_: ::std::os::raw::c_uchar,
|
||||||
|
pub irqtype: ::std::os::raw::c_uchar,
|
||||||
|
pub irqflag: ::std::os::raw::c_ushort,
|
||||||
|
pub srcbusid: ::std::os::raw::c_uchar,
|
||||||
|
pub srcbusirq: ::std::os::raw::c_uchar,
|
||||||
|
pub destapic: ::std::os::raw::c_uchar,
|
||||||
|
pub destapiclint: ::std::os::raw::c_uchar,
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn bindgen_test_layout_mpc_lintsrc() {
|
||||||
|
assert_eq!(
|
||||||
|
::std::mem::size_of::<mpc_lintsrc>(),
|
||||||
|
8usize,
|
||||||
|
concat!("Size of: ", stringify!(mpc_lintsrc))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
::std::mem::align_of::<mpc_lintsrc>(),
|
||||||
|
2usize,
|
||||||
|
concat!("Alignment of ", stringify!(mpc_lintsrc))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(0 as *const mpc_lintsrc)).type_ as *const _ as usize },
|
||||||
|
0usize,
|
||||||
|
concat!(
|
||||||
|
"Alignment of field: ",
|
||||||
|
stringify!(mpc_lintsrc),
|
||||||
|
"::",
|
||||||
|
stringify!(type_)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(0 as *const mpc_lintsrc)).irqtype as *const _ as usize },
|
||||||
|
1usize,
|
||||||
|
concat!(
|
||||||
|
"Alignment of field: ",
|
||||||
|
stringify!(mpc_lintsrc),
|
||||||
|
"::",
|
||||||
|
stringify!(irqtype)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(0 as *const mpc_lintsrc)).irqflag as *const _ as usize },
|
||||||
|
2usize,
|
||||||
|
concat!(
|
||||||
|
"Alignment of field: ",
|
||||||
|
stringify!(mpc_lintsrc),
|
||||||
|
"::",
|
||||||
|
stringify!(irqflag)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(0 as *const mpc_lintsrc)).srcbusid as *const _ as usize },
|
||||||
|
4usize,
|
||||||
|
concat!(
|
||||||
|
"Alignment of field: ",
|
||||||
|
stringify!(mpc_lintsrc),
|
||||||
|
"::",
|
||||||
|
stringify!(srcbusid)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(0 as *const mpc_lintsrc)).srcbusirq as *const _ as usize },
|
||||||
|
5usize,
|
||||||
|
concat!(
|
||||||
|
"Alignment of field: ",
|
||||||
|
stringify!(mpc_lintsrc),
|
||||||
|
"::",
|
||||||
|
stringify!(srcbusirq)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(0 as *const mpc_lintsrc)).destapic as *const _ as usize },
|
||||||
|
6usize,
|
||||||
|
concat!(
|
||||||
|
"Alignment of field: ",
|
||||||
|
stringify!(mpc_lintsrc),
|
||||||
|
"::",
|
||||||
|
stringify!(destapic)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(0 as *const mpc_lintsrc)).destapiclint as *const _ as usize },
|
||||||
|
7usize,
|
||||||
|
concat!(
|
||||||
|
"Alignment of field: ",
|
||||||
|
stringify!(mpc_lintsrc),
|
||||||
|
"::",
|
||||||
|
stringify!(destapiclint)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
impl Clone for mpc_lintsrc {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
*self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Default, Copy)]
|
||||||
|
pub struct mpc_oemtable {
|
||||||
|
pub signature: [::std::os::raw::c_char; 4usize],
|
||||||
|
pub length: ::std::os::raw::c_ushort,
|
||||||
|
pub rev: ::std::os::raw::c_char,
|
||||||
|
pub checksum: ::std::os::raw::c_char,
|
||||||
|
pub mpc: [::std::os::raw::c_char; 8usize],
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn bindgen_test_layout_mpc_oemtable() {
|
||||||
|
assert_eq!(
|
||||||
|
::std::mem::size_of::<mpc_oemtable>(),
|
||||||
|
16usize,
|
||||||
|
concat!("Size of: ", stringify!(mpc_oemtable))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
::std::mem::align_of::<mpc_oemtable>(),
|
||||||
|
2usize,
|
||||||
|
concat!("Alignment of ", stringify!(mpc_oemtable))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(0 as *const mpc_oemtable)).signature as *const _ as usize },
|
||||||
|
0usize,
|
||||||
|
concat!(
|
||||||
|
"Alignment of field: ",
|
||||||
|
stringify!(mpc_oemtable),
|
||||||
|
"::",
|
||||||
|
stringify!(signature)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(0 as *const mpc_oemtable)).length as *const _ as usize },
|
||||||
|
4usize,
|
||||||
|
concat!(
|
||||||
|
"Alignment of field: ",
|
||||||
|
stringify!(mpc_oemtable),
|
||||||
|
"::",
|
||||||
|
stringify!(length)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(0 as *const mpc_oemtable)).rev as *const _ as usize },
|
||||||
|
6usize,
|
||||||
|
concat!(
|
||||||
|
"Alignment of field: ",
|
||||||
|
stringify!(mpc_oemtable),
|
||||||
|
"::",
|
||||||
|
stringify!(rev)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(0 as *const mpc_oemtable)).checksum as *const _ as usize },
|
||||||
|
7usize,
|
||||||
|
concat!(
|
||||||
|
"Alignment of field: ",
|
||||||
|
stringify!(mpc_oemtable),
|
||||||
|
"::",
|
||||||
|
stringify!(checksum)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { &(*(0 as *const mpc_oemtable)).mpc as *const _ as usize },
|
||||||
|
8usize,
|
||||||
|
concat!(
|
||||||
|
"Alignment of field: ",
|
||||||
|
stringify!(mpc_oemtable),
|
||||||
|
"::",
|
||||||
|
stringify!(mpc)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
impl Clone for mpc_oemtable {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
*self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub const mp_bustype_MP_BUS_ISA: mp_bustype = 1;
|
||||||
|
pub const mp_bustype_MP_BUS_EISA: mp_bustype = 2;
|
||||||
|
pub const mp_bustype_MP_BUS_PCI: mp_bustype = 3;
|
||||||
|
pub type mp_bustype = ::std::os::raw::c_uint;
|
||||||
543
arch_gen/src/x86/msr_index.rs
Normal file
543
arch_gen/src/x86/msr_index.rs
Normal file
@@ -0,0 +1,543 @@
|
|||||||
|
// Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE-BSD-3-Clause file.
|
||||||
|
|
||||||
|
/*
|
||||||
|
* automatically generated by rust-bindgen
|
||||||
|
* From upstream linux msr-index.h at commit:
|
||||||
|
* 806276b7f07a39a1cc3f38bb1ef5c573d4594a38
|
||||||
|
*/
|
||||||
|
|
||||||
|
pub const MSR_EFER: ::std::os::raw::c_uint = 0xc0000080;
|
||||||
|
pub const MSR_STAR: ::std::os::raw::c_uint = 0xc0000081;
|
||||||
|
pub const MSR_LSTAR: ::std::os::raw::c_uint = 0xc0000082;
|
||||||
|
pub const MSR_CSTAR: ::std::os::raw::c_uint = 0xc0000083;
|
||||||
|
pub const MSR_SYSCALL_MASK: ::std::os::raw::c_uint = 0xc0000084;
|
||||||
|
pub const MSR_FS_BASE: ::std::os::raw::c_uint = 0xc0000100;
|
||||||
|
pub const MSR_GS_BASE: ::std::os::raw::c_uint = 0xc0000101;
|
||||||
|
pub const MSR_KERNEL_GS_BASE: ::std::os::raw::c_uint = 0xc0000102;
|
||||||
|
pub const MSR_TSC_AUX: ::std::os::raw::c_uint = 0xc0000103;
|
||||||
|
pub const _EFER_SCE: ::std::os::raw::c_uint = 0x00000000;
|
||||||
|
pub const _EFER_LME: ::std::os::raw::c_uint = 0x00000008;
|
||||||
|
pub const _EFER_LMA: ::std::os::raw::c_uint = 0x0000000a;
|
||||||
|
pub const _EFER_NX: ::std::os::raw::c_uint = 0x0000000b;
|
||||||
|
pub const _EFER_SVME: ::std::os::raw::c_uint = 0x0000000c;
|
||||||
|
pub const _EFER_LMSLE: ::std::os::raw::c_uint = 0x0000000d;
|
||||||
|
pub const _EFER_FFXSR: ::std::os::raw::c_uint = 0x0000000e;
|
||||||
|
pub const EFER_SCE: ::std::os::raw::c_uint = 0x00000001;
|
||||||
|
pub const EFER_LME: ::std::os::raw::c_uint = 0x00000100;
|
||||||
|
pub const EFER_LMA: ::std::os::raw::c_uint = 0x00000400;
|
||||||
|
pub const EFER_NX: ::std::os::raw::c_uint = 0x00000800;
|
||||||
|
pub const EFER_SVME: ::std::os::raw::c_uint = 0x00001000;
|
||||||
|
pub const EFER_LMSLE: ::std::os::raw::c_uint = 0x00002000;
|
||||||
|
pub const EFER_FFXSR: ::std::os::raw::c_uint = 0x00004000;
|
||||||
|
pub const MSR_PPIN_CTL: ::std::os::raw::c_uint = 0x0000004e;
|
||||||
|
pub const MSR_PPIN: ::std::os::raw::c_uint = 0x0000004f;
|
||||||
|
pub const MSR_IA32_PERFCTR0: ::std::os::raw::c_uint = 0x000000c1;
|
||||||
|
pub const MSR_IA32_PERFCTR1: ::std::os::raw::c_uint = 0x000000c2;
|
||||||
|
pub const MSR_FSB_FREQ: ::std::os::raw::c_uint = 0x000000cd;
|
||||||
|
pub const MSR_PLATFORM_INFO: ::std::os::raw::c_uint = 0x000000ce;
|
||||||
|
pub const MSR_PKG_CST_CONFIG_CONTROL: ::std::os::raw::c_uint = 0x000000e2;
|
||||||
|
pub const NHM_C3_AUTO_DEMOTE: ::std::os::raw::c_uint = 0x02000000;
|
||||||
|
pub const NHM_C1_AUTO_DEMOTE: ::std::os::raw::c_uint = 0x04000000;
|
||||||
|
pub const ATM_LNC_C6_AUTO_DEMOTE: ::std::os::raw::c_uint = 0x02000000;
|
||||||
|
pub const SNB_C1_AUTO_UNDEMOTE: ::std::os::raw::c_uint = 0x08000000;
|
||||||
|
pub const SNB_C3_AUTO_UNDEMOTE: ::std::os::raw::c_uint = 0x10000000;
|
||||||
|
pub const MSR_MTRRcap: ::std::os::raw::c_uint = 0x000000fe;
|
||||||
|
pub const MSR_IA32_BBL_CR_CTL: ::std::os::raw::c_uint = 0x00000119;
|
||||||
|
pub const MSR_IA32_BBL_CR_CTL3: ::std::os::raw::c_uint = 0x0000011e;
|
||||||
|
pub const MSR_IA32_SYSENTER_CS: ::std::os::raw::c_uint = 0x00000174;
|
||||||
|
pub const MSR_IA32_SYSENTER_ESP: ::std::os::raw::c_uint = 0x00000175;
|
||||||
|
pub const MSR_IA32_SYSENTER_EIP: ::std::os::raw::c_uint = 0x00000176;
|
||||||
|
pub const MSR_IA32_MCG_CAP: ::std::os::raw::c_uint = 0x00000179;
|
||||||
|
pub const MSR_IA32_MCG_STATUS: ::std::os::raw::c_uint = 0x0000017a;
|
||||||
|
pub const MSR_IA32_MCG_CTL: ::std::os::raw::c_uint = 0x0000017b;
|
||||||
|
pub const MSR_IA32_MCG_EXT_CTL: ::std::os::raw::c_uint = 0x000004d0;
|
||||||
|
pub const MSR_OFFCORE_RSP_0: ::std::os::raw::c_uint = 0x000001a6;
|
||||||
|
pub const MSR_OFFCORE_RSP_1: ::std::os::raw::c_uint = 0x000001a7;
|
||||||
|
pub const MSR_TURBO_RATIO_LIMIT: ::std::os::raw::c_uint = 0x000001ad;
|
||||||
|
pub const MSR_TURBO_RATIO_LIMIT1: ::std::os::raw::c_uint = 0x000001ae;
|
||||||
|
pub const MSR_TURBO_RATIO_LIMIT2: ::std::os::raw::c_uint = 0x000001af;
|
||||||
|
pub const MSR_LBR_SELECT: ::std::os::raw::c_uint = 0x000001c8;
|
||||||
|
pub const MSR_LBR_TOS: ::std::os::raw::c_uint = 0x000001c9;
|
||||||
|
pub const MSR_LBR_NHM_FROM: ::std::os::raw::c_uint = 0x00000680;
|
||||||
|
pub const MSR_LBR_NHM_TO: ::std::os::raw::c_uint = 0x000006c0;
|
||||||
|
pub const MSR_LBR_CORE_FROM: ::std::os::raw::c_uint = 0x00000040;
|
||||||
|
pub const MSR_LBR_CORE_TO: ::std::os::raw::c_uint = 0x00000060;
|
||||||
|
pub const MSR_LBR_INFO_0: ::std::os::raw::c_uint = 0x00000dc0;
|
||||||
|
pub const LBR_INFO_CYCLES: ::std::os::raw::c_uint = 0x0000ffff;
|
||||||
|
pub const MSR_IA32_PEBS_ENABLE: ::std::os::raw::c_uint = 0x000003f1;
|
||||||
|
pub const MSR_IA32_DS_AREA: ::std::os::raw::c_uint = 0x00000600;
|
||||||
|
pub const MSR_IA32_PERF_CAPABILITIES: ::std::os::raw::c_uint = 0x00000345;
|
||||||
|
pub const MSR_PEBS_LD_LAT_THRESHOLD: ::std::os::raw::c_uint = 0x000003f6;
|
||||||
|
pub const MSR_IA32_RTIT_CTL: ::std::os::raw::c_uint = 0x00000570;
|
||||||
|
pub const MSR_IA32_RTIT_STATUS: ::std::os::raw::c_uint = 0x00000571;
|
||||||
|
pub const MSR_IA32_RTIT_ADDR0_A: ::std::os::raw::c_uint = 0x00000580;
|
||||||
|
pub const MSR_IA32_RTIT_ADDR0_B: ::std::os::raw::c_uint = 0x00000581;
|
||||||
|
pub const MSR_IA32_RTIT_ADDR1_A: ::std::os::raw::c_uint = 0x00000582;
|
||||||
|
pub const MSR_IA32_RTIT_ADDR1_B: ::std::os::raw::c_uint = 0x00000583;
|
||||||
|
pub const MSR_IA32_RTIT_ADDR2_A: ::std::os::raw::c_uint = 0x00000584;
|
||||||
|
pub const MSR_IA32_RTIT_ADDR2_B: ::std::os::raw::c_uint = 0x00000585;
|
||||||
|
pub const MSR_IA32_RTIT_ADDR3_A: ::std::os::raw::c_uint = 0x00000586;
|
||||||
|
pub const MSR_IA32_RTIT_ADDR3_B: ::std::os::raw::c_uint = 0x00000587;
|
||||||
|
pub const MSR_IA32_RTIT_CR3_MATCH: ::std::os::raw::c_uint = 0x00000572;
|
||||||
|
pub const MSR_IA32_RTIT_OUTPUT_BASE: ::std::os::raw::c_uint = 0x00000560;
|
||||||
|
pub const MSR_IA32_RTIT_OUTPUT_MASK: ::std::os::raw::c_uint = 0x00000561;
|
||||||
|
pub const MSR_MTRRfix64K_00000: ::std::os::raw::c_uint = 0x00000250;
|
||||||
|
pub const MSR_MTRRfix16K_80000: ::std::os::raw::c_uint = 0x00000258;
|
||||||
|
pub const MSR_MTRRfix16K_A0000: ::std::os::raw::c_uint = 0x00000259;
|
||||||
|
pub const MSR_MTRRfix4K_C0000: ::std::os::raw::c_uint = 0x00000268;
|
||||||
|
pub const MSR_MTRRfix4K_C8000: ::std::os::raw::c_uint = 0x00000269;
|
||||||
|
pub const MSR_MTRRfix4K_D0000: ::std::os::raw::c_uint = 0x0000026a;
|
||||||
|
pub const MSR_MTRRfix4K_D8000: ::std::os::raw::c_uint = 0x0000026b;
|
||||||
|
pub const MSR_MTRRfix4K_E0000: ::std::os::raw::c_uint = 0x0000026c;
|
||||||
|
pub const MSR_MTRRfix4K_E8000: ::std::os::raw::c_uint = 0x0000026d;
|
||||||
|
pub const MSR_MTRRfix4K_F0000: ::std::os::raw::c_uint = 0x0000026e;
|
||||||
|
pub const MSR_MTRRfix4K_F8000: ::std::os::raw::c_uint = 0x0000026f;
|
||||||
|
pub const MSR_MTRRdefType: ::std::os::raw::c_uint = 0x000002ff;
|
||||||
|
pub const MSR_IA32_CR_PAT: ::std::os::raw::c_uint = 0x00000277;
|
||||||
|
pub const MSR_IA32_DEBUGCTLMSR: ::std::os::raw::c_uint = 0x000001d9;
|
||||||
|
pub const MSR_IA32_LASTBRANCHFROMIP: ::std::os::raw::c_uint = 0x000001db;
|
||||||
|
pub const MSR_IA32_LASTBRANCHTOIP: ::std::os::raw::c_uint = 0x000001dc;
|
||||||
|
pub const MSR_IA32_LASTINTFROMIP: ::std::os::raw::c_uint = 0x000001dd;
|
||||||
|
pub const MSR_IA32_LASTINTTOIP: ::std::os::raw::c_uint = 0x000001de;
|
||||||
|
pub const DEBUGCTLMSR_LBR: ::std::os::raw::c_uint = 0x00000001;
|
||||||
|
pub const DEBUGCTLMSR_BTF: ::std::os::raw::c_uint = 0x00000002;
|
||||||
|
pub const DEBUGCTLMSR_TR: ::std::os::raw::c_uint = 0x00000040;
|
||||||
|
pub const DEBUGCTLMSR_BTS: ::std::os::raw::c_uint = 0x00000080;
|
||||||
|
pub const DEBUGCTLMSR_BTINT: ::std::os::raw::c_uint = 0x00000100;
|
||||||
|
pub const DEBUGCTLMSR_BTS_OFF_OS: ::std::os::raw::c_uint = 0x00000200;
|
||||||
|
pub const DEBUGCTLMSR_BTS_OFF_USR: ::std::os::raw::c_uint = 0x00000400;
|
||||||
|
pub const DEBUGCTLMSR_FREEZE_LBRS_ON_PMI: ::std::os::raw::c_uint = 0x00000800;
|
||||||
|
pub const MSR_PEBS_FRONTEND: ::std::os::raw::c_uint = 0x000003f7;
|
||||||
|
pub const MSR_IA32_POWER_CTL: ::std::os::raw::c_uint = 0x000001fc;
|
||||||
|
pub const MSR_IA32_MC0_CTL: ::std::os::raw::c_uint = 0x00000400;
|
||||||
|
pub const MSR_IA32_MC0_STATUS: ::std::os::raw::c_uint = 0x00000401;
|
||||||
|
pub const MSR_IA32_MC0_ADDR: ::std::os::raw::c_uint = 0x00000402;
|
||||||
|
pub const MSR_IA32_MC0_MISC: ::std::os::raw::c_uint = 0x00000403;
|
||||||
|
pub const MSR_PKG_C3_RESIDENCY: ::std::os::raw::c_uint = 0x000003f8;
|
||||||
|
pub const MSR_PKG_C6_RESIDENCY: ::std::os::raw::c_uint = 0x000003f9;
|
||||||
|
pub const MSR_ATOM_PKG_C6_RESIDENCY: ::std::os::raw::c_uint = 0x000003fa;
|
||||||
|
pub const MSR_PKG_C7_RESIDENCY: ::std::os::raw::c_uint = 0x000003fa;
|
||||||
|
pub const MSR_CORE_C3_RESIDENCY: ::std::os::raw::c_uint = 0x000003fc;
|
||||||
|
pub const MSR_CORE_C6_RESIDENCY: ::std::os::raw::c_uint = 0x000003fd;
|
||||||
|
pub const MSR_CORE_C7_RESIDENCY: ::std::os::raw::c_uint = 0x000003fe;
|
||||||
|
pub const MSR_KNL_CORE_C6_RESIDENCY: ::std::os::raw::c_uint = 0x000003ff;
|
||||||
|
pub const MSR_PKG_C2_RESIDENCY: ::std::os::raw::c_uint = 0x0000060d;
|
||||||
|
pub const MSR_PKG_C8_RESIDENCY: ::std::os::raw::c_uint = 0x00000630;
|
||||||
|
pub const MSR_PKG_C9_RESIDENCY: ::std::os::raw::c_uint = 0x00000631;
|
||||||
|
pub const MSR_PKG_C10_RESIDENCY: ::std::os::raw::c_uint = 0x00000632;
|
||||||
|
pub const MSR_PKGC3_IRTL: ::std::os::raw::c_uint = 0x0000060a;
|
||||||
|
pub const MSR_PKGC6_IRTL: ::std::os::raw::c_uint = 0x0000060b;
|
||||||
|
pub const MSR_PKGC7_IRTL: ::std::os::raw::c_uint = 0x0000060c;
|
||||||
|
pub const MSR_PKGC8_IRTL: ::std::os::raw::c_uint = 0x00000633;
|
||||||
|
pub const MSR_PKGC9_IRTL: ::std::os::raw::c_uint = 0x00000634;
|
||||||
|
pub const MSR_PKGC10_IRTL: ::std::os::raw::c_uint = 0x00000635;
|
||||||
|
pub const MSR_RAPL_POWER_UNIT: ::std::os::raw::c_uint = 0x00000606;
|
||||||
|
pub const MSR_PKG_POWER_LIMIT: ::std::os::raw::c_uint = 0x00000610;
|
||||||
|
pub const MSR_PKG_ENERGY_STATUS: ::std::os::raw::c_uint = 0x00000611;
|
||||||
|
pub const MSR_PKG_PERF_STATUS: ::std::os::raw::c_uint = 0x00000613;
|
||||||
|
pub const MSR_PKG_POWER_INFO: ::std::os::raw::c_uint = 0x00000614;
|
||||||
|
pub const MSR_DRAM_POWER_LIMIT: ::std::os::raw::c_uint = 0x00000618;
|
||||||
|
pub const MSR_DRAM_ENERGY_STATUS: ::std::os::raw::c_uint = 0x00000619;
|
||||||
|
pub const MSR_DRAM_PERF_STATUS: ::std::os::raw::c_uint = 0x0000061b;
|
||||||
|
pub const MSR_DRAM_POWER_INFO: ::std::os::raw::c_uint = 0x0000061c;
|
||||||
|
pub const MSR_PP0_POWER_LIMIT: ::std::os::raw::c_uint = 0x00000638;
|
||||||
|
pub const MSR_PP0_ENERGY_STATUS: ::std::os::raw::c_uint = 0x00000639;
|
||||||
|
pub const MSR_PP0_POLICY: ::std::os::raw::c_uint = 0x0000063a;
|
||||||
|
pub const MSR_PP0_PERF_STATUS: ::std::os::raw::c_uint = 0x0000063b;
|
||||||
|
pub const MSR_PP1_POWER_LIMIT: ::std::os::raw::c_uint = 0x00000640;
|
||||||
|
pub const MSR_PP1_ENERGY_STATUS: ::std::os::raw::c_uint = 0x00000641;
|
||||||
|
pub const MSR_PP1_POLICY: ::std::os::raw::c_uint = 0x00000642;
|
||||||
|
pub const MSR_CONFIG_TDP_NOMINAL: ::std::os::raw::c_uint = 0x00000648;
|
||||||
|
pub const MSR_CONFIG_TDP_LEVEL_1: ::std::os::raw::c_uint = 0x00000649;
|
||||||
|
pub const MSR_CONFIG_TDP_LEVEL_2: ::std::os::raw::c_uint = 0x0000064a;
|
||||||
|
pub const MSR_CONFIG_TDP_CONTROL: ::std::os::raw::c_uint = 0x0000064b;
|
||||||
|
pub const MSR_TURBO_ACTIVATION_RATIO: ::std::os::raw::c_uint = 0x0000064c;
|
||||||
|
pub const MSR_PLATFORM_ENERGY_STATUS: ::std::os::raw::c_uint = 0x0000064d;
|
||||||
|
pub const MSR_PKG_WEIGHTED_CORE_C0_RES: ::std::os::raw::c_uint = 0x00000658;
|
||||||
|
pub const MSR_PKG_ANY_CORE_C0_RES: ::std::os::raw::c_uint = 0x00000659;
|
||||||
|
pub const MSR_PKG_ANY_GFXE_C0_RES: ::std::os::raw::c_uint = 0x0000065a;
|
||||||
|
pub const MSR_PKG_BOTH_CORE_GFXE_C0_RES: ::std::os::raw::c_uint = 0x0000065b;
|
||||||
|
pub const MSR_CORE_C1_RES: ::std::os::raw::c_uint = 0x00000660;
|
||||||
|
pub const MSR_MODULE_C6_RES_MS: ::std::os::raw::c_uint = 0x00000664;
|
||||||
|
pub const MSR_CC6_DEMOTION_POLICY_CONFIG: ::std::os::raw::c_uint = 0x00000668;
|
||||||
|
pub const MSR_MC6_DEMOTION_POLICY_CONFIG: ::std::os::raw::c_uint = 0x00000669;
|
||||||
|
pub const MSR_ATOM_CORE_RATIOS: ::std::os::raw::c_uint = 0x0000066a;
|
||||||
|
pub const MSR_ATOM_CORE_VIDS: ::std::os::raw::c_uint = 0x0000066b;
|
||||||
|
pub const MSR_ATOM_CORE_TURBO_RATIOS: ::std::os::raw::c_uint = 0x0000066c;
|
||||||
|
pub const MSR_ATOM_CORE_TURBO_VIDS: ::std::os::raw::c_uint = 0x0000066d;
|
||||||
|
pub const MSR_CORE_PERF_LIMIT_REASONS: ::std::os::raw::c_uint = 0x00000690;
|
||||||
|
pub const MSR_GFX_PERF_LIMIT_REASONS: ::std::os::raw::c_uint = 0x000006b0;
|
||||||
|
pub const MSR_RING_PERF_LIMIT_REASONS: ::std::os::raw::c_uint = 0x000006b1;
|
||||||
|
pub const MSR_PPERF: ::std::os::raw::c_uint = 0x0000064e;
|
||||||
|
pub const MSR_PERF_LIMIT_REASONS: ::std::os::raw::c_uint = 0x0000064f;
|
||||||
|
pub const MSR_PM_ENABLE: ::std::os::raw::c_uint = 0x00000770;
|
||||||
|
pub const MSR_HWP_CAPABILITIES: ::std::os::raw::c_uint = 0x00000771;
|
||||||
|
pub const MSR_HWP_REQUEST_PKG: ::std::os::raw::c_uint = 0x00000772;
|
||||||
|
pub const MSR_HWP_INTERRUPT: ::std::os::raw::c_uint = 0x00000773;
|
||||||
|
pub const MSR_HWP_REQUEST: ::std::os::raw::c_uint = 0x00000774;
|
||||||
|
pub const MSR_HWP_STATUS: ::std::os::raw::c_uint = 0x00000777;
|
||||||
|
pub const HWP_BASE_BIT: ::std::os::raw::c_uint = 0x00000080;
|
||||||
|
pub const HWP_NOTIFICATIONS_BIT: ::std::os::raw::c_uint = 0x00000100;
|
||||||
|
pub const HWP_ACTIVITY_WINDOW_BIT: ::std::os::raw::c_uint = 0x00000200;
|
||||||
|
pub const HWP_ENERGY_PERF_PREFERENCE_BIT: ::std::os::raw::c_uint = 0x00000400;
|
||||||
|
pub const HWP_PACKAGE_LEVEL_REQUEST_BIT: ::std::os::raw::c_uint = 0x00000800;
|
||||||
|
pub const MSR_AMD64_MC0_MASK: ::std::os::raw::c_uint = 0xc0010044;
|
||||||
|
pub const MSR_IA32_MC0_CTL2: ::std::os::raw::c_uint = 0x00000280;
|
||||||
|
pub const MSR_P6_PERFCTR0: ::std::os::raw::c_uint = 0x000000c1;
|
||||||
|
pub const MSR_P6_PERFCTR1: ::std::os::raw::c_uint = 0x000000c2;
|
||||||
|
pub const MSR_P6_EVNTSEL0: ::std::os::raw::c_uint = 0x00000186;
|
||||||
|
pub const MSR_P6_EVNTSEL1: ::std::os::raw::c_uint = 0x00000187;
|
||||||
|
pub const MSR_KNC_PERFCTR0: ::std::os::raw::c_uint = 0x00000020;
|
||||||
|
pub const MSR_KNC_PERFCTR1: ::std::os::raw::c_uint = 0x00000021;
|
||||||
|
pub const MSR_KNC_EVNTSEL0: ::std::os::raw::c_uint = 0x00000028;
|
||||||
|
pub const MSR_KNC_EVNTSEL1: ::std::os::raw::c_uint = 0x00000029;
|
||||||
|
pub const MSR_IA32_PMC0: ::std::os::raw::c_uint = 0x000004c1;
|
||||||
|
pub const MSR_AMD64_PATCH_LEVEL: ::std::os::raw::c_uint = 0x0000008b;
|
||||||
|
pub const MSR_AMD64_TSC_RATIO: ::std::os::raw::c_uint = 0xc0000104;
|
||||||
|
pub const MSR_AMD64_NB_CFG: ::std::os::raw::c_uint = 0xc001001f;
|
||||||
|
pub const MSR_AMD64_PATCH_LOADER: ::std::os::raw::c_uint = 0xc0010020;
|
||||||
|
pub const MSR_AMD64_OSVW_ID_LENGTH: ::std::os::raw::c_uint = 0xc0010140;
|
||||||
|
pub const MSR_AMD64_OSVW_STATUS: ::std::os::raw::c_uint = 0xc0010141;
|
||||||
|
pub const MSR_AMD64_LS_CFG: ::std::os::raw::c_uint = 0xc0011020;
|
||||||
|
pub const MSR_AMD64_DC_CFG: ::std::os::raw::c_uint = 0xc0011022;
|
||||||
|
pub const MSR_AMD64_BU_CFG2: ::std::os::raw::c_uint = 0xc001102a;
|
||||||
|
pub const MSR_AMD64_IBSFETCHCTL: ::std::os::raw::c_uint = 0xc0011030;
|
||||||
|
pub const MSR_AMD64_IBSFETCHLINAD: ::std::os::raw::c_uint = 0xc0011031;
|
||||||
|
pub const MSR_AMD64_IBSFETCHPHYSAD: ::std::os::raw::c_uint = 0xc0011032;
|
||||||
|
pub const MSR_AMD64_IBSFETCH_REG_COUNT: ::std::os::raw::c_uint = 0x00000003;
|
||||||
|
pub const MSR_AMD64_IBSFETCH_REG_MASK: ::std::os::raw::c_uint = 0x00000007;
|
||||||
|
pub const MSR_AMD64_IBSOPCTL: ::std::os::raw::c_uint = 0xc0011033;
|
||||||
|
pub const MSR_AMD64_IBSOPRIP: ::std::os::raw::c_uint = 0xc0011034;
|
||||||
|
pub const MSR_AMD64_IBSOPDATA: ::std::os::raw::c_uint = 0xc0011035;
|
||||||
|
pub const MSR_AMD64_IBSOPDATA2: ::std::os::raw::c_uint = 0xc0011036;
|
||||||
|
pub const MSR_AMD64_IBSOPDATA3: ::std::os::raw::c_uint = 0xc0011037;
|
||||||
|
pub const MSR_AMD64_IBSDCLINAD: ::std::os::raw::c_uint = 0xc0011038;
|
||||||
|
pub const MSR_AMD64_IBSDCPHYSAD: ::std::os::raw::c_uint = 0xc0011039;
|
||||||
|
pub const MSR_AMD64_IBSOP_REG_COUNT: ::std::os::raw::c_uint = 0x00000007;
|
||||||
|
pub const MSR_AMD64_IBSOP_REG_MASK: ::std::os::raw::c_uint = 0x0000007f;
|
||||||
|
pub const MSR_AMD64_IBSCTL: ::std::os::raw::c_uint = 0xc001103a;
|
||||||
|
pub const MSR_AMD64_IBSBRTARGET: ::std::os::raw::c_uint = 0xc001103b;
|
||||||
|
pub const MSR_AMD64_IBSOPDATA4: ::std::os::raw::c_uint = 0xc001103d;
|
||||||
|
pub const MSR_AMD64_IBS_REG_COUNT_MAX: ::std::os::raw::c_uint = 0x00000008;
|
||||||
|
pub const MSR_F17H_IRPERF: ::std::os::raw::c_uint = 0xc00000e9;
|
||||||
|
pub const MSR_F16H_L2I_PERF_CTL: ::std::os::raw::c_uint = 0xc0010230;
|
||||||
|
pub const MSR_F16H_L2I_PERF_CTR: ::std::os::raw::c_uint = 0xc0010231;
|
||||||
|
pub const MSR_F16H_DR1_ADDR_MASK: ::std::os::raw::c_uint = 0xc0011019;
|
||||||
|
pub const MSR_F16H_DR2_ADDR_MASK: ::std::os::raw::c_uint = 0xc001101a;
|
||||||
|
pub const MSR_F16H_DR3_ADDR_MASK: ::std::os::raw::c_uint = 0xc001101b;
|
||||||
|
pub const MSR_F16H_DR0_ADDR_MASK: ::std::os::raw::c_uint = 0xc0011027;
|
||||||
|
pub const MSR_F15H_PERF_CTL: ::std::os::raw::c_uint = 0xc0010200;
|
||||||
|
pub const MSR_F15H_PERF_CTR: ::std::os::raw::c_uint = 0xc0010201;
|
||||||
|
pub const MSR_F15H_NB_PERF_CTL: ::std::os::raw::c_uint = 0xc0010240;
|
||||||
|
pub const MSR_F15H_NB_PERF_CTR: ::std::os::raw::c_uint = 0xc0010241;
|
||||||
|
pub const MSR_F15H_PTSC: ::std::os::raw::c_uint = 0xc0010280;
|
||||||
|
pub const MSR_F15H_IC_CFG: ::std::os::raw::c_uint = 0xc0011021;
|
||||||
|
pub const MSR_FAM10H_MMIO_CONF_BASE: ::std::os::raw::c_uint = 0xc0010058;
|
||||||
|
pub const FAM10H_MMIO_CONF_ENABLE: ::std::os::raw::c_uint = 0x00000001;
|
||||||
|
pub const FAM10H_MMIO_CONF_BUSRANGE_MASK: ::std::os::raw::c_uint = 0x0000000f;
|
||||||
|
pub const FAM10H_MMIO_CONF_BUSRANGE_SHIFT: ::std::os::raw::c_uint = 0x00000002;
|
||||||
|
pub const FAM10H_MMIO_CONF_BASE_MASK: ::std::os::raw::c_uint = 0x0fffffff;
|
||||||
|
pub const FAM10H_MMIO_CONF_BASE_SHIFT: ::std::os::raw::c_uint = 0x00000014;
|
||||||
|
pub const MSR_FAM10H_NODE_ID: ::std::os::raw::c_uint = 0xc001100c;
|
||||||
|
pub const MSR_K8_TOP_MEM1: ::std::os::raw::c_uint = 0xc001001a;
|
||||||
|
pub const MSR_K8_TOP_MEM2: ::std::os::raw::c_uint = 0xc001001d;
|
||||||
|
pub const MSR_K8_SYSCFG: ::std::os::raw::c_uint = 0xc0010010;
|
||||||
|
pub const MSR_K8_INT_PENDING_MSG: ::std::os::raw::c_uint = 0xc0010055;
|
||||||
|
pub const K8_INTP_C1E_ACTIVE_MASK: ::std::os::raw::c_uint = 0x18000000;
|
||||||
|
pub const MSR_K8_TSEG_ADDR: ::std::os::raw::c_uint = 0xc0010112;
|
||||||
|
pub const MSR_K8_TSEG_MASK: ::std::os::raw::c_uint = 0xc0010113;
|
||||||
|
pub const K8_MTRRFIXRANGE_DRAM_ENABLE: ::std::os::raw::c_uint = 0x00040000;
|
||||||
|
pub const K8_MTRRFIXRANGE_DRAM_MODIFY: ::std::os::raw::c_uint = 0x00080000;
|
||||||
|
pub const K8_MTRR_RDMEM_WRMEM_MASK: ::std::os::raw::c_uint = 0x18181818;
|
||||||
|
pub const MSR_K7_EVNTSEL0: ::std::os::raw::c_uint = 0xc0010000;
|
||||||
|
pub const MSR_K7_PERFCTR0: ::std::os::raw::c_uint = 0xc0010004;
|
||||||
|
pub const MSR_K7_EVNTSEL1: ::std::os::raw::c_uint = 0xc0010001;
|
||||||
|
pub const MSR_K7_PERFCTR1: ::std::os::raw::c_uint = 0xc0010005;
|
||||||
|
pub const MSR_K7_EVNTSEL2: ::std::os::raw::c_uint = 0xc0010002;
|
||||||
|
pub const MSR_K7_PERFCTR2: ::std::os::raw::c_uint = 0xc0010006;
|
||||||
|
pub const MSR_K7_EVNTSEL3: ::std::os::raw::c_uint = 0xc0010003;
|
||||||
|
pub const MSR_K7_PERFCTR3: ::std::os::raw::c_uint = 0xc0010007;
|
||||||
|
pub const MSR_K7_CLK_CTL: ::std::os::raw::c_uint = 0xc001001b;
|
||||||
|
pub const MSR_K7_HWCR: ::std::os::raw::c_uint = 0xc0010015;
|
||||||
|
pub const MSR_K7_FID_VID_CTL: ::std::os::raw::c_uint = 0xc0010041;
|
||||||
|
pub const MSR_K7_FID_VID_STATUS: ::std::os::raw::c_uint = 0xc0010042;
|
||||||
|
pub const MSR_K6_WHCR: ::std::os::raw::c_uint = 0xc0000082;
|
||||||
|
pub const MSR_K6_UWCCR: ::std::os::raw::c_uint = 0xc0000085;
|
||||||
|
pub const MSR_K6_EPMR: ::std::os::raw::c_uint = 0xc0000086;
|
||||||
|
pub const MSR_K6_PSOR: ::std::os::raw::c_uint = 0xc0000087;
|
||||||
|
pub const MSR_K6_PFIR: ::std::os::raw::c_uint = 0xc0000088;
|
||||||
|
pub const MSR_IDT_FCR1: ::std::os::raw::c_uint = 0x00000107;
|
||||||
|
pub const MSR_IDT_FCR2: ::std::os::raw::c_uint = 0x00000108;
|
||||||
|
pub const MSR_IDT_FCR3: ::std::os::raw::c_uint = 0x00000109;
|
||||||
|
pub const MSR_IDT_FCR4: ::std::os::raw::c_uint = 0x0000010a;
|
||||||
|
pub const MSR_IDT_MCR0: ::std::os::raw::c_uint = 0x00000110;
|
||||||
|
pub const MSR_IDT_MCR1: ::std::os::raw::c_uint = 0x00000111;
|
||||||
|
pub const MSR_IDT_MCR2: ::std::os::raw::c_uint = 0x00000112;
|
||||||
|
pub const MSR_IDT_MCR3: ::std::os::raw::c_uint = 0x00000113;
|
||||||
|
pub const MSR_IDT_MCR4: ::std::os::raw::c_uint = 0x00000114;
|
||||||
|
pub const MSR_IDT_MCR5: ::std::os::raw::c_uint = 0x00000115;
|
||||||
|
pub const MSR_IDT_MCR6: ::std::os::raw::c_uint = 0x00000116;
|
||||||
|
pub const MSR_IDT_MCR7: ::std::os::raw::c_uint = 0x00000117;
|
||||||
|
pub const MSR_IDT_MCR_CTRL: ::std::os::raw::c_uint = 0x00000120;
|
||||||
|
pub const MSR_VIA_FCR: ::std::os::raw::c_uint = 0x00001107;
|
||||||
|
pub const MSR_VIA_LONGHAUL: ::std::os::raw::c_uint = 0x0000110a;
|
||||||
|
pub const MSR_VIA_RNG: ::std::os::raw::c_uint = 0x0000110b;
|
||||||
|
pub const MSR_VIA_BCR2: ::std::os::raw::c_uint = 0x00001147;
|
||||||
|
pub const MSR_TMTA_LONGRUN_CTRL: ::std::os::raw::c_uint = 0x80868010;
|
||||||
|
pub const MSR_TMTA_LONGRUN_FLAGS: ::std::os::raw::c_uint = 0x80868011;
|
||||||
|
pub const MSR_TMTA_LRTI_READOUT: ::std::os::raw::c_uint = 0x80868018;
|
||||||
|
pub const MSR_TMTA_LRTI_VOLT_MHZ: ::std::os::raw::c_uint = 0x8086801a;
|
||||||
|
pub const MSR_IA32_P5_MC_ADDR: ::std::os::raw::c_uint = 0x00000000;
|
||||||
|
pub const MSR_IA32_P5_MC_TYPE: ::std::os::raw::c_uint = 0x00000001;
|
||||||
|
pub const MSR_IA32_TSC: ::std::os::raw::c_uint = 0x00000010;
|
||||||
|
pub const MSR_IA32_PLATFORM_ID: ::std::os::raw::c_uint = 0x00000017;
|
||||||
|
pub const MSR_IA32_EBL_CR_POWERON: ::std::os::raw::c_uint = 0x0000002a;
|
||||||
|
pub const MSR_EBC_FREQUENCY_ID: ::std::os::raw::c_uint = 0x0000002c;
|
||||||
|
pub const MSR_SMI_COUNT: ::std::os::raw::c_uint = 0x00000034;
|
||||||
|
pub const MSR_IA32_FEATURE_CONTROL: ::std::os::raw::c_uint = 0x0000003a;
|
||||||
|
pub const MSR_IA32_TSC_ADJUST: ::std::os::raw::c_uint = 0x0000003b;
|
||||||
|
pub const MSR_IA32_BNDCFGS: ::std::os::raw::c_uint = 0x00000d90;
|
||||||
|
pub const MSR_IA32_XSS: ::std::os::raw::c_uint = 0x00000da0;
|
||||||
|
pub const FEATURE_CONTROL_LOCKED: ::std::os::raw::c_uint = 0x00000001;
|
||||||
|
pub const FEATURE_CONTROL_VMXON_ENABLED_INSIDE_SMX: ::std::os::raw::c_uint = 0x00000002;
|
||||||
|
pub const FEATURE_CONTROL_VMXON_ENABLED_OUTSIDE_SMX: ::std::os::raw::c_uint = 0x00000004;
|
||||||
|
pub const FEATURE_CONTROL_LMCE: ::std::os::raw::c_uint = 0x00100000;
|
||||||
|
pub const MSR_IA32_APICBASE: ::std::os::raw::c_uint = 0x0000001b;
|
||||||
|
pub const MSR_IA32_APICBASE_BSP: ::std::os::raw::c_uint = 0x00000100;
|
||||||
|
pub const MSR_IA32_APICBASE_ENABLE: ::std::os::raw::c_uint = 0x00000800;
|
||||||
|
pub const MSR_IA32_APICBASE_BASE: ::std::os::raw::c_uint = 0xfffff000;
|
||||||
|
pub const MSR_IA32_TSCDEADLINE: ::std::os::raw::c_uint = 0x000006e0;
|
||||||
|
pub const MSR_IA32_UCODE_WRITE: ::std::os::raw::c_uint = 0x00000079;
|
||||||
|
pub const MSR_IA32_UCODE_REV: ::std::os::raw::c_uint = 0x0000008b;
|
||||||
|
pub const MSR_IA32_SMM_MONITOR_CTL: ::std::os::raw::c_uint = 0x0000009b;
|
||||||
|
pub const MSR_IA32_SMBASE: ::std::os::raw::c_uint = 0x0000009e;
|
||||||
|
pub const MSR_IA32_PERF_STATUS: ::std::os::raw::c_uint = 0x00000198;
|
||||||
|
pub const MSR_IA32_PERF_CTL: ::std::os::raw::c_uint = 0x00000199;
|
||||||
|
pub const INTEL_PERF_CTL_MASK: ::std::os::raw::c_uint = 0x0000ffff;
|
||||||
|
pub const MSR_AMD_PSTATE_DEF_BASE: ::std::os::raw::c_uint = 0xc0010064;
|
||||||
|
pub const MSR_AMD_PERF_STATUS: ::std::os::raw::c_uint = 0xc0010063;
|
||||||
|
pub const MSR_AMD_PERF_CTL: ::std::os::raw::c_uint = 0xc0010062;
|
||||||
|
pub const MSR_IA32_MPERF: ::std::os::raw::c_uint = 0x000000e7;
|
||||||
|
pub const MSR_IA32_APERF: ::std::os::raw::c_uint = 0x000000e8;
|
||||||
|
pub const MSR_IA32_THERM_CONTROL: ::std::os::raw::c_uint = 0x0000019a;
|
||||||
|
pub const MSR_IA32_THERM_INTERRUPT: ::std::os::raw::c_uint = 0x0000019b;
|
||||||
|
pub const THERM_INT_HIGH_ENABLE: ::std::os::raw::c_uint = 0x00000001;
|
||||||
|
pub const THERM_INT_LOW_ENABLE: ::std::os::raw::c_uint = 0x00000002;
|
||||||
|
pub const THERM_INT_PLN_ENABLE: ::std::os::raw::c_uint = 0x01000000;
|
||||||
|
pub const MSR_IA32_THERM_STATUS: ::std::os::raw::c_uint = 0x0000019c;
|
||||||
|
pub const THERM_STATUS_PROCHOT: ::std::os::raw::c_uint = 0x00000001;
|
||||||
|
pub const THERM_STATUS_POWER_LIMIT: ::std::os::raw::c_uint = 0x00000400;
|
||||||
|
pub const MSR_THERM2_CTL: ::std::os::raw::c_uint = 0x0000019d;
|
||||||
|
pub const MSR_THERM2_CTL_TM_SELECT: ::std::os::raw::c_uint = 0x00010000;
|
||||||
|
pub const MSR_IA32_MISC_ENABLE: ::std::os::raw::c_uint = 0x000001a0;
|
||||||
|
pub const MSR_IA32_TEMPERATURE_TARGET: ::std::os::raw::c_uint = 0x000001a2;
|
||||||
|
pub const MSR_MISC_FEATURE_CONTROL: ::std::os::raw::c_uint = 0x000001a4;
|
||||||
|
pub const MSR_MISC_PWR_MGMT: ::std::os::raw::c_uint = 0x000001aa;
|
||||||
|
pub const MSR_IA32_ENERGY_PERF_BIAS: ::std::os::raw::c_uint = 0x000001b0;
|
||||||
|
pub const ENERGY_PERF_BIAS_PERFORMANCE: ::std::os::raw::c_uint = 0x00000000;
|
||||||
|
pub const ENERGY_PERF_BIAS_NORMAL: ::std::os::raw::c_uint = 0x00000006;
|
||||||
|
pub const ENERGY_PERF_BIAS_POWERSAVE: ::std::os::raw::c_uint = 0x0000000f;
|
||||||
|
pub const MSR_IA32_PACKAGE_THERM_STATUS: ::std::os::raw::c_uint = 0x000001b1;
|
||||||
|
pub const PACKAGE_THERM_STATUS_PROCHOT: ::std::os::raw::c_uint = 0x00000001;
|
||||||
|
pub const PACKAGE_THERM_STATUS_POWER_LIMIT: ::std::os::raw::c_uint = 0x00000400;
|
||||||
|
pub const MSR_IA32_PACKAGE_THERM_INTERRUPT: ::std::os::raw::c_uint = 0x000001b2;
|
||||||
|
pub const PACKAGE_THERM_INT_HIGH_ENABLE: ::std::os::raw::c_uint = 0x00000001;
|
||||||
|
pub const PACKAGE_THERM_INT_LOW_ENABLE: ::std::os::raw::c_uint = 0x00000002;
|
||||||
|
pub const PACKAGE_THERM_INT_PLN_ENABLE: ::std::os::raw::c_uint = 0x01000000;
|
||||||
|
pub const THERM_INT_THRESHOLD0_ENABLE: ::std::os::raw::c_uint = 0x00008000;
|
||||||
|
pub const THERM_SHIFT_THRESHOLD0: ::std::os::raw::c_uint = 0x00000008;
|
||||||
|
pub const THERM_MASK_THRESHOLD0: ::std::os::raw::c_uint = 0x00007f00;
|
||||||
|
pub const THERM_INT_THRESHOLD1_ENABLE: ::std::os::raw::c_uint = 0x00800000;
|
||||||
|
pub const THERM_SHIFT_THRESHOLD1: ::std::os::raw::c_uint = 0x00000010;
|
||||||
|
pub const THERM_MASK_THRESHOLD1: ::std::os::raw::c_uint = 0x007f0000;
|
||||||
|
pub const THERM_STATUS_THRESHOLD0: ::std::os::raw::c_uint = 0x00000040;
|
||||||
|
pub const THERM_LOG_THRESHOLD0: ::std::os::raw::c_uint = 0x00000080;
|
||||||
|
pub const THERM_STATUS_THRESHOLD1: ::std::os::raw::c_uint = 0x00000100;
|
||||||
|
pub const THERM_LOG_THRESHOLD1: ::std::os::raw::c_uint = 0x00000200;
|
||||||
|
pub const MSR_IA32_MISC_ENABLE_FAST_STRING_BIT: ::std::os::raw::c_uint = 0x00000000;
|
||||||
|
pub const MSR_IA32_MISC_ENABLE_FAST_STRING: ::std::os::raw::c_uint = 0x00000001;
|
||||||
|
pub const MSR_IA32_MISC_ENABLE_TCC_BIT: ::std::os::raw::c_uint = 0x00000001;
|
||||||
|
pub const MSR_IA32_MISC_ENABLE_TCC: ::std::os::raw::c_uint = 0x00000002;
|
||||||
|
pub const MSR_IA32_MISC_ENABLE_EMON_BIT: ::std::os::raw::c_uint = 0x00000007;
|
||||||
|
pub const MSR_IA32_MISC_ENABLE_EMON: ::std::os::raw::c_uint = 0x00000080;
|
||||||
|
pub const MSR_IA32_MISC_ENABLE_BTS_UNAVAIL_BIT: ::std::os::raw::c_uint = 0x0000000b;
|
||||||
|
pub const MSR_IA32_MISC_ENABLE_BTS_UNAVAIL: ::std::os::raw::c_uint = 0x00000800;
|
||||||
|
pub const MSR_IA32_MISC_ENABLE_PEBS_UNAVAIL_BIT: ::std::os::raw::c_uint = 0x0000000c;
|
||||||
|
pub const MSR_IA32_MISC_ENABLE_PEBS_UNAVAIL: ::std::os::raw::c_uint = 0x00001000;
|
||||||
|
pub const MSR_IA32_MISC_ENABLE_ENHANCED_SPEEDSTEP_BIT: ::std::os::raw::c_uint = 0x00000010;
|
||||||
|
pub const MSR_IA32_MISC_ENABLE_ENHANCED_SPEEDSTEP: ::std::os::raw::c_uint = 0x00010000;
|
||||||
|
pub const MSR_IA32_MISC_ENABLE_MWAIT_BIT: ::std::os::raw::c_uint = 0x00000012;
|
||||||
|
pub const MSR_IA32_MISC_ENABLE_MWAIT: ::std::os::raw::c_uint = 0x00040000;
|
||||||
|
pub const MSR_IA32_MISC_ENABLE_LIMIT_CPUID_BIT: ::std::os::raw::c_uint = 0x00000016;
|
||||||
|
pub const MSR_IA32_MISC_ENABLE_LIMIT_CPUID: ::std::os::raw::c_uint = 0x00400000;
|
||||||
|
pub const MSR_IA32_MISC_ENABLE_XTPR_DISABLE_BIT: ::std::os::raw::c_uint = 0x00000017;
|
||||||
|
pub const MSR_IA32_MISC_ENABLE_XTPR_DISABLE: ::std::os::raw::c_uint = 0x00800000;
|
||||||
|
pub const MSR_IA32_MISC_ENABLE_XD_DISABLE_BIT: ::std::os::raw::c_uint = 0x00000022;
|
||||||
|
pub const MSR_IA32_MISC_ENABLE_XD_DISABLE: ::std::os::raw::c_ulonglong = 0x400000000;
|
||||||
|
pub const MSR_IA32_MISC_ENABLE_X87_COMPAT_BIT: ::std::os::raw::c_uint = 0x00000002;
|
||||||
|
pub const MSR_IA32_MISC_ENABLE_X87_COMPAT: ::std::os::raw::c_uint = 0x00000004;
|
||||||
|
pub const MSR_IA32_MISC_ENABLE_TM1_BIT: ::std::os::raw::c_uint = 0x00000003;
|
||||||
|
pub const MSR_IA32_MISC_ENABLE_TM1: ::std::os::raw::c_uint = 0x00000008;
|
||||||
|
pub const MSR_IA32_MISC_ENABLE_SPLIT_LOCK_DISABLE_BIT: ::std::os::raw::c_uint = 0x00000004;
|
||||||
|
pub const MSR_IA32_MISC_ENABLE_SPLIT_LOCK_DISABLE: ::std::os::raw::c_uint = 0x00000010;
|
||||||
|
pub const MSR_IA32_MISC_ENABLE_L3CACHE_DISABLE_BIT: ::std::os::raw::c_uint = 0x00000006;
|
||||||
|
pub const MSR_IA32_MISC_ENABLE_L3CACHE_DISABLE: ::std::os::raw::c_uint = 0x00000040;
|
||||||
|
pub const MSR_IA32_MISC_ENABLE_SUPPRESS_LOCK_BIT: ::std::os::raw::c_uint = 0x00000008;
|
||||||
|
pub const MSR_IA32_MISC_ENABLE_SUPPRESS_LOCK: ::std::os::raw::c_uint = 0x00000100;
|
||||||
|
pub const MSR_IA32_MISC_ENABLE_PREFETCH_DISABLE_BIT: ::std::os::raw::c_uint = 0x00000009;
|
||||||
|
pub const MSR_IA32_MISC_ENABLE_PREFETCH_DISABLE: ::std::os::raw::c_uint = 0x00000200;
|
||||||
|
pub const MSR_IA32_MISC_ENABLE_FERR_BIT: ::std::os::raw::c_uint = 0x0000000a;
|
||||||
|
pub const MSR_IA32_MISC_ENABLE_FERR: ::std::os::raw::c_uint = 0x00000400;
|
||||||
|
pub const MSR_IA32_MISC_ENABLE_FERR_MULTIPLEX_BIT: ::std::os::raw::c_uint = 0x0000000a;
|
||||||
|
pub const MSR_IA32_MISC_ENABLE_FERR_MULTIPLEX: ::std::os::raw::c_uint = 0x00000400;
|
||||||
|
pub const MSR_IA32_MISC_ENABLE_TM2_BIT: ::std::os::raw::c_uint = 0x0000000d;
|
||||||
|
pub const MSR_IA32_MISC_ENABLE_TM2: ::std::os::raw::c_uint = 0x00002000;
|
||||||
|
pub const MSR_IA32_MISC_ENABLE_ADJ_PREF_DISABLE_BIT: ::std::os::raw::c_uint = 0x00000013;
|
||||||
|
pub const MSR_IA32_MISC_ENABLE_ADJ_PREF_DISABLE: ::std::os::raw::c_uint = 0x00080000;
|
||||||
|
pub const MSR_IA32_MISC_ENABLE_SPEEDSTEP_LOCK_BIT: ::std::os::raw::c_uint = 0x00000014;
|
||||||
|
pub const MSR_IA32_MISC_ENABLE_SPEEDSTEP_LOCK: ::std::os::raw::c_uint = 0x00100000;
|
||||||
|
pub const MSR_IA32_MISC_ENABLE_L1D_CONTEXT_BIT: ::std::os::raw::c_uint = 0x00000018;
|
||||||
|
pub const MSR_IA32_MISC_ENABLE_L1D_CONTEXT: ::std::os::raw::c_uint = 0x01000000;
|
||||||
|
pub const MSR_IA32_MISC_ENABLE_DCU_PREF_DISABLE_BIT: ::std::os::raw::c_uint = 0x00000025;
|
||||||
|
pub const MSR_IA32_MISC_ENABLE_DCU_PREF_DISABLE: ::std::os::raw::c_ulonglong = 0x2000000000;
|
||||||
|
pub const MSR_IA32_MISC_ENABLE_TURBO_DISABLE_BIT: ::std::os::raw::c_uint = 0x00000026;
|
||||||
|
pub const MSR_IA32_MISC_ENABLE_TURBO_DISABLE: ::std::os::raw::c_ulonglong = 0x4000000000;
|
||||||
|
pub const MSR_IA32_MISC_ENABLE_IP_PREF_DISABLE_BIT: ::std::os::raw::c_uint = 0x00000027;
|
||||||
|
pub const MSR_IA32_MISC_ENABLE_IP_PREF_DISABLE: ::std::os::raw::c_ulonglong = 0x8000000000;
|
||||||
|
pub const MSR_MISC_FEATURE_ENABLES: ::std::os::raw::c_uint = 0x00000140;
|
||||||
|
pub const MSR_MISC_FEATURE_ENABLES_RING3MWAIT_BIT: ::std::os::raw::c_uint = 0x00000001;
|
||||||
|
pub const MSR_IA32_TSC_DEADLINE: ::std::os::raw::c_uint = 0x000006e0;
|
||||||
|
pub const MSR_IA32_MCG_EAX: ::std::os::raw::c_uint = 0x00000180;
|
||||||
|
pub const MSR_IA32_MCG_EBX: ::std::os::raw::c_uint = 0x00000181;
|
||||||
|
pub const MSR_IA32_MCG_ECX: ::std::os::raw::c_uint = 0x00000182;
|
||||||
|
pub const MSR_IA32_MCG_EDX: ::std::os::raw::c_uint = 0x00000183;
|
||||||
|
pub const MSR_IA32_MCG_ESI: ::std::os::raw::c_uint = 0x00000184;
|
||||||
|
pub const MSR_IA32_MCG_EDI: ::std::os::raw::c_uint = 0x00000185;
|
||||||
|
pub const MSR_IA32_MCG_EBP: ::std::os::raw::c_uint = 0x00000186;
|
||||||
|
pub const MSR_IA32_MCG_ESP: ::std::os::raw::c_uint = 0x00000187;
|
||||||
|
pub const MSR_IA32_MCG_EFLAGS: ::std::os::raw::c_uint = 0x00000188;
|
||||||
|
pub const MSR_IA32_MCG_EIP: ::std::os::raw::c_uint = 0x00000189;
|
||||||
|
pub const MSR_IA32_MCG_RESERVED: ::std::os::raw::c_uint = 0x0000018a;
|
||||||
|
pub const MSR_P4_BPU_PERFCTR0: ::std::os::raw::c_uint = 0x00000300;
|
||||||
|
pub const MSR_P4_BPU_PERFCTR1: ::std::os::raw::c_uint = 0x00000301;
|
||||||
|
pub const MSR_P4_BPU_PERFCTR2: ::std::os::raw::c_uint = 0x00000302;
|
||||||
|
pub const MSR_P4_BPU_PERFCTR3: ::std::os::raw::c_uint = 0x00000303;
|
||||||
|
pub const MSR_P4_MS_PERFCTR0: ::std::os::raw::c_uint = 0x00000304;
|
||||||
|
pub const MSR_P4_MS_PERFCTR1: ::std::os::raw::c_uint = 0x00000305;
|
||||||
|
pub const MSR_P4_MS_PERFCTR2: ::std::os::raw::c_uint = 0x00000306;
|
||||||
|
pub const MSR_P4_MS_PERFCTR3: ::std::os::raw::c_uint = 0x00000307;
|
||||||
|
pub const MSR_P4_FLAME_PERFCTR0: ::std::os::raw::c_uint = 0x00000308;
|
||||||
|
pub const MSR_P4_FLAME_PERFCTR1: ::std::os::raw::c_uint = 0x00000309;
|
||||||
|
pub const MSR_P4_FLAME_PERFCTR2: ::std::os::raw::c_uint = 0x0000030a;
|
||||||
|
pub const MSR_P4_FLAME_PERFCTR3: ::std::os::raw::c_uint = 0x0000030b;
|
||||||
|
pub const MSR_P4_IQ_PERFCTR0: ::std::os::raw::c_uint = 0x0000030c;
|
||||||
|
pub const MSR_P4_IQ_PERFCTR1: ::std::os::raw::c_uint = 0x0000030d;
|
||||||
|
pub const MSR_P4_IQ_PERFCTR2: ::std::os::raw::c_uint = 0x0000030e;
|
||||||
|
pub const MSR_P4_IQ_PERFCTR3: ::std::os::raw::c_uint = 0x0000030f;
|
||||||
|
pub const MSR_P4_IQ_PERFCTR4: ::std::os::raw::c_uint = 0x00000310;
|
||||||
|
pub const MSR_P4_IQ_PERFCTR5: ::std::os::raw::c_uint = 0x00000311;
|
||||||
|
pub const MSR_P4_BPU_CCCR0: ::std::os::raw::c_uint = 0x00000360;
|
||||||
|
pub const MSR_P4_BPU_CCCR1: ::std::os::raw::c_uint = 0x00000361;
|
||||||
|
pub const MSR_P4_BPU_CCCR2: ::std::os::raw::c_uint = 0x00000362;
|
||||||
|
pub const MSR_P4_BPU_CCCR3: ::std::os::raw::c_uint = 0x00000363;
|
||||||
|
pub const MSR_P4_MS_CCCR0: ::std::os::raw::c_uint = 0x00000364;
|
||||||
|
pub const MSR_P4_MS_CCCR1: ::std::os::raw::c_uint = 0x00000365;
|
||||||
|
pub const MSR_P4_MS_CCCR2: ::std::os::raw::c_uint = 0x00000366;
|
||||||
|
pub const MSR_P4_MS_CCCR3: ::std::os::raw::c_uint = 0x00000367;
|
||||||
|
pub const MSR_P4_FLAME_CCCR0: ::std::os::raw::c_uint = 0x00000368;
|
||||||
|
pub const MSR_P4_FLAME_CCCR1: ::std::os::raw::c_uint = 0x00000369;
|
||||||
|
pub const MSR_P4_FLAME_CCCR2: ::std::os::raw::c_uint = 0x0000036a;
|
||||||
|
pub const MSR_P4_FLAME_CCCR3: ::std::os::raw::c_uint = 0x0000036b;
|
||||||
|
pub const MSR_P4_IQ_CCCR0: ::std::os::raw::c_uint = 0x0000036c;
|
||||||
|
pub const MSR_P4_IQ_CCCR1: ::std::os::raw::c_uint = 0x0000036d;
|
||||||
|
pub const MSR_P4_IQ_CCCR2: ::std::os::raw::c_uint = 0x0000036e;
|
||||||
|
pub const MSR_P4_IQ_CCCR3: ::std::os::raw::c_uint = 0x0000036f;
|
||||||
|
pub const MSR_P4_IQ_CCCR4: ::std::os::raw::c_uint = 0x00000370;
|
||||||
|
pub const MSR_P4_IQ_CCCR5: ::std::os::raw::c_uint = 0x00000371;
|
||||||
|
pub const MSR_P4_ALF_ESCR0: ::std::os::raw::c_uint = 0x000003ca;
|
||||||
|
pub const MSR_P4_ALF_ESCR1: ::std::os::raw::c_uint = 0x000003cb;
|
||||||
|
pub const MSR_P4_BPU_ESCR0: ::std::os::raw::c_uint = 0x000003b2;
|
||||||
|
pub const MSR_P4_BPU_ESCR1: ::std::os::raw::c_uint = 0x000003b3;
|
||||||
|
pub const MSR_P4_BSU_ESCR0: ::std::os::raw::c_uint = 0x000003a0;
|
||||||
|
pub const MSR_P4_BSU_ESCR1: ::std::os::raw::c_uint = 0x000003a1;
|
||||||
|
pub const MSR_P4_CRU_ESCR0: ::std::os::raw::c_uint = 0x000003b8;
|
||||||
|
pub const MSR_P4_CRU_ESCR1: ::std::os::raw::c_uint = 0x000003b9;
|
||||||
|
pub const MSR_P4_CRU_ESCR2: ::std::os::raw::c_uint = 0x000003cc;
|
||||||
|
pub const MSR_P4_CRU_ESCR3: ::std::os::raw::c_uint = 0x000003cd;
|
||||||
|
pub const MSR_P4_CRU_ESCR4: ::std::os::raw::c_uint = 0x000003e0;
|
||||||
|
pub const MSR_P4_CRU_ESCR5: ::std::os::raw::c_uint = 0x000003e1;
|
||||||
|
pub const MSR_P4_DAC_ESCR0: ::std::os::raw::c_uint = 0x000003a8;
|
||||||
|
pub const MSR_P4_DAC_ESCR1: ::std::os::raw::c_uint = 0x000003a9;
|
||||||
|
pub const MSR_P4_FIRM_ESCR0: ::std::os::raw::c_uint = 0x000003a4;
|
||||||
|
pub const MSR_P4_FIRM_ESCR1: ::std::os::raw::c_uint = 0x000003a5;
|
||||||
|
pub const MSR_P4_FLAME_ESCR0: ::std::os::raw::c_uint = 0x000003a6;
|
||||||
|
pub const MSR_P4_FLAME_ESCR1: ::std::os::raw::c_uint = 0x000003a7;
|
||||||
|
pub const MSR_P4_FSB_ESCR0: ::std::os::raw::c_uint = 0x000003a2;
|
||||||
|
pub const MSR_P4_FSB_ESCR1: ::std::os::raw::c_uint = 0x000003a3;
|
||||||
|
pub const MSR_P4_IQ_ESCR0: ::std::os::raw::c_uint = 0x000003ba;
|
||||||
|
pub const MSR_P4_IQ_ESCR1: ::std::os::raw::c_uint = 0x000003bb;
|
||||||
|
pub const MSR_P4_IS_ESCR0: ::std::os::raw::c_uint = 0x000003b4;
|
||||||
|
pub const MSR_P4_IS_ESCR1: ::std::os::raw::c_uint = 0x000003b5;
|
||||||
|
pub const MSR_P4_ITLB_ESCR0: ::std::os::raw::c_uint = 0x000003b6;
|
||||||
|
pub const MSR_P4_ITLB_ESCR1: ::std::os::raw::c_uint = 0x000003b7;
|
||||||
|
pub const MSR_P4_IX_ESCR0: ::std::os::raw::c_uint = 0x000003c8;
|
||||||
|
pub const MSR_P4_IX_ESCR1: ::std::os::raw::c_uint = 0x000003c9;
|
||||||
|
pub const MSR_P4_MOB_ESCR0: ::std::os::raw::c_uint = 0x000003aa;
|
||||||
|
pub const MSR_P4_MOB_ESCR1: ::std::os::raw::c_uint = 0x000003ab;
|
||||||
|
pub const MSR_P4_MS_ESCR0: ::std::os::raw::c_uint = 0x000003c0;
|
||||||
|
pub const MSR_P4_MS_ESCR1: ::std::os::raw::c_uint = 0x000003c1;
|
||||||
|
pub const MSR_P4_PMH_ESCR0: ::std::os::raw::c_uint = 0x000003ac;
|
||||||
|
pub const MSR_P4_PMH_ESCR1: ::std::os::raw::c_uint = 0x000003ad;
|
||||||
|
pub const MSR_P4_RAT_ESCR0: ::std::os::raw::c_uint = 0x000003bc;
|
||||||
|
pub const MSR_P4_RAT_ESCR1: ::std::os::raw::c_uint = 0x000003bd;
|
||||||
|
pub const MSR_P4_SAAT_ESCR0: ::std::os::raw::c_uint = 0x000003ae;
|
||||||
|
pub const MSR_P4_SAAT_ESCR1: ::std::os::raw::c_uint = 0x000003af;
|
||||||
|
pub const MSR_P4_SSU_ESCR0: ::std::os::raw::c_uint = 0x000003be;
|
||||||
|
pub const MSR_P4_SSU_ESCR1: ::std::os::raw::c_uint = 0x000003bf;
|
||||||
|
pub const MSR_P4_TBPU_ESCR0: ::std::os::raw::c_uint = 0x000003c2;
|
||||||
|
pub const MSR_P4_TBPU_ESCR1: ::std::os::raw::c_uint = 0x000003c3;
|
||||||
|
pub const MSR_P4_TC_ESCR0: ::std::os::raw::c_uint = 0x000003c4;
|
||||||
|
pub const MSR_P4_TC_ESCR1: ::std::os::raw::c_uint = 0x000003c5;
|
||||||
|
pub const MSR_P4_U2L_ESCR0: ::std::os::raw::c_uint = 0x000003b0;
|
||||||
|
pub const MSR_P4_U2L_ESCR1: ::std::os::raw::c_uint = 0x000003b1;
|
||||||
|
pub const MSR_P4_PEBS_MATRIX_VERT: ::std::os::raw::c_uint = 0x000003f2;
|
||||||
|
pub const MSR_CORE_PERF_FIXED_CTR0: ::std::os::raw::c_uint = 0x00000309;
|
||||||
|
pub const MSR_CORE_PERF_FIXED_CTR1: ::std::os::raw::c_uint = 0x0000030a;
|
||||||
|
pub const MSR_CORE_PERF_FIXED_CTR2: ::std::os::raw::c_uint = 0x0000030b;
|
||||||
|
pub const MSR_CORE_PERF_FIXED_CTR_CTRL: ::std::os::raw::c_uint = 0x0000038d;
|
||||||
|
pub const MSR_CORE_PERF_GLOBAL_STATUS: ::std::os::raw::c_uint = 0x0000038e;
|
||||||
|
pub const MSR_CORE_PERF_GLOBAL_CTRL: ::std::os::raw::c_uint = 0x0000038f;
|
||||||
|
pub const MSR_CORE_PERF_GLOBAL_OVF_CTRL: ::std::os::raw::c_uint = 0x00000390;
|
||||||
|
pub const MSR_GEODE_BUSCONT_CONF0: ::std::os::raw::c_uint = 0x00001900;
|
||||||
|
pub const MSR_IA32_VMX_BASIC: ::std::os::raw::c_uint = 0x00000480;
|
||||||
|
pub const MSR_IA32_VMX_PINBASED_CTLS: ::std::os::raw::c_uint = 0x00000481;
|
||||||
|
pub const MSR_IA32_VMX_PROCBASED_CTLS: ::std::os::raw::c_uint = 0x00000482;
|
||||||
|
pub const MSR_IA32_VMX_EXIT_CTLS: ::std::os::raw::c_uint = 0x00000483;
|
||||||
|
pub const MSR_IA32_VMX_ENTRY_CTLS: ::std::os::raw::c_uint = 0x00000484;
|
||||||
|
pub const MSR_IA32_VMX_MISC: ::std::os::raw::c_uint = 0x00000485;
|
||||||
|
pub const MSR_IA32_VMX_CR0_FIXED0: ::std::os::raw::c_uint = 0x00000486;
|
||||||
|
pub const MSR_IA32_VMX_CR0_FIXED1: ::std::os::raw::c_uint = 0x00000487;
|
||||||
|
pub const MSR_IA32_VMX_CR4_FIXED0: ::std::os::raw::c_uint = 0x00000488;
|
||||||
|
pub const MSR_IA32_VMX_CR4_FIXED1: ::std::os::raw::c_uint = 0x00000489;
|
||||||
|
pub const MSR_IA32_VMX_VMCS_ENUM: ::std::os::raw::c_uint = 0x0000048a;
|
||||||
|
pub const MSR_IA32_VMX_PROCBASED_CTLS2: ::std::os::raw::c_uint = 0x0000048b;
|
||||||
|
pub const MSR_IA32_VMX_EPT_VPID_CAP: ::std::os::raw::c_uint = 0x0000048c;
|
||||||
|
pub const MSR_IA32_VMX_TRUE_PINBASED_CTLS: ::std::os::raw::c_uint = 0x0000048d;
|
||||||
|
pub const MSR_IA32_VMX_TRUE_PROCBASED_CTLS: ::std::os::raw::c_uint = 0x0000048e;
|
||||||
|
pub const MSR_IA32_VMX_TRUE_EXIT_CTLS: ::std::os::raw::c_uint = 0x0000048f;
|
||||||
|
pub const MSR_IA32_VMX_TRUE_ENTRY_CTLS: ::std::os::raw::c_uint = 0x00000490;
|
||||||
|
pub const MSR_IA32_VMX_VMFUNC: ::std::os::raw::c_uint = 0x00000491;
|
||||||
|
pub const VMX_BASIC_VMCS_SIZE_SHIFT: ::std::os::raw::c_uint = 0x00000020;
|
||||||
|
pub const VMX_BASIC_TRUE_CTLS: ::std::os::raw::c_ulonglong = 0x80000000000000;
|
||||||
|
pub const VMX_BASIC_64: ::std::os::raw::c_ulonglong = 0x1000000000000;
|
||||||
|
pub const VMX_BASIC_MEM_TYPE_SHIFT: ::std::os::raw::c_uint = 0x00000032;
|
||||||
|
pub const VMX_BASIC_MEM_TYPE_MASK: ::std::os::raw::c_ulonglong = 0x3c000000000000;
|
||||||
|
pub const VMX_BASIC_MEM_TYPE_WB: ::std::os::raw::c_uint = 0x00000006;
|
||||||
|
pub const VMX_BASIC_INOUT: ::std::os::raw::c_ulonglong = 0x40000000000000;
|
||||||
|
pub const MSR_IA32_VMX_MISC_VMWRITE_SHADOW_RO_FIELDS: ::std::os::raw::c_uint = 0x20000000;
|
||||||
|
pub const MSR_IA32_VMX_MISC_PREEMPTION_TIMER_SCALE: ::std::os::raw::c_uint = 0x0000001f;
|
||||||
|
pub const MSR_VM_CR: ::std::os::raw::c_uint = 0xc0010114;
|
||||||
|
pub const MSR_VM_IGNNE: ::std::os::raw::c_uint = 0xc0010115;
|
||||||
|
pub const MSR_VM_HSAVE_PA: ::std::os::raw::c_uint = 0xc0010117;
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
[package]
|
|
||||||
authors = ["The Chromium OS Authors", "The Cloud Hypervisor Authors"]
|
|
||||||
edition.workspace = true
|
|
||||||
name = "block"
|
|
||||||
rust-version.workspace = true
|
|
||||||
version = "0.1.0"
|
|
||||||
|
|
||||||
[features]
|
|
||||||
default = []
|
|
||||||
io_uring = ["dep:io-uring"]
|
|
||||||
test-utils = []
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
bitflags = { workspace = true }
|
|
||||||
byteorder = { workspace = true }
|
|
||||||
crc-any = "3.0.0"
|
|
||||||
flate2 = "1.1"
|
|
||||||
io-uring = { version = "0.7.12", optional = true }
|
|
||||||
libc = { workspace = true }
|
|
||||||
log = { workspace = true }
|
|
||||||
remain = "0.2.15"
|
|
||||||
serde = { workspace = true, features = ["derive"] }
|
|
||||||
smallvec = { workspace = true }
|
|
||||||
thiserror = { workspace = true }
|
|
||||||
uuid = { workspace = true, features = ["v4"] }
|
|
||||||
virtio-bindings = { workspace = true }
|
|
||||||
virtio-queue = { workspace = true }
|
|
||||||
vm-memory = { workspace = true, features = [
|
|
||||||
"backend-atomic",
|
|
||||||
"backend-bitmap",
|
|
||||||
"backend-mmap",
|
|
||||||
] }
|
|
||||||
vm-virtio = { path = "../vm-virtio" }
|
|
||||||
vmm-sys-util = { workspace = true }
|
|
||||||
zerocopy = { workspace = true, features = ["derive"] }
|
|
||||||
zstd = "0.13"
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
cfg-if = { workspace = true }
|
|
||||||
|
|
||||||
[lints]
|
|
||||||
workspace = true
|
|
||||||
@@ -1,269 +0,0 @@
|
|||||||
// Copyright 2026 The Cloud Hypervisor Authors. All rights reserved.
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
use std::alloc::{Layout, alloc_zeroed, dealloc};
|
|
||||||
use std::os::unix::fs::FileExt;
|
|
||||||
use std::{io, slice};
|
|
||||||
|
|
||||||
/// RAII aligned heap buffer for O_DIRECT I/O.
|
|
||||||
///
|
|
||||||
/// Handles the alignment math for offset and length, allocating a buffer
|
|
||||||
/// that satisfies O_DIRECT constraints. The caller's logical data lives
|
|
||||||
/// at `as_slice()`/`as_mut_slice()` (accounting for head padding when the
|
|
||||||
/// requested offset is not alignment-aligned). The full aligned region is
|
|
||||||
/// used internally for pread/pwrite via `FileExt`.
|
|
||||||
pub(crate) struct AlignedBuffer {
|
|
||||||
ptr: *mut u8,
|
|
||||||
layout: Layout,
|
|
||||||
head_pad: usize,
|
|
||||||
user_len: usize,
|
|
||||||
aligned_len: usize,
|
|
||||||
aligned_offset: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AlignedBuffer {
|
|
||||||
/// Create a new aligned buffer for I/O at `offset` of `len` bytes with
|
|
||||||
/// the given `alignment` requirement.
|
|
||||||
///
|
|
||||||
/// When offset and length are already aligned, `head_pad == 0` and the
|
|
||||||
/// full buffer equals the user's logical portion (no overhead).
|
|
||||||
pub fn new(offset: u64, len: usize, alignment: usize) -> io::Result<Self> {
|
|
||||||
if alignment == 0 || !alignment.is_power_of_two() {
|
|
||||||
return Err(io::Error::new(
|
|
||||||
io::ErrorKind::InvalidInput,
|
|
||||||
"alignment must be a non-zero power of two",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let mask = alignment as u64 - 1;
|
|
||||||
let aligned_offset = offset & !mask;
|
|
||||||
let head_pad = (offset - aligned_offset) as usize;
|
|
||||||
let min_len = head_pad
|
|
||||||
.checked_add(len)
|
|
||||||
.ok_or_else(|| io::Error::other("aligned buffer length overflow"))?;
|
|
||||||
let aligned_len = if min_len == 0 {
|
|
||||||
0
|
|
||||||
} else {
|
|
||||||
let remainder = min_len % alignment;
|
|
||||||
if remainder == 0 {
|
|
||||||
min_len
|
|
||||||
} else {
|
|
||||||
min_len
|
|
||||||
.checked_add(alignment - remainder)
|
|
||||||
.ok_or_else(|| io::Error::other("aligned buffer length overflow"))?
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// alloc_zeroed is UB on a zero-sized layout, so round the allocation
|
|
||||||
// up to one alignment unit for the zero-length case. The padding is
|
|
||||||
// never exposed: as_slice/full_slice report aligned_len/user_len (0).
|
|
||||||
let layout = Layout::from_size_align(aligned_len.max(alignment), alignment)
|
|
||||||
.map_err(|e| io::Error::other(format!("invalid aligned layout: {e}")))?;
|
|
||||||
// SAFETY: layout has non-zero size.
|
|
||||||
let ptr = unsafe { alloc_zeroed(layout) };
|
|
||||||
if ptr.is_null() {
|
|
||||||
return Err(io::Error::new(
|
|
||||||
io::ErrorKind::OutOfMemory,
|
|
||||||
"aligned allocation failed",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(AlignedBuffer {
|
|
||||||
ptr,
|
|
||||||
layout,
|
|
||||||
head_pad,
|
|
||||||
user_len: len,
|
|
||||||
aligned_len,
|
|
||||||
aligned_offset,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The caller's logical portion of the buffer (read-only).
|
|
||||||
pub fn as_slice(&self) -> &[u8] {
|
|
||||||
// SAFETY: ptr is valid for layout.size() bytes; head_pad + user_len <= layout.size().
|
|
||||||
unsafe { slice::from_raw_parts(self.ptr.add(self.head_pad), self.user_len) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The caller's logical portion of the buffer (mutable).
|
|
||||||
pub fn as_mut_slice(&mut self) -> &mut [u8] {
|
|
||||||
// SAFETY: ptr is valid for layout.size() bytes; head_pad + user_len <= layout.size().
|
|
||||||
unsafe { slice::from_raw_parts_mut(self.ptr.add(self.head_pad), self.user_len) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn full_slice(&self) -> &[u8] {
|
|
||||||
// SAFETY: ptr is valid for layout.size() bytes; aligned_len <= layout.size().
|
|
||||||
unsafe { slice::from_raw_parts(self.ptr, self.aligned_len) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn full_mut_slice(&mut self) -> &mut [u8] {
|
|
||||||
// SAFETY: ptr is valid for layout.size() bytes; aligned_len <= layout.size().
|
|
||||||
unsafe { slice::from_raw_parts_mut(self.ptr, self.aligned_len) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Read into the buffer from `f`, tolerating a short read at EOF.
|
|
||||||
///
|
|
||||||
/// Returns the number of caller-logical bytes now valid in `as_slice()`,
|
|
||||||
/// accounting for head padding and any short read.
|
|
||||||
pub fn read_from(&mut self, f: &impl FileExt) -> io::Result<usize> {
|
|
||||||
let mut total = 0usize;
|
|
||||||
while total < self.aligned_len {
|
|
||||||
let offset = self
|
|
||||||
.aligned_offset
|
|
||||||
.checked_add(total as u64)
|
|
||||||
.ok_or_else(|| io::Error::other("aligned buffer offset overflow"))?;
|
|
||||||
match f.read_at(&mut self.full_mut_slice()[total..], offset) {
|
|
||||||
Ok(0) => break,
|
|
||||||
Ok(n) => total += n,
|
|
||||||
Err(e) if e.kind() == io::ErrorKind::Interrupted => {}
|
|
||||||
Err(e) => return Err(e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(total.saturating_sub(self.head_pad).min(self.user_len))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Write the full aligned region from this buffer to `f`.
|
|
||||||
pub fn write_to(&self, f: &impl FileExt) -> io::Result<()> {
|
|
||||||
f.write_all_at(self.full_slice(), self.aligned_offset)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for AlignedBuffer {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
// SAFETY: ptr was allocated by alloc_zeroed with self.layout.
|
|
||||||
unsafe { dealloc(self.ptr, self.layout) };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SAFETY: The buffer is a plain heap allocation with no interior references.
|
|
||||||
unsafe impl Send for AlignedBuffer {}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use std::io::Write;
|
|
||||||
use std::os::unix::fs::FileExt;
|
|
||||||
|
|
||||||
use vmm_sys_util::tempfile::TempFile;
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
fn create_pattern_file(size: usize) -> TempFile {
|
|
||||||
let tf = TempFile::new().unwrap();
|
|
||||||
let pattern: Vec<u8> = (0..size).map(|i| (i % 251) as u8).collect();
|
|
||||||
tf.as_file().write_all(&pattern).unwrap();
|
|
||||||
tf.as_file().sync_all().unwrap();
|
|
||||||
tf
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_read_aligned() {
|
|
||||||
let size = 4096usize;
|
|
||||||
let tf = create_pattern_file(size);
|
|
||||||
let alignment = 512;
|
|
||||||
|
|
||||||
let mut abuf = AlignedBuffer::new(0, size, alignment).unwrap();
|
|
||||||
abuf.read_from(tf.as_file()).unwrap();
|
|
||||||
|
|
||||||
let expected: Vec<u8> = (0..size).map(|i| (i % 251) as u8).collect();
|
|
||||||
assert_eq!(abuf.as_slice(), &expected[..]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_zero_len_is_noop() {
|
|
||||||
let tf = create_pattern_file(512);
|
|
||||||
let mut abuf = AlignedBuffer::new(100, 0, 512).unwrap();
|
|
||||||
|
|
||||||
abuf.read_from(tf.as_file()).unwrap();
|
|
||||||
abuf.write_to(tf.as_file()).unwrap();
|
|
||||||
|
|
||||||
assert!(abuf.as_slice().is_empty());
|
|
||||||
assert!(abuf.as_mut_slice().is_empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_read_unaligned_offset() {
|
|
||||||
let file_size = 8192usize;
|
|
||||||
let tf = create_pattern_file(file_size);
|
|
||||||
let alignment = 512;
|
|
||||||
|
|
||||||
let offset = 100u64;
|
|
||||||
let len = 200usize;
|
|
||||||
let mut abuf = AlignedBuffer::new(offset, len, alignment).unwrap();
|
|
||||||
abuf.read_from(tf.as_file()).unwrap();
|
|
||||||
|
|
||||||
let expected: Vec<u8> = (offset as usize..offset as usize + len)
|
|
||||||
.map(|i| (i % 251) as u8)
|
|
||||||
.collect();
|
|
||||||
assert_eq!(abuf.as_slice(), &expected[..]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_write_aligned() {
|
|
||||||
let size = 4096usize;
|
|
||||||
let tf = create_pattern_file(size);
|
|
||||||
let alignment = 512;
|
|
||||||
|
|
||||||
let data: Vec<u8> = (0..size).map(|i| ((i + 1) % 251) as u8).collect();
|
|
||||||
let mut abuf = AlignedBuffer::new(0, size, alignment).unwrap();
|
|
||||||
abuf.as_mut_slice().copy_from_slice(&data);
|
|
||||||
abuf.write_to(tf.as_file()).unwrap();
|
|
||||||
|
|
||||||
let mut readback = vec![0u8; size];
|
|
||||||
tf.as_file().read_exact_at(&mut readback, 0).unwrap();
|
|
||||||
assert_eq!(readback, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_write_unaligned_offset_rmw() {
|
|
||||||
let file_size = 8192usize;
|
|
||||||
let tf = create_pattern_file(file_size);
|
|
||||||
let alignment = 512;
|
|
||||||
|
|
||||||
let offset = 100u64;
|
|
||||||
let len = 200usize;
|
|
||||||
let data: Vec<u8> = (0..len).map(|i| ((i + 1) % 239) as u8).collect();
|
|
||||||
|
|
||||||
let mut abuf = AlignedBuffer::new(offset, len, alignment).unwrap();
|
|
||||||
abuf.read_from(tf.as_file()).unwrap();
|
|
||||||
abuf.as_mut_slice().copy_from_slice(&data);
|
|
||||||
abuf.write_to(tf.as_file()).unwrap();
|
|
||||||
|
|
||||||
let mut whole = vec![0u8; file_size];
|
|
||||||
tf.as_file().read_exact_at(&mut whole, 0).unwrap();
|
|
||||||
|
|
||||||
let before: Vec<u8> = (0..offset as usize).map(|i| (i % 251) as u8).collect();
|
|
||||||
assert_eq!(&whole[..offset as usize], &before[..]);
|
|
||||||
assert_eq!(&whole[offset as usize..offset as usize + len], &data[..]);
|
|
||||||
let after_start = offset as usize + len;
|
|
||||||
let after: Vec<u8> = (after_start..file_size).map(|i| (i % 251) as u8).collect();
|
|
||||||
assert_eq!(&whole[after_start..], &after[..]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_4096_alignment() {
|
|
||||||
let file_size = 16384usize;
|
|
||||||
let tf = create_pattern_file(file_size);
|
|
||||||
let alignment = 4096;
|
|
||||||
|
|
||||||
let offset = 4096u64;
|
|
||||||
let len = 4096usize;
|
|
||||||
let data: Vec<u8> = (0..len).map(|i| ((i + 1) % 239) as u8).collect();
|
|
||||||
|
|
||||||
let mut abuf = AlignedBuffer::new(offset, len, alignment).unwrap();
|
|
||||||
abuf.read_from(tf.as_file()).unwrap();
|
|
||||||
abuf.as_mut_slice().copy_from_slice(&data);
|
|
||||||
abuf.write_to(tf.as_file()).unwrap();
|
|
||||||
|
|
||||||
let mut abuf = AlignedBuffer::new(offset, len, alignment).unwrap();
|
|
||||||
abuf.read_from(tf.as_file()).unwrap();
|
|
||||||
assert_eq!(abuf.as_slice(), &data[..]);
|
|
||||||
|
|
||||||
let mut whole = vec![0u8; file_size];
|
|
||||||
tf.as_file().read_exact_at(&mut whole, 0).unwrap();
|
|
||||||
let before: Vec<u8> = (0..offset as usize).map(|i| (i % 251) as u8).collect();
|
|
||||||
assert_eq!(&whole[..offset as usize], &before[..]);
|
|
||||||
let after_start = offset as usize + len;
|
|
||||||
let after: Vec<u8> = (after_start..file_size).map(|i| (i % 251) as u8).collect();
|
|
||||||
assert_eq!(&whole[after_start..], &after[..]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,573 +0,0 @@
|
|||||||
// Copyright 2026 The Cloud Hypervisor Authors. All rights reserved.
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
use std::fs::{File, Metadata};
|
|
||||||
use std::os::fd::{AsFd, BorrowedFd};
|
|
||||||
use std::os::unix::fs::FileExt;
|
|
||||||
use std::os::unix::io::{AsRawFd, RawFd};
|
|
||||||
use std::{io, slice};
|
|
||||||
|
|
||||||
use vmm_sys_util::file_traits::FileSync;
|
|
||||||
use vmm_sys_util::seek_hole::SeekHole;
|
|
||||||
use vmm_sys_util::write_zeroes::{PunchHole, WriteZeroesAt};
|
|
||||||
|
|
||||||
use crate::aligned_buffer::AlignedBuffer;
|
|
||||||
use crate::{SECTOR_SIZE, probe_direct_alignment};
|
|
||||||
|
|
||||||
/// True when `buf_ptr`/`len`/`offset` already satisfy `alignment`
|
|
||||||
/// (`alignment == 0` means no O_DIRECT, so everything is "aligned").
|
|
||||||
fn is_aligned(alignment: usize, buf_ptr: usize, len: usize, offset: u64) -> bool {
|
|
||||||
alignment == 0
|
|
||||||
|| (buf_ptr.is_multiple_of(alignment)
|
|
||||||
&& len.is_multiple_of(alignment)
|
|
||||||
&& offset.is_multiple_of(alignment as u64))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// True when `offset` and every iovec base/length satisfy `alignment`
|
|
||||||
/// (`alignment == 0` means no O_DIRECT, so everything is "aligned").
|
|
||||||
fn iovecs_are_aligned(alignment: usize, iovecs: &[libc::iovec], offset: u64) -> bool {
|
|
||||||
alignment == 0
|
|
||||||
|| (offset.is_multiple_of(alignment as u64)
|
|
||||||
&& iovecs.iter().all(|iov| {
|
|
||||||
(iov.iov_base as usize).is_multiple_of(alignment)
|
|
||||||
&& iov.iov_len.is_multiple_of(alignment)
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A `File` that transparently satisfies O_DIRECT alignment requirements.
|
|
||||||
///
|
|
||||||
/// `alignment == 0` means no O_DIRECT (all I/O passes straight through).
|
|
||||||
/// For unaligned requests under O_DIRECT, I/O is bounced through an
|
|
||||||
/// `AlignedBuffer` (read-modify-write for writes).
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct AlignedFile {
|
|
||||||
file: File,
|
|
||||||
alignment: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AlignedFile {
|
|
||||||
/// Wrap `file`, querying the O_DIRECT block alignment when `direct_io`.
|
|
||||||
pub fn new(file: File, direct_io: bool) -> Self {
|
|
||||||
let alignment = if direct_io {
|
|
||||||
probe_direct_alignment(file.as_raw_fd()).unwrap_or(SECTOR_SIZE) as usize
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
};
|
|
||||||
AlignedFile { file, alignment }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn alignment(&self) -> usize {
|
|
||||||
self.alignment
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn file(&self) -> &File {
|
|
||||||
&self.file
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn file_mut(&mut self) -> &mut File {
|
|
||||||
&mut self.file
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn try_clone(&self) -> io::Result<Self> {
|
|
||||||
Ok(AlignedFile {
|
|
||||||
file: self.file.try_clone()?,
|
|
||||||
alignment: self.alignment,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_len(&self, size: u64) -> io::Result<()> {
|
|
||||||
self.file.set_len(size)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn metadata(&self) -> io::Result<Metadata> {
|
|
||||||
self.file.metadata()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn sync_all(&self) -> io::Result<()> {
|
|
||||||
self.file.sync_all()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn sync_data(&self) -> io::Result<()> {
|
|
||||||
self.file.sync_data()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_direct(&self) -> bool {
|
|
||||||
self.alignment != 0
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_writable(&self) -> bool {
|
|
||||||
// SAFETY: fcntl with F_GETFL is safe and doesn't modify the file descriptor
|
|
||||||
let flags = unsafe { libc::fcntl(self.file.as_raw_fd(), libc::F_GETFL) };
|
|
||||||
if flags < 0 {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
let access_mode = flags & libc::O_ACCMODE;
|
|
||||||
access_mode == libc::O_WRONLY || access_mode == libc::O_RDWR
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Wrap `file` with an explicit alignment, bypassing the probe. Used by
|
|
||||||
/// tests to force the bounce/RMW path without a real O_DIRECT fd.
|
|
||||||
#[cfg(test)]
|
|
||||||
pub fn with_alignment(file: File, alignment: usize) -> Self {
|
|
||||||
AlignedFile { file, alignment }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Read `len` bytes at `offset` through an aligned bounce buffer.
|
|
||||||
pub(crate) fn read_unaligned(
|
|
||||||
&self,
|
|
||||||
offset: u64,
|
|
||||||
len: usize,
|
|
||||||
scatter: impl FnOnce(&[u8]) -> io::Result<()>,
|
|
||||||
) -> io::Result<usize> {
|
|
||||||
let mut abuf = AlignedBuffer::new(offset, len, self.alignment)?;
|
|
||||||
let n = abuf.read_from(&self.file)?;
|
|
||||||
scatter(&abuf.as_slice()[..n])?;
|
|
||||||
Ok(n)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Write `len` bytes at `offset` through an aligned bounce buffer.
|
|
||||||
pub(crate) fn write_unaligned(
|
|
||||||
&self,
|
|
||||||
offset: u64,
|
|
||||||
len: usize,
|
|
||||||
gather: impl FnOnce(&mut [u8]) -> io::Result<()>,
|
|
||||||
) -> io::Result<usize> {
|
|
||||||
let mut abuf = AlignedBuffer::new(offset, len, self.alignment)?;
|
|
||||||
abuf.read_from(&self.file)?; // RMW: preserve head/tail padding
|
|
||||||
gather(abuf.as_mut_slice())?;
|
|
||||||
abuf.write_to(&self.file)?;
|
|
||||||
Ok(len)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Read into the buffers described by `iovecs`, starting at `offset`.
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
/// Every iovec must describe valid, writable memory of `iov_len` bytes.
|
|
||||||
pub(crate) unsafe fn read_vectored_at(
|
|
||||||
&self,
|
|
||||||
iovecs: &[libc::iovec],
|
|
||||||
offset: u64,
|
|
||||||
) -> io::Result<usize> {
|
|
||||||
if iovecs.is_empty() {
|
|
||||||
return Ok(0);
|
|
||||||
}
|
|
||||||
if iovecs_are_aligned(self.alignment, iovecs, offset) {
|
|
||||||
// SAFETY: upheld by this fn contract.
|
|
||||||
let ret = unsafe {
|
|
||||||
libc::preadv(
|
|
||||||
self.file.as_raw_fd(),
|
|
||||||
iovecs.as_ptr(),
|
|
||||||
iovecs.len() as libc::c_int,
|
|
||||||
offset as libc::off_t,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
if ret < 0 {
|
|
||||||
return Err(io::Error::last_os_error());
|
|
||||||
}
|
|
||||||
return Ok(ret as usize);
|
|
||||||
}
|
|
||||||
let total_len = iovecs.iter().map(|iov| iov.iov_len).sum();
|
|
||||||
self.read_unaligned(offset, total_len, |mut data| {
|
|
||||||
for iov in iovecs {
|
|
||||||
if data.is_empty() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
let n = data.len().min(iov.iov_len);
|
|
||||||
// SAFETY: upheld by this fn contract.
|
|
||||||
let dst = unsafe { slice::from_raw_parts_mut(iov.iov_base as *mut u8, n) };
|
|
||||||
dst.copy_from_slice(&data[..n]);
|
|
||||||
data = &data[n..];
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Write the buffers described by `iovecs`, starting at `offset`.
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
/// Every iovec must describe valid, readable memory of `iov_len` bytes.
|
|
||||||
pub(crate) unsafe fn write_vectored_at(
|
|
||||||
&self,
|
|
||||||
iovecs: &[libc::iovec],
|
|
||||||
offset: u64,
|
|
||||||
) -> io::Result<usize> {
|
|
||||||
if iovecs.is_empty() {
|
|
||||||
return Ok(0);
|
|
||||||
}
|
|
||||||
if iovecs_are_aligned(self.alignment, iovecs, offset) {
|
|
||||||
// SAFETY: upheld by this fn contract.
|
|
||||||
let ret = unsafe {
|
|
||||||
libc::pwritev(
|
|
||||||
self.file.as_raw_fd(),
|
|
||||||
iovecs.as_ptr(),
|
|
||||||
iovecs.len() as libc::c_int,
|
|
||||||
offset as libc::off_t,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
if ret < 0 {
|
|
||||||
return Err(io::Error::last_os_error());
|
|
||||||
}
|
|
||||||
return Ok(ret as usize);
|
|
||||||
}
|
|
||||||
let total_len = iovecs.iter().map(|iov| iov.iov_len).sum();
|
|
||||||
self.write_unaligned(offset, total_len, |mut dst| {
|
|
||||||
for iov in iovecs {
|
|
||||||
if dst.is_empty() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
let n = dst.len().min(iov.iov_len);
|
|
||||||
// SAFETY: upheld by this fn contract.
|
|
||||||
let src = unsafe { slice::from_raw_parts(iov.iov_base as *const u8, n) };
|
|
||||||
dst[..n].copy_from_slice(src);
|
|
||||||
dst = &mut dst[n..];
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FileExt for AlignedFile {
|
|
||||||
fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result<usize> {
|
|
||||||
if buf.is_empty() {
|
|
||||||
return Ok(0);
|
|
||||||
}
|
|
||||||
if is_aligned(self.alignment, buf.as_ptr() as usize, buf.len(), offset) {
|
|
||||||
return self.file.read_at(buf, offset);
|
|
||||||
}
|
|
||||||
self.read_unaligned(offset, buf.len(), |data| {
|
|
||||||
buf[..data.len()].copy_from_slice(data);
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_at(&self, buf: &[u8], offset: u64) -> io::Result<usize> {
|
|
||||||
if buf.is_empty() {
|
|
||||||
return Ok(0);
|
|
||||||
}
|
|
||||||
if is_aligned(self.alignment, buf.as_ptr() as usize, buf.len(), offset) {
|
|
||||||
return self.file.write_at(buf, offset);
|
|
||||||
}
|
|
||||||
self.write_unaligned(offset, buf.len(), |dst| {
|
|
||||||
dst.copy_from_slice(buf);
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WriteZeroesAt for AlignedFile {
|
|
||||||
fn write_zeroes_at(&mut self, offset: u64, length: usize) -> io::Result<usize> {
|
|
||||||
self.file.write_zeroes_at(offset, length)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PunchHole for AlignedFile {
|
|
||||||
fn punch_hole(&mut self, offset: u64, length: u64) -> io::Result<()> {
|
|
||||||
self.file.punch_hole(offset, length)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FileSync for AlignedFile {
|
|
||||||
fn fsync(&mut self) -> io::Result<()> {
|
|
||||||
self.file.fsync()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SeekHole for AlignedFile {
|
|
||||||
fn seek_hole(&mut self, offset: u64) -> io::Result<Option<u64>> {
|
|
||||||
self.file.seek_hole(offset)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn seek_data(&mut self, offset: u64) -> io::Result<Option<u64>> {
|
|
||||||
self.file.seek_data(offset)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Clone for AlignedFile {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
self.try_clone().expect("AlignedFile cloning failed")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsRawFd for AlignedFile {
|
|
||||||
fn as_raw_fd(&self) -> RawFd {
|
|
||||||
self.file.as_raw_fd()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsFd for AlignedFile {
|
|
||||||
fn as_fd(&self) -> BorrowedFd<'_> {
|
|
||||||
self.file.as_fd()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use std::io::Write;
|
|
||||||
use std::os::unix::fs::FileExt;
|
|
||||||
|
|
||||||
use vmm_sys_util::tempfile::TempFile;
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
fn pattern_file(size: usize) -> TempFile {
|
|
||||||
let tf = TempFile::new().unwrap();
|
|
||||||
let p: Vec<u8> = (0..size).map(|i| (i % 251) as u8).collect();
|
|
||||||
tf.as_file().write_all(&p).unwrap();
|
|
||||||
tf.as_file().sync_all().unwrap();
|
|
||||||
tf
|
|
||||||
}
|
|
||||||
|
|
||||||
fn forced(file: File, alignment: usize) -> AlignedFile {
|
|
||||||
AlignedFile { file, alignment }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn new_probes_alignment_and_accessors() {
|
|
||||||
let tf = pattern_file(8192);
|
|
||||||
// Not O_DIRECT, so new() falls back to SECTOR_SIZE (512).
|
|
||||||
let mut af = AlignedFile::new(tf.as_file().try_clone().unwrap(), true);
|
|
||||||
assert_eq!(af.alignment(), 512);
|
|
||||||
let _ = af.file();
|
|
||||||
let _ = af.file_mut();
|
|
||||||
let _ = af.try_clone().unwrap();
|
|
||||||
|
|
||||||
let plain = AlignedFile::new(tf.as_file().try_clone().unwrap(), false);
|
|
||||||
assert_eq!(plain.alignment(), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn read_unaligned_offset_matches_contents() {
|
|
||||||
let tf = pattern_file(8192);
|
|
||||||
let af = forced(tf.as_file().try_clone().unwrap(), 512);
|
|
||||||
let mut buf = vec![0u8; 200];
|
|
||||||
assert_eq!(af.read_at(&mut buf, 100).unwrap(), 200);
|
|
||||||
let want: Vec<u8> = (100..300).map(|i| (i % 251) as u8).collect();
|
|
||||||
assert_eq!(buf, want);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn read_unaligned_short_at_eof() {
|
|
||||||
let tf = pattern_file(100);
|
|
||||||
let af = forced(tf.as_file().try_clone().unwrap(), 512);
|
|
||||||
let mut buf = vec![0u8; 200];
|
|
||||||
assert_eq!(af.read_at(&mut buf, 10).unwrap(), 90);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn write_unaligned_offset_is_rmw() {
|
|
||||||
let tf = pattern_file(8192);
|
|
||||||
let af = forced(tf.as_file().try_clone().unwrap(), 512);
|
|
||||||
let data: Vec<u8> = (0..200).map(|i| ((i + 1) % 239) as u8).collect();
|
|
||||||
assert_eq!(af.write_at(&data, 100).unwrap(), 200);
|
|
||||||
|
|
||||||
let mut whole = vec![0u8; 8192];
|
|
||||||
tf.as_file().read_exact_at(&mut whole, 0).unwrap();
|
|
||||||
let before: Vec<u8> = (0..100).map(|i| (i % 251) as u8).collect();
|
|
||||||
assert_eq!(&whole[..100], &before[..]);
|
|
||||||
assert_eq!(&whole[100..300], &data[..]);
|
|
||||||
let after: Vec<u8> = (300..8192).map(|i| (i % 251) as u8).collect();
|
|
||||||
assert_eq!(&whole[300..], &after[..]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn aligned_passthrough_roundtrip() {
|
|
||||||
let tf = pattern_file(4096);
|
|
||||||
let af = forced(tf.as_file().try_clone().unwrap(), 512);
|
|
||||||
let mut buf = vec![0u8; 512];
|
|
||||||
assert_eq!(af.read_at(&mut buf, 512).unwrap(), 512);
|
|
||||||
let want: Vec<u8> = (512..1024).map(|i| (i % 251) as u8).collect();
|
|
||||||
assert_eq!(buf, want);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn no_alignment_is_plain_passthrough() {
|
|
||||||
let tf = pattern_file(100);
|
|
||||||
let af = forced(tf.as_file().try_clone().unwrap(), 0);
|
|
||||||
let mut buf = vec![0u8; 50];
|
|
||||||
assert_eq!(af.read_at(&mut buf, 10).unwrap(), 50);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_unaligned_read_beyond_eof_returns_zero() {
|
|
||||||
let tf = pattern_file(100);
|
|
||||||
let af = forced(tf.as_file().try_clone().unwrap(), 512);
|
|
||||||
let mut buf = vec![0u8; 16];
|
|
||||||
assert_eq!(af.read_at(&mut buf, 200).unwrap(), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_unaligned_write_extends_at_eof() {
|
|
||||||
let file_size = 100usize;
|
|
||||||
let tf = pattern_file(file_size);
|
|
||||||
let af = forced(tf.as_file().try_clone().unwrap(), 512);
|
|
||||||
let data = b"xyz";
|
|
||||||
assert_eq!(af.write_at(data, file_size as u64).unwrap(), data.len());
|
|
||||||
|
|
||||||
let mut readback = vec![0u8; file_size + data.len()];
|
|
||||||
tf.as_file().read_exact_at(&mut readback, 0).unwrap();
|
|
||||||
let expected_prefix: Vec<u8> = (0..file_size).map(|i| (i % 251) as u8).collect();
|
|
||||||
assert_eq!(&readback[..file_size], &expected_prefix[..]);
|
|
||||||
assert_eq!(&readback[file_size..], data);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_empty_unaligned_io_is_noop() {
|
|
||||||
let tf = pattern_file(100);
|
|
||||||
let af = forced(tf.as_file().try_clone().unwrap(), 512);
|
|
||||||
let mut read_buf = [];
|
|
||||||
assert_eq!(af.read_at(&mut read_buf, 1).unwrap(), 0);
|
|
||||||
assert_eq!(af.write_at(&[], 1).unwrap(), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn read_unaligned_scatters_in_a_single_copy() {
|
|
||||||
let file = pattern_file(8192);
|
|
||||||
let aligned_file = forced(file.as_file().try_clone().unwrap(), 512);
|
|
||||||
let mut out = vec![0u8; 200];
|
|
||||||
let n = aligned_file
|
|
||||||
.read_unaligned(100, 200, |data| {
|
|
||||||
out.copy_from_slice(data);
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(n, 200);
|
|
||||||
let want: Vec<u8> = (100..300).map(|i| (i % 251) as u8).collect();
|
|
||||||
assert_eq!(out, want);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn read_unaligned_closure_short_at_eof() {
|
|
||||||
let file = pattern_file(100);
|
|
||||||
let aligned_file = forced(file.as_file().try_clone().unwrap(), 512);
|
|
||||||
let mut seen = 0usize;
|
|
||||||
let n = aligned_file
|
|
||||||
.read_unaligned(10, 200, |data| {
|
|
||||||
seen = data.len();
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(n, 90);
|
|
||||||
assert_eq!(seen, 90);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn write_unaligned_gather_is_rmw() {
|
|
||||||
let file = pattern_file(8192);
|
|
||||||
let aligned_file = forced(file.as_file().try_clone().unwrap(), 512);
|
|
||||||
let data: Vec<u8> = (0..200).map(|i| ((i + 1) % 239) as u8).collect();
|
|
||||||
let n = aligned_file
|
|
||||||
.write_unaligned(100, 200, |buf| {
|
|
||||||
buf.copy_from_slice(&data);
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(n, 200);
|
|
||||||
|
|
||||||
let mut whole = vec![0u8; 8192];
|
|
||||||
file.as_file().read_exact_at(&mut whole, 0).unwrap();
|
|
||||||
let before: Vec<u8> = (0..100).map(|i| (i % 251) as u8).collect();
|
|
||||||
assert_eq!(&whole[..100], &before[..]);
|
|
||||||
assert_eq!(&whole[100..300], &data[..]);
|
|
||||||
let after: Vec<u8> = (300..8192).map(|i| (i % 251) as u8).collect();
|
|
||||||
assert_eq!(&whole[300..], &after[..]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn read_unaligned_propagates_closure_error() {
|
|
||||||
let file = pattern_file(8192);
|
|
||||||
let aligned_file = forced(file.as_file().try_clone().unwrap(), 512);
|
|
||||||
let err = aligned_file
|
|
||||||
.read_unaligned(100, 200, |_| {
|
|
||||||
Err(io::Error::new(io::ErrorKind::InvalidInput, "boom"))
|
|
||||||
})
|
|
||||||
.unwrap_err();
|
|
||||||
assert_eq!(err.kind(), io::ErrorKind::InvalidInput);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn write_unaligned_propagates_closure_error() {
|
|
||||||
let file = pattern_file(8192);
|
|
||||||
let aligned_file = forced(file.as_file().try_clone().unwrap(), 512);
|
|
||||||
let err = aligned_file
|
|
||||||
.write_unaligned(100, 200, |_| {
|
|
||||||
Err(io::Error::new(io::ErrorKind::InvalidInput, "boom"))
|
|
||||||
})
|
|
||||||
.unwrap_err();
|
|
||||||
assert_eq!(err.kind(), io::ErrorKind::InvalidInput);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Build an iovec over `buf`, which must outlive the iovec.
|
|
||||||
fn iovec_of(buf: &mut [u8]) -> libc::iovec {
|
|
||||||
libc::iovec {
|
|
||||||
iov_base: buf.as_mut_ptr() as *mut libc::c_void,
|
|
||||||
iov_len: buf.len(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn vectored_empty_is_noop() {
|
|
||||||
let tf = pattern_file(100);
|
|
||||||
let af = forced(tf.as_file().try_clone().unwrap(), 512);
|
|
||||||
// SAFETY: empty iovec slices point to no memory.
|
|
||||||
assert_eq!(unsafe { af.read_vectored_at(&[], 0) }.unwrap(), 0);
|
|
||||||
// SAFETY: empty iovec slices point to no memory.
|
|
||||||
assert_eq!(unsafe { af.write_vectored_at(&[], 0) }.unwrap(), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn vectored_fast_path_roundtrip() {
|
|
||||||
let tf = pattern_file(4096);
|
|
||||||
// alignment 0 sends any iovecs through the single preadv/pwritev path.
|
|
||||||
let af = forced(tf.as_file().try_clone().unwrap(), 0);
|
|
||||||
|
|
||||||
let mut w0: Vec<u8> = (0..30).map(|i| ((i + 7) % 239) as u8).collect();
|
|
||||||
let mut w1: Vec<u8> = (0..70).map(|i| ((i + 37) % 239) as u8).collect();
|
|
||||||
let wiovecs = [iovec_of(&mut w0), iovec_of(&mut w1)];
|
|
||||||
// SAFETY: the iovecs describe the live w0/w1 buffers.
|
|
||||||
assert_eq!(unsafe { af.write_vectored_at(&wiovecs, 10) }.unwrap(), 100);
|
|
||||||
let data = [w0.as_slice(), &w1].concat();
|
|
||||||
|
|
||||||
let mut plain = vec![0u8; 100];
|
|
||||||
tf.as_file().read_exact_at(&mut plain, 10).unwrap();
|
|
||||||
assert_eq!(plain, data);
|
|
||||||
|
|
||||||
let mut r0 = vec![0u8; 30];
|
|
||||||
let mut r1 = vec![0u8; 70];
|
|
||||||
let riovecs = [iovec_of(&mut r0), iovec_of(&mut r1)];
|
|
||||||
// SAFETY: the iovecs describe the live r0/r1 buffers.
|
|
||||||
assert_eq!(unsafe { af.read_vectored_at(&riovecs, 10) }.unwrap(), 100);
|
|
||||||
assert_eq!([r0.as_slice(), &r1].concat(), data);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn vectored_unaligned_scatter_gather_roundtrip() {
|
|
||||||
let tf = pattern_file(8192);
|
|
||||||
let af = forced(tf.as_file().try_clone().unwrap(), 512);
|
|
||||||
|
|
||||||
// Gather three iovecs into an unaligned read-modify-write.
|
|
||||||
let mut w0: Vec<u8> = (0..50).map(|i| ((i + 1) % 239) as u8).collect();
|
|
||||||
let mut w1: Vec<u8> = (0..100).map(|i| ((i + 51) % 239) as u8).collect();
|
|
||||||
let mut w2: Vec<u8> = (0..50).map(|i| ((i + 151) % 239) as u8).collect();
|
|
||||||
let wiovecs = [iovec_of(&mut w0), iovec_of(&mut w1), iovec_of(&mut w2)];
|
|
||||||
// SAFETY: the iovecs describe the live w0/w1/w2 buffers.
|
|
||||||
assert_eq!(unsafe { af.write_vectored_at(&wiovecs, 100) }.unwrap(), 200);
|
|
||||||
let data = [w0.as_slice(), &w1, &w2].concat();
|
|
||||||
|
|
||||||
// Independently confirm the region and the untouched neighbors.
|
|
||||||
let mut expected: Vec<u8> = (0..8192).map(|i| (i % 251) as u8).collect();
|
|
||||||
expected[100..300].copy_from_slice(&data);
|
|
||||||
let mut whole = vec![0u8; 8192];
|
|
||||||
tf.as_file().read_exact_at(&mut whole, 0).unwrap();
|
|
||||||
assert_eq!(whole, expected);
|
|
||||||
|
|
||||||
// Scatter the same region back across three iovecs.
|
|
||||||
let mut r0 = vec![0u8; 50];
|
|
||||||
let mut r1 = vec![0u8; 100];
|
|
||||||
let mut r2 = vec![0u8; 50];
|
|
||||||
let riovecs = [iovec_of(&mut r0), iovec_of(&mut r1), iovec_of(&mut r2)];
|
|
||||||
// SAFETY: the iovecs describe the live r0/r1/r2 buffers.
|
|
||||||
assert_eq!(unsafe { af.read_vectored_at(&riovecs, 100) }.unwrap(), 200);
|
|
||||||
assert_eq!([r0.as_slice(), &r1, &r2].concat(), data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,181 +0,0 @@
|
|||||||
// Copyright 2026 The Cloud Hypervisor Authors. All rights reserved.
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
//! Composable disk capability traits for the block crate.
|
|
||||||
//!
|
|
||||||
//! Small traits define individual capabilities:
|
|
||||||
//!
|
|
||||||
//! - [`DiskSize`] - reported capacity (logical size)
|
|
||||||
//! - [`PhysicalSize`] - host allocation size
|
|
||||||
//! - [`DiskFd`] - backing file descriptor access
|
|
||||||
//! - [`Geometry`] - sector/cluster geometry (default 512B)
|
|
||||||
//! - [`SparseCapable`] - sparse and zero flag support
|
|
||||||
//! - [`Resizable`] - online resize
|
|
||||||
//! - [`MetadataSync`] - flush of format metadata cached in memory
|
|
||||||
//!
|
|
||||||
//! [`DiskFile`] is a supertrait that bundles the universal capabilities
|
|
||||||
//! (`DiskSize` + `Geometry`). [`FullDiskFile`] adds all optional
|
|
||||||
//! capabilities. [`AsyncDiskFile`] extends `DiskFile` with async I/O
|
|
||||||
//! construction for virtio queue workers. [`AsyncFullDiskFile`]
|
|
||||||
//! combines both axes.
|
|
||||||
//!
|
|
||||||
//! ```text
|
|
||||||
//! DiskFile: DiskSize + Geometry + Sync
|
|
||||||
//! / \
|
|
||||||
//! FullDiskFile: AsyncDiskFile:
|
|
||||||
//! DiskFile + PhysicalSize + DiskFile + Unpin
|
|
||||||
//! DiskFd + SparseCapable + try_clone, create_async_io
|
|
||||||
//! Resizable + MetadataSync
|
|
||||||
//! \ /
|
|
||||||
//! AsyncFullDiskFile: FullDiskFile + AsyncDiskFile
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! Readonly accessors take `&self`. Only [`Resizable::resize`] requires
|
|
||||||
//! `&mut self`. Errors are returned as [`BlockResult`].
|
|
||||||
|
|
||||||
use std::fmt::Debug;
|
|
||||||
|
|
||||||
use crate::async_io::{AsyncIo, BorrowedDiskFd};
|
|
||||||
use crate::{BlockResult, DiskTopology};
|
|
||||||
|
|
||||||
/// Reported capacity of a disk image.
|
|
||||||
pub trait DiskSize: Send + Debug {
|
|
||||||
/// Virtual size of the disk image in bytes (reported capacity).
|
|
||||||
fn logical_size(&self) -> BlockResult<u64>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Host allocation size of a file-backed disk image.
|
|
||||||
pub trait PhysicalSize: Send + Debug {
|
|
||||||
/// Actual bytes occupied on the host filesystem.
|
|
||||||
fn physical_size(&self) -> BlockResult<u64>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Backing file descriptor access for disk images backed by a file.
|
|
||||||
pub trait DiskFd: Send + Debug {
|
|
||||||
/// Borrows the underlying file descriptor.
|
|
||||||
fn fd(&self) -> BorrowedDiskFd<'_>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sector and cluster geometry of a disk image.
|
|
||||||
///
|
|
||||||
/// Default returns `DiskTopology::default()` (512B logical/physical).
|
|
||||||
pub trait Geometry: Send + Debug {
|
|
||||||
/// Returns the disk topology.
|
|
||||||
fn topology(&self) -> DiskTopology {
|
|
||||||
DiskTopology::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sparse and zero flag support for thin provisioned disk images.
|
|
||||||
pub trait SparseCapable: Send + Debug {
|
|
||||||
/// Indicates support for sparse operations (punch hole, write zeroes, discard).
|
|
||||||
fn supports_sparse_operations(&self) -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Indicates support for a metadata level zero flag optimization in
|
|
||||||
/// virtio `VIRTIO_BLK_T_WRITE_ZEROES` requests. When true, the format
|
|
||||||
/// can mark regions as reading zeros via a metadata bit rather than
|
|
||||||
/// writing actual zero bytes to disk.
|
|
||||||
fn supports_zero_flag(&self) -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Live disk resize support.
|
|
||||||
///
|
|
||||||
/// Implementations may return an error if the backend does not
|
|
||||||
/// support resizing (e.g. fixed size formats).
|
|
||||||
pub trait Resizable: Send + Debug {
|
|
||||||
/// Resizes the disk image to the given size in bytes, if the backend supports it.
|
|
||||||
fn resize(&mut self, size: u64) -> BlockResult<()>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Flush of format metadata cached in memory.
|
|
||||||
///
|
|
||||||
/// Default is a no-op for formats that keep no metadata cache
|
|
||||||
/// (e.g. raw, fixed vhd).
|
|
||||||
pub trait MetadataSync: Send + Debug {
|
|
||||||
/// Flushes format metadata cached in memory (e.g. qcow2 L2/refcount
|
|
||||||
/// tables) to the underlying file.
|
|
||||||
///
|
|
||||||
/// Called on device pause so that an externally copied or reopened
|
|
||||||
/// image is self-consistent without requiring a guest-initiated
|
|
||||||
/// flush.
|
|
||||||
fn sync_metadata(&self) -> BlockResult<()> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Supertrait bundling universal disk capabilities.
|
|
||||||
///
|
|
||||||
/// Every disk format implements `DiskSize` and `Geometry`.
|
|
||||||
/// `Sync` is required so that `Arc<dyn DiskFile>` can be shared
|
|
||||||
/// across threads for concurrent readonly access.
|
|
||||||
pub trait DiskFile: DiskSize + Geometry + Sync {}
|
|
||||||
|
|
||||||
/// Full capability disk file trait.
|
|
||||||
///
|
|
||||||
/// Bundles all optional capabilities on top of [`DiskFile`]:
|
|
||||||
/// file descriptor access, physical size, sparse operations, resize,
|
|
||||||
/// and metadata sync. Used by consumers that need feature negotiation
|
|
||||||
/// without async I/O (e.g. vhost user block).
|
|
||||||
pub trait FullDiskFile:
|
|
||||||
DiskFile + PhysicalSize + DiskFd + SparseCapable + Resizable + MetadataSync
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Blanket implementation: any type implementing all constituent traits
|
|
||||||
/// automatically satisfies [`FullDiskFile`].
|
|
||||||
impl<T: DiskFile + PhysicalSize + DiskFd + SparseCapable + Resizable + MetadataSync> FullDiskFile
|
|
||||||
for T
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Extended disk file trait for virtio queue workers.
|
|
||||||
///
|
|
||||||
/// Adds cloning and async I/O construction on top of [`DiskFile`].
|
|
||||||
/// `Unpin` is required so trait objects can be moved freely.
|
|
||||||
pub trait AsyncDiskFile: DiskFile + Unpin {
|
|
||||||
/// Creates an independent handle for a queue worker.
|
|
||||||
///
|
|
||||||
/// The clone shares internally reference counted state (e.g.
|
|
||||||
/// `Arc<Metadata>`) with the original, but owns its own file
|
|
||||||
/// descriptor and I/O completion resources. Each virtio queue
|
|
||||||
/// gets one clone so that workers can operate in parallel
|
|
||||||
/// without contending on I/O state.
|
|
||||||
///
|
|
||||||
/// Returns `Box<dyn AsyncDiskFile>` (not `AsyncFullDiskFile`)
|
|
||||||
/// because clones only serve as data plane handles for queue
|
|
||||||
/// workers. The original remains the control plane for feature
|
|
||||||
/// negotiation and configuration.
|
|
||||||
fn try_clone(&self) -> BlockResult<Box<dyn AsyncDiskFile>>;
|
|
||||||
|
|
||||||
/// Constructs a per queue async I/O engine.
|
|
||||||
///
|
|
||||||
/// # Arguments
|
|
||||||
///
|
|
||||||
/// * `ring_depth` - maximum number of in flight I/O operations.
|
|
||||||
/// Callers typically pass the virtio queue size. Must be greater
|
|
||||||
/// than zero. Backends that do not use an async ring (e.g. sync
|
|
||||||
/// fallback implementations) may ignore this value.
|
|
||||||
fn create_async_io(&self, ring_depth: u32) -> BlockResult<Box<dyn AsyncIo>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Full capability async disk file trait.
|
|
||||||
///
|
|
||||||
/// Combines [`FullDiskFile`] (all optional capabilities) with
|
|
||||||
/// [`AsyncDiskFile`] (async I/O construction). This is the top level
|
|
||||||
/// trait for virtio block devices that need both feature negotiation
|
|
||||||
/// and async queue workers.
|
|
||||||
///
|
|
||||||
/// The type narrowing on [`AsyncDiskFile::try_clone`] is intentional:
|
|
||||||
/// clones only serve as data plane handles for queue workers, while
|
|
||||||
/// the original `AsyncFullDiskFile` handle remains the control plane
|
|
||||||
/// for feature negotiation and configuration.
|
|
||||||
pub trait AsyncFullDiskFile: FullDiskFile + AsyncDiskFile {}
|
|
||||||
|
|
||||||
/// Blanket implementation: any type implementing both [`FullDiskFile`]
|
|
||||||
/// and [`AsyncDiskFile`] automatically satisfies [`AsyncFullDiskFile`].
|
|
||||||
impl<T: FullDiskFile + AsyncDiskFile> AsyncFullDiskFile for T {}
|
|
||||||
@@ -1,245 +0,0 @@
|
|||||||
// Copyright 2025 The Cloud Hypervisor Authors. All rights reserved.
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
//! Unified error handling for the block crate.
|
|
||||||
//!
|
|
||||||
//! # Architecture
|
|
||||||
//!
|
|
||||||
//! ```text
|
|
||||||
//! BlockError -- single public error type
|
|
||||||
//! |-- BlockErrorKind -- small, stable, matchable classification
|
|
||||||
//! |-- ErrorContext -- optional diagnostic metadata (path, offset, op)
|
|
||||||
//! +-- source -- format-specific error (boxed)
|
|
||||||
//! |-- QcowError
|
|
||||||
//! |-- VhdError / RawError / ...
|
|
||||||
//! +-- io::Error / etc.
|
|
||||||
//! ```
|
|
||||||
|
|
||||||
use std::error::Error as StdError;
|
|
||||||
use std::fmt::{self, Display, Formatter};
|
|
||||||
use std::io;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
/// Small, stable classification of block errors.
|
|
||||||
///
|
|
||||||
/// Callers match on this for control flow. Adding new format specific
|
|
||||||
/// errors does not require new variants here.
|
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
|
||||||
#[non_exhaustive]
|
|
||||||
pub enum BlockErrorKind {
|
|
||||||
/// An underlying I/O operation failed.
|
|
||||||
Io,
|
|
||||||
/// The disk image format is structurally invalid.
|
|
||||||
InvalidFormat,
|
|
||||||
/// The disk image requires a feature that is not implemented.
|
|
||||||
UnsupportedFeature,
|
|
||||||
/// The image is marked or detected as corrupt.
|
|
||||||
CorruptImage,
|
|
||||||
/// An address, offset, or index is outside the valid range.
|
|
||||||
OutOfBounds,
|
|
||||||
/// A file or required internal structure could not be found.
|
|
||||||
NotFound,
|
|
||||||
/// An internal counter or limit was exceeded.
|
|
||||||
Overflow,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for BlockErrorKind {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::Io => write!(f, "I/O error"),
|
|
||||||
Self::InvalidFormat => write!(f, "Invalid format"),
|
|
||||||
Self::UnsupportedFeature => write!(f, "Unsupported feature"),
|
|
||||||
Self::CorruptImage => write!(f, "Corrupt image"),
|
|
||||||
Self::OutOfBounds => write!(f, "Out of bounds"),
|
|
||||||
Self::NotFound => write!(f, "Not found"),
|
|
||||||
Self::Overflow => write!(f, "Overflow"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Classification of the operation that was in progress when an error occurred.
|
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
|
||||||
#[non_exhaustive]
|
|
||||||
pub enum ErrorOp {
|
|
||||||
/// Opening a disk image file.
|
|
||||||
Open,
|
|
||||||
/// Detecting the image format.
|
|
||||||
DetectImageType,
|
|
||||||
/// Duplicating a backing-file descriptor.
|
|
||||||
DupBackingFd,
|
|
||||||
/// Resizing a disk image.
|
|
||||||
Resize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for ErrorOp {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::Open => write!(f, "open"),
|
|
||||||
Self::DetectImageType => write!(f, "detect_image_type"),
|
|
||||||
Self::DupBackingFd => write!(f, "dup_backing_fd"),
|
|
||||||
Self::Resize => write!(f, "resize"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Optional diagnostic context attached to a [`BlockError`].
|
|
||||||
#[derive(Debug, Default, Clone)]
|
|
||||||
pub struct ErrorContext {
|
|
||||||
pub path: Option<PathBuf>,
|
|
||||||
pub offset: Option<u64>,
|
|
||||||
pub op: Option<ErrorOp>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for ErrorContext {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
|
||||||
let mut first = true;
|
|
||||||
if let Some(path) = &self.path {
|
|
||||||
write!(f, "path={}", path.display())?;
|
|
||||||
first = false;
|
|
||||||
}
|
|
||||||
if let Some(offset) = self.offset {
|
|
||||||
if !first {
|
|
||||||
write!(f, " ")?;
|
|
||||||
}
|
|
||||||
write!(f, "offset={offset:#x}")?;
|
|
||||||
first = false;
|
|
||||||
}
|
|
||||||
if let Some(op) = self.op {
|
|
||||||
if !first {
|
|
||||||
write!(f, " ")?;
|
|
||||||
}
|
|
||||||
write!(f, "op={op}")?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Unified error type for the block crate.
|
|
||||||
///
|
|
||||||
/// Pairs a stable [`BlockErrorKind`] classification with an optional
|
|
||||||
/// boxed source error (format-specific) and optional [`ErrorContext`].
|
|
||||||
///
|
|
||||||
/// Display renders kind + context only; the underlying cause is
|
|
||||||
/// exposed via [`std::error::Error::source()`] for reporters that
|
|
||||||
/// walk the chain.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct BlockError {
|
|
||||||
kind: BlockErrorKind,
|
|
||||||
source: Option<Box<dyn StdError + Send + Sync + 'static>>,
|
|
||||||
ctx: Option<ErrorContext>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BlockError {
|
|
||||||
/// Create a new `BlockError` from a kind and a source error.
|
|
||||||
pub fn new<E>(kind: BlockErrorKind, source: E) -> Self
|
|
||||||
where
|
|
||||||
E: StdError + Send + Sync + 'static,
|
|
||||||
{
|
|
||||||
Self {
|
|
||||||
kind,
|
|
||||||
source: Some(Box::new(source)),
|
|
||||||
ctx: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a `BlockError` from just a kind, with no underlying cause.
|
|
||||||
pub fn from_kind(kind: BlockErrorKind) -> Self {
|
|
||||||
Self {
|
|
||||||
kind,
|
|
||||||
source: None,
|
|
||||||
ctx: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Attach or replace the source error (builder-style).
|
|
||||||
pub fn with_source<E>(mut self, source: E) -> Self
|
|
||||||
where
|
|
||||||
E: StdError + Send + Sync + 'static,
|
|
||||||
{
|
|
||||||
self.source = Some(Box::new(source));
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Attach diagnostic context.
|
|
||||||
pub fn with_ctx(mut self, ctx: ErrorContext) -> Self {
|
|
||||||
self.ctx = Some(ctx);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Replace the error classification (builder-style).
|
|
||||||
pub fn with_kind(mut self, kind: BlockErrorKind) -> Self {
|
|
||||||
self.kind = kind;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Shorthand: attach an operation name.
|
|
||||||
pub fn with_op(mut self, op: ErrorOp) -> Self {
|
|
||||||
self.ctx.get_or_insert_with(ErrorContext::default).op = Some(op);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Shorthand: attach a file path.
|
|
||||||
pub fn with_path(mut self, path: impl Into<PathBuf>) -> Self {
|
|
||||||
self.ctx.get_or_insert_with(ErrorContext::default).path = Some(path.into());
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Shorthand: attach a byte offset.
|
|
||||||
pub fn with_offset(mut self, offset: u64) -> Self {
|
|
||||||
self.ctx.get_or_insert_with(ErrorContext::default).offset = Some(offset);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The error classification.
|
|
||||||
pub fn kind(&self) -> BlockErrorKind {
|
|
||||||
self.kind
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The diagnostic context, if any.
|
|
||||||
pub fn context(&self) -> Option<&ErrorContext> {
|
|
||||||
self.ctx.as_ref()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Access the underlying source error, if any.
|
|
||||||
pub fn source_ref(&self) -> Option<&(dyn StdError + Send + Sync + 'static)> {
|
|
||||||
self.source.as_deref()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Try to downcast the source to a concrete type.
|
|
||||||
pub fn downcast_ref<T: StdError + 'static>(&self) -> Option<&T> {
|
|
||||||
self.source.as_ref()?.downcast_ref::<T>()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Consume the error and return the boxed source, if any.
|
|
||||||
pub fn into_source(self) -> Option<Box<dyn StdError + Send + Sync + 'static>> {
|
|
||||||
self.source
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for BlockError {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
|
||||||
write!(f, "{}", self.kind)?;
|
|
||||||
if let Some(ctx) = &self.ctx {
|
|
||||||
write!(f, " ({ctx})")?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StdError for BlockError {
|
|
||||||
fn source(&self) -> Option<&(dyn StdError + 'static)> {
|
|
||||||
self.source
|
|
||||||
.as_ref()
|
|
||||||
.map(|e| e.as_ref() as &(dyn StdError + 'static))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Convenience: wrap an `io::Error` as `BlockErrorKind::Io`.
|
|
||||||
impl From<io::Error> for BlockError {
|
|
||||||
fn from(e: io::Error) -> Self {
|
|
||||||
Self::new(BlockErrorKind::Io, e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type BlockResult<T> = Result<T, BlockError>;
|
|
||||||
@@ -1,312 +0,0 @@
|
|||||||
// Copyright 2026 The Cloud Hypervisor Authors. All rights reserved.
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
//! Disk image factory.
|
|
||||||
//!
|
|
||||||
//! [`open_disk`] is the single entry point for opening a disk image.
|
|
||||||
//! It opens the file, detects the image format, probes async I/O
|
|
||||||
//! support, and constructs the appropriate backend. Callers receive
|
|
||||||
//! a trait object that is ready for use by virtio queue workers.
|
|
||||||
|
|
||||||
use std::os::unix::fs::OpenOptionsExt;
|
|
||||||
use std::path::Path;
|
|
||||||
use std::sync::OnceLock;
|
|
||||||
use std::{fmt, fs};
|
|
||||||
|
|
||||||
use log::info;
|
|
||||||
|
|
||||||
#[cfg(feature = "io_uring")]
|
|
||||||
use crate::block_io_uring_is_supported;
|
|
||||||
use crate::disk_file::AsyncFullDiskFile;
|
|
||||||
use crate::error::{BlockError, BlockErrorKind, BlockResult};
|
|
||||||
use crate::formats::qcow::QcowDisk;
|
|
||||||
use crate::formats::raw::{RawBackend, RawDisk};
|
|
||||||
use crate::formats::vhd::VhdDisk;
|
|
||||||
use crate::formats::vhdx::VhdxDisk;
|
|
||||||
use crate::formats::vmdk::VmdkDisk;
|
|
||||||
use crate::{
|
|
||||||
ImageType, block_aio_is_supported, detect_image_type, open_disk_image, preallocate_disk,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Options for opening a disk image via [`open_disk`].
|
|
||||||
pub struct DiskOpenOptions<'a> {
|
|
||||||
pub path: &'a Path,
|
|
||||||
pub readonly: bool,
|
|
||||||
pub direct: bool,
|
|
||||||
pub sparse: bool,
|
|
||||||
pub backing_files: bool,
|
|
||||||
pub disable_io_uring: bool,
|
|
||||||
pub disable_aio: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Result of [`open_disk`], carrying the detected image type alongside
|
|
||||||
/// the constructed backend.
|
|
||||||
pub struct OpenedDisk {
|
|
||||||
pub image_type: ImageType,
|
|
||||||
pub disk: Box<dyn AsyncFullDiskFile>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Debug for OpenedDisk {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
f.debug_struct("OpenedDisk")
|
|
||||||
.field("image_type", &self.image_type)
|
|
||||||
.finish_non_exhaustive()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns true when io_uring is supported on the running kernel.
|
|
||||||
///
|
|
||||||
/// The result is cached so the probe runs at most once per process.
|
|
||||||
#[cfg(feature = "io_uring")]
|
|
||||||
fn io_uring_supported() -> bool {
|
|
||||||
static SUPPORTED: OnceLock<bool> = OnceLock::new();
|
|
||||||
*SUPPORTED.get_or_init(block_io_uring_is_supported)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns true when Linux AIO is supported on the running kernel.
|
|
||||||
///
|
|
||||||
/// The result is cached so the probe runs at most once per process.
|
|
||||||
fn aio_supported() -> bool {
|
|
||||||
static SUPPORTED: OnceLock<bool> = OnceLock::new();
|
|
||||||
*SUPPORTED.get_or_init(block_aio_is_supported)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Open a disk image and construct the appropriate async backend.
|
|
||||||
///
|
|
||||||
/// - Opens the file with the requested access mode and flags.
|
|
||||||
/// - Detects the image format from the file header.
|
|
||||||
/// - Probes io_uring and Linux AIO support on the running kernel.
|
|
||||||
/// - Constructs the most capable backend available for the detected
|
|
||||||
/// format, preferring io_uring over AIO over synchronous fallback.
|
|
||||||
///
|
|
||||||
/// The returned [`OpenedDisk`] exposes the detected [`ImageType`] so
|
|
||||||
/// callers can perform post construction validation (e.g. type mismatch
|
|
||||||
/// checks, configuration warnings).
|
|
||||||
pub fn open_disk(options: &DiskOpenOptions<'_>) -> BlockResult<OpenedDisk> {
|
|
||||||
let mut fs_options = fs::OpenOptions::new();
|
|
||||||
fs_options.read(true);
|
|
||||||
fs_options.write(!options.readonly);
|
|
||||||
if options.direct {
|
|
||||||
fs_options.custom_flags(libc::O_DIRECT);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut file = open_disk_image(options.path, &fs_options)?;
|
|
||||||
let image_type = detect_image_type(&mut file)?;
|
|
||||||
|
|
||||||
let disk: Box<dyn AsyncFullDiskFile> = match image_type {
|
|
||||||
ImageType::FixedVhd => open_fixed_vhd(file, options)?,
|
|
||||||
ImageType::Raw => open_raw(file, options)?,
|
|
||||||
ImageType::Qcow2 => open_qcow2(file, options)?,
|
|
||||||
ImageType::Vhdx => open_vhdx(file, options)?,
|
|
||||||
ImageType::FlatVmdk => open_flat_vmdk(file, options)?,
|
|
||||||
ImageType::Unknown => {
|
|
||||||
return Err(
|
|
||||||
BlockError::from_kind(BlockErrorKind::UnsupportedFeature).with_path(options.path)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(OpenedDisk { image_type, disk })
|
|
||||||
}
|
|
||||||
|
|
||||||
fn open_vhdx(
|
|
||||||
file: fs::File,
|
|
||||||
options: &DiskOpenOptions<'_>,
|
|
||||||
) -> BlockResult<Box<dyn AsyncFullDiskFile>> {
|
|
||||||
info!("Opening VHDX disk file with synchronous backend");
|
|
||||||
Ok(Box::new(
|
|
||||||
VhdxDisk::new(file, options.direct).map_err(|e| e.with_path(options.path))?,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn open_fixed_vhd(
|
|
||||||
file: fs::File,
|
|
||||||
options: &DiskOpenOptions<'_>,
|
|
||||||
) -> BlockResult<Box<dyn AsyncFullDiskFile>> {
|
|
||||||
#[cfg(feature = "io_uring")]
|
|
||||||
if !options.disable_io_uring {
|
|
||||||
if io_uring_supported() {
|
|
||||||
info!("Opening fixed VHD disk file with io_uring backend");
|
|
||||||
return Ok(Box::new(
|
|
||||||
VhdDisk::new(file, true, options.direct).map_err(|e| e.with_path(options.path))?,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
info!("io_uring runtime probe failed for fixed VHD, using synchronous backend");
|
|
||||||
}
|
|
||||||
|
|
||||||
info!("Opening fixed VHD disk file with synchronous backend");
|
|
||||||
Ok(Box::new(
|
|
||||||
VhdDisk::new(file, false, options.direct).map_err(|e| e.with_path(options.path))?,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn open_raw(
|
|
||||||
file: fs::File,
|
|
||||||
options: &DiskOpenOptions<'_>,
|
|
||||||
) -> BlockResult<Box<dyn AsyncFullDiskFile>> {
|
|
||||||
if !options.readonly && !options.sparse {
|
|
||||||
preallocate_disk(&file, options.path);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "io_uring")]
|
|
||||||
if !options.disable_io_uring {
|
|
||||||
if io_uring_supported() {
|
|
||||||
info!("Opening RAW disk file with io_uring backend");
|
|
||||||
return Ok(Box::new(RawDisk::new(
|
|
||||||
file,
|
|
||||||
RawBackend::IoUring,
|
|
||||||
options.direct,
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
info!("io_uring runtime probe failed for RAW, trying next backend");
|
|
||||||
}
|
|
||||||
|
|
||||||
if !options.disable_aio {
|
|
||||||
if aio_supported() {
|
|
||||||
info!("Opening RAW disk file with AIO backend");
|
|
||||||
return Ok(Box::new(RawDisk::new(
|
|
||||||
file,
|
|
||||||
RawBackend::Aio,
|
|
||||||
options.direct,
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
info!("AIO runtime probe failed for RAW, using synchronous backend");
|
|
||||||
}
|
|
||||||
|
|
||||||
info!("Opening RAW disk file with synchronous backend");
|
|
||||||
Ok(Box::new(RawDisk::new(
|
|
||||||
file,
|
|
||||||
RawBackend::Sync,
|
|
||||||
options.direct,
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn open_qcow2(
|
|
||||||
file: fs::File,
|
|
||||||
options: &DiskOpenOptions<'_>,
|
|
||||||
) -> BlockResult<Box<dyn AsyncFullDiskFile>> {
|
|
||||||
#[cfg(feature = "io_uring")]
|
|
||||||
if !options.disable_io_uring {
|
|
||||||
if io_uring_supported() {
|
|
||||||
info!("Opening QCOW2 disk file with io_uring backend");
|
|
||||||
return Ok(Box::new(
|
|
||||||
QcowDisk::new(
|
|
||||||
file,
|
|
||||||
options.direct,
|
|
||||||
options.backing_files,
|
|
||||||
options.sparse,
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
.map_err(|e| e.with_path(options.path))?,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
info!("io_uring runtime probe failed for QCOW2, using synchronous backend");
|
|
||||||
}
|
|
||||||
|
|
||||||
info!("Opening QCOW2 disk file with synchronous backend");
|
|
||||||
Ok(Box::new(
|
|
||||||
QcowDisk::new(
|
|
||||||
file,
|
|
||||||
options.direct,
|
|
||||||
options.backing_files,
|
|
||||||
options.sparse,
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
.map_err(|e| e.with_path(options.path))?,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn open_flat_vmdk(
|
|
||||||
file: fs::File,
|
|
||||||
options: &DiskOpenOptions<'_>,
|
|
||||||
) -> BlockResult<Box<dyn AsyncFullDiskFile>> {
|
|
||||||
info!("Opening VMDK disk file with synchronous backend");
|
|
||||||
Ok(Box::new(
|
|
||||||
VmdkDisk::new(file, options.path, options.direct).map_err(|e| e.with_path(options.path))?,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod unit_tests {
|
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
use vmm_sys_util::tempfile::TempFile;
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
use crate::formats::qcow;
|
|
||||||
|
|
||||||
fn default_options(path: &Path) -> DiskOpenOptions<'_> {
|
|
||||||
DiskOpenOptions {
|
|
||||||
path,
|
|
||||||
readonly: false,
|
|
||||||
direct: false,
|
|
||||||
sparse: false,
|
|
||||||
backing_files: false,
|
|
||||||
disable_io_uring: true,
|
|
||||||
disable_aio: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn nonexistent_path_returns_error() {
|
|
||||||
let path = Path::new("/tmp/no_such_disk_image.raw");
|
|
||||||
let options = default_options(path);
|
|
||||||
match open_disk(&options) {
|
|
||||||
Err(e) => assert_eq!(e.kind(), BlockErrorKind::Io),
|
|
||||||
Ok(_) => panic!("expected error for nonexistent path"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn detect_raw_image() {
|
|
||||||
let tmp = TempFile::new().unwrap();
|
|
||||||
tmp.as_file().set_len(1 << 20).unwrap();
|
|
||||||
let path = tmp.as_path().to_owned();
|
|
||||||
let options = default_options(&path);
|
|
||||||
let opened = open_disk(&options).unwrap();
|
|
||||||
assert_eq!(opened.image_type, ImageType::Raw);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn detect_qcow2_image() {
|
|
||||||
let tmp = qcow::QcowTempDisk::new(100 * 1024 * 1024, None, false, true, false)
|
|
||||||
.unwrap()
|
|
||||||
.into_tempfile();
|
|
||||||
let path = tmp.as_path().to_owned();
|
|
||||||
let options = default_options(&path);
|
|
||||||
let opened = open_disk(&options).unwrap();
|
|
||||||
assert_eq!(opened.image_type, ImageType::Qcow2);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn open_readonly() {
|
|
||||||
let tmp = TempFile::new().unwrap();
|
|
||||||
tmp.as_file().set_len(1 << 20).unwrap();
|
|
||||||
let path = tmp.as_path().to_owned();
|
|
||||||
let mut options = default_options(&path);
|
|
||||||
options.readonly = true;
|
|
||||||
let opened = open_disk(&options).unwrap();
|
|
||||||
assert_eq!(opened.image_type, ImageType::Raw);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn sync_fallback_when_async_disabled() {
|
|
||||||
let tmp = TempFile::new().unwrap();
|
|
||||||
let size = 1u64 << 20;
|
|
||||||
tmp.as_file().set_len(size).unwrap();
|
|
||||||
let path = tmp.as_path().to_owned();
|
|
||||||
let options = DiskOpenOptions {
|
|
||||||
path: &path,
|
|
||||||
readonly: false,
|
|
||||||
direct: false,
|
|
||||||
sparse: false,
|
|
||||||
backing_files: false,
|
|
||||||
disable_io_uring: true,
|
|
||||||
disable_aio: true,
|
|
||||||
};
|
|
||||||
let opened = open_disk(&options).unwrap();
|
|
||||||
assert_eq!(opened.image_type, ImageType::Raw);
|
|
||||||
assert_eq!(opened.disk.logical_size().unwrap(), size);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
// Copyright 2026 The Cloud Hypervisor Authors. All rights reserved.
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
//! Disk format implementations.
|
|
||||||
//!
|
|
||||||
//! Each format lives in its own submodule with a `DiskFile` wrapper,
|
|
||||||
//! format specific internals, and sync/async I/O workers.
|
|
||||||
|
|
||||||
pub mod qcow;
|
|
||||||
pub mod raw;
|
|
||||||
pub mod vhd;
|
|
||||||
pub mod vhdx;
|
|
||||||
pub mod vmdk;
|
|
||||||
@@ -1,173 +0,0 @@
|
|||||||
// Copyright © 2021 Intel Corporation
|
|
||||||
//
|
|
||||||
// Copyright 2026 The Cloud Hypervisor Authors. All rights reserved.
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
|
||||||
|
|
||||||
//! Thread safe backing file readers for QCOW2 images.
|
|
||||||
|
|
||||||
use std::fs::File;
|
|
||||||
use std::io;
|
|
||||||
use std::os::fd::{AsFd, BorrowedFd, OwnedFd};
|
|
||||||
use std::os::unix::fs::FileExt;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use super::decoder::Decoder;
|
|
||||||
use super::metadata::{BackingRead, ClusterReadMapping, QcowMetadata};
|
|
||||||
use super::parser::{BackingFile, BackingKind, Error as QcowError};
|
|
||||||
use crate::error::{BlockError, BlockErrorKind, BlockResult, ErrorOp};
|
|
||||||
use crate::formats::qcow::common::decompress_cluster;
|
|
||||||
|
|
||||||
/// Raw backing file using position-independent reads on a duplicated fd.
|
|
||||||
pub(crate) struct RawBacking {
|
|
||||||
pub(crate) file: File,
|
|
||||||
pub(crate) virtual_size: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
// SAFETY: The only I/O operation is read_at which is position independent
|
|
||||||
// and safe for concurrent use from multiple threads.
|
|
||||||
unsafe impl Sync for RawBacking {}
|
|
||||||
|
|
||||||
impl BackingRead for RawBacking {
|
|
||||||
fn read_at(&self, address: u64, buf: &mut [u8]) -> io::Result<()> {
|
|
||||||
if address >= self.virtual_size {
|
|
||||||
buf.fill(0);
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
let available = (self.virtual_size - address) as usize;
|
|
||||||
if available >= buf.len() {
|
|
||||||
self.file.read_exact_at(buf, address)
|
|
||||||
} else {
|
|
||||||
self.file.read_exact_at(&mut buf[..available], address)?;
|
|
||||||
buf[available..].fill(0);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// QCOW2 image used as a backing file for another QCOW2 image.
|
|
||||||
///
|
|
||||||
/// Resolves guest offsets through the QCOW2 cluster mapping (L1/L2
|
|
||||||
/// tables, refcounts) before reading the underlying data. Read only
|
|
||||||
/// because backing files never receive writes. Nested backing chains
|
|
||||||
/// are handled recursively via the optional `backing_file` field.
|
|
||||||
pub(crate) struct Qcow2Backing {
|
|
||||||
pub(crate) metadata: Arc<QcowMetadata>,
|
|
||||||
pub(crate) data_file: File,
|
|
||||||
pub(crate) backing_file: Option<Arc<dyn BackingRead>>,
|
|
||||||
pub(crate) cluster_size: u64,
|
|
||||||
pub(crate) decoder: Arc<dyn Decoder>,
|
|
||||||
}
|
|
||||||
|
|
||||||
// SAFETY: All reads go through QcowMetadata which uses RwLock
|
|
||||||
// and read_exact_at which is position independent and thread safe.
|
|
||||||
unsafe impl Sync for Qcow2Backing {}
|
|
||||||
|
|
||||||
impl BackingRead for Qcow2Backing {
|
|
||||||
fn read_at(&self, address: u64, buf: &mut [u8]) -> io::Result<()> {
|
|
||||||
let virtual_size = self.metadata.virtual_size();
|
|
||||||
if address >= virtual_size {
|
|
||||||
buf.fill(0);
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
let available = (virtual_size - address) as usize;
|
|
||||||
if available < buf.len() {
|
|
||||||
self.read_clusters(address, &mut buf[..available])?;
|
|
||||||
buf[available..].fill(0);
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
self.read_clusters(address, buf)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Qcow2Backing {
|
|
||||||
fn read_clusters(&self, address: u64, buf: &mut [u8]) -> io::Result<()> {
|
|
||||||
let total_len = buf.len();
|
|
||||||
let has_backing = self.backing_file.is_some();
|
|
||||||
|
|
||||||
let mappings = self
|
|
||||||
.metadata
|
|
||||||
.map_clusters_for_read(address, total_len, has_backing)?;
|
|
||||||
|
|
||||||
let mut buf_offset = 0usize;
|
|
||||||
for mapping in mappings {
|
|
||||||
match mapping {
|
|
||||||
ClusterReadMapping::Zero { length } => {
|
|
||||||
buf[buf_offset..buf_offset + length as usize].fill(0);
|
|
||||||
buf_offset += length as usize;
|
|
||||||
}
|
|
||||||
ClusterReadMapping::Allocated {
|
|
||||||
offset: host_offset,
|
|
||||||
length,
|
|
||||||
} => {
|
|
||||||
self.data_file.read_exact_at(
|
|
||||||
&mut buf[buf_offset..buf_offset + length as usize],
|
|
||||||
host_offset,
|
|
||||||
)?;
|
|
||||||
buf_offset += length as usize;
|
|
||||||
}
|
|
||||||
ClusterReadMapping::Compressed {
|
|
||||||
host_offset,
|
|
||||||
compressed_size,
|
|
||||||
cluster_offset,
|
|
||||||
length,
|
|
||||||
} => {
|
|
||||||
let mut compressed = vec![0u8; compressed_size];
|
|
||||||
self.data_file.read_exact_at(&mut compressed, host_offset)?;
|
|
||||||
let decompressed = decompress_cluster(
|
|
||||||
&compressed,
|
|
||||||
self.cluster_size as usize,
|
|
||||||
&*self.decoder,
|
|
||||||
)?;
|
|
||||||
buf[buf_offset..buf_offset + length]
|
|
||||||
.copy_from_slice(&decompressed[cluster_offset..cluster_offset + length]);
|
|
||||||
buf_offset += length;
|
|
||||||
}
|
|
||||||
ClusterReadMapping::Backing {
|
|
||||||
offset: backing_offset,
|
|
||||||
length,
|
|
||||||
} => {
|
|
||||||
self.backing_file.as_ref().unwrap().read_at(
|
|
||||||
backing_offset,
|
|
||||||
&mut buf[buf_offset..buf_offset + length as usize],
|
|
||||||
)?;
|
|
||||||
buf_offset += length as usize;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Construct a thread safe backing file reader.
|
|
||||||
pub(super) fn shared_backing_from(bf: BackingFile) -> BlockResult<Arc<dyn BackingRead>> {
|
|
||||||
let (kind, virtual_size) = bf.into_kind();
|
|
||||||
|
|
||||||
let dup_fd = |fd: BorrowedFd<'_>| -> BlockResult<OwnedFd> {
|
|
||||||
fd.try_clone_to_owned().map_err(|e| {
|
|
||||||
BlockError::new(
|
|
||||||
BlockErrorKind::Io,
|
|
||||||
QcowError::BackingFileIo(String::new(), e),
|
|
||||||
)
|
|
||||||
.with_op(ErrorOp::DupBackingFd)
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
match kind {
|
|
||||||
BackingKind::Raw(raw_file) => {
|
|
||||||
let file = File::from(dup_fd(raw_file.as_fd())?);
|
|
||||||
Ok(Arc::new(RawBacking { file, virtual_size }))
|
|
||||||
}
|
|
||||||
BackingKind::Qcow { inner, backing } => {
|
|
||||||
let data_file = File::from(dup_fd(inner.raw_file.as_fd())?);
|
|
||||||
let metadata = Arc::new(QcowMetadata::new(*inner));
|
|
||||||
Ok(Arc::new(Qcow2Backing {
|
|
||||||
cluster_size: metadata.cluster_size(),
|
|
||||||
decoder: metadata.decoder(),
|
|
||||||
metadata,
|
|
||||||
data_file,
|
|
||||||
backing_file: backing.map(|bf| shared_backing_from(*bf)).transpose()?,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,361 +0,0 @@
|
|||||||
// Copyright © 2021 Intel Corporation
|
|
||||||
//
|
|
||||||
// Copyright 2026 The Cloud Hypervisor Authors. All rights reserved.
|
|
||||||
//
|
|
||||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
|
||||||
|
|
||||||
//! Shared helpers for QCOW2 sync and async backends.
|
|
||||||
|
|
||||||
use std::cmp::min;
|
|
||||||
use std::io;
|
|
||||||
use std::os::unix::fs::FileExt;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use vmm_sys_util::write_zeroes::{PunchHole, WriteZeroesAt};
|
|
||||||
|
|
||||||
use super::decoder::Decoder;
|
|
||||||
use super::metadata::{
|
|
||||||
BackingRead, ClusterReadMapping, ClusterWriteMapping, DeallocAction, QcowMetadata,
|
|
||||||
};
|
|
||||||
use super::qcow_raw_file::QcowRawFile;
|
|
||||||
use crate::async_io::{AsyncIoError, AsyncIoOperation, AsyncIoResult};
|
|
||||||
|
|
||||||
/// Decompress a full QCOW2 cluster from compressed data.
|
|
||||||
///
|
|
||||||
/// Returns a `cluster_size` byte buffer with the decompressed cluster
|
|
||||||
/// content. Fails if the decoder does not produce exactly `cluster_size`
|
|
||||||
/// bytes.
|
|
||||||
pub(super) fn decompress_cluster(
|
|
||||||
compressed: &[u8],
|
|
||||||
cluster_size: usize,
|
|
||||||
decoder: &dyn Decoder,
|
|
||||||
) -> io::Result<Vec<u8>> {
|
|
||||||
let mut decompressed = vec![0u8; cluster_size];
|
|
||||||
let n = decoder
|
|
||||||
.decode(compressed, &mut decompressed)
|
|
||||||
.map_err(|_| io::Error::from_raw_os_error(libc::EIO))?;
|
|
||||||
if n != cluster_size {
|
|
||||||
return Err(io::Error::from_raw_os_error(libc::EIO));
|
|
||||||
}
|
|
||||||
Ok(decompressed)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Applies one deallocation action to the data file and refcount table.
|
|
||||||
pub(super) fn apply_dealloc_action(
|
|
||||||
metadata: &QcowMetadata,
|
|
||||||
data_file: &mut QcowRawFile,
|
|
||||||
action: &DeallocAction,
|
|
||||||
) -> io::Result<()> {
|
|
||||||
match action {
|
|
||||||
DeallocAction::PunchHole {
|
|
||||||
host_offset,
|
|
||||||
length,
|
|
||||||
} => {
|
|
||||||
data_file.file_mut().punch_hole(*host_offset, *length)?;
|
|
||||||
metadata.complete_punch_hole(*host_offset);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
DeallocAction::WriteZeroes {
|
|
||||||
host_offset,
|
|
||||||
length,
|
|
||||||
} => data_file
|
|
||||||
.file_mut()
|
|
||||||
.write_zeroes_at(*host_offset, *length)
|
|
||||||
.map(|_| ()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Deallocates a byte range and returns the completion result, 0 on
|
|
||||||
/// success or a negative errno on the first failing action.
|
|
||||||
pub(super) fn deallocate_range_result(
|
|
||||||
metadata: &QcowMetadata,
|
|
||||||
data_file: &mut QcowRawFile,
|
|
||||||
offset: u64,
|
|
||||||
length: usize,
|
|
||||||
sparse: bool,
|
|
||||||
write_zeroes: bool,
|
|
||||||
backing_file: Option<&dyn BackingRead>,
|
|
||||||
) -> i32 {
|
|
||||||
let wrap_error: fn(io::Error) -> AsyncIoError = if write_zeroes {
|
|
||||||
AsyncIoError::WriteZeroes
|
|
||||||
} else {
|
|
||||||
AsyncIoError::PunchHole
|
|
||||||
};
|
|
||||||
let result = metadata
|
|
||||||
.deallocate_bytes(offset, length, sparse, write_zeroes, backing_file)
|
|
||||||
.and_then(|actions| {
|
|
||||||
let mut first_error = None;
|
|
||||||
for action in &actions {
|
|
||||||
if let Err(e) = apply_dealloc_action(metadata, data_file, action) {
|
|
||||||
first_error.get_or_insert(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
first_error.map_or(Ok(()), Err)
|
|
||||||
})
|
|
||||||
.map_err(wrap_error);
|
|
||||||
match result {
|
|
||||||
Ok(()) => 0,
|
|
||||||
Err(AsyncIoError::PunchHole(e) | AsyncIoError::WriteZeroes(e)) => {
|
|
||||||
-e.raw_os_error().unwrap_or(libc::EIO)
|
|
||||||
}
|
|
||||||
Err(_) => -libc::EIO,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Writes an operation to the data file cluster by cluster, allocating
|
|
||||||
/// and copying up backing data as needed. Writes are synchronous because
|
|
||||||
/// the host offset is only known after the metadata allocation.
|
|
||||||
pub(super) fn cow_write_sync(
|
|
||||||
address: u64,
|
|
||||||
op: &AsyncIoOperation,
|
|
||||||
metadata: &QcowMetadata,
|
|
||||||
data_file: &QcowRawFile,
|
|
||||||
backing_file: &Option<Arc<dyn BackingRead>>,
|
|
||||||
cluster_size: u64,
|
|
||||||
) -> AsyncIoResult<()> {
|
|
||||||
let total_len = op.total_len();
|
|
||||||
let mut buf_offset = 0usize;
|
|
||||||
|
|
||||||
while buf_offset < total_len {
|
|
||||||
let curr_addr = address + buf_offset as u64;
|
|
||||||
let intra_offset = curr_addr & (cluster_size - 1);
|
|
||||||
let remaining_in_cluster = (cluster_size - intra_offset) as usize;
|
|
||||||
let count = min(total_len - buf_offset, remaining_in_cluster);
|
|
||||||
|
|
||||||
let backing_data = if let Some(backing) = backing_file
|
|
||||||
.as_ref()
|
|
||||||
.filter(|_| intra_offset != 0 || count < cluster_size as usize)
|
|
||||||
{
|
|
||||||
let cluster_begin = curr_addr - intra_offset;
|
|
||||||
let mut data = vec![0u8; cluster_size as usize];
|
|
||||||
backing
|
|
||||||
.read_at(cluster_begin, &mut data)
|
|
||||||
.map_err(AsyncIoError::WriteVectored)?;
|
|
||||||
Some(data)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
let mapping = metadata
|
|
||||||
.map_cluster_for_write(curr_addr, backing_data)
|
|
||||||
.map_err(AsyncIoError::WriteVectored)?;
|
|
||||||
|
|
||||||
match mapping {
|
|
||||||
ClusterWriteMapping::Allocated {
|
|
||||||
offset: host_offset,
|
|
||||||
} => {
|
|
||||||
let mut buf = vec![0u8; count];
|
|
||||||
op.read_bytes_at(buf_offset, &mut buf)
|
|
||||||
.map_err(AsyncIoError::WriteVectored)?;
|
|
||||||
data_file
|
|
||||||
.file()
|
|
||||||
.write_all_at(&buf, host_offset)
|
|
||||||
.map_err(AsyncIoError::WriteVectored)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
buf_offset += count;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reads cluster mappings synchronously into an owned operation, filling
|
|
||||||
/// holes, decompressing, and reading from the backing file as each
|
|
||||||
/// mapping requires.
|
|
||||||
pub(super) fn scatter_read_sync(
|
|
||||||
mappings: Vec<ClusterReadMapping>,
|
|
||||||
op: &mut AsyncIoOperation,
|
|
||||||
data_file: &QcowRawFile,
|
|
||||||
backing_file: &Option<Arc<dyn BackingRead>>,
|
|
||||||
cluster_size: u64,
|
|
||||||
decoder: &dyn Decoder,
|
|
||||||
) -> AsyncIoResult<()> {
|
|
||||||
let mut buf_offset = 0usize;
|
|
||||||
for mapping in mappings {
|
|
||||||
match mapping {
|
|
||||||
ClusterReadMapping::Zero { length } => {
|
|
||||||
op.fill_zeroes_at(buf_offset, length as usize)
|
|
||||||
.map_err(AsyncIoError::ReadVectored)?;
|
|
||||||
buf_offset += length as usize;
|
|
||||||
}
|
|
||||||
ClusterReadMapping::Allocated {
|
|
||||||
offset: host_offset,
|
|
||||||
length,
|
|
||||||
} => {
|
|
||||||
let len = length as usize;
|
|
||||||
let mut buf = vec![0u8; len];
|
|
||||||
data_file
|
|
||||||
.file()
|
|
||||||
.read_exact_at(&mut buf, host_offset)
|
|
||||||
.map_err(AsyncIoError::ReadVectored)?;
|
|
||||||
op.write_bytes_at(buf_offset, &buf)
|
|
||||||
.map_err(AsyncIoError::ReadVectored)?;
|
|
||||||
buf_offset += len;
|
|
||||||
}
|
|
||||||
ClusterReadMapping::Compressed {
|
|
||||||
host_offset,
|
|
||||||
compressed_size,
|
|
||||||
cluster_offset,
|
|
||||||
length,
|
|
||||||
} => {
|
|
||||||
let mut compressed = vec![0u8; compressed_size];
|
|
||||||
data_file
|
|
||||||
.file()
|
|
||||||
.read_exact_at(&mut compressed, host_offset)
|
|
||||||
.map_err(AsyncIoError::ReadVectored)?;
|
|
||||||
let decompressed = decompress_cluster(&compressed, cluster_size as usize, decoder)
|
|
||||||
.map_err(AsyncIoError::ReadVectored)?;
|
|
||||||
op.write_bytes_at(
|
|
||||||
buf_offset,
|
|
||||||
&decompressed[cluster_offset..cluster_offset + length],
|
|
||||||
)
|
|
||||||
.map_err(AsyncIoError::ReadVectored)?;
|
|
||||||
buf_offset += length;
|
|
||||||
}
|
|
||||||
ClusterReadMapping::Backing {
|
|
||||||
offset: backing_offset,
|
|
||||||
length,
|
|
||||||
} => {
|
|
||||||
let mut buf = vec![0u8; length as usize];
|
|
||||||
backing_file
|
|
||||||
.as_ref()
|
|
||||||
.unwrap()
|
|
||||||
.read_at(backing_offset, &mut buf)
|
|
||||||
.map_err(AsyncIoError::ReadVectored)?;
|
|
||||||
op.write_bytes_at(buf_offset, &buf)
|
|
||||||
.map_err(AsyncIoError::ReadVectored)?;
|
|
||||||
buf_offset += length as usize;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
pub(crate) mod unit_tests {
|
|
||||||
use std::fs::File;
|
|
||||||
use std::io::Write;
|
|
||||||
use std::os::unix::fs::FileExt;
|
|
||||||
|
|
||||||
use flate2::Compression;
|
|
||||||
use flate2::write::DeflateEncoder;
|
|
||||||
|
|
||||||
use super::super::decoder::ZlibDecoder;
|
|
||||||
use super::decompress_cluster;
|
|
||||||
|
|
||||||
const COMPRESSED_FLAG: u64 = 1 << 62;
|
|
||||||
const CLUSTER_USED_FLAG: u64 = 1 << 63;
|
|
||||||
const COMPRESSED_SECTOR_SIZE: u64 = 512;
|
|
||||||
|
|
||||||
const HEADER_CLUSTER_BITS_OFFSET: u64 = 20;
|
|
||||||
const HEADER_L1_SIZE_OFFSET: u64 = 36;
|
|
||||||
const HEADER_L1_TABLE_OFFSET: u64 = 40;
|
|
||||||
|
|
||||||
const L1_L2_ADDR_MASK: u64 = 0x00ff_ffff_ffff_fe00;
|
|
||||||
|
|
||||||
fn make_compressed_l2_entry(host_offset: u64, compressed_len: usize, cluster_bits: u32) -> u64 {
|
|
||||||
let compressed_size_shift = 62 - (cluster_bits - 8);
|
|
||||||
let intra_sector_offset = host_offset & (COMPRESSED_SECTOR_SIZE - 1);
|
|
||||||
let total_bytes = compressed_len as u64 + intra_sector_offset;
|
|
||||||
let nsectors = total_bytes.div_ceil(COMPRESSED_SECTOR_SIZE);
|
|
||||||
let addr_part = host_offset & ((1 << compressed_size_shift) - 1);
|
|
||||||
let size_part = (nsectors - 1) << compressed_size_shift;
|
|
||||||
COMPRESSED_FLAG | size_part | addr_part
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Compress every allocated cluster in a QCOW2 image file in place.
|
|
||||||
pub fn compress_allocated_clusters(file: &mut File) {
|
|
||||||
let mut buf4 = [0u8; 4];
|
|
||||||
file.read_exact_at(&mut buf4, HEADER_CLUSTER_BITS_OFFSET)
|
|
||||||
.unwrap();
|
|
||||||
let cluster_bits = u32::from_be_bytes(buf4);
|
|
||||||
let cluster_size = 1u64 << cluster_bits;
|
|
||||||
|
|
||||||
file.read_exact_at(&mut buf4, HEADER_L1_SIZE_OFFSET)
|
|
||||||
.unwrap();
|
|
||||||
let l1_size = u32::from_be_bytes(buf4);
|
|
||||||
|
|
||||||
let mut buf8 = [0u8; 8];
|
|
||||||
file.read_exact_at(&mut buf8, HEADER_L1_TABLE_OFFSET)
|
|
||||||
.unwrap();
|
|
||||||
let l1_table_offset = u64::from_be_bytes(buf8);
|
|
||||||
|
|
||||||
let entries_per_l2 = cluster_size / 8;
|
|
||||||
|
|
||||||
let mut append_offset = file.metadata().unwrap().len();
|
|
||||||
append_offset = (append_offset + 511) & !511;
|
|
||||||
|
|
||||||
for l1_idx in 0..l1_size as u64 {
|
|
||||||
let l1_entry_offset = l1_table_offset + l1_idx * 8;
|
|
||||||
file.read_exact_at(&mut buf8, l1_entry_offset).unwrap();
|
|
||||||
let l1_entry = u64::from_be_bytes(buf8);
|
|
||||||
|
|
||||||
let l2_table_addr = l1_entry & L1_L2_ADDR_MASK;
|
|
||||||
if l2_table_addr == 0 {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
for l2_idx in 0..entries_per_l2 {
|
|
||||||
let l2_entry_offset = l2_table_addr + l2_idx * 8;
|
|
||||||
file.read_exact_at(&mut buf8, l2_entry_offset).unwrap();
|
|
||||||
let l2_entry = u64::from_be_bytes(buf8);
|
|
||||||
|
|
||||||
if l2_entry & CLUSTER_USED_FLAG == 0 || l2_entry & COMPRESSED_FLAG != 0 {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let host_cluster_addr = l2_entry & L1_L2_ADDR_MASK;
|
|
||||||
if host_cluster_addr == 0 {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut cluster_data = vec![0u8; cluster_size as usize];
|
|
||||||
file.read_exact_at(&mut cluster_data, host_cluster_addr)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let mut encoder = DeflateEncoder::new(Vec::new(), Compression::default());
|
|
||||||
encoder.write_all(&cluster_data).unwrap();
|
|
||||||
let compressed = encoder.finish().unwrap();
|
|
||||||
|
|
||||||
file.write_all_at(&compressed, append_offset).unwrap();
|
|
||||||
|
|
||||||
let padded_len = (compressed.len() + 511) & !511;
|
|
||||||
if padded_len > compressed.len() {
|
|
||||||
let padding = vec![0u8; padded_len - compressed.len()];
|
|
||||||
file.write_all_at(&padding, append_offset + compressed.len() as u64)
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
let new_entry =
|
|
||||||
make_compressed_l2_entry(append_offset, compressed.len(), cluster_bits);
|
|
||||||
file.write_all_at(&new_entry.to_be_bytes(), l2_entry_offset)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
append_offset += padded_len as u64;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
file.flush().unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_decompress_cluster() {
|
|
||||||
let cluster_size = 65536;
|
|
||||||
let original: Vec<u8> = (0..=255).cycle().take(cluster_size).collect();
|
|
||||||
|
|
||||||
let mut encoder = DeflateEncoder::new(Vec::new(), Compression::default());
|
|
||||||
encoder.write_all(&original).unwrap();
|
|
||||||
let compressed = encoder.finish().unwrap();
|
|
||||||
|
|
||||||
let result = decompress_cluster(&compressed, cluster_size, &ZlibDecoder {}).unwrap();
|
|
||||||
assert_eq!(result, original);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_decompress_cluster_corrupt_input() {
|
|
||||||
let corrupt = vec![0xffu8; 64];
|
|
||||||
let err = decompress_cluster(&corrupt, 65536, &ZlibDecoder {}).unwrap_err();
|
|
||||||
assert_eq!(err.raw_os_error(), Some(libc::EIO));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
// Copyright 2025 The Cloud Hypervisor Authors. All rights reserved.
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
use std::{io, result};
|
|
||||||
|
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
|
||||||
pub enum Error {
|
|
||||||
#[error("Zlib decompress error")]
|
|
||||||
ZlibDecompress(#[source] flate2::DecompressError),
|
|
||||||
#[error("Zlib unexpected status: {0:?}")]
|
|
||||||
ZlibUnexpectedStatus(flate2::Status),
|
|
||||||
#[error("Zstd decompress error")]
|
|
||||||
ZstdDecompress(#[source] io::Error),
|
|
||||||
#[error("Zstd: failed to fill buffer")]
|
|
||||||
ZstdFillBuffer(#[source] io::Error),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) type Result<T> = result::Result<T, Error>;
|
|
||||||
|
|
||||||
/// Generic trait for decoding zlib/zstd formats
|
|
||||||
pub trait Decoder: Send + Sync {
|
|
||||||
fn decode(&self, input: &[u8], output: &mut [u8]) -> Result<usize>;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub(super) struct ZlibDecoder {}
|
|
||||||
|
|
||||||
impl Decoder for ZlibDecoder {
|
|
||||||
fn decode(&self, input: &[u8], output: &mut [u8]) -> Result<usize> {
|
|
||||||
use flate2::{Decompress, FlushDecompress, Status};
|
|
||||||
|
|
||||||
let mut decompressor = Decompress::new(false);
|
|
||||||
let status = decompressor
|
|
||||||
.decompress(input, output, FlushDecompress::Finish)
|
|
||||||
.map_err(Error::ZlibDecompress)?;
|
|
||||||
if status == Status::StreamEnd {
|
|
||||||
Ok(decompressor.total_out() as usize)
|
|
||||||
} else {
|
|
||||||
Err(Error::ZlibUnexpectedStatus(status))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub(super) struct ZstdDecoder {}
|
|
||||||
|
|
||||||
impl Decoder for ZstdDecoder {
|
|
||||||
fn decode(&self, input: &[u8], output: &mut [u8]) -> Result<usize> {
|
|
||||||
use std::io::Read;
|
|
||||||
|
|
||||||
let mut decoder = zstd::stream::read::Decoder::new(input).map_err(Error::ZstdDecompress)?;
|
|
||||||
let decoded_size = decoder.read(output).map_err(Error::ZstdFillBuffer)?;
|
|
||||||
Ok(decoded_size)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_zlib_decode() {
|
|
||||||
let d = ZlibDecoder::default();
|
|
||||||
let valid_input = vec![99, 96, 100, 98, 6, 0];
|
|
||||||
let mut output1 = vec![0; 4];
|
|
||||||
d.decode(&valid_input, &mut output1).unwrap();
|
|
||||||
assert_eq!(&output1, b"\x00\x01\x02\x03");
|
|
||||||
|
|
||||||
let invalid_input = vec![1, 2, 3, 4];
|
|
||||||
let mut output2 = vec![0; 1024];
|
|
||||||
d.decode(&invalid_input, &mut output2).unwrap_err();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_zstd_decode() {
|
|
||||||
let d = ZstdDecoder::default();
|
|
||||||
let valid_input = vec![40, 181, 47, 253, 32, 2, 17, 0, 0, 1, 254];
|
|
||||||
let mut output1 = vec![0; 2];
|
|
||||||
d.decode(&valid_input, &mut output1).unwrap();
|
|
||||||
assert_eq!(&output1, b"\x01\xfe");
|
|
||||||
|
|
||||||
let invalid_input = vec![1, 2, 3, 4];
|
|
||||||
let mut output2 = vec![0; 1024];
|
|
||||||
d.decode(&invalid_input, &mut output2).unwrap_err();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,910 +0,0 @@
|
|||||||
// Copyright © 2021 Intel Corporation
|
|
||||||
//
|
|
||||||
// Copyright 2026 The Cloud Hypervisor Authors. All rights reserved.
|
|
||||||
//
|
|
||||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
|
||||||
|
|
||||||
//! QCOW2 async disk backend.
|
|
||||||
|
|
||||||
use std::io;
|
|
||||||
use std::os::unix::io::AsRawFd;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
|
||||||
|
|
||||||
use super::common::{cow_write_sync, deallocate_range_result, scatter_read_sync};
|
|
||||||
use super::decoder::Decoder;
|
|
||||||
use super::metadata::{BackingRead, ClusterReadMapping, QcowMetadata};
|
|
||||||
use super::qcow_raw_file::QcowRawFile;
|
|
||||||
use crate::async_io::{
|
|
||||||
AsyncIo, AsyncIoCompletion, AsyncIoError, AsyncIoOperation, AsyncIoResult, UringDataIo,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Per queue QCOW2 I/O worker using io_uring.
|
|
||||||
///
|
|
||||||
/// Reads against fully allocated single mapping clusters are submitted
|
|
||||||
/// to io_uring for true asynchronous completion. All other cluster
|
|
||||||
/// types (zero, compressed, backing) and multi mapping reads fall back
|
|
||||||
/// to synchronous I/O with synthetic completions.
|
|
||||||
///
|
|
||||||
/// Writes are synchronous because metadata allocation must complete
|
|
||||||
/// before the host offset is known.
|
|
||||||
pub(super) struct QcowAsync {
|
|
||||||
metadata: Arc<QcowMetadata>,
|
|
||||||
// Drop before data_file so pending SQEs can be submitted while fd is valid.
|
|
||||||
data_io: UringDataIo,
|
|
||||||
data_file: QcowRawFile,
|
|
||||||
backing_file: Option<Arc<dyn BackingRead>>,
|
|
||||||
sparse: bool,
|
|
||||||
cluster_size: u64,
|
|
||||||
decoder: Arc<dyn Decoder>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl QcowAsync {
|
|
||||||
pub(crate) fn new(
|
|
||||||
metadata: Arc<QcowMetadata>,
|
|
||||||
data_file: QcowRawFile,
|
|
||||||
backing_file: Option<Arc<dyn BackingRead>>,
|
|
||||||
sparse: bool,
|
|
||||||
ring_depth: u32,
|
|
||||||
) -> io::Result<Self> {
|
|
||||||
Ok(QcowAsync {
|
|
||||||
cluster_size: metadata.cluster_size(),
|
|
||||||
decoder: metadata.decoder(),
|
|
||||||
metadata,
|
|
||||||
data_io: UringDataIo::new(ring_depth)?,
|
|
||||||
data_file,
|
|
||||||
backing_file,
|
|
||||||
sparse,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn async_error_result(error: &AsyncIoError) -> i32 {
|
|
||||||
let io_error = match error {
|
|
||||||
AsyncIoError::ReadVectored(e)
|
|
||||||
| AsyncIoError::WriteVectored(e)
|
|
||||||
| AsyncIoError::SubmitBatchRequests(e)
|
|
||||||
| AsyncIoError::Fsync(e)
|
|
||||||
| AsyncIoError::PunchHole(e)
|
|
||||||
| AsyncIoError::WriteZeroes(e) => e,
|
|
||||||
};
|
|
||||||
-io_error.raw_os_error().unwrap_or(libc::EIO)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn inject_operation_completion(&mut self, op: AsyncIoOperation, result: i32) {
|
|
||||||
self.data_io
|
|
||||||
.inject_completion(AsyncIoCompletion::from_operation(op, result));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn prepare_read_operation(
|
|
||||||
&mut self,
|
|
||||||
mut op: AsyncIoOperation,
|
|
||||||
) -> Result<Option<AsyncIoOperation>, Box<(AsyncIoOperation, AsyncIoError)>> {
|
|
||||||
let total_len = op.total_len();
|
|
||||||
let host_offset = match Self::resolve_read(
|
|
||||||
&self.metadata,
|
|
||||||
&self.data_file,
|
|
||||||
&self.backing_file,
|
|
||||||
op.offset() as u64,
|
|
||||||
&mut op,
|
|
||||||
total_len,
|
|
||||||
self.cluster_size,
|
|
||||||
&*self.decoder,
|
|
||||||
) {
|
|
||||||
Ok(host_offset) => host_offset,
|
|
||||||
Err(e) => return Err(Box::new((op, e))),
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(host_offset) = host_offset {
|
|
||||||
op.set_offset(host_offset as libc::off_t);
|
|
||||||
Ok(Some(op))
|
|
||||||
} else {
|
|
||||||
self.inject_operation_completion(op, total_len as i32);
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn complete_write_operation_sync(
|
|
||||||
&mut self,
|
|
||||||
op: AsyncIoOperation,
|
|
||||||
) -> Result<(), Box<(AsyncIoOperation, AsyncIoError)>> {
|
|
||||||
// TODO Make writes async.
|
|
||||||
// Writes are synchronous. Async writes require a multi step
|
|
||||||
// state machine for COW (backing read, cluster allocation, data
|
|
||||||
// write, L2 commit) with per request buffer lifetime tracking
|
|
||||||
// and write ordering.
|
|
||||||
let total_len = op.total_len();
|
|
||||||
if let Err(e) = cow_write_sync(
|
|
||||||
op.offset() as u64,
|
|
||||||
&op,
|
|
||||||
&self.metadata,
|
|
||||||
&self.data_file,
|
|
||||||
&self.backing_file,
|
|
||||||
self.cluster_size,
|
|
||||||
) {
|
|
||||||
return Err(Box::new((op, e)));
|
|
||||||
}
|
|
||||||
|
|
||||||
self.inject_operation_completion(op, total_len as i32);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsyncIo for QcowAsync {
|
|
||||||
fn notifier(&self) -> &EventFd {
|
|
||||||
self.data_io.notifier()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn submit_data_operation(&mut self, op: AsyncIoOperation) -> AsyncIoResult<()> {
|
|
||||||
if op.is_read() {
|
|
||||||
match self.prepare_read_operation(op) {
|
|
||||||
Ok(Some(op)) => {
|
|
||||||
self.data_io
|
|
||||||
.submit_operation(self.data_file.as_raw_fd(), op)
|
|
||||||
.map_err(AsyncIoError::ReadVectored)?;
|
|
||||||
}
|
|
||||||
Ok(None) => {}
|
|
||||||
Err(e) => return Err(e.1),
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
self.complete_write_operation_sync(op).map_err(|e| e.1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fsync(&mut self, user_data: Option<u64>) -> AsyncIoResult<()> {
|
|
||||||
self.metadata.flush().map_err(AsyncIoError::Fsync)?;
|
|
||||||
if let Some(user_data) = user_data {
|
|
||||||
self.data_io
|
|
||||||
.inject_completion(AsyncIoCompletion::new(user_data, 0, None));
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn next_completed_request(&mut self) -> Option<AsyncIoCompletion> {
|
|
||||||
self.data_io.next_completion()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn punch_hole(&mut self, offset: u64, length: u64, user_data: u64) -> AsyncIoResult<()> {
|
|
||||||
let result = deallocate_range_result(
|
|
||||||
&self.metadata,
|
|
||||||
&mut self.data_file,
|
|
||||||
offset,
|
|
||||||
length as usize,
|
|
||||||
self.sparse,
|
|
||||||
false,
|
|
||||||
self.backing_file.as_deref(),
|
|
||||||
);
|
|
||||||
self.data_io
|
|
||||||
.inject_completion(AsyncIoCompletion::new(user_data, result, None));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_zeroes(&mut self, offset: u64, length: u64, user_data: u64) -> AsyncIoResult<()> {
|
|
||||||
let result = deallocate_range_result(
|
|
||||||
&self.metadata,
|
|
||||||
&mut self.data_file,
|
|
||||||
offset,
|
|
||||||
length as usize,
|
|
||||||
self.sparse,
|
|
||||||
true,
|
|
||||||
self.backing_file.as_deref(),
|
|
||||||
);
|
|
||||||
self.data_io
|
|
||||||
.inject_completion(AsyncIoCompletion::new(user_data, result, None));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn batch_requests_enabled(&self) -> bool {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
fn submit_batch_requests(&mut self, batch_request: Vec<AsyncIoOperation>) -> AsyncIoResult<()> {
|
|
||||||
let mut async_reads = Vec::new();
|
|
||||||
|
|
||||||
for op in batch_request {
|
|
||||||
if op.is_read() {
|
|
||||||
match self.prepare_read_operation(op) {
|
|
||||||
Ok(Some(op)) => async_reads.push(op),
|
|
||||||
Ok(None) => {}
|
|
||||||
Err(boxed) => {
|
|
||||||
let (op, e) = *boxed;
|
|
||||||
// The operation was not submitted to the kernel. Accept
|
|
||||||
// it at the qcow layer and surface the failure through
|
|
||||||
// the common completion path so batch acceptance remains
|
|
||||||
// all-or-none for the virtqueue.
|
|
||||||
let result = Self::async_error_result(&e);
|
|
||||||
self.inject_operation_completion(op, result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if let Err(boxed) = self.complete_write_operation_sync(op) {
|
|
||||||
let (op, e) = *boxed;
|
|
||||||
let result = Self::async_error_result(&e);
|
|
||||||
self.inject_operation_completion(op, result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !async_reads.is_empty() {
|
|
||||||
self.data_io
|
|
||||||
.submit_batch(self.data_file.as_raw_fd(), async_reads)
|
|
||||||
.map_err(AsyncIoError::SubmitBatchRequests)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl QcowAsync {
|
|
||||||
/// Resolves read mappings for a guest read request.
|
|
||||||
///
|
|
||||||
/// Returns `Some(host_offset)` if the entire read falls within a single
|
|
||||||
/// allocated cluster (fast path). Otherwise handles the read
|
|
||||||
/// synchronously via `scatter_read_sync` and returns `None`.
|
|
||||||
#[expect(clippy::too_many_arguments)]
|
|
||||||
fn resolve_read(
|
|
||||||
metadata: &QcowMetadata,
|
|
||||||
data_file: &QcowRawFile,
|
|
||||||
backing_file: &Option<Arc<dyn BackingRead>>,
|
|
||||||
address: u64,
|
|
||||||
op: &mut AsyncIoOperation,
|
|
||||||
total_len: usize,
|
|
||||||
cluster_size: u64,
|
|
||||||
decoder: &dyn Decoder,
|
|
||||||
) -> AsyncIoResult<Option<u64>> {
|
|
||||||
let has_backing = backing_file.is_some();
|
|
||||||
let mappings = metadata
|
|
||||||
.map_clusters_for_read(address, total_len, has_backing)
|
|
||||||
.map_err(AsyncIoError::ReadVectored)?;
|
|
||||||
|
|
||||||
// The fast path returns a host offset so the caller can submit a
|
|
||||||
// single io_uring readv with the original iovecs. This only works
|
|
||||||
// without O_DIRECT because it requires I/O
|
|
||||||
// size and file offset to be multiples of the device sector size.
|
|
||||||
// Guest requests can be smaller (e.g. 512 byte UEFI reads on a
|
|
||||||
// 4096 byte sector device), so O_DIRECT reads fall through to the
|
|
||||||
// alignment aware synchronous path instead.
|
|
||||||
if !data_file.file().is_direct()
|
|
||||||
&& mappings.len() == 1
|
|
||||||
&& let ClusterReadMapping::Allocated {
|
|
||||||
offset: host_offset,
|
|
||||||
length,
|
|
||||||
} = &mappings[0]
|
|
||||||
&& *length as usize == total_len
|
|
||||||
{
|
|
||||||
return Ok(Some(*host_offset));
|
|
||||||
}
|
|
||||||
|
|
||||||
scatter_read_sync(mappings, op, data_file, backing_file, cluster_size, decoder)?;
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod unit_tests {
|
|
||||||
use std::io::Write;
|
|
||||||
use std::sync::Arc;
|
|
||||||
use std::{mem, thread};
|
|
||||||
|
|
||||||
use vm_memory::{Bytes, GuestAddress, GuestMemoryMmap};
|
|
||||||
use vmm_sys_util::tempfile::TempFile;
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
use crate::SECTOR_SIZE;
|
|
||||||
use crate::async_io::{AsyncIoCompletion, AsyncIoOperation, GuestMemoryTarget, OwnedIoBuffer};
|
|
||||||
use crate::disk_file::AsyncDiskFile;
|
|
||||||
use crate::formats::qcow::common::unit_tests::compress_allocated_clusters;
|
|
||||||
use crate::formats::qcow::{BackingFileConfig, ImageType, QcowDisk, QcowTempDisk};
|
|
||||||
|
|
||||||
fn create_disk_with_data(
|
|
||||||
file_size: u64,
|
|
||||||
data: &[u8],
|
|
||||||
offset: u64,
|
|
||||||
sparse: bool,
|
|
||||||
) -> (TempFile, QcowDisk) {
|
|
||||||
let temp_file = if data.is_empty() {
|
|
||||||
QcowTempDisk::new(file_size, None, false, sparse, true)
|
|
||||||
.unwrap()
|
|
||||||
.into_tempfile()
|
|
||||||
} else {
|
|
||||||
let tmp_disk = QcowTempDisk::new(file_size, None, false, sparse, true).unwrap();
|
|
||||||
tmp_disk.disk().write_all_at(offset, data);
|
|
||||||
tmp_disk.into_tempfile()
|
|
||||||
};
|
|
||||||
let disk = QcowDisk::new(
|
|
||||||
temp_file.as_file().try_clone().unwrap(),
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
sparse,
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
(temp_file, disk)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_overlay_disk_with_raw_backing_pattern(
|
|
||||||
file_size: u64,
|
|
||||||
value: u8,
|
|
||||||
) -> (TempFile, TempFile, QcowDisk) {
|
|
||||||
let backing_temp = TempFile::new().unwrap();
|
|
||||||
let backing_data = vec![value; file_size as usize];
|
|
||||||
backing_temp.as_file().write_all(&backing_data).unwrap();
|
|
||||||
backing_temp.as_file().sync_all().unwrap();
|
|
||||||
let backing_path = backing_temp.as_path().to_str().unwrap().to_string();
|
|
||||||
|
|
||||||
let backing_config = BackingFileConfig {
|
|
||||||
path: backing_path,
|
|
||||||
format: Some(ImageType::Raw),
|
|
||||||
};
|
|
||||||
let overlay_temp = QcowTempDisk::new(file_size, Some(&backing_config), false, true, false)
|
|
||||||
.unwrap()
|
|
||||||
.into_tempfile();
|
|
||||||
|
|
||||||
let disk = QcowDisk::new(
|
|
||||||
overlay_temp.as_file().try_clone().unwrap(),
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
(backing_temp, overlay_temp, disk)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn wait_for_completion(async_io: &mut dyn AsyncIo) -> AsyncIoCompletion {
|
|
||||||
loop {
|
|
||||||
if let Some(c) = async_io.next_completed_request() {
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
// Block until the eventfd is signaled (io_uring or synthetic).
|
|
||||||
let fd = async_io.notifier().as_raw_fd();
|
|
||||||
let mut val = 0u64;
|
|
||||||
// SAFETY: reading 8 bytes from a valid eventfd.
|
|
||||||
unsafe {
|
|
||||||
libc::read(fd, (&raw mut val).cast(), 8);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn completion_tuple(completion: &AsyncIoCompletion) -> (u64, i32) {
|
|
||||||
(completion.user_data, completion.result)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn async_write(disk: &QcowDisk, offset: u64, data: &[u8]) {
|
|
||||||
let mut async_io = disk.create_async_io(1).unwrap();
|
|
||||||
async_io
|
|
||||||
.write_from_vec(
|
|
||||||
offset as libc::off_t,
|
|
||||||
OwnedIoBuffer::from_vec(data.to_vec()),
|
|
||||||
2,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
let completion = wait_for_completion(async_io.as_mut());
|
|
||||||
let (user_data, result) = completion_tuple(&completion);
|
|
||||||
assert_eq!(user_data, 2);
|
|
||||||
assert_eq!(
|
|
||||||
result as usize,
|
|
||||||
data.len(),
|
|
||||||
"write should return requested length"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn async_read(disk: &QcowDisk, offset: u64, len: usize) -> Vec<u8> {
|
|
||||||
let mut async_io = disk.create_async_io(1).unwrap();
|
|
||||||
async_io
|
|
||||||
.read_to_vec(
|
|
||||||
offset as libc::off_t,
|
|
||||||
OwnedIoBuffer::from_vec(vec![0xFF; len]),
|
|
||||||
1,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
let mut completion = wait_for_completion(async_io.as_mut());
|
|
||||||
let (user_data, result) = completion_tuple(&completion);
|
|
||||||
assert_eq!(user_data, 1);
|
|
||||||
assert_eq!(result as usize, len, "read should return requested length");
|
|
||||||
match completion.buffer.take() {
|
|
||||||
Some(buffer) => buffer.as_slice().to_vec(),
|
|
||||||
other => panic!("unexpected read completion: {other:?}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_qcow_async_punch_hole_completion() {
|
|
||||||
let data = vec![0xDD; 128 * 1024];
|
|
||||||
let offset = 0u64;
|
|
||||||
let (_temp, disk) = create_disk_with_data(100 * 1024 * 1024, &data, offset, true);
|
|
||||||
|
|
||||||
let mut async_io = disk.create_async_io(1).unwrap();
|
|
||||||
async_io.punch_hole(offset, data.len() as u64, 100).unwrap();
|
|
||||||
let completion = async_io.next_completed_request().unwrap();
|
|
||||||
let (user_data, result) = completion_tuple(&completion);
|
|
||||||
assert_eq!(user_data, 100);
|
|
||||||
assert_eq!(result, 0, "punch_hole should succeed");
|
|
||||||
drop(async_io);
|
|
||||||
|
|
||||||
let read_buf = async_read(&disk, offset, data.len());
|
|
||||||
assert!(
|
|
||||||
read_buf.iter().all(|&b| b == 0),
|
|
||||||
"Punched hole should read as zeros"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_qcow_async_write_zeroes_completion() {
|
|
||||||
let data = vec![0xAA; 128 * 1024];
|
|
||||||
let offset = 0u64;
|
|
||||||
let (_temp, disk) = create_disk_with_data(100 * 1024 * 1024, &data, offset, true);
|
|
||||||
|
|
||||||
let mut async_io = disk.create_async_io(1).unwrap();
|
|
||||||
async_io
|
|
||||||
.write_zeroes(offset, data.len() as u64, 200)
|
|
||||||
.unwrap();
|
|
||||||
let completion = async_io.next_completed_request().unwrap();
|
|
||||||
let (user_data, result) = completion_tuple(&completion);
|
|
||||||
assert_eq!(user_data, 200);
|
|
||||||
assert_eq!(result, 0, "write_zeroes should succeed");
|
|
||||||
drop(async_io);
|
|
||||||
|
|
||||||
let read_buf = async_read(&disk, offset, data.len());
|
|
||||||
assert!(
|
|
||||||
read_buf.iter().all(|&b| b == 0),
|
|
||||||
"Write zeroes region should read as zeros"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_qcow_async_write_zeroes_unallocated_overlay_with_backing_must_read_zero() {
|
|
||||||
let cluster_size = 1u64 << 16;
|
|
||||||
let file_size = cluster_size * 4;
|
|
||||||
let offset = cluster_size;
|
|
||||||
let (_backing_temp, _overlay_temp, disk) =
|
|
||||||
create_overlay_disk_with_raw_backing_pattern(file_size, 0xAB);
|
|
||||||
|
|
||||||
let mut async_io = disk.create_async_io(1).unwrap();
|
|
||||||
async_io.write_zeroes(offset, cluster_size, 201).unwrap();
|
|
||||||
let completion = wait_for_completion(async_io.as_mut());
|
|
||||||
let (user_data, result) = completion_tuple(&completion);
|
|
||||||
assert_eq!(user_data, 201);
|
|
||||||
assert_eq!(result, 0, "write_zeroes should succeed");
|
|
||||||
drop(async_io);
|
|
||||||
|
|
||||||
let read_buf = async_read(&disk, offset, cluster_size as usize);
|
|
||||||
assert!(
|
|
||||||
read_buf.iter().all(|&b| b == 0),
|
|
||||||
"zeroed unallocated overlay cluster exposed backing data"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_qcow_async_write_read_roundtrip() {
|
|
||||||
let file_size = 100 * 1024 * 1024;
|
|
||||||
let temp_file = QcowTempDisk::new(file_size, None, false, true, false)
|
|
||||||
.unwrap()
|
|
||||||
.into_tempfile();
|
|
||||||
let disk = QcowDisk::new(
|
|
||||||
temp_file.as_file().try_clone().unwrap(),
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let pattern: Vec<u8> = (0..128 * 1024).map(|i| (i % 251) as u8).collect();
|
|
||||||
let offset = 64 * 1024;
|
|
||||||
|
|
||||||
async_write(&disk, offset, &pattern);
|
|
||||||
let read_buf = async_read(&disk, offset, pattern.len());
|
|
||||||
assert_eq!(read_buf, pattern, "read should match written data");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_qcow_async_read_spanning_cluster_boundary() {
|
|
||||||
let cluster_size: u64 = 65536;
|
|
||||||
let file_size = 100 * 1024 * 1024;
|
|
||||||
|
|
||||||
// Write distinct patterns into two adjacent clusters.
|
|
||||||
let pattern_a = vec![0xAA; cluster_size as usize];
|
|
||||||
let pattern_b = vec![0xBB; cluster_size as usize];
|
|
||||||
let (_temp, disk) = create_disk_with_data(file_size, &pattern_a, 0, true);
|
|
||||||
async_write(&disk, cluster_size, &pattern_b);
|
|
||||||
|
|
||||||
// Read across the boundary: last 4K of cluster 0 + first 4K of cluster 1.
|
|
||||||
let read_offset = cluster_size - 4096;
|
|
||||||
let read_len = 8192;
|
|
||||||
let buf = async_read(&disk, read_offset, read_len);
|
|
||||||
|
|
||||||
assert!(
|
|
||||||
buf[..4096].iter().all(|&b| b == 0xAA),
|
|
||||||
"first half should come from cluster 0"
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
buf[4096..].iter().all(|&b| b == 0xBB),
|
|
||||||
"second half should come from cluster 1"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_qcow_async_sync_read_to_guest_memory() {
|
|
||||||
let cluster_size = 65536usize;
|
|
||||||
let file_size = 100 * 1024 * 1024;
|
|
||||||
let data: Vec<u8> = (0..cluster_size * 2).map(|i| (i % 251) as u8).collect();
|
|
||||||
let (_temp, disk) = create_disk_with_data(file_size, &data, 0, true);
|
|
||||||
let mem = Arc::new(
|
|
||||||
GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0x1000), 0x4000)]).unwrap(),
|
|
||||||
);
|
|
||||||
let ranges = [(GuestAddress(0x1000), 2048), (GuestAddress(0x2000), 2048)];
|
|
||||||
let target = GuestMemoryTarget::new(Arc::clone(&mem), &ranges).unwrap();
|
|
||||||
let read_offset = cluster_size as u64 - 2048;
|
|
||||||
|
|
||||||
let mut async_io = disk.create_async_io(1).unwrap();
|
|
||||||
async_io
|
|
||||||
.read_to_memory(read_offset as libc::off_t, target, 55)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let completion = wait_for_completion(async_io.as_mut());
|
|
||||||
assert_eq!(completion_tuple(&completion), (55, 4096));
|
|
||||||
assert!(completion.buffer.is_none());
|
|
||||||
|
|
||||||
let mut first = vec![0u8; 2048];
|
|
||||||
let mut second = vec![0u8; 2048];
|
|
||||||
mem.read_slice(&mut first, GuestAddress(0x1000)).unwrap();
|
|
||||||
mem.read_slice(&mut second, GuestAddress(0x2000)).unwrap();
|
|
||||||
|
|
||||||
let expected = &data[read_offset as usize..read_offset as usize + 4096];
|
|
||||||
assert_eq!(&first[..], &expected[..2048]);
|
|
||||||
assert_eq!(&second[..], &expected[2048..]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_qcow_async_sync_write_from_guest_memory() {
|
|
||||||
let file_size = 100 * 1024 * 1024;
|
|
||||||
let temp_file = QcowTempDisk::new(file_size, None, false, true, false)
|
|
||||||
.unwrap()
|
|
||||||
.into_tempfile();
|
|
||||||
let disk = QcowDisk::new(
|
|
||||||
temp_file.as_file().try_clone().unwrap(),
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
let mem = Arc::new(
|
|
||||||
GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0x1000), 0x4000)]).unwrap(),
|
|
||||||
);
|
|
||||||
let first = vec![0x5a; 2048];
|
|
||||||
let second = vec![0xc3; 2048];
|
|
||||||
mem.write_slice(&first, GuestAddress(0x1000)).unwrap();
|
|
||||||
mem.write_slice(&second, GuestAddress(0x2000)).unwrap();
|
|
||||||
let ranges = [(GuestAddress(0x1000), 2048), (GuestAddress(0x2000), 2048)];
|
|
||||||
let target = GuestMemoryTarget::new(Arc::clone(&mem), &ranges).unwrap();
|
|
||||||
|
|
||||||
let mut async_io = disk.create_async_io(1).unwrap();
|
|
||||||
async_io.write_from_memory(4096, target, 56).unwrap();
|
|
||||||
let completion = wait_for_completion(async_io.as_mut());
|
|
||||||
assert_eq!(completion_tuple(&completion), (56, 4096));
|
|
||||||
drop(async_io);
|
|
||||||
|
|
||||||
let mut expected = first;
|
|
||||||
expected.extend_from_slice(&second);
|
|
||||||
let read_buf = async_read(&disk, 4096, expected.len());
|
|
||||||
assert_eq!(read_buf, expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_qcow_async_batch_mixed_requests() {
|
|
||||||
let file_size = 100 * 1024 * 1024;
|
|
||||||
let temp_file = QcowTempDisk::new(file_size, None, false, true, false)
|
|
||||||
.unwrap()
|
|
||||||
.into_tempfile();
|
|
||||||
let disk = QcowDisk::new(
|
|
||||||
temp_file.as_file().try_clone().unwrap(),
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let mut async_io = disk.create_async_io(8).unwrap();
|
|
||||||
|
|
||||||
// Prepare write data for two regions.
|
|
||||||
let write_a = vec![0xAA; 4096];
|
|
||||||
let write_b = vec![0xBB; 4096];
|
|
||||||
let offset_a: u64 = 0;
|
|
||||||
let offset_b: u64 = 65536;
|
|
||||||
|
|
||||||
let batch = vec![
|
|
||||||
AsyncIoOperation::write_from_vec(
|
|
||||||
offset_a as libc::off_t,
|
|
||||||
OwnedIoBuffer::from_vec(write_a.clone()),
|
|
||||||
10,
|
|
||||||
),
|
|
||||||
AsyncIoOperation::write_from_vec(
|
|
||||||
offset_b as libc::off_t,
|
|
||||||
OwnedIoBuffer::from_vec(write_b.clone()),
|
|
||||||
20,
|
|
||||||
),
|
|
||||||
];
|
|
||||||
|
|
||||||
async_io.submit_batch_requests(batch).unwrap();
|
|
||||||
|
|
||||||
let mut completions = [
|
|
||||||
completion_tuple(&wait_for_completion(async_io.as_mut())),
|
|
||||||
completion_tuple(&wait_for_completion(async_io.as_mut())),
|
|
||||||
];
|
|
||||||
completions.sort_by_key(|c| c.0);
|
|
||||||
assert_eq!(completions[0], (10, 4096));
|
|
||||||
assert_eq!(completions[1], (20, 4096));
|
|
||||||
drop(async_io);
|
|
||||||
|
|
||||||
// Batch read both regions back.
|
|
||||||
let mut async_io = disk.create_async_io(8).unwrap();
|
|
||||||
let read_batch = vec![
|
|
||||||
AsyncIoOperation::read_to_vec(
|
|
||||||
offset_a as libc::off_t,
|
|
||||||
OwnedIoBuffer::from_vec(vec![0; 4096]),
|
|
||||||
30,
|
|
||||||
),
|
|
||||||
AsyncIoOperation::read_to_vec(
|
|
||||||
offset_b as libc::off_t,
|
|
||||||
OwnedIoBuffer::from_vec(vec![0; 4096]),
|
|
||||||
40,
|
|
||||||
),
|
|
||||||
];
|
|
||||||
|
|
||||||
async_io.submit_batch_requests(read_batch).unwrap();
|
|
||||||
|
|
||||||
let mut completion_a = wait_for_completion(async_io.as_mut());
|
|
||||||
let mut completion_b = wait_for_completion(async_io.as_mut());
|
|
||||||
if completion_a.user_data > completion_b.user_data {
|
|
||||||
mem::swap(&mut completion_a, &mut completion_b);
|
|
||||||
}
|
|
||||||
assert_eq!(completion_tuple(&completion_a), (30, 4096));
|
|
||||||
assert_eq!(completion_tuple(&completion_b), (40, 4096));
|
|
||||||
|
|
||||||
let read_a = match completion_a.buffer.take() {
|
|
||||||
Some(buffer) => buffer.as_slice().to_vec(),
|
|
||||||
other => panic!("unexpected read completion A: {other:?}"),
|
|
||||||
};
|
|
||||||
let read_b = match completion_b.buffer.take() {
|
|
||||||
Some(buffer) => buffer.as_slice().to_vec(),
|
|
||||||
other => panic!("unexpected read completion B: {other:?}"),
|
|
||||||
};
|
|
||||||
assert_eq!(read_a, write_a, "batch read A should match written data");
|
|
||||||
assert_eq!(read_b, write_b, "batch read B should match written data");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_qcow_async_read_unallocated() {
|
|
||||||
let file_size = 100 * 1024 * 1024;
|
|
||||||
let temp_file = QcowTempDisk::new(file_size, None, false, true, false)
|
|
||||||
.unwrap()
|
|
||||||
.into_tempfile();
|
|
||||||
let disk = QcowDisk::new(
|
|
||||||
temp_file.as_file().try_clone().unwrap(),
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let buf = async_read(&disk, 0, 128 * 1024);
|
|
||||||
assert!(
|
|
||||||
buf.iter().all(|&b| b == 0),
|
|
||||||
"unallocated region should read as zeroes"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_qcow_async_sub_cluster_write() {
|
|
||||||
let cluster_size = 65536usize;
|
|
||||||
let file_size = 100 * 1024 * 1024;
|
|
||||||
let temp_file = QcowTempDisk::new(file_size, None, false, true, false)
|
|
||||||
.unwrap()
|
|
||||||
.into_tempfile();
|
|
||||||
let disk = QcowDisk::new(
|
|
||||||
temp_file.as_file().try_clone().unwrap(),
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Write 4K into the middle of a cluster.
|
|
||||||
let write_offset = 4096u64;
|
|
||||||
let write_len = 4096;
|
|
||||||
let pattern = vec![0xCC; write_len];
|
|
||||||
async_write(&disk, write_offset, &pattern);
|
|
||||||
|
|
||||||
// Read the entire cluster back.
|
|
||||||
let buf = async_read(&disk, 0, cluster_size);
|
|
||||||
|
|
||||||
assert!(
|
|
||||||
buf[..write_offset as usize].iter().all(|&b| b == 0),
|
|
||||||
"bytes before the write should be zero"
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
&buf[write_offset as usize..write_offset as usize + write_len],
|
|
||||||
&pattern[..],
|
|
||||||
"written region should match"
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
buf[write_offset as usize + write_len..]
|
|
||||||
.iter()
|
|
||||||
.all(|&b| b == 0),
|
|
||||||
"bytes after the write should be zero"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_qcow_async_write_after_punch_hole() {
|
|
||||||
let data = vec![0xAA; 64 * 1024];
|
|
||||||
let offset = 0u64;
|
|
||||||
let (_temp, disk) = create_disk_with_data(100 * 1024 * 1024, &data, offset, true);
|
|
||||||
|
|
||||||
let buf = async_read(&disk, offset, data.len());
|
|
||||||
assert!(buf.iter().all(|&b| b == 0xAA));
|
|
||||||
|
|
||||||
let mut async_io = disk.create_async_io(1).unwrap();
|
|
||||||
async_io.punch_hole(offset, data.len() as u64, 10).unwrap();
|
|
||||||
let result = wait_for_completion(async_io.as_mut()).result;
|
|
||||||
assert_eq!(result, 0);
|
|
||||||
drop(async_io);
|
|
||||||
|
|
||||||
let buf = async_read(&disk, offset, data.len());
|
|
||||||
assert!(
|
|
||||||
buf.iter().all(|&b| b == 0),
|
|
||||||
"should be zero after punch hole"
|
|
||||||
);
|
|
||||||
|
|
||||||
let new_data = vec![0xBB; 64 * 1024];
|
|
||||||
async_write(&disk, offset, &new_data);
|
|
||||||
|
|
||||||
let buf = async_read(&disk, offset, new_data.len());
|
|
||||||
assert_eq!(buf, new_data, "should read new data after rewrite");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_qcow_async_large_sequential_io() {
|
|
||||||
let cluster_size = 64 * 1024;
|
|
||||||
let num_clusters = 8;
|
|
||||||
let total_len = cluster_size * num_clusters;
|
|
||||||
let offset = 0u64;
|
|
||||||
|
|
||||||
let mut data = vec![0u8; total_len];
|
|
||||||
for (i, chunk) in data.chunks_mut(cluster_size).enumerate() {
|
|
||||||
chunk.fill((i + 1) as u8);
|
|
||||||
}
|
|
||||||
|
|
||||||
let (_temp, disk) = create_disk_with_data(100 * 1024 * 1024, &data, offset, true);
|
|
||||||
|
|
||||||
let buf = async_read(&disk, offset, total_len);
|
|
||||||
assert_eq!(buf.len(), total_len);
|
|
||||||
for (i, chunk) in buf.chunks(cluster_size).enumerate() {
|
|
||||||
assert!(
|
|
||||||
chunk.iter().all(|&b| b == (i + 1) as u8),
|
|
||||||
"cluster {i} mismatch"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_qcow_async_alignment_without_direct_io() {
|
|
||||||
let file_size = 100 * 1024 * 1024;
|
|
||||||
let temp_file = QcowTempDisk::new(file_size, None, false, true, false)
|
|
||||||
.unwrap()
|
|
||||||
.into_tempfile();
|
|
||||||
let disk = QcowDisk::new(
|
|
||||||
temp_file.as_file().try_clone().unwrap(),
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
let async_io = disk.create_async_io(1).unwrap();
|
|
||||||
assert_eq!(async_io.alignment(), SECTOR_SIZE);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_qcow_async_alignment_with_direct_io() {
|
|
||||||
let tmp_disk = match QcowTempDisk::new(100 * 1024 * 1024, None, true, true, true) {
|
|
||||||
Ok(d) => d,
|
|
||||||
Err(_) => {
|
|
||||||
eprintln!("skipping: O_DIRECT not supported on this filesystem");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let async_io = tmp_disk.disk().create_async_io(1).unwrap();
|
|
||||||
assert!(async_io.alignment() >= SECTOR_SIZE);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_qcow_async_sub_sector_read_with_direct_io() {
|
|
||||||
let tmp_disk = match QcowTempDisk::new(100 * 1024 * 1024, None, true, true, true) {
|
|
||||||
Ok(d) => d,
|
|
||||||
Err(_) => {
|
|
||||||
eprintln!("skipping: O_DIRECT not supported on this filesystem");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let pattern = vec![0xAB; 65536];
|
|
||||||
async_write(tmp_disk.disk(), 0, &pattern);
|
|
||||||
|
|
||||||
let buf = async_read(tmp_disk.disk(), 0, 512);
|
|
||||||
assert!(
|
|
||||||
buf.iter().all(|&b| b == 0xAB),
|
|
||||||
"sub-sector O_DIRECT read should return written data"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_qcow_async_direct_io_write_read_roundtrip() {
|
|
||||||
let tmp_disk = match QcowTempDisk::new(100 * 1024 * 1024, None, true, true, true) {
|
|
||||||
Ok(d) => d,
|
|
||||||
Err(_) => {
|
|
||||||
eprintln!("skipping: O_DIRECT not supported on this filesystem");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let pattern: Vec<u8> = (0..128 * 1024).map(|i| (i % 251) as u8).collect();
|
|
||||||
async_write(tmp_disk.disk(), 0, &pattern);
|
|
||||||
|
|
||||||
let buf = async_read(tmp_disk.disk(), 0, pattern.len());
|
|
||||||
assert_eq!(buf, pattern, "O_DIRECT roundtrip should match");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_compressed_read_multi_queue() {
|
|
||||||
let cluster_size = 65536usize;
|
|
||||||
let data: Vec<u8> = (0..=255).cycle().take(cluster_size).collect();
|
|
||||||
let (temp, disk) = create_disk_with_data(100 * 1024 * 1024, &data, 0, false);
|
|
||||||
drop(disk);
|
|
||||||
|
|
||||||
compress_allocated_clusters(&mut temp.as_file().try_clone().unwrap());
|
|
||||||
|
|
||||||
let disk = Arc::new(
|
|
||||||
QcowDisk::new(
|
|
||||||
temp.as_file().try_clone().unwrap(),
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
.unwrap(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let handles: Vec<_> = (0..4)
|
|
||||||
.map(|_| {
|
|
||||||
let disk = Arc::clone(&disk);
|
|
||||||
let expected = data.clone();
|
|
||||||
thread::spawn(move || {
|
|
||||||
let mut async_io = disk.create_async_io(1).unwrap();
|
|
||||||
async_io
|
|
||||||
.read_to_vec(0, OwnedIoBuffer::from_vec(vec![0xFF; cluster_size]), 1)
|
|
||||||
.unwrap();
|
|
||||||
let mut completion = wait_for_completion(async_io.as_mut());
|
|
||||||
let result = completion.result;
|
|
||||||
assert_eq!(result as usize, cluster_size);
|
|
||||||
let buf = match completion.buffer.take() {
|
|
||||||
Some(buffer) => buffer.as_slice().to_vec(),
|
|
||||||
other => panic!("unexpected read completion: {other:?}"),
|
|
||||||
};
|
|
||||||
assert_eq!(buf, expected);
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
for h in handles {
|
|
||||||
h.join().unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,700 +0,0 @@
|
|||||||
// Copyright 2018 The Chromium OS Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
|
||||||
// found in the LICENSE-BSD-3-Clause file.
|
|
||||||
//
|
|
||||||
// Copyright 2026 The Cloud Hypervisor Authors. All rights reserved.
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
|
||||||
|
|
||||||
//! QCOW2 header parsing, validation, and creation.
|
|
||||||
|
|
||||||
use std::fmt::{Display, Formatter, Result as FmtResult};
|
|
||||||
use std::os::unix::fs::FileExt;
|
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
use bitflags::bitflags;
|
|
||||||
use vmm_sys_util::file_traits::FileSync;
|
|
||||||
use zerocopy::big_endian::{U32 as BeU32, U64 as BeU64};
|
|
||||||
use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
|
|
||||||
|
|
||||||
use super::decoder::{Decoder, ZlibDecoder, ZstdDecoder};
|
|
||||||
use super::parser::{Error, Result};
|
|
||||||
use super::util::{div_round_up_u32, div_round_up_u64};
|
|
||||||
use crate::aligned_file::AlignedFile;
|
|
||||||
use crate::error::{BlockError, BlockErrorKind, BlockResult};
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub enum ImageType {
|
|
||||||
Raw,
|
|
||||||
Qcow2,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for ImageType {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
|
|
||||||
match self {
|
|
||||||
ImageType::Raw => write!(f, "raw"),
|
|
||||||
ImageType::Qcow2 => write!(f, "qcow2"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for ImageType {
|
|
||||||
type Err = Error;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self> {
|
|
||||||
match s {
|
|
||||||
"raw" => Ok(ImageType::Raw),
|
|
||||||
"qcow2" => Ok(ImageType::Qcow2),
|
|
||||||
_ => Err(Error::UnsupportedBackingFileFormat(s.to_string())),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub enum CompressionType {
|
|
||||||
Zlib,
|
|
||||||
Zstd,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct BackingFileConfig {
|
|
||||||
pub path: String,
|
|
||||||
// If this is None, we will autodetect it.
|
|
||||||
pub format: Option<ImageType>,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Maximum data size supported.
|
|
||||||
pub(super) const MAX_QCOW_FILE_SIZE: u64 = 0x01 << 44; // 16 TB.
|
|
||||||
|
|
||||||
// QCOW magic constant that starts the header.
|
|
||||||
pub(super) const QCOW_MAGIC: u32 = 0x5146_49fb;
|
|
||||||
// Default to a cluster size of 2^DEFAULT_CLUSTER_BITS
|
|
||||||
pub(super) const DEFAULT_CLUSTER_BITS: u32 = 16;
|
|
||||||
// Limit clusters to reasonable sizes. Choose the same limits as qemu. Making the clusters smaller
|
|
||||||
// increases the amount of overhead for book keeping.
|
|
||||||
pub(super) const MIN_CLUSTER_BITS: u32 = 9;
|
|
||||||
pub(super) const MAX_CLUSTER_BITS: u32 = 21;
|
|
||||||
// The L1 and RefCount table are kept in RAM, only handle files that require less than 35M entries.
|
|
||||||
// This easily covers 1 TB files. When support for bigger files is needed the assumptions made to
|
|
||||||
// keep these tables in RAM needs to be thrown out.
|
|
||||||
pub(super) const MAX_RAM_POINTER_TABLE_SIZE: u64 = 35_000_000;
|
|
||||||
// 16-bit refcounts.
|
|
||||||
pub(super) const DEFAULT_REFCOUNT_ORDER: u32 = 4;
|
|
||||||
|
|
||||||
pub(super) const V2_BARE_HEADER_SIZE: u32 = 72;
|
|
||||||
pub(super) const V3_BARE_HEADER_SIZE: u32 = 104;
|
|
||||||
pub(super) const AUTOCLEAR_FEATURES_OFFSET: u64 = 88;
|
|
||||||
|
|
||||||
pub(super) const COMPATIBLE_FEATURES_LAZY_REFCOUNTS: u64 = 1;
|
|
||||||
|
|
||||||
// Compression types as defined in https://www.qemu.org/docs/master/interop/qcow2.html
|
|
||||||
const COMPRESSION_TYPE_ZLIB: u64 = 0; // zlib/deflate <https://www.ietf.org/rfc/rfc1951.txt>
|
|
||||||
const COMPRESSION_TYPE_ZSTD: u64 = 1; // zstd <http://github.com/facebook/zstd>
|
|
||||||
|
|
||||||
// Header extension types
|
|
||||||
pub(super) const HEADER_EXT_END: u32 = 0x00000000;
|
|
||||||
// Backing file format name (raw, qcow2)
|
|
||||||
pub(super) const HEADER_EXT_BACKING_FORMAT: u32 = 0xe2792aca;
|
|
||||||
// Feature name table
|
|
||||||
const HEADER_EXT_FEATURE_NAME_TABLE: u32 = 0x6803f857;
|
|
||||||
|
|
||||||
// Feature name table entry type incompatible
|
|
||||||
const FEAT_TYPE_INCOMPATIBLE: u8 = 0;
|
|
||||||
|
|
||||||
bitflags! {
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
pub struct IncompatFeatures: u64 {
|
|
||||||
const DIRTY = 1 << 0;
|
|
||||||
const CORRUPT = 1 << 1;
|
|
||||||
const DATA_FILE = 1 << 2;
|
|
||||||
const COMPRESSION = 1 << 3;
|
|
||||||
const EXTENDED_L2 = 1 << 4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IncompatFeatures {
|
|
||||||
/// Features supported by this implementation.
|
|
||||||
pub(super) const SUPPORTED: IncompatFeatures = IncompatFeatures::DIRTY
|
|
||||||
.union(IncompatFeatures::CORRUPT)
|
|
||||||
.union(IncompatFeatures::COMPRESSION);
|
|
||||||
|
|
||||||
/// Get the fallback name for a known feature bit.
|
|
||||||
fn flag_name(bit: u8) -> Option<&'static str> {
|
|
||||||
Some(match Self::from_bits_truncate(1u64 << bit) {
|
|
||||||
Self::DIRTY => "dirty bit",
|
|
||||||
Self::CORRUPT => "corrupt bit",
|
|
||||||
Self::DATA_FILE => "external data file",
|
|
||||||
Self::EXTENDED_L2 => "extended L2 entries",
|
|
||||||
_ => return None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Error type for unsupported incompatible features.
|
|
||||||
#[derive(Debug, Clone, thiserror::Error)]
|
|
||||||
pub struct MissingFeatureError {
|
|
||||||
/// Unsupported feature bits.
|
|
||||||
features: IncompatFeatures,
|
|
||||||
/// Feature name table from the qcow2 image.
|
|
||||||
feature_names: Vec<(u8, String)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MissingFeatureError {
|
|
||||||
pub(super) fn new(features: IncompatFeatures, feature_names: Vec<(u8, String)>) -> Self {
|
|
||||||
Self {
|
|
||||||
features,
|
|
||||||
feature_names,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for MissingFeatureError {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
|
|
||||||
let names: Vec<String> = (0u8..64)
|
|
||||||
.filter(|&bit| self.features.bits() & (1u64 << bit) != 0)
|
|
||||||
.map(|bit| {
|
|
||||||
// First try the image's feature name table
|
|
||||||
self.feature_names
|
|
||||||
.iter()
|
|
||||||
.find(|(b, _)| *b == bit)
|
|
||||||
.map(|(_, name)| name.clone())
|
|
||||||
// Then try hardcoded fallback names
|
|
||||||
.or_else(|| IncompatFeatures::flag_name(bit).map(|s| s.to_string()))
|
|
||||||
// Finally, use generic description
|
|
||||||
.unwrap_or_else(|| format!("unknown feature bit {bit}"))
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
write!(f, "Missing features: {}", names.join(", "))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The format supports a "header extension area", that crosvm does not use.
|
|
||||||
const QCOW_EMPTY_HEADER_EXTENSION_SIZE: u32 = 8;
|
|
||||||
|
|
||||||
// Defined by the specification
|
|
||||||
const MAX_BACKING_FILE_SIZE: u32 = 1023;
|
|
||||||
|
|
||||||
/// Contains the information from the header of a qcow file.
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct QcowHeader {
|
|
||||||
pub magic: u32,
|
|
||||||
pub version: u32,
|
|
||||||
|
|
||||||
pub backing_file_offset: u64,
|
|
||||||
pub backing_file_size: u32,
|
|
||||||
|
|
||||||
pub cluster_bits: u32,
|
|
||||||
pub size: u64,
|
|
||||||
pub crypt_method: u32,
|
|
||||||
|
|
||||||
pub l1_size: u32,
|
|
||||||
pub l1_table_offset: u64,
|
|
||||||
|
|
||||||
pub refcount_table_offset: u64,
|
|
||||||
pub refcount_table_clusters: u32,
|
|
||||||
|
|
||||||
pub nb_snapshots: u32,
|
|
||||||
pub snapshots_offset: u64,
|
|
||||||
|
|
||||||
// v3 entries
|
|
||||||
pub incompatible_features: u64,
|
|
||||||
pub compatible_features: u64,
|
|
||||||
pub autoclear_features: u64,
|
|
||||||
pub refcount_order: u32,
|
|
||||||
pub header_size: u32,
|
|
||||||
pub compression_type: CompressionType,
|
|
||||||
|
|
||||||
// Post-header entries
|
|
||||||
pub backing_file: Option<BackingFileConfig>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// On-disk layout of the bare qcow2 header shared by v2 and v3 (72 bytes).
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(FromBytes, IntoBytes, KnownLayout, Immutable)]
|
|
||||||
struct RawHeaderV2 {
|
|
||||||
magic: BeU32,
|
|
||||||
version: BeU32,
|
|
||||||
backing_file_offset: BeU64,
|
|
||||||
backing_file_size: BeU32,
|
|
||||||
cluster_bits: BeU32,
|
|
||||||
size: BeU64,
|
|
||||||
crypt_method: BeU32,
|
|
||||||
l1_size: BeU32,
|
|
||||||
l1_table_offset: BeU64,
|
|
||||||
refcount_table_offset: BeU64,
|
|
||||||
refcount_table_clusters: BeU32,
|
|
||||||
nb_snapshots: BeU32,
|
|
||||||
snapshots_offset: BeU64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RawHeaderV2 {
|
|
||||||
fn from_header(header: &QcowHeader) -> Self {
|
|
||||||
Self {
|
|
||||||
magic: BeU32::new(header.magic),
|
|
||||||
version: BeU32::new(header.version),
|
|
||||||
backing_file_offset: BeU64::new(header.backing_file_offset),
|
|
||||||
backing_file_size: BeU32::new(header.backing_file_size),
|
|
||||||
cluster_bits: BeU32::new(header.cluster_bits),
|
|
||||||
size: BeU64::new(header.size),
|
|
||||||
crypt_method: BeU32::new(header.crypt_method),
|
|
||||||
l1_size: BeU32::new(header.l1_size),
|
|
||||||
l1_table_offset: BeU64::new(header.l1_table_offset),
|
|
||||||
refcount_table_offset: BeU64::new(header.refcount_table_offset),
|
|
||||||
refcount_table_clusters: BeU32::new(header.refcount_table_clusters),
|
|
||||||
nb_snapshots: BeU32::new(header.nb_snapshots),
|
|
||||||
snapshots_offset: BeU64::new(header.snapshots_offset),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// On-disk layout of the fields v3 adds after the bare header (32 bytes).
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(FromBytes, IntoBytes, KnownLayout, Immutable)]
|
|
||||||
struct RawHeaderV3Tail {
|
|
||||||
incompatible_features: BeU64,
|
|
||||||
compatible_features: BeU64,
|
|
||||||
autoclear_features: BeU64,
|
|
||||||
refcount_order: BeU32,
|
|
||||||
header_size: BeU32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RawHeaderV3Tail {
|
|
||||||
fn from_header(header: &QcowHeader) -> Self {
|
|
||||||
Self {
|
|
||||||
incompatible_features: BeU64::new(header.incompatible_features),
|
|
||||||
compatible_features: BeU64::new(header.compatible_features),
|
|
||||||
autoclear_features: BeU64::new(header.autoclear_features),
|
|
||||||
refcount_order: BeU32::new(header.refcount_order),
|
|
||||||
header_size: BeU32::new(header.header_size),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(FromBytes, IntoBytes, KnownLayout, Immutable)]
|
|
||||||
struct ExtensionHeader {
|
|
||||||
extension_type: BeU32,
|
|
||||||
length: BeU32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ExtensionHeader {
|
|
||||||
fn end() -> Self {
|
|
||||||
Self {
|
|
||||||
extension_type: BeU32::new(HEADER_EXT_END),
|
|
||||||
length: BeU32::ZERO,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl QcowHeader {
|
|
||||||
/// Read header extensions, optionally collecting feature names for error reporting.
|
|
||||||
pub(super) fn read_header_extensions(
|
|
||||||
f: &AlignedFile,
|
|
||||||
header: &mut QcowHeader,
|
|
||||||
mut feature_table: Option<&mut Vec<(u8, String)>>,
|
|
||||||
) -> Result<()> {
|
|
||||||
// Extensions start directly after the header.
|
|
||||||
let mut offset = header.header_size as u64;
|
|
||||||
|
|
||||||
loop {
|
|
||||||
let mut field = [0u8; size_of::<ExtensionHeader>()];
|
|
||||||
f.read_exact_at(&mut field, offset)
|
|
||||||
.map_err(Error::ReadingHeader)?;
|
|
||||||
offset += field.len() as u64;
|
|
||||||
|
|
||||||
let extension =
|
|
||||||
ExtensionHeader::read_from_bytes(&field).expect("buffer covers extension header");
|
|
||||||
let ext_type = extension.extension_type.get();
|
|
||||||
if ext_type == HEADER_EXT_END {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
let ext_length = extension.length.get();
|
|
||||||
|
|
||||||
match ext_type {
|
|
||||||
HEADER_EXT_BACKING_FORMAT => {
|
|
||||||
let mut format_bytes = vec![0u8; ext_length as usize];
|
|
||||||
f.read_exact_at(&mut format_bytes, offset)
|
|
||||||
.map_err(Error::ReadingHeader)?;
|
|
||||||
offset += format_bytes.len() as u64;
|
|
||||||
let format_str = String::from_utf8(format_bytes)
|
|
||||||
.map_err(|err| Error::InvalidBackingFileName(err.utf8_error()))?;
|
|
||||||
if let Some(backing_file) = &mut header.backing_file {
|
|
||||||
backing_file.format = Some(format_str.parse()?);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
HEADER_EXT_FEATURE_NAME_TABLE if feature_table.is_some() => {
|
|
||||||
const FEATURE_NAME_ENTRY_SIZE: usize = 1 + 1 + 46; // type + bit + name
|
|
||||||
let mut data = vec![0u8; ext_length as usize];
|
|
||||||
f.read_exact_at(&mut data, offset)
|
|
||||||
.map_err(Error::ReadingHeader)?;
|
|
||||||
offset += data.len() as u64;
|
|
||||||
let table = feature_table.as_mut().unwrap();
|
|
||||||
for entry in data.as_chunks::<FEATURE_NAME_ENTRY_SIZE>().0 {
|
|
||||||
if entry[0] == FEAT_TYPE_INCOMPATIBLE {
|
|
||||||
let bit_number = entry[1];
|
|
||||||
let name_bytes = &entry[2..];
|
|
||||||
let name_len = name_bytes.iter().position(|&b| b == 0).unwrap_or(46);
|
|
||||||
let name = String::from_utf8_lossy(&name_bytes[..name_len]).to_string();
|
|
||||||
table.push((bit_number, name));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
// Skip unknown extension
|
|
||||||
offset += ext_length as u64;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip to the next 8 byte boundary
|
|
||||||
let padding = (8 - (ext_length % 8)) % 8;
|
|
||||||
offset += padding as u64;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a QcowHeader from a reference to a file.
|
|
||||||
pub fn new(f: &AlignedFile) -> Result<QcowHeader> {
|
|
||||||
// The bare header fits in V3_BARE_HEADER_SIZE plus the optional
|
|
||||||
// compression field. Read it once, then decode each region as a typed
|
|
||||||
// view whose layout matches the on-disk header.
|
|
||||||
let mut buf = [0u8; V3_BARE_HEADER_SIZE as usize + size_of::<u64>()];
|
|
||||||
f.read_exact_at(&mut buf, 0).map_err(Error::ReadingHeader)?;
|
|
||||||
|
|
||||||
// `buf` is always larger than the views, and the views are unaligned,
|
|
||||||
// so the casts cannot fail.
|
|
||||||
let (v2, tail) = RawHeaderV2::ref_from_prefix(&buf).expect("buffer covers the v2 header");
|
|
||||||
|
|
||||||
let magic = v2.magic.get();
|
|
||||||
if magic != QCOW_MAGIC {
|
|
||||||
return Err(Error::InvalidMagic);
|
|
||||||
}
|
|
||||||
let version = v2.version.get();
|
|
||||||
|
|
||||||
let mut header = QcowHeader {
|
|
||||||
magic,
|
|
||||||
version,
|
|
||||||
backing_file_offset: v2.backing_file_offset.get(),
|
|
||||||
backing_file_size: v2.backing_file_size.get(),
|
|
||||||
cluster_bits: v2.cluster_bits.get(),
|
|
||||||
size: v2.size.get(),
|
|
||||||
crypt_method: v2.crypt_method.get(),
|
|
||||||
l1_size: v2.l1_size.get(),
|
|
||||||
l1_table_offset: v2.l1_table_offset.get(),
|
|
||||||
refcount_table_offset: v2.refcount_table_offset.get(),
|
|
||||||
refcount_table_clusters: v2.refcount_table_clusters.get(),
|
|
||||||
nb_snapshots: v2.nb_snapshots.get(),
|
|
||||||
snapshots_offset: v2.snapshots_offset.get(),
|
|
||||||
incompatible_features: 0,
|
|
||||||
compatible_features: 0,
|
|
||||||
autoclear_features: 0,
|
|
||||||
refcount_order: DEFAULT_REFCOUNT_ORDER,
|
|
||||||
header_size: V2_BARE_HEADER_SIZE,
|
|
||||||
compression_type: CompressionType::Zlib,
|
|
||||||
backing_file: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
if version != 2 {
|
|
||||||
let (v3, rest) =
|
|
||||||
RawHeaderV3Tail::ref_from_prefix(tail).expect("buffer covers the v3 header");
|
|
||||||
header.incompatible_features = v3.incompatible_features.get();
|
|
||||||
header.compatible_features = v3.compatible_features.get();
|
|
||||||
header.autoclear_features = v3.autoclear_features.get();
|
|
||||||
header.refcount_order = v3.refcount_order.get();
|
|
||||||
header.header_size = v3.header_size.get();
|
|
||||||
|
|
||||||
if version == 3 && header.header_size > V3_BARE_HEADER_SIZE {
|
|
||||||
let (compression, _) =
|
|
||||||
BeU64::ref_from_prefix(rest).expect("buffer covers the compression field");
|
|
||||||
let raw_compression_type = compression.get() >> (64 - 8);
|
|
||||||
header.compression_type = if raw_compression_type == COMPRESSION_TYPE_ZLIB {
|
|
||||||
Ok(CompressionType::Zlib)
|
|
||||||
} else if raw_compression_type == COMPRESSION_TYPE_ZSTD {
|
|
||||||
Ok(CompressionType::Zstd)
|
|
||||||
} else {
|
|
||||||
Err(Error::UnsupportedCompressionType)
|
|
||||||
}?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if header.backing_file_size > MAX_BACKING_FILE_SIZE {
|
|
||||||
return Err(Error::BackingFileTooLong(header.backing_file_size as usize));
|
|
||||||
}
|
|
||||||
if header.backing_file_offset == 0 && header.backing_file_size != 0 {
|
|
||||||
return Err(Error::BackingFileSizeWithoutOffset(
|
|
||||||
header.backing_file_size,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
if header.backing_file_offset != 0 && header.backing_file_size == 0 {
|
|
||||||
return Err(Error::BackingFileOffsetWithoutSize(
|
|
||||||
header.backing_file_offset,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
if header.backing_file_offset != 0 {
|
|
||||||
let cluster_size = 1u64
|
|
||||||
.checked_shl(header.cluster_bits)
|
|
||||||
.ok_or(Error::InvalidClusterSize)?;
|
|
||||||
if header.backing_file_offset < u64::from(header.header_size) {
|
|
||||||
return Err(Error::BackingFileOverlapsHeader(
|
|
||||||
header.backing_file_offset,
|
|
||||||
header.backing_file_size,
|
|
||||||
header.header_size,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
if header.backing_file_offset >= cluster_size
|
|
||||||
|| header.backing_file_offset + u64::from(header.backing_file_size) > cluster_size
|
|
||||||
{
|
|
||||||
return Err(Error::BackingFileOutsideFirstCluster(
|
|
||||||
header.backing_file_offset,
|
|
||||||
header.backing_file_size,
|
|
||||||
cluster_size,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
let mut backing_file_name_bytes = vec![0u8; header.backing_file_size as usize];
|
|
||||||
f.read_exact_at(&mut backing_file_name_bytes, header.backing_file_offset)
|
|
||||||
.map_err(Error::ReadingHeader)?;
|
|
||||||
let path = String::from_utf8(backing_file_name_bytes)
|
|
||||||
.map_err(|err| Error::InvalidBackingFileName(err.utf8_error()))?;
|
|
||||||
header.backing_file = Some(BackingFileConfig { path, format: None });
|
|
||||||
}
|
|
||||||
|
|
||||||
if version == 3 {
|
|
||||||
// Check for unsupported incompatible features first
|
|
||||||
let features = IncompatFeatures::from_bits_retain(header.incompatible_features);
|
|
||||||
let unsupported = features - IncompatFeatures::SUPPORTED;
|
|
||||||
if !unsupported.is_empty() {
|
|
||||||
// Read extensions only to get feature names for error reporting
|
|
||||||
let mut feature_table = Vec::new();
|
|
||||||
if header.header_size > V3_BARE_HEADER_SIZE {
|
|
||||||
let _ = Self::read_header_extensions(f, &mut header, Some(&mut feature_table));
|
|
||||||
}
|
|
||||||
return Err(Error::UnsupportedFeature(MissingFeatureError::new(
|
|
||||||
unsupported,
|
|
||||||
feature_table,
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Features OK, now read extensions normally
|
|
||||||
if header.header_size > V3_BARE_HEADER_SIZE {
|
|
||||||
Self::read_header_extensions(f, &mut header, None)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(header)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_decoder(&self) -> Box<dyn Decoder> {
|
|
||||||
match self.compression_type {
|
|
||||||
CompressionType::Zlib => Box::new(ZlibDecoder {}),
|
|
||||||
CompressionType::Zstd => Box::new(ZstdDecoder {}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_for_size_and_path(
|
|
||||||
version: u32,
|
|
||||||
size: u64,
|
|
||||||
backing_file: Option<&str>,
|
|
||||||
) -> Result<QcowHeader> {
|
|
||||||
let header_size = if version == 2 {
|
|
||||||
V2_BARE_HEADER_SIZE
|
|
||||||
} else {
|
|
||||||
V3_BARE_HEADER_SIZE + QCOW_EMPTY_HEADER_EXTENSION_SIZE
|
|
||||||
};
|
|
||||||
let cluster_bits: u32 = DEFAULT_CLUSTER_BITS;
|
|
||||||
let cluster_size: u32 = 0x01 << cluster_bits;
|
|
||||||
let max_length: usize = (cluster_size - header_size) as usize;
|
|
||||||
if let Some(path) = backing_file
|
|
||||||
&& path.len() > max_length
|
|
||||||
{
|
|
||||||
return Err(Error::BackingFileTooLong(path.len() - max_length));
|
|
||||||
}
|
|
||||||
|
|
||||||
// L2 blocks are always one cluster long. They contain cluster_size/sizeof(u64) addresses.
|
|
||||||
let entries_per_cluster: u32 = cluster_size / size_of::<u64>() as u32;
|
|
||||||
let num_clusters: u32 = div_round_up_u64(size, u64::from(cluster_size)) as u32;
|
|
||||||
let num_l2_clusters: u32 = div_round_up_u32(num_clusters, entries_per_cluster);
|
|
||||||
let l1_clusters: u32 = div_round_up_u32(num_l2_clusters, entries_per_cluster);
|
|
||||||
let header_clusters = div_round_up_u32(size_of::<QcowHeader>() as u32, cluster_size);
|
|
||||||
Ok(QcowHeader {
|
|
||||||
magic: QCOW_MAGIC,
|
|
||||||
version,
|
|
||||||
backing_file_offset: backing_file.map_or(0, |_| {
|
|
||||||
header_size
|
|
||||||
+ if version == 3 {
|
|
||||||
QCOW_EMPTY_HEADER_EXTENSION_SIZE
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
}) as u64,
|
|
||||||
backing_file_size: backing_file.map_or(0, |x| x.len()) as u32,
|
|
||||||
cluster_bits: DEFAULT_CLUSTER_BITS,
|
|
||||||
size,
|
|
||||||
crypt_method: 0,
|
|
||||||
l1_size: num_l2_clusters,
|
|
||||||
l1_table_offset: u64::from(cluster_size),
|
|
||||||
// The refcount table is after l1 + header.
|
|
||||||
refcount_table_offset: u64::from(cluster_size * (l1_clusters + 1)),
|
|
||||||
refcount_table_clusters: {
|
|
||||||
// Pre-allocate enough clusters for the entire refcount table as it must be
|
|
||||||
// continuous in the file. Allocate enough space to refcount all clusters, including
|
|
||||||
// the refcount clusters.
|
|
||||||
let max_refcount_clusters = max_refcount_clusters(
|
|
||||||
DEFAULT_REFCOUNT_ORDER,
|
|
||||||
cluster_size,
|
|
||||||
num_clusters + l1_clusters + num_l2_clusters + header_clusters,
|
|
||||||
) as u32;
|
|
||||||
// The refcount table needs to store the offset of each refcount cluster.
|
|
||||||
div_round_up_u32(
|
|
||||||
max_refcount_clusters * size_of::<u64>() as u32,
|
|
||||||
cluster_size,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
nb_snapshots: 0,
|
|
||||||
snapshots_offset: 0,
|
|
||||||
incompatible_features: 0,
|
|
||||||
compatible_features: 0,
|
|
||||||
autoclear_features: 0,
|
|
||||||
refcount_order: DEFAULT_REFCOUNT_ORDER,
|
|
||||||
header_size,
|
|
||||||
compression_type: CompressionType::Zlib,
|
|
||||||
backing_file: backing_file.map(|path| BackingFileConfig {
|
|
||||||
path: String::from(path),
|
|
||||||
format: None,
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Write the header to `f`.
|
|
||||||
pub fn write_to(&self, f: &AlignedFile) -> Result<()> {
|
|
||||||
// Build the header in memory, then write it in one positional write.
|
|
||||||
let mut buf = Vec::new();
|
|
||||||
let v2 = RawHeaderV2::from_header(self);
|
|
||||||
buf.extend_from_slice(v2.as_bytes());
|
|
||||||
|
|
||||||
if self.version == 3 {
|
|
||||||
let v3 = RawHeaderV3Tail::from_header(self);
|
|
||||||
buf.extend_from_slice(v3.as_bytes());
|
|
||||||
|
|
||||||
if self.header_size > V3_BARE_HEADER_SIZE {
|
|
||||||
let compression_type = match &self.compression_type {
|
|
||||||
CompressionType::Zlib => COMPRESSION_TYPE_ZLIB,
|
|
||||||
CompressionType::Zstd => COMPRESSION_TYPE_ZSTD,
|
|
||||||
};
|
|
||||||
let compression_type = BeU64::new(compression_type << (64 - 8));
|
|
||||||
buf.extend_from_slice(compression_type.as_bytes());
|
|
||||||
}
|
|
||||||
|
|
||||||
let end_extension = ExtensionHeader::end();
|
|
||||||
buf.extend_from_slice(end_extension.as_bytes());
|
|
||||||
}
|
|
||||||
|
|
||||||
f.write_all_at(&buf, 0).map_err(Error::WritingHeader)?;
|
|
||||||
|
|
||||||
if let Some(backing_file_path) = self.backing_file.as_ref().map(|bf| &bf.path) {
|
|
||||||
let offset = if self.backing_file_offset > 0 {
|
|
||||||
self.backing_file_offset
|
|
||||||
} else {
|
|
||||||
buf.len() as u64
|
|
||||||
};
|
|
||||||
f.write_all_at(backing_file_path.as_bytes(), offset)
|
|
||||||
.map_err(Error::WritingHeader)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the file length by writing a zero to the last byte. This also
|
|
||||||
// zeros the l1 and refcount table clusters.
|
|
||||||
let cluster_size = 0x01u64 << self.cluster_bits;
|
|
||||||
let refcount_blocks_size = u64::from(self.refcount_table_clusters) * cluster_size;
|
|
||||||
f.write_all_at(
|
|
||||||
&[0u8],
|
|
||||||
self.refcount_table_offset + refcount_blocks_size - 2,
|
|
||||||
)
|
|
||||||
.map_err(Error::WritingHeader)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Write only the incompatible_features field to the file at its fixed offset.
|
|
||||||
fn write_incompatible_features(&self, file: &AlignedFile) -> BlockResult<()> {
|
|
||||||
if self.version != 3 {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
file.write_all_at(
|
|
||||||
&self.incompatible_features.to_be_bytes(),
|
|
||||||
V2_BARE_HEADER_SIZE as u64,
|
|
||||||
)
|
|
||||||
.map_err(|e| BlockError::new(BlockErrorKind::Io, Error::WritingHeader(e)))?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set or clear the dirty bit for QCOW2 v3 images.
|
|
||||||
///
|
|
||||||
/// When `dirty` is true, sets the bit to indicate the image is in use.
|
|
||||||
/// When `dirty` is false, clears the bit to indicate a clean shutdown.
|
|
||||||
pub fn set_dirty_bit(&mut self, file: &mut AlignedFile, dirty: bool) -> BlockResult<()> {
|
|
||||||
if self.version == 3 {
|
|
||||||
if dirty {
|
|
||||||
self.incompatible_features |= IncompatFeatures::DIRTY.bits();
|
|
||||||
} else {
|
|
||||||
self.incompatible_features &= !IncompatFeatures::DIRTY.bits();
|
|
||||||
}
|
|
||||||
self.write_incompatible_features(file)?;
|
|
||||||
file.fsync()
|
|
||||||
.map_err(|e| BlockError::new(BlockErrorKind::Io, Error::SyncingHeader(e)))?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the corrupt bit for QCOW2 v3 images.
|
|
||||||
///
|
|
||||||
/// This marks the image as corrupted. Once set, the image can only be
|
|
||||||
/// opened read-only until repaired.
|
|
||||||
pub fn set_corrupt_bit(&mut self, file: &mut AlignedFile) -> BlockResult<()> {
|
|
||||||
if self.version == 3 {
|
|
||||||
self.incompatible_features |= IncompatFeatures::CORRUPT.bits();
|
|
||||||
self.write_incompatible_features(file)?;
|
|
||||||
file.fsync()
|
|
||||||
.map_err(|e| BlockError::new(BlockErrorKind::Io, Error::SyncingHeader(e)))?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_corrupt(&self) -> bool {
|
|
||||||
IncompatFeatures::from_bits_truncate(self.incompatible_features)
|
|
||||||
.contains(IncompatFeatures::CORRUPT)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Clear all autoclear feature bits for QCOW2 v3 images.
|
|
||||||
///
|
|
||||||
/// These bits indicate features that can be safely disabled when modified
|
|
||||||
/// by software that doesn't understand them.
|
|
||||||
pub fn clear_autoclear_features(&mut self, file: &mut AlignedFile) -> Result<()> {
|
|
||||||
if self.version == 3 && self.autoclear_features != 0 {
|
|
||||||
self.autoclear_features = 0;
|
|
||||||
file.write_all_at(&0u64.to_be_bytes(), AUTOCLEAR_FEATURES_OFFSET)
|
|
||||||
.map_err(Error::WritingHeader)?;
|
|
||||||
file.fsync().map_err(Error::SyncingHeader)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn max_refcount_clusters(
|
|
||||||
refcount_order: u32,
|
|
||||||
cluster_size: u32,
|
|
||||||
num_clusters: u32,
|
|
||||||
) -> u64 {
|
|
||||||
// Use u64 as the product of the u32 inputs can overflow.
|
|
||||||
let refcount_bits = 0x01u64 << u64::from(refcount_order);
|
|
||||||
let cluster_bits = u64::from(cluster_size) * 8;
|
|
||||||
let for_data = div_round_up_u64(u64::from(num_clusters) * refcount_bits, cluster_bits);
|
|
||||||
let for_refcounts = div_round_up_u64(for_data * refcount_bits, cluster_bits);
|
|
||||||
for_data + for_refcounts
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns an Error if the given offset doesn't align to a cluster boundary.
|
|
||||||
pub(super) fn offset_is_cluster_boundary(offset: u64, cluster_bits: u32) -> Result<()> {
|
|
||||||
if offset & ((0x01 << cluster_bits) - 1) != 0 {
|
|
||||||
return Err(Error::InvalidOffset(offset));
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,452 +0,0 @@
|
|||||||
// Copyright 2026 The Cloud Hypervisor Authors. All rights reserved.
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
//! QCOW2 disk image format.
|
|
||||||
//!
|
|
||||||
//! Provides [`QcowDisk`], the `DiskFile` wrapper for QCOW2 images
|
|
||||||
//! with backing file and compression support.
|
|
||||||
|
|
||||||
mod backing;
|
|
||||||
mod common;
|
|
||||||
mod decoder;
|
|
||||||
mod engine_sync;
|
|
||||||
#[cfg(feature = "io_uring")]
|
|
||||||
mod engine_uring;
|
|
||||||
mod header;
|
|
||||||
mod metadata;
|
|
||||||
mod parser;
|
|
||||||
mod qcow_raw_file;
|
|
||||||
mod refcount;
|
|
||||||
mod util;
|
|
||||||
mod vec_cache;
|
|
||||||
|
|
||||||
use std::fs::File;
|
|
||||||
use std::os::unix::io::AsRawFd;
|
|
||||||
#[cfg(any(test, feature = "test-utils"))]
|
|
||||||
use std::path::Path;
|
|
||||||
use std::sync::Arc;
|
|
||||||
use std::{fmt, io};
|
|
||||||
|
|
||||||
pub use parser::{
|
|
||||||
BackingFileConfig, CompressionType, Error, ImageType, IncompatFeatures, MissingFeatureError,
|
|
||||||
QcowHeader,
|
|
||||||
};
|
|
||||||
#[cfg(any(test, feature = "test-utils"))]
|
|
||||||
use vm_memory::{Bytes, GuestAddress, GuestMemoryMmap};
|
|
||||||
#[cfg(any(test, feature = "test-utils"))]
|
|
||||||
use vmm_sys_util::tempfile::TempFile;
|
|
||||||
|
|
||||||
use self::backing::shared_backing_from;
|
|
||||||
use self::engine_sync::QcowSync;
|
|
||||||
#[cfg(feature = "io_uring")]
|
|
||||||
use self::engine_uring::QcowAsync;
|
|
||||||
use self::metadata::{BackingRead, QcowMetadata};
|
|
||||||
use self::parser::{MAX_NESTING_DEPTH, parse_qcow};
|
|
||||||
use self::qcow_raw_file::QcowRawFile;
|
|
||||||
use crate::aligned_file::AlignedFile;
|
|
||||||
#[cfg(any(test, feature = "test-utils"))]
|
|
||||||
use crate::async_io::GuestMemoryTarget;
|
|
||||||
use crate::async_io::{AsyncIo, BorrowedDiskFd, DiskFileError};
|
|
||||||
use crate::disk_file;
|
|
||||||
#[cfg(any(test, feature = "test-utils"))]
|
|
||||||
use crate::disk_file::AsyncDiskFile;
|
|
||||||
use crate::error::{BlockError, BlockErrorKind, BlockResult, ErrorOp};
|
|
||||||
|
|
||||||
/// Unified DiskFile wrapper for QCOW2 disk images.
|
|
||||||
///
|
|
||||||
/// Holds the in memory QCOW2 metadata, the data file, and an optional
|
|
||||||
/// backing file. The metadata is wrapped in an `Arc` because
|
|
||||||
/// [`QcowSync`] and [`QcowAsync`] I/O workers receive a clone when
|
|
||||||
/// they are created via [`create_async_io`](DiskFile::create_async_io).
|
|
||||||
/// The backing file is likewise shared with workers through an `Arc`.
|
|
||||||
///
|
|
||||||
/// The `sparse` flag controls whether the image advertises discard
|
|
||||||
/// support to the guest. The `use_io_uring` flag selects between the
|
|
||||||
/// [`QcowSync`] and [`QcowAsync`] I/O backends. Both are recorded at
|
|
||||||
/// construction time and propagated through [`try_clone`](DiskFile::try_clone).
|
|
||||||
pub struct QcowDisk {
|
|
||||||
metadata: Arc<QcowMetadata>,
|
|
||||||
backing_file: Option<Arc<dyn BackingRead>>,
|
|
||||||
sparse: bool,
|
|
||||||
data_raw_file: QcowRawFile,
|
|
||||||
use_io_uring: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Debug for QcowDisk {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
f.debug_struct("QcowDisk")
|
|
||||||
.field("sparse", &self.sparse)
|
|
||||||
.field("has_backing", &self.backing_file.is_some())
|
|
||||||
.field("use_io_uring", &self.use_io_uring)
|
|
||||||
.finish_non_exhaustive()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl QcowDisk {
|
|
||||||
pub fn new(
|
|
||||||
file: File,
|
|
||||||
direct_io: bool,
|
|
||||||
backing_files: bool,
|
|
||||||
sparse: bool,
|
|
||||||
use_io_uring: bool,
|
|
||||||
) -> BlockResult<Self> {
|
|
||||||
#[cfg(not(feature = "io_uring"))]
|
|
||||||
if use_io_uring {
|
|
||||||
return Err(BlockError::new(
|
|
||||||
BlockErrorKind::UnsupportedFeature,
|
|
||||||
DiskFileError::NewAsyncIo(io::Error::other(
|
|
||||||
"io_uring requested but feature is not enabled",
|
|
||||||
)),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let max_nesting_depth = if backing_files { MAX_NESTING_DEPTH } else { 0 };
|
|
||||||
let raw_file = AlignedFile::new(file, direct_io);
|
|
||||||
let (inner, backing_file, sparse) = parse_qcow(raw_file, max_nesting_depth, sparse)
|
|
||||||
.map_err(|e| {
|
|
||||||
let e = if !backing_files && matches!(e.kind(), BlockErrorKind::Overflow) {
|
|
||||||
e.with_kind(BlockErrorKind::UnsupportedFeature)
|
|
||||||
} else {
|
|
||||||
e
|
|
||||||
};
|
|
||||||
e.with_op(ErrorOp::Open)
|
|
||||||
})?;
|
|
||||||
let data_raw_file = inner.raw_file.clone();
|
|
||||||
Ok(QcowDisk {
|
|
||||||
metadata: Arc::new(QcowMetadata::new(inner)),
|
|
||||||
backing_file: backing_file.map(shared_backing_from).transpose()?,
|
|
||||||
sparse,
|
|
||||||
data_raw_file,
|
|
||||||
use_io_uring,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Synchronous write convenience for tests and benchmarks.
|
|
||||||
#[cfg(any(test, feature = "test-utils"))]
|
|
||||||
pub fn write_all_at(&self, offset: u64, data: &[u8]) {
|
|
||||||
let mut async_io = self.create_async_io(1).unwrap();
|
|
||||||
let mem =
|
|
||||||
Arc::new(GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), data.len())]).unwrap());
|
|
||||||
mem.write_slice(data, GuestAddress(0)).unwrap();
|
|
||||||
let range = [(GuestAddress(0), data.len() as u32)];
|
|
||||||
let target = GuestMemoryTarget::new(Arc::clone(&mem), &range).unwrap();
|
|
||||||
async_io
|
|
||||||
.write_from_memory(offset as libc::off_t, target, 0)
|
|
||||||
.unwrap();
|
|
||||||
while async_io.next_completed_request().is_some() {}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Synchronous read convenience for tests and benchmarks.
|
|
||||||
#[cfg(test)]
|
|
||||||
pub fn read_all_at(&self, offset: u64, len: usize) -> Vec<u8> {
|
|
||||||
let mut async_io = self.create_async_io(1).unwrap();
|
|
||||||
let mem = Arc::new(GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), len)]).unwrap());
|
|
||||||
let range = [(GuestAddress(0), len as u32)];
|
|
||||||
let target = GuestMemoryTarget::new(Arc::clone(&mem), &range).unwrap();
|
|
||||||
async_io
|
|
||||||
.read_to_memory(offset as libc::off_t, target, 0)
|
|
||||||
.unwrap();
|
|
||||||
while async_io.next_completed_request().is_some() {}
|
|
||||||
let mut buf = vec![0u8; len];
|
|
||||||
mem.read_slice(&mut buf, GuestAddress(0)).unwrap();
|
|
||||||
buf
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
fn metadata(&self) -> &QcowMetadata {
|
|
||||||
&self.metadata
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Writes a fresh qcow2 layout into `file`
|
|
||||||
#[cfg(any(test, feature = "test-utils"))]
|
|
||||||
pub(crate) fn create_image(
|
|
||||||
file: &File,
|
|
||||||
virtual_size: u64,
|
|
||||||
backing_config: Option<&BackingFileConfig>,
|
|
||||||
) -> BlockResult<()> {
|
|
||||||
let path = backing_config.map(|cfg| cfg.path.as_str());
|
|
||||||
let mut header = QcowHeader::create_for_size_and_path(3, virtual_size, path)
|
|
||||||
.map_err(|e| BlockError::new(BlockErrorKind::Io, e))?;
|
|
||||||
if let Some(cfg) = backing_config
|
|
||||||
&& let Some(backing_file) = &mut header.backing_file
|
|
||||||
{
|
|
||||||
backing_file.format = cfg.format;
|
|
||||||
}
|
|
||||||
let raw = AlignedFile::new(
|
|
||||||
file.try_clone()
|
|
||||||
.map_err(|e| BlockError::new(BlockErrorKind::Io, DiskFileError::Clone(e)))?,
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
header
|
|
||||||
.write_to(&raw)
|
|
||||||
.map_err(|e| BlockError::new(BlockErrorKind::Io, e))?;
|
|
||||||
let (inner, _backing, _sparse) = parse_qcow(raw, MAX_NESTING_DEPTH, true)?;
|
|
||||||
// Flush dirty caches and clear the dirty bit
|
|
||||||
QcowMetadata::new(inner).shutdown();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Helper struct to create a new qcow2 image in a temporary file.
|
|
||||||
#[cfg(any(test, feature = "test-utils"))]
|
|
||||||
pub struct QcowTempDisk {
|
|
||||||
tmp: TempFile,
|
|
||||||
disk: QcowDisk,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-utils"))]
|
|
||||||
impl QcowTempDisk {
|
|
||||||
/// Creates a new qcow2 image in a temporary file with optional
|
|
||||||
/// backing file. Flags are passed to QcowDisk::new.
|
|
||||||
pub fn new(
|
|
||||||
virtual_size: u64,
|
|
||||||
backing_config: Option<&BackingFileConfig>,
|
|
||||||
direct_io: bool,
|
|
||||||
sparse: bool,
|
|
||||||
use_io_uring: bool,
|
|
||||||
) -> BlockResult<Self> {
|
|
||||||
let tmp = TempFile::new().map_err(io::Error::from)?;
|
|
||||||
create_image(tmp.as_file(), virtual_size, backing_config)?;
|
|
||||||
let file = tmp
|
|
||||||
.as_file()
|
|
||||||
.try_clone()
|
|
||||||
.map_err(|e| BlockError::new(BlockErrorKind::Io, DiskFileError::Clone(e)))?;
|
|
||||||
let disk = QcowDisk::new(
|
|
||||||
file,
|
|
||||||
direct_io,
|
|
||||||
backing_config.is_some(),
|
|
||||||
sparse,
|
|
||||||
use_io_uring,
|
|
||||||
)?;
|
|
||||||
Ok(Self { tmp, disk })
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn path(&self) -> &Path {
|
|
||||||
self.tmp.as_path()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn as_file(&self) -> &File {
|
|
||||||
self.tmp.as_file()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn disk(&self) -> &QcowDisk {
|
|
||||||
&self.disk
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Drops the disk handle and returns the underlying TempFile.
|
|
||||||
pub fn into_tempfile(self) -> TempFile {
|
|
||||||
self.tmp
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl disk_file::DiskSize for QcowDisk {
|
|
||||||
fn logical_size(&self) -> BlockResult<u64> {
|
|
||||||
Ok(self.metadata.virtual_size())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl disk_file::PhysicalSize for QcowDisk {
|
|
||||||
fn physical_size(&self) -> BlockResult<u64> {
|
|
||||||
Ok(self.data_raw_file.physical_size()?)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl disk_file::DiskFd for QcowDisk {
|
|
||||||
fn fd(&self) -> BorrowedDiskFd<'_> {
|
|
||||||
BorrowedDiskFd::new(self.data_raw_file.as_raw_fd())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl disk_file::Geometry for QcowDisk {}
|
|
||||||
|
|
||||||
impl disk_file::SparseCapable for QcowDisk {
|
|
||||||
fn supports_sparse_operations(&self) -> bool {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
fn supports_zero_flag(&self) -> bool {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl disk_file::Resizable for QcowDisk {
|
|
||||||
fn resize(&mut self, size: u64) -> BlockResult<()> {
|
|
||||||
if self.backing_file.is_some() {
|
|
||||||
return Err(BlockError::new(
|
|
||||||
BlockErrorKind::UnsupportedFeature,
|
|
||||||
DiskFileError::ResizeError(io::Error::other(
|
|
||||||
"resize not supported with backing files",
|
|
||||||
)),
|
|
||||||
)
|
|
||||||
.with_op(ErrorOp::Resize));
|
|
||||||
}
|
|
||||||
self.metadata.resize(size).map_err(|e| {
|
|
||||||
BlockError::new(BlockErrorKind::Io, DiskFileError::ResizeError(e))
|
|
||||||
.with_op(ErrorOp::Resize)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl disk_file::MetadataSync for QcowDisk {
|
|
||||||
fn sync_metadata(&self) -> BlockResult<()> {
|
|
||||||
self.metadata
|
|
||||||
.flush()
|
|
||||||
.map_err(|e| BlockError::new(BlockErrorKind::Io, DiskFileError::SyncMetadata(e)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl disk_file::DiskFile for QcowDisk {}
|
|
||||||
|
|
||||||
impl disk_file::AsyncDiskFile for QcowDisk {
|
|
||||||
fn try_clone(&self) -> BlockResult<Box<dyn disk_file::AsyncDiskFile>> {
|
|
||||||
Ok(Box::new(QcowDisk {
|
|
||||||
metadata: Arc::clone(&self.metadata),
|
|
||||||
backing_file: self.backing_file.as_ref().map(Arc::clone),
|
|
||||||
sparse: self.sparse,
|
|
||||||
data_raw_file: self.data_raw_file.clone(),
|
|
||||||
use_io_uring: self.use_io_uring,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_async_io(&self, ring_depth: u32) -> BlockResult<Box<dyn AsyncIo>> {
|
|
||||||
if self.use_io_uring {
|
|
||||||
#[cfg(feature = "io_uring")]
|
|
||||||
{
|
|
||||||
return Ok(Box::new(
|
|
||||||
QcowAsync::new(
|
|
||||||
Arc::clone(&self.metadata),
|
|
||||||
self.data_raw_file.clone(),
|
|
||||||
self.backing_file.as_ref().map(Arc::clone),
|
|
||||||
self.sparse,
|
|
||||||
ring_depth,
|
|
||||||
)
|
|
||||||
.map_err(|e| {
|
|
||||||
BlockError::new(BlockErrorKind::Io, DiskFileError::NewAsyncIo(e))
|
|
||||||
})?,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "io_uring"))]
|
|
||||||
unreachable!("use_io_uring is set but io_uring feature is not enabled");
|
|
||||||
}
|
|
||||||
|
|
||||||
let _ = ring_depth;
|
|
||||||
Ok(Box::new(QcowSync::new(
|
|
||||||
Arc::clone(&self.metadata),
|
|
||||||
self.data_raw_file.clone(),
|
|
||||||
self.backing_file.as_ref().map(Arc::clone),
|
|
||||||
self.sparse,
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod unit_tests {
|
|
||||||
use std::os::unix::fs::FileExt;
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
use crate::async_io::AsyncIo;
|
|
||||||
use crate::disk_file::{AsyncDiskFile, DiskSize, PhysicalSize};
|
|
||||||
|
|
||||||
const TEST_SIZE: u64 = 0x5566_7788;
|
|
||||||
|
|
||||||
fn make_qcow_file() -> File {
|
|
||||||
QcowTempDisk::new(TEST_SIZE, None, false, true, false)
|
|
||||||
.unwrap()
|
|
||||||
.into_tempfile()
|
|
||||||
.into_file()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn dirty_bit_is_set(file: &File) -> bool {
|
|
||||||
let mut buf = [0u8; 8];
|
|
||||||
file.read_exact_at(&mut buf, header::V2_BARE_HEADER_SIZE as u64)
|
|
||||||
.unwrap();
|
|
||||||
u64::from_be_bytes(buf) & IncompatFeatures::DIRTY.bits() != 0
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn new_sync_returns_correct_size() {
|
|
||||||
let file = make_qcow_file();
|
|
||||||
let disk = QcowDisk::new(file, false, false, true, false).unwrap();
|
|
||||||
assert_eq!(disk.logical_size().unwrap(), TEST_SIZE);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn assert_async_io_from_dyn(disk: &dyn AsyncDiskFile, expect_batch: bool) {
|
|
||||||
let io: Box<dyn AsyncIo> = disk.create_async_io(128).unwrap();
|
|
||||||
assert_eq!(io.batch_requests_enabled(), expect_batch);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn assert_async_io(disk: &QcowDisk, expect_batch: bool) {
|
|
||||||
assert_async_io_from_dyn(disk, expect_batch);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn sync_backend_disables_batch_requests() {
|
|
||||||
let file = make_qcow_file();
|
|
||||||
let disk = QcowDisk::new(file, false, false, true, false).unwrap();
|
|
||||||
assert_async_io(&disk, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "io_uring")]
|
|
||||||
#[test]
|
|
||||||
fn io_uring_backend_enables_batch_requests() {
|
|
||||||
let file = make_qcow_file();
|
|
||||||
let disk = QcowDisk::new(file, false, false, true, true).unwrap();
|
|
||||||
assert_async_io(&disk, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn try_clone_preserves_sync_dispatch() {
|
|
||||||
let file = make_qcow_file();
|
|
||||||
let disk = QcowDisk::new(file, false, false, true, false).unwrap();
|
|
||||||
let cloned = disk.try_clone().unwrap();
|
|
||||||
assert_async_io_from_dyn(cloned.as_ref(), false);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn dropping_clone_does_not_clear_dirty_bit() {
|
|
||||||
let file = make_qcow_file();
|
|
||||||
let disk = QcowDisk::new(file, false, false, true, false).unwrap();
|
|
||||||
let cloned = disk.try_clone().unwrap();
|
|
||||||
|
|
||||||
drop(cloned);
|
|
||||||
|
|
||||||
assert_ne!(
|
|
||||||
disk.metadata().header().incompatible_features & IncompatFeatures::DIRTY.bits(),
|
|
||||||
0
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn async_io_clears_dirty_bit_when_last_metadata_owner_drops() {
|
|
||||||
let file = make_qcow_file();
|
|
||||||
let inspect = file.try_clone().unwrap();
|
|
||||||
let disk = QcowDisk::new(file, false, false, true, false).unwrap();
|
|
||||||
let async_io = disk.create_async_io(1).unwrap();
|
|
||||||
|
|
||||||
drop(disk);
|
|
||||||
assert!(dirty_bit_is_set(&inspect));
|
|
||||||
|
|
||||||
drop(async_io);
|
|
||||||
assert!(!dirty_bit_is_set(&inspect));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "io_uring")]
|
|
||||||
#[test]
|
|
||||||
fn try_clone_preserves_io_uring_dispatch() {
|
|
||||||
let file = make_qcow_file();
|
|
||||||
let disk = QcowDisk::new(file, false, false, true, true).unwrap();
|
|
||||||
let cloned = disk.try_clone().unwrap();
|
|
||||||
assert_async_io_from_dyn(cloned.as_ref(), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn physical_size_less_than_logical() {
|
|
||||||
// make_qcow_file() writes no guest data, so the file on disk
|
|
||||||
// only contains QCOW2 headers and metadata tables.
|
|
||||||
let file = make_qcow_file();
|
|
||||||
let disk = QcowDisk::new(file, false, false, true, false).unwrap();
|
|
||||||
assert!(disk.physical_size().unwrap() < disk.logical_size().unwrap());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,557 +0,0 @@
|
|||||||
// Copyright 2018 The Chromium OS Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
|
||||||
// found in the LICENSE-BSD-3-Clause file.
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
|
||||||
|
|
||||||
use std::fmt::Debug;
|
|
||||||
use std::io::{self, Write};
|
|
||||||
use std::os::fd::{AsFd, AsRawFd, BorrowedFd, RawFd};
|
|
||||||
use std::os::unix::fs::FileExt;
|
|
||||||
|
|
||||||
use byteorder::{BigEndian, WriteBytesExt};
|
|
||||||
use vmm_sys_util::write_zeroes::WriteZeroesAt;
|
|
||||||
|
|
||||||
use crate::aligned_file::AlignedFile;
|
|
||||||
|
|
||||||
// Type aliases for the refcount read/write function pointers
|
|
||||||
type RefcountReader = fn(&mut AlignedFile, u64, usize) -> io::Result<Vec<u64>>;
|
|
||||||
type RefcountWriter = fn(&mut AlignedFile, u64, &[u64]) -> io::Result<()>;
|
|
||||||
|
|
||||||
/// Big-endian file access trait.
|
|
||||||
pub(super) trait BeUint: Sized + Copy {
|
|
||||||
fn from_be_slice(bytes: &[u8]) -> u64;
|
|
||||||
fn write_be<W: Write>(w: &mut W, val: Self) -> io::Result<()>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BeUint for u8 {
|
|
||||||
#[inline(always)]
|
|
||||||
fn from_be_slice(bytes: &[u8]) -> u64 {
|
|
||||||
bytes[0] as u64
|
|
||||||
}
|
|
||||||
#[inline(always)]
|
|
||||||
fn write_be<W: Write>(w: &mut W, val: Self) -> io::Result<()> {
|
|
||||||
w.write_u8(val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BeUint for u16 {
|
|
||||||
#[inline(always)]
|
|
||||||
fn from_be_slice(bytes: &[u8]) -> u64 {
|
|
||||||
u16::from_be_bytes([bytes[0], bytes[1]]) as u64
|
|
||||||
}
|
|
||||||
#[inline(always)]
|
|
||||||
fn write_be<W: Write>(w: &mut W, val: Self) -> io::Result<()> {
|
|
||||||
w.write_u16::<BigEndian>(val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BeUint for u32 {
|
|
||||||
#[inline(always)]
|
|
||||||
fn from_be_slice(bytes: &[u8]) -> u64 {
|
|
||||||
u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) as u64
|
|
||||||
}
|
|
||||||
#[inline(always)]
|
|
||||||
fn write_be<W: Write>(w: &mut W, val: Self) -> io::Result<()> {
|
|
||||||
w.write_u32::<BigEndian>(val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BeUint for u64 {
|
|
||||||
#[inline(always)]
|
|
||||||
fn from_be_slice(bytes: &[u8]) -> u64 {
|
|
||||||
u64::from_be_bytes([
|
|
||||||
bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
|
|
||||||
])
|
|
||||||
}
|
|
||||||
#[inline(always)]
|
|
||||||
fn write_be<W: Write>(w: &mut W, val: Self) -> io::Result<()> {
|
|
||||||
w.write_u64::<BigEndian>(val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Read byte-aligned refcounts.
|
|
||||||
fn read_refcount<T: BeUint>(
|
|
||||||
file: &mut AlignedFile,
|
|
||||||
offset: u64,
|
|
||||||
count: usize,
|
|
||||||
) -> io::Result<Vec<u64>> {
|
|
||||||
let bytes_per_entry = size_of::<T>();
|
|
||||||
let mut data = vec![0u8; count * bytes_per_entry];
|
|
||||||
file.read_exact_at(&mut data, offset)?;
|
|
||||||
Ok(data
|
|
||||||
.chunks_exact(bytes_per_entry)
|
|
||||||
.map(T::from_be_slice)
|
|
||||||
.collect())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Write byte-aligned refcounts.
|
|
||||||
fn write_refcount<T: BeUint + TryFrom<u64>>(
|
|
||||||
file: &mut AlignedFile,
|
|
||||||
offset: u64,
|
|
||||||
table: &[u64],
|
|
||||||
) -> io::Result<()>
|
|
||||||
where
|
|
||||||
<T as TryFrom<u64>>::Error: Debug,
|
|
||||||
{
|
|
||||||
let bytes_per_entry = size_of::<T>();
|
|
||||||
let mut buffer = Vec::with_capacity(table.len() * bytes_per_entry);
|
|
||||||
for &val in table {
|
|
||||||
let converted = T::try_from(val).expect("refcount values are validated on increment");
|
|
||||||
T::write_be(&mut buffer, converted)?;
|
|
||||||
}
|
|
||||||
file.write_all_at(&buffer, offset)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Read sub-byte refcounts. Bit 0 is the least significant bit.
|
|
||||||
fn read_refcount_subbyte<const BITS: usize>(
|
|
||||||
file: &mut AlignedFile,
|
|
||||||
offset: u64,
|
|
||||||
count: usize,
|
|
||||||
) -> io::Result<Vec<u64>> {
|
|
||||||
const { assert!(BITS == 1 || BITS == 2 || BITS == 4) };
|
|
||||||
let entries_per_byte = 8 / BITS;
|
|
||||||
let mask = (1u64 << BITS) - 1;
|
|
||||||
let bytes_needed = count.div_ceil(entries_per_byte);
|
|
||||||
let mut bytes = vec![0u8; bytes_needed];
|
|
||||||
file.read_exact_at(&mut bytes, offset)?;
|
|
||||||
|
|
||||||
let mut table = vec![0u64; count];
|
|
||||||
for (i, val) in table.iter_mut().enumerate() {
|
|
||||||
let byte_idx = i / entries_per_byte;
|
|
||||||
let bit_offset = (i % entries_per_byte) * BITS;
|
|
||||||
*val = (bytes[byte_idx] as u64 >> bit_offset) & mask;
|
|
||||||
}
|
|
||||||
Ok(table)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Write sub-byte refcounts. Bit 0 is the least significant bit.
|
|
||||||
fn write_refcount_subbyte<const BITS: usize>(
|
|
||||||
file: &mut AlignedFile,
|
|
||||||
offset: u64,
|
|
||||||
table: &[u64],
|
|
||||||
) -> io::Result<()> {
|
|
||||||
const { assert!(BITS == 1 || BITS == 2 || BITS == 4) };
|
|
||||||
let entries_per_byte = 8 / BITS;
|
|
||||||
let mask = (1u64 << BITS) - 1;
|
|
||||||
let mut buffer = Vec::with_capacity(table.len().div_ceil(entries_per_byte));
|
|
||||||
|
|
||||||
for chunk in table.chunks(entries_per_byte) {
|
|
||||||
let mut byte = 0u8;
|
|
||||||
for (i, &val) in chunk.iter().enumerate() {
|
|
||||||
let bit_offset = i * BITS;
|
|
||||||
byte |= ((val & mask) << bit_offset) as u8;
|
|
||||||
}
|
|
||||||
buffer.push(byte);
|
|
||||||
}
|
|
||||||
file.write_all_at(&buffer, offset)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A qcow file. Allows reading/writing clusters and appending clusters.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub(super) struct QcowRawFile {
|
|
||||||
file: AlignedFile,
|
|
||||||
cluster_size: u64,
|
|
||||||
cluster_mask: u64,
|
|
||||||
refcount_block_entries: u64,
|
|
||||||
read_refcount_fn: RefcountReader,
|
|
||||||
write_refcount_fn: RefcountWriter,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl QcowRawFile {
|
|
||||||
/// Creates a `QcowRawFile` from the given `File`, `None` is returned if `cluster_size` is not
|
|
||||||
/// a power of two or refcount_bits is invalid.
|
|
||||||
pub(super) fn from(file: AlignedFile, cluster_size: u64, refcount_bits: u64) -> Option<Self> {
|
|
||||||
if !cluster_size.is_power_of_two() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let (read_refcount_fn, write_refcount_fn): (RefcountReader, RefcountWriter) =
|
|
||||||
match refcount_bits {
|
|
||||||
1 => (read_refcount_subbyte::<1>, write_refcount_subbyte::<1>),
|
|
||||||
2 => (read_refcount_subbyte::<2>, write_refcount_subbyte::<2>),
|
|
||||||
4 => (read_refcount_subbyte::<4>, write_refcount_subbyte::<4>),
|
|
||||||
8 => (read_refcount::<u8>, write_refcount::<u8>),
|
|
||||||
16 => (read_refcount::<u16>, write_refcount::<u16>),
|
|
||||||
32 => (read_refcount::<u32>, write_refcount::<u32>),
|
|
||||||
64 => (read_refcount::<u64>, write_refcount::<u64>),
|
|
||||||
_ => return None,
|
|
||||||
};
|
|
||||||
|
|
||||||
// For sub-byte refcounts (1,2,4 bits), entries pack multiple per byte
|
|
||||||
let refcount_block_entries = cluster_size * 8 / refcount_bits;
|
|
||||||
|
|
||||||
Some(QcowRawFile {
|
|
||||||
file,
|
|
||||||
cluster_size,
|
|
||||||
cluster_mask: cluster_size - 1,
|
|
||||||
refcount_block_entries,
|
|
||||||
read_refcount_fn,
|
|
||||||
write_refcount_fn,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reads `count` 64 bit offsets and returns them as a vector.
|
|
||||||
/// `mask` optionally `&`s out some of the bits on the file.
|
|
||||||
pub(super) fn read_pointer_table(
|
|
||||||
&mut self,
|
|
||||||
offset: u64,
|
|
||||||
count: u64,
|
|
||||||
mask: Option<u64>,
|
|
||||||
) -> io::Result<Vec<u64>> {
|
|
||||||
let mut bytes = vec![0u8; count as usize * size_of::<u64>()];
|
|
||||||
self.file.read_exact_at(&mut bytes, offset)?;
|
|
||||||
let m = mask.unwrap_or(u64::MAX);
|
|
||||||
let table = bytes
|
|
||||||
.as_chunks::<{ size_of::<u64>() }>()
|
|
||||||
.0
|
|
||||||
.iter()
|
|
||||||
.map(|c| u64::from_be_bytes(*c) & m)
|
|
||||||
.collect();
|
|
||||||
Ok(table)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reads a cluster's worth of 64 bit offsets and returns them as a vector.
|
|
||||||
/// `mask` optionally `&`s out some of the bits on the file.
|
|
||||||
pub(super) fn read_pointer_cluster(
|
|
||||||
&mut self,
|
|
||||||
offset: u64,
|
|
||||||
mask: Option<u64>,
|
|
||||||
) -> io::Result<Vec<u64>> {
|
|
||||||
let count = self.cluster_size / size_of::<u64>() as u64;
|
|
||||||
self.read_pointer_table(offset, count, mask)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Writes a pointer table to `offset` in the file.
|
|
||||||
/// Entries are computed on-the-fly by the callback.
|
|
||||||
///
|
|
||||||
/// The callback may perform metadata I/O on this `QcowRawFile`, so all
|
|
||||||
/// entries are materialized before the final positional write.
|
|
||||||
pub(super) fn write_pointer_table<'a, T: Copy + 'a>(
|
|
||||||
&mut self,
|
|
||||||
offset: u64,
|
|
||||||
entries: impl Iterator<Item = &'a T>,
|
|
||||||
mut f: impl FnMut(&mut QcowRawFile, T) -> io::Result<u64>,
|
|
||||||
) -> io::Result<()> {
|
|
||||||
let mut buffer = Vec::with_capacity(entries.size_hint().0 * size_of::<u64>());
|
|
||||||
for addr in entries {
|
|
||||||
let entry = f(self, *addr)?;
|
|
||||||
buffer.extend_from_slice(&entry.to_be_bytes());
|
|
||||||
}
|
|
||||||
self.file.write_all_at(&buffer, offset)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Writes a pointer table directly without transforming values.
|
|
||||||
///
|
|
||||||
/// Uses the same materialize-then-write path as `write_pointer_table`.
|
|
||||||
pub(super) fn write_pointer_table_direct<'a>(
|
|
||||||
&mut self,
|
|
||||||
offset: u64,
|
|
||||||
entries: impl Iterator<Item = &'a u64>,
|
|
||||||
) -> io::Result<()> {
|
|
||||||
let mut buffer = Vec::with_capacity(entries.size_hint().0 * size_of::<u64>());
|
|
||||||
for &entry in entries {
|
|
||||||
buffer.extend_from_slice(&entry.to_be_bytes());
|
|
||||||
}
|
|
||||||
self.file.write_all_at(&buffer, offset)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Read a refcount block from the file and returns a Vec containing the block.
|
|
||||||
/// Always returns a cluster's worth of data.
|
|
||||||
#[inline]
|
|
||||||
pub(super) fn read_refcount_block(&mut self, offset: u64) -> io::Result<Vec<u64>> {
|
|
||||||
(self.read_refcount_fn)(&mut self.file, offset, self.refcount_block_entries as usize)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Writes a refcount block to the file.
|
|
||||||
#[inline]
|
|
||||||
pub(super) fn write_refcount_block(&mut self, offset: u64, table: &[u64]) -> io::Result<()> {
|
|
||||||
(self.write_refcount_fn)(&mut self.file, offset, table)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Allocates a new cluster at the end of the current file, return the address.
|
|
||||||
pub(super) fn add_cluster_end(
|
|
||||||
&mut self,
|
|
||||||
max_valid_cluster_offset: u64,
|
|
||||||
) -> io::Result<Option<u64>> {
|
|
||||||
// Determine where the new end of the file should be and set_len, which
|
|
||||||
// translates to truncate(2).
|
|
||||||
let file_end: u64 = self.physical_size()?;
|
|
||||||
let new_cluster_address: u64 = (file_end + self.cluster_size - 1) & !self.cluster_mask;
|
|
||||||
|
|
||||||
if new_cluster_address > max_valid_cluster_offset {
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.file.set_len(new_cluster_address + self.cluster_size)?;
|
|
||||||
|
|
||||||
Ok(Some(new_cluster_address))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a reference to the underlying file.
|
|
||||||
pub(super) fn file(&self) -> &AlignedFile {
|
|
||||||
&self.file
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a mutable reference to the underlying file.
|
|
||||||
pub(super) fn file_mut(&mut self) -> &mut AlignedFile {
|
|
||||||
&mut self.file
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the size of the file's clusters.
|
|
||||||
pub(super) fn cluster_size(&self) -> u64 {
|
|
||||||
self.cluster_size
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the offset of `address` within a cluster.
|
|
||||||
pub(super) fn cluster_offset(&self, address: u64) -> u64 {
|
|
||||||
address & self.cluster_mask
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the base address of the cluster containing `address`.
|
|
||||||
pub(super) fn cluster_address(&self, address: u64) -> u64 {
|
|
||||||
address & !self.cluster_mask
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Zeros out a cluster in the file.
|
|
||||||
pub(super) fn zero_cluster(&mut self, address: u64) -> io::Result<()> {
|
|
||||||
let cluster_size = self.cluster_size as usize;
|
|
||||||
self.file.write_all_zeroes_at(address, cluster_size)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Writes
|
|
||||||
pub(super) fn write_cluster(&mut self, address: u64, data: &[u8]) -> io::Result<()> {
|
|
||||||
let cluster_size = self.cluster_size as usize;
|
|
||||||
self.file.write_all_at(&data[0..cluster_size], address)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn physical_size(&self) -> io::Result<u64> {
|
|
||||||
self.file.metadata().map(|m| m.len())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Clone for QcowRawFile {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
QcowRawFile {
|
|
||||||
file: self.file.try_clone().expect("QcowRawFile cloning failed"),
|
|
||||||
cluster_size: self.cluster_size,
|
|
||||||
cluster_mask: self.cluster_mask,
|
|
||||||
refcount_block_entries: self.refcount_block_entries,
|
|
||||||
read_refcount_fn: self.read_refcount_fn,
|
|
||||||
write_refcount_fn: self.write_refcount_fn,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsRawFd for QcowRawFile {
|
|
||||||
fn as_raw_fd(&self) -> RawFd {
|
|
||||||
self.file.as_raw_fd()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsFd for QcowRawFile {
|
|
||||||
fn as_fd(&self) -> BorrowedFd<'_> {
|
|
||||||
self.file.as_fd()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod unit_tests {
|
|
||||||
use std::io::Read;
|
|
||||||
use std::os::unix::fs::FileExt;
|
|
||||||
|
|
||||||
use vmm_sys_util::tempfile::TempFile;
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
fn be_bytes(entries: &[u64]) -> Vec<u8> {
|
|
||||||
let mut v = Vec::with_capacity(size_of_val(entries));
|
|
||||||
for e in entries {
|
|
||||||
v.extend_from_slice(&e.to_be_bytes());
|
|
||||||
}
|
|
||||||
v
|
|
||||||
}
|
|
||||||
|
|
||||||
fn find_all(haystack: &[u8], needle: &[u8]) -> Vec<usize> {
|
|
||||||
haystack
|
|
||||||
.windows(needle.len())
|
|
||||||
.enumerate()
|
|
||||||
.filter(|(_, w)| *w == needle)
|
|
||||||
.map(|(i, _)| i)
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
const CLUSTER_SIZE: u64 = 0x10000; // 64 KiB
|
|
||||||
const TARGET_OFFSET: u64 = 0x1000; // where the table must be written
|
|
||||||
const FAR_OFFSET: u64 = 0x9000; // where the callback reads (refcount block)
|
|
||||||
const FILE_LEN: u64 = 0x40000; // 256 KiB filler so all offsets are valid
|
|
||||||
|
|
||||||
fn make_qcow_raw() -> (TempFile, QcowRawFile) {
|
|
||||||
make_qcow_raw_bits(16)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn make_qcow_raw_bits(refcount_bits: u64) -> (TempFile, QcowRawFile) {
|
|
||||||
let temp_file = TempFile::new().unwrap();
|
|
||||||
temp_file.as_file().set_len(FILE_LEN).unwrap();
|
|
||||||
|
|
||||||
let file = temp_file.as_file().try_clone().unwrap();
|
|
||||||
let raw = AlignedFile::new(file, false);
|
|
||||||
let qcow_raw =
|
|
||||||
QcowRawFile::from(raw, CLUSTER_SIZE, refcount_bits).expect("QcowRawFile::from");
|
|
||||||
(temp_file, qcow_raw)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn write_pointer_table_lands_at_offset_despite_callback_seek() {
|
|
||||||
let (temp_file, mut qcow) = make_qcow_raw();
|
|
||||||
let entries: Vec<u64> = vec![0x1111_2222_3333_4444u64; 8]; // 64 bytes
|
|
||||||
|
|
||||||
qcow.write_pointer_table(TARGET_OFFSET, entries.iter(), |q, addr| {
|
|
||||||
let _ = q.read_refcount_block(FAR_OFFSET)?;
|
|
||||||
Ok(addr)
|
|
||||||
})
|
|
||||||
.expect("write_pointer_table");
|
|
||||||
|
|
||||||
let expected = be_bytes(&entries);
|
|
||||||
let mut verify = temp_file.as_file().try_clone().unwrap();
|
|
||||||
let mut whole = Vec::new();
|
|
||||||
verify.read_to_end(&mut whole).unwrap();
|
|
||||||
let found_at = find_all(&whole, &expected);
|
|
||||||
|
|
||||||
let mut at_target = vec![0u8; expected.len()];
|
|
||||||
verify.read_exact_at(&mut at_target, TARGET_OFFSET).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
at_target, expected,
|
|
||||||
"pointer table did NOT land at TARGET_OFFSET {TARGET_OFFSET:#x}; \
|
|
||||||
found matching bytes at {found_at:x?}"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn write_pointer_table_direct_lands_at_offset() {
|
|
||||||
let (temp_file, mut qcow) = make_qcow_raw();
|
|
||||||
let entries: Vec<u64> = vec![0xAAAA_BBBB_CCCC_DDDDu64; 8];
|
|
||||||
|
|
||||||
qcow.write_pointer_table_direct(TARGET_OFFSET, entries.iter())
|
|
||||||
.expect("write_pointer_table_direct");
|
|
||||||
|
|
||||||
let expected = be_bytes(&entries);
|
|
||||||
let verify = temp_file.as_file().try_clone().unwrap();
|
|
||||||
let mut at_target = vec![0u8; expected.len()];
|
|
||||||
verify.read_exact_at(&mut at_target, TARGET_OFFSET).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
at_target, expected,
|
|
||||||
"write_pointer_table_direct did not land at {TARGET_OFFSET:#x}"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn read_pointer_table_round_trips() {
|
|
||||||
let (_temp_file, mut qcow) = make_qcow_raw();
|
|
||||||
let entries: Vec<u64> = vec![
|
|
||||||
0x0000_0000_0000_0000,
|
|
||||||
0x0011_2233_4455_6677,
|
|
||||||
0x8899_aabb_ccdd_eeff,
|
|
||||||
0xffff_ffff_ffff_ffff,
|
|
||||||
];
|
|
||||||
|
|
||||||
qcow.write_pointer_table_direct(TARGET_OFFSET, entries.iter())
|
|
||||||
.expect("write_pointer_table_direct");
|
|
||||||
|
|
||||||
let read_back = qcow
|
|
||||||
.read_pointer_table(TARGET_OFFSET, entries.len() as u64, None)
|
|
||||||
.expect("read_pointer_table");
|
|
||||||
|
|
||||||
assert_eq!(read_back, entries);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn read_pointer_table_applies_mask() {
|
|
||||||
let (_temp_file, mut qcow) = make_qcow_raw();
|
|
||||||
let entries: Vec<u64> = vec![0xffff_ffff_ffff_ffffu64; 4];
|
|
||||||
let mask = 0x00ff_ffff_ffff_fe00u64;
|
|
||||||
|
|
||||||
qcow.write_pointer_table_direct(TARGET_OFFSET, entries.iter())
|
|
||||||
.expect("write_pointer_table_direct");
|
|
||||||
|
|
||||||
let read_back = qcow
|
|
||||||
.read_pointer_table(TARGET_OFFSET, entries.len() as u64, Some(mask))
|
|
||||||
.expect("read_pointer_table");
|
|
||||||
|
|
||||||
assert!(read_back.iter().all(|&e| e == mask));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn write_cluster_then_zero_cluster_round_trips() {
|
|
||||||
let (temp_file, mut qcow) = make_qcow_raw();
|
|
||||||
let cluster_size = CLUSTER_SIZE as usize;
|
|
||||||
let data: Vec<u8> = (0..cluster_size).map(|i| (i % 251) as u8).collect();
|
|
||||||
|
|
||||||
qcow.write_cluster(CLUSTER_SIZE, &data)
|
|
||||||
.expect("write_cluster");
|
|
||||||
|
|
||||||
let verify = temp_file.as_file().try_clone().unwrap();
|
|
||||||
let mut buf = vec![0u8; cluster_size];
|
|
||||||
verify.read_exact_at(&mut buf, CLUSTER_SIZE).unwrap();
|
|
||||||
assert_eq!(buf, data);
|
|
||||||
|
|
||||||
qcow.zero_cluster(CLUSTER_SIZE).expect("zero_cluster");
|
|
||||||
|
|
||||||
verify.read_exact_at(&mut buf, CLUSTER_SIZE).unwrap();
|
|
||||||
assert!(buf.iter().all(|&b| b == 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn refcount_block_round_trips() {
|
|
||||||
let (_temp_file, mut qcow) = make_qcow_raw_bits(16);
|
|
||||||
let count = qcow.refcount_block_entries as usize;
|
|
||||||
let table: Vec<u64> = (0..count).map(|i| (i % 251) as u64).collect();
|
|
||||||
|
|
||||||
qcow.write_refcount_block(TARGET_OFFSET, &table)
|
|
||||||
.expect("write_refcount_block");
|
|
||||||
let read_back = qcow
|
|
||||||
.read_refcount_block(TARGET_OFFSET)
|
|
||||||
.expect("read_refcount_block");
|
|
||||||
|
|
||||||
assert_eq!(read_back, table);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn refcount_block_subbyte_round_trips() {
|
|
||||||
let (_temp_file, mut qcow) = make_qcow_raw_bits(4);
|
|
||||||
let count = qcow.refcount_block_entries as usize;
|
|
||||||
let table: Vec<u64> = (0..count).map(|i| (i % 16) as u64).collect();
|
|
||||||
|
|
||||||
qcow.write_refcount_block(TARGET_OFFSET, &table)
|
|
||||||
.expect("write_refcount_block");
|
|
||||||
let read_back = qcow
|
|
||||||
.read_refcount_block(TARGET_OFFSET)
|
|
||||||
.expect("read_refcount_block");
|
|
||||||
|
|
||||||
assert_eq!(read_back, table);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn add_cluster_end_appends_aligned_cluster() {
|
|
||||||
let (_temp_file, mut qcow) = make_qcow_raw();
|
|
||||||
let before = qcow.physical_size().unwrap();
|
|
||||||
|
|
||||||
let addr = qcow
|
|
||||||
.add_cluster_end(u64::MAX)
|
|
||||||
.expect("add_cluster_end")
|
|
||||||
.expect("a cluster was allocated");
|
|
||||||
|
|
||||||
assert_eq!(addr % CLUSTER_SIZE, 0);
|
|
||||||
assert!(addr >= before);
|
|
||||||
assert_eq!(qcow.physical_size().unwrap(), addr + CLUSTER_SIZE);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn add_cluster_end_respects_max_offset() {
|
|
||||||
let (_temp_file, mut qcow) = make_qcow_raw();
|
|
||||||
assert!(qcow.add_cluster_end(0).unwrap().is_none());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
// Copyright 2018 The Chromium OS Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
|
||||||
// found in the LICENSE-BSD-3-Clause file.
|
|
||||||
//
|
|
||||||
// Copyright 2026 The Cloud Hypervisor Authors. All rights reserved.
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
|
||||||
|
|
||||||
//! Pure helper functions and constants for QCOW2 L1/L2 table entry
|
|
||||||
//! manipulation and integer arithmetic. Shared across the `qcow` submodules.
|
|
||||||
|
|
||||||
/// Nesting depth limit for disk formats that can open other disk files.
|
|
||||||
pub(crate) const MAX_NESTING_DEPTH: u32 = 10;
|
|
||||||
|
|
||||||
// bits 0-8 and 56-63 are reserved.
|
|
||||||
pub(super) const L1_TABLE_OFFSET_MASK: u64 = 0x00ff_ffff_ffff_fe00;
|
|
||||||
pub(super) const L2_TABLE_OFFSET_MASK: u64 = 0x00ff_ffff_ffff_fe00;
|
|
||||||
// Flags
|
|
||||||
pub(super) const ZERO_FLAG: u64 = 1 << 0;
|
|
||||||
pub(super) const COMPRESSED_FLAG: u64 = 1 << 62;
|
|
||||||
pub(super) const COMPRESSED_SECTOR_SIZE: u64 = 512;
|
|
||||||
pub(super) const CLUSTER_USED_FLAG: u64 = 1 << 63;
|
|
||||||
|
|
||||||
/// Check if L2 entry is empty (unallocated).
|
|
||||||
pub(super) fn l2_entry_is_empty(l2_entry: u64) -> bool {
|
|
||||||
l2_entry == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check bit 0 - only valid for standard clusters.
|
|
||||||
pub(super) fn l2_entry_is_zero(l2_entry: u64) -> bool {
|
|
||||||
l2_entry & ZERO_FLAG != 0
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check if L2 entry refers to a compressed cluster.
|
|
||||||
pub(super) fn l2_entry_is_compressed(l2_entry: u64) -> bool {
|
|
||||||
l2_entry & COMPRESSED_FLAG != 0
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get file offset and size of compressed cluster data.
|
|
||||||
pub(super) fn l2_entry_compressed_cluster_layout(l2_entry: u64, cluster_bits: u32) -> (u64, usize) {
|
|
||||||
let compressed_size_shift = 62 - (cluster_bits - 8);
|
|
||||||
let compressed_size_mask = (1 << (cluster_bits - 8)) - 1;
|
|
||||||
let compressed_cluster_addr = l2_entry & ((1 << compressed_size_shift) - 1);
|
|
||||||
let nsectors = (l2_entry >> compressed_size_shift & compressed_size_mask) + 1;
|
|
||||||
let compressed_cluster_size = ((nsectors * COMPRESSED_SECTOR_SIZE)
|
|
||||||
- (compressed_cluster_addr & (COMPRESSED_SECTOR_SIZE - 1)))
|
|
||||||
as usize;
|
|
||||||
(compressed_cluster_addr, compressed_cluster_size)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get file offset of standard (non-compressed) cluster.
|
|
||||||
pub(super) fn l2_entry_std_cluster_addr(l2_entry: u64) -> u64 {
|
|
||||||
l2_entry & L2_TABLE_OFFSET_MASK
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Make L2 entry for standard (non-compressed) cluster.
|
|
||||||
pub(super) fn l2_entry_make_std(cluster_addr: u64) -> u64 {
|
|
||||||
(cluster_addr & L2_TABLE_OFFSET_MASK) | CLUSTER_USED_FLAG
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Make L2 entry for preallocated zero cluster.
|
|
||||||
pub(super) fn l2_entry_make_zero(cluster_addr: u64) -> u64 {
|
|
||||||
(cluster_addr & L2_TABLE_OFFSET_MASK) | CLUSTER_USED_FLAG | ZERO_FLAG
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Make L2 entry for an unallocated cluster that reads as logical zeros.
|
|
||||||
pub(super) fn l2_entry_make_zero_plain() -> u64 {
|
|
||||||
ZERO_FLAG
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Make L1 entry with optional flags.
|
|
||||||
pub(super) fn l1_entry_make(cluster_addr: u64, refcount_is_one: bool) -> u64 {
|
|
||||||
(cluster_addr & L1_TABLE_OFFSET_MASK) | (refcount_is_one as u64 * CLUSTER_USED_FLAG)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Ceiling of the division of `dividend`/`divisor`.
|
|
||||||
pub(super) fn div_round_up_u32(dividend: u32, divisor: u32) -> u32 {
|
|
||||||
dividend / divisor + u32::from(!dividend.is_multiple_of(divisor))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Ceiling of the division of `dividend`/`divisor`.
|
|
||||||
pub(super) fn div_round_up_u64(dividend: u64, divisor: u64) -> u64 {
|
|
||||||
dividend / divisor + u64::from(!dividend.is_multiple_of(divisor))
|
|
||||||
}
|
|
||||||
@@ -1,149 +0,0 @@
|
|||||||
// Copyright © 2023 Intel Corporation
|
|
||||||
//
|
|
||||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
|
||||||
//
|
|
||||||
// Copyright © 2023 Crusoe Energy Systems LLC
|
|
||||||
//
|
|
||||||
|
|
||||||
use std::os::unix::io::AsRawFd;
|
|
||||||
|
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
|
||||||
|
|
||||||
use super::{operation_is_aligned, run_unaligned_operation};
|
|
||||||
use crate::async_io::{
|
|
||||||
AioDataIo, AsyncIo, AsyncIoCompletion, AsyncIoError, AsyncIoOperation, AsyncIoResult,
|
|
||||||
};
|
|
||||||
use crate::error::{BlockError, BlockErrorKind, BlockResult};
|
|
||||||
use crate::sparse::{punch_hole, write_zeroes};
|
|
||||||
use crate::{AlignedFile, is_block_device};
|
|
||||||
|
|
||||||
pub(super) struct RawAio {
|
|
||||||
raw_file: AlignedFile,
|
|
||||||
data_io: AioDataIo,
|
|
||||||
alignment: u64,
|
|
||||||
is_block_device: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RawAio {
|
|
||||||
pub(super) fn new(raw_file: AlignedFile, queue_depth: u32) -> BlockResult<Self> {
|
|
||||||
let data_io =
|
|
||||||
AioDataIo::new(queue_depth).map_err(|e| BlockError::new(BlockErrorKind::Io, e))?;
|
|
||||||
let is_block_device = is_block_device(raw_file.as_raw_fd());
|
|
||||||
let alignment = raw_file.alignment() as u64;
|
|
||||||
|
|
||||||
Ok(RawAio {
|
|
||||||
raw_file,
|
|
||||||
data_io,
|
|
||||||
alignment,
|
|
||||||
is_block_device,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsyncIo for RawAio {
|
|
||||||
fn notifier(&self) -> &EventFd {
|
|
||||||
self.data_io.notifier()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn alignment(&self) -> u64 {
|
|
||||||
self.alignment
|
|
||||||
}
|
|
||||||
|
|
||||||
fn submit_data_operation(&mut self, op: AsyncIoOperation) -> AsyncIoResult<()> {
|
|
||||||
let is_read = op.is_read();
|
|
||||||
|
|
||||||
if operation_is_aligned(&op, self.alignment) {
|
|
||||||
let fd = self.raw_file.as_raw_fd();
|
|
||||||
return self.data_io.submit_operation(fd, op).map_err(|e| {
|
|
||||||
if is_read {
|
|
||||||
AsyncIoError::ReadVectored(e)
|
|
||||||
} else {
|
|
||||||
AsyncIoError::WriteVectored(e)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let result = run_unaligned_operation(&self.raw_file, &op)?;
|
|
||||||
self.data_io
|
|
||||||
.inject_completion(AsyncIoCompletion::from_operation(op, result));
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fsync(&mut self, user_data: Option<u64>) -> AsyncIoResult<()> {
|
|
||||||
let fd = self.raw_file.as_raw_fd();
|
|
||||||
if let Some(user_data) = user_data {
|
|
||||||
self.data_io
|
|
||||||
.submit_fsync(fd, user_data)
|
|
||||||
.map_err(AsyncIoError::Fsync)?;
|
|
||||||
} else {
|
|
||||||
// SAFETY: FFI call with a valid fd
|
|
||||||
unsafe { libc::fsync(fd) };
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn next_completed_request(&mut self) -> Option<AsyncIoCompletion> {
|
|
||||||
self.data_io.next_completion()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn punch_hole(&mut self, offset: u64, length: u64, user_data: u64) -> AsyncIoResult<()> {
|
|
||||||
// Linux AIO has no IOCB command for fallocate, so perform the
|
|
||||||
// operation synchronously and signal completion via the completion
|
|
||||||
// list, matching the pattern used by the sync backend (RawSync).
|
|
||||||
punch_hole(&mut self.raw_file, self.is_block_device, offset, length)
|
|
||||||
.map_err(AsyncIoError::PunchHole)?;
|
|
||||||
self.data_io
|
|
||||||
.inject_completion(AsyncIoCompletion::new(user_data, 0, None));
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_zeroes(&mut self, offset: u64, length: u64, user_data: u64) -> AsyncIoResult<()> {
|
|
||||||
// Same as punch_hole().
|
|
||||||
write_zeroes(&mut self.raw_file, self.is_block_device, offset, length)
|
|
||||||
.map_err(AsyncIoError::WriteZeroes)?;
|
|
||||||
self.data_io
|
|
||||||
.inject_completion(AsyncIoCompletion::new(user_data, 0, None));
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod unit_tests {
|
|
||||||
use vmm_sys_util::tempfile::TempFile;
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
use crate::formats::raw::tests;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_punch_hole() {
|
|
||||||
let temp_file = TempFile::new().unwrap();
|
|
||||||
let mut file = temp_file.into_file();
|
|
||||||
let mut async_io =
|
|
||||||
RawAio::new(AlignedFile::new(file.try_clone().unwrap(), false), 128).unwrap();
|
|
||||||
tests::test_punch_hole(&mut async_io, &mut file);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_write_zeroes() {
|
|
||||||
let temp_file = TempFile::new().unwrap();
|
|
||||||
let mut file = temp_file.into_file();
|
|
||||||
let mut async_io =
|
|
||||||
RawAio::new(AlignedFile::new(file.try_clone().unwrap(), false), 128).unwrap();
|
|
||||||
tests::test_write_zeroes(&mut async_io, &mut file);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_punch_hole_multiple_operations() {
|
|
||||||
let temp_file = TempFile::new().unwrap();
|
|
||||||
let mut file = temp_file.into_file();
|
|
||||||
let mut async_io =
|
|
||||||
RawAio::new(AlignedFile::new(file.try_clone().unwrap(), false), 128).unwrap();
|
|
||||||
tests::test_punch_hole_multiple_operations(&mut async_io, &mut file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,135 +0,0 @@
|
|||||||
// Copyright © 2021 Intel Corporation
|
|
||||||
//
|
|
||||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
|
||||||
|
|
||||||
use std::io;
|
|
||||||
use std::os::unix::io::AsRawFd;
|
|
||||||
|
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
|
||||||
|
|
||||||
use crate::async_io::{
|
|
||||||
AsyncIo, AsyncIoCompletion, AsyncIoError, AsyncIoOperation, AsyncIoResult, CompletionCommon,
|
|
||||||
};
|
|
||||||
use crate::sparse::{punch_hole, write_zeroes};
|
|
||||||
use crate::{AlignedFile, is_block_device};
|
|
||||||
|
|
||||||
pub(crate) struct RawSync {
|
|
||||||
raw_file: AlignedFile,
|
|
||||||
completions: CompletionCommon,
|
|
||||||
alignment: u64,
|
|
||||||
is_block_device: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RawSync {
|
|
||||||
pub(crate) fn new(raw_file: AlignedFile) -> Self {
|
|
||||||
let is_block_device = is_block_device(raw_file.as_raw_fd());
|
|
||||||
let alignment = raw_file.alignment() as u64;
|
|
||||||
RawSync {
|
|
||||||
raw_file,
|
|
||||||
completions: CompletionCommon::new(),
|
|
||||||
alignment,
|
|
||||||
is_block_device,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsyncIo for RawSync {
|
|
||||||
fn notifier(&self) -> &EventFd {
|
|
||||||
self.completions.notifier()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn alignment(&self) -> u64 {
|
|
||||||
self.alignment
|
|
||||||
}
|
|
||||||
|
|
||||||
fn submit_data_operation(&mut self, op: AsyncIoOperation) -> AsyncIoResult<()> {
|
|
||||||
let is_read = op.is_read();
|
|
||||||
let iovecs = op.iovecs();
|
|
||||||
let offset = op.offset() as u64;
|
|
||||||
|
|
||||||
let result = if is_read {
|
|
||||||
// SAFETY: op.iovecs() describes valid memory for iov_len bytes by
|
|
||||||
// construction of AsyncIoOperation.
|
|
||||||
unsafe { self.raw_file.read_vectored_at(iovecs, offset) }
|
|
||||||
.map_err(AsyncIoError::ReadVectored)?
|
|
||||||
} else {
|
|
||||||
// SAFETY: op.iovecs() describes valid memory for iov_len bytes by
|
|
||||||
// construction of AsyncIoOperation.
|
|
||||||
unsafe { self.raw_file.write_vectored_at(iovecs, offset) }
|
|
||||||
.map_err(AsyncIoError::WriteVectored)?
|
|
||||||
} as i32;
|
|
||||||
self.completions
|
|
||||||
.complete(AsyncIoCompletion::from_operation(op, result));
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fsync(&mut self, user_data: Option<u64>) -> AsyncIoResult<()> {
|
|
||||||
// SAFETY: FFI call
|
|
||||||
let result = unsafe { libc::fsync(self.raw_file.as_raw_fd() as libc::c_int) };
|
|
||||||
if result < 0 {
|
|
||||||
return Err(AsyncIoError::Fsync(io::Error::last_os_error()));
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(user_data) = user_data {
|
|
||||||
self.completions
|
|
||||||
.complete(AsyncIoCompletion::new(user_data, result, None));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn next_completed_request(&mut self) -> Option<AsyncIoCompletion> {
|
|
||||||
self.completions.next_completed()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn punch_hole(&mut self, offset: u64, length: u64, user_data: u64) -> AsyncIoResult<()> {
|
|
||||||
punch_hole(&mut self.raw_file, self.is_block_device, offset, length)
|
|
||||||
.map_err(AsyncIoError::PunchHole)?;
|
|
||||||
self.completions
|
|
||||||
.complete(AsyncIoCompletion::new(user_data, 0, None));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_zeroes(&mut self, offset: u64, length: u64, user_data: u64) -> AsyncIoResult<()> {
|
|
||||||
write_zeroes(&mut self.raw_file, self.is_block_device, offset, length)
|
|
||||||
.map_err(AsyncIoError::WriteZeroes)?;
|
|
||||||
self.completions
|
|
||||||
.complete(AsyncIoCompletion::new(user_data, 0, None));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod unit_tests {
|
|
||||||
use vmm_sys_util::tempfile::TempFile;
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
use crate::formats::raw::tests;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_punch_hole() {
|
|
||||||
let temp_file = TempFile::new().unwrap();
|
|
||||||
let mut file = temp_file.into_file();
|
|
||||||
let mut async_io = RawSync::new(AlignedFile::new(file.try_clone().unwrap(), false));
|
|
||||||
tests::test_punch_hole(&mut async_io, &mut file);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_write_zeroes() {
|
|
||||||
let temp_file = TempFile::new().unwrap();
|
|
||||||
let mut file = temp_file.into_file();
|
|
||||||
let mut async_io = RawSync::new(AlignedFile::new(file.try_clone().unwrap(), false));
|
|
||||||
tests::test_write_zeroes(&mut async_io, &mut file);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_punch_hole_multiple_operations() {
|
|
||||||
let temp_file = TempFile::new().unwrap();
|
|
||||||
let mut file = temp_file.into_file();
|
|
||||||
let mut async_io = RawSync::new(AlignedFile::new(file.try_clone().unwrap(), false));
|
|
||||||
tests::test_punch_hole_multiple_operations(&mut async_io, &mut file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,144 +0,0 @@
|
|||||||
// Copyright © 2021 Intel Corporation
|
|
||||||
//
|
|
||||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
|
||||||
|
|
||||||
use std::os::unix::io::AsRawFd;
|
|
||||||
|
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
|
||||||
|
|
||||||
use super::{operation_is_aligned, run_unaligned_operation};
|
|
||||||
use crate::async_io::{
|
|
||||||
AsyncIo, AsyncIoCompletion, AsyncIoError, AsyncIoOperation, AsyncIoResult, UringDataIo,
|
|
||||||
};
|
|
||||||
use crate::error::{BlockError, BlockErrorKind, BlockResult};
|
|
||||||
use crate::sparse::{punch_hole, write_zeroes};
|
|
||||||
use crate::{AlignedFile, is_block_device};
|
|
||||||
|
|
||||||
pub(crate) struct RawAsync {
|
|
||||||
raw_file: AlignedFile,
|
|
||||||
data_io: UringDataIo,
|
|
||||||
alignment: u64,
|
|
||||||
is_block_device: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RawAsync {
|
|
||||||
pub(crate) fn new(raw_file: AlignedFile, ring_depth: u32) -> BlockResult<Self> {
|
|
||||||
let data_io =
|
|
||||||
UringDataIo::new(ring_depth).map_err(|e| BlockError::new(BlockErrorKind::Io, e))?;
|
|
||||||
let is_block_device = is_block_device(raw_file.as_raw_fd());
|
|
||||||
let alignment = raw_file.alignment() as u64;
|
|
||||||
|
|
||||||
Ok(RawAsync {
|
|
||||||
raw_file,
|
|
||||||
data_io,
|
|
||||||
alignment,
|
|
||||||
is_block_device,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsyncIo for RawAsync {
|
|
||||||
fn notifier(&self) -> &EventFd {
|
|
||||||
self.data_io.notifier()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn alignment(&self) -> u64 {
|
|
||||||
self.alignment
|
|
||||||
}
|
|
||||||
|
|
||||||
fn submit_data_operation(&mut self, op: AsyncIoOperation) -> AsyncIoResult<()> {
|
|
||||||
let is_read = op.is_read();
|
|
||||||
|
|
||||||
if operation_is_aligned(&op, self.alignment) {
|
|
||||||
let fd = self.raw_file.as_raw_fd();
|
|
||||||
return self.data_io.submit_operation(fd, op).map_err(|e| {
|
|
||||||
if is_read {
|
|
||||||
AsyncIoError::ReadVectored(e)
|
|
||||||
} else {
|
|
||||||
AsyncIoError::WriteVectored(e)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let result = run_unaligned_operation(&self.raw_file, &op)?;
|
|
||||||
self.data_io
|
|
||||||
.inject_completion(AsyncIoCompletion::from_operation(op, result));
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fsync(&mut self, user_data: Option<u64>) -> AsyncIoResult<()> {
|
|
||||||
let fd = self.raw_file.as_raw_fd();
|
|
||||||
if let Some(user_data) = user_data {
|
|
||||||
self.data_io
|
|
||||||
.submit_fsync(fd, user_data)
|
|
||||||
.map_err(AsyncIoError::Fsync)?;
|
|
||||||
} else {
|
|
||||||
// SAFETY: FFI call with a valid fd
|
|
||||||
unsafe { libc::fsync(fd) };
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn next_completed_request(&mut self) -> Option<AsyncIoCompletion> {
|
|
||||||
self.data_io.next_completion()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn batch_requests_enabled(&self) -> bool {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
fn submit_batch_requests(&mut self, batch_request: Vec<AsyncIoOperation>) -> AsyncIoResult<()> {
|
|
||||||
if self.alignment != 0 {
|
|
||||||
let mut aligned_batch = Vec::with_capacity(batch_request.len());
|
|
||||||
for op in batch_request {
|
|
||||||
if operation_is_aligned(&op, self.alignment) {
|
|
||||||
aligned_batch.push(op);
|
|
||||||
} else {
|
|
||||||
let result = run_unaligned_operation(&self.raw_file, &op)?;
|
|
||||||
self.data_io
|
|
||||||
.inject_completion(AsyncIoCompletion::from_operation(op, result));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if aligned_batch.is_empty() {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
return self
|
|
||||||
.data_io
|
|
||||||
.submit_batch(self.raw_file.as_raw_fd(), aligned_batch)
|
|
||||||
.map_err(AsyncIoError::SubmitBatchRequests);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.data_io
|
|
||||||
.submit_batch(self.raw_file.as_raw_fd(), batch_request)
|
|
||||||
.map_err(AsyncIoError::SubmitBatchRequests)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn punch_hole(&mut self, offset: u64, length: u64, user_data: u64) -> AsyncIoResult<()> {
|
|
||||||
// Run synchronously rather than submitting a fallocate request through
|
|
||||||
// the ring. This avoids reaping ENOTSUPP in the completion routine and
|
|
||||||
// reissuing the request, and lets the sparse helper handle the ioctl
|
|
||||||
// path for block devices and the write fallback for unsupported
|
|
||||||
// filesystems.
|
|
||||||
punch_hole(&mut self.raw_file, self.is_block_device, offset, length)
|
|
||||||
.map_err(AsyncIoError::PunchHole)?;
|
|
||||||
// Deliver the completion through the normal io_uring path by
|
|
||||||
// queuing a NOP carrying `user_data`. The registered eventfd will
|
|
||||||
// fire when it completes, just like any other request.
|
|
||||||
self.data_io
|
|
||||||
.submit_nop(user_data)
|
|
||||||
.map_err(AsyncIoError::PunchHole)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_zeroes(&mut self, offset: u64, length: u64, user_data: u64) -> AsyncIoResult<()> {
|
|
||||||
// Same rationale as punch_hole().
|
|
||||||
write_zeroes(&mut self.raw_file, self.is_block_device, offset, length)
|
|
||||||
.map_err(AsyncIoError::WriteZeroes)?;
|
|
||||||
self.data_io
|
|
||||||
.submit_nop(user_data)
|
|
||||||
.map_err(AsyncIoError::WriteZeroes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,323 +0,0 @@
|
|||||||
// Copyright 2026 The Cloud Hypervisor Authors. All rights reserved.
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
|
||||||
|
|
||||||
//! Raw disk image format.
|
|
||||||
//!
|
|
||||||
//! Provides [`RawDisk`], the `DiskFile` wrapper for flat disk images
|
|
||||||
//! with no metadata or copy on write layer.
|
|
||||||
|
|
||||||
use std::fs::File;
|
|
||||||
use std::io;
|
|
||||||
use std::os::unix::fs::FileTypeExt;
|
|
||||||
use std::os::unix::io::AsRawFd;
|
|
||||||
|
|
||||||
use log::warn;
|
|
||||||
|
|
||||||
use self::engine_aio::RawAio;
|
|
||||||
use self::engine_sync::RawSync;
|
|
||||||
#[cfg(feature = "io_uring")]
|
|
||||||
use self::engine_uring::RawAsync;
|
|
||||||
use crate::async_io::{
|
|
||||||
AsyncIo, AsyncIoError, AsyncIoOperation, AsyncIoResult, BorrowedDiskFd, DiskFileError,
|
|
||||||
};
|
|
||||||
use crate::error::{BlockError, BlockErrorKind, BlockResult};
|
|
||||||
use crate::{AlignedFile, DiskTopology, disk_file, probe_sparse_support, query_device_size};
|
|
||||||
|
|
||||||
mod engine_aio;
|
|
||||||
pub(crate) mod engine_sync;
|
|
||||||
#[cfg(feature = "io_uring")]
|
|
||||||
pub(crate) mod engine_uring;
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests;
|
|
||||||
|
|
||||||
/// Selects which async I/O backend a `RawDisk` uses.
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
|
||||||
pub enum RawBackend {
|
|
||||||
/// Blocking I/O where the caller waits for completion.
|
|
||||||
Sync,
|
|
||||||
/// Modern asynchronous I/O using shared submission and completion
|
|
||||||
/// rings for lower overhead operation dispatch and completion handling.
|
|
||||||
#[cfg(feature = "io_uring")]
|
|
||||||
IoUring,
|
|
||||||
/// Legacy asynchronous I/O where requests are handed to the kernel
|
|
||||||
/// and completions are collected later.
|
|
||||||
Aio,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Unified DiskFile wrapper for raw disk images.
|
|
||||||
///
|
|
||||||
/// Owns the underlying file and delegates async I/O creation to the
|
|
||||||
/// backend selected at construction time via [`RawBackend`].
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct RawDisk {
|
|
||||||
file: File,
|
|
||||||
backend: RawBackend,
|
|
||||||
direct: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RawDisk {
|
|
||||||
pub fn new(file: File, backend: RawBackend, direct: bool) -> Self {
|
|
||||||
Self {
|
|
||||||
file,
|
|
||||||
backend,
|
|
||||||
direct,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl disk_file::DiskSize for RawDisk {
|
|
||||||
fn logical_size(&self) -> BlockResult<u64> {
|
|
||||||
query_device_size(&self.file)
|
|
||||||
.map(|(logical_size, _)| logical_size)
|
|
||||||
.map_err(|e| BlockError::new(BlockErrorKind::Io, DiskFileError::Size(e)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl disk_file::PhysicalSize for RawDisk {
|
|
||||||
fn physical_size(&self) -> BlockResult<u64> {
|
|
||||||
query_device_size(&self.file)
|
|
||||||
.map(|(_, physical_size)| physical_size)
|
|
||||||
.map_err(|e| BlockError::new(BlockErrorKind::Io, DiskFileError::Size(e)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl disk_file::DiskFd for RawDisk {
|
|
||||||
fn fd(&self) -> BorrowedDiskFd<'_> {
|
|
||||||
BorrowedDiskFd::new(self.file.as_raw_fd())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl disk_file::Geometry for RawDisk {
|
|
||||||
fn topology(&self) -> DiskTopology {
|
|
||||||
DiskTopology::probe(&self.file).unwrap_or_else(|_| {
|
|
||||||
warn!("Unable to get device topology. Using default topology");
|
|
||||||
DiskTopology::default()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl disk_file::SparseCapable for RawDisk {
|
|
||||||
fn supports_sparse_operations(&self) -> bool {
|
|
||||||
probe_sparse_support(&self.file)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl disk_file::Resizable for RawDisk {
|
|
||||||
fn resize(&mut self, size: u64) -> BlockResult<()> {
|
|
||||||
let fd_metadata = self
|
|
||||||
.file
|
|
||||||
.metadata()
|
|
||||||
.map_err(|e| BlockError::new(BlockErrorKind::Io, DiskFileError::ResizeError(e)))?;
|
|
||||||
|
|
||||||
if fd_metadata.file_type().is_block_device() {
|
|
||||||
// Block devices cannot be resized via ftruncate; they are resized
|
|
||||||
// externally (LVM, losetup, etc.). Verify the size matches.
|
|
||||||
let (actual_size, _) = query_device_size(&self.file)
|
|
||||||
.map_err(|e| BlockError::new(BlockErrorKind::Io, DiskFileError::ResizeError(e)))?;
|
|
||||||
if actual_size != size {
|
|
||||||
return Err(BlockError::new(
|
|
||||||
BlockErrorKind::Io,
|
|
||||||
DiskFileError::ResizeError(io::Error::other(format!(
|
|
||||||
"Block device size {actual_size} does not match requested size {size}"
|
|
||||||
))),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
self.file
|
|
||||||
.set_len(size)
|
|
||||||
.map_err(|e| BlockError::new(BlockErrorKind::Io, DiskFileError::ResizeError(e)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl disk_file::MetadataSync for RawDisk {}
|
|
||||||
|
|
||||||
impl disk_file::DiskFile for RawDisk {}
|
|
||||||
|
|
||||||
impl disk_file::AsyncDiskFile for RawDisk {
|
|
||||||
fn try_clone(&self) -> BlockResult<Box<dyn disk_file::AsyncDiskFile>> {
|
|
||||||
let file = self
|
|
||||||
.file
|
|
||||||
.try_clone()
|
|
||||||
.map_err(|e| BlockError::new(BlockErrorKind::Io, DiskFileError::Clone(e)))?;
|
|
||||||
Ok(Box::new(RawDisk {
|
|
||||||
file,
|
|
||||||
backend: self.backend,
|
|
||||||
direct: self.direct,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_async_io(&self, ring_depth: u32) -> BlockResult<Box<dyn AsyncIo>> {
|
|
||||||
let file = self
|
|
||||||
.file
|
|
||||||
.try_clone()
|
|
||||||
.map_err(|e| BlockError::new(BlockErrorKind::Io, DiskFileError::Clone(e)))?;
|
|
||||||
let raw_file = AlignedFile::new(file, self.direct);
|
|
||||||
match self.backend {
|
|
||||||
RawBackend::Sync => Ok(Box::new(RawSync::new(raw_file))),
|
|
||||||
#[cfg(feature = "io_uring")]
|
|
||||||
RawBackend::IoUring => Ok(Box::new(RawAsync::new(raw_file, ring_depth)?)),
|
|
||||||
RawBackend::Aio => Ok(Box::new(RawAio::new(raw_file, ring_depth)?)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// True when `op` satisfies `alignment` and can go straight to the kernel.
|
|
||||||
fn operation_is_aligned(op: &AsyncIoOperation, alignment: u64) -> bool {
|
|
||||||
if alignment == 0 {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if !(op.offset() as u64).is_multiple_of(alignment) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
op.iovecs().iter().all(|iov| {
|
|
||||||
(iov.iov_base as u64).is_multiple_of(alignment)
|
|
||||||
&& (iov.iov_len as u64).is_multiple_of(alignment)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Runs an unaligned O_DIRECT operation synchronously through `aligned_file`.
|
|
||||||
fn run_unaligned_operation(
|
|
||||||
aligned_file: &AlignedFile,
|
|
||||||
op: &AsyncIoOperation,
|
|
||||||
) -> AsyncIoResult<i32> {
|
|
||||||
let offset = op.offset() as u64;
|
|
||||||
let iovecs = op.iovecs();
|
|
||||||
|
|
||||||
let n = if op.is_read() {
|
|
||||||
// SAFETY: op.iovecs() describes valid memory for iov_len bytes by
|
|
||||||
// construction of AsyncIoOperation.
|
|
||||||
unsafe { aligned_file.read_vectored_at(iovecs, offset) }
|
|
||||||
.map_err(AsyncIoError::ReadVectored)?
|
|
||||||
} else {
|
|
||||||
// SAFETY: op.iovecs() describes valid memory for iov_len bytes by
|
|
||||||
// construction of AsyncIoOperation.
|
|
||||||
unsafe { aligned_file.write_vectored_at(iovecs, offset) }
|
|
||||||
.map_err(AsyncIoError::WriteVectored)?
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(n as i32)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod unit_tests {
|
|
||||||
use std::fs::File;
|
|
||||||
|
|
||||||
use vmm_sys_util::tempfile::TempFile;
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
use crate::async_io::AsyncIo;
|
|
||||||
use crate::disk_file::{AsyncDiskFile, DiskSize, PhysicalSize, Resizable};
|
|
||||||
|
|
||||||
const TEST_SIZE: u64 = 0x1122_3344;
|
|
||||||
|
|
||||||
fn make_raw_file() -> File {
|
|
||||||
let file: File = TempFile::new().unwrap().into_file();
|
|
||||||
file.set_len(TEST_SIZE).unwrap();
|
|
||||||
file
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn new_sync_returns_correct_size() {
|
|
||||||
let file = make_raw_file();
|
|
||||||
let disk = RawDisk::new(file, RawBackend::Sync, false);
|
|
||||||
assert_eq!(disk.logical_size().unwrap(), TEST_SIZE);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn assert_async_io_from_dyn(disk: &dyn AsyncDiskFile, expect_backend: RawBackend) {
|
|
||||||
let io: Box<dyn AsyncIo> = disk.create_async_io(128).unwrap();
|
|
||||||
cfg_if::cfg_if! {
|
|
||||||
if #[cfg(feature = "io_uring")] {
|
|
||||||
let expected_batch_requests = expect_backend == RawBackend::IoUring;
|
|
||||||
} else {
|
|
||||||
let _ = expect_backend;
|
|
||||||
let expected_batch_requests = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assert_eq!(io.batch_requests_enabled(), expected_batch_requests);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn assert_sync_backend(disk: &RawDisk) {
|
|
||||||
assert_eq!(disk.backend, RawBackend::Sync);
|
|
||||||
assert_async_io_from_dyn(disk, RawBackend::Sync);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn assert_aio_backend(disk: &RawDisk) {
|
|
||||||
assert_eq!(disk.backend, RawBackend::Aio);
|
|
||||||
assert_async_io_from_dyn(disk, RawBackend::Aio);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "io_uring")]
|
|
||||||
fn assert_io_uring_backend(disk: &RawDisk) {
|
|
||||||
assert_eq!(disk.backend, RawBackend::IoUring);
|
|
||||||
assert_async_io_from_dyn(disk, RawBackend::IoUring);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn sync_backend_disables_batch_requests() {
|
|
||||||
let file = make_raw_file();
|
|
||||||
let disk = RawDisk::new(file, RawBackend::Sync, false);
|
|
||||||
assert_sync_backend(&disk);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn aio_backend_disables_batch_requests() {
|
|
||||||
let file = make_raw_file();
|
|
||||||
let disk = RawDisk::new(file, RawBackend::Aio, false);
|
|
||||||
assert_aio_backend(&disk);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "io_uring")]
|
|
||||||
#[test]
|
|
||||||
fn io_uring_backend_enables_batch_requests() {
|
|
||||||
let file = make_raw_file();
|
|
||||||
let disk = RawDisk::new(file, RawBackend::IoUring, false);
|
|
||||||
assert_io_uring_backend(&disk);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn assert_try_clone(disk: &RawDisk, expect_backend: RawBackend) {
|
|
||||||
let cloned = disk.try_clone().unwrap();
|
|
||||||
assert_async_io_from_dyn(cloned.as_ref(), expect_backend);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn try_clone_preserves_sync_backend() {
|
|
||||||
let file = make_raw_file();
|
|
||||||
let disk = RawDisk::new(file, RawBackend::Sync, false);
|
|
||||||
assert_try_clone(&disk, RawBackend::Sync);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn try_clone_preserves_aio_backend() {
|
|
||||||
let file = make_raw_file();
|
|
||||||
let disk = RawDisk::new(file, RawBackend::Aio, false);
|
|
||||||
assert_try_clone(&disk, RawBackend::Aio);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "io_uring")]
|
|
||||||
#[test]
|
|
||||||
fn try_clone_preserves_io_uring_backend() {
|
|
||||||
let file = make_raw_file();
|
|
||||||
let disk = RawDisk::new(file, RawBackend::IoUring, false);
|
|
||||||
assert_try_clone(&disk, RawBackend::IoUring);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn resize_changes_file_size() {
|
|
||||||
let file = make_raw_file();
|
|
||||||
let mut disk = RawDisk::new(file, RawBackend::Aio, false);
|
|
||||||
let new_size = TEST_SIZE * 2;
|
|
||||||
disk.resize(new_size).unwrap();
|
|
||||||
assert_eq!(disk.logical_size().unwrap(), new_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn physical_size_reports_allocated_blocks() {
|
|
||||||
let file = make_raw_file();
|
|
||||||
let disk = RawDisk::new(file, RawBackend::Aio, false);
|
|
||||||
// Sparse file: physical size is less than logical size.
|
|
||||||
assert!(disk.physical_size().unwrap() < disk.logical_size().unwrap());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,158 +0,0 @@
|
|||||||
// Copyright 2026 The Cloud Hypervisor Authors. All rights reserved.
|
|
||||||
//
|
|
||||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
|
||||||
|
|
||||||
//! Shared test helpers for [`AsyncIo`] backends.
|
|
||||||
//!
|
|
||||||
//! Each helper takes a `&mut dyn AsyncIo` together with the [`File`] handle
|
|
||||||
//! that backs the I/O object, so the same logic exercises every backend with
|
|
||||||
//! only the constructor differing.
|
|
||||||
|
|
||||||
use std::fs::File;
|
|
||||||
use std::io::{Read, Seek, SeekFrom, Write};
|
|
||||||
|
|
||||||
use crate::async_io::AsyncIo;
|
|
||||||
|
|
||||||
fn next_completion(async_io: &mut dyn AsyncIo) -> (u64, i32) {
|
|
||||||
let completion = async_io.next_completed_request().expect("No completion");
|
|
||||||
(completion.user_data, completion.result)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Tests punching a hole in the middle of a 4 MB file and verifying data
|
|
||||||
/// integrity around the hole.
|
|
||||||
pub fn test_punch_hole(async_io: &mut dyn AsyncIo, file: &mut File) {
|
|
||||||
// Write 4MB of data
|
|
||||||
let data = vec![0xAA; 4 * 1024 * 1024];
|
|
||||||
file.write_all(&data).unwrap();
|
|
||||||
file.sync_all().unwrap();
|
|
||||||
|
|
||||||
// Punch hole in the middle (1MB at offset 1MB)
|
|
||||||
let offset = 1024 * 1024;
|
|
||||||
let length = 1024 * 1024;
|
|
||||||
async_io.punch_hole(offset, length, 1).unwrap();
|
|
||||||
|
|
||||||
// Check completion
|
|
||||||
let (user_data, result) = next_completion(async_io);
|
|
||||||
assert_eq!(user_data, 1);
|
|
||||||
assert_eq!(result, 0);
|
|
||||||
|
|
||||||
// Verify the hole reads as zeros
|
|
||||||
file.seek(SeekFrom::Start(offset)).unwrap();
|
|
||||||
let mut read_buf = vec![0; length as usize];
|
|
||||||
file.read_exact(&mut read_buf).unwrap();
|
|
||||||
assert!(
|
|
||||||
read_buf.iter().all(|&b| b == 0),
|
|
||||||
"Punched hole should read as zeros"
|
|
||||||
);
|
|
||||||
|
|
||||||
// Verify data before hole is intact
|
|
||||||
file.seek(SeekFrom::Start(0)).unwrap();
|
|
||||||
let mut read_buf = vec![0; 1024];
|
|
||||||
file.read_exact(&mut read_buf).unwrap();
|
|
||||||
assert!(
|
|
||||||
read_buf.iter().all(|&b| b == 0xAA),
|
|
||||||
"Data before hole should be intact"
|
|
||||||
);
|
|
||||||
|
|
||||||
// Verify data after hole is intact
|
|
||||||
file.seek(SeekFrom::Start(offset + length)).unwrap();
|
|
||||||
let mut read_buf = vec![0; 1024];
|
|
||||||
file.read_exact(&mut read_buf).unwrap();
|
|
||||||
assert!(
|
|
||||||
read_buf.iter().all(|&b| b == 0xAA),
|
|
||||||
"Data after hole should be intact"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Tests writing zeroes to a 512 KB region inside a 4 MB file and verifying
|
|
||||||
/// surrounding data is preserved.
|
|
||||||
pub fn test_write_zeroes(async_io: &mut dyn AsyncIo, file: &mut File) {
|
|
||||||
// Write 4MB of data
|
|
||||||
let data = vec![0xBB; 4 * 1024 * 1024];
|
|
||||||
file.write_all(&data).unwrap();
|
|
||||||
file.sync_all().unwrap();
|
|
||||||
|
|
||||||
// Write zeros in the middle (512KB at offset 2MB)
|
|
||||||
let offset = 2 * 1024 * 1024;
|
|
||||||
let length = 512 * 1024;
|
|
||||||
async_io.write_zeroes(offset, length, 2).unwrap();
|
|
||||||
|
|
||||||
// Check completion
|
|
||||||
let (user_data, result) = next_completion(async_io);
|
|
||||||
assert_eq!(user_data, 2);
|
|
||||||
assert_eq!(result, 0);
|
|
||||||
|
|
||||||
// Verify the zeroed region reads as zeros
|
|
||||||
file.seek(SeekFrom::Start(offset)).unwrap();
|
|
||||||
let mut read_buf = vec![0; length as usize];
|
|
||||||
file.read_exact(&mut read_buf).unwrap();
|
|
||||||
assert!(
|
|
||||||
read_buf.iter().all(|&b| b == 0),
|
|
||||||
"Zeroed region should read as zeros"
|
|
||||||
);
|
|
||||||
|
|
||||||
// Verify data before zeroed region is intact
|
|
||||||
file.seek(SeekFrom::Start(offset - 1024)).unwrap();
|
|
||||||
let mut read_buf = vec![0; 1024];
|
|
||||||
file.read_exact(&mut read_buf).unwrap();
|
|
||||||
assert!(
|
|
||||||
read_buf.iter().all(|&b| b == 0xBB),
|
|
||||||
"Data before zeroed region should be intact"
|
|
||||||
);
|
|
||||||
|
|
||||||
// Verify data after zeroed region is intact
|
|
||||||
file.seek(SeekFrom::Start(offset + length)).unwrap();
|
|
||||||
let mut read_buf = vec![0; 1024];
|
|
||||||
file.read_exact(&mut read_buf).unwrap();
|
|
||||||
assert!(
|
|
||||||
read_buf.iter().all(|&b| b == 0xBB),
|
|
||||||
"Data after zeroed region should be intact"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Tests punching multiple holes in an 8 MB file and verifying each hole
|
|
||||||
/// independently reads as zeroes.
|
|
||||||
pub fn test_punch_hole_multiple_operations(async_io: &mut dyn AsyncIo, file: &mut File) {
|
|
||||||
// Write 8MB of data
|
|
||||||
let data = vec![0xCC; 8 * 1024 * 1024];
|
|
||||||
file.write_all(&data).unwrap();
|
|
||||||
file.sync_all().unwrap();
|
|
||||||
|
|
||||||
// Punch multiple holes
|
|
||||||
async_io.punch_hole(1024 * 1024, 512 * 1024, 10).unwrap();
|
|
||||||
async_io
|
|
||||||
.punch_hole(3 * 1024 * 1024, 512 * 1024, 11)
|
|
||||||
.unwrap();
|
|
||||||
async_io
|
|
||||||
.punch_hole(5 * 1024 * 1024, 512 * 1024, 12)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Check all completions
|
|
||||||
let (user_data, result) = next_completion(async_io);
|
|
||||||
assert_eq!(user_data, 10);
|
|
||||||
assert_eq!(result, 0);
|
|
||||||
|
|
||||||
let (user_data, result) = next_completion(async_io);
|
|
||||||
assert_eq!(user_data, 11);
|
|
||||||
assert_eq!(result, 0);
|
|
||||||
|
|
||||||
let (user_data, result) = next_completion(async_io);
|
|
||||||
assert_eq!(user_data, 12);
|
|
||||||
assert_eq!(result, 0);
|
|
||||||
|
|
||||||
// Verify all holes read as zeros
|
|
||||||
file.seek(SeekFrom::Start(1024 * 1024)).unwrap();
|
|
||||||
let mut read_buf = vec![0; 512 * 1024];
|
|
||||||
file.read_exact(&mut read_buf).unwrap();
|
|
||||||
assert!(read_buf.iter().all(|&b| b == 0));
|
|
||||||
|
|
||||||
file.seek(SeekFrom::Start(3 * 1024 * 1024)).unwrap();
|
|
||||||
file.read_exact(&mut read_buf).unwrap();
|
|
||||||
assert!(read_buf.iter().all(|&b| b == 0));
|
|
||||||
|
|
||||||
file.seek(SeekFrom::Start(5 * 1024 * 1024)).unwrap();
|
|
||||||
file.read_exact(&mut read_buf).unwrap();
|
|
||||||
assert!(read_buf.iter().all(|&b| b == 0));
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
// Copyright © 2021 Intel Corporation
|
|
||||||
//
|
|
||||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
use std::io;
|
|
||||||
|
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
|
||||||
|
|
||||||
use crate::AlignedFile;
|
|
||||||
use crate::async_io::{AsyncIo, AsyncIoCompletion, AsyncIoError, AsyncIoOperation, AsyncIoResult};
|
|
||||||
use crate::formats::raw::engine_sync::RawSync;
|
|
||||||
|
|
||||||
pub(super) struct FixedVhdSync {
|
|
||||||
raw_file_sync: RawSync,
|
|
||||||
size: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FixedVhdSync {
|
|
||||||
pub(super) fn new(raw_file: AlignedFile, size: u64) -> Self {
|
|
||||||
FixedVhdSync {
|
|
||||||
raw_file_sync: RawSync::new(raw_file),
|
|
||||||
size,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsyncIo for FixedVhdSync {
|
|
||||||
fn notifier(&self) -> &EventFd {
|
|
||||||
self.raw_file_sync.notifier()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn submit_data_operation(&mut self, op: AsyncIoOperation) -> AsyncIoResult<()> {
|
|
||||||
op.validate_bounds(self.size)?;
|
|
||||||
self.raw_file_sync.submit_data_operation(op)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fsync(&mut self, user_data: Option<u64>) -> AsyncIoResult<()> {
|
|
||||||
self.raw_file_sync.fsync(user_data)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn next_completed_request(&mut self) -> Option<AsyncIoCompletion> {
|
|
||||||
self.raw_file_sync.next_completed_request()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn punch_hole(&mut self, _offset: u64, _length: u64, _user_data: u64) -> AsyncIoResult<()> {
|
|
||||||
Err(AsyncIoError::PunchHole(io::Error::other(
|
|
||||||
"punch_hole not supported for fixed VHD",
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_zeroes(&mut self, _offset: u64, _length: u64, _user_data: u64) -> AsyncIoResult<()> {
|
|
||||||
Err(AsyncIoError::WriteZeroes(io::Error::other(
|
|
||||||
"write_zeroes not supported for fixed VHD",
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
// Copyright © 2021 Intel Corporation
|
|
||||||
//
|
|
||||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
use std::io;
|
|
||||||
|
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
|
||||||
|
|
||||||
use crate::AlignedFile;
|
|
||||||
use crate::async_io::{AsyncIo, AsyncIoCompletion, AsyncIoError, AsyncIoOperation, AsyncIoResult};
|
|
||||||
use crate::error::BlockResult;
|
|
||||||
use crate::formats::raw::engine_uring::RawAsync;
|
|
||||||
|
|
||||||
pub(super) struct FixedVhdAsync {
|
|
||||||
raw_file_async: RawAsync,
|
|
||||||
size: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FixedVhdAsync {
|
|
||||||
pub(super) fn new(raw_file: AlignedFile, ring_depth: u32, size: u64) -> BlockResult<Self> {
|
|
||||||
let raw_file_async = RawAsync::new(raw_file, ring_depth)?;
|
|
||||||
|
|
||||||
Ok(FixedVhdAsync {
|
|
||||||
raw_file_async,
|
|
||||||
size,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsyncIo for FixedVhdAsync {
|
|
||||||
fn notifier(&self) -> &EventFd {
|
|
||||||
self.raw_file_async.notifier()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn submit_data_operation(&mut self, op: AsyncIoOperation) -> AsyncIoResult<()> {
|
|
||||||
op.validate_bounds(self.size)?;
|
|
||||||
self.raw_file_async.submit_data_operation(op)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fsync(&mut self, user_data: Option<u64>) -> AsyncIoResult<()> {
|
|
||||||
self.raw_file_async.fsync(user_data)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn next_completed_request(&mut self) -> Option<AsyncIoCompletion> {
|
|
||||||
self.raw_file_async.next_completed_request()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn punch_hole(&mut self, _offset: u64, _length: u64, _user_data: u64) -> AsyncIoResult<()> {
|
|
||||||
Err(AsyncIoError::PunchHole(io::Error::other(
|
|
||||||
"punch_hole not supported for fixed VHD",
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_zeroes(&mut self, _offset: u64, _length: u64, _user_data: u64) -> AsyncIoResult<()> {
|
|
||||||
Err(AsyncIoError::WriteZeroes(io::Error::other(
|
|
||||||
"write_zeroes not supported for fixed VHD",
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn batch_requests_enabled(&self) -> bool {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
fn submit_batch_requests(&mut self, batch_request: Vec<AsyncIoOperation>) -> AsyncIoResult<()> {
|
|
||||||
for op in &batch_request {
|
|
||||||
op.validate_bounds(self.size)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.raw_file_async.submit_batch_requests(batch_request)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
// Copyright © 2021 Intel Corporation
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
use std::fs::File;
|
|
||||||
use std::io;
|
|
||||||
use std::os::unix::io::{AsRawFd, RawFd};
|
|
||||||
|
|
||||||
use super::footer::VhdFooter;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub(super) struct FixedVhd {
|
|
||||||
file: File,
|
|
||||||
size: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FixedVhd {
|
|
||||||
pub(super) fn new(mut file: File) -> io::Result<Self> {
|
|
||||||
let footer = VhdFooter::new(&mut file)?;
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
file,
|
|
||||||
size: footer.current_size(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn file(&self) -> &File {
|
|
||||||
&self.file
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsRawFd for FixedVhd {
|
|
||||||
fn as_raw_fd(&self) -> RawFd {
|
|
||||||
self.file.as_raw_fd()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FixedVhd {
|
|
||||||
pub(crate) fn logical_size(&self) -> Result<u64, crate::Error> {
|
|
||||||
Ok(self.size)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the physical size of the underlying file.
|
|
||||||
pub(crate) fn physical_size(&self) -> Result<u64, crate::Error> {
|
|
||||||
self.file
|
|
||||||
.metadata()
|
|
||||||
.map(|m| m.len())
|
|
||||||
.map_err(crate::Error::GetFileMetadata)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Clone for FixedVhd {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
Self {
|
|
||||||
file: self.file.try_clone().expect("FixedVhd cloning failed"),
|
|
||||||
size: self.size,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,243 +0,0 @@
|
|||||||
// Copyright © 2021 Intel Corporation
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
use std::fs::File;
|
|
||||||
use std::io;
|
|
||||||
use std::os::unix::fs::FileExt;
|
|
||||||
|
|
||||||
use crate::{AlignedFile, query_device_size};
|
|
||||||
|
|
||||||
// Production code uses: cookie, file_format_version, data_offset,
|
|
||||||
// current_size, disk_type. The remaining fields are parsed for VHD
|
|
||||||
// spec completeness and exercised only by unit tests.
|
|
||||||
#[derive(Clone, Copy)]
|
|
||||||
#[cfg_attr(not(test), expect(dead_code))]
|
|
||||||
pub(super) struct VhdFooter {
|
|
||||||
cookie: u64,
|
|
||||||
features: u32,
|
|
||||||
file_format_version: u32,
|
|
||||||
data_offset: u64,
|
|
||||||
time_stamp: u32,
|
|
||||||
creator_application: u32,
|
|
||||||
creator_version: u32,
|
|
||||||
creator_host_os: u32,
|
|
||||||
original_size: u64,
|
|
||||||
current_size: u64,
|
|
||||||
disk_geometry: u32,
|
|
||||||
disk_type: u32,
|
|
||||||
checksum: u32,
|
|
||||||
unique_id: u128,
|
|
||||||
saved_state: u8,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VhdFooter {
|
|
||||||
pub(super) fn new(file: &mut File) -> io::Result<VhdFooter> {
|
|
||||||
let aligned = AlignedFile::new(file.try_clone()?, true);
|
|
||||||
let size = query_device_size(file)?.0;
|
|
||||||
let footer_offset = size.checked_sub(512).ok_or_else(|| {
|
|
||||||
io::Error::new(io::ErrorKind::InvalidInput, "file too small for VHD footer")
|
|
||||||
})?;
|
|
||||||
let mut sector = [0u8; 512];
|
|
||||||
aligned.read_exact_at(&mut sector, footer_offset)?;
|
|
||||||
|
|
||||||
Ok(VhdFooter {
|
|
||||||
cookie: u64::from_be_bytes(sector[0..8].try_into().unwrap()),
|
|
||||||
features: u32::from_be_bytes(sector[8..12].try_into().unwrap()),
|
|
||||||
file_format_version: u32::from_be_bytes(sector[12..16].try_into().unwrap()),
|
|
||||||
data_offset: u64::from_be_bytes(sector[16..24].try_into().unwrap()),
|
|
||||||
time_stamp: u32::from_be_bytes(sector[24..28].try_into().unwrap()),
|
|
||||||
creator_application: u32::from_be_bytes(sector[28..32].try_into().unwrap()),
|
|
||||||
creator_version: u32::from_be_bytes(sector[32..36].try_into().unwrap()),
|
|
||||||
creator_host_os: u32::from_be_bytes(sector[36..40].try_into().unwrap()),
|
|
||||||
original_size: u64::from_be_bytes(sector[40..48].try_into().unwrap()),
|
|
||||||
current_size: u64::from_be_bytes(sector[48..56].try_into().unwrap()),
|
|
||||||
disk_geometry: u32::from_be_bytes(sector[56..60].try_into().unwrap()),
|
|
||||||
disk_type: u32::from_be_bytes(sector[60..64].try_into().unwrap()),
|
|
||||||
checksum: u32::from_be_bytes(sector[64..68].try_into().unwrap()),
|
|
||||||
unique_id: u128::from_be_bytes(sector[68..84].try_into().unwrap()),
|
|
||||||
saved_state: u8::from_be_bytes(sector[84..85].try_into().unwrap()),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn cookie(&self) -> u64 {
|
|
||||||
self.cookie
|
|
||||||
}
|
|
||||||
#[cfg(test)]
|
|
||||||
pub fn features(&self) -> u32 {
|
|
||||||
self.features
|
|
||||||
}
|
|
||||||
pub(super) fn file_format_version(&self) -> u32 {
|
|
||||||
self.file_format_version
|
|
||||||
}
|
|
||||||
pub(super) fn data_offset(&self) -> u64 {
|
|
||||||
self.data_offset
|
|
||||||
}
|
|
||||||
#[cfg(test)]
|
|
||||||
pub fn time_stamp(&self) -> u32 {
|
|
||||||
self.time_stamp
|
|
||||||
}
|
|
||||||
#[cfg(test)]
|
|
||||||
pub fn creator_application(&self) -> u32 {
|
|
||||||
self.creator_application
|
|
||||||
}
|
|
||||||
#[cfg(test)]
|
|
||||||
pub fn creator_version(&self) -> u32 {
|
|
||||||
self.creator_version
|
|
||||||
}
|
|
||||||
#[cfg(test)]
|
|
||||||
pub fn creator_host_os(&self) -> u32 {
|
|
||||||
self.creator_host_os
|
|
||||||
}
|
|
||||||
#[cfg(test)]
|
|
||||||
pub fn original_size(&self) -> u64 {
|
|
||||||
self.original_size
|
|
||||||
}
|
|
||||||
pub(super) fn current_size(&self) -> u64 {
|
|
||||||
self.current_size
|
|
||||||
}
|
|
||||||
#[cfg(test)]
|
|
||||||
pub fn disk_geometry(&self) -> u32 {
|
|
||||||
self.disk_geometry
|
|
||||||
}
|
|
||||||
pub(super) fn disk_type(&self) -> u32 {
|
|
||||||
self.disk_type
|
|
||||||
}
|
|
||||||
#[cfg(test)]
|
|
||||||
pub fn checksum(&self) -> u32 {
|
|
||||||
self.checksum
|
|
||||||
}
|
|
||||||
#[cfg(test)]
|
|
||||||
pub fn unique_id(&self) -> u128 {
|
|
||||||
self.unique_id
|
|
||||||
}
|
|
||||||
#[cfg(test)]
|
|
||||||
pub fn saved_state(&self) -> u8 {
|
|
||||||
self.saved_state
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Determine image type through file parsing.
|
|
||||||
pub fn is_fixed_vhd(f: &mut File) -> io::Result<bool> {
|
|
||||||
let footer = match VhdFooter::new(f) {
|
|
||||||
Ok(footer) => footer,
|
|
||||||
Err(e) if e.kind() == io::ErrorKind::InvalidInput => return Ok(false),
|
|
||||||
Err(e) => return Err(e),
|
|
||||||
};
|
|
||||||
|
|
||||||
// "conectix" => 0x636f6e6563746978
|
|
||||||
Ok(footer.cookie() == 0x636f6e6563746978
|
|
||||||
&& footer.file_format_version() == 0x0001_0000
|
|
||||||
&& footer.data_offset() == 0xffff_ffff_ffff_ffff
|
|
||||||
&& footer.disk_type() == 0x2)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod unit_tests {
|
|
||||||
use std::fs::File;
|
|
||||||
use std::io::{Seek, SeekFrom, Write};
|
|
||||||
|
|
||||||
use vmm_sys_util::tempfile::TempFile;
|
|
||||||
|
|
||||||
use super::{VhdFooter, is_fixed_vhd};
|
|
||||||
|
|
||||||
fn valid_fixed_vhd_footer() -> Vec<u8> {
|
|
||||||
vec![
|
|
||||||
0x63, 0x6f, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x78, // cookie
|
|
||||||
0x00, 0x00, 0x00, 0x02, // features
|
|
||||||
0x00, 0x01, 0x00, 0x00, // file format version
|
|
||||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // data offset
|
|
||||||
0x27, 0xa6, 0xa6, 0x5d, // time stamp
|
|
||||||
0x71, 0x65, 0x6d, 0x75, // creator application
|
|
||||||
0x00, 0x05, 0x00, 0x03, // creator version
|
|
||||||
0x57, 0x69, 0x32, 0x6b, // creator host os
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, // original size
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, // current size
|
|
||||||
0x11, 0xe0, 0x10, 0x3f, // disk geometry
|
|
||||||
0x00, 0x00, 0x00, 0x02, // disk type
|
|
||||||
0x00, 0x00, 0x00, 0x00, // checksum
|
|
||||||
0x98, 0x7b, 0xb1, 0xcd, 0x84, 0x14, 0x41, 0xfc, 0xa4, 0xab, 0xd0, 0x69, 0x45, 0x2b,
|
|
||||||
0xf2, 0x23, // unique id
|
|
||||||
0x00, // saved state
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn valid_dynamic_vhd_footer() -> Vec<u8> {
|
|
||||||
vec![
|
|
||||||
0x63, 0x6f, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x78, // cookie
|
|
||||||
0x00, 0x00, 0x00, 0x02, // features
|
|
||||||
0x00, 0x01, 0x00, 0x00, // file format version
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // data offset
|
|
||||||
0x27, 0xa6, 0xa6, 0x5d, // time stamp
|
|
||||||
0x71, 0x65, 0x6d, 0x75, // creator application
|
|
||||||
0x00, 0x05, 0x00, 0x03, // creator version
|
|
||||||
0x57, 0x69, 0x32, 0x6b, // creator host os
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, // original size
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, // current size
|
|
||||||
0x11, 0xe0, 0x10, 0x3f, // disk geometry
|
|
||||||
0x00, 0x00, 0x00, 0x03, // disk type
|
|
||||||
0x00, 0x00, 0x00, 0x00, // checksum
|
|
||||||
0x98, 0x7b, 0xb1, 0xcd, 0x84, 0x14, 0x41, 0xfc, 0xa4, 0xab, 0xd0, 0x69, 0x45, 0x2b,
|
|
||||||
0xf2, 0x23, // unique id
|
|
||||||
0x00, // saved state
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn with_file<F>(footer: &[u8], mut testfn: F)
|
|
||||||
where
|
|
||||||
F: FnMut(File),
|
|
||||||
{
|
|
||||||
let mut disk_file: File = TempFile::new().unwrap().into_file();
|
|
||||||
disk_file.set_len(0x1000_0200).unwrap();
|
|
||||||
disk_file.seek(SeekFrom::Start(0x1000_0000)).unwrap();
|
|
||||||
disk_file.write_all(footer).unwrap();
|
|
||||||
|
|
||||||
testfn(disk_file); // File closed when the function exits.
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_check_vhd_footer() {
|
|
||||||
with_file(&valid_fixed_vhd_footer(), |mut file: File| {
|
|
||||||
let vhd_footer = VhdFooter::new(&mut file).expect("Failed to create VHD footer");
|
|
||||||
assert_eq!(vhd_footer.cookie(), 0x636f_6e65_6374_6978);
|
|
||||||
assert_eq!(vhd_footer.features(), 0x0000_0002);
|
|
||||||
assert_eq!(vhd_footer.file_format_version(), 0x0001_0000);
|
|
||||||
assert_eq!(vhd_footer.data_offset(), 0xffff_ffff_ffff_ffff);
|
|
||||||
assert_eq!(vhd_footer.time_stamp(), 0x27a6_a65d);
|
|
||||||
assert_eq!(vhd_footer.creator_application(), 0x7165_6d75);
|
|
||||||
assert_eq!(vhd_footer.creator_version(), 0x0005_0003);
|
|
||||||
assert_eq!(vhd_footer.creator_host_os(), 0x5769_326b);
|
|
||||||
assert_eq!(vhd_footer.original_size(), 0x0000_0000_1000_0000);
|
|
||||||
assert_eq!(vhd_footer.current_size(), 0x0000_0000_1000_0000);
|
|
||||||
assert_eq!(vhd_footer.disk_geometry(), 0x11e0_103f);
|
|
||||||
assert_eq!(vhd_footer.disk_type(), 0x0000_0002);
|
|
||||||
assert_eq!(vhd_footer.checksum(), 0x0000_0000);
|
|
||||||
assert_eq!(
|
|
||||||
vhd_footer.unique_id(),
|
|
||||||
0x987b_b1cd_8414_41fc_a4ab_d069_452b_f223
|
|
||||||
);
|
|
||||||
assert_eq!(vhd_footer.saved_state(), 0x00);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_is_fixed_vhd() {
|
|
||||||
with_file(&valid_fixed_vhd_footer(), |mut file: File| {
|
|
||||||
assert!(is_fixed_vhd(&mut file).unwrap());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_is_not_fixed_vhd() {
|
|
||||||
with_file(&valid_dynamic_vhd_footer(), |mut file: File| {
|
|
||||||
assert!(!(is_fixed_vhd(&mut file).unwrap()));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_is_fixed_vhd_short_file_is_not_vhd() {
|
|
||||||
let mut file: File = TempFile::new().unwrap().into_file();
|
|
||||||
file.write_all(b"# Disk DescriptorFile\n").unwrap();
|
|
||||||
assert!(!is_fixed_vhd(&mut file).unwrap());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,334 +0,0 @@
|
|||||||
// Copyright 2026 The Cloud Hypervisor Authors. All rights reserved.
|
|
||||||
//
|
|
||||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
//! Fixed VHD disk image format.
|
|
||||||
//!
|
|
||||||
//! Provides [`VhdDisk`], the `DiskFile` wrapper for fixed size VHD
|
|
||||||
//! images.
|
|
||||||
|
|
||||||
mod engine_sync;
|
|
||||||
#[cfg(feature = "io_uring")]
|
|
||||||
mod engine_uring;
|
|
||||||
mod fixed;
|
|
||||||
mod footer;
|
|
||||||
|
|
||||||
use std::fs::File;
|
|
||||||
use std::io;
|
|
||||||
use std::os::unix::io::AsRawFd;
|
|
||||||
|
|
||||||
pub use footer::is_fixed_vhd;
|
|
||||||
use log::warn;
|
|
||||||
|
|
||||||
use self::engine_sync::FixedVhdSync;
|
|
||||||
#[cfg(feature = "io_uring")]
|
|
||||||
use self::engine_uring::FixedVhdAsync;
|
|
||||||
use self::fixed::FixedVhd;
|
|
||||||
use crate::async_io::{AsyncIo, BorrowedDiskFd, DiskFileError};
|
|
||||||
use crate::disk_file::DiskSize;
|
|
||||||
use crate::error::{BlockError, BlockErrorKind, BlockResult, ErrorOp};
|
|
||||||
use crate::{AlignedFile, DiskTopology, Error, disk_file};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct VhdDisk {
|
|
||||||
inner: FixedVhd,
|
|
||||||
use_io_uring: bool,
|
|
||||||
direct: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VhdDisk {
|
|
||||||
pub fn new(file: File, use_io_uring: bool, direct: bool) -> BlockResult<Self> {
|
|
||||||
#[cfg(not(feature = "io_uring"))]
|
|
||||||
if use_io_uring {
|
|
||||||
return Err(BlockError::new(
|
|
||||||
BlockErrorKind::UnsupportedFeature,
|
|
||||||
DiskFileError::NewAsyncIo(io::Error::other(
|
|
||||||
"io_uring requested but feature is not enabled",
|
|
||||||
)),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
inner: FixedVhd::new(file).map_err(|e| BlockError::from(e).with_op(ErrorOp::Open))?,
|
|
||||||
use_io_uring,
|
|
||||||
direct,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl disk_file::DiskSize for VhdDisk {
|
|
||||||
fn logical_size(&self) -> BlockResult<u64> {
|
|
||||||
self.inner
|
|
||||||
.logical_size()
|
|
||||||
.map_err(|e| BlockError::new(BlockErrorKind::Io, e))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl disk_file::PhysicalSize for VhdDisk {
|
|
||||||
fn physical_size(&self) -> BlockResult<u64> {
|
|
||||||
self.inner.physical_size().map_err(|e| match e {
|
|
||||||
Error::GetFileMetadata(io) => {
|
|
||||||
BlockError::new(BlockErrorKind::Io, Error::GetFileMetadata(io))
|
|
||||||
}
|
|
||||||
_ => unreachable!("unexpected error from FixedVhd::physical_size(): {e}"),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl disk_file::DiskFd for VhdDisk {
|
|
||||||
fn fd(&self) -> BorrowedDiskFd<'_> {
|
|
||||||
BorrowedDiskFd::new(self.inner.as_raw_fd())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl disk_file::Geometry for VhdDisk {
|
|
||||||
fn topology(&self) -> DiskTopology {
|
|
||||||
DiskTopology::probe(self.inner.file()).unwrap_or_else(|_| {
|
|
||||||
warn!("Unable to get device topology. Using default topology");
|
|
||||||
DiskTopology::default()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl disk_file::SparseCapable for VhdDisk {}
|
|
||||||
|
|
||||||
impl disk_file::Resizable for VhdDisk {
|
|
||||||
fn resize(&mut self, _size: u64) -> BlockResult<()> {
|
|
||||||
Err(BlockError::new(
|
|
||||||
BlockErrorKind::UnsupportedFeature,
|
|
||||||
DiskFileError::ResizeError(io::Error::other("resize not supported for fixed VHD")),
|
|
||||||
)
|
|
||||||
.with_op(ErrorOp::Resize))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl disk_file::MetadataSync for VhdDisk {}
|
|
||||||
|
|
||||||
impl disk_file::DiskFile for VhdDisk {}
|
|
||||||
|
|
||||||
impl disk_file::AsyncDiskFile for VhdDisk {
|
|
||||||
fn try_clone(&self) -> BlockResult<Box<dyn disk_file::AsyncDiskFile>> {
|
|
||||||
Ok(Box::new(VhdDisk {
|
|
||||||
inner: self.inner.clone(),
|
|
||||||
use_io_uring: self.use_io_uring,
|
|
||||||
direct: self.direct,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_async_io(&self, ring_depth: u32) -> BlockResult<Box<dyn AsyncIo>> {
|
|
||||||
let size = self.logical_size()?;
|
|
||||||
let file = self.inner.file().try_clone().map_err(|e| {
|
|
||||||
BlockError::new(BlockErrorKind::Io, DiskFileError::NewAsyncIo(e)).with_op(ErrorOp::Open)
|
|
||||||
})?;
|
|
||||||
let raw_file = AlignedFile::new(file, self.direct);
|
|
||||||
|
|
||||||
if self.use_io_uring {
|
|
||||||
#[cfg(feature = "io_uring")]
|
|
||||||
{
|
|
||||||
return Ok(Box::new(FixedVhdAsync::new(raw_file, ring_depth, size)?));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "io_uring"))]
|
|
||||||
unreachable!("use_io_uring is set but io_uring feature is not enabled");
|
|
||||||
}
|
|
||||||
|
|
||||||
let _ = ring_depth;
|
|
||||||
Ok(Box::new(FixedVhdSync::new(raw_file, size)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod unit_tests {
|
|
||||||
use std::fs::File;
|
|
||||||
use std::io::{Seek, SeekFrom, Write};
|
|
||||||
|
|
||||||
use vmm_sys_util::tempfile::TempFile;
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
use crate::async_io::{AsyncIo, AsyncIoError, AsyncIoOperation, OwnedIoBuffer};
|
|
||||||
use crate::disk_file::{AsyncDiskFile, DiskSize, PhysicalSize, Resizable};
|
|
||||||
|
|
||||||
/// Minimal fixed VHD footer (disk type = 2, current_size = 0x11223344).
|
|
||||||
fn fixed_vhd_footer() -> &'static [u8] {
|
|
||||||
&[
|
|
||||||
0x63, 0x6f, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x78, // cookie
|
|
||||||
0x00, 0x00, 0x00, 0x02, // features
|
|
||||||
0x00, 0x01, 0x00, 0x00, // file format version
|
|
||||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // data offset
|
|
||||||
0x27, 0xa6, 0xa6, 0x5d, // time stamp
|
|
||||||
0x71, 0x65, 0x6d, 0x75, // creator application
|
|
||||||
0x00, 0x05, 0x00, 0x03, // creator version
|
|
||||||
0x57, 0x69, 0x32, 0x6b, // creator host os
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x11, 0x22, 0x33, 0x44, // original size
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x11, 0x22, 0x33, 0x44, // current size
|
|
||||||
0x11, 0xe0, 0x10, 0x3f, // disk geometry
|
|
||||||
0x00, 0x00, 0x00, 0x02, // disk type
|
|
||||||
0x00, 0x00, 0x00, 0x00, // checksum
|
|
||||||
0x98, 0x7b, 0xb1, 0xcd, 0x84, 0x14, 0x41, 0xfc, // unique id
|
|
||||||
0xa4, 0xab, 0xd0, 0x69, 0x45, 0x2b, 0xf2, 0x23, 0x00, // saved state
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn make_vhd_file() -> File {
|
|
||||||
let mut file: File = TempFile::new().unwrap().into_file();
|
|
||||||
let data_size: u64 = 0x1122_3344;
|
|
||||||
file.set_len(data_size + 0x200).unwrap();
|
|
||||||
file.seek(SeekFrom::Start(data_size)).unwrap();
|
|
||||||
file.write_all(fixed_vhd_footer()).unwrap();
|
|
||||||
file
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn new_sync_returns_correct_size() {
|
|
||||||
let file = make_vhd_file();
|
|
||||||
let disk = VhdDisk::new(file, false, false).unwrap();
|
|
||||||
assert_eq!(disk.logical_size().unwrap(), 0x1122_3344);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn assert_async_io_from_dyn(disk: &dyn AsyncDiskFile, expect_batch: bool) {
|
|
||||||
let io: Box<dyn AsyncIo> = disk.create_async_io(128).unwrap();
|
|
||||||
assert_eq!(io.batch_requests_enabled(), expect_batch);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn assert_async_io(disk: &VhdDisk, expect_batch: bool) {
|
|
||||||
assert_async_io_from_dyn(disk, expect_batch);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn sync_backend_disables_batch_requests() {
|
|
||||||
let file = make_vhd_file();
|
|
||||||
let disk = VhdDisk::new(file, false, false).unwrap();
|
|
||||||
assert_async_io(&disk, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "io_uring")]
|
|
||||||
#[test]
|
|
||||||
fn io_uring_backend_enables_batch_requests() {
|
|
||||||
let file = make_vhd_file();
|
|
||||||
let disk = VhdDisk::new(file, true, false).unwrap();
|
|
||||||
assert_async_io(&disk, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn sync_rejects_read_straddling_logical_size() {
|
|
||||||
let file = TempFile::new().unwrap().into_file();
|
|
||||||
file.set_len(0x2000).unwrap();
|
|
||||||
let mut sync_io =
|
|
||||||
FixedVhdSync::new(AlignedFile::new(file.try_clone().unwrap(), false), 0x1000);
|
|
||||||
let op = AsyncIoOperation::read_to_vec(0x800, OwnedIoBuffer::from_vec(vec![0; 0x900]), 1);
|
|
||||||
|
|
||||||
assert!(matches!(
|
|
||||||
sync_io.submit_data_operation(op),
|
|
||||||
Err(AsyncIoError::ReadVectored(_))
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn sync_rejects_write_straddling_logical_size() {
|
|
||||||
let file = TempFile::new().unwrap().into_file();
|
|
||||||
file.set_len(0x2000).unwrap();
|
|
||||||
let mut sync_io =
|
|
||||||
FixedVhdSync::new(AlignedFile::new(file.try_clone().unwrap(), false), 0x1000);
|
|
||||||
let op =
|
|
||||||
AsyncIoOperation::write_from_vec(0x800, OwnedIoBuffer::from_vec(vec![0; 0x900]), 1);
|
|
||||||
|
|
||||||
assert!(matches!(
|
|
||||||
sync_io.submit_data_operation(op),
|
|
||||||
Err(AsyncIoError::WriteVectored(_))
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn sync_accepts_operation_exactly_filling_logical_size() {
|
|
||||||
let file = TempFile::new().unwrap().into_file();
|
|
||||||
file.set_len(0x2000).unwrap();
|
|
||||||
let mut sync_io =
|
|
||||||
FixedVhdSync::new(AlignedFile::new(file.try_clone().unwrap(), false), 0x1000);
|
|
||||||
// end == size: boundary must be accepted
|
|
||||||
let op = AsyncIoOperation::read_to_vec(0, OwnedIoBuffer::from_vec(vec![0; 0x1000]), 1);
|
|
||||||
sync_io.submit_data_operation(op).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn sync_accepts_operation_at_last_byte() {
|
|
||||||
let file = TempFile::new().unwrap().into_file();
|
|
||||||
file.set_len(0x2000).unwrap();
|
|
||||||
let mut sync_io =
|
|
||||||
FixedVhdSync::new(AlignedFile::new(file.try_clone().unwrap(), false), 0x1000);
|
|
||||||
// end = 0xFFF + 1 = 0x1000 == size: boundary must be accepted
|
|
||||||
let op = AsyncIoOperation::read_to_vec(0xFFF, OwnedIoBuffer::from_vec(vec![0; 1]), 1);
|
|
||||||
sync_io.submit_data_operation(op).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "io_uring")]
|
|
||||||
#[test]
|
|
||||||
fn io_uring_batch_rejects_request_straddling_logical_size() {
|
|
||||||
let file = TempFile::new().unwrap().into_file();
|
|
||||||
file.set_len(0x2000).unwrap();
|
|
||||||
let mut async_io = FixedVhdAsync::new(
|
|
||||||
AlignedFile::new(file.try_clone().unwrap(), false),
|
|
||||||
8,
|
|
||||||
0x1000,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
let op = AsyncIoOperation::read_to_vec(0x800, OwnedIoBuffer::from_vec(vec![0; 0x900]), 1);
|
|
||||||
|
|
||||||
assert!(matches!(
|
|
||||||
async_io.submit_batch_requests(vec![op]),
|
|
||||||
Err(AsyncIoError::ReadVectored(_))
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "io_uring")]
|
|
||||||
#[test]
|
|
||||||
fn io_uring_rejects_single_op_straddling_logical_size() {
|
|
||||||
let file = TempFile::new().unwrap().into_file();
|
|
||||||
file.set_len(0x2000).unwrap();
|
|
||||||
let mut async_io = FixedVhdAsync::new(
|
|
||||||
AlignedFile::new(file.try_clone().unwrap(), false),
|
|
||||||
8,
|
|
||||||
0x1000,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
let op = AsyncIoOperation::read_to_vec(0x800, OwnedIoBuffer::from_vec(vec![0; 0x900]), 1);
|
|
||||||
|
|
||||||
assert!(matches!(
|
|
||||||
async_io.submit_data_operation(op),
|
|
||||||
Err(AsyncIoError::ReadVectored(_))
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn try_clone_preserves_sync_dispatch() {
|
|
||||||
let file = make_vhd_file();
|
|
||||||
let disk = VhdDisk::new(file, false, false).unwrap();
|
|
||||||
let cloned = disk.try_clone().unwrap();
|
|
||||||
assert_async_io_from_dyn(cloned.as_ref(), false);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "io_uring")]
|
|
||||||
#[test]
|
|
||||||
fn try_clone_preserves_io_uring_dispatch() {
|
|
||||||
let file = make_vhd_file();
|
|
||||||
let disk = VhdDisk::new(file, true, false).unwrap();
|
|
||||||
let cloned = disk.try_clone().unwrap();
|
|
||||||
assert_async_io_from_dyn(cloned.as_ref(), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn resize_returns_error() {
|
|
||||||
let file = make_vhd_file();
|
|
||||||
let mut disk = VhdDisk::new(file, false, false).unwrap();
|
|
||||||
assert!(disk.resize(0x2000_0000).is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn physical_size_includes_footer() {
|
|
||||||
let file = make_vhd_file();
|
|
||||||
let disk = VhdDisk::new(file, false, false).unwrap();
|
|
||||||
// Data region (0x1122_3344) + VHD footer (0x200).
|
|
||||||
assert_eq!(disk.physical_size().unwrap(), 0x1122_3344 + 0x200);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
// Copyright © 2021 Intel Corporation
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
use std::os::unix::fs::FileExt;
|
|
||||||
use std::{io, result};
|
|
||||||
|
|
||||||
use byteorder::{ByteOrder, LittleEndian};
|
|
||||||
use remain::sorted;
|
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
use super::header::RegionTableEntry;
|
|
||||||
use super::metadata::DiskSpec;
|
|
||||||
use crate::aligned_file::AlignedFile;
|
|
||||||
|
|
||||||
// Payload BAT Entry States
|
|
||||||
pub(super) const PAYLOAD_BLOCK_NOT_PRESENT: u64 = 0;
|
|
||||||
pub(super) const PAYLOAD_BLOCK_UNDEFINED: u64 = 1;
|
|
||||||
pub(super) const PAYLOAD_BLOCK_ZERO: u64 = 2;
|
|
||||||
pub(super) const PAYLOAD_BLOCK_UNMAPPED: u64 = 3;
|
|
||||||
pub(super) const PAYLOAD_BLOCK_FULLY_PRESENT: u64 = 6;
|
|
||||||
pub(super) const PAYLOAD_BLOCK_PARTIALLY_PRESENT: u64 = 7;
|
|
||||||
|
|
||||||
// Mask for the BAT state
|
|
||||||
pub(super) const BAT_STATE_BIT_MASK: u64 = 0x07;
|
|
||||||
// Mask for the offset within the file in units of 1 MB
|
|
||||||
pub(super) const BAT_FILE_OFF_MASK: u64 = 0xFFFFFFFFFFF00000;
|
|
||||||
|
|
||||||
#[sorted]
|
|
||||||
#[derive(Error, Debug)]
|
|
||||||
pub enum VhdxBatError {
|
|
||||||
#[error("Invalid BAT entry")]
|
|
||||||
InvalidBatEntry,
|
|
||||||
#[error("Invalid BAT entry count")]
|
|
||||||
InvalidEntryCount,
|
|
||||||
#[error("Failed to read BAT entry")]
|
|
||||||
ReadBat(#[source] io::Error),
|
|
||||||
#[error("Failed to write BAT entry")]
|
|
||||||
WriteBat(#[source] io::Error),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) type Result<T> = result::Result<T, VhdxBatError>;
|
|
||||||
|
|
||||||
#[derive(Default, Clone, Debug)]
|
|
||||||
pub(super) struct BatEntry(pub u64);
|
|
||||||
|
|
||||||
impl BatEntry {
|
|
||||||
// Read all BAT entries presented on the disk and insert them to a vector
|
|
||||||
pub(super) fn collect_bat_entries(
|
|
||||||
f: &AlignedFile,
|
|
||||||
disk_spec: &DiskSpec,
|
|
||||||
bat_entry: &RegionTableEntry,
|
|
||||||
) -> Result<Vec<BatEntry>> {
|
|
||||||
let entry_count = BatEntry::calculate_entries(
|
|
||||||
disk_spec.block_size,
|
|
||||||
disk_spec.virtual_disk_size,
|
|
||||||
disk_spec.chunk_ratio,
|
|
||||||
);
|
|
||||||
if entry_count as usize > (bat_entry.length as usize / size_of::<BatEntry>()) {
|
|
||||||
return Err(VhdxBatError::InvalidEntryCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut bat: Vec<BatEntry> = Vec::with_capacity(bat_entry.length as usize);
|
|
||||||
let offset = bat_entry.file_offset;
|
|
||||||
for i in 0..entry_count {
|
|
||||||
let mut entry = [0u8; size_of::<u64>()];
|
|
||||||
f.read_exact_at(&mut entry, offset + i * size_of::<u64>() as u64)
|
|
||||||
.map_err(VhdxBatError::ReadBat)?;
|
|
||||||
bat.insert(i as usize, BatEntry(LittleEndian::read_u64(&entry)));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(bat)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate the number of entries in the BAT
|
|
||||||
fn calculate_entries(block_size: u32, virtual_disk_size: u64, chunk_ratio: u64) -> u64 {
|
|
||||||
let data_blocks_count = virtual_disk_size.div_ceil(block_size as u64);
|
|
||||||
data_blocks_count + (data_blocks_count - 1) / chunk_ratio
|
|
||||||
}
|
|
||||||
|
|
||||||
// Routine for writing BAT entries to the disk
|
|
||||||
pub(super) fn write_bat_entries(
|
|
||||||
f: &AlignedFile,
|
|
||||||
bat_offset: u64,
|
|
||||||
bat_entries: &[BatEntry],
|
|
||||||
) -> Result<()> {
|
|
||||||
for i in 0..bat_entries.len() as u64 {
|
|
||||||
let bat_entry = match bat_entries.get(i as usize) {
|
|
||||||
Some(entry) => entry.0,
|
|
||||||
None => {
|
|
||||||
return Err(VhdxBatError::InvalidBatEntry);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut buf = [0u8; size_of::<u64>()];
|
|
||||||
LittleEndian::write_u64(&mut buf, bat_entry);
|
|
||||||
f.write_all_at(&buf, bat_offset + i * size_of::<u64>() as u64)
|
|
||||||
.map_err(VhdxBatError::WriteBat)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,204 +0,0 @@
|
|||||||
// Copyright © 2021 Intel Corporation
|
|
||||||
//
|
|
||||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
use std::io::{self, Read, Seek, SeekFrom, Write};
|
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
|
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
|
||||||
|
|
||||||
use crate::async_io::{
|
|
||||||
AsyncIo, AsyncIoCompletion, AsyncIoError, AsyncIoOperation, AsyncIoResult, CompletionCommon,
|
|
||||||
};
|
|
||||||
use crate::formats::vhdx::Vhdx;
|
|
||||||
|
|
||||||
pub(super) struct VhdxSync {
|
|
||||||
vhdx_file: Arc<Mutex<Vhdx>>,
|
|
||||||
completions: CompletionCommon,
|
|
||||||
size: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VhdxSync {
|
|
||||||
pub(super) fn new(vhdx_file: Arc<Mutex<Vhdx>>, size: u64) -> Self {
|
|
||||||
VhdxSync {
|
|
||||||
vhdx_file,
|
|
||||||
completions: CompletionCommon::new(),
|
|
||||||
size,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_operation(&mut self, op: &mut AsyncIoOperation) -> AsyncIoResult<usize> {
|
|
||||||
let offset = op.offset();
|
|
||||||
let mut buf = vec![0u8; op.total_len()];
|
|
||||||
let mut vhdx = self.vhdx_file.lock().unwrap();
|
|
||||||
vhdx.seek(SeekFrom::Start(offset as u64))
|
|
||||||
.map_err(AsyncIoError::ReadVectored)?;
|
|
||||||
let result = vhdx.read(&mut buf).map_err(AsyncIoError::ReadVectored)?;
|
|
||||||
drop(vhdx);
|
|
||||||
|
|
||||||
op.write_bytes_at(0, &buf[..result])
|
|
||||||
.map_err(AsyncIoError::ReadVectored)?;
|
|
||||||
Ok(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_operation(&mut self, op: &AsyncIoOperation) -> AsyncIoResult<usize> {
|
|
||||||
let offset = op.offset();
|
|
||||||
let mut buf = vec![0u8; op.total_len()];
|
|
||||||
op.read_bytes_at(0, &mut buf)
|
|
||||||
.map_err(AsyncIoError::WriteVectored)?;
|
|
||||||
|
|
||||||
let mut vhdx = self.vhdx_file.lock().unwrap();
|
|
||||||
vhdx.seek(SeekFrom::Start(offset as u64))
|
|
||||||
.map_err(AsyncIoError::WriteVectored)?;
|
|
||||||
let result = vhdx.write(&buf).map_err(AsyncIoError::WriteVectored)?;
|
|
||||||
Ok(result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsyncIo for VhdxSync {
|
|
||||||
fn notifier(&self) -> &EventFd {
|
|
||||||
self.completions.notifier()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn submit_data_operation(&mut self, op: AsyncIoOperation) -> AsyncIoResult<()> {
|
|
||||||
op.validate_bounds(self.size)?;
|
|
||||||
let is_read = op.is_read();
|
|
||||||
let mut op = op;
|
|
||||||
let result = if is_read {
|
|
||||||
self.read_operation(&mut op)?
|
|
||||||
} else {
|
|
||||||
self.write_operation(&op)?
|
|
||||||
};
|
|
||||||
|
|
||||||
self.completions
|
|
||||||
.complete(AsyncIoCompletion::from_operation(op, result as i32));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fsync(&mut self, user_data: Option<u64>) -> AsyncIoResult<()> {
|
|
||||||
self.vhdx_file
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.flush()
|
|
||||||
.map_err(AsyncIoError::Fsync)?;
|
|
||||||
if let Some(user_data) = user_data {
|
|
||||||
self.completions
|
|
||||||
.complete(AsyncIoCompletion::new(user_data, 0, None));
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn next_completed_request(&mut self) -> Option<AsyncIoCompletion> {
|
|
||||||
self.completions.next_completed()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn punch_hole(&mut self, _offset: u64, _length: u64, _user_data: u64) -> AsyncIoResult<()> {
|
|
||||||
Err(AsyncIoError::PunchHole(io::Error::other(
|
|
||||||
"punch_hole not supported for VHDX",
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_zeroes(&mut self, _offset: u64, _length: u64, _user_data: u64) -> AsyncIoResult<()> {
|
|
||||||
Err(AsyncIoError::WriteZeroes(io::Error::other(
|
|
||||||
"write_zeroes not supported for VHDX",
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use std::fs;
|
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
|
|
||||||
use vmm_sys_util::tempfile::TempFile;
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
use crate::async_io::{AsyncIo, AsyncIoError, AsyncIoOperation, OwnedIoBuffer};
|
|
||||||
use crate::formats::vhdx::Vhdx;
|
|
||||||
use crate::formats::vhdx::test_util::create_dynamic_vhdx;
|
|
||||||
|
|
||||||
fn make_vhdx_sync(tf: &TempFile) -> (VhdxSync, u64) {
|
|
||||||
let file = fs::OpenOptions::new()
|
|
||||||
.read(true)
|
|
||||||
.write(true)
|
|
||||||
.open(tf.as_path())
|
|
||||||
.unwrap();
|
|
||||||
let vhdx = Vhdx::new(file, false).unwrap();
|
|
||||||
let size = vhdx.virtual_disk_size();
|
|
||||||
let sync = VhdxSync::new(Arc::new(Mutex::new(vhdx)), size);
|
|
||||||
(sync, size)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Builds a `VhdxSync` from a fresh 1 MiB dynamic VHDX, or `None`
|
|
||||||
/// if `qemu-img` is unavailable to generate one.
|
|
||||||
fn setup() -> Option<(VhdxSync, u64)> {
|
|
||||||
let tf = create_dynamic_vhdx(1)?;
|
|
||||||
Some(make_vhdx_sync(&tf))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn sync_rejects_read_straddling_logical_size() {
|
|
||||||
let Some((mut sync, size)) = setup() else {
|
|
||||||
eprintln!("skipping: qemu-img unavailable");
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let op = AsyncIoOperation::read_to_vec(
|
|
||||||
(size - 512) as i64,
|
|
||||||
OwnedIoBuffer::from_vec(vec![0u8; 1024]),
|
|
||||||
1,
|
|
||||||
);
|
|
||||||
assert!(matches!(
|
|
||||||
sync.submit_data_operation(op),
|
|
||||||
Err(AsyncIoError::ReadVectored(_))
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn sync_rejects_write_straddling_logical_size() {
|
|
||||||
let Some((mut sync, size)) = setup() else {
|
|
||||||
eprintln!("skipping: qemu-img unavailable");
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let op = AsyncIoOperation::write_from_vec(
|
|
||||||
(size - 512) as i64,
|
|
||||||
OwnedIoBuffer::from_vec(vec![0u8; 1024]),
|
|
||||||
1,
|
|
||||||
);
|
|
||||||
assert!(matches!(
|
|
||||||
sync.submit_data_operation(op),
|
|
||||||
Err(AsyncIoError::WriteVectored(_))
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn sync_accepts_operation_exactly_filling_logical_size() {
|
|
||||||
let Some((mut sync, size)) = setup() else {
|
|
||||||
eprintln!("skipping: qemu-img unavailable");
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let op =
|
|
||||||
AsyncIoOperation::read_to_vec(0, OwnedIoBuffer::from_vec(vec![0u8; size as usize]), 1);
|
|
||||||
sync.submit_data_operation(op).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn sync_accepts_operation_at_last_sector() {
|
|
||||||
let Some((mut sync, size)) = setup() else {
|
|
||||||
eprintln!("skipping: qemu-img unavailable");
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
// VHDX operates in 512-byte sectors; read exactly the last sector.
|
|
||||||
let op = AsyncIoOperation::read_to_vec(
|
|
||||||
(size - 512) as i64,
|
|
||||||
OwnedIoBuffer::from_vec(vec![0u8; 512]),
|
|
||||||
1,
|
|
||||||
);
|
|
||||||
sync.submit_data_operation(op).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,589 +0,0 @@
|
|||||||
// Copyright © 2021 Intel Corporation
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
use std::collections::btree_map::BTreeMap;
|
|
||||||
use std::os::unix::fs::FileExt;
|
|
||||||
use std::{io, result};
|
|
||||||
|
|
||||||
use byteorder::{ByteOrder, LittleEndian};
|
|
||||||
use remain::sorted;
|
|
||||||
use thiserror::Error;
|
|
||||||
use uuid::Uuid;
|
|
||||||
use zerocopy::{FromBytes, Immutable, IntoBytes};
|
|
||||||
|
|
||||||
use crate::aligned_file::AlignedFile;
|
|
||||||
|
|
||||||
const VHDX_SIGN: u64 = 0x656C_6966_7864_6876; // "vhdxfile"
|
|
||||||
const HEADER_SIGN: u32 = 0x6461_6568; // "head"
|
|
||||||
const REGION_SIGN: u32 = 0x6967_6572; // "regi"
|
|
||||||
|
|
||||||
const FILE_START: u64 = 0; // The first element
|
|
||||||
const HEADER_1_START: u64 = 64 * 1024; // Header 1 start in Bytes
|
|
||||||
const HEADER_2_START: u64 = 128 * 1024; // Header 2 start in Bytes
|
|
||||||
pub(super) const REGION_TABLE_1_START: u64 = 192 * 1024; // Region 1 start in Bytes
|
|
||||||
const REGION_TABLE_2_START: u64 = 256 * 1024; // Region 2 start in Bytes
|
|
||||||
|
|
||||||
const HEADER_SIZE: u64 = 4 * 1024; // Each header is 64 KiB, but only first 4 kiB contains info
|
|
||||||
const REGION_SIZE: u64 = 64 * 1024; // Each region size is 64 KiB
|
|
||||||
|
|
||||||
const REGION_ENTRY_REQUIRED: u32 = 1;
|
|
||||||
|
|
||||||
// VHDX stores GUIDs using little-endian GUID byte order.
|
|
||||||
const BAT_GUID: [u8; 16] = [
|
|
||||||
0x66, 0x77, 0xc2, 0x2d, 0x23, 0xf6, 0x00, 0x42, 0x9d, 0x64, 0x11, 0x5e, 0x9b, 0xfd, 0x4a, 0x08,
|
|
||||||
];
|
|
||||||
const MDR_GUID: [u8; 16] = [
|
|
||||||
0x06, 0xa2, 0x7c, 0x8b, 0x90, 0x47, 0x9a, 0x4b, 0xb8, 0xfe, 0x57, 0x5f, 0x05, 0x0f, 0x88, 0x6e,
|
|
||||||
];
|
|
||||||
|
|
||||||
#[sorted]
|
|
||||||
#[derive(Error, Debug)]
|
|
||||||
pub enum VhdxHeaderError {
|
|
||||||
#[error("Failed to calculate checksum")]
|
|
||||||
CalculateChecksum,
|
|
||||||
#[error("BAT entry is not unique")]
|
|
||||||
DuplicateBATEntry,
|
|
||||||
#[error("Metadata region entry is not unique")]
|
|
||||||
DuplicateMDREntry,
|
|
||||||
#[error("Checksum doesn't match for {0}")]
|
|
||||||
InvalidChecksum(String),
|
|
||||||
#[error("Invalid entry count")]
|
|
||||||
InvalidEntryCount,
|
|
||||||
#[error("Not a valid VHDx header")]
|
|
||||||
InvalidHeaderSign,
|
|
||||||
#[error("Not a valid VHDx region")]
|
|
||||||
InvalidRegionSign,
|
|
||||||
#[error("Not a VHDx file")]
|
|
||||||
InvalidVHDXSign,
|
|
||||||
#[error("No valid header found")]
|
|
||||||
NoValidHeader,
|
|
||||||
#[error("Cannot read checksum")]
|
|
||||||
ReadChecksum,
|
|
||||||
#[error("Failed to read File Type Identifier {0}")]
|
|
||||||
ReadFileTypeIdentifier(#[source] io::Error),
|
|
||||||
#[error("Failed to read headers {0}")]
|
|
||||||
ReadHeader(#[source] io::Error),
|
|
||||||
#[error("Failed to read metadata {0}")]
|
|
||||||
ReadMetadata(#[source] io::Error),
|
|
||||||
#[error("Failed to read region table entries {0}")]
|
|
||||||
ReadRegionTableEntries(#[source] io::Error),
|
|
||||||
#[error("Failed to read region table header {0}")]
|
|
||||||
ReadRegionTableHeader(#[source] io::Error),
|
|
||||||
#[error("Failed to read region entries")]
|
|
||||||
RegionEntryCollectionFailed,
|
|
||||||
#[error("Region entry file offset ({0}) and length ({1}) overflow u64")]
|
|
||||||
RegionEntryOverflow(u64 /* start */, usize /* length */),
|
|
||||||
#[error("Overlapping regions found")]
|
|
||||||
RegionOverlap,
|
|
||||||
#[error("Reserved region has non-zero value")]
|
|
||||||
ReservedIsNonZero,
|
|
||||||
#[error("We do not recognize this entry")]
|
|
||||||
UnrecognizedRegionEntry,
|
|
||||||
#[error("Failed to write header {0}")]
|
|
||||||
WriteHeader(#[source] io::Error),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) type Result<T> = result::Result<T, VhdxHeaderError>;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub(super) struct FileTypeIdentifier {
|
|
||||||
pub _signature: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FileTypeIdentifier {
|
|
||||||
/// Reads the File Type Identifier structure from a reference VHDx file
|
|
||||||
pub(super) fn new(f: &AlignedFile) -> Result<FileTypeIdentifier> {
|
|
||||||
let mut buf = [0u8; size_of::<u64>()];
|
|
||||||
f.read_exact_at(&mut buf, FILE_START)
|
|
||||||
.map_err(VhdxHeaderError::ReadFileTypeIdentifier)?;
|
|
||||||
let _signature = LittleEndian::read_u64(&buf);
|
|
||||||
if _signature != VHDX_SIGN {
|
|
||||||
return Err(VhdxHeaderError::InvalidVHDXSign);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(FileTypeIdentifier { _signature })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(C, packed)]
|
|
||||||
#[derive(Clone, Copy, Debug, FromBytes, Immutable, IntoBytes)]
|
|
||||||
pub(super) struct Header {
|
|
||||||
pub signature: u32,
|
|
||||||
pub checksum: u32,
|
|
||||||
pub sequence_number: u64,
|
|
||||||
pub file_write_guid: u128,
|
|
||||||
pub data_write_guid: u128,
|
|
||||||
pub log_guid: u128,
|
|
||||||
pub log_version: u16,
|
|
||||||
pub version: u16,
|
|
||||||
pub log_length: u32,
|
|
||||||
pub log_offset: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Header {
|
|
||||||
/// Reads the Header structure from a reference VHDx file
|
|
||||||
pub(super) fn new(f: &AlignedFile, start: u64) -> Result<Header> {
|
|
||||||
// Read the whole header into a buffer. We will need it for
|
|
||||||
// calculating checksum.
|
|
||||||
let mut buffer = [0; HEADER_SIZE as usize];
|
|
||||||
f.read_exact_at(&mut buffer, start)
|
|
||||||
.map_err(VhdxHeaderError::ReadHeader)?;
|
|
||||||
|
|
||||||
let header = Header::read_from_prefix(&buffer).unwrap().0;
|
|
||||||
if header.signature != HEADER_SIGN {
|
|
||||||
return Err(VhdxHeaderError::InvalidHeaderSign);
|
|
||||||
}
|
|
||||||
|
|
||||||
let new_checksum = calculate_checksum(&mut buffer, size_of::<u32>());
|
|
||||||
if header.checksum != new_checksum {
|
|
||||||
return Err(VhdxHeaderError::InvalidChecksum(String::from("Header")));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(header)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates and returns new updated header from the provided current header
|
|
||||||
fn update_header(
|
|
||||||
f: &AlignedFile,
|
|
||||||
current_header: &Header,
|
|
||||||
change_data_guid: bool,
|
|
||||||
file_write_guid: u128,
|
|
||||||
start: u64,
|
|
||||||
) -> Result<Header> {
|
|
||||||
let mut buffer = [0u8; HEADER_SIZE as usize];
|
|
||||||
let data_write_guid = if change_data_guid {
|
|
||||||
Uuid::new_v4().as_u128()
|
|
||||||
} else {
|
|
||||||
current_header.data_write_guid
|
|
||||||
};
|
|
||||||
|
|
||||||
let file_write_guid = if file_write_guid == 0 {
|
|
||||||
current_header.file_write_guid
|
|
||||||
} else {
|
|
||||||
file_write_guid
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut new_header = Header {
|
|
||||||
signature: current_header.signature,
|
|
||||||
checksum: 0,
|
|
||||||
sequence_number: current_header.sequence_number + 1,
|
|
||||||
file_write_guid,
|
|
||||||
data_write_guid,
|
|
||||||
log_guid: current_header.log_guid,
|
|
||||||
log_version: current_header.log_version,
|
|
||||||
version: current_header.version,
|
|
||||||
log_length: current_header.log_length,
|
|
||||||
log_offset: current_header.log_offset,
|
|
||||||
};
|
|
||||||
|
|
||||||
new_header.write_to_prefix(&mut buffer).unwrap();
|
|
||||||
new_header.checksum = calculate_checksum(&mut buffer, size_of::<u32>());
|
|
||||||
new_header.write_to_prefix(&mut buffer).unwrap();
|
|
||||||
|
|
||||||
f.write_all_at(&buffer, start)
|
|
||||||
.map_err(VhdxHeaderError::WriteHeader)?;
|
|
||||||
|
|
||||||
Ok(new_header)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(C, packed)]
|
|
||||||
#[derive(Clone, Copy, Debug, FromBytes)]
|
|
||||||
struct RegionTableHeader {
|
|
||||||
pub signature: u32,
|
|
||||||
pub checksum: u32,
|
|
||||||
pub entry_count: u32,
|
|
||||||
pub reserved: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RegionTableHeader {
|
|
||||||
/// Reads the Region Table Header structure from a reference VHDx file
|
|
||||||
pub(crate) fn new(f: &AlignedFile, start: u64) -> Result<RegionTableHeader> {
|
|
||||||
// Read the whole header into a buffer. We will need it for calculating
|
|
||||||
// checksum.
|
|
||||||
let mut buffer = [0u8; REGION_SIZE as usize];
|
|
||||||
f.read_exact_at(&mut buffer, start)
|
|
||||||
.map_err(VhdxHeaderError::ReadRegionTableHeader)?;
|
|
||||||
|
|
||||||
let region_table_header = RegionTableHeader::read_from_prefix(&buffer).unwrap().0;
|
|
||||||
if region_table_header.signature != REGION_SIGN {
|
|
||||||
return Err(VhdxHeaderError::InvalidRegionSign);
|
|
||||||
}
|
|
||||||
|
|
||||||
let new_checksum = calculate_checksum(&mut buffer, size_of::<u32>());
|
|
||||||
if region_table_header.checksum != new_checksum {
|
|
||||||
return Err(VhdxHeaderError::InvalidChecksum(String::from("Region")));
|
|
||||||
}
|
|
||||||
|
|
||||||
if region_table_header.entry_count > 2047 {
|
|
||||||
return Err(VhdxHeaderError::InvalidEntryCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
if region_table_header.reserved != 0 {
|
|
||||||
return Err(VhdxHeaderError::ReservedIsNonZero);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(region_table_header)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns `true` if the half-open byte ranges `[a_start, a_end)` and
|
|
||||||
/// `[b_start, b_end)` overlap.
|
|
||||||
fn ranges_overlap(a_start: u64, a_end: u64, b_start: u64, b_end: u64) -> bool {
|
|
||||||
a_start < b_end && b_start < a_end
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) struct RegionInfo {
|
|
||||||
pub bat_entry: RegionTableEntry,
|
|
||||||
pub mdr_entry: RegionTableEntry,
|
|
||||||
pub region_entries: BTreeMap<u64, u64>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RegionInfo {
|
|
||||||
/// Collect all entries in a BTreeMap from the Region Table and identifies
|
|
||||||
/// BAT and metadata regions
|
|
||||||
pub(super) fn new(f: &AlignedFile, region_start: u64, entry_count: u32) -> Result<RegionInfo> {
|
|
||||||
let mut bat_entry: Option<RegionTableEntry> = None;
|
|
||||||
let mut mdr_entry: Option<RegionTableEntry> = None;
|
|
||||||
|
|
||||||
let mut offset = 0;
|
|
||||||
let mut region_entries = BTreeMap::new();
|
|
||||||
|
|
||||||
let mut buffer = [0; REGION_SIZE as usize];
|
|
||||||
// Read after the Region Table Header
|
|
||||||
f.read_exact_at(
|
|
||||||
&mut buffer,
|
|
||||||
region_start + size_of::<RegionTableHeader>() as u64,
|
|
||||||
)
|
|
||||||
.map_err(VhdxHeaderError::ReadRegionTableEntries)?;
|
|
||||||
|
|
||||||
for _ in 0..entry_count {
|
|
||||||
let entry = RegionTableEntry::read_from_bytes(
|
|
||||||
&buffer[offset..offset + size_of::<RegionTableEntry>()],
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
offset += size_of::<RegionTableEntry>();
|
|
||||||
let start = entry.file_offset;
|
|
||||||
let end = start.checked_add(entry.length as u64).ok_or(
|
|
||||||
VhdxHeaderError::RegionEntryOverflow(start, entry.length as usize),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
for (region_ent_start, region_ent_end) in region_entries.iter() {
|
|
||||||
if ranges_overlap(start, end, *region_ent_start, *region_ent_end) {
|
|
||||||
return Err(VhdxHeaderError::RegionOverlap);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
region_entries.insert(start, end);
|
|
||||||
|
|
||||||
if entry.guid == BAT_GUID {
|
|
||||||
if bat_entry.is_none() {
|
|
||||||
bat_entry = Some(entry);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
return Err(VhdxHeaderError::DuplicateBATEntry);
|
|
||||||
}
|
|
||||||
|
|
||||||
if entry.guid == MDR_GUID {
|
|
||||||
if mdr_entry.is_none() {
|
|
||||||
mdr_entry = Some(entry);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
return Err(VhdxHeaderError::DuplicateMDREntry);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (entry.required & REGION_ENTRY_REQUIRED) == 1 {
|
|
||||||
// This implementation doesn't recognize this field.
|
|
||||||
// Therefore, according to the spec, we are throwing an error.
|
|
||||||
return Err(VhdxHeaderError::UnrecognizedRegionEntry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if bat_entry.is_none() || mdr_entry.is_none() {
|
|
||||||
region_entries.clear();
|
|
||||||
return Err(VhdxHeaderError::RegionEntryCollectionFailed);
|
|
||||||
}
|
|
||||||
|
|
||||||
// It's safe to unwrap as we checked both entries have been filled.
|
|
||||||
// Otherwise, an error is already returned.
|
|
||||||
let bat_entry = bat_entry.unwrap();
|
|
||||||
let mdr_entry = mdr_entry.unwrap();
|
|
||||||
|
|
||||||
Ok(RegionInfo {
|
|
||||||
bat_entry,
|
|
||||||
mdr_entry,
|
|
||||||
region_entries,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(C, packed)]
|
|
||||||
#[derive(Clone, Copy, Debug, FromBytes)]
|
|
||||||
pub(super) struct RegionTableEntry {
|
|
||||||
guid: [u8; 16],
|
|
||||||
pub file_offset: u64,
|
|
||||||
pub length: u32,
|
|
||||||
pub required: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
enum HeaderNo {
|
|
||||||
First,
|
|
||||||
Second,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Contains the information from the header of a VHDx file
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub(super) struct VhdxHeader {
|
|
||||||
_file_type_identifier: FileTypeIdentifier,
|
|
||||||
header_1: Header,
|
|
||||||
header_2: Header,
|
|
||||||
region_table_1: RegionTableHeader,
|
|
||||||
_region_table_2: RegionTableHeader,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VhdxHeader {
|
|
||||||
/// Creates a VhdxHeader from a reference to a file
|
|
||||||
pub(super) fn new(f: &AlignedFile) -> Result<VhdxHeader> {
|
|
||||||
Ok(VhdxHeader {
|
|
||||||
_file_type_identifier: FileTypeIdentifier::new(f)?,
|
|
||||||
header_1: Header::new(f, HEADER_1_START)?,
|
|
||||||
header_2: Header::new(f, HEADER_2_START)?,
|
|
||||||
region_table_1: RegionTableHeader::new(f, REGION_TABLE_1_START)?,
|
|
||||||
_region_table_2: RegionTableHeader::new(f, REGION_TABLE_2_START)?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Identify the current header and return both headers along with an
|
|
||||||
/// integer indicating the current header.
|
|
||||||
fn current_header(
|
|
||||||
header_1: Result<Header>,
|
|
||||||
header_2: Result<Header>,
|
|
||||||
) -> Result<(HeaderNo, Header)> {
|
|
||||||
let header_1 = header_1.ok();
|
|
||||||
let header_2 = header_2.ok();
|
|
||||||
|
|
||||||
match (header_1, header_2) {
|
|
||||||
(None, None) => Err(VhdxHeaderError::NoValidHeader),
|
|
||||||
(Some(header_1), None) => Ok((HeaderNo::First, header_1)),
|
|
||||||
(None, Some(header_2)) => Ok((HeaderNo::Second, header_2)),
|
|
||||||
(Some(header_1), Some(header_2)) => {
|
|
||||||
if header_1.sequence_number >= header_2.sequence_number {
|
|
||||||
Ok((HeaderNo::First, header_1))
|
|
||||||
} else {
|
|
||||||
Ok((HeaderNo::Second, header_2))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This takes two headers and update the noncurrent header with the
|
|
||||||
/// current one. Returns both headers as a tuple sequenced the way it was
|
|
||||||
/// received from the parameter list.
|
|
||||||
fn update_header(
|
|
||||||
f: &AlignedFile,
|
|
||||||
header_1: Result<Header>,
|
|
||||||
header_2: Result<Header>,
|
|
||||||
guid: u128,
|
|
||||||
) -> Result<(Header, Header)> {
|
|
||||||
let (header_no, current_header) = VhdxHeader::current_header(header_1, header_2)?;
|
|
||||||
|
|
||||||
match header_no {
|
|
||||||
HeaderNo::First => {
|
|
||||||
let other_header =
|
|
||||||
Header::update_header(f, ¤t_header, true, guid, HEADER_2_START)?;
|
|
||||||
Ok((current_header, other_header))
|
|
||||||
}
|
|
||||||
HeaderNo::Second => {
|
|
||||||
let other_header =
|
|
||||||
Header::update_header(f, ¤t_header, true, guid, HEADER_1_START)?;
|
|
||||||
Ok((other_header, current_header))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the provided headers according to the spec
|
|
||||||
fn update_headers(
|
|
||||||
f: &AlignedFile,
|
|
||||||
header_1: Result<Header>,
|
|
||||||
header_2: Result<Header>,
|
|
||||||
guid: u128,
|
|
||||||
) -> Result<(Header, Header)> {
|
|
||||||
// According to the spec, update twice
|
|
||||||
let (header_1, header_2) = VhdxHeader::update_header(f, header_1, header_2, guid)?;
|
|
||||||
VhdxHeader::update_header(f, Ok(header_1), Ok(header_2), guid)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn update(&mut self, f: &AlignedFile) -> Result<()> {
|
|
||||||
let headers = VhdxHeader::update_headers(f, Ok(self.header_1), Ok(self.header_2), 0)?;
|
|
||||||
self.header_1 = headers.0;
|
|
||||||
self.header_2 = headers.1;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn region_entry_count(&self) -> u32 {
|
|
||||||
self.region_table_1.entry_count
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Calculates the checksum of a buffer that itself contains its checksum
|
|
||||||
/// Therefore, before calculating, the existing checksum is retrieved and the
|
|
||||||
/// corresponding field is made zero. After the calculation, the existing checksum
|
|
||||||
/// is put back to the buffer.
|
|
||||||
fn calculate_checksum(buffer: &mut [u8], csum_offset: usize) -> u32 {
|
|
||||||
// Read the original checksum from the buffer
|
|
||||||
let orig_csum = LittleEndian::read_u32(&buffer[csum_offset..csum_offset + 4]);
|
|
||||||
// Zero the checksum in the buffer
|
|
||||||
LittleEndian::write_u32(&mut buffer[csum_offset..csum_offset + 4], 0);
|
|
||||||
// Calculate the checksum on the resulting buffer
|
|
||||||
let mut crc = crc_any::CRC::crc32c();
|
|
||||||
crc.digest(&buffer);
|
|
||||||
let new_csum = crc.get_crc() as u32;
|
|
||||||
|
|
||||||
// Put back the original checksum in the buffer
|
|
||||||
LittleEndian::write_u32(&mut buffer[csum_offset..csum_offset + 4], orig_csum);
|
|
||||||
|
|
||||||
new_csum
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use std::os::unix::fs::FileExt;
|
|
||||||
|
|
||||||
use vmm_sys_util::tempfile::TempFile;
|
|
||||||
use zerocopy::{FromBytes, IntoBytes};
|
|
||||||
|
|
||||||
use super::{
|
|
||||||
BAT_GUID, HEADER_SIGN, Header, MDR_GUID, REGION_TABLE_1_START, RegionInfo,
|
|
||||||
RegionTableHeader, VhdxHeaderError, ranges_overlap,
|
|
||||||
};
|
|
||||||
use crate::aligned_file::AlignedFile;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_header_bytes_round_trip() {
|
|
||||||
let header = Header {
|
|
||||||
signature: HEADER_SIGN,
|
|
||||||
checksum: 0x1122_3344,
|
|
||||||
sequence_number: 0x0102_0304_0506_0708,
|
|
||||||
file_write_guid: 0x0f0e_0d0c_0b0a_0908_0706_0504_0302_0100,
|
|
||||||
data_write_guid: 0x1f1e_1d1c_1b1a_1918_1716_1514_1312_1110,
|
|
||||||
log_guid: 0x2f2e_2d2c_2b2a_2928_2726_2524_2322_2120,
|
|
||||||
log_version: 0xabcd,
|
|
||||||
version: 0x0001,
|
|
||||||
log_length: 0x0010_0000,
|
|
||||||
log_offset: 0x0000_0100_0000_0000,
|
|
||||||
};
|
|
||||||
|
|
||||||
let bytes = header.as_bytes();
|
|
||||||
assert_eq!(&bytes[0..4], &header.signature.to_le_bytes()[..]);
|
|
||||||
assert_eq!(&bytes[8..16], &header.sequence_number.to_le_bytes()[..]);
|
|
||||||
assert_eq!(&bytes[16..32], &header.file_write_guid.to_le_bytes()[..]);
|
|
||||||
assert_eq!(&bytes[64..66], &header.log_version.to_le_bytes()[..]);
|
|
||||||
assert_eq!(&bytes[72..80], &header.log_offset.to_le_bytes()[..]);
|
|
||||||
|
|
||||||
let parsed = Header::read_from_bytes(bytes).unwrap();
|
|
||||||
assert_eq!({ parsed.signature }, { header.signature });
|
|
||||||
assert_eq!({ parsed.checksum }, { header.checksum });
|
|
||||||
assert_eq!({ parsed.sequence_number }, { header.sequence_number });
|
|
||||||
assert_eq!({ parsed.file_write_guid }, { header.file_write_guid });
|
|
||||||
assert_eq!({ parsed.data_write_guid }, { header.data_write_guid });
|
|
||||||
assert_eq!({ parsed.log_guid }, { header.log_guid });
|
|
||||||
assert_eq!({ parsed.log_version }, { header.log_version });
|
|
||||||
assert_eq!({ parsed.version }, { header.version });
|
|
||||||
assert_eq!({ parsed.log_length }, { header.log_length });
|
|
||||||
assert_eq!({ parsed.log_offset }, { header.log_offset });
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_ranges_overlap() {
|
|
||||||
// (new [start,end), existing [s,e), expected overlap)
|
|
||||||
let cases: &[(u64, u64, u64, u64, bool)] = &[
|
|
||||||
// Genuine overlaps — all of these must be detected.
|
|
||||||
(0, 10, 0, 10, true), // identical
|
|
||||||
(2, 8, 0, 10, true), // new fully inside existing
|
|
||||||
(0, 20, 5, 10, true), // new fully contains existing
|
|
||||||
(5, 15, 0, 10, true), // partial, new starts inside existing
|
|
||||||
(0, 8, 5, 15, true), // partial, new starts before existing
|
|
||||||
// Non-overlapping — must not be flagged.
|
|
||||||
(0, 5, 10, 20, false), // disjoint, new before existing
|
|
||||||
(30, 40, 10, 20, false), // disjoint, new after existing
|
|
||||||
(0, 10, 10, 20, false), // touching at the boundary (half-open)
|
|
||||||
];
|
|
||||||
|
|
||||||
for &(a_start, a_end, b_start, b_end, expected) in cases {
|
|
||||||
assert_eq!(
|
|
||||||
ranges_overlap(a_start, a_end, b_start, b_end),
|
|
||||||
expected,
|
|
||||||
"[{a_start},{a_end}) vs [{b_start},{b_end})"
|
|
||||||
);
|
|
||||||
// Overlap is symmetric.
|
|
||||||
assert_eq!(
|
|
||||||
ranges_overlap(b_start, b_end, a_start, a_end),
|
|
||||||
expected,
|
|
||||||
"symmetry: [{b_start},{b_end}) vs [{a_start},{a_end})"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Builds the 32-byte on-disk region table entry for `guid` describing
|
|
||||||
/// the region `[file_offset, file_offset + length)`.
|
|
||||||
fn region_entry(guid: [u8; 16], file_offset: u64, length: u32) -> [u8; 32] {
|
|
||||||
let mut e = [0u8; 32];
|
|
||||||
e[0..16].copy_from_slice(&guid);
|
|
||||||
e[16..24].copy_from_slice(&file_offset.to_le_bytes());
|
|
||||||
e[24..28].copy_from_slice(&length.to_le_bytes());
|
|
||||||
// `required` (e[28..32]) left zero.
|
|
||||||
e
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_region_info_rejects_overlapping_regions() {
|
|
||||||
// BAT region [1 MiB, 3 MiB) and metadata region [2 MiB, 4 MiB) overlap
|
|
||||||
// on [2 MiB, 3 MiB); per [MS-VHDX] all region objects must be
|
|
||||||
// non-overlapping, so this image must be rejected.
|
|
||||||
const MIB: u64 = 1024 * 1024;
|
|
||||||
let region_start = REGION_TABLE_1_START;
|
|
||||||
let entries_at = region_start + size_of::<RegionTableHeader>() as u64;
|
|
||||||
|
|
||||||
let temp = TempFile::new().unwrap();
|
|
||||||
let f = temp.into_file();
|
|
||||||
f.set_len(entries_at + 64 * 1024).unwrap();
|
|
||||||
f.write_all_at(®ion_entry(BAT_GUID, MIB, (2 * MIB) as u32), entries_at)
|
|
||||||
.unwrap();
|
|
||||||
f.write_all_at(
|
|
||||||
®ion_entry(MDR_GUID, 2 * MIB, (2 * MIB) as u32),
|
|
||||||
entries_at + 32,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let af = AlignedFile::new(f, false);
|
|
||||||
let res = RegionInfo::new(&af, region_start, 2);
|
|
||||||
assert!(
|
|
||||||
matches!(res, Err(VhdxHeaderError::RegionOverlap)),
|
|
||||||
"expected RegionOverlap for an overlapping region table"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_region_info_rejects_overflowing_region() {
|
|
||||||
// A region whose file offset plus length wraps past u64::MAX must be
|
|
||||||
// rejected rather than silently producing a small end offset that
|
|
||||||
// could mask a genuine overlap.
|
|
||||||
let region_start = REGION_TABLE_1_START;
|
|
||||||
let entries_at = region_start + size_of::<RegionTableHeader>() as u64;
|
|
||||||
|
|
||||||
let temp = TempFile::new().unwrap();
|
|
||||||
let f = temp.into_file();
|
|
||||||
f.set_len(entries_at + 64 * 1024).unwrap();
|
|
||||||
f.write_all_at(®ion_entry(BAT_GUID, u64::MAX, 0x1000), entries_at)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let af = AlignedFile::new(f, false);
|
|
||||||
let res = RegionInfo::new(&af, region_start, 1);
|
|
||||||
assert!(
|
|
||||||
matches!(res, Err(VhdxHeaderError::RegionEntryOverflow(..))),
|
|
||||||
"expected RegionEntryOverflow for a wrapping region entry"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,311 +0,0 @@
|
|||||||
// Copyright © 2021 Intel Corporation
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
use std::os::unix::fs::FileExt;
|
|
||||||
use std::{io, result};
|
|
||||||
|
|
||||||
use remain::sorted;
|
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
use super::bat::{self, BatEntry, VhdxBatError};
|
|
||||||
use super::metadata::{self, DiskSpec};
|
|
||||||
use crate::aligned_file::AlignedFile;
|
|
||||||
|
|
||||||
#[sorted]
|
|
||||||
#[derive(Error, Debug)]
|
|
||||||
pub enum VhdxIoError {
|
|
||||||
#[error("Invalid BAT entry state")]
|
|
||||||
InvalidBatEntryState,
|
|
||||||
#[error("Invalid BAT entry count")]
|
|
||||||
InvalidBatIndex,
|
|
||||||
#[error("Buffer length does not match the requested sector count")]
|
|
||||||
InvalidBufferLength,
|
|
||||||
#[error("Invalid disk size")]
|
|
||||||
InvalidDiskSize,
|
|
||||||
#[error("Failed reading sector blocks from file {0}")]
|
|
||||||
ReadSectorBlock(#[source] io::Error),
|
|
||||||
#[error("Failed changing file length {0}")]
|
|
||||||
ResizeFile(#[source] io::Error),
|
|
||||||
#[error("Differencing mode is not supported yet")]
|
|
||||||
UnsupportedMode,
|
|
||||||
#[error("Failed writing BAT to file {0}")]
|
|
||||||
WriteBat(#[source] VhdxBatError),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) type Result<T> = result::Result<T, VhdxIoError>;
|
|
||||||
|
|
||||||
macro_rules! align {
|
|
||||||
($n:expr, $align:expr) => {{ $n.div_ceil($align) * $align }};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
struct Sector {
|
|
||||||
bat_index: u64,
|
|
||||||
free_sectors: u64,
|
|
||||||
free_bytes: u64,
|
|
||||||
file_offset: u64,
|
|
||||||
block_offset: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Sector {
|
|
||||||
/// Translate sector index and count of data in file to actual offsets and
|
|
||||||
/// BAT index.
|
|
||||||
pub(crate) fn new(
|
|
||||||
disk_spec: &DiskSpec,
|
|
||||||
bat: &[BatEntry],
|
|
||||||
sector_index: u64,
|
|
||||||
sector_count: u64,
|
|
||||||
) -> Result<Sector> {
|
|
||||||
let mut sector = Sector::default();
|
|
||||||
|
|
||||||
sector.bat_index = sector_index / disk_spec.sectors_per_block as u64;
|
|
||||||
sector.block_offset = sector_index % disk_spec.sectors_per_block as u64;
|
|
||||||
sector.free_sectors = disk_spec.sectors_per_block as u64 - sector.block_offset;
|
|
||||||
if sector.free_sectors > sector_count {
|
|
||||||
sector.free_sectors = sector_count;
|
|
||||||
}
|
|
||||||
|
|
||||||
sector.free_bytes = sector.free_sectors * disk_spec.logical_sector_size as u64;
|
|
||||||
sector.block_offset *= disk_spec.logical_sector_size as u64;
|
|
||||||
|
|
||||||
let bat_entry = match bat.get(sector.bat_index as usize) {
|
|
||||||
Some(entry) => entry.0,
|
|
||||||
None => {
|
|
||||||
return Err(VhdxIoError::InvalidBatIndex);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
sector.file_offset = bat_entry & bat::BAT_FILE_OFF_MASK;
|
|
||||||
if sector.file_offset != 0 {
|
|
||||||
sector.file_offset += sector.block_offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(sector)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// VHDx IO read routine: requires relative sector index and count for the
|
|
||||||
/// requested data.
|
|
||||||
pub(super) fn read(
|
|
||||||
f: &AlignedFile,
|
|
||||||
buf: &mut [u8],
|
|
||||||
disk_spec: &DiskSpec,
|
|
||||||
bat: &[BatEntry],
|
|
||||||
mut sector_index: u64,
|
|
||||||
mut sector_count: u64,
|
|
||||||
) -> Result<usize> {
|
|
||||||
if disk_spec.has_parent {
|
|
||||||
return Err(VhdxIoError::UnsupportedMode);
|
|
||||||
}
|
|
||||||
let expected_len = sector_count
|
|
||||||
.checked_mul(disk_spec.logical_sector_size as u64)
|
|
||||||
.ok_or(VhdxIoError::InvalidBufferLength)?;
|
|
||||||
if buf.len() as u64 != expected_len {
|
|
||||||
return Err(VhdxIoError::InvalidBufferLength);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut read_count: usize = 0;
|
|
||||||
while sector_count > 0 {
|
|
||||||
let sector = Sector::new(disk_spec, bat, sector_index, sector_count)?;
|
|
||||||
|
|
||||||
let bat_entry = match bat.get(sector.bat_index as usize) {
|
|
||||||
Some(entry) => entry.0,
|
|
||||||
None => {
|
|
||||||
return Err(VhdxIoError::InvalidBatIndex);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
match bat_entry & bat::BAT_STATE_BIT_MASK {
|
|
||||||
bat::PAYLOAD_BLOCK_NOT_PRESENT
|
|
||||||
| bat::PAYLOAD_BLOCK_UNDEFINED
|
|
||||||
| bat::PAYLOAD_BLOCK_UNMAPPED
|
|
||||||
| bat::PAYLOAD_BLOCK_ZERO => {}
|
|
||||||
bat::PAYLOAD_BLOCK_FULLY_PRESENT => {
|
|
||||||
f.read_exact_at(
|
|
||||||
&mut buf[read_count..(read_count + sector.free_bytes as usize)],
|
|
||||||
sector.file_offset,
|
|
||||||
)
|
|
||||||
.map_err(VhdxIoError::ReadSectorBlock)?;
|
|
||||||
}
|
|
||||||
bat::PAYLOAD_BLOCK_PARTIALLY_PRESENT => {
|
|
||||||
return Err(VhdxIoError::UnsupportedMode);
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
return Err(VhdxIoError::InvalidBatEntryState);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sector_count -= sector.free_sectors;
|
|
||||||
sector_index += sector.free_sectors;
|
|
||||||
read_count += sector.free_bytes as usize;
|
|
||||||
}
|
|
||||||
Ok(read_count)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// VHDx IO write routine: requires relative sector index and count for the
|
|
||||||
/// requested data.
|
|
||||||
pub(super) fn write(
|
|
||||||
f: &AlignedFile,
|
|
||||||
buf: &[u8],
|
|
||||||
disk_spec: &mut DiskSpec,
|
|
||||||
bat_offset: u64,
|
|
||||||
bat: &mut [BatEntry],
|
|
||||||
mut sector_index: u64,
|
|
||||||
mut sector_count: u64,
|
|
||||||
) -> Result<usize> {
|
|
||||||
if disk_spec.has_parent {
|
|
||||||
return Err(VhdxIoError::UnsupportedMode);
|
|
||||||
}
|
|
||||||
let expected_len = sector_count
|
|
||||||
.checked_mul(disk_spec.logical_sector_size as u64)
|
|
||||||
.ok_or(VhdxIoError::InvalidBufferLength)?;
|
|
||||||
if buf.len() as u64 != expected_len {
|
|
||||||
return Err(VhdxIoError::InvalidBufferLength);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut write_count: usize = 0;
|
|
||||||
while sector_count > 0 {
|
|
||||||
let sector = Sector::new(disk_spec, bat, sector_index, sector_count)?;
|
|
||||||
|
|
||||||
let bat_entry = match bat.get(sector.bat_index as usize) {
|
|
||||||
Some(entry) => entry.0,
|
|
||||||
None => {
|
|
||||||
return Err(VhdxIoError::InvalidBatIndex);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
match bat_entry & bat::BAT_STATE_BIT_MASK {
|
|
||||||
bat::PAYLOAD_BLOCK_NOT_PRESENT
|
|
||||||
| bat::PAYLOAD_BLOCK_UNDEFINED
|
|
||||||
| bat::PAYLOAD_BLOCK_UNMAPPED
|
|
||||||
| bat::PAYLOAD_BLOCK_ZERO => {
|
|
||||||
let file_offset = align!(disk_spec.image_size, metadata::BLOCK_SIZE_MIN as u64);
|
|
||||||
let new_size = file_offset
|
|
||||||
.checked_add(disk_spec.block_size as u64)
|
|
||||||
.ok_or(VhdxIoError::InvalidDiskSize)?;
|
|
||||||
|
|
||||||
f.file()
|
|
||||||
.set_len(new_size)
|
|
||||||
.map_err(VhdxIoError::ResizeFile)?;
|
|
||||||
disk_spec.image_size = new_size;
|
|
||||||
|
|
||||||
let new_bat_entry =
|
|
||||||
file_offset | (bat::PAYLOAD_BLOCK_FULLY_PRESENT & 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 < metadata::BLOCK_SIZE_MIN as u64 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
f.write_all_at(
|
|
||||||
&buf[write_count..(write_count + sector.free_bytes as usize)],
|
|
||||||
file_offset,
|
|
||||||
)
|
|
||||||
.map_err(VhdxIoError::ReadSectorBlock)?;
|
|
||||||
}
|
|
||||||
bat::PAYLOAD_BLOCK_FULLY_PRESENT => {
|
|
||||||
if sector.file_offset < metadata::BLOCK_SIZE_MIN as u64 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
f.write_all_at(
|
|
||||||
&buf[write_count..(write_count + sector.free_bytes as usize)],
|
|
||||||
sector.file_offset,
|
|
||||||
)
|
|
||||||
.map_err(VhdxIoError::ReadSectorBlock)?;
|
|
||||||
}
|
|
||||||
bat::PAYLOAD_BLOCK_PARTIALLY_PRESENT => {
|
|
||||||
return Err(VhdxIoError::UnsupportedMode);
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
return Err(VhdxIoError::InvalidBatEntryState);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sector_count -= sector.free_sectors;
|
|
||||||
sector_index += sector.free_sectors;
|
|
||||||
write_count += sector.free_bytes as usize;
|
|
||||||
}
|
|
||||||
Ok(write_count)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use vmm_sys_util::tempfile::TempFile;
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
// 512 is the only sector size read/write allowed by metadata::parse_metadata.
|
|
||||||
// [MS-VHDX] allows 4096, but it's not currently implemented
|
|
||||||
const SECTOR_SIZE: u64 = 512;
|
|
||||||
|
|
||||||
// The first BLOCK_SIZE_MIN bytes of a VHDx file are always headers, so
|
|
||||||
// write() treats a file offset below BLOCK_SIZE_MIN as malformed and skips
|
|
||||||
// the writing operation.
|
|
||||||
// Use a DATA_OFFSET greater than BLOCK_SIZE_MIN to bypass that early exit.
|
|
||||||
const DATA_OFFSET: u64 = 2 * metadata::BLOCK_SIZE_MIN as u64;
|
|
||||||
|
|
||||||
fn fixture() -> (AlignedFile, DiskSpec, Vec<BatEntry>) {
|
|
||||||
let disk_spec = DiskSpec {
|
|
||||||
// One block == one sector, so there's exactly one BAT entry.
|
|
||||||
sectors_per_block: 1,
|
|
||||||
logical_sector_size: SECTOR_SIZE as u32,
|
|
||||||
virtual_disk_size: SECTOR_SIZE,
|
|
||||||
image_size: DATA_OFFSET + SECTOR_SIZE,
|
|
||||||
block_size: SECTOR_SIZE as u32,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
let file = TempFile::new().unwrap().into_file();
|
|
||||||
file.set_len(DATA_OFFSET + SECTOR_SIZE).unwrap();
|
|
||||||
file.write_all_at(&vec![0xABu8; SECTOR_SIZE as usize], DATA_OFFSET)
|
|
||||||
.unwrap();
|
|
||||||
// A BAT entry saying "this block's data is already written to the
|
|
||||||
// file at `file_offset`".
|
|
||||||
let bat = vec![BatEntry(DATA_OFFSET | bat::PAYLOAD_BLOCK_FULLY_PRESENT)];
|
|
||||||
(AlignedFile::new(file, false), disk_spec, bat)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn read_sector() {
|
|
||||||
let (f, disk_spec, bat) = fixture();
|
|
||||||
|
|
||||||
let mut buf = vec![0u8; SECTOR_SIZE as usize];
|
|
||||||
let n = read(&f, &mut buf, &disk_spec, &bat, 0, 1).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(n, SECTOR_SIZE as usize);
|
|
||||||
assert!(buf.iter().all(|&b| b == 0xAB));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn write_sector() {
|
|
||||||
let (f, mut disk_spec, mut bat) = fixture();
|
|
||||||
|
|
||||||
let data = vec![0xCDu8; SECTOR_SIZE as usize];
|
|
||||||
let n = write(&f, &data, &mut disk_spec, 0, &mut bat, 0, 1).unwrap();
|
|
||||||
assert_eq!(n, SECTOR_SIZE as usize);
|
|
||||||
|
|
||||||
let mut readback = vec![0u8; SECTOR_SIZE as usize];
|
|
||||||
f.file().read_exact_at(&mut readback, DATA_OFFSET).unwrap();
|
|
||||||
assert_eq!(readback, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn read_short_buffer_is_rejected() {
|
|
||||||
let (f, disk_spec, bat) = fixture();
|
|
||||||
|
|
||||||
let mut buf = vec![0u8; SECTOR_SIZE as usize - 1];
|
|
||||||
let err = read(&f, &mut buf, &disk_spec, &bat, 0, 1).unwrap_err();
|
|
||||||
|
|
||||||
assert!(matches!(err, VhdxIoError::InvalidBufferLength));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn write_short_buffer_is_rejected() {
|
|
||||||
let (f, mut disk_spec, mut bat) = fixture();
|
|
||||||
|
|
||||||
let data = vec![0xCDu8; SECTOR_SIZE as usize - 1];
|
|
||||||
let err = write(&f, &data, &mut disk_spec, 0, &mut bat, 0, 1).unwrap_err();
|
|
||||||
|
|
||||||
assert!(matches!(err, VhdxIoError::InvalidBufferLength));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user