mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
Compare commits
236 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9acf610a7b | ||
|
|
c4cce38631 | ||
|
|
e302d50b09 | ||
|
|
3791062b23 | ||
|
|
e3fa27e251 | ||
|
|
932e1a636a | ||
|
|
d59dfdf8b6 | ||
|
|
9158c36dbf | ||
|
|
2a39b4a4e5 | ||
|
|
0395b10b29 | ||
|
|
1356b26c0f | ||
|
|
7536a95424 | ||
|
|
e295719967 | ||
|
|
6198fa7a79 | ||
|
|
f74cde7882 | ||
|
|
f033c5837b | ||
|
|
03349e7523 | ||
|
|
5d9f75ca6d | ||
|
|
861b7ab64d | ||
|
|
fe32002141 | ||
|
|
3e339439a4 | ||
|
|
cb5aaca809 | ||
|
|
0a25a77095 | ||
|
|
7d6c450cee | ||
|
|
d39b56544a | ||
|
|
8481026b60 | ||
|
|
e6ddfe07c3 | ||
|
|
221cbeba5a | ||
|
|
99d08a2b7f | ||
|
|
1b4b4fb0c2 | ||
|
|
3ed44c41a6 | ||
|
|
11eabaf97d | ||
|
|
4290d4f20e | ||
|
|
a8335d0b06 | ||
|
|
d10f6dd357 | ||
|
|
4dcd689cd9 | ||
|
|
68f9e82447 | ||
|
|
f256317ac6 | ||
|
|
0a785c1db2 | ||
|
|
d3b8300e9c | ||
|
|
bb9d3529db | ||
|
|
3cf310229c | ||
|
|
0e50cc320e | ||
|
|
9cdcd920b2 | ||
|
|
256f0c39e9 | ||
|
|
205e62aaa8 | ||
|
|
d760301c8d | ||
|
|
8e40413886 | ||
|
|
5549d846da | ||
|
|
76bd1418a2 | ||
|
|
15db321245 | ||
|
|
15ca4751b7 | ||
|
|
017a366a6e | ||
|
|
4e008e2dbc | ||
|
|
7eb5a6cd70 | ||
|
|
4961b93e69 | ||
|
|
44f187715f | ||
|
|
39e540ebb1 | ||
|
|
690741de13 | ||
|
|
4c16285dde | ||
|
|
bb713e8c01 | ||
|
|
a6426e3615 | ||
|
|
f1206a19df | ||
|
|
f2dfa7f6e0 | ||
|
|
ea83fe314c | ||
|
|
ad0f695352 | ||
|
|
70c798d2b9 | ||
|
|
db37ef2ad7 | ||
|
|
452424b66e | ||
|
|
ed27e93b81 | ||
|
|
0cd87053ee | ||
|
|
60efa1aa03 | ||
|
|
fae32412dc | ||
|
|
7a5781e5a2 | ||
|
|
e5c9f7d615 | ||
|
|
78a16227d2 | ||
|
|
66aa0743f0 | ||
|
|
d28d9eb34e | ||
|
|
1e8996f94f | ||
|
|
f122398262 | ||
|
|
10fb713218 | ||
|
|
90b0dc911c | ||
|
|
79fe396da9 | ||
|
|
72dc280a0c | ||
|
|
ad9a1878bf | ||
|
|
8fd8523042 | ||
|
|
d002090afe | ||
|
|
95e3c8f8fd | ||
|
|
5f399fc06d | ||
|
|
43a6a914b8 | ||
|
|
ad3f94638f | ||
|
|
4dd635a56a | ||
|
|
1179a1a1c9 | ||
|
|
c995b72384 | ||
|
|
f73a6c8d8e | ||
|
|
363273111a | ||
|
|
061351d82d | ||
|
|
5790bcefee | ||
|
|
86c736fe76 | ||
|
|
5737e58f29 | ||
|
|
e4af58b88e | ||
|
|
093f62858f | ||
|
|
305bec056f | ||
|
|
6cac99bd76 | ||
|
|
533d3a85d1 | ||
|
|
c7eac0f65a | ||
|
|
e7e850bbdd | ||
|
|
08b197bbc1 | ||
|
|
3259234e58 | ||
|
|
05d222f0eb | ||
|
|
57bc78da4f | ||
|
|
47cff7c37f | ||
|
|
3f5c719705 | ||
|
|
706b56e97d | ||
|
|
2d9e243163 | ||
|
|
7281459bf9 | ||
|
|
9bf4696b09 | ||
|
|
f7f79642ab | ||
|
|
1cc193ea3c | ||
|
|
676fb93c1a | ||
|
|
f7e72456b9 | ||
|
|
bd22190043 | ||
|
|
92325fc073 | ||
|
|
1ca6c159ef | ||
|
|
c38596d6d3 | ||
|
|
456f9b3871 | ||
|
|
f68880600c | ||
|
|
c3a809696a | ||
|
|
a9d6807522 | ||
|
|
c4bab33020 | ||
|
|
245bce23fa | ||
|
|
67ab81874a | ||
|
|
8b2af1a2c8 | ||
|
|
41930d11f7 | ||
|
|
9c85fbb1af | ||
|
|
5357761c37 | ||
|
|
bec975a7fc | ||
|
|
92370e8ff1 | ||
|
|
4a1eb0c485 | ||
|
|
6e6cf099ef | ||
|
|
6e002defe2 | ||
|
|
a51998605a | ||
|
|
b8be33dff7 | ||
|
|
3864230430 | ||
|
|
2c6426460e | ||
|
|
1a63b4b2ff | ||
|
|
c1f4df600f | ||
|
|
34c5a081aa | ||
|
|
3bff69734e | ||
|
|
91d15c331d | ||
|
|
e454bb629a | ||
|
|
09cf8fba5b | ||
|
|
c34baac649 | ||
|
|
a132001021 | ||
|
|
2c7d6be3f4 | ||
|
|
dd8687aebb | ||
|
|
dac0638fe8 | ||
|
|
92f415ea3f | ||
|
|
78799187e8 | ||
|
|
9011ff2161 | ||
|
|
5128ee9ba6 | ||
|
|
51e095c54d | ||
|
|
4c1ee0329e | ||
|
|
a09c8329fb | ||
|
|
f0febcae3d | ||
|
|
2c282a5a54 | ||
|
|
c72414552b | ||
|
|
bd17c84d3c | ||
|
|
55212b0437 | ||
|
|
84613d4273 | ||
|
|
34385e99f2 | ||
|
|
10b79431f6 | ||
|
|
3d5e5f318b | ||
|
|
68a1bf38e5 | ||
|
|
6e0403a959 | ||
|
|
364a0972f0 | ||
|
|
9283f87f90 | ||
|
|
458721629c | ||
|
|
d70d132a5c | ||
|
|
35121c44db | ||
|
|
aa8e9cd91a | ||
|
|
5d478c534e | ||
|
|
a70c1b38e7 | ||
|
|
971f552e09 | ||
|
|
edee53ac1a | ||
|
|
1f51e4525b | ||
|
|
f0b69d56d0 | ||
|
|
623fadfa9d | ||
|
|
777b7ee11e | ||
|
|
5226ceb974 | ||
|
|
17195e1a46 | ||
|
|
0df4b1ac4f | ||
|
|
2e0ec8095c | ||
|
|
ef2bbe5012 | ||
|
|
2524b015b8 | ||
|
|
9825397350 | ||
|
|
a637940be7 | ||
|
|
684fb1dfce | ||
|
|
e718e0ad36 | ||
|
|
f87772cd36 | ||
|
|
0d9a941a2c | ||
|
|
4f4940b02a | ||
|
|
0bfa160360 | ||
|
|
530719a57a | ||
|
|
cd2c43b489 | ||
|
|
394fd230b0 | ||
|
|
abcec231eb | ||
|
|
cda1ea53a5 | ||
|
|
e2d2b2f2f0 | ||
|
|
9ee3b29a11 | ||
|
|
eb147cb3cc | ||
|
|
b630b22ed9 | ||
|
|
a7645ae9d6 | ||
|
|
254db85a33 | ||
|
|
16b0e08482 | ||
|
|
8a37e154db | ||
|
|
003e89e8cd | ||
|
|
8e010f1aa3 | ||
|
|
7c631b2d1b | ||
|
|
ba962a30d5 | ||
|
|
8c136041cb | ||
|
|
b0bf889d58 | ||
|
|
2c3e6cd93d | ||
|
|
985dcfbf3e | ||
|
|
511a100842 | ||
|
|
8a26380657 | ||
|
|
76d8d47f6a | ||
|
|
423280fb96 | ||
|
|
5f2392c095 | ||
|
|
2d9fc3beb6 | ||
|
|
4cae96f070 | ||
|
|
f6568042ce | ||
|
|
2ae5b2567b | ||
|
|
6d70c570f3 | ||
|
|
930a911862 | ||
|
|
03b22a510d |
65
.github/dependabot.yml
vendored
65
.github/dependabot.yml
vendored
@@ -1,21 +1,66 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: cargo
|
||||
directory: "/"
|
||||
directories:
|
||||
- "/"
|
||||
- "/fuzz"
|
||||
schedule:
|
||||
interval: daily
|
||||
open-pull-requests-limit: 1
|
||||
interval: weekly
|
||||
allow:
|
||||
- dependency-type: direct
|
||||
- dependency-type: indirect
|
||||
- dependency-name: "acpi_tables"
|
||||
- dependency-name: "kvm-bindings"
|
||||
- dependency-name: "kvm-ioctls"
|
||||
- dependency-name: "linux-loader"
|
||||
- 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
|
||||
directory: "/fuzz"
|
||||
directories:
|
||||
- "/"
|
||||
- "/fuzz"
|
||||
schedule:
|
||||
interval: daily
|
||||
open-pull-requests-limit: 1
|
||||
interval: weekly
|
||||
allow:
|
||||
- dependency-type: direct
|
||||
- dependency-type: indirect
|
||||
- dependency-type: all
|
||||
ignore:
|
||||
- dependency-name: "acpi_tables"
|
||||
- dependency-name: "kvm-bindings"
|
||||
- dependency-name: "kvm-ioctls"
|
||||
- dependency-name: "linux-loader"
|
||||
- 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:
|
||||
|
||||
2
.github/workflows/audit.yaml
vendored
2
.github/workflows/audit.yaml
vendored
@@ -10,7 +10,7 @@ jobs:
|
||||
name: Audit
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions-rust-lang/audit@v1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
10
.github/workflows/build.yaml
vendored
10
.github/workflows/build.yaml
vendored
@@ -15,13 +15,13 @@ jobs:
|
||||
- stable
|
||||
- beta
|
||||
- nightly
|
||||
- "1.83.0"
|
||||
- "1.88.0"
|
||||
target:
|
||||
- x86_64-unknown-linux-gnu
|
||||
- x86_64-unknown-linux-musl
|
||||
steps:
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -52,6 +52,12 @@ jobs:
|
||||
- name: Build (default features + pvmemcontrol)
|
||||
run: cargo rustc --locked --bin cloud-hypervisor --features "pvmemcontrol" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
||||
|
||||
- name: Build (default features + fw_cfg)
|
||||
run: cargo rustc --locked --bin cloud-hypervisor --features "fw_cfg" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
||||
|
||||
- name: Build (default features + ivshmem)
|
||||
run: cargo rustc --locked --bin cloud-hypervisor --features "ivshmem" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
||||
|
||||
- name: Build (mshv)
|
||||
run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "mshv" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
||||
|
||||
|
||||
4
.github/workflows/dco.yaml
vendored
4
.github/workflows/dco.yaml
vendored
@@ -6,9 +6,9 @@ jobs:
|
||||
name: DCO Check ("Signed-Off-By")
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Set up Python 3.x
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.x'
|
||||
- name: Check DCO
|
||||
|
||||
4
.github/workflows/docker-image.yaml
vendored
4
.github/workflows/docker-image.yaml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
@@ -41,7 +41,7 @@ jobs:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
# generate Docker tags based on the following events/attributes
|
||||
tags: |
|
||||
type=raw,value=20250412-0
|
||||
type=raw,value=20250815-0
|
||||
type=sha
|
||||
|
||||
- name: Build and push
|
||||
|
||||
2
.github/workflows/formatting.yaml
vendored
2
.github/workflows/formatting.yaml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
RUSTFLAGS: -D warnings
|
||||
steps:
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
- name: Install Rust toolchain (${{ matrix.rust }})
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
|
||||
2
.github/workflows/fuzz-build.yaml
vendored
2
.github/workflows/fuzz-build.yaml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
RUSTFLAGS: -D warnings
|
||||
steps:
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
- name: Install Rust toolchain (${{ matrix.rust }})
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
|
||||
4
.github/workflows/gitlint.yaml
vendored
4
.github/workflows/gitlint.yaml
vendored
@@ -8,12 +8,12 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
fetch-depth: 0
|
||||
- name: Set up Python 3.10
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: "3.10"
|
||||
- name: Install dependencies
|
||||
|
||||
2
.github/workflows/hadolint.yaml
vendored
2
.github/workflows/hadolint.yaml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Lint Dockerfile
|
||||
uses: hadolint/hadolint-action@master
|
||||
|
||||
2
.github/workflows/integration-arm64.yaml
vendored
2
.github/workflows/integration-arm64.yaml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
- name: Fix workspace permissions
|
||||
run: sudo chown -R runner:runner ${GITHUB_WORKSPACE}
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Run unit tests (musl)
|
||||
|
||||
2
.github/workflows/integration-metrics.yaml
vendored
2
.github/workflows/integration-metrics.yaml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
METRICS_PUBLISH_KEY: ${{ secrets.METRICS_PUBLISH_KEY }}
|
||||
steps:
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Run metrics tests
|
||||
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
steps:
|
||||
- name: Code checkout
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Run rate-limiter integration tests
|
||||
|
||||
2
.github/workflows/integration-vfio.yaml
vendored
2
.github/workflows/integration-vfio.yaml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
run: sudo chown -R runner:runner ${GITHUB_WORKSPACE}
|
||||
- name: Code checkout
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Run VFIO integration tests
|
||||
|
||||
2
.github/workflows/integration-windows.yaml
vendored
2
.github/workflows/integration-windows.yaml
vendored
@@ -11,7 +11,7 @@ jobs:
|
||||
steps:
|
||||
- name: Code checkout
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Install Docker
|
||||
|
||||
2
.github/workflows/integration-x86-64.yaml
vendored
2
.github/workflows/integration-x86-64.yaml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
steps:
|
||||
- name: Code checkout
|
||||
if: ${{ github.event_name != 'pull_request' || (matrix.runner == 'garm-jammy' && matrix.libc == 'gnu') }}
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Install Docker
|
||||
|
||||
41
.github/workflows/lychee.yaml
vendored
41
.github/workflows/lychee.yaml
vendored
@@ -1,16 +1,45 @@
|
||||
name: Link Check (lychee)
|
||||
on:
|
||||
pull_request
|
||||
|
||||
on: pull_request
|
||||
jobs:
|
||||
link_check:
|
||||
name: Link Check
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
# Fetch the entire history so git diff can compare against the base branch
|
||||
fetch-depth: 0
|
||||
- name: Get changed files in PR
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@v47 # Using a dedicated action for robustness
|
||||
with:
|
||||
# Compare the HEAD of the PR with the merge-base (where the PR branches off)
|
||||
base_sha: ${{ github.event.pull_request.base.sha }}
|
||||
|
||||
- name: Link Availability Check
|
||||
# NEW STEP: Print all changed-files outputs for verification
|
||||
- name: Verify Changed Files
|
||||
run: |
|
||||
echo "--- tj-actions/changed-files Outputs ---"
|
||||
echo "any_changed: ${{ steps.changed-files.outputs.any_changed }}"
|
||||
echo "all_changed_files: ${{ steps.changed-files.outputs.all_changed_files }}"
|
||||
echo "added_files: ${{ steps.changed-files.outputs.added_files }}"
|
||||
echo "modified_files: ${{ steps.changed-files.outputs.modified_files }}"
|
||||
echo "deleted_files: ${{ steps.changed-files.outputs.deleted_files }}"
|
||||
echo "renamed_files: ${{ steps.changed-files.outputs.renamed_files }}"
|
||||
echo "----------------------------------------"
|
||||
# This will also show if the all_changed_files string is empty or not
|
||||
if [ -n "${{ steps.changed-files.outputs.all_changed_files }}" ]; then
|
||||
echo "Detected changes: all_changed_files output is NOT empty."
|
||||
else
|
||||
echo "No changes detected: all_changed_files output IS empty."
|
||||
fi
|
||||
- name: Link Availability Check (Diff Only)
|
||||
# MODIFIED: Only run lychee if the 'all_changed_files' output is not an empty string
|
||||
if: ${{ steps.changed-files.outputs.all_changed_files != '' }}
|
||||
uses: lycheeverse/lychee-action@master
|
||||
with:
|
||||
args: --verbose --config .lychee.toml .
|
||||
# Pass the space-separated list of changed files to lychee
|
||||
args: --verbose --config .lychee.toml ${{ steps.changed-files.outputs.all_changed_files }}
|
||||
failIfEmpty: false
|
||||
fail: true
|
||||
237
.github/workflows/mshv-infra.yaml
vendored
Normal file
237
.github/workflows/mshv-infra.yaml
vendored
Normal file
@@ -0,0 +1,237 @@
|
||||
name: MSHV Infra Setup
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
ARCH:
|
||||
description: 'Architecture for the VM'
|
||||
required: true
|
||||
type: string
|
||||
KEY:
|
||||
description: 'SSH Key Name'
|
||||
required: true
|
||||
type: string
|
||||
OS_DISK_SIZE:
|
||||
description: 'OS Disk Size in GB'
|
||||
required: true
|
||||
type: string
|
||||
RG:
|
||||
description: 'Resource Group Name'
|
||||
required: true
|
||||
type: string
|
||||
VM_SKU:
|
||||
description: 'VM SKU'
|
||||
required: true
|
||||
type: string
|
||||
secrets:
|
||||
MI_CLIENT_ID:
|
||||
required: true
|
||||
RUNNER_RG:
|
||||
required: true
|
||||
STORAGE_ACCOUNT_PATHS:
|
||||
required: true
|
||||
ARCH_SOURCE_PATH:
|
||||
required: true
|
||||
USERNAME:
|
||||
required: true
|
||||
outputs:
|
||||
PRIVATE_IP:
|
||||
description: 'Private IP of the VM'
|
||||
value: ${{ jobs.infra-setup.outputs.PRIVATE_IP }}
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
jobs:
|
||||
infra-setup:
|
||||
name: ${{ inputs.ARCH }} VM Provision
|
||||
runs-on: mshv
|
||||
continue-on-error: true
|
||||
outputs:
|
||||
PRIVATE_IP: ${{ steps.get-vm-ip.outputs.PRIVATE_IP }}
|
||||
steps:
|
||||
- name: Install & login to AZ CLI
|
||||
env:
|
||||
MI_CLIENT_ID: ${{ secrets.MI_CLIENT_ID }}
|
||||
run: |
|
||||
set -e
|
||||
echo "Installing Azure CLI if not already installed"
|
||||
if ! command -v az &>/dev/null; then
|
||||
curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash
|
||||
else
|
||||
echo "Azure CLI already installed"
|
||||
fi
|
||||
az --version
|
||||
echo "Logging into Azure CLI using Managed Identity"
|
||||
az login --identity --client-id ${MI_CLIENT_ID}
|
||||
|
||||
- name: Get Location
|
||||
id: get-location
|
||||
env:
|
||||
SKU: ${{ inputs.VM_SKU }}
|
||||
STORAGE_ACCOUNT_PATHS: ${{ secrets.STORAGE_ACCOUNT_PATHS }}
|
||||
run: |
|
||||
set -e
|
||||
# Extract vCPU count from SKU (e.g., "Standard_D2s_v3" => 2)
|
||||
vcpu=$(echo "$SKU" | sed -n 's/^Standard_[A-Za-z]\+\([0-9]\+\).*/\1/p')
|
||||
if [[ -z "$vcpu" ]]; then
|
||||
echo "Cannot extract vCPU count from SKU: $SKU"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SUPPORTED_LOCATIONS=$(echo "$STORAGE_ACCOUNT_PATHS" | jq -r 'to_entries[] | .key')
|
||||
|
||||
for location in $SUPPORTED_LOCATIONS; do
|
||||
family=$(az vm list-skus --size "$SKU" --location "$location" --resource-type "virtualMachines" --query '[0].family' -o tsv)
|
||||
if [[ -z "$family" ]]; then
|
||||
echo "Cannot determine VM family for SKU: $SKU in $location"
|
||||
continue
|
||||
fi
|
||||
|
||||
usage=$(az vm list-usage --location "$location" --query "[?name.value=='$family'] | [0]" -o json)
|
||||
current=$(echo "$usage" | jq -r '.currentValue')
|
||||
limit=$(echo "$usage" | jq -r '.limit')
|
||||
|
||||
if [[ $((limit - current)) -ge $vcpu ]]; then
|
||||
echo "Sufficient quota found in $location"
|
||||
echo "location=$location" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
done
|
||||
|
||||
echo "No location found with sufficient vCPU quota for SKU: $SKU"
|
||||
exit 1
|
||||
|
||||
- name: Create Resource Group
|
||||
id: rg-setup
|
||||
env:
|
||||
LOCATION: ${{ steps.get-location.outputs.location }}
|
||||
RG: ${{ inputs.RG }}
|
||||
STORAGE_ACCOUNT_PATHS: ${{ secrets.STORAGE_ACCOUNT_PATHS }}
|
||||
run: |
|
||||
set -e
|
||||
echo "Creating Resource Group: $RG"
|
||||
# Create the resource group
|
||||
echo "Creating resource group in location: ${LOCATION}"
|
||||
az group create --name ${RG} --location ${LOCATION}
|
||||
echo "Resource group created successfully."
|
||||
|
||||
- name: Generate SSH Key
|
||||
id: generate-ssh-key
|
||||
env:
|
||||
KEY: ${{ inputs.KEY }}
|
||||
run: |
|
||||
set -e
|
||||
echo "Generating SSH key: $KEY"
|
||||
mkdir -p ~/.ssh
|
||||
ssh-keygen -t rsa -b 4096 -f ~/.ssh/${KEY} -N ""
|
||||
|
||||
- name: Create VM
|
||||
id: vm-setup
|
||||
env:
|
||||
KEY: ${{ inputs.KEY }}
|
||||
LOCATION: ${{ steps.get-location.outputs.location }}
|
||||
OS_DISK_SIZE: ${{ inputs.OS_DISK_SIZE }}
|
||||
RG: ${{ inputs.RG }}
|
||||
RUNNER_RG: ${{ secrets.RUNNER_RG }}
|
||||
USERNAME: ${{ secrets.USERNAME }}
|
||||
VM_SKU: ${{ inputs.VM_SKU }}
|
||||
VM_IMAGE_NAME: ${{ inputs.ARCH }}_${{ steps.get-location.outputs.location }}_image
|
||||
VM_NAME: ${{ inputs.ARCH }}_${{ steps.get-location.outputs.location }}_${{ github.run_id }}
|
||||
run: |
|
||||
set -e
|
||||
echo "Creating $VM_SKU VM: $VM_NAME"
|
||||
|
||||
# Extract subnet ID from the runner VM
|
||||
echo "Retrieving subnet ID..."
|
||||
SUBNET_ID=$(az network vnet list --resource-group ${RUNNER_RG} --query "[?contains(location, '${LOCATION}')].{SUBNETS:subnets}" | jq -r ".[0].SUBNETS[0].id")
|
||||
if [[ -z "${SUBNET_ID}" ]]; then
|
||||
echo "ERROR: Failed to retrieve Subnet ID."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Extract image ID from the runner VM
|
||||
echo "Retrieving image ID..."
|
||||
IMAGE_ID=$(az image show --resource-group ${RUNNER_RG} --name ${VM_IMAGE_NAME} --query "id" -o tsv)
|
||||
if [[ -z "${IMAGE_ID}" ]]; then
|
||||
echo "ERROR: Failed to retrieve Image ID."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Create VM
|
||||
az vm create \
|
||||
--resource-group ${RG} \
|
||||
--name ${VM_NAME} \
|
||||
--subnet ${SUBNET_ID} \
|
||||
--size ${VM_SKU} \
|
||||
--location ${LOCATION} \
|
||||
--image ${IMAGE_ID} \
|
||||
--os-disk-size-gb ${OS_DISK_SIZE} \
|
||||
--public-ip-sku Standard \
|
||||
--storage-sku Premium_LRS \
|
||||
--public-ip-address "" \
|
||||
--admin-username ${USERNAME} \
|
||||
--ssh-key-value ~/.ssh/${KEY}.pub \
|
||||
--security-type Standard \
|
||||
--output json
|
||||
|
||||
echo "VM creation process completed successfully."
|
||||
|
||||
- name: Get VM Private IP
|
||||
id: get-vm-ip
|
||||
env:
|
||||
RG: ${{ inputs.RG }}
|
||||
VM_NAME: ${{ inputs.ARCH }}_${{ steps.get-location.outputs.location }}_${{ github.run_id }}
|
||||
run: |
|
||||
set -e
|
||||
echo "Retrieving VM Private IP address..."
|
||||
# Retrieve VM Private IP address
|
||||
PRIVATE_IP=$(az vm show -g ${RG} -n ${VM_NAME} -d --query privateIps -o tsv)
|
||||
if [[ -z "$PRIVATE_IP" ]]; then
|
||||
echo "ERROR: Failed to retrieve private IP address."
|
||||
exit 1
|
||||
fi
|
||||
echo "PRIVATE_IP=$PRIVATE_IP" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Wait for SSH availability
|
||||
env:
|
||||
KEY: ${{ inputs.KEY }}
|
||||
PRIVATE_IP: ${{ steps.get-vm-ip.outputs.PRIVATE_IP }}
|
||||
USERNAME: ${{ secrets.USERNAME }}
|
||||
run: |
|
||||
echo "Waiting for SSH to be accessible..."
|
||||
timeout 120 bash -c 'until ssh -o StrictHostKeyChecking=no -i ~/.ssh/${KEY} ${USERNAME}@${PRIVATE_IP} "exit" 2>/dev/null; do sleep 5; done'
|
||||
echo "VM is accessible!"
|
||||
|
||||
- name: Remove Old Host Key
|
||||
env:
|
||||
PRIVATE_IP: ${{ steps.get-vm-ip.outputs.PRIVATE_IP }}
|
||||
run: |
|
||||
set -e
|
||||
echo "Removing the old host key"
|
||||
ssh-keygen -R $PRIVATE_IP
|
||||
|
||||
- name: SSH into VM and Install Dependencies
|
||||
env:
|
||||
KEY: ${{ inputs.KEY }}
|
||||
PRIVATE_IP: ${{ steps.get-vm-ip.outputs.PRIVATE_IP }}
|
||||
USERNAME: ${{ secrets.USERNAME }}
|
||||
run: |
|
||||
set -e
|
||||
ssh -i ~/.ssh/${KEY} -o StrictHostKeyChecking=no ${USERNAME}@${PRIVATE_IP} << EOF
|
||||
set -e
|
||||
echo "Logged in successfully."
|
||||
echo "Installing dependencies..."
|
||||
sudo tdnf install -y git moby-engine moby-cli clang llvm pkg-config make gcc glibc-devel
|
||||
echo "Installing Rust..."
|
||||
curl -sSf https://sh.rustup.rs | sh -s -- --default-toolchain stable --profile default -y
|
||||
export PATH="\$HOME/.cargo/bin:\$PATH"
|
||||
cargo --version
|
||||
sudo mkdir -p /etc/docker/
|
||||
echo '{"default-ulimits":{"nofile":{"Hard":65535,"Name":"nofile","Soft":65535}}}' | sudo tee /etc/docker/daemon.json
|
||||
sudo systemctl stop docker
|
||||
sudo systemctl enable docker.service
|
||||
sudo systemctl enable containerd.service
|
||||
sudo systemctl start docker
|
||||
sudo groupadd -f docker
|
||||
sudo usermod -a -G docker ${USERNAME}
|
||||
sudo systemctl restart docker
|
||||
EOF
|
||||
109
.github/workflows/mshv-integration.yaml
vendored
Normal file
109
.github/workflows/mshv-integration.yaml
vendored
Normal file
@@ -0,0 +1,109 @@
|
||||
name: Cloud Hypervisor Tests (MSHV) (x86_64)
|
||||
on: [pull_request_target, merge_group]
|
||||
|
||||
jobs:
|
||||
infra-setup:
|
||||
name: MSHV Infra Setup (x86_64)
|
||||
uses: ./.github/workflows/mshv-infra.yaml
|
||||
with:
|
||||
ARCH: x86_64
|
||||
KEY: azure_key_${{ github.run_id }}
|
||||
OS_DISK_SIZE: 512
|
||||
RG: MSHV-INTEGRATION-${{ github.run_id }}
|
||||
VM_SKU: Standard_D16s_v5
|
||||
secrets:
|
||||
MI_CLIENT_ID: ${{ secrets.MSHV_MI_CLIENT_ID }}
|
||||
RUNNER_RG: ${{ secrets.MSHV_RUNNER_RG }}
|
||||
STORAGE_ACCOUNT_PATHS: ${{ secrets.MSHV_STORAGE_ACCOUNT_PATHS }}
|
||||
ARCH_SOURCE_PATH: ${{ secrets.MSHV_X86_SOURCE_PATH }}
|
||||
USERNAME: ${{ secrets.MSHV_USERNAME }}
|
||||
|
||||
run-tests:
|
||||
name: Integration Tests (x86_64)
|
||||
needs: infra-setup
|
||||
if: ${{ always() && needs.infra-setup.result == 'success' }}
|
||||
runs-on: mshv
|
||||
continue-on-error: true
|
||||
steps:
|
||||
- name: Run integration tests
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
KEY: azure_key_${{ github.run_id }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
REPO_URL: https://github.com/cloud-hypervisor/cloud-hypervisor.git
|
||||
REPO_DIR: cloud-hypervisor
|
||||
PRIVATE_IP: ${{ needs.infra-setup.outputs.PRIVATE_IP }}
|
||||
RG: MSHV-${{ github.run_id }}
|
||||
USERNAME: ${{ secrets.MSHV_USERNAME }}
|
||||
run: |
|
||||
set -e
|
||||
echo "Connecting to the VM via SSH..."
|
||||
ssh -i ~/.ssh/${KEY} -o StrictHostKeyChecking=no ${USERNAME}@${PRIVATE_IP} << EOF
|
||||
set -e
|
||||
echo "Logged in successfully."
|
||||
export PATH="\$HOME/.cargo/bin:\$PATH"
|
||||
|
||||
if [[ "${{ github.event_name }}" == "pull_request_target" ]]; then
|
||||
git clone --depth 1 "$REPO_URL" "$REPO_DIR"
|
||||
cd "$REPO_DIR"
|
||||
git fetch origin pull/${{ github.event.pull_request.number }}/merge
|
||||
git checkout FETCH_HEAD
|
||||
else
|
||||
git clone --depth 1 --single-branch --branch "${{ github.ref_name }}" "$REPO_URL" "$REPO_DIR"
|
||||
cd "$REPO_DIR"
|
||||
fi
|
||||
|
||||
echo "Loading VDPA kernel modules..."
|
||||
sudo modprobe vdpa
|
||||
sudo modprobe vhost_vdpa
|
||||
sudo modprobe vdpa_sim
|
||||
sudo modprobe vdpa_sim_blk
|
||||
sudo modprobe vdpa_sim_net
|
||||
|
||||
echo "Creating VDPA devices..."
|
||||
sudo vdpa dev add name vdpa-blk0 mgmtdev vdpasim_blk
|
||||
sudo vdpa dev add name vdpa-blk1 mgmtdev vdpasim_blk
|
||||
sudo vdpa dev add name vdpa-blk2 mgmtdev vdpasim_net
|
||||
|
||||
echo "Setting permissions..."
|
||||
for i in 0 1 2; do
|
||||
dev="/dev/vhost-vdpa-$i"
|
||||
if [ -e "$dev" ]; then
|
||||
sudo chown $USER:$USER "$dev"
|
||||
sudo chmod 660 "$dev"
|
||||
else
|
||||
echo "Warning: Device $dev not found"
|
||||
fi
|
||||
done
|
||||
|
||||
sudo ./scripts/dev_cli.sh tests --hypervisor mshv --integration
|
||||
EOF
|
||||
|
||||
cleanup:
|
||||
name: Cleanup
|
||||
needs: run-tests
|
||||
if: always()
|
||||
runs-on: mshv
|
||||
steps:
|
||||
- name: Delete RG
|
||||
env:
|
||||
RG: MSHV-INTEGRATION-${{ github.run_id }}
|
||||
run: |
|
||||
if az group exists --name ${RG}; then
|
||||
az group delete --name ${RG} --yes --no-wait
|
||||
else
|
||||
echo "Resource Group ${RG} does not exist. Skipping deletion."
|
||||
fi
|
||||
echo "Cleanup process completed."
|
||||
|
||||
- name: Delete SSH Key
|
||||
env:
|
||||
KEY: azure_key_${{ github.run_id }}
|
||||
run: |
|
||||
if [ -f ~/.ssh/${KEY} ]; then
|
||||
rm -f ~/.ssh/${KEY} ~/.ssh/${KEY}.pub
|
||||
echo "SSH key deleted successfully."
|
||||
else
|
||||
echo "SSH key does not exist. Skipping deletion."
|
||||
fi
|
||||
echo "Cleanup process completed."
|
||||
2
.github/workflows/openapi.yaml
vendored
2
.github/workflows/openapi.yaml
vendored
@@ -6,7 +6,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
container: openapitools/openapi-generator-cli
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Validate OpenAPI
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
2
.github/workflows/package-consistency.yaml
vendored
2
.github/workflows/package-consistency.yaml
vendored
@@ -10,7 +10,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
||||
30
.github/workflows/preview-riscv64-build.yaml
vendored
Normal file
30
.github/workflows/preview-riscv64-build.yaml
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
name: Cloud Hypervisor RISC-V 64-bit kvm build Preview
|
||||
on: [pull_request, merge_group]
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Cargo
|
||||
runs-on: riscv64-qemu-host
|
||||
strategy:
|
||||
fail-fast: false
|
||||
|
||||
steps:
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install Rust toolchain
|
||||
run: /opt/scripts/exec-in-qemu.sh rustup default 1.88.0
|
||||
|
||||
- name: Build test (kvm)
|
||||
run: /opt/scripts/exec-in-qemu.sh cargo rustc --locked --no-default-features --features "kvm"
|
||||
|
||||
- name: Clippy test (kvm)
|
||||
run: /opt/scripts/exec-in-qemu.sh cargo clippy --locked --no-default-features --features "kvm"
|
||||
|
||||
- name: Check no files were modified
|
||||
run: test -z "$(git status --porcelain)"
|
||||
@@ -19,12 +19,12 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install Rust toolchain
|
||||
run: /opt/scripts/exec-in-qemu.sh rustup default 1.83.0
|
||||
run: /opt/scripts/exec-in-qemu.sh rustup default 1.88.0
|
||||
|
||||
- name: Build ${{ matrix.module }} Module (kvm)
|
||||
run: /opt/scripts/exec-in-qemu.sh cargo rustc --locked -p ${{ matrix.module }} --no-default-features --features "kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
||||
21
.github/workflows/quality.yaml
vendored
21
.github/workflows/quality.yaml
vendored
@@ -29,7 +29,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -111,6 +111,21 @@ jobs:
|
||||
toolchain: ${{ matrix.rust }}
|
||||
target: ${{ matrix.target }}
|
||||
args: --locked --all --all-targets --tests --examples --features "tracing" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
||||
- name: Clippy (default features + fw_cfg)
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
|
||||
command: clippy
|
||||
args: --target=${{ matrix.target }} --locked --all --all-targets --tests --examples --features "fw_cfg" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
||||
|
||||
- name: Clippy (default features + ivshmem)
|
||||
uses: houseabsolute/actions-rust-cross@v1
|
||||
with:
|
||||
command: clippy
|
||||
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
|
||||
toolchain: ${{ matrix.rust }}
|
||||
target: ${{ matrix.target }}
|
||||
args: --locked --all --all-targets --tests --examples --features "ivshmem" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
||||
|
||||
- name: Clippy (sev_snp)
|
||||
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
|
||||
@@ -150,6 +165,6 @@ jobs:
|
||||
name: Typos / Spellcheck
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
# Executes "typos ."
|
||||
- uses: crate-ci/typos@v1.34.0
|
||||
- uses: crate-ci/typos@v1.39.0
|
||||
|
||||
8
.github/workflows/release.yaml
vendored
8
.github/workflows/release.yaml
vendored
@@ -29,7 +29,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
- name: Install musl-gcc
|
||||
if: contains(matrix.platform.target, 'musl')
|
||||
run: sudo apt install -y musl-tools
|
||||
@@ -45,7 +45,7 @@ jobs:
|
||||
target: ${{ matrix.platform.target }}
|
||||
args: ${{ matrix.platform.args }}
|
||||
strip: true
|
||||
toolchain: "1.83.0"
|
||||
toolchain: "1.88.0"
|
||||
- name: Copy Release Binaries
|
||||
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
||||
shell: bash
|
||||
@@ -54,7 +54,7 @@ jobs:
|
||||
cp target/${{ matrix.platform.target }}/release/ch-remote ./${{ matrix.platform.name_ch_remote }}
|
||||
- name: Upload Release Artifacts
|
||||
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: Artifacts for ${{ matrix.platform.target }}
|
||||
path: |
|
||||
@@ -80,7 +80,7 @@ jobs:
|
||||
github.event_name == 'create' && github.event.ref_type == 'tag' &&
|
||||
matrix.platform.target == 'x86_64-unknown-linux-gnu'
|
||||
id: upload-release-cloud-hypervisor-vendored-sources
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
path: cloud-hypervisor-${{ github.event.ref }}.tar.xz
|
||||
name: cloud-hypervisor-${{ github.event.ref }}.tar.xz
|
||||
|
||||
4
.github/workflows/reuse.yaml
vendored
4
.github/workflows/reuse.yaml
vendored
@@ -7,6 +7,6 @@ jobs:
|
||||
name: REUSE Compliance Check
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: REUSE Compliance Check
|
||||
uses: fsfe/reuse-action@v5
|
||||
uses: fsfe/reuse-action@v6
|
||||
|
||||
2
.github/workflows/shlint.yaml
vendored
2
.github/workflows/shlint.yaml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
- name: Run the shell script checkers
|
||||
uses: luizm/action-sh-checker@master
|
||||
env:
|
||||
|
||||
2
.github/workflows/taplo.yaml
vendored
2
.github/workflows/taplo.yaml
vendored
@@ -10,7 +10,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
- name: Install Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
- name: Install build dependencies
|
||||
|
||||
10
.lychee.toml
10
.lychee.toml
@@ -2,8 +2,6 @@ verbose = "info"
|
||||
|
||||
exclude = [
|
||||
# Availability of links below should be manually verified.
|
||||
# Page for intel SGX support, returns 403 while querying.
|
||||
'^https://www.intel.com/content/www/us/en/developer/tools/software-guard-extensions/linux-overview.html',
|
||||
# Page for intel TDX support, returns 403 while querying.
|
||||
'^https://www.intel.com/content/www/us/en/developer/tools/trust-domain-extensions/overview.html',
|
||||
# Page for TPM, returns 403 while querying.
|
||||
@@ -14,8 +12,16 @@ exclude = [
|
||||
|
||||
# OSDev has added bot protection and accesses my result in 403 Forbidden.
|
||||
'^https://wiki.osdev.org',
|
||||
# Exclude all pages with $ in the URL since $XXX is a variable
|
||||
"\\$.*",
|
||||
# Exclude local files
|
||||
"file://.*",
|
||||
]
|
||||
|
||||
# Exclude loopback addresses
|
||||
exclude_loopback = true
|
||||
|
||||
|
||||
max_retries = 3
|
||||
|
||||
retry_wait_time = 5
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
group_imports="StdExternalCrate"
|
||||
imports_granularity="Module"
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ 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"
|
||||
|
||||
@@ -114,3 +114,13 @@ 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
|
||||
[commit message](https://help.github.com/articles/closing-issues-via-commit-messages/).
|
||||
|
||||
## AI Generated Code
|
||||
|
||||
Our policy is to decline any contributions known to contain contents
|
||||
generated or derived from using Large Language Models (LLMs). This
|
||||
includes ChatGPT, Gemini, Claude, Copilot and similar tools.
|
||||
|
||||
The goal is to avoid ambiguity in license compliance and optimize the
|
||||
use of limited project resources, especially for code review and
|
||||
maintenance. This policy can be revisited as LLMs evolve and mature.
|
||||
|
||||
950
Cargo.lock
generated
950
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
68
Cargo.toml
68
Cargo.toml
@@ -3,11 +3,11 @@ authors = ["The Cloud Hypervisor Authors"]
|
||||
build = "build.rs"
|
||||
default-run = "cloud-hypervisor"
|
||||
description = "Open source Virtual Machine Monitor (VMM) that runs on top of KVM & MSHV"
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
homepage = "https://github.com/cloud-hypervisor/cloud-hypervisor"
|
||||
license = "Apache-2.0 AND BSD-3-Clause"
|
||||
name = "cloud-hypervisor"
|
||||
version = "47.0.0"
|
||||
version = "49.0.0"
|
||||
# Minimum buildable version:
|
||||
# Keep in sync with version in .github/workflows/build.yaml
|
||||
# Policy on MSRV (see #4318):
|
||||
@@ -15,7 +15,7 @@ version = "47.0.0"
|
||||
# a.) A dependency requires it,
|
||||
# b.) If we want to use a new feature and that MSRV is at least 6 months old,
|
||||
# c.) There is a security issue that is addressed by the toolchain update.
|
||||
rust-version = "1.83.0"
|
||||
rust-version = "1.88.0"
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
@@ -29,20 +29,20 @@ inherits = "release"
|
||||
strip = false
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.94"
|
||||
anyhow = { workspace = true }
|
||||
api_client = { path = "api_client" }
|
||||
clap = { version = "4.5.13", features = ["string"] }
|
||||
dhat = { version = "0.3.3", optional = true }
|
||||
clap = { workspace = true, features = ["string"] }
|
||||
dhat = { workspace = true, optional = true }
|
||||
env_logger = { workspace = true }
|
||||
epoll = "4.3.3"
|
||||
epoll = { workspace = true }
|
||||
event_monitor = { path = "event_monitor" }
|
||||
hypervisor = { path = "hypervisor" }
|
||||
libc = "0.2.167"
|
||||
log = { version = "0.4.22", features = ["std"] }
|
||||
libc = { workspace = true }
|
||||
log = { workspace = true, features = ["std"] }
|
||||
option_parser = { path = "option_parser" }
|
||||
seccompiler = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
signal-hook = "0.3.18"
|
||||
signal-hook = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tpm = { path = "tpm" }
|
||||
tracer = { path = "tracer" }
|
||||
@@ -52,11 +52,11 @@ vmm-sys-util = { workspace = true }
|
||||
zbus = { version = "5.7.1", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
dirs = "6.0.0"
|
||||
dirs = { workspace = true }
|
||||
net_util = { path = "net_util" }
|
||||
serde_json = { workspace = true }
|
||||
test_infra = { path = "test_infra" }
|
||||
wait-timeout = "0.2.0"
|
||||
wait-timeout = { workspace = true }
|
||||
|
||||
# Please adjust `vmm::feature_list()` accordingly when changing the
|
||||
# feature list below
|
||||
@@ -64,9 +64,11 @@ wait-timeout = "0.2.0"
|
||||
dbus_api = ["vmm/dbus_api", "zbus"]
|
||||
default = ["io_uring", "kvm"]
|
||||
dhat-heap = ["dhat", "vmm/dhat-heap"] # For heap profiling
|
||||
fw_cfg = ["vmm/fw_cfg"]
|
||||
guest_debug = ["vmm/guest_debug"]
|
||||
igvm = ["mshv", "vmm/igvm"]
|
||||
io_uring = ["vmm/io_uring"]
|
||||
ivshmem = ["vmm/ivshmem"]
|
||||
kvm = ["vmm/kvm"]
|
||||
mshv = ["vmm/mshv"]
|
||||
pvmemcontrol = ["vmm/pvmemcontrol"]
|
||||
@@ -100,24 +102,25 @@ members = [
|
||||
"vm-virtio",
|
||||
"vmm",
|
||||
]
|
||||
package.edition = "2024"
|
||||
|
||||
[workspace.dependencies]
|
||||
# rust-vmm crates
|
||||
acpi_tables = { git = "https://github.com/rust-vmm/acpi_tables", branch = "main" }
|
||||
kvm-bindings = "0.12.0"
|
||||
kvm-ioctls = "0.22.0"
|
||||
linux-loader = "0.13.0"
|
||||
mshv-bindings = "0.5.2"
|
||||
mshv-ioctls = "0.5.2"
|
||||
kvm-bindings = "0.12.1"
|
||||
kvm-ioctls = "0.22.1"
|
||||
linux-loader = "0.13.1"
|
||||
mshv-bindings = "0.6.0"
|
||||
mshv-ioctls = "0.6.0"
|
||||
seccompiler = "0.5.0"
|
||||
vfio-bindings = { version = "0.5.0", default-features = false }
|
||||
vfio-ioctls = { version = "0.5.0", default-features = false }
|
||||
vfio_user = { version = "0.1.0", default-features = false }
|
||||
vfio-bindings = { version = "0.6.0", default-features = false }
|
||||
vfio-ioctls = { version = "0.5.1", default-features = false }
|
||||
vfio_user = { version = "0.1.1", default-features = false }
|
||||
vhost = { version = "0.14.0", default-features = false }
|
||||
vhost-user-backend = { version = "0.20.0", default-features = false }
|
||||
virtio-bindings = "0.2.6"
|
||||
virtio-queue = "0.16.0"
|
||||
vm-fdt = { git = "https://github.com/rust-vmm/vm-fdt", branch = "main" }
|
||||
vm-fdt = "0.3.0"
|
||||
vm-memory = "0.16.1"
|
||||
vmm-sys-util = "0.14.0"
|
||||
|
||||
@@ -127,10 +130,25 @@ igvm = { git = "https://github.com/microsoft/igvm", branch = "main" }
|
||||
igvm_defs = { git = "https://github.com/microsoft/igvm", branch = "main" }
|
||||
|
||||
# serde crates
|
||||
serde_json = "1.0.120"
|
||||
serde = "1.0.228"
|
||||
serde_json = "1.0.145"
|
||||
serde_with = { version = "3.15.0", default-features = false }
|
||||
|
||||
# other crates
|
||||
anyhow = "1.0.99"
|
||||
bitflags = "2.9.4"
|
||||
byteorder = "1.5.0"
|
||||
cfg-if = "1.0.3"
|
||||
clap = "4.5.49"
|
||||
dhat = "0.3.3"
|
||||
dirs = "6.0.0"
|
||||
env_logger = "0.11.8"
|
||||
thiserror = "2.0.12"
|
||||
uuid = { version = "1.17.0" }
|
||||
zerocopy = { version = "0.8.26", default-features = false }
|
||||
epoll = "4.4.0"
|
||||
flume = "0.11.1"
|
||||
libc = "0.2.177"
|
||||
log = "0.4.28"
|
||||
signal-hook = "0.3.18"
|
||||
thiserror = "2.0.17"
|
||||
uuid = { version = "1.18.1" }
|
||||
wait-timeout = "0.2.1"
|
||||
zerocopy = { version = "0.8.27", default-features = false }
|
||||
|
||||
14
README.md
14
README.md
@@ -153,7 +153,7 @@ interface will be enabled as per `network-config` details.
|
||||
$ sudo setcap cap_net_admin+ep ./cloud-hypervisor
|
||||
$ ./create-cloud-init.sh
|
||||
$ ./cloud-hypervisor \
|
||||
--kernel ./hypervisor-fw \
|
||||
--firmware ./hypervisor-fw \
|
||||
--disk path=focal-server-cloudimg-amd64.raw path=/tmp/ubuntu-cloudinit.img \
|
||||
--cpus boot=4 \
|
||||
--memory size=1024M \
|
||||
@@ -175,6 +175,18 @@ $ ./cloud-hypervisor \
|
||||
--console off
|
||||
```
|
||||
|
||||
## Booting: `--firmware` vs `--kernel`
|
||||
|
||||
The following scenarios are supported by Cloud Hypervisor to bootstrap a VM, i.e.,
|
||||
to load a payload/bootitem(s):
|
||||
|
||||
- Provide firmware
|
||||
- Provide kernel \[+ cmdline\]\ [+ initrd\]
|
||||
|
||||
Please note that our Cloud Hypervisor firmware (`hypervisor-fw`) has a Xen PVH
|
||||
boot entry, therefore it can also be booted via the `--kernel` parameter, as
|
||||
seen in some examples.
|
||||
|
||||
### Custom Kernel and Disk Image
|
||||
|
||||
#### Building your Kernel
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
authors = ["The Cloud Hypervisor Authors"]
|
||||
edition = "2021"
|
||||
edition.workspace = true
|
||||
name = "api_client"
|
||||
version = "0.1.0"
|
||||
|
||||
|
||||
@@ -118,12 +118,11 @@ fn parse_http_response(socket: &mut dyn Read) -> Result<Option<String>, Error> {
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(body_offset) = body_offset {
|
||||
if let Some(content_length) = content_length {
|
||||
if res.len() >= content_length + body_offset {
|
||||
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..])));
|
||||
@@ -189,8 +188,8 @@ pub fn simple_api_full_command_with_fds<T: Read + Write + ScmSocket>(
|
||||
request_fds,
|
||||
)?;
|
||||
|
||||
if response.is_some() {
|
||||
println!("{}", response.unwrap());
|
||||
if let Some(response) = response {
|
||||
println!("{response}");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -1,23 +1,24 @@
|
||||
[package]
|
||||
authors = ["The Chromium OS Authors"]
|
||||
edition = "2021"
|
||||
edition.workspace = true
|
||||
name = "arch"
|
||||
version = "0.1.0"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
fw_cfg = []
|
||||
kvm = ["hypervisor/kvm"]
|
||||
sev_snp = []
|
||||
tdx = []
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.94"
|
||||
byteorder = "1.5.0"
|
||||
anyhow = { workspace = true }
|
||||
byteorder = { workspace = true }
|
||||
hypervisor = { path = "../hypervisor" }
|
||||
libc = "0.2.167"
|
||||
libc = { workspace = true }
|
||||
linux-loader = { workspace = true, features = ["bzimage", "elf", "pe"] }
|
||||
log = "0.4.22"
|
||||
serde = { version = "1.0.208", features = ["derive", "rc"] }
|
||||
log = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive", "rc"] }
|
||||
thiserror = { workspace = true }
|
||||
uuid = { workspace = true }
|
||||
vm-memory = { workspace = true, features = ["backend-bitmap", "backend-mmap"] }
|
||||
|
||||
@@ -25,8 +25,9 @@ use vm_memory::{Address, Bytes, GuestMemory, GuestMemoryError, GuestMemoryRegion
|
||||
|
||||
use super::super::{DeviceType, GuestMemoryMmap, InitramfsConfig};
|
||||
use super::layout::{
|
||||
GIC_V2M_COMPATIBLE, 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, SPI_BASE, SPI_NUM,
|
||||
GIC_V2M_COMPATIBLE, GICV2M_SPI_BASE, GICV2M_SPI_NUM, IRQ_BASE, MEM_32BIT_DEVICES_SIZE,
|
||||
MEM_32BIT_DEVICES_START, MEM_PCI_IO_SIZE, MEM_PCI_IO_START, PCI_HIGH_BASE,
|
||||
PCI_MMIO_CONFIG_SIZE_PER_SEGMENT,
|
||||
};
|
||||
use crate::{NumaNodes, PciSpaceInfo};
|
||||
|
||||
@@ -110,11 +111,8 @@ pub fn get_cache_size(cache_level: CacheLevel) -> u32 {
|
||||
|
||||
let file_path = Path::new(&file_directory);
|
||||
if !file_path.exists() {
|
||||
warn!("File: {} does not exist.", file_directory);
|
||||
0
|
||||
} else {
|
||||
info!("File: {} exist.", file_directory);
|
||||
|
||||
let src = fs::read_to_string(file_directory).expect("File not exists or file corrupted.");
|
||||
// The content of the file is as simple as a size, like: "32K"
|
||||
let src = src.trim();
|
||||
@@ -144,11 +142,8 @@ pub fn get_cache_coherency_line_size(cache_level: CacheLevel) -> u32 {
|
||||
|
||||
let file_path = Path::new(&file_directory);
|
||||
if !file_path.exists() {
|
||||
warn!("File: {} does not exist.", file_directory);
|
||||
0
|
||||
} else {
|
||||
info!("File: {} exist.", file_directory);
|
||||
|
||||
let src = fs::read_to_string(file_directory).expect("File not exists or file corrupted.");
|
||||
src.trim().parse::<u32>().unwrap()
|
||||
}
|
||||
@@ -167,11 +162,8 @@ pub fn get_cache_number_of_sets(cache_level: CacheLevel) -> u32 {
|
||||
|
||||
let file_path = Path::new(&file_directory);
|
||||
if !file_path.exists() {
|
||||
warn!("File: {} does not exist.", file_directory);
|
||||
0
|
||||
} else {
|
||||
info!("File: {} exist.", file_directory);
|
||||
|
||||
let src = fs::read_to_string(file_directory).expect("File not exists or file corrupted.");
|
||||
src.trim().parse::<u32>().unwrap()
|
||||
}
|
||||
@@ -195,11 +187,8 @@ pub fn get_cache_shared(cache_level: CacheLevel) -> bool {
|
||||
|
||||
let file_path = Path::new(&file_directory);
|
||||
if !file_path.exists() {
|
||||
warn!("File: {} does not exist.", file_directory);
|
||||
result = false;
|
||||
} else {
|
||||
info!("File: {} exist.", file_directory);
|
||||
|
||||
let src = fs::read_to_string(file_directory).expect("File not exists or file corrupted.");
|
||||
let src = src.trim();
|
||||
if src.is_empty() {
|
||||
@@ -218,7 +207,7 @@ pub fn create_fdt<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHash
|
||||
guest_mem: &GuestMemoryMmap,
|
||||
cmdline: &str,
|
||||
vcpu_mpidr: Vec<u64>,
|
||||
vcpu_topology: Option<(u8, u8, u8)>,
|
||||
vcpu_topology: Option<(u16, u16, u16, u16)>,
|
||||
device_info: &HashMap<(DeviceType, String), T, S>,
|
||||
gic_device: &Arc<Mutex<dyn Vgic>>,
|
||||
initrd: &Option<InitramfsConfig>,
|
||||
@@ -231,8 +220,8 @@ pub fn create_fdt<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHash
|
||||
let mut fdt = FdtWriter::new().unwrap();
|
||||
|
||||
// For an explanation why these nodes were introduced in the blob take a look at
|
||||
// https://github.com/torvalds/linux/blob/master/Documentation/devicetree/booting-without-of.txt#L845
|
||||
// Look for "Required nodes and properties".
|
||||
// the "Device Node Requirements" chapter of the Devicetree Specification.
|
||||
// https://www.devicetree.org/specifications/
|
||||
|
||||
// Header or the root node as per above mentioned documentation.
|
||||
let root_node = fdt.begin_node("")?;
|
||||
@@ -280,7 +269,7 @@ pub fn write_fdt_to_memory(fdt_final: Vec<u8>, guest_mem: &GuestMemoryMmap) -> R
|
||||
fn create_cpu_nodes(
|
||||
fdt: &mut FdtWriter,
|
||||
vcpu_mpidr: &[u64],
|
||||
vcpu_topology: Option<(u8, u8, u8)>,
|
||||
vcpu_topology: Option<(u16, u16, u16, u16)>,
|
||||
numa_nodes: &NumaNodes,
|
||||
) -> FdtWriterResult<()> {
|
||||
// See https://github.com/torvalds/linux/blob/master/Documentation/devicetree/bindings/arm/cpus.yaml.
|
||||
@@ -289,8 +278,11 @@ fn create_cpu_nodes(
|
||||
fdt.property_u32("#size-cells", 0x0)?;
|
||||
|
||||
let num_cpus = vcpu_mpidr.len();
|
||||
let (threads_per_core, cores_per_package, packages) = vcpu_topology.unwrap_or((1, 1, 1));
|
||||
let max_cpus: u32 = (threads_per_core * cores_per_package * packages).into();
|
||||
let (threads_per_core, cores_per_die, dies_per_package, packages) =
|
||||
vcpu_topology.unwrap_or((1, 1, 1, 1));
|
||||
let cores_per_package = cores_per_die * dies_per_package;
|
||||
let max_cpus: u32 =
|
||||
threads_per_core as u32 * cores_per_die as u32 * dies_per_package as u32 * packages as u32;
|
||||
|
||||
// Add cache info.
|
||||
// L1 Data Cache Info.
|
||||
@@ -322,7 +314,6 @@ fn create_cpu_nodes(
|
||||
if !cache_exist {
|
||||
warn!("cache sysfs system does not exist.");
|
||||
} else {
|
||||
info!("cache sysfs system exists.");
|
||||
// L1 Data Cache Info.
|
||||
l1_d_cache_size = get_cache_size(CacheLevel::L1D);
|
||||
l1_d_cache_line_size = get_cache_coherency_line_size(CacheLevel::L1D);
|
||||
@@ -370,7 +361,7 @@ fn create_cpu_nodes(
|
||||
if numa_nodes.len() > 1 {
|
||||
for numa_node_idx in 0..numa_nodes.len() {
|
||||
let numa_node = numa_nodes.get(&(numa_node_idx as u32));
|
||||
if numa_node.unwrap().cpus.contains(&(cpu_id as u8)) {
|
||||
if numa_node.unwrap().cpus.contains(&(cpu_id as u32)) {
|
||||
fdt.property_u32("numa-node-id", numa_node_idx as u32)?;
|
||||
}
|
||||
}
|
||||
@@ -423,9 +414,6 @@ fn create_cpu_nodes(
|
||||
|
||||
fdt.end_node(l2_cache_node)?;
|
||||
}
|
||||
if l2_cache_size != 0 && l2_cache_shared {
|
||||
warn!("L2 cache shared with other cpus");
|
||||
}
|
||||
}
|
||||
|
||||
fdt.end_node(cpu_node)?;
|
||||
@@ -462,7 +450,8 @@ fn create_cpu_nodes(
|
||||
}
|
||||
|
||||
if let Some(topology) = vcpu_topology {
|
||||
let (threads_per_core, cores_per_package, packages) = topology;
|
||||
let (threads_per_core, cores_per_die, dies_per_package, packages) = topology;
|
||||
let cores_per_package = cores_per_die * dies_per_package;
|
||||
let cpu_map_node = fdt.begin_node("cpu-map")?;
|
||||
|
||||
// Create device tree nodes with regard of above mapping.
|
||||
@@ -678,8 +667,8 @@ fn create_gic_node(fdt: &mut FdtWriter, gic_device: &Arc<Mutex<dyn Vgic>>) -> Fd
|
||||
fdt.property_array_u64("reg", &msi_reg_prop)?;
|
||||
|
||||
if msi_compatibility == GIC_V2M_COMPATIBLE {
|
||||
fdt.property_u32("arm,msi-base-spi", SPI_BASE)?;
|
||||
fdt.property_u32("arm,msi-num-spis", SPI_NUM)?;
|
||||
fdt.property_u32("arm,msi-base-spi", GICV2M_SPI_BASE)?;
|
||||
fdt.property_u32("arm,msi-num-spis", GICV2M_SPI_NUM)?;
|
||||
}
|
||||
|
||||
fdt.end_node(msic_node)?;
|
||||
@@ -850,6 +839,21 @@ fn create_gpio_node<T: DeviceInfoForFdt + Clone + Debug>(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// https://www.kernel.org/doc/Documentation/devicetree/bindings/arm/fw-cfg.txt
|
||||
#[cfg(feature = "fw_cfg")]
|
||||
fn create_fw_cfg_node<T: DeviceInfoForFdt + Clone + Debug>(
|
||||
fdt: &mut FdtWriter,
|
||||
dev_info: &T,
|
||||
) -> FdtWriterResult<()> {
|
||||
// FwCfg node
|
||||
let fw_cfg_node = fdt.begin_node(&format!("fw-cfg@{:x}", dev_info.addr()))?;
|
||||
fdt.property("compatible", b"qemu,fw-cfg-mmio\0")?;
|
||||
fdt.property_array_u64("reg", &[dev_info.addr(), dev_info.length()])?;
|
||||
fdt.end_node(fw_cfg_node)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_devices_node<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHasher>(
|
||||
fdt: &mut FdtWriter,
|
||||
dev_info: &HashMap<(DeviceType, String), T, S>,
|
||||
@@ -865,6 +869,8 @@ fn create_devices_node<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::Buil
|
||||
DeviceType::Virtio(_) => {
|
||||
ordered_virtio_device.push(info);
|
||||
}
|
||||
#[cfg(feature = "fw_cfg")]
|
||||
DeviceType::FwCfg => create_fw_cfg_node(fdt, info)?,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -994,39 +1000,39 @@ fn create_pci_nodes(
|
||||
fdt.property_array_u32("msi-map", &msi_map)?;
|
||||
fdt.property_u32("msi-parent", MSI_PHANDLE)?;
|
||||
|
||||
if pci_device_info_elem.pci_segment_id == 0 {
|
||||
if let Some(virtio_iommu_bdf) = virtio_iommu_bdf {
|
||||
// See kernel document Documentation/devicetree/bindings/pci/pci-iommu.txt
|
||||
// for 'iommu-map' attribute setting.
|
||||
let iommu_map = [
|
||||
0_u32,
|
||||
VIRTIO_IOMMU_PHANDLE,
|
||||
0_u32,
|
||||
virtio_iommu_bdf,
|
||||
virtio_iommu_bdf + 1,
|
||||
VIRTIO_IOMMU_PHANDLE,
|
||||
virtio_iommu_bdf + 1,
|
||||
0xffff - virtio_iommu_bdf,
|
||||
];
|
||||
fdt.property_array_u32("iommu-map", &iommu_map)?;
|
||||
if pci_device_info_elem.pci_segment_id == 0
|
||||
&& let Some(virtio_iommu_bdf) = virtio_iommu_bdf
|
||||
{
|
||||
// See kernel document Documentation/devicetree/bindings/pci/pci-iommu.txt
|
||||
// for 'iommu-map' attribute setting.
|
||||
let iommu_map = [
|
||||
0_u32,
|
||||
VIRTIO_IOMMU_PHANDLE,
|
||||
0_u32,
|
||||
virtio_iommu_bdf,
|
||||
virtio_iommu_bdf + 1,
|
||||
VIRTIO_IOMMU_PHANDLE,
|
||||
virtio_iommu_bdf + 1,
|
||||
0xffff - virtio_iommu_bdf,
|
||||
];
|
||||
fdt.property_array_u32("iommu-map", &iommu_map)?;
|
||||
|
||||
// See kernel document Documentation/devicetree/bindings/virtio/iommu.txt
|
||||
// for virtio-iommu node settings.
|
||||
let virtio_iommu_node_name = format!("virtio_iommu@{virtio_iommu_bdf:x}");
|
||||
let virtio_iommu_node = fdt.begin_node(&virtio_iommu_node_name)?;
|
||||
fdt.property_u32("#iommu-cells", 1)?;
|
||||
fdt.property_string("compatible", "virtio,pci-iommu")?;
|
||||
// See kernel document Documentation/devicetree/bindings/virtio/iommu.txt
|
||||
// for virtio-iommu node settings.
|
||||
let virtio_iommu_node_name = format!("virtio_iommu@{virtio_iommu_bdf:x}");
|
||||
let virtio_iommu_node = fdt.begin_node(&virtio_iommu_node_name)?;
|
||||
fdt.property_u32("#iommu-cells", 1)?;
|
||||
fdt.property_string("compatible", "virtio,pci-iommu")?;
|
||||
|
||||
// 'reg' is a five-cell address encoded as
|
||||
// (phys.hi phys.mid phys.lo size.hi size.lo). phys.hi should contain the
|
||||
// device's BDF as 0b00000000 bbbbbbbb dddddfff 00000000. The other cells
|
||||
// should be zero.
|
||||
let reg = [virtio_iommu_bdf << 8, 0_u32, 0_u32, 0_u32, 0_u32];
|
||||
fdt.property_array_u32("reg", ®)?;
|
||||
fdt.property_u32("phandle", VIRTIO_IOMMU_PHANDLE)?;
|
||||
// 'reg' is a five-cell address encoded as
|
||||
// (phys.hi phys.mid phys.lo size.hi size.lo). phys.hi should contain the
|
||||
// device's BDF as 0b00000000 bbbbbbbb dddddfff 00000000. The other cells
|
||||
// should be zero.
|
||||
let reg = [virtio_iommu_bdf << 8, 0_u32, 0_u32, 0_u32, 0_u32];
|
||||
fdt.property_array_u32("reg", ®)?;
|
||||
fdt.property_u32("phandle", VIRTIO_IOMMU_PHANDLE)?;
|
||||
|
||||
fdt.end_node(virtio_iommu_node)?;
|
||||
}
|
||||
fdt.end_node(virtio_iommu_node)?;
|
||||
}
|
||||
|
||||
fdt.end_node(pci_node)?;
|
||||
|
||||
@@ -139,11 +139,11 @@ pub const IRQ_BASE: u32 = 32;
|
||||
/// Number of supported interrupts
|
||||
pub const IRQ_NUM: u32 = 256;
|
||||
|
||||
/// Base SPI interrupt number
|
||||
pub const SPI_BASE: u32 = 32;
|
||||
/// Base SPI interrupt number for the GICv2M MSI frame
|
||||
pub const GICV2M_SPI_BASE: u32 = 128;
|
||||
|
||||
/// Total number of SPIs
|
||||
pub const SPI_NUM: u32 = 64;
|
||||
/// 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";
|
||||
|
||||
@@ -15,7 +15,7 @@ use std::sync::{Arc, Mutex};
|
||||
|
||||
use hypervisor::arch::aarch64::gic::Vgic;
|
||||
use hypervisor::arch::aarch64::regs::MPIDR_EL1;
|
||||
use log::{log_enabled, Level};
|
||||
use log::{Level, log_enabled};
|
||||
use thiserror::Error;
|
||||
use vm_memory::{Address, GuestAddress, GuestMemory, GuestMemoryAtomic};
|
||||
|
||||
@@ -66,8 +66,8 @@ pub struct EntryPoint {
|
||||
|
||||
/// Configure the specified VCPU, and return its MPIDR.
|
||||
pub fn configure_vcpu(
|
||||
vcpu: &Arc<dyn hypervisor::Vcpu>,
|
||||
id: u8,
|
||||
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 {
|
||||
@@ -126,7 +126,7 @@ pub fn configure_system<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::Bui
|
||||
guest_mem: &GuestMemoryMmap,
|
||||
cmdline: &str,
|
||||
vcpu_mpidr: Vec<u64>,
|
||||
vcpu_topology: Option<(u8, u8, u8)>,
|
||||
vcpu_topology: Option<(u16, u16, u16, u16)>,
|
||||
device_info: &HashMap<(DeviceType, String), T, S>,
|
||||
initrd: &Option<super::InitramfsConfig>,
|
||||
pci_space_info: &[PciSpaceInfo],
|
||||
@@ -180,7 +180,7 @@ pub fn initramfs_load_addr(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_host_cpu_phys_bits(hypervisor: &Arc<dyn hypervisor::Hypervisor>) -> u8 {
|
||||
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`,
|
||||
|
||||
@@ -18,9 +18,6 @@ use std::{fmt, result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use crate::x86_64::SgxEpcSection;
|
||||
|
||||
type GuestMemoryMmap = vm_memory::GuestMemoryMmap<vm_memory::bitmap::AtomicBitmap>;
|
||||
type GuestRegionMmap = vm_memory::GuestRegionMmap<vm_memory::bitmap::AtomicBitmap>;
|
||||
|
||||
@@ -84,9 +81,9 @@ pub mod aarch64;
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub use aarch64::{
|
||||
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, EntryPoint, _NSIG,
|
||||
_NSIG, EntryPoint, arch_memory_regions, configure_system, configure_vcpu,
|
||||
fdt::DeviceInfoForFdt, get_host_cpu_phys_bits, initramfs_load_addr, layout,
|
||||
layout::CMDLINE_MAX_SIZE, layout::IRQ_BASE, uefi,
|
||||
};
|
||||
|
||||
/// Module for riscv64 related functionality.
|
||||
@@ -95,9 +92,9 @@ pub mod riscv64;
|
||||
|
||||
#[cfg(target_arch = "riscv64")]
|
||||
pub use riscv64::{
|
||||
arch_memory_regions, configure_system, configure_vcpu, fdt::DeviceInfoForFdt,
|
||||
get_host_cpu_phys_bits, initramfs_load_addr, layout, layout::CMDLINE_MAX_SIZE,
|
||||
layout::IRQ_BASE, EntryPoint, _NSIG,
|
||||
_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")]
|
||||
@@ -105,10 +102,9 @@ pub mod x86_64;
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub use x86_64::{
|
||||
arch_memory_regions, configure_system, configure_vcpu, generate_common_cpuid,
|
||||
generate_ram_ranges, get_host_cpu_phys_bits, initramfs_load_addr, layout,
|
||||
layout::CMDLINE_MAX_SIZE, layout::CMDLINE_START, regs, CpuidConfig, CpuidFeatureEntry,
|
||||
EntryPoint, _NSIG,
|
||||
_NSIG, CpuidConfig, CpuidFeatureEntry, EntryPoint, arch_memory_regions, configure_system,
|
||||
configure_vcpu, generate_common_cpuid, generate_ram_ranges, get_host_cpu_phys_bits,
|
||||
initramfs_load_addr, layout, layout::CMDLINE_MAX_SIZE, layout::CMDLINE_START, regs,
|
||||
};
|
||||
|
||||
/// Safe wrapper for `sysconf(_SC_PAGESIZE)`.
|
||||
@@ -123,12 +119,10 @@ fn pagesize() -> usize {
|
||||
pub struct NumaNode {
|
||||
pub memory_regions: Vec<Arc<GuestRegionMmap>>,
|
||||
pub hotplug_regions: Vec<Arc<GuestRegionMmap>>,
|
||||
pub cpus: Vec<u8>,
|
||||
pub cpus: Vec<u32>,
|
||||
pub pci_segments: Vec<u16>,
|
||||
pub distances: BTreeMap<u32, u8>,
|
||||
pub memory_zones: Vec<String>,
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub sgx_epc_sections: Vec<SgxEpcSection>,
|
||||
}
|
||||
|
||||
pub type NumaNodes = BTreeMap<u32, NumaNode>;
|
||||
@@ -155,6 +149,9 @@ pub enum DeviceType {
|
||||
/// Device Type: GPIO.
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
Gpio,
|
||||
/// Device Type: fw_cfg.
|
||||
#[cfg(feature = "fw_cfg")]
|
||||
FwCfg,
|
||||
}
|
||||
|
||||
/// Default (smallest) memory page size for the supported architectures.
|
||||
|
||||
@@ -119,7 +119,7 @@ fn create_cpu_nodes(fdt: &mut FdtWriter, num_cpus: u32) -> FdtWriterResult<()> {
|
||||
fdt.property_u32("timebase-frequency", timebase_frequency)?;
|
||||
|
||||
for cpu_index in 0..num_cpus {
|
||||
let cpu = fdt.begin_node(&format!("cpu@{:x}", cpu_index))?;
|
||||
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")?;
|
||||
@@ -184,7 +184,7 @@ fn create_memory_node(fdt: &mut FdtWriter, guest_mem: &GuestMemoryMmap) -> FdtWr
|
||||
}
|
||||
|
||||
let ram_start = super::layout::RAM_START.raw_value();
|
||||
let memory_node_name = format!("memory@{:x}", ram_start);
|
||||
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)?;
|
||||
@@ -448,10 +448,7 @@ fn print_node(node: fdt_parser::node::FdtNode<'_, '_>, n_spaces: usize) {
|
||||
// - At first, try to convert it to CStr and print,
|
||||
// - If failed, print it as u32 array.
|
||||
let value_result = match CStr::from_bytes_with_nul(value) {
|
||||
Ok(value_cstr) => match value_cstr.to_str() {
|
||||
Ok(value_str) => Some(value_str),
|
||||
Err(_e) => None,
|
||||
},
|
||||
Ok(value_cstr) => value_cstr.to_str().ok(),
|
||||
Err(_e) => None,
|
||||
};
|
||||
|
||||
|
||||
@@ -44,16 +44,23 @@
|
||||
// | |
|
||||
// | 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
|
||||
/// 0x0 ~ 0x0400_0000 (64 MiB) resides APLICs
|
||||
pub const APLIC_START: GuestAddress = GuestAddress(0);
|
||||
/// 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
|
||||
@@ -91,7 +98,12 @@ pub const CMDLINE_MAX_SIZE: usize = 1024;
|
||||
pub const FDT_START: GuestAddress = RAM_START;
|
||||
pub const FDT_MAX_SIZE: u64 = 0x1_0000;
|
||||
|
||||
/// Kernel start after FDT
|
||||
/// 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
|
||||
|
||||
@@ -7,19 +7,26 @@
|
||||
pub mod fdt;
|
||||
/// Layout for this riscv64 system.
|
||||
pub mod layout;
|
||||
/// Module for loading UEFI binary.
|
||||
pub mod uefi;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Debug;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use hypervisor::arch::riscv64::aia::Vaia;
|
||||
use log::{log_enabled, Level};
|
||||
use log::{Level, log_enabled};
|
||||
use thiserror::Error;
|
||||
use vm_memory::{Address, GuestAddress, GuestMemory, GuestMemoryAtomic};
|
||||
|
||||
pub use self::fdt::DeviceInfoForFdt;
|
||||
use crate::{DeviceType, GuestMemoryMmap, PciSpaceInfo, RegionType};
|
||||
|
||||
pub const CLOUDHV_IRQCHIP_NUM_MSIS: u16 = 255;
|
||||
pub const CLOUDHV_IRQCHIP_NUM_SOURCES: u8 = 96;
|
||||
pub const CLOUDHV_IRQCHIP_NUM_PRIO_BITS: u8 = 3;
|
||||
pub const CLOUDHV_IRQCHIP_MAX_GUESTS_BITS: u8 = 3;
|
||||
pub const CLOUDHV_IRQCHIP_MAX_GUESTS: u8 = (1 << CLOUDHV_IRQCHIP_MAX_GUESTS_BITS) - 1;
|
||||
pub const _NSIG: i32 = 65;
|
||||
|
||||
/// Errors thrown while configuring riscv64 system.
|
||||
@@ -56,8 +63,8 @@ pub struct EntryPoint {
|
||||
|
||||
/// Configure the specified VCPU, and return its MPIDR.
|
||||
pub fn configure_vcpu(
|
||||
vcpu: &Arc<dyn hypervisor::Vcpu>,
|
||||
id: u8,
|
||||
vcpu: &dyn hypervisor::Vcpu,
|
||||
id: u32,
|
||||
boot_setup: Option<(EntryPoint, &GuestMemoryAtomic<GuestMemoryMmap>)>,
|
||||
) -> super::Result<()> {
|
||||
if let Some((kernel_entry_point, _guest_memory)) = boot_setup {
|
||||
@@ -149,7 +156,7 @@ pub fn initramfs_load_addr(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_host_cpu_phys_bits(_hypervisor: &Arc<dyn hypervisor::Hypervisor>) -> u8 {
|
||||
pub fn get_host_cpu_phys_bits(_hypervisor: &dyn hypervisor::Hypervisor) -> u8 {
|
||||
40
|
||||
}
|
||||
|
||||
|
||||
50
arch/src/riscv64/uefi.rs
Normal file
50
arch/src/riscv64/uefi.rs
Normal file
@@ -0,0 +1,50 @@
|
||||
// Copyright 2020 Arm Limited (or its affiliates). All rights reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
use std::os::fd::AsFd;
|
||||
use std::result;
|
||||
|
||||
use thiserror::Error;
|
||||
use vm_memory::{GuestAddress, GuestMemory};
|
||||
|
||||
/// Errors thrown while loading UEFI binary
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
/// Unable to seek to UEFI image start.
|
||||
#[error("Unable to seek to UEFI image start")]
|
||||
SeekUefiStart,
|
||||
/// Unable to seek to UEFI image end.
|
||||
#[error("Unable to seek to UEFI image end")]
|
||||
SeekUefiEnd,
|
||||
/// UEFI image too big.
|
||||
#[error("UEFI image too big")]
|
||||
UefiTooBig,
|
||||
/// Unable to read UEFI image
|
||||
#[error("Unable to read UEFI image")]
|
||||
ReadUefiImage,
|
||||
}
|
||||
type Result<T> = result::Result<T, Error>;
|
||||
|
||||
pub fn load_uefi<F, M: GuestMemory>(
|
||||
guest_mem: &M,
|
||||
guest_addr: GuestAddress,
|
||||
uefi_image: &mut F,
|
||||
) -> Result<()>
|
||||
where
|
||||
F: Read + Seek + AsFd,
|
||||
{
|
||||
let uefi_size = uefi_image
|
||||
.seek(SeekFrom::End(0))
|
||||
.map_err(|_| Error::SeekUefiEnd)? as usize;
|
||||
|
||||
// edk2 image on virtual platform is smaller than 3M
|
||||
if uefi_size > 0x300000 {
|
||||
return Err(Error::UefiTooBig);
|
||||
}
|
||||
uefi_image.rewind().map_err(|_| Error::SeekUefiStart)?;
|
||||
guest_mem
|
||||
.read_exact_volatile_from(guest_addr, &mut uefi_image.as_fd(), uefi_size)
|
||||
.map_err(|_| Error::ReadUefiImage)
|
||||
}
|
||||
@@ -6,7 +6,6 @@
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
|
||||
use std::result;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub type Result<T> = result::Result<T, hypervisor::HypervisorCpuError>;
|
||||
|
||||
@@ -24,7 +23,7 @@ pub fn set_apic_delivery_mode(reg: u32, mode: u32) -> u32 {
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `vcpu` - The VCPU object to configure.
|
||||
pub fn set_lint(vcpu: &Arc<dyn hypervisor::Vcpu>) -> Result<()> {
|
||||
pub fn set_lint(vcpu: &dyn hypervisor::Vcpu) -> Result<()> {
|
||||
let mut klapic = vcpu.get_lapic()?;
|
||||
|
||||
let lvt_lint0 = klapic.get_klapic_reg(APIC_LVT0);
|
||||
|
||||
@@ -6,16 +6,14 @@
|
||||
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
use std::sync::Arc;
|
||||
pub mod interrupts;
|
||||
pub mod layout;
|
||||
mod mpspec;
|
||||
mod mptable;
|
||||
pub mod regs;
|
||||
use std::collections::BTreeMap;
|
||||
use std::mem;
|
||||
|
||||
use hypervisor::arch::x86::{CpuIdEntry, CPUID_FLAG_VALID_INDEX};
|
||||
use hypervisor::arch::x86::{CPUID_FLAG_VALID_INDEX, CpuIdEntry};
|
||||
use hypervisor::{CpuVendor, HypervisorCpuError, HypervisorError};
|
||||
use linux_loader::loader::bootparam::{boot_params, setup_header};
|
||||
use linux_loader::loader::elf::start_info::{
|
||||
@@ -24,7 +22,7 @@ use linux_loader::loader::elf::start_info::{
|
||||
use thiserror::Error;
|
||||
use vm_memory::{
|
||||
Address, Bytes, GuestAddress, GuestAddressSpace, GuestMemory, GuestMemoryAtomic,
|
||||
GuestMemoryRegion, GuestUsize,
|
||||
GuestMemoryRegion,
|
||||
};
|
||||
|
||||
use crate::{GuestMemoryMmap, InitramfsConfig, RegionType};
|
||||
@@ -33,6 +31,10 @@ use std::arch::x86_64;
|
||||
#[cfg(feature = "tdx")]
|
||||
pub mod tdx;
|
||||
|
||||
// While modern architectures support more than 255 CPUs via x2APIC,
|
||||
// legacy devices such as mptable support at most 254 CPUs.
|
||||
pub const MAX_SUPPORTED_CPUS_LEGACY: u32 = 254;
|
||||
|
||||
// CPUID feature bits
|
||||
#[cfg(feature = "kvm")]
|
||||
const TSC_DEADLINE_TIMER_ECX_BIT: u8 = 24; // tsc deadline timer ecx bit.
|
||||
@@ -57,6 +59,8 @@ const KVM_FEATURE_ASYNC_PF_VMEXIT_BIT: u8 = 10;
|
||||
#[cfg(feature = "tdx")]
|
||||
const KVM_FEATURE_STEAL_TIME_BIT: u8 = 5;
|
||||
|
||||
const KVM_FEATURE_MSI_EXT_DEST_ID: u8 = 15;
|
||||
|
||||
pub const _NSIG: i32 = 65;
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
@@ -73,55 +77,7 @@ pub struct EntryPoint {
|
||||
const E820_RAM: u32 = 1;
|
||||
const E820_RESERVED: u32 = 2;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SgxEpcSection {
|
||||
start: GuestAddress,
|
||||
size: GuestUsize,
|
||||
}
|
||||
|
||||
impl SgxEpcSection {
|
||||
pub fn new(start: GuestAddress, size: GuestUsize) -> Self {
|
||||
SgxEpcSection { start, size }
|
||||
}
|
||||
pub fn start(&self) -> GuestAddress {
|
||||
self.start
|
||||
}
|
||||
pub fn size(&self) -> GuestUsize {
|
||||
self.size
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SgxEpcRegion {
|
||||
start: GuestAddress,
|
||||
size: GuestUsize,
|
||||
epc_sections: BTreeMap<String, SgxEpcSection>,
|
||||
}
|
||||
|
||||
impl SgxEpcRegion {
|
||||
pub fn new(start: GuestAddress, size: GuestUsize) -> Self {
|
||||
SgxEpcRegion {
|
||||
start,
|
||||
size,
|
||||
epc_sections: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
pub fn start(&self) -> GuestAddress {
|
||||
self.start
|
||||
}
|
||||
pub fn size(&self) -> GuestUsize {
|
||||
self.size
|
||||
}
|
||||
pub fn epc_sections(&self) -> &BTreeMap<String, SgxEpcSection> {
|
||||
&self.epc_sections
|
||||
}
|
||||
pub fn insert(&mut self, id: String, epc_section: SgxEpcSection) {
|
||||
self.epc_sections.insert(id, epc_section);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CpuidConfig {
|
||||
pub sgx_epc_sections: Option<Vec<SgxEpcSection>>,
|
||||
pub phys_bits: u8,
|
||||
pub kvm_hyperv: bool,
|
||||
#[cfg(feature = "tdx")]
|
||||
@@ -163,18 +119,6 @@ pub enum Error {
|
||||
#[error("Error setting up SMBIOS table")]
|
||||
SmbiosSetup(#[source] smbios::Error),
|
||||
|
||||
/// Could not find any SGX EPC section
|
||||
#[error("Could not find any SGX EPC section")]
|
||||
NoSgxEpcSection,
|
||||
|
||||
/// Missing SGX CPU feature
|
||||
#[error("Missing SGX CPU feature")]
|
||||
MissingSgxFeature,
|
||||
|
||||
/// Missing SGX_LC CPU feature
|
||||
#[error("Missing SGX_LC CPU feature")]
|
||||
MissingSgxLaunchControlFeature,
|
||||
|
||||
/// Error getting supported CPUID through the hypervisor (kvm/mshv) API
|
||||
#[error("Error getting supported CPUID through the hypervisor API")]
|
||||
CpuidGetSupported(#[source] HypervisorError),
|
||||
@@ -209,11 +153,11 @@ pub enum Error {
|
||||
E820Configuration,
|
||||
}
|
||||
|
||||
pub fn get_x2apic_id(cpu_id: u32, topology: Option<(u8, u8, u8)>) -> u32 {
|
||||
pub fn get_x2apic_id(cpu_id: u32, topology: Option<(u16, u16, u16, u16)>) -> u32 {
|
||||
if let Some(t) = topology {
|
||||
let thread_mask_width = u8::BITS - (t.0 - 1).leading_zeros();
|
||||
let core_mask_width = u8::BITS - (t.1 - 1).leading_zeros();
|
||||
let die_mask_width = u8::BITS - (t.2 - 1).leading_zeros();
|
||||
let thread_mask_width = u16::BITS - (t.0 - 1).leading_zeros();
|
||||
let core_mask_width = u16::BITS - (t.1 - 1).leading_zeros();
|
||||
let die_mask_width = u16::BITS - (t.2 - 1).leading_zeros();
|
||||
|
||||
let thread_id = cpu_id % (t.0 as u32);
|
||||
let core_id = cpu_id / (t.0 as u32) % (t.1 as u32);
|
||||
@@ -229,6 +173,13 @@ pub fn get_x2apic_id(cpu_id: u32, topology: Option<(u8, u8, u8)>) -> u32 {
|
||||
cpu_id
|
||||
}
|
||||
|
||||
pub fn get_max_x2apic_id(topology: (u16, u16, u16, u16)) -> u32 {
|
||||
get_x2apic_id(
|
||||
(topology.0 as u32 * topology.1 as u32 * topology.2 as u32 * topology.3 as u32) - 1,
|
||||
Some(topology),
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum CpuidReg {
|
||||
EAX,
|
||||
@@ -454,7 +405,7 @@ impl CpuidFeatureEntry {
|
||||
feature_reg: CpuidReg::EDX,
|
||||
compatible_check: CpuidCompatibleCheck::BitwiseSubset,
|
||||
},
|
||||
// KVM CPUID bits: https://www.kernel.org/doc/html/latest/virt/kvm/cpuid.html
|
||||
// KVM CPUID bits: https://www.kernel.org/doc/html/latest/virt/kvm/x86/cpuid.html
|
||||
// Leaf 0x4000_0000, EAX/EBX/ECX/EDX, KVM CPUID SIGNATURE
|
||||
CpuidFeatureEntry {
|
||||
function: 0x4000_0000,
|
||||
@@ -573,9 +524,13 @@ impl CpuidFeatureEntry {
|
||||
error!(
|
||||
"Detected incompatible CPUID entry: leaf={:#02x} (subleaf={:#02x}), register='{:?}', \
|
||||
compatible_check='{:?}', source VM feature='{:#04x}', destination VM feature'{:#04x}'.",
|
||||
entry.function, entry.index, entry.feature_reg,
|
||||
entry.compatible_check, src_vm_feature, dest_vm_feature
|
||||
);
|
||||
entry.function,
|
||||
entry.index,
|
||||
entry.feature_reg,
|
||||
entry.compatible_check,
|
||||
src_vm_feature,
|
||||
dest_vm_feature
|
||||
);
|
||||
|
||||
compatible = false;
|
||||
}
|
||||
@@ -591,7 +546,7 @@ impl CpuidFeatureEntry {
|
||||
}
|
||||
|
||||
pub fn generate_common_cpuid(
|
||||
hypervisor: &Arc<dyn hypervisor::Hypervisor>,
|
||||
hypervisor: &dyn hypervisor::Hypervisor,
|
||||
config: &CpuidConfig,
|
||||
) -> super::Result<Vec<CpuIdEntry>> {
|
||||
// SAFETY: cpuid called with valid leaves
|
||||
@@ -662,10 +617,6 @@ pub fn generate_common_cpuid(
|
||||
|
||||
CpuidPatch::patch_cpuid(&mut cpuid, cpuid_patches);
|
||||
|
||||
if let Some(sgx_epc_sections) = &config.sgx_epc_sections {
|
||||
update_cpuid_sgx(&mut cpuid, sgx_epc_sections)?;
|
||||
}
|
||||
|
||||
#[cfg(feature = "tdx")]
|
||||
let tdx_capabilities = if config.tdx {
|
||||
let caps = hypervisor
|
||||
@@ -738,6 +689,10 @@ pub fn generate_common_cpuid(
|
||||
entry.eax = (entry.eax & 0xffff_ff00) | (config.phys_bits as u32 & 0xff);
|
||||
}
|
||||
0x4000_0001 => {
|
||||
// Enable KVM_FEATURE_MSI_EXT_DEST_ID. This allows the guest to target
|
||||
// device interrupts to cpus with APIC IDs > 254 without interrupt remapping.
|
||||
entry.eax |= 1 << KVM_FEATURE_MSI_EXT_DEST_ID;
|
||||
|
||||
// These features are not supported by TDX
|
||||
#[cfg(feature = "tdx")]
|
||||
if config.tdx {
|
||||
@@ -819,15 +774,15 @@ pub fn generate_common_cpuid(
|
||||
}
|
||||
|
||||
pub fn configure_vcpu(
|
||||
vcpu: &Arc<dyn hypervisor::Vcpu>,
|
||||
id: u8,
|
||||
vcpu: &dyn hypervisor::Vcpu,
|
||||
id: u32,
|
||||
boot_setup: Option<(EntryPoint, &GuestMemoryAtomic<GuestMemoryMmap>)>,
|
||||
cpuid: Vec<CpuIdEntry>,
|
||||
kvm_hyperv: bool,
|
||||
cpu_vendor: CpuVendor,
|
||||
topology: Option<(u8, u8, u8)>,
|
||||
topology: (u16, u16, u16, u16),
|
||||
) -> super::Result<()> {
|
||||
let x2apic_id = get_x2apic_id(id as u32, topology);
|
||||
let x2apic_id = get_x2apic_id(id, Some(topology));
|
||||
|
||||
// Per vCPU CPUID changes; common are handled via generate_common_cpuid()
|
||||
let mut cpuid = cpuid;
|
||||
@@ -849,40 +804,31 @@ pub fn configure_vcpu(
|
||||
}
|
||||
assert!(apic_id_patched);
|
||||
|
||||
if let Some(t) = topology {
|
||||
update_cpuid_topology(&mut cpuid, t.0, t.1, t.2, cpu_vendor, id);
|
||||
}
|
||||
update_cpuid_topology(
|
||||
&mut cpuid, topology.0, topology.1, topology.2, topology.3, cpu_vendor, id,
|
||||
);
|
||||
|
||||
// The TSC frequency CPUID leaf should not be included when running with HyperV emulation
|
||||
if !kvm_hyperv {
|
||||
if let Some(tsc_khz) = vcpu.tsc_khz().map_err(Error::GetTscFrequency)? {
|
||||
// Need to check that the TSC doesn't vary with dynamic frequency
|
||||
// SAFETY: cpuid called with valid leaves
|
||||
if unsafe { std::arch::x86_64::__cpuid(0x8000_0007) }.edx
|
||||
& (1u32 << INVARIANT_TSC_EDX_BIT)
|
||||
> 0
|
||||
{
|
||||
CpuidPatch::set_cpuid_reg(
|
||||
&mut cpuid,
|
||||
0x4000_0000,
|
||||
None,
|
||||
CpuidReg::EAX,
|
||||
0x4000_0010,
|
||||
);
|
||||
cpuid.retain(|c| c.function != 0x4000_0010);
|
||||
cpuid.push(CpuIdEntry {
|
||||
function: 0x4000_0010,
|
||||
eax: tsc_khz,
|
||||
ebx: 1000000, /* LAPIC resolution of 1ns (freq: 1GHz) is hardcoded in KVM's
|
||||
* APIC_BUS_CYCLE_NS */
|
||||
..Default::default()
|
||||
});
|
||||
};
|
||||
}
|
||||
if !kvm_hyperv && let Some(tsc_khz) = vcpu.tsc_khz().map_err(Error::GetTscFrequency)? {
|
||||
// Need to check that the TSC doesn't vary with dynamic frequency
|
||||
// SAFETY: cpuid called with valid leaves
|
||||
if unsafe { std::arch::x86_64::__cpuid(0x8000_0007) }.edx & (1u32 << INVARIANT_TSC_EDX_BIT)
|
||||
> 0
|
||||
{
|
||||
CpuidPatch::set_cpuid_reg(&mut cpuid, 0x4000_0000, None, CpuidReg::EAX, 0x4000_0010);
|
||||
cpuid.retain(|c| c.function != 0x4000_0010);
|
||||
cpuid.push(CpuIdEntry {
|
||||
function: 0x4000_0010,
|
||||
eax: tsc_khz,
|
||||
ebx: 1000000, /* LAPIC resolution of 1ns (freq: 1GHz) is hardcoded in KVM's
|
||||
* APIC_BUS_CYCLE_NS */
|
||||
..Default::default()
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
for c in &cpuid {
|
||||
debug!("{}", c);
|
||||
debug!("{c}");
|
||||
}
|
||||
|
||||
vcpu.set_cpuid2(&cpuid)
|
||||
@@ -896,7 +842,15 @@ pub fn configure_vcpu(
|
||||
if let Some((kernel_entry_point, guest_memory)) = boot_setup {
|
||||
regs::setup_regs(vcpu, kernel_entry_point).map_err(Error::RegsConfiguration)?;
|
||||
regs::setup_fpu(vcpu).map_err(Error::FpuConfiguration)?;
|
||||
regs::setup_sregs(&guest_memory.memory(), vcpu).map_err(Error::SregsConfiguration)?;
|
||||
|
||||
// CPUs are required (by Intel sdm spec) to boot in x2apic mode if any
|
||||
// of the apic IDs is larger than 255. Experimentally, the Linux kernel
|
||||
// does not recognize the last vCPU if x2apic is not enabled when
|
||||
// there are 256 vCPUs in a flat hierarchy (i.e. max x2apic ID is 255),
|
||||
// so we need to enable x2apic in this case as well.
|
||||
let enable_x2_apic_mode = get_max_x2apic_id(topology) > MAX_SUPPORTED_CPUS_LEGACY;
|
||||
regs::setup_sregs(&guest_memory.memory(), vcpu, enable_x2_apic_mode)
|
||||
.map_err(Error::SregsConfiguration)?;
|
||||
}
|
||||
interrupts::set_lint(vcpu).map_err(|e| Error::LocalIntConfiguration(e.into()))?;
|
||||
Ok(())
|
||||
@@ -946,14 +900,13 @@ pub fn configure_system(
|
||||
cmdline_addr: GuestAddress,
|
||||
cmdline_size: usize,
|
||||
initramfs: &Option<InitramfsConfig>,
|
||||
_num_cpus: u8,
|
||||
_num_cpus: u32,
|
||||
setup_header: Option<setup_header>,
|
||||
rsdp_addr: Option<GuestAddress>,
|
||||
sgx_epc_region: Option<SgxEpcRegion>,
|
||||
serial_number: Option<&str>,
|
||||
uuid: Option<&str>,
|
||||
oem_strings: Option<&[&str]>,
|
||||
topology: Option<(u8, u8, u8)>,
|
||||
topology: Option<(u16, u16, u16, u16)>,
|
||||
) -> super::Result<()> {
|
||||
// Write EBDA address to location where ACPICA expects to find it
|
||||
guest_mem
|
||||
@@ -969,10 +922,10 @@ pub fn configure_system(
|
||||
mptable::setup_mptable(offset, guest_mem, _num_cpus, topology).map_err(Error::MpTableSetup)?;
|
||||
|
||||
// Check that the RAM is not smaller than the RSDP start address
|
||||
if let Some(rsdp_addr) = rsdp_addr {
|
||||
if rsdp_addr.0 > guest_mem.last_addr().0 {
|
||||
return Err(super::Error::RsdpPastRamEnd);
|
||||
}
|
||||
if let Some(rsdp_addr) = rsdp_addr
|
||||
&& rsdp_addr.0 > guest_mem.last_addr().0
|
||||
{
|
||||
return Err(super::Error::RsdpPastRamEnd);
|
||||
}
|
||||
|
||||
match setup_header {
|
||||
@@ -983,15 +936,8 @@ pub fn configure_system(
|
||||
initramfs,
|
||||
hdr,
|
||||
rsdp_addr,
|
||||
sgx_epc_region,
|
||||
),
|
||||
None => configure_pvh(
|
||||
guest_mem,
|
||||
cmdline_addr,
|
||||
initramfs,
|
||||
rsdp_addr,
|
||||
sgx_epc_region,
|
||||
),
|
||||
None => configure_pvh(guest_mem, cmdline_addr, initramfs, rsdp_addr),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1048,17 +994,15 @@ pub fn generate_ram_ranges(guest_mem: &GuestMemoryMmap) -> super::Result<Vec<Ram
|
||||
&& (first_region_end <= &mem_32bit_reserved_start))
|
||||
{
|
||||
error!(
|
||||
"Unexpected first memory region layout: (start: 0x{:08x}, end: 0x{:08x}).
|
||||
high_ram_start: 0x{:08x}, mem_32bit_reserved_start: 0x{:08x}",
|
||||
first_region_start, first_region_end, high_ram_start, mem_32bit_reserved_start
|
||||
"Unexpected first memory region layout: (start: 0x{first_region_start:08x}, end: 0x{first_region_end:08x}).
|
||||
high_ram_start: 0x{high_ram_start:08x}, mem_32bit_reserved_start: 0x{mem_32bit_reserved_start:08x}"
|
||||
);
|
||||
|
||||
return Err(super::Error::MemmapTableSetup);
|
||||
}
|
||||
|
||||
info!(
|
||||
"first usable physical memory range, start: 0x{:08x}, end: 0x{:08x}",
|
||||
high_ram_start, first_region_end
|
||||
"first usable physical memory range, start: 0x{high_ram_start:08x}, end: 0x{first_region_end:08x}"
|
||||
);
|
||||
|
||||
(high_ram_start, *first_region_end)
|
||||
@@ -1083,7 +1027,6 @@ fn configure_pvh(
|
||||
cmdline_addr: GuestAddress,
|
||||
initramfs: &Option<InitramfsConfig>,
|
||||
rsdp_addr: Option<GuestAddress>,
|
||||
sgx_epc_region: Option<SgxEpcRegion>,
|
||||
) -> super::Result<()> {
|
||||
const XEN_HVM_START_MAGIC_VALUE: u32 = 0x336ec578;
|
||||
|
||||
@@ -1149,15 +1092,6 @@ fn configure_pvh(
|
||||
E820_RESERVED,
|
||||
);
|
||||
|
||||
if let Some(sgx_epc_region) = sgx_epc_region {
|
||||
add_memmap_entry(
|
||||
&mut memmap,
|
||||
sgx_epc_region.start().raw_value(),
|
||||
sgx_epc_region.size(),
|
||||
E820_RESERVED,
|
||||
);
|
||||
}
|
||||
|
||||
start_info.memmap_entries = memmap.len() as u32;
|
||||
|
||||
// Copy the vector with the memmap table to the MEMMAP_START address
|
||||
@@ -1204,7 +1138,6 @@ fn configure_32bit_entry(
|
||||
initramfs: &Option<InitramfsConfig>,
|
||||
setup_hdr: setup_header,
|
||||
rsdp_addr: Option<GuestAddress>,
|
||||
sgx_epc_region: Option<SgxEpcRegion>,
|
||||
) -> super::Result<()> {
|
||||
const KERNEL_LOADER_OTHER: u8 = 0xff;
|
||||
|
||||
@@ -1260,15 +1193,6 @@ fn configure_32bit_entry(
|
||||
E820_RESERVED,
|
||||
)?;
|
||||
|
||||
if let Some(sgx_epc_region) = sgx_epc_region {
|
||||
add_e820_entry(
|
||||
&mut params,
|
||||
sgx_epc_region.start().raw_value(),
|
||||
sgx_epc_region.size(),
|
||||
E820_RESERVED,
|
||||
)?;
|
||||
}
|
||||
|
||||
if let Some(rsdp_addr) = rsdp_addr {
|
||||
params.acpi_rsdp_addr = rsdp_addr.0;
|
||||
}
|
||||
@@ -1333,7 +1257,7 @@ pub fn initramfs_load_addr(
|
||||
Ok(aligned_addr)
|
||||
}
|
||||
|
||||
pub fn get_host_cpu_phys_bits(hypervisor: &Arc<dyn hypervisor::Hypervisor>) -> u8 {
|
||||
pub fn get_host_cpu_phys_bits(hypervisor: &dyn hypervisor::Hypervisor) -> u8 {
|
||||
// SAFETY: call cpuid with valid leaves
|
||||
unsafe {
|
||||
let leaf = x86_64::__cpuid(0x8000_0000);
|
||||
@@ -1361,21 +1285,24 @@ pub fn get_host_cpu_phys_bits(hypervisor: &Arc<dyn hypervisor::Hypervisor>) -> u
|
||||
|
||||
fn update_cpuid_topology(
|
||||
cpuid: &mut Vec<CpuIdEntry>,
|
||||
threads_per_core: u8,
|
||||
cores_per_die: u8,
|
||||
dies_per_package: u8,
|
||||
threads_per_core: u16,
|
||||
cores_per_die: u16,
|
||||
dies_per_package: u16,
|
||||
packages: u16,
|
||||
cpu_vendor: CpuVendor,
|
||||
id: u8,
|
||||
id: u32,
|
||||
) {
|
||||
let x2apic_id = get_x2apic_id(
|
||||
id as u32,
|
||||
Some((threads_per_core, cores_per_die, dies_per_package)),
|
||||
id,
|
||||
Some((threads_per_core, cores_per_die, dies_per_package, packages)),
|
||||
);
|
||||
|
||||
let thread_width = 8 - (threads_per_core - 1).leading_zeros();
|
||||
let core_width = (8 - (cores_per_die - 1).leading_zeros()) + thread_width;
|
||||
let die_width = (8 - (dies_per_package - 1).leading_zeros()) + core_width;
|
||||
// Note: the topology defined here is per "package" (~NUMA node).
|
||||
let thread_width = u16::BITS - (threads_per_core - 1).leading_zeros();
|
||||
let core_width = u16::BITS - (cores_per_die - 1).leading_zeros() + thread_width;
|
||||
let die_width = u16::BITS - (dies_per_package - 1).leading_zeros() + core_width;
|
||||
|
||||
// The very old way: a flat number of logical CPUs per package: CPUID.1H:EBX[23:16] bits.
|
||||
let mut cpu_ebx = CpuidPatch::get_cpuid_reg(cpuid, 0x1, None, CpuidReg::EBX).unwrap_or(0);
|
||||
cpu_ebx |= ((dies_per_package as u32) * (cores_per_die as u32) * (threads_per_core as u32))
|
||||
& (0xff << 16);
|
||||
@@ -1385,6 +1312,7 @@ fn update_cpuid_topology(
|
||||
cpu_edx |= 1 << 28;
|
||||
CpuidPatch::set_cpuid_reg(cpuid, 0x1, None, CpuidReg::EDX, cpu_edx);
|
||||
|
||||
// The legacy way: threads+cores per package.
|
||||
// CPU Topology leaf 0xb
|
||||
CpuidPatch::set_cpuid_reg(cpuid, 0xb, Some(0), CpuidReg::EAX, thread_width);
|
||||
CpuidPatch::set_cpuid_reg(
|
||||
@@ -1407,6 +1335,7 @@ fn update_cpuid_topology(
|
||||
CpuidPatch::set_cpuid_reg(cpuid, 0xb, Some(1), CpuidReg::ECX, 2 << 8);
|
||||
CpuidPatch::set_cpuid_reg(cpuid, 0xb, Some(1), CpuidReg::EDX, x2apic_id);
|
||||
|
||||
// The modern way: many-level hierarchy (but we here only support four levels).
|
||||
// CPU Topology leaf 0x1f
|
||||
CpuidPatch::set_cpuid_reg(cpuid, 0x1f, Some(0), CpuidReg::EAX, thread_width);
|
||||
CpuidPatch::set_cpuid_reg(
|
||||
@@ -1497,57 +1426,6 @@ fn update_cpuid_topology(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The goal is to update the CPUID sub-leaves to reflect the number of EPC
|
||||
// sections exposed to the guest.
|
||||
fn update_cpuid_sgx(
|
||||
cpuid: &mut Vec<CpuIdEntry>,
|
||||
epc_sections: &[SgxEpcSection],
|
||||
) -> Result<(), Error> {
|
||||
// Something's wrong if there's no EPC section.
|
||||
if epc_sections.is_empty() {
|
||||
return Err(Error::NoSgxEpcSection);
|
||||
}
|
||||
// We can't go further if the hypervisor does not support SGX feature.
|
||||
if !CpuidPatch::is_feature_enabled(cpuid, 0x7, 0, CpuidReg::EBX, 2) {
|
||||
return Err(Error::MissingSgxFeature);
|
||||
}
|
||||
// We can't go further if the hypervisor does not support SGX_LC feature.
|
||||
if !CpuidPatch::is_feature_enabled(cpuid, 0x7, 0, CpuidReg::ECX, 30) {
|
||||
return Err(Error::MissingSgxLaunchControlFeature);
|
||||
}
|
||||
|
||||
// Get host CPUID for leaf 0x12, subleaf 0x2. This is to retrieve EPC
|
||||
// properties such as confidentiality and integrity.
|
||||
// SAFETY: call cpuid with valid leaves
|
||||
let leaf = unsafe { std::arch::x86_64::__cpuid_count(0x12, 0x2) };
|
||||
|
||||
for (i, epc_section) in epc_sections.iter().enumerate() {
|
||||
let subleaf_idx = i + 2;
|
||||
let start = epc_section.start().raw_value();
|
||||
let size = epc_section.size();
|
||||
let eax = (start & 0xffff_f000) as u32 | 0x1;
|
||||
let ebx = (start >> 32) as u32;
|
||||
let ecx = (size & 0xffff_f000) as u32 | (leaf.ecx & 0xf);
|
||||
let edx = (size >> 32) as u32;
|
||||
// CPU Topology leaf 0x12
|
||||
CpuidPatch::set_cpuid_reg(cpuid, 0x12, Some(subleaf_idx as u32), CpuidReg::EAX, eax);
|
||||
CpuidPatch::set_cpuid_reg(cpuid, 0x12, Some(subleaf_idx as u32), CpuidReg::EBX, ebx);
|
||||
CpuidPatch::set_cpuid_reg(cpuid, 0x12, Some(subleaf_idx as u32), CpuidReg::ECX, ecx);
|
||||
CpuidPatch::set_cpuid_reg(cpuid, 0x12, Some(subleaf_idx as u32), CpuidReg::EDX, edx);
|
||||
}
|
||||
|
||||
// Add one NULL entry to terminate the dynamic list
|
||||
let subleaf_idx = epc_sections.len() + 2;
|
||||
// CPU Topology leaf 0x12
|
||||
CpuidPatch::set_cpuid_reg(cpuid, 0x12, Some(subleaf_idx as u32), CpuidReg::EAX, 0);
|
||||
CpuidPatch::set_cpuid_reg(cpuid, 0x12, Some(subleaf_idx as u32), CpuidReg::EBX, 0);
|
||||
CpuidPatch::set_cpuid_reg(cpuid, 0x12, Some(subleaf_idx as u32), CpuidReg::ECX, 0);
|
||||
CpuidPatch::set_cpuid_reg(cpuid, 0x12, Some(subleaf_idx as u32), CpuidReg::EDX, 0);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use linux_loader::loader::bootparam::boot_e820_entry;
|
||||
@@ -1578,7 +1456,6 @@ mod tests {
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
config_err.unwrap_err();
|
||||
|
||||
@@ -1603,7 +1480,6 @@ mod tests {
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -1633,7 +1509,6 @@ mod tests {
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -1649,7 +1524,6 @@ mod tests {
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
@@ -1721,22 +1595,27 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_get_x2apic_id() {
|
||||
let x2apic_id = get_x2apic_id(0, Some((2, 3, 1)));
|
||||
let x2apic_id = get_x2apic_id(0, Some((2, 3, 1, 1)));
|
||||
assert_eq!(x2apic_id, 0);
|
||||
|
||||
let x2apic_id = get_x2apic_id(1, Some((2, 3, 1)));
|
||||
let x2apic_id = get_x2apic_id(1, Some((2, 3, 1, 1)));
|
||||
assert_eq!(x2apic_id, 1);
|
||||
|
||||
let x2apic_id = get_x2apic_id(2, Some((2, 3, 1)));
|
||||
let x2apic_id = get_x2apic_id(2, Some((2, 3, 1, 1)));
|
||||
assert_eq!(x2apic_id, 2);
|
||||
|
||||
let x2apic_id = get_x2apic_id(6, Some((2, 3, 1)));
|
||||
let x2apic_id = get_x2apic_id(6, Some((2, 3, 1, 1)));
|
||||
assert_eq!(x2apic_id, 8);
|
||||
|
||||
let x2apic_id = get_x2apic_id(7, Some((2, 3, 1)));
|
||||
let x2apic_id = get_x2apic_id(7, Some((2, 3, 1, 1)));
|
||||
assert_eq!(x2apic_id, 9);
|
||||
|
||||
let x2apic_id = get_x2apic_id(8, Some((2, 3, 1)));
|
||||
let x2apic_id = get_x2apic_id(8, Some((2, 3, 1, 1)));
|
||||
assert_eq!(x2apic_id, 10);
|
||||
|
||||
let x2apic_id = get_x2apic_id(257, Some((1, 312, 1, 1)));
|
||||
assert_eq!(x2apic_id, 257);
|
||||
|
||||
assert_eq!(255, get_max_x2apic_id((1, 256, 1, 1)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,9 +11,10 @@ use libc::c_uchar;
|
||||
use thiserror::Error;
|
||||
use vm_memory::{Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryError};
|
||||
|
||||
use super::MAX_SUPPORTED_CPUS_LEGACY;
|
||||
use crate::GuestMemoryMmap;
|
||||
use crate::layout::{APIC_START, HIGH_RAM_START, IOAPIC_START};
|
||||
use crate::x86_64::{get_x2apic_id, mpspec};
|
||||
use crate::GuestMemoryMmap;
|
||||
|
||||
// This is a workaround to the Rust enforcement specifying that any implementation of a foreign
|
||||
// trait (in this case `ByteValued`) where:
|
||||
@@ -61,9 +62,6 @@ pub enum Error {
|
||||
/// Failure while zeroing out the memory for the MP table.
|
||||
#[error("Failure while zeroing out the memory for the MP table")]
|
||||
Clear(#[source] GuestMemoryError),
|
||||
/// Number of CPUs exceeds the maximum supported CPUs
|
||||
#[error("Number of CPUs exceeds the maximum supported CPUs")]
|
||||
TooManyCpus,
|
||||
/// Failure to write the MP floating pointer.
|
||||
#[error("Failure to write the MP floating pointer")]
|
||||
WriteMpfIntel(#[source] GuestMemoryError),
|
||||
@@ -89,11 +87,6 @@ pub enum 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;
|
||||
|
||||
// Most of these variables are sourced from the Intel MP Spec 1.4.
|
||||
const SMP_MAGIC_IDENT: &[c_uchar; 4] = b"_MP_";
|
||||
const MPC_SIGNATURE: &[c_uchar; 4] = b"PCMP";
|
||||
@@ -121,7 +114,7 @@ fn mpf_intel_compute_checksum(v: &mpspec::mpf_intel) -> u8 {
|
||||
(!checksum).wrapping_add(1)
|
||||
}
|
||||
|
||||
fn compute_mp_size(num_cpus: u8) -> usize {
|
||||
fn compute_mp_size(num_cpus: u32) -> usize {
|
||||
mem::size_of::<MpfIntelWrapper>()
|
||||
+ mem::size_of::<MpcTableWrapper>()
|
||||
+ mem::size_of::<MpcCpuWrapper>() * (num_cpus as usize)
|
||||
@@ -135,14 +128,15 @@ fn compute_mp_size(num_cpus: u8) -> usize {
|
||||
pub fn setup_mptable(
|
||||
offset: GuestAddress,
|
||||
mem: &GuestMemoryMmap,
|
||||
num_cpus: u8,
|
||||
topology: Option<(u8, u8, u8)>,
|
||||
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.into(), topology);
|
||||
if x2apic_id_max >= MAX_SUPPORTED_CPUS {
|
||||
return Err(Error::TooManyCpus);
|
||||
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(());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,7 +151,7 @@ pub fn setup_mptable(
|
||||
}
|
||||
|
||||
let mut checksum: u8 = 0;
|
||||
let ioapicid: u8 = MAX_SUPPORTED_CPUS as u8 + 1;
|
||||
let ioapicid: u8 = MAX_SUPPORTED_CPUS_LEGACY as u8 + 1;
|
||||
|
||||
// The checked_add here ensures the all of the following base_mp.unchecked_add's will be without
|
||||
// overflow.
|
||||
@@ -195,7 +189,7 @@ pub fn setup_mptable(
|
||||
for cpu_id in 0..num_cpus {
|
||||
let mut mpc_cpu = MpcCpuWrapper(mpspec::mpc_cpu::default());
|
||||
mpc_cpu.0.type_ = mpspec::MP_PROCESSOR as u8;
|
||||
mpc_cpu.0.apicid = get_x2apic_id(cpu_id as u32, topology) as u8;
|
||||
mpc_cpu.0.apicid = get_x2apic_id(cpu_id, topology) as u8;
|
||||
mpc_cpu.0.apicver = APIC_VERSION;
|
||||
mpc_cpu.0.cpuflag = mpspec::CPU_ENABLED as u8
|
||||
| if cpu_id == 0 {
|
||||
@@ -394,11 +388,11 @@ mod tests {
|
||||
fn cpu_entry_count() {
|
||||
let mem = GuestMemoryMmap::from_ranges(&[(
|
||||
MPTABLE_START,
|
||||
compute_mp_size(MAX_SUPPORTED_CPUS as u8),
|
||||
compute_mp_size(MAX_SUPPORTED_CPUS_LEGACY),
|
||||
)])
|
||||
.unwrap();
|
||||
|
||||
for i in 0..MAX_SUPPORTED_CPUS as u8 {
|
||||
for i in 0..MAX_SUPPORTED_CPUS_LEGACY {
|
||||
setup_mptable(MPTABLE_START, &mem, i, None).unwrap();
|
||||
|
||||
let mpf_intel: MpfIntelWrapper = mem.read_obj(MPTABLE_START).unwrap();
|
||||
@@ -428,11 +422,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn cpu_entry_count_max() {
|
||||
let cpus = MAX_SUPPORTED_CPUS + 1;
|
||||
let mem =
|
||||
GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(cpus as u8))]).unwrap();
|
||||
let cpus = MAX_SUPPORTED_CPUS_LEGACY + 1;
|
||||
let mem = GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(cpus))]).unwrap();
|
||||
|
||||
let result = setup_mptable(MPTABLE_START, &mem, cpus as u8, None);
|
||||
result.unwrap_err();
|
||||
setup_mptable(MPTABLE_START, &mem, cpus, None).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
use std::sync::Arc;
|
||||
use std::{mem, result};
|
||||
|
||||
use hypervisor::arch::x86::gdt::{gdt_entry, segment_from_gdt};
|
||||
@@ -67,7 +66,7 @@ pub type Result<T> = result::Result<T, Error>;
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `vcpu` - Structure for the VCPU that holds the VCPU's fd.
|
||||
pub fn setup_fpu(vcpu: &Arc<dyn hypervisor::Vcpu>) -> Result<()> {
|
||||
pub fn setup_fpu(vcpu: &dyn hypervisor::Vcpu) -> Result<()> {
|
||||
let fpu: FpuState = FpuState {
|
||||
fcw: 0x37f,
|
||||
mxcsr: 0x1f80,
|
||||
@@ -82,7 +81,7 @@ pub fn setup_fpu(vcpu: &Arc<dyn hypervisor::Vcpu>) -> Result<()> {
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `vcpu` - Structure for the VCPU that holds the VCPU's fd.
|
||||
pub fn setup_msrs(vcpu: &Arc<dyn hypervisor::Vcpu>) -> Result<()> {
|
||||
pub fn setup_msrs(vcpu: &dyn hypervisor::Vcpu) -> Result<()> {
|
||||
vcpu.set_msrs(&vcpu.boot_msr_entries())
|
||||
.map_err(Error::SetModelSpecificRegisters)?;
|
||||
|
||||
@@ -95,7 +94,7 @@ pub fn setup_msrs(vcpu: &Arc<dyn hypervisor::Vcpu>) -> Result<()> {
|
||||
///
|
||||
/// * `vcpu` - Structure for the VCPU that holds the VCPU's fd.
|
||||
/// * `entry_point` - Description of the boot entry to set up.
|
||||
pub fn setup_regs(vcpu: &Arc<dyn hypervisor::Vcpu>, entry_point: EntryPoint) -> Result<()> {
|
||||
pub fn setup_regs(vcpu: &dyn hypervisor::Vcpu, entry_point: EntryPoint) -> Result<()> {
|
||||
let mut regs = vcpu.create_standard_regs();
|
||||
match entry_point.setup_header {
|
||||
None => {
|
||||
@@ -119,9 +118,13 @@ pub fn setup_regs(vcpu: &Arc<dyn hypervisor::Vcpu>, entry_point: EntryPoint) ->
|
||||
///
|
||||
/// * `mem` - The memory that will be passed to the guest.
|
||||
/// * `vcpu` - Structure for the VCPU that holds the VCPU's fd.
|
||||
pub fn setup_sregs(mem: &GuestMemoryMmap, vcpu: &Arc<dyn hypervisor::Vcpu>) -> Result<()> {
|
||||
pub fn setup_sregs(
|
||||
mem: &GuestMemoryMmap,
|
||||
vcpu: &dyn hypervisor::Vcpu,
|
||||
enable_x2_apic_mode: bool,
|
||||
) -> Result<()> {
|
||||
let mut sregs: SpecialRegisters = vcpu.get_sregs().map_err(Error::GetStatusRegisters)?;
|
||||
configure_segments_and_sregs(mem, &mut sregs)?;
|
||||
configure_segments_and_sregs(mem, &mut sregs, enable_x2_apic_mode)?;
|
||||
vcpu.set_sregs(&sregs).map_err(Error::SetStatusRegisters)
|
||||
}
|
||||
|
||||
@@ -148,6 +151,7 @@ fn write_idt_value(val: u64, guest_mem: &GuestMemoryMmap) -> Result<()> {
|
||||
pub fn configure_segments_and_sregs(
|
||||
mem: &GuestMemoryMmap,
|
||||
sregs: &mut SpecialRegisters,
|
||||
enable_x2_apic_mode: bool,
|
||||
) -> Result<()> {
|
||||
let gdt_table: [u64; BOOT_GDT_MAX] = {
|
||||
// Configure GDT entries as specified by PVH boot protocol
|
||||
@@ -183,6 +187,11 @@ pub fn configure_segments_and_sregs(
|
||||
sregs.cr0 = CR0_PE;
|
||||
sregs.cr4 = 0;
|
||||
|
||||
if enable_x2_apic_mode {
|
||||
const X2APIC_ENABLE_BIT: u64 = 1 << 10;
|
||||
sregs.apic_base |= X2APIC_ENABLE_BIT;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -204,7 +213,7 @@ mod tests {
|
||||
fn segments_and_sregs() {
|
||||
let mut sregs: SpecialRegisters = Default::default();
|
||||
let gm = create_guest_mem();
|
||||
configure_segments_and_sregs(&gm, &mut sregs).unwrap();
|
||||
configure_segments_and_sregs(&gm, &mut sregs, false).unwrap();
|
||||
assert_eq!(0x0, read_u64(&gm, BOOT_GDT_START));
|
||||
assert_eq!(
|
||||
0xcf9b000000ffff,
|
||||
|
||||
@@ -12,8 +12,8 @@ use thiserror::Error;
|
||||
use uuid::Uuid;
|
||||
use vm_memory::{Address, ByteValued, Bytes, GuestAddress};
|
||||
|
||||
use crate::layout::SMBIOS_START;
|
||||
use crate::GuestMemoryMmap;
|
||||
use crate::layout::SMBIOS_START;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
|
||||
@@ -108,7 +108,7 @@ fn tdvf_descriptor_offset(file: &mut File) -> Result<(SeekFrom, bool), TdvfError
|
||||
u16::from_le_bytes(table[offset - 18..offset - 16].try_into().unwrap()) as usize;
|
||||
debug!(
|
||||
"Entry GUID = {}, size = {}",
|
||||
entry_uuid.hyphenated().to_string(),
|
||||
entry_uuid.hyphenated(),
|
||||
entry_size
|
||||
);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
authors = ["The Chromium OS Authors", "The Cloud Hypervisor Authors"]
|
||||
edition = "2021"
|
||||
edition.workspace = true
|
||||
name = "block"
|
||||
version = "0.1.0"
|
||||
|
||||
@@ -9,14 +9,14 @@ default = []
|
||||
io_uring = ["dep:io-uring"]
|
||||
|
||||
[dependencies]
|
||||
byteorder = "1.5.0"
|
||||
byteorder = { workspace = true }
|
||||
crc-any = "2.5.0"
|
||||
io-uring = { version = "0.6.4", optional = true }
|
||||
libc = "0.2.167"
|
||||
log = "0.4.22"
|
||||
io-uring = { version = "0.7.10", optional = true }
|
||||
libc = { workspace = true }
|
||||
log = { workspace = true }
|
||||
remain = "0.2.15"
|
||||
serde = { version = "1.0.208", features = ["derive"] }
|
||||
smallvec = "1.13.2"
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
smallvec = "1.15.1"
|
||||
thiserror = { workspace = true }
|
||||
uuid = { workspace = true, features = ["v4"] }
|
||||
virtio-bindings = { workspace = true }
|
||||
|
||||
@@ -8,7 +8,7 @@ use std::os::fd::{AsRawFd, OwnedFd, RawFd};
|
||||
use thiserror::Error;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
use crate::DiskTopology;
|
||||
use crate::{BatchRequest, DiskTopology};
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum DiskFileError {
|
||||
@@ -79,6 +79,9 @@ pub enum AsyncIoError {
|
||||
/// Failed synchronizing file.
|
||||
#[error("Failed synchronizing file")]
|
||||
Fsync(#[source] std::io::Error),
|
||||
/// Failed submitting batch requests.
|
||||
#[error("Failed submitting batch requests")]
|
||||
SubmitBatchRequests(#[source] std::io::Error),
|
||||
}
|
||||
|
||||
pub type AsyncIoResult<T> = std::result::Result<T, AsyncIoError>;
|
||||
@@ -99,4 +102,10 @@ pub trait AsyncIo: Send {
|
||||
) -> AsyncIoResult<()>;
|
||||
fn fsync(&mut self, user_data: Option<u64>) -> AsyncIoResult<()>;
|
||||
fn next_completed_request(&mut self) -> Option<(u64, i32)>;
|
||||
fn batch_requests_enabled(&self) -> bool {
|
||||
false
|
||||
}
|
||||
fn submit_batch_requests(&mut self, _batch_request: &[BatchRequest]) -> AsyncIoResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@ use std::fs::File;
|
||||
use std::io::{Read, Seek, SeekFrom, Write};
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
|
||||
use crate::vhd::VhdFooter;
|
||||
use crate::BlockBackend;
|
||||
use crate::vhd::VhdFooter;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FixedVhd {
|
||||
|
||||
@@ -12,7 +12,7 @@ use crate::async_io::{
|
||||
};
|
||||
use crate::fixed_vhd::FixedVhd;
|
||||
use crate::raw_async::RawFileAsync;
|
||||
use crate::BlockBackend;
|
||||
use crate::{BatchRequest, BlockBackend};
|
||||
|
||||
pub struct FixedVhdDiskAsync(FixedVhd);
|
||||
|
||||
@@ -106,4 +106,12 @@ impl AsyncIo for FixedVhdAsync {
|
||||
fn next_completed_request(&mut self) -> Option<(u64, i32)> {
|
||||
self.raw_file_async.next_completed_request()
|
||||
}
|
||||
|
||||
fn batch_requests_enabled(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn submit_batch_requests(&mut self, batch_request: &[BatchRequest]) -> AsyncIoResult<()> {
|
||||
self.raw_file_async.submit_batch_requests(batch_request)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,12 +7,12 @@ use std::os::unix::io::{AsRawFd, RawFd};
|
||||
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
use crate::BlockBackend;
|
||||
use crate::async_io::{
|
||||
AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult,
|
||||
};
|
||||
use crate::fixed_vhd::FixedVhd;
|
||||
use crate::raw_sync::RawFileSync;
|
||||
use crate::BlockBackend;
|
||||
|
||||
pub struct FixedVhdDiskSync(FixedVhd);
|
||||
|
||||
|
||||
113
block/src/lib.rs
113
block/src/lib.rs
@@ -31,7 +31,7 @@ pub mod vhd;
|
||||
pub mod vhdx;
|
||||
pub mod vhdx_sync;
|
||||
|
||||
use std::alloc::{alloc_zeroed, dealloc, Layout};
|
||||
use std::alloc::{Layout, alloc_zeroed, dealloc};
|
||||
use std::collections::VecDeque;
|
||||
use std::fmt::Debug;
|
||||
use std::fs::File;
|
||||
@@ -39,13 +39,12 @@ use std::io::{self, IoSlice, IoSliceMut, Read, Seek, SeekFrom, Write};
|
||||
use std::os::linux::fs::MetadataExt;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::path::Path;
|
||||
use std::sync::{Arc, MutexGuard};
|
||||
use std::time::Instant;
|
||||
use std::{cmp, result};
|
||||
|
||||
#[cfg(feature = "io_uring")]
|
||||
use io_uring::{opcode, IoUring, Probe};
|
||||
use libc::{ioctl, S_IFBLK, S_IFMT};
|
||||
use io_uring::{IoUring, Probe, opcode};
|
||||
use libc::{S_IFBLK, S_IFMT, ioctl};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use smallvec::SmallVec;
|
||||
use thiserror::Error;
|
||||
@@ -139,6 +138,8 @@ pub enum ExecuteError {
|
||||
Read(#[source] GuestMemoryError),
|
||||
#[error("Failed to read_exact")]
|
||||
ReadExact(#[source] io::Error),
|
||||
#[error("Can't execute an operation other than `read` on a read-only device")]
|
||||
ReadOnly,
|
||||
#[error("Failed to seek")]
|
||||
Seek(#[source] io::Error),
|
||||
#[error("Failed to write")]
|
||||
@@ -168,6 +169,7 @@ impl ExecuteError {
|
||||
ExecuteError::Flush(_) => VIRTIO_BLK_S_IOERR,
|
||||
ExecuteError::Read(_) => VIRTIO_BLK_S_IOERR,
|
||||
ExecuteError::ReadExact(_) => VIRTIO_BLK_S_IOERR,
|
||||
ExecuteError::ReadOnly => VIRTIO_BLK_S_IOERR,
|
||||
ExecuteError::Seek(_) => VIRTIO_BLK_S_IOERR,
|
||||
ExecuteError::Write(_) => VIRTIO_BLK_S_IOERR,
|
||||
ExecuteError::WriteAll(_) => VIRTIO_BLK_S_IOERR,
|
||||
@@ -229,6 +231,20 @@ pub struct AlignedOperation {
|
||||
layout: Layout,
|
||||
}
|
||||
|
||||
pub struct BatchRequest {
|
||||
pub offset: libc::off_t,
|
||||
pub iovecs: SmallVec<[libc::iovec; DEFAULT_DESCRIPTOR_VEC_SIZE]>,
|
||||
pub user_data: u64,
|
||||
pub request_type: RequestType,
|
||||
}
|
||||
|
||||
pub struct ExecuteAsync {
|
||||
// `true` if the execution will complete asynchronously
|
||||
pub async_complete: bool,
|
||||
// request need to be batched for submission if any
|
||||
pub batch_request: Option<BatchRequest>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Request {
|
||||
pub request_type: RequestType,
|
||||
@@ -243,7 +259,7 @@ pub struct Request {
|
||||
impl Request {
|
||||
pub fn parse<B: Bitmap + 'static>(
|
||||
desc_chain: &mut DescriptorChain<GuestMemoryLoadGuard<vm_memory::GuestMemoryMmap<B>>>,
|
||||
access_platform: Option<&Arc<dyn AccessPlatform>>,
|
||||
access_platform: Option<&dyn AccessPlatform>,
|
||||
) -> result::Result<Request, Error> {
|
||||
let hdr_desc = desc_chain
|
||||
.next()
|
||||
@@ -276,14 +292,14 @@ impl Request {
|
||||
.next()
|
||||
.ok_or(Error::DescriptorChainTooShort)
|
||||
.inspect_err(|_| {
|
||||
error!("Only head descriptor present: request = {:?}", req);
|
||||
error!("Only head descriptor present: request = {req:?}");
|
||||
})?;
|
||||
|
||||
if !desc.has_next() {
|
||||
status_desc = desc;
|
||||
// Only flush requests are allowed to skip the data descriptor.
|
||||
if req.request_type != RequestType::Flush {
|
||||
error!("Need a data descriptor: request = {:?}", req);
|
||||
error!("Need a data descriptor: request = {req:?}");
|
||||
return Err(Error::DescriptorChainTooShort);
|
||||
}
|
||||
} else {
|
||||
@@ -308,7 +324,7 @@ impl Request {
|
||||
.next()
|
||||
.ok_or(Error::DescriptorChainTooShort)
|
||||
.inspect_err(|_| {
|
||||
error!("DescriptorChain corrupted: request = {:?}", req);
|
||||
error!("DescriptorChain corrupted: request = {req:?}");
|
||||
})?;
|
||||
}
|
||||
status_desc = desc;
|
||||
@@ -394,7 +410,7 @@ impl Request {
|
||||
disk_image: &mut dyn AsyncIo,
|
||||
serial: &[u8],
|
||||
user_data: u64,
|
||||
) -> result::Result<bool, ExecuteError> {
|
||||
) -> result::Result<ExecuteAsync, ExecuteError> {
|
||||
let sector = self.sector;
|
||||
let request_type = self.request_type;
|
||||
let offset = (sector << SECTOR_SHIFT) as libc::off_t;
|
||||
@@ -431,7 +447,7 @@ impl Request {
|
||||
// In case it's not properly aligned, an intermediate buffer is
|
||||
// created with the correct alignment, and a copy from/to the
|
||||
// origin buffer is performed, depending on the type of operation.
|
||||
let iov_base = if (origin_ptr.as_ptr() as u64) % SECTOR_SIZE != 0 {
|
||||
let iov_base = if !(origin_ptr.as_ptr() as u64).is_multiple_of(SECTOR_SIZE) {
|
||||
let layout = Layout::from_size_align(data_len, SECTOR_SIZE as usize).unwrap();
|
||||
// SAFETY: layout has non-zero size
|
||||
let aligned_ptr = unsafe { alloc_zeroed(layout) };
|
||||
@@ -470,6 +486,10 @@ impl Request {
|
||||
iovecs.push(iovec);
|
||||
}
|
||||
|
||||
let mut ret = ExecuteAsync {
|
||||
async_complete: true,
|
||||
batch_request: None,
|
||||
};
|
||||
// Queue operations expected to be submitted.
|
||||
match request_type {
|
||||
RequestType::In => {
|
||||
@@ -479,14 +499,32 @@ impl Request {
|
||||
.bitmap()
|
||||
.mark_dirty(0, *data_len as usize);
|
||||
}
|
||||
disk_image
|
||||
.read_vectored(offset, &iovecs, user_data)
|
||||
.map_err(ExecuteError::AsyncRead)?;
|
||||
if disk_image.batch_requests_enabled() {
|
||||
ret.batch_request = Some(BatchRequest {
|
||||
offset,
|
||||
iovecs,
|
||||
user_data,
|
||||
request_type,
|
||||
});
|
||||
} else {
|
||||
disk_image
|
||||
.read_vectored(offset, &iovecs, user_data)
|
||||
.map_err(ExecuteError::AsyncRead)?;
|
||||
}
|
||||
}
|
||||
RequestType::Out => {
|
||||
disk_image
|
||||
.write_vectored(offset, &iovecs, user_data)
|
||||
.map_err(ExecuteError::AsyncWrite)?;
|
||||
if disk_image.batch_requests_enabled() {
|
||||
ret.batch_request = Some(BatchRequest {
|
||||
offset,
|
||||
iovecs,
|
||||
user_data,
|
||||
request_type,
|
||||
});
|
||||
} else {
|
||||
disk_image
|
||||
.write_vectored(offset, &iovecs, user_data)
|
||||
.map_err(ExecuteError::AsyncWrite)?;
|
||||
}
|
||||
}
|
||||
RequestType::Flush => {
|
||||
disk_image
|
||||
@@ -504,12 +542,13 @@ impl Request {
|
||||
}
|
||||
mem.write_slice(serial, data_addr)
|
||||
.map_err(ExecuteError::Write)?;
|
||||
return Ok(false);
|
||||
ret.async_complete = false;
|
||||
return Ok(ret);
|
||||
}
|
||||
RequestType::Unsupported(t) => return Err(ExecuteError::Unsupported(t)),
|
||||
}
|
||||
|
||||
Ok(true)
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
pub fn complete_async(&mut self) -> result::Result<(), Error> {
|
||||
@@ -646,10 +685,7 @@ pub fn block_io_uring_is_supported() -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
pub trait AsyncAdaptor<F>
|
||||
where
|
||||
F: Read + Write + Seek,
|
||||
{
|
||||
pub trait AsyncAdaptor {
|
||||
fn read_vectored_sync(
|
||||
&mut self,
|
||||
offset: libc::off_t,
|
||||
@@ -657,7 +693,10 @@ where
|
||||
user_data: u64,
|
||||
eventfd: &EventFd,
|
||||
completion_list: &mut VecDeque<(u64, i32)>,
|
||||
) -> AsyncIoResult<()> {
|
||||
) -> AsyncIoResult<()>
|
||||
where
|
||||
Self: Read + Seek,
|
||||
{
|
||||
// Convert libc::iovec into IoSliceMut
|
||||
let mut slices: SmallVec<[IoSliceMut; DEFAULT_DESCRIPTOR_VEC_SIZE]> =
|
||||
SmallVec::with_capacity(iovecs.len());
|
||||
@@ -669,15 +708,13 @@ where
|
||||
}
|
||||
|
||||
let result = {
|
||||
let mut file = self.file();
|
||||
|
||||
// Move the cursor to the right offset
|
||||
file.seek(SeekFrom::Start(offset as u64))
|
||||
self.seek(SeekFrom::Start(offset as u64))
|
||||
.map_err(AsyncIoError::ReadVectored)?;
|
||||
|
||||
let mut r = 0;
|
||||
for b in slices.iter_mut() {
|
||||
r += file.read(b).map_err(AsyncIoError::ReadVectored)?;
|
||||
r += self.read(b).map_err(AsyncIoError::ReadVectored)?;
|
||||
}
|
||||
r
|
||||
};
|
||||
@@ -695,7 +732,10 @@ where
|
||||
user_data: u64,
|
||||
eventfd: &EventFd,
|
||||
completion_list: &mut VecDeque<(u64, i32)>,
|
||||
) -> AsyncIoResult<()> {
|
||||
) -> AsyncIoResult<()>
|
||||
where
|
||||
Self: Write + Seek,
|
||||
{
|
||||
// Convert libc::iovec into IoSlice
|
||||
let mut slices: SmallVec<[IoSlice; DEFAULT_DESCRIPTOR_VEC_SIZE]> =
|
||||
SmallVec::with_capacity(iovecs.len());
|
||||
@@ -707,15 +747,13 @@ where
|
||||
}
|
||||
|
||||
let result = {
|
||||
let mut file = self.file();
|
||||
|
||||
// Move the cursor to the right offset
|
||||
file.seek(SeekFrom::Start(offset as u64))
|
||||
self.seek(SeekFrom::Start(offset as u64))
|
||||
.map_err(AsyncIoError::WriteVectored)?;
|
||||
|
||||
let mut r = 0;
|
||||
for b in slices.iter() {
|
||||
r += file.write(b).map_err(AsyncIoError::WriteVectored)?;
|
||||
r += self.write(b).map_err(AsyncIoError::WriteVectored)?;
|
||||
}
|
||||
r
|
||||
};
|
||||
@@ -731,12 +769,13 @@ where
|
||||
user_data: Option<u64>,
|
||||
eventfd: &EventFd,
|
||||
completion_list: &mut VecDeque<(u64, i32)>,
|
||||
) -> AsyncIoResult<()> {
|
||||
) -> AsyncIoResult<()>
|
||||
where
|
||||
Self: Write,
|
||||
{
|
||||
let result: i32 = {
|
||||
let mut file = self.file();
|
||||
|
||||
// Flush
|
||||
file.flush().map_err(AsyncIoError::Fsync)?;
|
||||
self.flush().map_err(AsyncIoError::Fsync)?;
|
||||
|
||||
0
|
||||
};
|
||||
@@ -748,8 +787,6 @@ where
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn file(&mut self) -> MutexGuard<'_, F>;
|
||||
}
|
||||
|
||||
pub enum ImageType {
|
||||
|
||||
@@ -24,11 +24,11 @@ use vmm_sys_util::file_traits::{FileSetLen, FileSync};
|
||||
use vmm_sys_util::seek_hole::SeekHole;
|
||||
use vmm_sys_util::write_zeroes::{PunchHole, WriteZeroesAt};
|
||||
|
||||
use crate::BlockBackend;
|
||||
use crate::qcow::qcow_raw_file::QcowRawFile;
|
||||
pub use crate::qcow::raw_file::RawFile;
|
||||
use crate::qcow::refcount::RefCount;
|
||||
use crate::qcow::vec_cache::{CacheMap, Cacheable, VecCache};
|
||||
use crate::BlockBackend;
|
||||
|
||||
/// Nesting depth limit for disk formats that can open other disk files.
|
||||
const MAX_NESTING_DEPTH: u32 = 10;
|
||||
@@ -287,11 +287,12 @@ impl QcowHeader {
|
||||
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 {
|
||||
if path.len() > max_length {
|
||||
return Err(Error::BackingFileTooLong(path.len() - max_length));
|
||||
}
|
||||
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;
|
||||
@@ -425,7 +426,7 @@ fn max_refcount_clusters(refcount_order: u32, cluster_size: u32, num_clusters: u
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct QcowFile {
|
||||
raw_file: QcowRawFile,
|
||||
header: QcowHeader,
|
||||
@@ -589,14 +590,12 @@ impl QcowFile {
|
||||
|
||||
// Check for compressed blocks
|
||||
for l2_addr_disk in l1_table.get_values() {
|
||||
if *l2_addr_disk != 0 {
|
||||
if let Err(e) = Self::read_l2_cluster(&mut raw_file, *l2_addr_disk) {
|
||||
if let Some(os_error) = e.raw_os_error() {
|
||||
if os_error == ENOTSUP {
|
||||
return Err(Error::CompressedBlocksNotSupported);
|
||||
}
|
||||
}
|
||||
}
|
||||
if *l2_addr_disk != 0
|
||||
&& let Err(e) = Self::read_l2_cluster(&mut raw_file, *l2_addr_disk)
|
||||
&& let Some(os_error) = e.raw_os_error()
|
||||
&& os_error == ENOTSUP
|
||||
{
|
||||
return Err(Error::CompressedBlocksNotSupported);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1071,21 +1070,7 @@ impl QcowFile {
|
||||
|
||||
let l2_index = self.l2_table_index(address) as usize;
|
||||
|
||||
if !self.l2_cache.contains_key(l1_index) {
|
||||
// Not in the cache.
|
||||
let table =
|
||||
VecCache::from_vec(Self::read_l2_cluster(&mut self.raw_file, l2_addr_disk)?);
|
||||
|
||||
let l1_table = &self.l1_table;
|
||||
let raw_file = &mut self.raw_file;
|
||||
self.l2_cache.insert(l1_index, table, |index, evicted| {
|
||||
raw_file.write_pointer_table(
|
||||
l1_table[index],
|
||||
evicted.get_values(),
|
||||
CLUSTER_USED_FLAG,
|
||||
)
|
||||
})?;
|
||||
};
|
||||
self.cache_l2_cluster(l1_index, l2_addr_disk, false)?;
|
||||
|
||||
let cluster_addr = self.l2_cache.get(l1_index).unwrap()[l2_index];
|
||||
if cluster_addr == 0 {
|
||||
@@ -1110,28 +1095,9 @@ impl QcowFile {
|
||||
|
||||
let mut set_refcounts = Vec::new();
|
||||
|
||||
if !self.l2_cache.contains_key(l1_index) {
|
||||
// Not in the cache.
|
||||
let l2_table = if l2_addr_disk == 0 {
|
||||
// Allocate a new cluster to store the L2 table and update the L1 table to point
|
||||
// to the new table.
|
||||
let new_addr: u64 = self.get_new_cluster(None)?;
|
||||
// The cluster refcount starts at one meaning it is used but doesn't need COW.
|
||||
set_refcounts.push((new_addr, 1));
|
||||
self.l1_table[l1_index] = new_addr;
|
||||
VecCache::new(self.l2_entries as usize)
|
||||
} else {
|
||||
VecCache::from_vec(Self::read_l2_cluster(&mut self.raw_file, l2_addr_disk)?)
|
||||
};
|
||||
let l1_table = &self.l1_table;
|
||||
let raw_file = &mut self.raw_file;
|
||||
self.l2_cache.insert(l1_index, l2_table, |index, evicted| {
|
||||
raw_file.write_pointer_table(
|
||||
l1_table[index],
|
||||
evicted.get_values(),
|
||||
CLUSTER_USED_FLAG,
|
||||
)
|
||||
})?;
|
||||
if let Some(new_addr) = self.cache_l2_cluster(l1_index, l2_addr_disk, true)? {
|
||||
// The cluster refcount starts at one meaning it is used but doesn't need COW.
|
||||
set_refcounts.push((new_addr, 1));
|
||||
}
|
||||
|
||||
let cluster_addr = match self.l2_cache.get(l1_index).unwrap()[l2_index] {
|
||||
@@ -1248,20 +1214,7 @@ impl QcowFile {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
if !self.l2_cache.contains_key(l1_index) {
|
||||
// Not in the cache.
|
||||
let table =
|
||||
VecCache::from_vec(Self::read_l2_cluster(&mut self.raw_file, l2_addr_disk)?);
|
||||
let l1_table = &self.l1_table;
|
||||
let raw_file = &mut self.raw_file;
|
||||
self.l2_cache.insert(l1_index, table, |index, evicted| {
|
||||
raw_file.write_pointer_table(
|
||||
l1_table[index],
|
||||
evicted.get_values(),
|
||||
CLUSTER_USED_FLAG,
|
||||
)
|
||||
})?;
|
||||
}
|
||||
self.cache_l2_cluster(l1_index, l2_addr_disk, false)?;
|
||||
|
||||
let cluster_addr = self.l2_cache.get(l1_index).unwrap()[l2_index];
|
||||
// If cluster_addr != 0, the cluster is allocated.
|
||||
@@ -1320,20 +1273,7 @@ impl QcowFile {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if !self.l2_cache.contains_key(l1_index) {
|
||||
// Not in the cache.
|
||||
let table =
|
||||
VecCache::from_vec(Self::read_l2_cluster(&mut self.raw_file, l2_addr_disk)?);
|
||||
let l1_table = &self.l1_table;
|
||||
let raw_file = &mut self.raw_file;
|
||||
self.l2_cache.insert(l1_index, table, |index, evicted| {
|
||||
raw_file.write_pointer_table(
|
||||
l1_table[index],
|
||||
evicted.get_values(),
|
||||
CLUSTER_USED_FLAG,
|
||||
)
|
||||
})?;
|
||||
}
|
||||
self.cache_l2_cluster(l1_index, l2_addr_disk, false)?;
|
||||
|
||||
let cluster_addr = self.l2_cache.get(l1_index).unwrap()[l2_index];
|
||||
if cluster_addr == 0 {
|
||||
@@ -1418,6 +1358,41 @@ impl QcowFile {
|
||||
.collect())
|
||||
}
|
||||
|
||||
// Put an L2 cluster to the cache with evicting less-used cluster
|
||||
// The new cluster may be allocated if necessary
|
||||
// (may_alloc argument is true and l2_addr_disk == 0)
|
||||
fn cache_l2_cluster(
|
||||
&mut self,
|
||||
l1_index: usize,
|
||||
l2_addr_disk: u64,
|
||||
may_alloc: bool,
|
||||
) -> std::io::Result<Option<u64>> {
|
||||
let mut new_cluster: Option<u64> = None;
|
||||
if !self.l2_cache.contains_key(l1_index) {
|
||||
// Not in the cache.
|
||||
let l2_table = if may_alloc && l2_addr_disk == 0 {
|
||||
// Allocate a new cluster to store the L2 table and update the L1 table to point
|
||||
// to the new table.
|
||||
let new_addr: u64 = self.get_new_cluster(None)?;
|
||||
new_cluster = Some(new_addr);
|
||||
self.l1_table[l1_index] = new_addr;
|
||||
VecCache::new(self.l2_entries as usize)
|
||||
} else {
|
||||
VecCache::from_vec(Self::read_l2_cluster(&mut self.raw_file, l2_addr_disk)?)
|
||||
};
|
||||
let l1_table = &self.l1_table;
|
||||
let raw_file = &mut self.raw_file;
|
||||
self.l2_cache.insert(l1_index, l2_table, |index, evicted| {
|
||||
raw_file.write_pointer_table(
|
||||
l1_table[index],
|
||||
evicted.get_values(),
|
||||
CLUSTER_USED_FLAG,
|
||||
)
|
||||
})?;
|
||||
}
|
||||
Ok(new_cluster)
|
||||
}
|
||||
|
||||
// Set the refcount for a cluster with the given address.
|
||||
// Returns a list of any refblocks that can be reused, this happens when a refblock is moved,
|
||||
// the old location can be reused.
|
||||
@@ -1584,11 +1559,11 @@ impl Seek for QcowFile {
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(o) = new_offset {
|
||||
if o <= self.virtual_size() {
|
||||
self.current_offset = o;
|
||||
return Ok(o);
|
||||
}
|
||||
if let Some(o) = new_offset
|
||||
&& o <= self.virtual_size()
|
||||
{
|
||||
self.current_offset = o;
|
||||
return Ok(o);
|
||||
}
|
||||
Err(std::io::Error::from_raw_os_error(EINVAL))
|
||||
}
|
||||
@@ -1705,12 +1680,12 @@ fn offset_is_cluster_boundary(offset: u64, cluster_bits: u32) -> Result<()> {
|
||||
|
||||
// Ceiling of the division of `dividend`/`divisor`.
|
||||
fn div_round_up_u64(dividend: u64, divisor: u64) -> u64 {
|
||||
dividend / divisor + u64::from(dividend % divisor != 0)
|
||||
dividend / divisor + u64::from(!dividend.is_multiple_of(divisor))
|
||||
}
|
||||
|
||||
// Ceiling of the division of `dividend`/`divisor`.
|
||||
fn div_round_up_u32(dividend: u32, divisor: u32) -> u32 {
|
||||
dividend / divisor + u32::from(dividend % divisor != 0)
|
||||
dividend / divisor + u32::from(!dividend.is_multiple_of(divisor))
|
||||
}
|
||||
|
||||
fn convert_copy<R, W>(reader: &mut R, writer: &mut W, offset: u64, size: u64) -> Result<()>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||
|
||||
use std::alloc::{alloc_zeroed, dealloc, Layout};
|
||||
use std::alloc::{Layout, alloc_zeroed, dealloc};
|
||||
use std::fs::{File, Metadata};
|
||||
use std::io::{self, Read, Seek, SeekFrom, Write};
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
@@ -89,9 +89,9 @@ impl RawFile {
|
||||
|
||||
let align64: u64 = self.alignment.try_into().unwrap();
|
||||
|
||||
(self.position % align64 == 0)
|
||||
&& ((buf.as_ptr() as usize) % self.alignment == 0)
|
||||
&& (buf.len() % self.alignment == 0)
|
||||
self.position.is_multiple_of(align64)
|
||||
&& (buf.as_ptr() as usize).is_multiple_of(self.alignment)
|
||||
&& buf.len().is_multiple_of(self.alignment)
|
||||
}
|
||||
|
||||
pub fn set_len(&self, size: u64) -> std::io::Result<()> {
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||
|
||||
use std::collections::hash_map::IterMut;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::hash_map::IterMut;
|
||||
use std::io;
|
||||
use std::ops::{Index, IndexMut};
|
||||
use std::slice::SliceIndex;
|
||||
@@ -123,10 +123,10 @@ impl<T: Cacheable> CacheMap<T> {
|
||||
if self.map.len() == self.capacity {
|
||||
// TODO(dgreid) - smarter eviction strategy.
|
||||
let to_evict = *self.map.iter().next().unwrap().0;
|
||||
if let Some(evicted) = self.map.remove(&to_evict) {
|
||||
if evicted.dirty() {
|
||||
write_callback(to_evict, evicted)?;
|
||||
}
|
||||
if let Some(evicted) = self.map.remove(&to_evict)
|
||||
&& evicted.dirty()
|
||||
{
|
||||
write_callback(to_evict, evicted)?;
|
||||
}
|
||||
}
|
||||
self.map.insert(index, block);
|
||||
|
||||
@@ -6,33 +6,32 @@ use std::collections::VecDeque;
|
||||
use std::fs::File;
|
||||
use std::io::{Seek, SeekFrom};
|
||||
use std::os::fd::AsRawFd;
|
||||
use std::sync::{Arc, Mutex, MutexGuard};
|
||||
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
use crate::AsyncAdaptor;
|
||||
use crate::async_io::{
|
||||
AsyncIo, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult,
|
||||
};
|
||||
use crate::qcow::{QcowFile, RawFile, Result as QcowResult};
|
||||
use crate::AsyncAdaptor;
|
||||
|
||||
pub struct QcowDiskSync {
|
||||
qcow_file: Arc<Mutex<QcowFile>>,
|
||||
qcow_file: QcowFile,
|
||||
}
|
||||
|
||||
impl QcowDiskSync {
|
||||
pub fn new(file: File, direct_io: bool) -> QcowResult<Self> {
|
||||
Ok(QcowDiskSync {
|
||||
qcow_file: Arc::new(Mutex::new(QcowFile::from(RawFile::new(file, direct_io))?)),
|
||||
qcow_file: QcowFile::from(RawFile::new(file, direct_io))?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl DiskFile for QcowDiskSync {
|
||||
fn size(&mut self) -> DiskFileResult<u64> {
|
||||
let mut file = self.qcow_file.lock().unwrap();
|
||||
|
||||
file.seek(SeekFrom::End(0)).map_err(DiskFileError::Size)
|
||||
self.qcow_file
|
||||
.seek(SeekFrom::End(0))
|
||||
.map_err(DiskFileError::Size)
|
||||
}
|
||||
|
||||
fn new_async_io(&self, _ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
|
||||
@@ -40,19 +39,18 @@ impl DiskFile for QcowDiskSync {
|
||||
}
|
||||
|
||||
fn fd(&mut self) -> BorrowedDiskFd<'_> {
|
||||
let lock = self.qcow_file.lock().unwrap();
|
||||
BorrowedDiskFd::new(lock.as_raw_fd())
|
||||
BorrowedDiskFd::new(self.qcow_file.as_raw_fd())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct QcowSync {
|
||||
qcow_file: Arc<Mutex<QcowFile>>,
|
||||
qcow_file: QcowFile,
|
||||
eventfd: EventFd,
|
||||
completion_list: VecDeque<(u64, i32)>,
|
||||
}
|
||||
|
||||
impl QcowSync {
|
||||
pub fn new(qcow_file: Arc<Mutex<QcowFile>>) -> Self {
|
||||
pub fn new(qcow_file: QcowFile) -> Self {
|
||||
QcowSync {
|
||||
qcow_file,
|
||||
eventfd: EventFd::new(libc::EFD_NONBLOCK)
|
||||
@@ -62,11 +60,7 @@ impl QcowSync {
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncAdaptor<QcowFile> for Arc<Mutex<QcowFile>> {
|
||||
fn file(&mut self) -> MutexGuard<'_, QcowFile> {
|
||||
self.lock().unwrap()
|
||||
}
|
||||
}
|
||||
impl AsyncAdaptor for QcowFile {}
|
||||
|
||||
impl AsyncIo for QcowSync {
|
||||
fn notifier(&self) -> &EventFd {
|
||||
|
||||
@@ -6,13 +6,13 @@ use std::fs::File;
|
||||
use std::io::{Error, Seek, SeekFrom};
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
|
||||
use io_uring::{opcode, types, IoUring};
|
||||
use io_uring::{IoUring, opcode, types};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
use crate::async_io::{
|
||||
AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult,
|
||||
};
|
||||
use crate::DiskTopology;
|
||||
use crate::{BatchRequest, DiskTopology, RequestType};
|
||||
|
||||
pub struct RawFileDisk {
|
||||
file: File,
|
||||
@@ -168,4 +168,77 @@ impl AsyncIo for RawFileAsync {
|
||||
.next()
|
||||
.map(|entry| (entry.user_data(), entry.result()))
|
||||
}
|
||||
|
||||
fn batch_requests_enabled(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn submit_batch_requests(&mut self, batch_request: &[BatchRequest]) -> AsyncIoResult<()> {
|
||||
if !self.batch_requests_enabled() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let (submitter, mut sq, _) = self.io_uring.split();
|
||||
let mut submitted = false;
|
||||
|
||||
for req in batch_request {
|
||||
match req.request_type {
|
||||
RequestType::In => {
|
||||
// SAFETY: we know the file descriptor is valid and we
|
||||
// relied on vm-memory to provide the buffer address.
|
||||
unsafe {
|
||||
sq.push(
|
||||
&opcode::Readv::new(
|
||||
types::Fd(self.fd),
|
||||
req.iovecs.as_ptr(),
|
||||
req.iovecs.len() as u32,
|
||||
)
|
||||
.offset(req.offset as u64)
|
||||
.build()
|
||||
.user_data(req.user_data),
|
||||
)
|
||||
.map_err(|_| {
|
||||
AsyncIoError::ReadVectored(Error::other("Submission queue is full"))
|
||||
})?
|
||||
};
|
||||
submitted = true;
|
||||
}
|
||||
RequestType::Out => {
|
||||
// SAFETY: we know the file descriptor is valid and we
|
||||
// relied on vm-memory to provide the buffer address.
|
||||
unsafe {
|
||||
sq.push(
|
||||
&opcode::Writev::new(
|
||||
types::Fd(self.fd),
|
||||
req.iovecs.as_ptr(),
|
||||
req.iovecs.len() as u32,
|
||||
)
|
||||
.offset(req.offset as u64)
|
||||
.build()
|
||||
.user_data(req.user_data),
|
||||
)
|
||||
.map_err(|_| {
|
||||
AsyncIoError::WriteVectored(Error::other("Submission queue is full"))
|
||||
})?
|
||||
};
|
||||
submitted = true;
|
||||
}
|
||||
_ => {
|
||||
unreachable!("Unexpected batch request type: {:?}", req.request_type)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Only submit if we actually queued something
|
||||
if submitted {
|
||||
// Update the submission queue and submit new operations to the
|
||||
// io_uring instance.
|
||||
sq.sync();
|
||||
submitter
|
||||
.submit()
|
||||
.map_err(AsyncIoError::SubmitBatchRequests)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,10 +12,10 @@ use std::os::unix::io::{AsRawFd, RawFd};
|
||||
use vmm_sys_util::aio;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
use crate::DiskTopology;
|
||||
use crate::async_io::{
|
||||
AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult,
|
||||
};
|
||||
use crate::DiskTopology;
|
||||
|
||||
pub struct RawFileDiskAio {
|
||||
file: File,
|
||||
|
||||
@@ -9,10 +9,10 @@ use std::os::unix::io::{AsRawFd, RawFd};
|
||||
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
use crate::DiskTopology;
|
||||
use crate::async_io::{
|
||||
AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult,
|
||||
};
|
||||
use crate::DiskTopology;
|
||||
|
||||
pub struct RawFileDiskSync {
|
||||
file: File,
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
use std::fs::File;
|
||||
use std::io::{Seek, SeekFrom};
|
||||
|
||||
use crate::{read_aligned_block_size, DiskTopology};
|
||||
use crate::{DiskTopology, read_aligned_block_size};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct VhdFooter {
|
||||
@@ -123,7 +123,7 @@ mod tests {
|
||||
|
||||
use vmm_sys_util::tempfile::TempFile;
|
||||
|
||||
use super::{is_fixed_vhd, VhdFooter};
|
||||
use super::{VhdFooter, is_fixed_vhd};
|
||||
|
||||
fn valid_fixed_vhd_footer() -> Vec<u8> {
|
||||
vec![
|
||||
|
||||
@@ -12,11 +12,11 @@ use remain::sorted;
|
||||
use thiserror::Error;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::BlockBackend;
|
||||
use crate::vhdx::vhdx_bat::{BatEntry, VhdxBatError};
|
||||
use crate::vhdx::vhdx_header::{RegionInfo, RegionTableEntry, VhdxHeader, VhdxHeaderError};
|
||||
use crate::vhdx::vhdx_io::VhdxIoError;
|
||||
use crate::vhdx::vhdx_metadata::{DiskSpec, VhdxMetadataError};
|
||||
use crate::BlockBackend;
|
||||
|
||||
mod vhdx_bat;
|
||||
mod vhdx_header;
|
||||
@@ -187,11 +187,11 @@ impl Seek for Vhdx {
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(o) = new_offset {
|
||||
if o <= self.virtual_disk_size() {
|
||||
self.current_offset = o;
|
||||
return Ok(o);
|
||||
}
|
||||
if let Some(o) = new_offset
|
||||
&& o <= self.virtual_disk_size()
|
||||
{
|
||||
self.current_offset = o;
|
||||
return Ok(o);
|
||||
}
|
||||
|
||||
Err(std::io::Error::new(
|
||||
|
||||
@@ -35,9 +35,7 @@ pub enum VhdxIoError {
|
||||
pub type Result<T> = std::result::Result<T, VhdxIoError>;
|
||||
|
||||
macro_rules! align {
|
||||
($n:expr, $align:expr) => {{
|
||||
$n.div_ceil($align) * $align
|
||||
}};
|
||||
($n:expr, $align:expr) => {{ $n.div_ceil($align) * $align }};
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
|
||||
@@ -5,31 +5,30 @@
|
||||
use std::collections::VecDeque;
|
||||
use std::fs::File;
|
||||
use std::os::fd::AsRawFd;
|
||||
use std::sync::{Arc, Mutex, MutexGuard};
|
||||
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
use crate::AsyncAdaptor;
|
||||
use crate::async_io::{
|
||||
AsyncIo, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult,
|
||||
};
|
||||
use crate::vhdx::{Result as VhdxResult, Vhdx};
|
||||
use crate::AsyncAdaptor;
|
||||
|
||||
pub struct VhdxDiskSync {
|
||||
vhdx_file: Arc<Mutex<Vhdx>>,
|
||||
vhdx_file: Vhdx,
|
||||
}
|
||||
|
||||
impl VhdxDiskSync {
|
||||
pub fn new(f: File) -> VhdxResult<Self> {
|
||||
Ok(VhdxDiskSync {
|
||||
vhdx_file: Arc::new(Mutex::new(Vhdx::new(f)?)),
|
||||
vhdx_file: Vhdx::new(f)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl DiskFile for VhdxDiskSync {
|
||||
fn size(&mut self) -> DiskFileResult<u64> {
|
||||
Ok(self.vhdx_file.lock().unwrap().virtual_disk_size())
|
||||
Ok(self.vhdx_file.virtual_disk_size())
|
||||
}
|
||||
|
||||
fn new_async_io(&self, _ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
|
||||
@@ -40,19 +39,18 @@ impl DiskFile for VhdxDiskSync {
|
||||
}
|
||||
|
||||
fn fd(&mut self) -> BorrowedDiskFd<'_> {
|
||||
let lock = self.vhdx_file.lock().unwrap();
|
||||
BorrowedDiskFd::new(lock.as_raw_fd())
|
||||
BorrowedDiskFd::new(self.vhdx_file.as_raw_fd())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct VhdxSync {
|
||||
vhdx_file: Arc<Mutex<Vhdx>>,
|
||||
vhdx_file: Vhdx,
|
||||
eventfd: EventFd,
|
||||
completion_list: VecDeque<(u64, i32)>,
|
||||
}
|
||||
|
||||
impl VhdxSync {
|
||||
pub fn new(vhdx_file: Arc<Mutex<Vhdx>>) -> std::io::Result<Self> {
|
||||
pub fn new(vhdx_file: Vhdx) -> std::io::Result<Self> {
|
||||
Ok(VhdxSync {
|
||||
vhdx_file,
|
||||
eventfd: EventFd::new(libc::EFD_NONBLOCK)?,
|
||||
@@ -61,11 +59,7 @@ impl VhdxSync {
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncAdaptor<Vhdx> for Arc<Mutex<Vhdx>> {
|
||||
fn file(&mut self) -> MutexGuard<'_, Vhdx> {
|
||||
self.lock().unwrap()
|
||||
}
|
||||
}
|
||||
impl AsyncAdaptor for Vhdx {}
|
||||
|
||||
impl AsyncIo for VhdxSync {
|
||||
fn notifier(&self) -> &EventFd {
|
||||
|
||||
15
build.rs
15
build.rs
@@ -9,14 +9,13 @@ use std::process::Command;
|
||||
fn main() {
|
||||
let mut version = "v".to_owned() + env!("CARGO_PKG_VERSION");
|
||||
|
||||
if let Ok(git_out) = Command::new("git").args(["describe", "--dirty"]).output() {
|
||||
if git_out.status.success() {
|
||||
if let Ok(git_out_str) = String::from_utf8(git_out.stdout) {
|
||||
version = git_out_str;
|
||||
// Pop the trailing newline.
|
||||
version.pop();
|
||||
}
|
||||
}
|
||||
if let Ok(git_out) = Command::new("git").args(["describe", "--dirty"]).output()
|
||||
&& git_out.status.success()
|
||||
&& let Ok(git_out_str) = String::from_utf8(git_out.stdout)
|
||||
{
|
||||
version = git_out_str;
|
||||
// Pop the trailing newline.
|
||||
version.pop();
|
||||
}
|
||||
|
||||
// Append CH_EXTRA_VERSION to version if it is set.
|
||||
|
||||
@@ -1,22 +1,28 @@
|
||||
[package]
|
||||
authors = ["The Chromium OS Authors"]
|
||||
edition = "2021"
|
||||
edition.workspace = true
|
||||
name = "devices"
|
||||
version = "0.1.0"
|
||||
|
||||
[dependencies]
|
||||
acpi_tables = { workspace = true }
|
||||
anyhow = "1.0.94"
|
||||
anyhow = { workspace = true }
|
||||
arch = { path = "../arch" }
|
||||
bitflags = "2.9.0"
|
||||
byteorder = "1.5.0"
|
||||
bitfield-struct = { version = "0.12.0", optional = true }
|
||||
bitflags = { workspace = true }
|
||||
byteorder = { workspace = true }
|
||||
event_monitor = { path = "../event_monitor" }
|
||||
hypervisor = { path = "../hypervisor" }
|
||||
libc = "0.2.167"
|
||||
log = "0.4.22"
|
||||
num_enum = "0.7.2"
|
||||
libc = { workspace = true }
|
||||
linux-loader = { workspace = true, features = [
|
||||
"bzimage",
|
||||
"elf",
|
||||
"pe",
|
||||
], optional = true }
|
||||
log = { workspace = true }
|
||||
num_enum = "0.7.4"
|
||||
pci = { path = "../pci" }
|
||||
serde = { version = "1.0.208", features = ["derive"] }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
thiserror = { workspace = true }
|
||||
tpm = { path = "../tpm" }
|
||||
vm-allocator = { path = "../vm-allocator" }
|
||||
@@ -28,11 +34,17 @@ vm-memory = { workspace = true, features = [
|
||||
] }
|
||||
vm-migration = { path = "../vm-migration" }
|
||||
vmm-sys-util = { workspace = true }
|
||||
zerocopy = { version = "0.8.27", features = [
|
||||
"alloc",
|
||||
"derive",
|
||||
], optional = true }
|
||||
|
||||
[target.'cfg(any(target_arch = "aarch64", target_arch = "riscv64"))'.dependencies]
|
||||
arch = { path = "../arch" }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
fw_cfg = ["arch/fw_cfg", "bitfield-struct", "linux-loader", "zerocopy"]
|
||||
ivshmem = []
|
||||
kvm = ["arch/kvm"]
|
||||
pvmemcontrol = []
|
||||
|
||||
@@ -8,9 +8,9 @@ use std::sync::{Arc, Barrier};
|
||||
use std::thread;
|
||||
use std::time::Instant;
|
||||
|
||||
use acpi_tables::{aml, Aml, AmlSink};
|
||||
use vm_device::interrupt::InterruptSourceGroup;
|
||||
use acpi_tables::{Aml, AmlSink, aml};
|
||||
use vm_device::BusDevice;
|
||||
use vm_device::interrupt::InterruptSourceGroup;
|
||||
use vm_memory::GuestAddress;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
@@ -51,7 +51,7 @@ impl BusDevice for AcpiShutdownDevice {
|
||||
if data[0] == 1 {
|
||||
info!("ACPI Reboot signalled");
|
||||
if let Err(e) = self.reset_evt.write(1) {
|
||||
error!("Error triggering ACPI reset event: {}", e);
|
||||
error!("Error triggering ACPI reset event: {e}");
|
||||
}
|
||||
// Spin until we are sure the reset_evt has been handled and that when
|
||||
// we return from the KVM_RUN we will exit rather than re-enter the guest.
|
||||
@@ -68,7 +68,7 @@ impl BusDevice for AcpiShutdownDevice {
|
||||
if data[0] == (S5_SLEEP_VALUE << SLEEP_VALUE_BIT) | (1 << SLEEP_STATUS_EN_BIT) {
|
||||
info!("ACPI Shutdown signalled");
|
||||
if let Err(e) = self.exit_evt.write(1) {
|
||||
error!("Error triggering ACPI shutdown event: {}", e);
|
||||
error!("Error triggering ACPI shutdown event: {e}");
|
||||
}
|
||||
// Spin until we are sure the reset_evt has been handled and that when
|
||||
// we return from the KVM_RUN we will exit rather than re-enter the guest.
|
||||
|
||||
@@ -40,7 +40,7 @@ pub struct Aia {
|
||||
|
||||
impl Aia {
|
||||
pub fn new(
|
||||
vcpu_count: u8,
|
||||
vcpu_count: u32,
|
||||
interrupt_manager: Arc<dyn InterruptManager<GroupConfig = MsiIrqGroupConfig>>,
|
||||
vm: Arc<dyn hypervisor::Vm>,
|
||||
) -> Result<Aia> {
|
||||
|
||||
@@ -9,8 +9,8 @@ use std::sync::{Arc, Mutex};
|
||||
|
||||
use anyhow::anyhow;
|
||||
use arch::layout;
|
||||
use hypervisor::arch::aarch64::gic::{GicState, Vgic, VgicConfig};
|
||||
use hypervisor::CpuState;
|
||||
use hypervisor::arch::aarch64::gic::{GicState, Vgic, VgicConfig};
|
||||
use vm_device::interrupt::{
|
||||
InterruptIndex, InterruptManager, InterruptSourceConfig, InterruptSourceGroup,
|
||||
LegacyIrqSourceConfig, MsiIrqGroupConfig,
|
||||
@@ -39,7 +39,7 @@ pub struct Gic {
|
||||
|
||||
impl Gic {
|
||||
pub fn new(
|
||||
vcpu_count: u8,
|
||||
vcpu_count: u32,
|
||||
interrupt_manager: Arc<dyn InterruptManager<GroupConfig = MsiIrqGroupConfig>>,
|
||||
vm: Arc<dyn hypervisor::Vm>,
|
||||
) -> Result<Gic> {
|
||||
|
||||
@@ -14,11 +14,11 @@ use std::sync::{Arc, Barrier};
|
||||
|
||||
use byteorder::{ByteOrder, LittleEndian};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use vm_device::BusDevice;
|
||||
use vm_device::interrupt::{
|
||||
InterruptIndex, InterruptManager, InterruptSourceConfig, InterruptSourceGroup,
|
||||
MsiIrqGroupConfig, MsiIrqSourceConfig,
|
||||
};
|
||||
use vm_device::BusDevice;
|
||||
use vm_memory::GuestAddress;
|
||||
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
@@ -151,13 +151,13 @@ impl BusDevice for Ioapic {
|
||||
return;
|
||||
}
|
||||
|
||||
debug!("IOAPIC_R @ offset 0x{:x}", offset);
|
||||
debug!("IOAPIC_R @ offset 0x{offset:x}");
|
||||
|
||||
let value: u32 = match offset as u8 {
|
||||
IOREGSEL_OFF => self.reg_sel,
|
||||
IOWIN_OFF => self.ioapic_read(),
|
||||
_ => {
|
||||
error!("IOAPIC: failed reading at offset {}", offset);
|
||||
error!("IOAPIC: failed reading at offset {offset}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
@@ -171,7 +171,7 @@ impl BusDevice for Ioapic {
|
||||
return None;
|
||||
}
|
||||
|
||||
debug!("IOAPIC_W @ offset 0x{:x}", offset);
|
||||
debug!("IOAPIC_W @ offset 0x{offset:x}");
|
||||
|
||||
let value = LittleEndian::read_u32(data);
|
||||
|
||||
@@ -179,7 +179,7 @@ impl BusDevice for Ioapic {
|
||||
IOREGSEL_OFF => self.reg_sel = value,
|
||||
IOWIN_OFF => self.ioapic_write(value),
|
||||
_ => {
|
||||
error!("IOAPIC: failed writing at offset {}", offset);
|
||||
error!("IOAPIC: failed writing at offset {offset}");
|
||||
}
|
||||
}
|
||||
None
|
||||
@@ -266,7 +266,7 @@ impl Ioapic {
|
||||
IOWIN_OFF..=REG_MAX_OFFSET => {
|
||||
let (index, is_high_bits) = decode_irq_from_selector(self.reg_sel as u8);
|
||||
if index > NUM_IOAPIC_PINS {
|
||||
warn!("IOAPIC index out of range: {}", index);
|
||||
warn!("IOAPIC index out of range: {index}");
|
||||
return;
|
||||
}
|
||||
if is_high_bits {
|
||||
@@ -282,7 +282,7 @@ impl Ioapic {
|
||||
// The entry must be updated through the interrupt source
|
||||
// group.
|
||||
if let Err(e) = self.update_entry(index, true) {
|
||||
error!("Failed updating IOAPIC entry: {:?}", e);
|
||||
error!("Failed updating IOAPIC entry: {e:?}");
|
||||
}
|
||||
// Store the information this IRQ is now being used.
|
||||
self.used_entries[index] = true;
|
||||
@@ -303,7 +303,7 @@ impl Ioapic {
|
||||
IOWIN_OFF..=REG_MAX_OFFSET => {
|
||||
let (index, is_high_bits) = decode_irq_from_selector(self.reg_sel as u8);
|
||||
if index > NUM_IOAPIC_PINS {
|
||||
warn!("IOAPIC index out of range: {}", index);
|
||||
warn!("IOAPIC index out of range: {index}");
|
||||
return 0;
|
||||
}
|
||||
if is_high_bits {
|
||||
|
||||
420
devices/src/ivshmem.rs
Normal file
420
devices/src/ivshmem.rs
Normal file
@@ -0,0 +1,420 @@
|
||||
// Copyright © 2024 Tencent Corporation. All rights reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
use std::any::Any;
|
||||
use std::path::PathBuf;
|
||||
use std::result;
|
||||
use std::sync::atomic::{AtomicU32, Ordering};
|
||||
use std::sync::{Arc, Barrier, Mutex};
|
||||
|
||||
use anyhow::anyhow;
|
||||
use byteorder::{ByteOrder, LittleEndian};
|
||||
use pci::{
|
||||
BarReprogrammingParams, PCI_CONFIGURATION_ID, PciBarConfiguration, PciBarPrefetchable,
|
||||
PciBarRegionType, PciClassCode, PciConfiguration, PciDevice, PciDeviceError, PciHeaderType,
|
||||
PciSubclass,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
use vm_allocator::{AddressAllocator, SystemAllocator};
|
||||
use vm_device::{BusDevice, Resource, UserspaceMapping};
|
||||
use vm_memory::bitmap::AtomicBitmap;
|
||||
use vm_memory::{Address, GuestAddress};
|
||||
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
|
||||
|
||||
const IVSHMEM_BAR0_IDX: usize = 0;
|
||||
const IVSHMEM_BAR1_IDX: usize = 1;
|
||||
const IVSHMEM_BAR2_IDX: usize = 2;
|
||||
|
||||
const IVSHMEM_VENDOR_ID: u16 = 0x1af4;
|
||||
const IVSHMEM_DEVICE_ID: u16 = 0x1110;
|
||||
|
||||
const IVSHMEM_REG_BAR_SIZE: u64 = 0x100;
|
||||
|
||||
type GuestRegionMmap = vm_memory::GuestRegionMmap<AtomicBitmap>;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum IvshmemError {
|
||||
#[error("Failed to retrieve PciConfigurationState: {0}")]
|
||||
RetrievePciConfigurationState(#[source] anyhow::Error),
|
||||
#[error("Failed to retrieve IvshmemDeviceState: {0}")]
|
||||
RetrieveIvshmemDeviceStateState(#[source] anyhow::Error),
|
||||
#[error("Failed to remove user memory region")]
|
||||
RemoveUserMemoryRegion,
|
||||
#[error("Failed to create user memory region.")]
|
||||
CreateUserMemoryRegion,
|
||||
#[error("Failed to create userspace mapping.")]
|
||||
CreateUserspaceMapping,
|
||||
#[error("Failed to remove old userspace mapping.")]
|
||||
RemoveUserspaceMapping,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum IvshmemSubclass {
|
||||
Other = 0x00,
|
||||
}
|
||||
|
||||
impl PciSubclass for IvshmemSubclass {
|
||||
fn get_register_value(&self) -> u8 {
|
||||
*self as u8
|
||||
}
|
||||
}
|
||||
|
||||
pub trait IvshmemOps: Send + Sync {
|
||||
fn map_ram_region(
|
||||
&mut self,
|
||||
start_addr: u64,
|
||||
size: usize,
|
||||
backing_file: Option<PathBuf>,
|
||||
) -> Result<(Arc<GuestRegionMmap>, UserspaceMapping), IvshmemError>;
|
||||
|
||||
fn unmap_ram_region(&mut self, mapping: UserspaceMapping) -> Result<(), IvshmemError>;
|
||||
}
|
||||
|
||||
/// Inner-Vm Shared Memory Device (Ivshmem device)
|
||||
///
|
||||
/// This device can share memory between host and guest(ivshmem-plain)
|
||||
/// and share memory between guests(ivshmem-doorbell).
|
||||
/// But only ivshmem-plain support now, ivshmem-doorbell doesn't support yet.
|
||||
pub struct IvshmemDevice {
|
||||
id: String,
|
||||
|
||||
// ivshmem device registers
|
||||
// (only used for ivshmem-doorbell, ivshmem-doorbell don't support yet)
|
||||
_interrupt_mask: u32,
|
||||
_interrupt_status: Arc<AtomicU32>,
|
||||
_iv_position: u32,
|
||||
_doorbell: u32,
|
||||
|
||||
// PCI configuration registers.
|
||||
configuration: PciConfiguration,
|
||||
bar_regions: Vec<PciBarConfiguration>,
|
||||
|
||||
region_size: u64,
|
||||
ivshmem_ops: Arc<Mutex<dyn IvshmemOps>>,
|
||||
backend_file: Option<PathBuf>,
|
||||
region: Option<Arc<GuestRegionMmap>>,
|
||||
userspace_mapping: Option<UserspaceMapping>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Default, Clone)]
|
||||
pub struct IvshmemDeviceState {
|
||||
interrupt_mask: u32,
|
||||
interrupt_status: u32,
|
||||
iv_position: u32,
|
||||
doorbell: u32,
|
||||
}
|
||||
|
||||
impl IvshmemDevice {
|
||||
pub fn new(
|
||||
id: String,
|
||||
region_size: u64,
|
||||
backend_file: Option<PathBuf>,
|
||||
ivshmem_ops: Arc<Mutex<dyn IvshmemOps>>,
|
||||
snapshot: Option<Snapshot>,
|
||||
) -> Result<Self, IvshmemError> {
|
||||
let pci_configuration_state =
|
||||
vm_migration::state_from_id(snapshot.as_ref(), PCI_CONFIGURATION_ID).map_err(|e| {
|
||||
IvshmemError::RetrievePciConfigurationState(anyhow!(
|
||||
"Failed to get PciConfigurationState from Snapshot: {e}",
|
||||
))
|
||||
})?;
|
||||
|
||||
let state: Option<IvshmemDeviceState> = snapshot
|
||||
.as_ref()
|
||||
.map(|s| s.to_state())
|
||||
.transpose()
|
||||
.map_err(|e| {
|
||||
IvshmemError::RetrieveIvshmemDeviceStateState(anyhow!(
|
||||
"Failed to get IvshmemDeviceState from Snapshot: {e}",
|
||||
))
|
||||
})?;
|
||||
|
||||
let configuration = PciConfiguration::new(
|
||||
IVSHMEM_VENDOR_ID,
|
||||
IVSHMEM_DEVICE_ID,
|
||||
0x1,
|
||||
PciClassCode::MemoryController,
|
||||
&IvshmemSubclass::Other,
|
||||
None,
|
||||
PciHeaderType::Device,
|
||||
0,
|
||||
0,
|
||||
None,
|
||||
pci_configuration_state,
|
||||
);
|
||||
|
||||
let device = if let Some(s) = state {
|
||||
IvshmemDevice {
|
||||
id,
|
||||
configuration,
|
||||
bar_regions: vec![],
|
||||
_interrupt_mask: s.interrupt_mask,
|
||||
_interrupt_status: Arc::new(AtomicU32::new(s.interrupt_status)),
|
||||
_iv_position: s.iv_position,
|
||||
_doorbell: s.doorbell,
|
||||
region_size,
|
||||
ivshmem_ops,
|
||||
region: None,
|
||||
userspace_mapping: None,
|
||||
backend_file,
|
||||
}
|
||||
} else {
|
||||
IvshmemDevice {
|
||||
id,
|
||||
configuration,
|
||||
bar_regions: vec![],
|
||||
_interrupt_mask: 0,
|
||||
_interrupt_status: Arc::new(AtomicU32::new(0)),
|
||||
_iv_position: 0,
|
||||
_doorbell: 0,
|
||||
region_size,
|
||||
ivshmem_ops,
|
||||
region: None,
|
||||
userspace_mapping: None,
|
||||
backend_file,
|
||||
}
|
||||
};
|
||||
Ok(device)
|
||||
}
|
||||
|
||||
pub fn set_region(
|
||||
&mut self,
|
||||
region: Arc<GuestRegionMmap>,
|
||||
userspace_mapping: UserspaceMapping,
|
||||
) {
|
||||
self.region = Some(region);
|
||||
self.userspace_mapping = Some(userspace_mapping);
|
||||
}
|
||||
|
||||
pub fn config_bar_addr(&self) -> u64 {
|
||||
self.configuration.get_bar_addr(IVSHMEM_BAR0_IDX)
|
||||
}
|
||||
|
||||
pub fn data_bar_addr(&self) -> u64 {
|
||||
self.configuration.get_bar_addr(IVSHMEM_BAR2_IDX)
|
||||
}
|
||||
|
||||
fn state(&self) -> IvshmemDeviceState {
|
||||
IvshmemDeviceState {
|
||||
interrupt_mask: self._interrupt_mask,
|
||||
interrupt_status: self._interrupt_status.load(Ordering::SeqCst),
|
||||
iv_position: self._iv_position,
|
||||
doorbell: self._doorbell,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BusDevice for IvshmemDevice {
|
||||
fn read(&mut self, base: u64, offset: u64, data: &mut [u8]) {
|
||||
self.read_bar(base, offset, data)
|
||||
}
|
||||
|
||||
fn write(&mut self, base: u64, offset: u64, data: &[u8]) -> Option<Arc<Barrier>> {
|
||||
self.write_bar(base, offset, data)
|
||||
}
|
||||
}
|
||||
|
||||
impl PciDevice for IvshmemDevice {
|
||||
fn allocate_bars(
|
||||
&mut self,
|
||||
_allocator: &Arc<Mutex<SystemAllocator>>,
|
||||
mmio32_allocator: &mut AddressAllocator,
|
||||
mmio64_allocator: &mut AddressAllocator,
|
||||
resources: Option<Vec<Resource>>,
|
||||
) -> std::result::Result<Vec<PciBarConfiguration>, PciDeviceError> {
|
||||
let mut bars = Vec::new();
|
||||
let mut bar0_addr = None;
|
||||
let mut bar2_addr = None;
|
||||
|
||||
let restoring = resources.is_some();
|
||||
if let Some(resources) = resources {
|
||||
for resource in resources {
|
||||
match resource {
|
||||
Resource::PciBar { index, base, .. } => {
|
||||
match index {
|
||||
IVSHMEM_BAR0_IDX => {
|
||||
bar0_addr = Some(GuestAddress(base));
|
||||
}
|
||||
IVSHMEM_BAR1_IDX => {}
|
||||
IVSHMEM_BAR2_IDX => {
|
||||
bar2_addr = Some(GuestAddress(base));
|
||||
}
|
||||
_ => {
|
||||
error!("Unexpected pci bar index {index}");
|
||||
}
|
||||
};
|
||||
}
|
||||
_ => {
|
||||
error!("Unexpected resource {resource:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
if bar0_addr.is_none() || bar2_addr.is_none() {
|
||||
return Err(PciDeviceError::MissingResource);
|
||||
}
|
||||
}
|
||||
|
||||
// BAR0 holds device registers (256 Byte MMIO)
|
||||
let bar0_addr = mmio32_allocator
|
||||
.allocate(bar0_addr, IVSHMEM_REG_BAR_SIZE, None)
|
||||
.ok_or(PciDeviceError::IoAllocationFailed(IVSHMEM_REG_BAR_SIZE))?;
|
||||
debug!("ivshmem bar0 address 0x{:x}", bar0_addr.0);
|
||||
|
||||
let bar0 = PciBarConfiguration::default()
|
||||
.set_index(IVSHMEM_BAR0_IDX)
|
||||
.set_address(bar0_addr.raw_value())
|
||||
.set_size(IVSHMEM_REG_BAR_SIZE)
|
||||
.set_region_type(PciBarRegionType::Memory32BitRegion)
|
||||
.set_prefetchable(PciBarPrefetchable::NotPrefetchable);
|
||||
|
||||
// BAR1 holds MSI-X table and PBA (only ivshmem-doorbell).
|
||||
|
||||
// BAR2 maps the shared memory object
|
||||
let bar2_size = self.region_size;
|
||||
let bar2_addr = mmio64_allocator
|
||||
.allocate(bar2_addr, bar2_size, None)
|
||||
.ok_or(PciDeviceError::IoAllocationFailed(bar2_size))?;
|
||||
debug!("ivshmem bar2 address 0x{:x}", bar2_addr.0);
|
||||
|
||||
let bar2 = PciBarConfiguration::default()
|
||||
.set_index(IVSHMEM_BAR2_IDX)
|
||||
.set_address(bar2_addr.raw_value())
|
||||
.set_size(bar2_size)
|
||||
.set_region_type(PciBarRegionType::Memory64BitRegion)
|
||||
.set_prefetchable(PciBarPrefetchable::Prefetchable);
|
||||
|
||||
if !restoring {
|
||||
self.configuration
|
||||
.add_pci_bar(&bar0)
|
||||
.map_err(|e| PciDeviceError::IoRegistrationFailed(bar0_addr.raw_value(), e))?;
|
||||
self.configuration
|
||||
.add_pci_bar(&bar2)
|
||||
.map_err(|e| PciDeviceError::IoRegistrationFailed(bar2_addr.raw_value(), e))?;
|
||||
}
|
||||
|
||||
bars.push(bar0);
|
||||
bars.push(bar2);
|
||||
self.bar_regions = bars.clone();
|
||||
|
||||
Ok(bars)
|
||||
}
|
||||
|
||||
fn free_bars(
|
||||
&mut self,
|
||||
_allocator: &mut SystemAllocator,
|
||||
_mmio32_allocator: &mut AddressAllocator,
|
||||
_mmio64_allocator: &mut AddressAllocator,
|
||||
) -> std::result::Result<(), PciDeviceError> {
|
||||
unimplemented!("Device hotplug and remove are not supported for ivshmem");
|
||||
}
|
||||
|
||||
fn write_config_register(
|
||||
&mut self,
|
||||
reg_idx: usize,
|
||||
offset: u64,
|
||||
data: &[u8],
|
||||
) -> (Vec<BarReprogrammingParams>, Option<Arc<Barrier>>) {
|
||||
(
|
||||
self.configuration
|
||||
.write_config_register(reg_idx, offset, data),
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
fn read_config_register(&mut self, reg_idx: usize) -> u32 {
|
||||
self.configuration.read_reg(reg_idx)
|
||||
}
|
||||
|
||||
fn read_bar(&mut self, base: u64, offset: u64, data: &mut [u8]) {
|
||||
debug!("read base {base:x} offset {offset}");
|
||||
|
||||
let mut bar_idx = 0;
|
||||
for (idx, bar) in self.bar_regions.iter().enumerate() {
|
||||
if bar.addr() == base {
|
||||
bar_idx = idx;
|
||||
}
|
||||
}
|
||||
match bar_idx {
|
||||
// bar 0
|
||||
0 => {
|
||||
// ivshmem don't use interrupt, we return zero now.
|
||||
LittleEndian::write_u32(data, 0);
|
||||
}
|
||||
// bar 2
|
||||
1 => warn!("Unexpected read ivshmem memory idx: {offset}"),
|
||||
_ => {
|
||||
warn!("Invalid bar_idx: {bar_idx}");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn write_bar(&mut self, base: u64, offset: u64, _data: &[u8]) -> Option<Arc<Barrier>> {
|
||||
debug!("write base {base:x} offset {offset}");
|
||||
warn!("Unexpected write ivshmem memory idx: {offset}");
|
||||
None
|
||||
}
|
||||
|
||||
fn move_bar(&mut self, old_base: u64, new_base: u64) -> result::Result<(), std::io::Error> {
|
||||
if new_base == self.data_bar_addr() {
|
||||
if let Some(old_mapping) = self.userspace_mapping.take() {
|
||||
self.ivshmem_ops
|
||||
.lock()
|
||||
.unwrap()
|
||||
.unmap_ram_region(old_mapping)
|
||||
.map_err(std::io::Error::other)?;
|
||||
}
|
||||
let (region, new_mapping) = self
|
||||
.ivshmem_ops
|
||||
.lock()
|
||||
.unwrap()
|
||||
.map_ram_region(
|
||||
new_base,
|
||||
self.region_size as usize,
|
||||
self.backend_file.clone(),
|
||||
)
|
||||
.map_err(std::io::Error::other)?;
|
||||
self.set_region(region, new_mapping);
|
||||
}
|
||||
for bar in self.bar_regions.iter_mut() {
|
||||
if bar.addr() == old_base {
|
||||
*bar = bar.set_address(new_base);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn id(&self) -> Option<String> {
|
||||
Some(self.id.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl Pausable for IvshmemDevice {}
|
||||
|
||||
impl Snapshottable for IvshmemDevice {
|
||||
fn id(&self) -> String {
|
||||
self.id.clone()
|
||||
}
|
||||
|
||||
// The snapshot/restore (also live migration) support only work for ivshmem-plain mode.
|
||||
// Additional work is needed for supporting ivshmem-doorbell.
|
||||
fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
|
||||
let mut snapshot = Snapshot::new_from_state(&self.state())?;
|
||||
|
||||
// Snapshot PciConfiguration
|
||||
snapshot.add_snapshot(self.configuration.id(), self.configuration.snapshot()?);
|
||||
|
||||
Ok(snapshot)
|
||||
}
|
||||
}
|
||||
|
||||
impl Transportable for IvshmemDevice {}
|
||||
|
||||
impl Migratable for IvshmemDevice {}
|
||||
@@ -12,7 +12,7 @@ use std::{mem, thread};
|
||||
// https://github.com/rust-lang/libc/issues/1848
|
||||
#[cfg_attr(target_env = "musl", allow(deprecated))]
|
||||
use libc::time_t;
|
||||
use libc::{clock_gettime, gmtime_r, timespec, tm, CLOCK_REALTIME};
|
||||
use libc::{CLOCK_REALTIME, clock_gettime, gmtime_r, timespec, tm};
|
||||
use vm_device::BusDevice;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
@@ -90,7 +90,7 @@ impl BusDevice for Cmos {
|
||||
self.data[(self.index & INDEX_MASK) as usize] = data[0]
|
||||
}
|
||||
}
|
||||
o => warn!("bad write offset on CMOS device: {}", o),
|
||||
o => warn!("bad write offset on CMOS device: {o}"),
|
||||
};
|
||||
None
|
||||
}
|
||||
@@ -164,7 +164,7 @@ impl BusDevice for Cmos {
|
||||
}
|
||||
}
|
||||
o => {
|
||||
warn!("bad read offset on CMOS device: {}", o);
|
||||
warn!("bad read offset on CMOS device: {o}");
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
954
devices/src/legacy/fw_cfg.rs
Normal file
954
devices/src/legacy/fw_cfg.rs
Normal file
@@ -0,0 +1,954 @@
|
||||
// Copyright 2025 Google LLC.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
/// Cloud Hypervisor implementation of Qemu's fw_cfg spec
|
||||
/// https://www.qemu.org/docs/master/specs/fw_cfg.html
|
||||
/// Linux kernel fw_cfg driver header
|
||||
/// https://github.com/torvalds/linux/blob/master/include/uapi/linux/qemu_fw_cfg.h
|
||||
/// Uploading files to the guest via fw_cfg is supported for all kernels 4.6+ w/ CONFIG_FW_CFG_SYSFS enabled
|
||||
/// https://cateee.net/lkddb/web-lkddb/FW_CFG_SYSFS.html
|
||||
/// No kernel requirement if above functionality is not required,
|
||||
/// only firmware must implement mechanism to interact with this fw_cfg device
|
||||
use std::{
|
||||
fs::File,
|
||||
io::{ErrorKind, Read, Result, Seek, SeekFrom},
|
||||
mem::offset_of,
|
||||
os::unix::fs::FileExt,
|
||||
sync::{Arc, Barrier},
|
||||
};
|
||||
|
||||
use acpi_tables::rsdp::Rsdp;
|
||||
use arch::RegionType;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
use arch::aarch64::layout::{
|
||||
MEM_32BIT_DEVICES_START, MEM_32BIT_RESERVED_START, RAM_64BIT_START, RAM_START as HIGH_RAM_START,
|
||||
};
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use arch::layout::{
|
||||
EBDA_START, HIGH_RAM_START, MEM_32BIT_DEVICES_SIZE, MEM_32BIT_DEVICES_START,
|
||||
MEM_32BIT_RESERVED_START, PCI_MMCONFIG_SIZE, PCI_MMCONFIG_START, RAM_64BIT_START,
|
||||
};
|
||||
use bitfield_struct::bitfield;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use linux_loader::bootparam::boot_params;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
use linux_loader::loader::pe::arm64_image_header as boot_params;
|
||||
use vm_device::BusDevice;
|
||||
use vm_memory::bitmap::AtomicBitmap;
|
||||
use vm_memory::{
|
||||
ByteValued, Bytes, GuestAddress, GuestAddressSpace, GuestMemoryAtomic, GuestMemoryMmap,
|
||||
};
|
||||
use vmm_sys_util::sock_ctrl_msg::IntoIovec;
|
||||
use zerocopy::{FromBytes, FromZeros, Immutable, IntoBytes};
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
// https://github.com/project-oak/oak/tree/main/stage0_bin#memory-layout
|
||||
const STAGE0_START_ADDRESS: GuestAddress = GuestAddress(0xfffe_0000);
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
const STAGE0_SIZE: usize = 0x2_0000;
|
||||
const E820_RAM: u32 = 1;
|
||||
const E820_RESERVED: u32 = 2;
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
const PORT_FW_CFG_SELECTOR: u64 = 0x510;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
const PORT_FW_CFG_DATA: u64 = 0x511;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
const PORT_FW_CFG_DMA_HI: u64 = 0x514;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
const PORT_FW_CFG_DMA_LO: u64 = 0x518;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub const PORT_FW_CFG_BASE: u64 = 0x510;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub const PORT_FW_CFG_WIDTH: u64 = 0xc;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
const PORT_FW_CFG_SELECTOR: u64 = 0x9030008;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
const PORT_FW_CFG_DATA: u64 = 0x9030000;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
const PORT_FW_CFG_DMA_HI: u64 = 0x9030010;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
const PORT_FW_CFG_DMA_LO: u64 = 0x9030014;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub const PORT_FW_CFG_BASE: u64 = 0x9030000;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub const PORT_FW_CFG_WIDTH: u64 = 0x10;
|
||||
|
||||
const FW_CFG_SIGNATURE: u16 = 0x00;
|
||||
const FW_CFG_ID: u16 = 0x01;
|
||||
const FW_CFG_KERNEL_SIZE: u16 = 0x08;
|
||||
const FW_CFG_INITRD_SIZE: u16 = 0x0b;
|
||||
const FW_CFG_KERNEL_DATA: u16 = 0x11;
|
||||
const FW_CFG_INITRD_DATA: u16 = 0x12;
|
||||
const FW_CFG_CMDLINE_SIZE: u16 = 0x14;
|
||||
const FW_CFG_CMDLINE_DATA: u16 = 0x15;
|
||||
const FW_CFG_SETUP_SIZE: u16 = 0x17;
|
||||
const FW_CFG_SETUP_DATA: u16 = 0x18;
|
||||
const FW_CFG_FILE_DIR: u16 = 0x19;
|
||||
const FW_CFG_KNOWN_ITEMS: usize = 0x20;
|
||||
|
||||
pub const FW_CFG_FILE_FIRST: u16 = 0x20;
|
||||
pub const FW_CFG_DMA_SIGNATURE: [u8; 8] = *b"QEMU CFG";
|
||||
// https://github.com/torvalds/linux/blob/master/include/uapi/linux/qemu_fw_cfg.h
|
||||
pub const FW_CFG_ACPI_ID: &str = "QEMU0002";
|
||||
// Reserved (must be enabled)
|
||||
const FW_CFG_F_RESERVED: u8 = 1 << 0;
|
||||
// DMA Toggle Bit (enabled by default)
|
||||
const FW_CFG_F_DMA: u8 = 1 << 1;
|
||||
pub const FW_CFG_FEATURE: [u8; 4] = [FW_CFG_F_RESERVED | FW_CFG_F_DMA, 0, 0, 0];
|
||||
|
||||
const COMMAND_ALLOCATE: u32 = 0x1;
|
||||
const COMMAND_ADD_POINTER: u32 = 0x2;
|
||||
const COMMAND_ADD_CHECKSUM: u32 = 0x3;
|
||||
|
||||
const ALLOC_ZONE_HIGH: u8 = 0x1;
|
||||
const ALLOC_ZONE_FSEG: u8 = 0x2;
|
||||
|
||||
const FW_CFG_FILENAME_TABLE_LOADER: &str = "etc/table-loader";
|
||||
const FW_CFG_FILENAME_RSDP: &str = "acpi/rsdp";
|
||||
const FW_CFG_FILENAME_ACPI_TABLES: &str = "acpi/tables";
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum FwCfgContent {
|
||||
Bytes(Vec<u8>),
|
||||
Slice(&'static [u8]),
|
||||
File(u64, File),
|
||||
U32(u32),
|
||||
}
|
||||
|
||||
struct FwCfgContentAccess<'a> {
|
||||
content: &'a FwCfgContent,
|
||||
offset: u32,
|
||||
}
|
||||
|
||||
impl Read for FwCfgContentAccess<'_> {
|
||||
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
|
||||
match self.content {
|
||||
FwCfgContent::File(offset, f) => {
|
||||
Seek::seek(&mut (&*f), SeekFrom::Start(offset + self.offset as u64))?;
|
||||
Read::read(&mut (&*f), buf)
|
||||
}
|
||||
FwCfgContent::Bytes(b) => match b.get(self.offset as usize..) {
|
||||
Some(mut s) => s.read(buf),
|
||||
None => Err(ErrorKind::UnexpectedEof)?,
|
||||
},
|
||||
FwCfgContent::Slice(b) => match b.get(self.offset as usize..) {
|
||||
Some(mut s) => s.read(buf),
|
||||
None => Err(ErrorKind::UnexpectedEof)?,
|
||||
},
|
||||
FwCfgContent::U32(n) => match n.to_le_bytes().get(self.offset as usize..) {
|
||||
Some(mut s) => s.read(buf),
|
||||
None => Err(ErrorKind::UnexpectedEof)?,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for FwCfgContent {
|
||||
fn default() -> Self {
|
||||
FwCfgContent::Slice(&[])
|
||||
}
|
||||
}
|
||||
|
||||
impl FwCfgContent {
|
||||
fn size(&self) -> Result<u32> {
|
||||
let ret = match self {
|
||||
FwCfgContent::Bytes(v) => v.len(),
|
||||
FwCfgContent::File(offset, f) => (f.metadata()?.len() - offset) as usize,
|
||||
FwCfgContent::Slice(s) => s.len(),
|
||||
FwCfgContent::U32(n) => size_of_val(n),
|
||||
};
|
||||
u32::try_from(ret).map_err(|_| std::io::ErrorKind::InvalidInput.into())
|
||||
}
|
||||
fn access(&self, offset: u32) -> FwCfgContentAccess<'_> {
|
||||
FwCfgContentAccess {
|
||||
content: self,
|
||||
offset,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct FwCfgItem {
|
||||
pub name: String,
|
||||
pub content: FwCfgContent,
|
||||
}
|
||||
|
||||
/// https://www.qemu.org/docs/master/specs/fw_cfg.html
|
||||
#[derive(Debug)]
|
||||
pub struct FwCfg {
|
||||
selector: u16,
|
||||
data_offset: u32,
|
||||
dma_address: u64,
|
||||
items: Vec<FwCfgItem>, // 0x20 and above
|
||||
known_items: [FwCfgContent; FW_CFG_KNOWN_ITEMS], // 0x0 to 0x19
|
||||
memory: GuestMemoryAtomic<GuestMemoryMmap<AtomicBitmap>>,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, IntoBytes, FromBytes)]
|
||||
struct FwCfgDmaAccess {
|
||||
control_be: u32,
|
||||
length_be: u32,
|
||||
address_be: u64,
|
||||
}
|
||||
|
||||
// https://github.com/torvalds/linux/blob/master/include/uapi/linux/qemu_fw_cfg.h#L67
|
||||
#[bitfield(u32)]
|
||||
struct AccessControl {
|
||||
// FW_CFG_DMA_CTL_ERROR = 0x01
|
||||
error: bool,
|
||||
// FW_CFG_DMA_CTL_READ = 0x02
|
||||
read: bool,
|
||||
#[bits(1)]
|
||||
_unused2: u8,
|
||||
// FW_CFG_DMA_CTL_SKIP = 0x04
|
||||
skip: bool,
|
||||
#[bits(3)]
|
||||
_unused3: u8,
|
||||
// FW_CFG_DMA_CTL_ERROR = 0x08
|
||||
select: bool,
|
||||
#[bits(7)]
|
||||
_unused4: u8,
|
||||
// FW_CFG_DMA_CTL_WRITE = 0x10
|
||||
write: bool,
|
||||
#[bits(16)]
|
||||
_unused: u32,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, IntoBytes, FromBytes)]
|
||||
struct FwCfgFilesHeader {
|
||||
count_be: u32,
|
||||
}
|
||||
|
||||
pub const FILE_NAME_SIZE: usize = 56;
|
||||
|
||||
pub fn create_file_name(name: &str) -> [u8; FILE_NAME_SIZE] {
|
||||
let mut c_name = [0u8; FILE_NAME_SIZE];
|
||||
let c_len = std::cmp::min(FILE_NAME_SIZE - 1, name.len());
|
||||
c_name[0..c_len].copy_from_slice(&name.as_bytes()[0..c_len]);
|
||||
c_name
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[repr(C, packed)]
|
||||
#[derive(Debug, IntoBytes, FromBytes, Clone, Copy)]
|
||||
struct BootE820Entry {
|
||||
addr: u64,
|
||||
size: u64,
|
||||
type_: u32,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, IntoBytes, FromBytes)]
|
||||
struct FwCfgFile {
|
||||
size_be: u32,
|
||||
select_be: u16,
|
||||
_reserved: u16,
|
||||
name: [u8; FILE_NAME_SIZE],
|
||||
}
|
||||
|
||||
#[repr(C, align(4))]
|
||||
#[derive(Debug, IntoBytes, Immutable)]
|
||||
struct Allocate {
|
||||
command: u32,
|
||||
file: [u8; FILE_NAME_SIZE],
|
||||
align: u32,
|
||||
zone: u8,
|
||||
_pad: [u8; 63],
|
||||
}
|
||||
|
||||
#[repr(C, align(4))]
|
||||
#[derive(Debug, IntoBytes, Immutable)]
|
||||
struct AddPointer {
|
||||
command: u32,
|
||||
dst: [u8; FILE_NAME_SIZE],
|
||||
src: [u8; FILE_NAME_SIZE],
|
||||
offset: u32,
|
||||
size: u8,
|
||||
_pad: [u8; 7],
|
||||
}
|
||||
|
||||
#[repr(C, align(4))]
|
||||
#[derive(Debug, IntoBytes, Immutable)]
|
||||
struct AddChecksum {
|
||||
command: u32,
|
||||
file: [u8; FILE_NAME_SIZE],
|
||||
offset: u32,
|
||||
start: u32,
|
||||
len: u32,
|
||||
_pad: [u8; 56],
|
||||
}
|
||||
|
||||
fn create_intra_pointer(name: &str, offset: usize, size: u8) -> AddPointer {
|
||||
AddPointer {
|
||||
command: COMMAND_ADD_POINTER,
|
||||
dst: create_file_name(name),
|
||||
src: create_file_name(name),
|
||||
offset: offset as u32,
|
||||
size,
|
||||
_pad: [0; 7],
|
||||
}
|
||||
}
|
||||
|
||||
fn create_acpi_table_checksum(offset: usize, len: usize) -> AddChecksum {
|
||||
AddChecksum {
|
||||
command: COMMAND_ADD_CHECKSUM,
|
||||
file: create_file_name(FW_CFG_FILENAME_ACPI_TABLES),
|
||||
offset: (offset + offset_of!(AcpiTableHeader, checksum)) as u32,
|
||||
start: offset as u32,
|
||||
len: len as u32,
|
||||
_pad: [0; 56],
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C, align(4))]
|
||||
#[derive(Debug, Clone, Default, FromBytes, IntoBytes)]
|
||||
struct AcpiTableHeader {
|
||||
signature: [u8; 4],
|
||||
length: u32,
|
||||
revision: u8,
|
||||
checksum: u8,
|
||||
oem_id: [u8; 6],
|
||||
oem_table_id: [u8; 8],
|
||||
oem_revision: u32,
|
||||
asl_compiler_id: [u8; 4],
|
||||
asl_compiler_revision: u32,
|
||||
}
|
||||
|
||||
struct AcpiTable {
|
||||
rsdp: Rsdp,
|
||||
tables: Vec<u8>,
|
||||
table_pointers: Vec<usize>,
|
||||
table_checksums: Vec<(usize, usize)>,
|
||||
}
|
||||
|
||||
impl AcpiTable {
|
||||
fn pointers(&self) -> &[usize] {
|
||||
&self.table_pointers
|
||||
}
|
||||
|
||||
fn checksums(&self) -> &[(usize, usize)] {
|
||||
&self.table_checksums
|
||||
}
|
||||
|
||||
fn take(self) -> (Rsdp, Vec<u8>) {
|
||||
(self.rsdp, self.tables)
|
||||
}
|
||||
}
|
||||
|
||||
// Creates fw_cfg items used by firmware to load and verify Acpi tables
|
||||
// https://github.com/qemu/qemu/blob/master/hw/acpi/bios-linker-loader.c
|
||||
fn create_acpi_loader(acpi_table: AcpiTable) -> [FwCfgItem; 3] {
|
||||
let mut table_loader_bytes: Vec<u8> = Vec::new();
|
||||
let allocate_rsdp = Allocate {
|
||||
command: COMMAND_ALLOCATE,
|
||||
file: create_file_name(FW_CFG_FILENAME_RSDP),
|
||||
align: 4,
|
||||
zone: ALLOC_ZONE_FSEG,
|
||||
_pad: [0; 63],
|
||||
};
|
||||
table_loader_bytes.extend(allocate_rsdp.as_bytes());
|
||||
|
||||
let allocate_tables = Allocate {
|
||||
command: COMMAND_ALLOCATE,
|
||||
file: create_file_name(FW_CFG_FILENAME_ACPI_TABLES),
|
||||
align: 4,
|
||||
zone: ALLOC_ZONE_HIGH,
|
||||
_pad: [0; 63],
|
||||
};
|
||||
table_loader_bytes.extend(allocate_tables.as_bytes());
|
||||
|
||||
for pointer_offset in acpi_table.pointers().iter() {
|
||||
let pointer = create_intra_pointer(FW_CFG_FILENAME_ACPI_TABLES, *pointer_offset, 8);
|
||||
table_loader_bytes.extend(pointer.as_bytes());
|
||||
}
|
||||
for (offset, len) in acpi_table.checksums().iter() {
|
||||
let checksum = create_acpi_table_checksum(*offset, *len);
|
||||
table_loader_bytes.extend(checksum.as_bytes());
|
||||
}
|
||||
let pointer_rsdp_to_xsdt = AddPointer {
|
||||
command: COMMAND_ADD_POINTER,
|
||||
dst: create_file_name(FW_CFG_FILENAME_RSDP),
|
||||
src: create_file_name(FW_CFG_FILENAME_ACPI_TABLES),
|
||||
offset: offset_of!(Rsdp, xsdt_addr) as u32,
|
||||
size: 8,
|
||||
_pad: [0; 7],
|
||||
};
|
||||
table_loader_bytes.extend(pointer_rsdp_to_xsdt.as_bytes());
|
||||
let checksum_rsdp = AddChecksum {
|
||||
command: COMMAND_ADD_CHECKSUM,
|
||||
file: create_file_name(FW_CFG_FILENAME_RSDP),
|
||||
offset: offset_of!(Rsdp, checksum) as u32,
|
||||
start: 0,
|
||||
len: offset_of!(Rsdp, length) as u32,
|
||||
_pad: [0; 56],
|
||||
};
|
||||
let checksum_rsdp_ext = AddChecksum {
|
||||
command: COMMAND_ADD_CHECKSUM,
|
||||
file: create_file_name(FW_CFG_FILENAME_RSDP),
|
||||
offset: offset_of!(Rsdp, extended_checksum) as u32,
|
||||
start: 0,
|
||||
len: size_of::<Rsdp>() as u32,
|
||||
_pad: [0; 56],
|
||||
};
|
||||
table_loader_bytes.extend(checksum_rsdp.as_bytes());
|
||||
table_loader_bytes.extend(checksum_rsdp_ext.as_bytes());
|
||||
|
||||
let table_loader = FwCfgItem {
|
||||
name: FW_CFG_FILENAME_TABLE_LOADER.to_owned(),
|
||||
content: FwCfgContent::Bytes(table_loader_bytes),
|
||||
};
|
||||
let (rsdp, tables) = acpi_table.take();
|
||||
let acpi_rsdp = FwCfgItem {
|
||||
name: FW_CFG_FILENAME_RSDP.to_owned(),
|
||||
content: FwCfgContent::Bytes(rsdp.as_bytes().to_owned()),
|
||||
};
|
||||
let apci_tables = FwCfgItem {
|
||||
name: FW_CFG_FILENAME_ACPI_TABLES.to_owned(),
|
||||
content: FwCfgContent::Bytes(tables),
|
||||
};
|
||||
[table_loader, acpi_rsdp, apci_tables]
|
||||
}
|
||||
|
||||
impl FwCfg {
|
||||
pub fn new(memory: GuestMemoryAtomic<GuestMemoryMmap<AtomicBitmap>>) -> FwCfg {
|
||||
const DEFAULT_ITEM: FwCfgContent = FwCfgContent::Slice(&[]);
|
||||
let mut known_items = [DEFAULT_ITEM; FW_CFG_KNOWN_ITEMS];
|
||||
known_items[FW_CFG_SIGNATURE as usize] = FwCfgContent::Slice(&FW_CFG_DMA_SIGNATURE);
|
||||
known_items[FW_CFG_ID as usize] = FwCfgContent::Slice(&FW_CFG_FEATURE);
|
||||
let file_buf = Vec::from(FwCfgFilesHeader { count_be: 0 }.as_mut_bytes());
|
||||
known_items[FW_CFG_FILE_DIR as usize] = FwCfgContent::Bytes(file_buf);
|
||||
|
||||
FwCfg {
|
||||
selector: 0,
|
||||
data_offset: 0,
|
||||
dma_address: 0,
|
||||
items: vec![],
|
||||
known_items,
|
||||
memory,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn populate_fw_cfg(
|
||||
&mut self,
|
||||
mem_size: Option<usize>,
|
||||
kernel: Option<File>,
|
||||
initramfs: Option<File>,
|
||||
cmdline: Option<std::ffi::CString>,
|
||||
fw_cfg_item_list: Option<Vec<FwCfgItem>>,
|
||||
) -> Result<()> {
|
||||
if let Some(mem_size) = mem_size {
|
||||
self.add_e820(mem_size)?
|
||||
}
|
||||
if let Some(kernel) = kernel {
|
||||
self.add_kernel_data(&kernel)?;
|
||||
}
|
||||
if let Some(cmdline) = cmdline {
|
||||
self.add_kernel_cmdline(cmdline);
|
||||
}
|
||||
if let Some(initramfs) = initramfs {
|
||||
self.add_initramfs_data(&initramfs)?
|
||||
}
|
||||
if let Some(fw_cfg_item_list) = fw_cfg_item_list {
|
||||
for item in fw_cfg_item_list {
|
||||
self.add_item(item)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn add_e820(&mut self, mem_size: usize) -> Result<()> {
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
let mut mem_regions = vec![
|
||||
(GuestAddress(0), EBDA_START.0 as usize, RegionType::Ram),
|
||||
(
|
||||
MEM_32BIT_DEVICES_START,
|
||||
MEM_32BIT_DEVICES_SIZE as usize,
|
||||
RegionType::Reserved,
|
||||
),
|
||||
(
|
||||
PCI_MMCONFIG_START,
|
||||
PCI_MMCONFIG_SIZE as usize,
|
||||
RegionType::Reserved,
|
||||
),
|
||||
(STAGE0_START_ADDRESS, STAGE0_SIZE, RegionType::Reserved),
|
||||
];
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
let mut mem_regions = arch::aarch64::arch_memory_regions();
|
||||
if mem_size < MEM_32BIT_DEVICES_START.0 as usize {
|
||||
mem_regions.push((
|
||||
HIGH_RAM_START,
|
||||
mem_size - HIGH_RAM_START.0 as usize,
|
||||
RegionType::Ram,
|
||||
));
|
||||
} else {
|
||||
mem_regions.push((
|
||||
HIGH_RAM_START,
|
||||
MEM_32BIT_RESERVED_START.0 as usize - HIGH_RAM_START.0 as usize,
|
||||
RegionType::Ram,
|
||||
));
|
||||
mem_regions.push((
|
||||
RAM_64BIT_START,
|
||||
mem_size - (MEM_32BIT_DEVICES_START.0 as usize),
|
||||
RegionType::Ram,
|
||||
));
|
||||
}
|
||||
let mut bytes = vec![];
|
||||
for (addr, size, region) in mem_regions.iter() {
|
||||
let type_ = match region {
|
||||
RegionType::Ram => E820_RAM,
|
||||
RegionType::Reserved => E820_RESERVED,
|
||||
RegionType::SubRegion => continue,
|
||||
};
|
||||
let mut entry = BootE820Entry {
|
||||
addr: addr.0,
|
||||
size: *size as u64,
|
||||
type_,
|
||||
};
|
||||
bytes.extend_from_slice(entry.as_mut_bytes());
|
||||
}
|
||||
let item = FwCfgItem {
|
||||
name: "etc/e820".to_owned(),
|
||||
content: FwCfgContent::Bytes(bytes),
|
||||
};
|
||||
self.add_item(item)
|
||||
}
|
||||
|
||||
fn file_dir_mut(&mut self) -> &mut Vec<u8> {
|
||||
let FwCfgContent::Bytes(file_buf) = &mut self.known_items[FW_CFG_FILE_DIR as usize] else {
|
||||
unreachable!("fw_cfg: selector {FW_CFG_FILE_DIR:#x} should be FwCfgContent::Byte!")
|
||||
};
|
||||
file_buf
|
||||
}
|
||||
|
||||
fn update_count(&mut self) {
|
||||
let mut header = FwCfgFilesHeader {
|
||||
count_be: (self.items.len() as u32).to_be(),
|
||||
};
|
||||
self.file_dir_mut()[0..4].copy_from_slice(header.as_mut_bytes());
|
||||
}
|
||||
|
||||
pub fn add_item(&mut self, item: FwCfgItem) -> Result<()> {
|
||||
let index = self.items.len();
|
||||
let c_name = create_file_name(&item.name);
|
||||
let size = item.content.size()?;
|
||||
let mut cfg_file = FwCfgFile {
|
||||
size_be: size.to_be(),
|
||||
select_be: (FW_CFG_FILE_FIRST + index as u16).to_be(),
|
||||
_reserved: 0,
|
||||
name: c_name,
|
||||
};
|
||||
self.file_dir_mut()
|
||||
.extend_from_slice(cfg_file.as_mut_bytes());
|
||||
self.items.push(item);
|
||||
self.update_count();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn dma_read_content(
|
||||
&self,
|
||||
content: &FwCfgContent,
|
||||
offset: u32,
|
||||
len: u32,
|
||||
address: u64,
|
||||
) -> Result<u32> {
|
||||
let content_size = content.size()?.saturating_sub(offset);
|
||||
let op_size = std::cmp::min(content_size, len);
|
||||
let mut access = content.access(offset);
|
||||
let mut buf = vec![0u8; op_size as usize];
|
||||
access.read_exact(buf.as_mut_bytes())?;
|
||||
let r = self
|
||||
.memory
|
||||
.memory()
|
||||
.write(buf.as_bytes(), GuestAddress(address));
|
||||
match r {
|
||||
Err(e) => {
|
||||
error!("fw_cfg: dma read error: {e:x?}");
|
||||
Err(ErrorKind::InvalidInput.into())
|
||||
}
|
||||
Ok(size) => Ok(size as u32),
|
||||
}
|
||||
}
|
||||
|
||||
fn dma_read(&mut self, selector: u16, len: u32, address: u64) -> Result<()> {
|
||||
let op_size = if let Some(content) = self.known_items.get(selector as usize) {
|
||||
self.dma_read_content(content, self.data_offset, len, address)
|
||||
} else if let Some(item) = self.items.get((selector - FW_CFG_FILE_FIRST) as usize) {
|
||||
self.dma_read_content(&item.content, self.data_offset, len, address)
|
||||
} else {
|
||||
error!("fw_cfg: selector {selector:#x} does not exist.");
|
||||
Err(ErrorKind::NotFound.into())
|
||||
}?;
|
||||
self.data_offset += op_size;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn do_dma(&mut self) {
|
||||
let dma_address = self.dma_address;
|
||||
let mut access = FwCfgDmaAccess::new_zeroed();
|
||||
let dma_access = match self
|
||||
.memory
|
||||
.memory()
|
||||
.read(access.as_mut_bytes(), GuestAddress(dma_address))
|
||||
{
|
||||
Ok(_) => access,
|
||||
Err(e) => {
|
||||
error!("fw_cfg: invalid address of dma access {dma_address:#x}: {e:?}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let control = AccessControl(u32::from_be(dma_access.control_be));
|
||||
if control.select() {
|
||||
self.selector = control.select() as u16;
|
||||
}
|
||||
let len = u32::from_be(dma_access.length_be);
|
||||
let addr = u64::from_be(dma_access.address_be);
|
||||
let ret = if control.read() {
|
||||
self.dma_read(self.selector, len, addr)
|
||||
} else if control.write() {
|
||||
Err(ErrorKind::InvalidInput.into())
|
||||
} else if control.skip() {
|
||||
self.data_offset += len;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(ErrorKind::InvalidData.into())
|
||||
};
|
||||
let mut access_resp = AccessControl(0);
|
||||
if let Err(e) = ret {
|
||||
error!("fw_cfg: dma operation {dma_access:x?}: {e:x?}");
|
||||
access_resp.set_error(true);
|
||||
}
|
||||
if let Err(e) = self.memory.memory().write(
|
||||
&access_resp.0.to_be_bytes(),
|
||||
GuestAddress(dma_address + core::mem::offset_of!(FwCfgDmaAccess, control_be) as u64),
|
||||
) {
|
||||
error!("fw_cfg: finishing dma: {e:?}")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_kernel_data(&mut self, file: &File) -> Result<()> {
|
||||
let mut buffer = vec![0u8; size_of::<boot_params>()];
|
||||
file.read_exact_at(&mut buffer, 0)?;
|
||||
let bp = boot_params::from_mut_slice(&mut buffer).unwrap();
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
{
|
||||
// must set to 4 for backwards compatibility
|
||||
// https://docs.kernel.org/arch/x86/boot.html#the-real-mode-kernel-header
|
||||
if bp.hdr.setup_sects == 0 {
|
||||
bp.hdr.setup_sects = 4;
|
||||
}
|
||||
// wildcard boot loader type
|
||||
bp.hdr.type_of_loader = 0xff;
|
||||
}
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
let kernel_start = bp.text_offset;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
let kernel_start = (bp.hdr.setup_sects as usize + 1) * 512;
|
||||
self.known_items[FW_CFG_SETUP_SIZE as usize] = FwCfgContent::U32(buffer.len() as u32);
|
||||
self.known_items[FW_CFG_SETUP_DATA as usize] = FwCfgContent::Bytes(buffer);
|
||||
self.known_items[FW_CFG_KERNEL_SIZE as usize] =
|
||||
FwCfgContent::U32(file.metadata()?.len() as u32 - kernel_start as u32);
|
||||
self.known_items[FW_CFG_KERNEL_DATA as usize] =
|
||||
FwCfgContent::File(kernel_start as u64, file.try_clone()?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn add_kernel_cmdline(&mut self, s: std::ffi::CString) {
|
||||
let bytes = s.into_bytes_with_nul();
|
||||
self.known_items[FW_CFG_CMDLINE_SIZE as usize] = FwCfgContent::U32(bytes.len() as u32);
|
||||
self.known_items[FW_CFG_CMDLINE_DATA as usize] = FwCfgContent::Bytes(bytes);
|
||||
}
|
||||
|
||||
pub fn add_acpi(
|
||||
&mut self,
|
||||
rsdp: Rsdp,
|
||||
tables: Vec<u8>,
|
||||
table_checksums: Vec<(usize, usize)>,
|
||||
table_pointers: Vec<usize>,
|
||||
) -> Result<()> {
|
||||
let acpi_table = AcpiTable {
|
||||
rsdp,
|
||||
tables,
|
||||
table_checksums,
|
||||
table_pointers,
|
||||
};
|
||||
let [table_loader, acpi_rsdp, apci_tables] = create_acpi_loader(acpi_table);
|
||||
self.add_item(table_loader)?;
|
||||
self.add_item(acpi_rsdp)?;
|
||||
self.add_item(apci_tables)
|
||||
}
|
||||
|
||||
pub fn add_initramfs_data(&mut self, file: &File) -> Result<()> {
|
||||
let initramfs_size = file.metadata()?.len();
|
||||
self.known_items[FW_CFG_INITRD_SIZE as usize] = FwCfgContent::U32(initramfs_size as _);
|
||||
self.known_items[FW_CFG_INITRD_DATA as usize] = FwCfgContent::File(0, file.try_clone()?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_content(content: &FwCfgContent, offset: u32, data: &mut [u8], size: u32) -> Option<u8> {
|
||||
let start = offset as usize;
|
||||
let end = start + size as usize;
|
||||
match content {
|
||||
FwCfgContent::Bytes(b) => {
|
||||
if b.len() >= size as usize {
|
||||
data.copy_from_slice(&b[start..end]);
|
||||
}
|
||||
}
|
||||
FwCfgContent::Slice(s) => {
|
||||
if s.len() >= size as usize {
|
||||
data.copy_from_slice(&s[start..end]);
|
||||
}
|
||||
}
|
||||
FwCfgContent::File(o, f) => {
|
||||
f.read_exact_at(data, o + offset as u64).ok()?;
|
||||
}
|
||||
FwCfgContent::U32(n) => {
|
||||
let bytes = n.to_le_bytes();
|
||||
data.copy_from_slice(&bytes[start..end]);
|
||||
}
|
||||
};
|
||||
Some(size as u8)
|
||||
}
|
||||
|
||||
fn read_data(&mut self, data: &mut [u8], size: u32) -> u8 {
|
||||
let ret = if let Some(content) = self.known_items.get(self.selector as usize) {
|
||||
Self::read_content(content, self.data_offset, data, size)
|
||||
} else if let Some(item) = self.items.get((self.selector - FW_CFG_FILE_FIRST) as usize) {
|
||||
Self::read_content(&item.content, self.data_offset, data, size)
|
||||
} else {
|
||||
error!("fw_cfg: selector {:#x} does not exist.", self.selector);
|
||||
None
|
||||
};
|
||||
if let Some(val) = ret {
|
||||
self.data_offset += size;
|
||||
val
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BusDevice for FwCfg {
|
||||
fn read(&mut self, _base: u64, offset: u64, data: &mut [u8]) {
|
||||
let port = offset + PORT_FW_CFG_BASE;
|
||||
let size = data.len();
|
||||
match (port, size) {
|
||||
(PORT_FW_CFG_SELECTOR, _) => {
|
||||
error!("fw_cfg: selector register is write-only.");
|
||||
}
|
||||
(PORT_FW_CFG_DATA, _) => _ = self.read_data(data, size as u32),
|
||||
(PORT_FW_CFG_DMA_HI, 4) => {
|
||||
let addr = self.dma_address;
|
||||
let addr_hi = (addr >> 32) as u32;
|
||||
data.copy_from_slice(&addr_hi.to_be_bytes());
|
||||
}
|
||||
(PORT_FW_CFG_DMA_LO, 4) => {
|
||||
let addr = self.dma_address;
|
||||
let addr_lo = (addr & 0xffff_ffff) as u32;
|
||||
data.copy_from_slice(&addr_lo.to_be_bytes());
|
||||
}
|
||||
_ => {
|
||||
debug!(
|
||||
"fw_cfg: read from unknown port {port:#x}: {size:#x} bytes and offset {offset:#x}."
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn write(&mut self, _base: u64, offset: u64, data: &[u8]) -> Option<Arc<Barrier>> {
|
||||
let port = offset + PORT_FW_CFG_BASE;
|
||||
let size = data.size();
|
||||
match (port, size) {
|
||||
(PORT_FW_CFG_SELECTOR, 2) => {
|
||||
let mut buf = [0u8; 2];
|
||||
buf[..size].copy_from_slice(&data[..size]);
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
let val = u16::from_le_bytes(buf);
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
let val = u16::from_be_bytes(buf);
|
||||
self.selector = val;
|
||||
self.data_offset = 0;
|
||||
}
|
||||
(PORT_FW_CFG_DATA, 1) => error!("fw_cfg: data register is read-only."),
|
||||
(PORT_FW_CFG_DMA_HI, 4) => {
|
||||
let mut buf = [0u8; 4];
|
||||
buf[..size].copy_from_slice(&data[..size]);
|
||||
let val = u32::from_be_bytes(buf);
|
||||
self.dma_address &= 0xffff_ffff;
|
||||
self.dma_address |= (val as u64) << 32;
|
||||
}
|
||||
(PORT_FW_CFG_DMA_LO, 4) => {
|
||||
let mut buf = [0u8; 4];
|
||||
buf[..size].copy_from_slice(&data[..size]);
|
||||
let val = u32::from_be_bytes(buf);
|
||||
self.dma_address &= !0xffff_ffff;
|
||||
self.dma_address |= val as u64;
|
||||
self.do_dma();
|
||||
}
|
||||
_ => debug!(
|
||||
"fw_cfg: write to unknown port {port:#x}: {size:#x} bytes and offset {offset:#x} ."
|
||||
),
|
||||
};
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::ffi::CString;
|
||||
use std::io::Write;
|
||||
|
||||
use vmm_sys_util::tempfile::TempFile;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
const SELECTOR_OFFSET: u64 = 0;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
const SELECTOR_OFFSET: u64 = 8;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
const DATA_OFFSET: u64 = 1;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
const DATA_OFFSET: u64 = 0;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
const DMA_OFFSET: u64 = 4;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
const DMA_OFFSET: u64 = 16;
|
||||
|
||||
#[test]
|
||||
fn test_signature() {
|
||||
let gm = GuestMemoryAtomic::new(
|
||||
GuestMemoryMmap::from_ranges(&[(GuestAddress(0), RAM_64BIT_START.0 as usize)]).unwrap(),
|
||||
);
|
||||
|
||||
let mut fw_cfg = FwCfg::new(gm);
|
||||
|
||||
let mut data = vec![0u8];
|
||||
|
||||
let mut sig_iter = FW_CFG_DMA_SIGNATURE.into_iter();
|
||||
fw_cfg.write(0, SELECTOR_OFFSET, &[FW_CFG_SIGNATURE as u8, 0]);
|
||||
loop {
|
||||
if let Some(char) = sig_iter.next() {
|
||||
fw_cfg.read(0, DATA_OFFSET, &mut data);
|
||||
assert_eq!(data[0], char);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn test_kernel_cmdline() {
|
||||
let gm = GuestMemoryAtomic::new(
|
||||
GuestMemoryMmap::from_ranges(&[(GuestAddress(0), RAM_64BIT_START.0 as usize)]).unwrap(),
|
||||
);
|
||||
|
||||
let mut fw_cfg = FwCfg::new(gm);
|
||||
|
||||
let cmdline = *b"cmdline\0";
|
||||
|
||||
fw_cfg.add_kernel_cmdline(CString::from_vec_with_nul(cmdline.to_vec()).unwrap());
|
||||
|
||||
let mut data = vec![0u8];
|
||||
|
||||
let mut cmdline_iter = cmdline.into_iter();
|
||||
fw_cfg.write(0, SELECTOR_OFFSET, &[FW_CFG_CMDLINE_DATA as u8, 0]);
|
||||
loop {
|
||||
if let Some(char) = cmdline_iter.next() {
|
||||
fw_cfg.read(0, DATA_OFFSET, &mut data);
|
||||
assert_eq!(data[0], char);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_initram_fs() {
|
||||
let gm = GuestMemoryAtomic::new(
|
||||
GuestMemoryMmap::from_ranges(&[(GuestAddress(0), RAM_64BIT_START.0 as usize)]).unwrap(),
|
||||
);
|
||||
|
||||
let mut fw_cfg = FwCfg::new(gm);
|
||||
|
||||
let temp = TempFile::new().unwrap();
|
||||
let mut temp_file = temp.as_file();
|
||||
|
||||
let initram_content = b"this is the initramfs";
|
||||
let written = temp_file.write(initram_content);
|
||||
assert_eq!(written.unwrap(), 21);
|
||||
let _ = fw_cfg.add_initramfs_data(temp_file);
|
||||
|
||||
let mut data = vec![0u8];
|
||||
|
||||
let mut initram_iter = (*initram_content).into_iter();
|
||||
fw_cfg.write(0, SELECTOR_OFFSET, &[FW_CFG_INITRD_DATA as u8, 0]);
|
||||
loop {
|
||||
if let Some(char) = initram_iter.next() {
|
||||
fw_cfg.read(0, DATA_OFFSET, &mut data);
|
||||
assert_eq!(data[0], char);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dma() {
|
||||
let code = [
|
||||
0xba, 0xf8, 0x03, 0x00, 0xd8, 0x04, b'0', 0xee, 0xb0, b'\n', 0xee, 0xf4,
|
||||
];
|
||||
|
||||
let content = FwCfgContent::Bytes(code.to_vec());
|
||||
|
||||
let mem_size = 0x1000;
|
||||
let load_addr = GuestAddress(0x1000);
|
||||
let mem: GuestMemoryMmap<AtomicBitmap> =
|
||||
GuestMemoryMmap::from_ranges(&[(load_addr, mem_size)]).unwrap();
|
||||
|
||||
// Note: In firmware we would just allocate FwCfgDmaAccess struct
|
||||
// and use address of struct (&) as dma address
|
||||
let mut access_control = AccessControl(0);
|
||||
// bit 1 = read access
|
||||
access_control.set_read(true);
|
||||
// length of data to access
|
||||
let length_be = (code.len() as u32).to_be();
|
||||
// guest address for data
|
||||
let code_address = 0x1900_u64;
|
||||
let address_be = code_address.to_be();
|
||||
let mut access = FwCfgDmaAccess {
|
||||
control_be: access_control.0.to_be(), // bit(1) = read bit
|
||||
length_be,
|
||||
address_be,
|
||||
};
|
||||
// access address is where to put the code
|
||||
let access_address = GuestAddress(load_addr.0);
|
||||
let address_bytes = access_address.0.to_be_bytes();
|
||||
let dma_lo: [u8; 4] = address_bytes[0..4].try_into().unwrap();
|
||||
let dma_hi: [u8; 4] = address_bytes[4..8].try_into().unwrap();
|
||||
|
||||
// writing the FwCfgDmaAccess to mem (this would just be self.dma_access.as_ref() in guest)
|
||||
let _ = mem.write(access.as_mut_bytes(), access_address);
|
||||
let mem_m = GuestMemoryAtomic::new(mem.clone());
|
||||
let mut fw_cfg = FwCfg::new(mem_m);
|
||||
let cfg_item = FwCfgItem {
|
||||
name: "code".to_string(),
|
||||
content,
|
||||
};
|
||||
let _ = fw_cfg.add_item(cfg_item);
|
||||
|
||||
let mut data = [0u8; 12];
|
||||
|
||||
let _ = mem.read(&mut data, GuestAddress(code_address));
|
||||
assert_ne!(data, code);
|
||||
|
||||
fw_cfg.write(0, SELECTOR_OFFSET, &[FW_CFG_FILE_FIRST as u8, 0]);
|
||||
fw_cfg.write(0, DMA_OFFSET, &dma_lo);
|
||||
fw_cfg.write(0, DMA_OFFSET + 4, &dma_hi);
|
||||
let _ = mem.read(&mut data, GuestAddress(code_address));
|
||||
assert_eq!(data, code);
|
||||
}
|
||||
}
|
||||
@@ -12,8 +12,8 @@ use std::{io, result};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
use vm_device::interrupt::InterruptSourceGroup;
|
||||
use vm_device::BusDevice;
|
||||
use vm_device::interrupt::InterruptSourceGroup;
|
||||
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
|
||||
|
||||
use crate::{read_le_u32, write_le_u32};
|
||||
@@ -28,10 +28,10 @@ const GPIORIE: u64 = 0x414; // Raw Interrupt Status Register
|
||||
const GPIOMIS: u64 = 0x418; // Masked Interrupt Status Register
|
||||
const GPIOIC: u64 = 0x41c; // Interrupt Clear Register
|
||||
const GPIOAFSEL: u64 = 0x420; // Mode Control Select Register
|
||||
// From 0x424 to 0xFDC => reserved space.
|
||||
// From 0xFE0 to 0xFFC => Peripheral and PrimeCell Identification Registers which are Read Only registers.
|
||||
// These registers can conceptually be treated as a 32-bit register, and PartNumber[11:0] is used to identify the peripheral.
|
||||
// We are putting the expected values (look at 'Reset value' column from above mentioned document) in an array.
|
||||
// From 0x424 to 0xFDC => reserved space.
|
||||
// From 0xFE0 to 0xFFC => Peripheral and PrimeCell Identification Registers which are Read Only registers.
|
||||
// These registers can conceptually be treated as a 32-bit register, and PartNumber[11:0] is used to identify the peripheral.
|
||||
// We are putting the expected values (look at 'Reset value' column from above mentioned document) in an array.
|
||||
const GPIO_ID: [u8; 8] = [0x61, 0x10, 0x14, 0x00, 0x0d, 0xf0, 0x05, 0xb1];
|
||||
// ID Margins
|
||||
const GPIO_ID_LOW: u64 = 0xfe0;
|
||||
|
||||
@@ -45,7 +45,7 @@ impl BusDevice for I8042Device {
|
||||
if data.len() == 1 && data[0] == 0xfe && offset == 3 {
|
||||
info!("i8042 reset signalled");
|
||||
if let Err(e) = self.reset_evt.write(1) {
|
||||
error!("Error triggering i8042 reset event: {}", e);
|
||||
error!("Error triggering i8042 reset event: {e}");
|
||||
}
|
||||
// Spin until we are sure the reset_evt has been handled and that when
|
||||
// we return from the KVM_RUN we will exit rather than re-enter the guest.
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
mod cmos;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
mod debug_port;
|
||||
#[cfg(feature = "fw_cfg")]
|
||||
pub mod fw_cfg;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
mod fwdebug;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
@@ -22,6 +24,8 @@ mod uart_pl011;
|
||||
pub use self::cmos::Cmos;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub use self::debug_port::DebugPort;
|
||||
#[cfg(feature = "fw_cfg")]
|
||||
pub use self::fw_cfg::FwCfg;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub use self::fwdebug::FwDebugDevice;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
|
||||
@@ -31,11 +31,11 @@ const RTCIMSC: u64 = 0x10; // Interrupt Mask Set or Clear Register.
|
||||
const RTCRIS: u64 = 0x14; // Raw Interrupt Status.
|
||||
const RTCMIS: u64 = 0x18; // Masked Interrupt Status.
|
||||
const RTCICR: u64 = 0x1c; // Interrupt Clear Register.
|
||||
// From 0x020 to 0xFDC => reserved space.
|
||||
// From 0xFE0 to 0x1000 => Peripheral and PrimeCell Identification Registers which are Read Only registers.
|
||||
// AMBA standard devices have CIDs (Cell IDs) and PIDs (Peripheral IDs). The linux kernel will look for these in order to assert the identity
|
||||
// of these devices (i.e look at the `amba_device_try_add` function).
|
||||
// We are putting the expected values (look at 'Reset value' column from above mentioned document) in an array.
|
||||
// From 0x020 to 0xFDC => reserved space.
|
||||
// From 0xFE0 to 0x1000 => Peripheral and PrimeCell Identification Registers which are Read Only registers.
|
||||
// AMBA standard devices have CIDs (Cell IDs) and PIDs (Peripheral IDs). The linux kernel will look for these in order to assert the identity
|
||||
// of these devices (i.e look at the `amba_device_try_add` function).
|
||||
// We are putting the expected values (look at 'Reset value' column from above mentioned document) in an array.
|
||||
const PL031_ID: [u8; 8] = [0x31, 0x10, 0x14, 0x00, 0x0d, 0xf0, 0x05, 0xb1];
|
||||
// We are only interested in the margins.
|
||||
const AMBA_ID_LOW: u64 = 0xFE0;
|
||||
|
||||
@@ -10,8 +10,8 @@ use std::sync::{Arc, Barrier};
|
||||
use std::{io, result};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use vm_device::interrupt::InterruptSourceGroup;
|
||||
use vm_device::BusDevice;
|
||||
use vm_device::interrupt::InterruptSourceGroup;
|
||||
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
|
||||
use vmm_sys_util::errno::Result;
|
||||
|
||||
|
||||
@@ -13,8 +13,8 @@ use std::{io, result};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
use vm_device::interrupt::InterruptSourceGroup;
|
||||
use vm_device::BusDevice;
|
||||
use vm_device::interrupt::InterruptSourceGroup;
|
||||
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
|
||||
|
||||
use crate::{read_le_u32, write_le_u32};
|
||||
|
||||
@@ -24,6 +24,8 @@ pub mod gic;
|
||||
pub mod interrupt_controller;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub mod ioapic;
|
||||
#[cfg(feature = "ivshmem")]
|
||||
pub mod ivshmem;
|
||||
pub mod legacy;
|
||||
#[cfg(feature = "pvmemcontrol")]
|
||||
pub mod pvmemcontrol;
|
||||
@@ -33,7 +35,9 @@ pub mod pvpanic;
|
||||
pub mod tpm;
|
||||
|
||||
pub use self::acpi::{AcpiGedDevice, AcpiPmTimerDevice, AcpiShutdownDevice};
|
||||
pub use self::pvpanic::{PvPanicDevice, PVPANIC_DEVICE_MMIO_SIZE};
|
||||
#[cfg(feature = "ivshmem")]
|
||||
pub use self::ivshmem::IvshmemDevice;
|
||||
pub use self::pvpanic::{PVPANIC_DEVICE_MMIO_SIZE, PvPanicDevice};
|
||||
|
||||
bitflags! {
|
||||
pub struct AcpiNotificationFlags: u8 {
|
||||
|
||||
@@ -137,7 +137,8 @@ impl PvmemcontrolTransport {
|
||||
}
|
||||
|
||||
unsafe fn as_register(self) -> PvmemcontrolTransportRegister {
|
||||
self.payload.register
|
||||
// SAFETY: We access initialized data.
|
||||
unsafe { self.payload.register }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -519,7 +520,7 @@ impl PvmemcontrolBusDevice {
|
||||
ret_value: get_page_size().into(),
|
||||
arg0: MAJOR_VERSION.into(),
|
||||
arg1: MINOR_VERSION.into(),
|
||||
})
|
||||
});
|
||||
}
|
||||
FunctionCode::Dontneed => self.madvise(addr, length, libc::MADV_DONTNEED),
|
||||
FunctionCode::Remove => self.madvise(addr, length, libc::MADV_REMOVE),
|
||||
|
||||
@@ -9,9 +9,9 @@ use std::sync::{Arc, Barrier, Mutex};
|
||||
|
||||
use anyhow::anyhow;
|
||||
use pci::{
|
||||
BarReprogrammingParams, PciBarConfiguration, PciBarPrefetchable, PciBarRegionType,
|
||||
PciClassCode, PciConfiguration, PciDevice, PciDeviceError, PciHeaderType, PciSubclass,
|
||||
PCI_CONFIGURATION_ID,
|
||||
BarReprogrammingParams, PCI_CONFIGURATION_ID, PciBarConfiguration, PciBarPrefetchable,
|
||||
PciBarRegionType, PciClassCode, PciConfiguration, PciDevice, PciDeviceError, PciHeaderType,
|
||||
PciSubclass,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
@@ -68,8 +68,7 @@ impl PvPanicDevice {
|
||||
let pci_configuration_state =
|
||||
vm_migration::state_from_id(snapshot.as_ref(), PCI_CONFIGURATION_ID).map_err(|e| {
|
||||
PvPanicError::RetrievePciConfigurationState(anyhow!(
|
||||
"Failed to get PciConfigurationState from Snapshot: {}",
|
||||
e
|
||||
"Failed to get PciConfigurationState from Snapshot: {e}"
|
||||
))
|
||||
})?;
|
||||
|
||||
@@ -100,8 +99,7 @@ impl PvPanicDevice {
|
||||
.transpose()
|
||||
.map_err(|e| {
|
||||
PvPanicError::CreatePvPanicDevice(anyhow!(
|
||||
"Failed to get PvPanicDeviceState from Snapshot: {}",
|
||||
e
|
||||
"Failed to get PvPanicDeviceState from Snapshot: {e}"
|
||||
))
|
||||
})?;
|
||||
let events = if let Some(state) = state {
|
||||
@@ -148,7 +146,7 @@ impl BusDevice for PvPanicDevice {
|
||||
|
||||
fn write(&mut self, _base: u64, _offset: u64, data: &[u8]) -> Option<Arc<Barrier>> {
|
||||
let event = self.event_to_string(data[0]);
|
||||
info!("pvpanic got guest event {}", event);
|
||||
info!("pvpanic got guest event {event}");
|
||||
event!("guest", "panic", "event", &event);
|
||||
None
|
||||
}
|
||||
|
||||
@@ -12,8 +12,8 @@ use arch::aarch64::layout::{TPM_SIZE, TPM_START};
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use arch::x86_64::layout::{TPM_SIZE, TPM_START};
|
||||
use thiserror::Error;
|
||||
use tpm::emulator::{BackendCmd, Emulator};
|
||||
use tpm::TPM_CRB_BUFFER_MAX;
|
||||
use tpm::emulator::{BackendCmd, Emulator};
|
||||
use vm_device::BusDevice;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
@@ -221,7 +221,7 @@ pub struct Tpm {
|
||||
impl Tpm {
|
||||
pub fn new(path: String) -> Result<Self> {
|
||||
let emulator = Emulator::new(path)
|
||||
.map_err(|e| Error::Init(anyhow!("Failed while initializing tpm Emulator: {:?}", e)))?;
|
||||
.map_err(|e| Error::Init(anyhow!("Failed while initializing tpm Emulator: {e:?}")))?;
|
||||
let mut tpm = Tpm {
|
||||
emulator,
|
||||
regs: [0; TPM_CRB_R_MAX],
|
||||
@@ -331,8 +331,7 @@ impl Tpm {
|
||||
|
||||
if let Err(e) = self.emulator.startup_tpm(self.backend_buff_size) {
|
||||
return Err(Error::Init(anyhow!(
|
||||
"Failed while running Startup TPM. Error: {:?}",
|
||||
e
|
||||
"Failed while running Startup TPM. Error: {e:?}"
|
||||
)));
|
||||
}
|
||||
Ok(())
|
||||
@@ -458,10 +457,9 @@ impl BusDevice for Tpm {
|
||||
CRB_CTRL_CANCEL => {
|
||||
if v == CRB_CANCEL_INVOKE
|
||||
&& (self.regs[CRB_CTRL_START as usize] & CRB_START_INVOKE != 0)
|
||||
&& let Err(e) = self.emulator.cancel_cmd()
|
||||
{
|
||||
if let Err(e) = self.emulator.cancel_cmd() {
|
||||
error!("Failed to run cancel command. Error: {:?}", e);
|
||||
}
|
||||
error!("Failed to run cancel command. Error: {e:?}");
|
||||
}
|
||||
}
|
||||
CRB_CTRL_START => {
|
||||
@@ -482,10 +480,7 @@ impl BusDevice for Tpm {
|
||||
}
|
||||
}
|
||||
CRB_LOC_CTRL => {
|
||||
warn!(
|
||||
"CRB_LOC_CTRL locality to write = {:?} val = {:?}",
|
||||
locality, v
|
||||
);
|
||||
warn!("CRB_LOC_CTRL locality to write = {locality:?} val = {v:?}");
|
||||
match v {
|
||||
CRB_LOC_CTRL_RESET_ESTABLISHMENT_BIT => {}
|
||||
CRB_LOC_CTRL_RELINQUISH => {
|
||||
@@ -518,7 +513,7 @@ impl BusDevice for Tpm {
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
error!("Invalid value to write in CRB_LOC_CTRL {:#X} ", v);
|
||||
error!("Invalid value to write in CRB_LOC_CTRL {v:#X} ");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
14
docs/api.md
14
docs/api.md
@@ -56,13 +56,6 @@ or a fd with `--api-socket fd=...`.
|
||||
|
||||
```
|
||||
$ ./target/debug/cloud-hypervisor --api-socket path=/tmp/cloud-hypervisor.sock
|
||||
Cloud Hypervisor Guest
|
||||
API server: /tmp/cloud-hypervisor.sock
|
||||
vCPUs: 1
|
||||
Memory: 512 MB
|
||||
Kernel: None
|
||||
Kernel cmdline:
|
||||
Disk(s): None
|
||||
```
|
||||
|
||||
#### REST API Endpoints
|
||||
@@ -121,13 +114,6 @@ the REST API available at `/tmp/cloud-hypervisor.sock`:
|
||||
|
||||
```
|
||||
$ ./target/debug/cloud-hypervisor --api-socket /tmp/cloud-hypervisor.sock
|
||||
Cloud Hypervisor Guest
|
||||
API server: /tmp/cloud-hypervisor.sock
|
||||
vCPUs: 1
|
||||
Memory: 512 MB
|
||||
Kernel: None
|
||||
Kernel cmdline:
|
||||
Disk(s): None
|
||||
```
|
||||
|
||||
##### Create a Virtual Machine
|
||||
|
||||
77
docs/fw_cfg.md
Normal file
77
docs/fw_cfg.md
Normal file
@@ -0,0 +1,77 @@
|
||||
# Firmware Configuration (fw_cfg) Device
|
||||
|
||||
The `fw_cfg` device is a QEMU-compatible device that allows the hypervisor to pass configuration and data to the guest operating system. This is particularly useful for firmware to access information like ACPI tables, kernel images, initramfs, kernel command lines, and other arbitrary data blobs.
|
||||
|
||||
Cloud Hypervisor implements the `fw_cfg` device with DMA-enabled access.
|
||||
|
||||
## Purpose
|
||||
|
||||
The `fw_cfg` device serves as a generic information channel between the VMM and the guest. It can be used to:
|
||||
|
||||
* Load the kernel, initramfs, and kernel command line for direct kernel boot with firmware.
|
||||
* Provide ACPI tables to the guest firmware or OS.
|
||||
* Pass custom configuration files or data blobs (e.g., attestation data, SEV-SNP launch secrets) to the guest.
|
||||
* Supply an E820 memory map to the guest.
|
||||
|
||||
## Enabling `fw_cfg`
|
||||
|
||||
The `fw_cfg` device is enabled via the `fw_cfg` feature flag when building Cloud Hypervisor:
|
||||
|
||||
```bash
|
||||
cargo build --features fw_cfg
|
||||
```
|
||||
|
||||
## Guest Kernel Configuration
|
||||
|
||||
For the guest Linux kernel to recognize and use the `fw_cfg` device via sysfs, the following kernel configuration option must be enabled:
|
||||
|
||||
* `CONFIG_FW_CFG_SYSFS=y`
|
||||
|
||||
This option allows the kernel to expose `fw_cfg` entries under `/sys/firmware/qemu_fw_cfg/by_name/`.
|
||||
|
||||
## Command Line Options
|
||||
|
||||
The `fw_cfg` device is configured using the `--fw-cfg-config` command-line option.
|
||||
|
||||
**Parameters:**
|
||||
* `e820=on|off`: (Default: `on`) Whether to add an E820 memory map entry to `fw_cfg`.
|
||||
* `kernel=on|off`: (Default: `on`) Whether to add the kernel image (specified by `--kernel`) to `fw_cfg`.
|
||||
* `cmdline=on|off`: (Default: `on`) Whether to add the kernel command line (specified by `--cmdline`) to `fw_cfg`.
|
||||
* `initramfs=on|off`: (Default: `on`) Whether to add the initramfs image (specified by `--initramfs`) to `fw_cfg`.
|
||||
* `acpi_table=on|off`: (Default: `on`) Whether to add generated ACPI tables to `fw_cfg`.
|
||||
* `items=[... : ...]`: A list of custom key-value pairs to be exposed via `fw_cfg`.
|
||||
* `name=<guest_sysfs_path>`: The path under which the item will appear in the guest's sysfs (e.g., `opt/org.example/my-data`).
|
||||
* `file=<host_file_path>`: The path to the file on the host whose content will be provided to the guest for this item.
|
||||
|
||||
**Example Usage:**
|
||||
|
||||
1. **Direct kernel boot with custom `fw_cfg` entries:**
|
||||
|
||||
```bash
|
||||
cloud-hypervisor \
|
||||
--kernel /path/to/vmlinux \
|
||||
--cmdline "console=hvc0 root=/dev/vda1" \
|
||||
--disk path=/path/to/rootfs.img \
|
||||
--fw-cfg-config initramfs=off,items=[name=opt/org.mycorp/setup_info,file=/tmp/guest_setup.txt] \
|
||||
...
|
||||
```
|
||||
In the guest, `/tmp/guest_setup.txt` from the host will be accessible at `/sys/firmware/qemu_fw_cfg/by_name/opt/org.mycorp/setup_info/raw`.
|
||||
|
||||
2. **Disabling `fw_cfg` explicitly:**
|
||||
|
||||
```bash
|
||||
cloud-hypervisor \
|
||||
--fw-cfg-config disable \
|
||||
...
|
||||
```
|
||||
|
||||
## Accessing `fw_cfg` Items in the Guest
|
||||
|
||||
If `CONFIG_FW_CFG_SYSFS` is enabled in the guest kernel, items added to `fw_cfg` can be accessed via sysfs.
|
||||
|
||||
For example, an item added with `name=opt/org.example/my-data` will be available at:
|
||||
`/sys/firmware/qemu_fw_cfg/by_name/opt/org.example/my-data/raw`
|
||||
|
||||
The `raw` file contains the binary content of the host file provided.
|
||||
|
||||
Standard items like kernel, initramfs, cmdline, and ACPI tables also have predefined names (e.g., `etc/kernel`, `etc/cmdline`) if they are enabled to be passed via `fw_cfg`.
|
||||
@@ -1,54 +0,0 @@
|
||||
# Intel SGX
|
||||
|
||||
Intel® Software Guard Extensions (Intel® SGX) is an Intel technology designed
|
||||
to increase the security of application code and data. Cloud Hypervisor supports
|
||||
SGX virtualization through KVM. Because SGX is built on hardware features that
|
||||
cannot be emulated in software, virtualizing SGX requires support in KVM and in
|
||||
the host kernel. The required Linux and KVM changes can be found in Linux 5.13+.
|
||||
|
||||
Utilizing SGX in the guest requires a kernel/OS with SGX support, e.g. a kernel
|
||||
since release 5.11, see
|
||||
[here](https://www.intel.com/content/www/us/en/developer/tools/software-guard-extensions/linux-overview.html).
|
||||
Running Linux 5.13+ as the guest kernel allows nested virtualization of SGX.
|
||||
|
||||
For more information about SGX, please refer to the [SGX Homepage](https://www.intel.com/content/www/us/en/developer/tools/software-guard-extensions/linux-overview.html).
|
||||
|
||||
For more information about SGX SDK and how to test SGX, please refer to the
|
||||
following [instructions](https://github.com/intel/linux-sgx).
|
||||
|
||||
## Cloud Hypervisor support
|
||||
|
||||
Assuming the host exposes `/dev/sgx_vepc`, we can pass SGX enclaves through
|
||||
the guest.
|
||||
|
||||
In order to use SGX enclaves within a Cloud Hypervisor VM, we must define one
|
||||
or several Enclave Page Cache (EPC) sections. Here is an example of a VM being
|
||||
created with 2 EPC sections, the first one being 64MiB with pre-allocated
|
||||
memory, the second one being 32MiB with no pre-allocated memory.
|
||||
|
||||
```bash
|
||||
./cloud-hypervisor \
|
||||
--cpus boot=1 \
|
||||
--memory size=1G \
|
||||
--disk path=focal-server-cloudimg-amd64.raw \
|
||||
--kernel vmlinux \
|
||||
--cmdline "console=ttyS0 console=hvc0 root=/dev/vda1 rw" \
|
||||
--sgx-epc id=epc0,size=64M,prefault=on id=epc1,size=32M,prefault=off
|
||||
```
|
||||
|
||||
Once booted, and assuming your guest kernel contains the patches from the
|
||||
[KVM SGX Tree](https://github.com/intel/kvm-sgx), you can validate SGX devices
|
||||
have been correctly created under `/dev/sgx`:
|
||||
|
||||
```bash
|
||||
ls /dev/sgx*
|
||||
/dev/sgx_enclave /dev/sgx_provision /dev/sgx_vepc
|
||||
```
|
||||
|
||||
From this point, it is possible to run any SGX application from the guest, as
|
||||
it will access `/dev/sgx_enclave` device to create dedicated SGX enclaves.
|
||||
|
||||
Note: There is only one contiguous SGX EPC region, which contains all SGX EPC
|
||||
sections. This region is exposed through ACPI and marked as reserved through
|
||||
the e820 table. It is treated as yet another device, which means it should
|
||||
appear at the end of the guest address space.
|
||||
51
docs/ivshmem.md
Normal file
51
docs/ivshmem.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# Inter-VM shared memory device
|
||||
|
||||
The Inter-VM shared memory device (ivshmem) is designed to share a memory
|
||||
region between a guest and the host. In order for all guests to be able to
|
||||
pick up the shared memory area, it is modeled as a PCI device exposing said
|
||||
memory to the guest as a PCI BAR.
|
||||
|
||||
Device Specification is
|
||||
at https://www.qemu.org/docs/master/specs/ivshmem-spec.html.
|
||||
|
||||
Now we support setting a backend file to share data between host and guest.
|
||||
In other words, we only support ivshmem-plain and ivshmem-doorbell is not
|
||||
supported yet.
|
||||
|
||||
## Usage
|
||||
|
||||
`--ivshmem`, an optional argument, can be passed to enable ivshmem device.
|
||||
This argument takes a file as a `path` value and a file size as a `size` value.
|
||||
|
||||
```
|
||||
--ivshmem <ivshmem> device backend file "path=</path/to/a/file>,size=<file_size/must=2^n>";
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
Create a file with a size bigger than passed to `cloud-hypervisor`:
|
||||
|
||||
```
|
||||
truncate -s 1M /tmp/ivshmem.data
|
||||
```
|
||||
|
||||
Start application to mmap the file data to a memory region:
|
||||
|
||||
```
|
||||
./cloud-hypervisor \
|
||||
--api-socket /tmp/cloud-hypervisor.sock \
|
||||
--kernel vmlinux \
|
||||
--disk path=focal-server-cloudimg-amd64.raw \
|
||||
--cpus boot=4 \
|
||||
--memory size=1024M \
|
||||
--ivshmem path=/tmp/ivshmem.data,size=1M
|
||||
```
|
||||
|
||||
Insmod a ivshmem device driver to enable the device. The file data will be
|
||||
mmapped to the PCI `bar2` of ivshmem device,
|
||||
guest can r/w data by accessing this memory.
|
||||
|
||||
A simple example of ivshmem driver can get from:
|
||||
https://github.com/lisongqian/clh-linux/commits/ch-6.12.8-ivshmem
|
||||
|
||||
The host process can r/w this data by remmaping the `/tmp/ivshmem.data`.
|
||||
@@ -437,12 +437,11 @@ struct NumaConfig {
|
||||
cpus: Option<Vec<u8>>,
|
||||
distances: Option<Vec<NumaDistance>>,
|
||||
memory_zones: Option<Vec<String>>,
|
||||
sgx_epc_sections: Option<Vec<String>>,
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
--numa <numa> Settings related to a given NUMA node "guest_numa_id=<node_id>,cpus=<cpus_id>,distances=<list_of_distances_to_destination_nodes>,memory_zones=<list_of_memory_zones>,sgx_epc_sections=<list_of_sgx_epc_sections>"
|
||||
--numa <numa> Settings related to a given NUMA node "guest_numa_id=<node_id>,cpus=<cpus_id>,distances=<list_of_distances_to_destination_nodes>,memory_zones=<list_of_memory_zones>
|
||||
```
|
||||
|
||||
### `guest_numa_id`
|
||||
@@ -550,26 +549,6 @@ _Example_
|
||||
--numa guest_numa_id=0,memory_zones=[mem0,mem2] guest_numa_id=1,memory_zones=mem1
|
||||
```
|
||||
|
||||
### `sgx_epc_sections`
|
||||
|
||||
List of SGX EPC sections attached to the guest NUMA node identified by the
|
||||
`guest_numa_id` option. This allows for describing a list of SGX EPC sections
|
||||
which must be seen by the guest as belonging to the NUMA node `guest_numa_id`.
|
||||
|
||||
Multiple values can be provided to define the list. Each value is a string
|
||||
referring to an existing SGX EPC section identifier. Values are separated from
|
||||
each other with the `,` separator.
|
||||
|
||||
As soon as one tries to describe a list of values, `[` and `]` must be used to
|
||||
demarcate the list.
|
||||
|
||||
_Example_
|
||||
|
||||
```
|
||||
--sgx-epc id=epc0,size=32M id=epc1,size=64M id=epc2,size=32M
|
||||
--numa guest_numa_id=0,sgx_epc_sections=epc1 guest_numa_id=1,sgx_epc_sections=[epc0,epc2]
|
||||
```
|
||||
|
||||
### PCI bus
|
||||
|
||||
Cloud Hypervisor supports guests with one or more PCI segments. The default PCI segment always
|
||||
|
||||
@@ -110,4 +110,4 @@ from the restored VM.
|
||||
|
||||
## Limitations
|
||||
|
||||
VFIO devices and Intel SGX are out of scope.
|
||||
VFIO devices is out of scope.
|
||||
|
||||
134
docs/windows.md
134
docs/windows.md
@@ -4,10 +4,10 @@ Starting with the release version [0.10.0](https://github.com/cloud-hypervisor/c
|
||||
|
||||
__Requirements__
|
||||
|
||||
- Host with KVM enabled
|
||||
- Host with KVM enabled
|
||||
- [UEFI](uefi.md) capable Windows guest image with Virtio drivers integrated
|
||||
|
||||
Any modern Windows Server version is compatible. Cloud Hypervisor has been successfully tested with Windows Server 2019 and Windows Server Core 2004.
|
||||
Any modern Windows Server version is compatible, as well as Windows 11. Cloud Hypervisor has been successfully tested with Windows Server 2019, Windows Server Core 2004 and Windows 11 IoT Enterprise LTSC 2024.
|
||||
|
||||
At the current stage, only UEFI capable Windows images are supported. This implies the presence of the OVMF firmware during the Windows installation and in any subsequent usage. BIOS boot is not supported.
|
||||
|
||||
@@ -20,10 +20,15 @@ The subsequent sections will tell, in detail, how to prepare an appropriate Wind
|
||||
__Prerequisites__
|
||||
|
||||
- QEMU, version >=5.0.0 is recommended.
|
||||
- Windows installation ISO. Obtained through MSDN, Visual Studio subscription, evaluation center, etc.
|
||||
- Windows installation ISO. Obtained through MSDN, Visual Studio subscription, evaluation center, etc.
|
||||
- [VirtIO driver ISO](https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/stable-virtio/)
|
||||
- Please use the [VirtIO Windows 11 attestation file](https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/upstream-virtio/virtio-win11-attestation-0.1-258.zip)
|
||||
for Windows 11
|
||||
- Suitable firmware for Cloud Hypervisor (`CLOUDHV.fd`) and for QEMU (`OVMF.fd`)
|
||||
- With the suggested image size of 30G, there should be enough free disk space to hold the installation ISO and any other necessary files
|
||||
- With the suggested image size of 30G for Windows Server, there should be enough free disk space to hold the installation ISO and any other necessary files
|
||||
- For Windows 11, increasing this image size to 64GB is recommended (see [minimal requirements](https://support.microsoft.com/en-us/windows/windows-11-system-requirements-86c11283-ea52-4782-9efd-7674389a7ba3))
|
||||
- Windows 11 only: TPM 2.0 support
|
||||
- Windows 11 only: 2 or more cores
|
||||
|
||||
This step currently requires QEMU to install Windows onto the guest. QEMU is only used at the preparation stage, the resulting image is then fully functional with Cloud Hypervisor.
|
||||
|
||||
@@ -37,11 +42,13 @@ OVMF_DIR=./FV
|
||||
```
|
||||
|
||||
Create an empty image file, `raw` is supported.
|
||||
|
||||
```shell
|
||||
qemu-img create -f raw $IMG_FILE 30G
|
||||
```
|
||||
|
||||
Begin the Windows installation process under QEMU
|
||||
Begin the Windows installation process under QEMU for Windows Server:
|
||||
|
||||
```shell
|
||||
qemu-system-x86_64 \
|
||||
-machine q35,accel=kvm \
|
||||
@@ -57,13 +64,56 @@ qemu-system-x86_64 \
|
||||
-vga std
|
||||
```
|
||||
|
||||
Before the installation can proceed, point the Windows installation program to the VirtIO disk and install the necessary storage controller drivers. After that, the attached hard drive will become visible and the actual installation can commence.
|
||||
For Windows 11 you can use `swtpm` to fulfill the TPM 2.0 requirement:
|
||||
|
||||
After the installation has completed, proceed further to the configuration section. QEMU will be needed at least once more to enable the Windows Special Administration Console (SAC) and to possibly install extra device drivers.
|
||||
```shell
|
||||
# Create directory to store state
|
||||
mkdir -p /tmp/mytpm1
|
||||
# Start swtpm daemon for TPM 2.0 support
|
||||
swtpm socket \
|
||||
--tpm2 \
|
||||
--ctrl type=unixio,path=/tmp/swtpm-sock \
|
||||
--tpmstate dir=/tmp/mytpm1 \
|
||||
--flags startup-clear \
|
||||
--log level=20 \
|
||||
--log file=/tmp/swtpm.log \
|
||||
--daemon
|
||||
```
|
||||
|
||||
Begin the Windows 11 installation process under QEMU like this:
|
||||
|
||||
```shell
|
||||
qemu-system-x86_64 \
|
||||
-machine q35,accel=kvm \
|
||||
-cpu host \
|
||||
-m 4G \
|
||||
-bios ./$OVMF_DIR/OVMF.fd \
|
||||
-cdrom ./$WIN_ISO_FILE \
|
||||
-drive file=./$VIRTIO_ISO_FILE,index=0,media=cdrom \
|
||||
-drive if=none,id=root,file=./$IMG_FILE \
|
||||
-device virtio-blk-pci,drive=root,disable-legacy=on \
|
||||
-device virtio-net-pci,netdev=mynet0,disable-legacy=on \
|
||||
-netdev user,id=mynet0 \
|
||||
-vga std \
|
||||
-smp 4 \
|
||||
-chardev socket,id=chrtpm,path=/tmp/swtpm-sock \
|
||||
-tpmdev emulator,id=tpm0,chardev=chrtpm \
|
||||
-device tpm-tis,tpmdev=tpm0
|
||||
```
|
||||
|
||||
This command needs at least `-smp 2` (2 cores), as well as the last three lines (TPM 2.0), to support Windows 11 minimal requirements. Additionally, using `OVMF_CODE.fd` leads to the following error: `qemu: could not load PC BIOS '././FV/OVMF_CODE.fd'`. Switching to `OVMF.fd` is therefore necessary.
|
||||
|
||||
For more details about TPM specifically, please continue with the [TPM documentation](./tpm.md).
|
||||
|
||||
Before the installation can proceed, point the Windows installation program to the VirtIO disk and install the necessary storage controller drivers. For Windows 11 with the attestation drivers, you need to navigate to the `viostor` directory to be able to see and install it. After that, the attached hard drive will become visible and the actual installation can commence.
|
||||
|
||||
Do not install network drivers for Windows 11 just yet, if you don't want to be forced to log-in to/create a Microsoft account. Simply select `I don't have internet` for now.
|
||||
|
||||
After the installation has completed, proceed further to the [configuration section](#image-configuration). QEMU will be needed at least once more to enable/install the Windows Special Administration Console (SAC) and to possibly install extra device drivers.
|
||||
|
||||
## Image Usage
|
||||
|
||||
The basic command to boot a Windows image. The configuration section should be checked before executing it for the first time.
|
||||
The basic command to boot a Windows image is shown in the next code snippet. The [configuration section](#image-configuration), as well as the [Getting Started section](../README.md#2-getting-started) should be checked before executing it for the first time. Please especially read the documentation for giving the cloud-hypervisor binary the correct capabilities for it to set TAP interfaces up on the host, otherwise the command below will fail:
|
||||
|
||||
```shell
|
||||
cloud-hypervisor \
|
||||
@@ -85,19 +135,25 @@ In cases where the host processor supports address space > 39 bits, it might be
|
||||
|
||||
To daemonize the Cloud Hypervisor process, `nohup` can be used. Some STDIO redirections might need to be done. In a simple case it is sufficient to just redirect all the output to `/dev/null`.
|
||||
|
||||
Be aware, currently, running the Windows 11 VM on Cloud Hypervisor with TPM 2.0 was not proven successful: `thread 'vcpu0' panicked`. Running the VM without TPM is a valid option though. Therefore the command as shown above is also valid for a Windows 11 VM.
|
||||
|
||||
## Image Configuration
|
||||
|
||||
### Device Drivers
|
||||
|
||||
After the Windows installation has finished under QEMU, there might be still devices with no drivers installed. This might happen for example, when a device was not used during the installation. In particular it is important to ensure that the VirtIO network device is setup correctly because further steps for the configuration and the usage require network in most case.
|
||||
|
||||
Boot once more under QEMU and use the [Device Manager](https://support.microsoft.com/en-in/help/4028443/windows-10-update-drivers), to ensure all the device drivers, and especially the network card, are installed correctly. Also, as Cloud Hypervisor can introduce new devices, it is advisable to repeat the procedure while booted under Cloud Hypervisor, when the RDP access to the image is functional.
|
||||
Boot once more under QEMU and use the [Device Manager](https://support.microsoft.com/en-in/help/4028443/windows-10-update-drivers), to ensure all the device drivers, and especially the network card, are installed correctly. If not, right click on the unknown network device, choose `Update driver` and browse to the `NetKvm` directory on the CD.
|
||||
|
||||
Also, as Cloud Hypervisor can introduce new devices, it is advisable to repeat the procedure while booted under Cloud Hypervisor, when the [RDP](#remote-desktop-protocol-rdp-enablement) access to the image is functional.
|
||||
|
||||
### Windows Special Administration Console (SAC) enablement
|
||||
|
||||
SAC provides a text based console access to the Windows guest. As Cloud Hypervisor doesn't implement a VGA adaptor, SAC is an important instrument for the Windows guest management.
|
||||
|
||||
Boot the Windows image under QEMU and execute the below commands to permanently enable SAC
|
||||
Boot the Windows image under QEMU. For all non-server Windows versions, the SAC needs to be downloaded and enabled first in the `Optional features` menu of Windows.
|
||||
|
||||
Execute the below commands to permanently enable SAC. You might need admin privileges.
|
||||
|
||||
```cmd
|
||||
bcdedit /emssettings emsport:1 emsbaudrate:115200
|
||||
@@ -105,15 +161,14 @@ bcdedit /ems on
|
||||
bcdedit /bootems on
|
||||
```
|
||||
|
||||
Once SAC is enabled, the image can be booted under Cloud Hypervisor. The SAC prompt will show up
|
||||
Once SAC is enabled, the image can be booted under Cloud Hypervisor. The SAC prompt will show up
|
||||
|
||||
<pre>
|
||||
Computer is booting, SAC started and initialized.
|
||||
|
||||
Use the "ch -?" command for information about using channels.
|
||||
Use the "?" command for general help.
|
||||
|
||||
|
||||
Computer is booting, SAC started and initialized.
|
||||
|
||||
Use the "ch -?" command for information about using channels.
|
||||
Use the "?" command for general help.
|
||||
|
||||
SAC>
|
||||
</pre>
|
||||
|
||||
@@ -139,7 +194,7 @@ As the simplest option, using `--net tap=` in the Cloud Hypervisor command line
|
||||
|
||||
<pre>
|
||||
SAC>i 10 192.168.249.2 255.255.255.0 192.168.249.1
|
||||
</pre>
|
||||
</pre>
|
||||
|
||||
Where `10` is the device index as shown by the `i` command.
|
||||
|
||||
@@ -149,26 +204,38 @@ Additional steps are necessary to provide the guest with internet access.
|
||||
|
||||
- On the guest, add the DNS server either by using `netsh` or by opening `Network and Connectivity Center` and editing the adapter properties.
|
||||
- On the host, configure the traffic forwarding. Replace the `NET_DEV` with the name of your network device.
|
||||
|
||||
```shell
|
||||
NET_DEV=wlp3s0
|
||||
sysctl -w net.ipv4.ip_forward=1
|
||||
iptables -t nat -A POSTROUTING -o $NET_DEV -j MASQUERADE
|
||||
```
|
||||
|
||||
If needed, you can also allow ICMP from host to guest via the following command executed on the guest:
|
||||
|
||||
```shell
|
||||
netsh advfirewall firewall add rule name="Allow ICMPv4" protocol=icmpv4:8,any dir=in action=allow
|
||||
```
|
||||
|
||||
This will enable simple `ping` requests from your host to the guest.
|
||||
|
||||
### Remote Desktop Protocol (RDP) enablement
|
||||
|
||||
#### Using QEMU
|
||||
- Execute `SystemPropertiesRemote`
|
||||
- In the properties window, choose "Allow remote connections to this computer"
|
||||
- Click "Select Users" and add some user to the allow list
|
||||
|
||||
- Execute `SystemPropertiesRemote`
|
||||
- In the properties window, choose "Allow remote connections to this computer"
|
||||
- Click "Select Users" and add some user to the allow list
|
||||
|
||||
#### Using powershell
|
||||
|
||||
```powershell
|
||||
Set-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server\" -Name "fDenyTSConnections" -Value 0
|
||||
Enable-NetFirewallRule -DisplayGroup "Remote Desktop"
|
||||
Add-LocalGroupMember -Group "Remote Desktop Users" -Member someuser
|
||||
```
|
||||
|
||||
Administrators can always RDP, non administrator users have to be explicitly enabled.
|
||||
|
||||
Administrators can always RDP, non administrator users have to be explicitly enabled.
|
||||
|
||||
Once the configuration is set, RDP clients can connect to `192.168.249.2`.
|
||||
|
||||
@@ -182,7 +249,15 @@ Start-Service sshd
|
||||
Set-Service -Name sshd -StartupType ‘Automatic’
|
||||
```
|
||||
|
||||
This allows for SSH login from a remote machine, for example through the `administrator` user: `ssh administrator@192.168.249.2`. For a more detailed OpenSSH guide, please follow the MSDN article from the [links](#links) section.
|
||||
This allows for SSH login from a remote machine, for example through the `administrator` user: `ssh administrator@192.168.249.2`.
|
||||
|
||||
On Windows 11, opening the firewall was needed as well:
|
||||
|
||||
```powershell
|
||||
New-NetFirewallRule -Name sshd -DisplayName "OpenSSH Server" -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 22
|
||||
```
|
||||
|
||||
For a more detailed OpenSSH guide, please follow the MSDN article from the [links](#links) section.
|
||||
|
||||
## Hotplug capability
|
||||
|
||||
@@ -196,6 +271,8 @@ Disk hotplug and hot-remove are supported. After the device has been hotplugged,
|
||||
|
||||
## Debugging
|
||||
|
||||
Disclaimer: This chapter was not verified on Windows 11 yet. Proceed with care.
|
||||
|
||||
The Windows guest debugging process relies heavily on QEMU and [socat](http://www.dest-unreach.org/socat/). The procedure requires two Windows VMs:
|
||||
|
||||
- A debugger VM running under QEMU.
|
||||
@@ -203,7 +280,7 @@ The Windows guest debugging process relies heavily on QEMU and [socat](http://ww
|
||||
|
||||
The connection between both guests happens over TCP, whereby on the guest side it is automatically translated to a COM port. Because the VMs are connected through TCP, the debugging infrastructure can be distributed over the network. The serial port, while slowly transferring data, is common enough to support a wide range of cases and tools.
|
||||
|
||||
In this exercise, [WinDbg](https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/) is used. Any other debugger of choice with the ability to use serial connection can be used instead.
|
||||
In this exercise, [WinDbg](https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/) is used. Any other debugger of choice with the ability to use serial connection can be used instead.
|
||||
|
||||
### Debugger and Debuggee
|
||||
|
||||
@@ -220,7 +297,7 @@ qemu-system-x86_64 \
|
||||
-smp 1 \
|
||||
-m 4G \
|
||||
-cdrom ./$WIN_ISO_FILE \
|
||||
-drive file=./$VIRTIO_ISO_FILE,index=0,media=cdrom
|
||||
-drive file=./$VIRTIO_ISO_FILE,index=0,media=cdrom \
|
||||
-drive if=none,id=root,file=./windbg-disk.raw \
|
||||
-device virtio-blk-pci,drive=root,disable-legacy=on \
|
||||
-device virtio-net-pci,netdev=mynet0,disable-legacy=on \
|
||||
@@ -256,7 +333,7 @@ bcdedit /debug on
|
||||
bcdedit /bootdebug on
|
||||
```
|
||||
|
||||
##### Turn on boot manager debug
|
||||
##### Turn on boot manager debug
|
||||
|
||||
```cmd
|
||||
bcdedit /set {bootmgr} bootdebug on
|
||||
@@ -308,6 +385,7 @@ Once started, WinDbg will wait for an incoming connection which is going to be i
|
||||
##### Under QEMU
|
||||
|
||||
Essentially it would be the command like depicted in the guest preparation sections, with a few modifications:
|
||||
|
||||
```shell
|
||||
qemu-system-x86_64 \
|
||||
-machine q35,accel=kvm \
|
||||
@@ -315,7 +393,7 @@ qemu-system-x86_64 \
|
||||
-m 4G \
|
||||
-bios ./$OVMF_DIR/OVMF_CODE.fd \
|
||||
-cdrom ./$WIN_ISO_FILE \
|
||||
-drive file=./$VIRTIO_ISO_FILE,index=0,media=cdrom
|
||||
-drive file=./$VIRTIO_ISO_FILE,index=0,media=cdrom \
|
||||
-drive if=none,id=root,file=./$IMG_FILE \
|
||||
-device virtio-blk-pci,drive=root,disable-legacy=on \
|
||||
-device virtio-net-pci,netdev=mynet0,disable-legacy=on \
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
[package]
|
||||
authors = ["The Cloud Hypervisor Authors"]
|
||||
edition = "2021"
|
||||
edition.workspace = true
|
||||
name = "event_monitor"
|
||||
version = "0.1.0"
|
||||
|
||||
[dependencies]
|
||||
flume = "0.11.1"
|
||||
libc = "0.2.167"
|
||||
serde = { version = "1.0.208", features = ["derive", "rc"] }
|
||||
flume = { workspace = true }
|
||||
libc = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive", "rc"] }
|
||||
serde_json = { workspace = true }
|
||||
|
||||
499
fuzz/Cargo.lock
generated
499
fuzz/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -11,20 +11,21 @@ cargo-fuzz = true
|
||||
[features]
|
||||
default = ["mshv_emulator"]
|
||||
igvm = []
|
||||
ivshmem = []
|
||||
mshv_emulator = ["hypervisor/mshv_emulator"]
|
||||
pvmemcontrol = []
|
||||
|
||||
[dependencies]
|
||||
arbitrary = "1.4.1"
|
||||
arbitrary = "1.4.2"
|
||||
block = { path = "../block" }
|
||||
devices = { path = "../devices" }
|
||||
epoll = "4.3.3"
|
||||
hypervisor = { path = "../hypervisor", features = ["mshv_emulator"] }
|
||||
libc = "0.2.155"
|
||||
libfuzzer-sys = "0.4.7"
|
||||
linux-loader = { version = "0.13.0", features = ["bzimage", "elf", "pe"] }
|
||||
libc = "0.2.177"
|
||||
libfuzzer-sys = "0.4.10"
|
||||
linux-loader = { version = "0.13.1", features = ["bzimage", "elf", "pe"] }
|
||||
micro_http = { git = "https://github.com/firecracker-microvm/micro-http", branch = "main" }
|
||||
mshv-bindings = "0.5.2"
|
||||
mshv-bindings = "0.6.0"
|
||||
net_util = { path = "../net_util" }
|
||||
seccompiler = "0.5.0"
|
||||
virtio-devices = { path = "../virtio-devices" }
|
||||
|
||||
@@ -186,8 +186,6 @@ impl RequestHandler for StubApiRequestHandler {
|
||||
#[cfg(feature = "pvmemcontrol")]
|
||||
pvmemcontrol: None,
|
||||
iommu: false,
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
sgx_epc: None,
|
||||
numa: None,
|
||||
watchdog: false,
|
||||
gdb: false,
|
||||
@@ -197,6 +195,8 @@ impl RequestHandler for StubApiRequestHandler {
|
||||
preserved_fds: None,
|
||||
landlock_enable: false,
|
||||
landlock_rules: None,
|
||||
#[cfg(feature = "ivshmem")]
|
||||
ivshmem: None,
|
||||
}),
|
||||
state: VmState::Running,
|
||||
memory_actual_size: 0,
|
||||
@@ -221,7 +221,7 @@ impl RequestHandler for StubApiRequestHandler {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn vm_resize(&mut self, _: Option<u8>, _: Option<u64>, _: Option<u64>) -> Result<(), VmError> {
|
||||
fn vm_resize(&mut self, _: Option<u32>, _: Option<u64>, _: Option<u64>) -> Result<(), VmError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -12,8 +12,9 @@ use std::{ffi, io};
|
||||
use libc::{MAP_NORESERVE, MAP_PRIVATE, PROT_READ, PROT_WRITE};
|
||||
use libfuzzer_sys::{fuzz_target, Corpus};
|
||||
use seccompiler::SeccompAction;
|
||||
use virtio_devices::{Pmem, UserspaceMapping, VirtioDevice, VirtioInterrupt, VirtioInterruptType};
|
||||
use virtio_devices::{Pmem, VirtioDevice, VirtioInterrupt, VirtioInterruptType};
|
||||
use virtio_queue::{Queue, QueueT};
|
||||
use vm_device::UserspaceMapping;
|
||||
use vm_memory::bitmap::AtomicBitmap;
|
||||
use vm_memory::guest_memory::FileOffset;
|
||||
use vm_memory::{Bytes, GuestAddress, GuestMemoryAtomic, MmapRegion};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
authors = ["Microsoft Authors"]
|
||||
edition = "2021"
|
||||
edition.workspace = true
|
||||
license = "Apache-2.0 OR BSD-3-Clause"
|
||||
name = "hypervisor"
|
||||
version = "0.1.0"
|
||||
@@ -13,27 +13,27 @@ sev_snp = ["igvm", "igvm_defs"]
|
||||
tdx = []
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.94"
|
||||
anyhow = { workspace = true }
|
||||
arc-swap = "1.7.1"
|
||||
bitfield-struct = "0.10.1"
|
||||
byteorder = "1.5.0"
|
||||
cfg-if = "1.0.0"
|
||||
bitfield-struct = "0.12.0"
|
||||
byteorder = { workspace = true }
|
||||
cfg-if = { workspace = true }
|
||||
concat-idents = "1.1.5"
|
||||
igvm = { workspace = true, optional = true }
|
||||
igvm_defs = { workspace = true, optional = true }
|
||||
kvm-bindings = { workspace = true, optional = true, features = ["serde"] }
|
||||
kvm-ioctls = { workspace = true, optional = true }
|
||||
libc = "0.2.167"
|
||||
log = "0.4.22"
|
||||
libc = { workspace = true }
|
||||
log = { workspace = true }
|
||||
mshv-bindings = { workspace = true, features = [
|
||||
"fam-wrappers",
|
||||
"with-serde",
|
||||
], optional = true }
|
||||
mshv-ioctls = { workspace = true, optional = true }
|
||||
open-enum = "0.5.2"
|
||||
serde = { version = "1.0.208", features = ["derive", "rc"] }
|
||||
serde = { workspace = true, features = ["derive", "rc"] }
|
||||
serde_json = { workspace = true }
|
||||
serde_with = { version = "3.9.0", default-features = false, features = [
|
||||
serde_with = { workspace = true, default-features = false, features = [
|
||||
"macros",
|
||||
] }
|
||||
thiserror = { workspace = true }
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user