mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
Compare commits
260 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
21c17ccf49 | ||
|
|
cd4d6ded75 | ||
|
|
bf94d3dad9 | ||
|
|
02e3570bdd | ||
|
|
dae66ce493 | ||
|
|
90cee24f98 | ||
|
|
5a0b6f2d06 | ||
|
|
30166a4ea5 | ||
|
|
f93340d337 | ||
|
|
4c1f854ee9 | ||
|
|
ecab9f1b96 | ||
|
|
9a24680abd | ||
|
|
736813f90c | ||
|
|
b15459106c | ||
|
|
5fb078305a | ||
|
|
aac240d076 | ||
|
|
37d71fa038 | ||
|
|
8e52bf251b | ||
|
|
d75bd1675c | ||
|
|
d696cea024 | ||
|
|
7288031c0e | ||
|
|
82e8002fa0 | ||
|
|
a8d1411307 | ||
|
|
9569809d4c | ||
|
|
603b5e862c | ||
|
|
53092359b4 | ||
|
|
0ddb032cab | ||
|
|
bcc6ac68e9 | ||
|
|
4b4954ff86 | ||
|
|
5c5f33050c | ||
|
|
be5b14ef3a | ||
|
|
9dc923f379 | ||
|
|
f57b9b2359 | ||
|
|
6bda6541be | ||
|
|
4592f37bcf | ||
|
|
41a8dcd9ba | ||
|
|
8de24b5f08 | ||
|
|
0be7d1bd82 | ||
|
|
3191ff26e1 | ||
|
|
9979e562b2 | ||
|
|
70146ee7af | ||
|
|
3d049765bd | ||
|
|
f104bcdb6b | ||
|
|
265d21b0c4 | ||
|
|
562af123d5 | ||
|
|
f0dfa2a5ef | ||
|
|
7c8372452b | ||
|
|
f795081bf9 | ||
|
|
c940f6642f | ||
|
|
f56adb8a5a | ||
|
|
e6dd429a64 | ||
|
|
248e786363 | ||
|
|
f1ffd795e0 | ||
|
|
efad6578d1 | ||
|
|
9e2e85a48f | ||
|
|
f7edfefe42 | ||
|
|
801059c2d5 | ||
|
|
5ec1e66e22 | ||
|
|
01383538f3 | ||
|
|
5fb50edb55 | ||
|
|
658a7f9175 | ||
|
|
d8360ddc8e | ||
|
|
f10d0bb33d | ||
|
|
0d5c914bdd | ||
|
|
deaf660a52 | ||
|
|
6897c2a462 | ||
|
|
85556951a6 | ||
|
|
4abebc9e56 | ||
|
|
a91235dab1 | ||
|
|
dbb148b216 | ||
|
|
1861bc49e7 | ||
|
|
3d43245608 | ||
|
|
b163fea66a | ||
|
|
afcb2b285f | ||
|
|
c53781bf5f | ||
|
|
ed4af3a005 | ||
|
|
ac5fc69b2e | ||
|
|
0d884d3f50 | ||
|
|
8e5dd296de | ||
|
|
2f8edc182e | ||
|
|
41382879d3 | ||
|
|
e7cda177cc | ||
|
|
e3fb425615 | ||
|
|
6a86c157af | ||
|
|
0a07c96d17 | ||
|
|
a0b72dce22 | ||
|
|
d2b19bb969 | ||
|
|
dda89d7027 | ||
|
|
a1a018bd83 | ||
|
|
8c50450002 | ||
|
|
7443a9f69e | ||
|
|
28b54df01d | ||
|
|
4e93f85ab1 | ||
|
|
bcf23c1bf6 | ||
|
|
67fc9d990e | ||
|
|
2157f1a1f2 | ||
|
|
adb7cfd72c | ||
|
|
1f70e20c17 | ||
|
|
29e8a242b6 | ||
|
|
d32b2dc137 | ||
|
|
7de45b3a75 | ||
|
|
3709105043 | ||
|
|
2c1eccc6bc | ||
|
|
6f4b5253b4 | ||
|
|
492f24c632 | ||
|
|
47dff4f6bc | ||
|
|
b3f929432e | ||
|
|
e39174ac51 | ||
|
|
5f66a26b2e | ||
|
|
75cdfb0117 | ||
|
|
e6d31a3d81 | ||
|
|
94ed7c1745 | ||
|
|
aa67250049 | ||
|
|
4d79709b5e | ||
|
|
e993310c96 | ||
|
|
16fbab30b1 | ||
|
|
f5d2973546 | ||
|
|
2be304b392 | ||
|
|
969a3b57a3 | ||
|
|
021f450cdb | ||
|
|
0e21b56aea | ||
|
|
8be28f8438 | ||
|
|
12c7cc5e4f | ||
|
|
8f6a6a85e0 | ||
|
|
06b76972e2 | ||
|
|
199d2d05d8 | ||
|
|
42522a88c0 | ||
|
|
fdc19ad85e | ||
|
|
00f0b9e42c | ||
|
|
0ff8d1cb28 | ||
|
|
b6032bc492 | ||
|
|
95b8c6afdd | ||
|
|
f02745a7ed | ||
|
|
fed010fcd1 | ||
|
|
b4c62bf159 | ||
|
|
ea4f07d3bf | ||
|
|
7cb73e9e56 | ||
|
|
06390342a6 | ||
|
|
11d17fbf79 | ||
|
|
e160a17131 | ||
|
|
7364fbdc8e | ||
|
|
35b91f76af | ||
|
|
d1680b9ff9 | ||
|
|
c990f1bdaa | ||
|
|
e3e9e1c84c | ||
|
|
be495ec64a | ||
|
|
8ee26286ac | ||
|
|
935332bc42 | ||
|
|
e4fd066d82 | ||
|
|
a7fa3a0c86 | ||
|
|
b6c266c880 | ||
|
|
fc99e299c3 | ||
|
|
3d5f9a3a98 | ||
|
|
ad9034ed1d | ||
|
|
96f4e33897 | ||
|
|
6147c4c8b7 | ||
|
|
f9076cccfa | ||
|
|
9f046f02a2 | ||
|
|
3734a13cbf | ||
|
|
2707b0f72a | ||
|
|
176023156e | ||
|
|
5051feb0bd | ||
|
|
ec57aade15 | ||
|
|
02da2f2d36 | ||
|
|
62345cd6fd | ||
|
|
f826d92601 | ||
|
|
063aa4b7d5 | ||
|
|
d586c844de | ||
|
|
b3b51bd3a2 | ||
|
|
8bf284e713 | ||
|
|
e023efce3d | ||
|
|
bb7730e00f | ||
|
|
dd66eb834c | ||
|
|
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 |
3
.config/nextest.toml
Normal file
3
.config/nextest.toml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[profile.default]
|
||||||
|
# Don't let one individual test run for more than 10 minutes
|
||||||
|
slow-timeout = { period = "60s", terminate-after = 10 }
|
||||||
67
.github/dependabot.yml
vendored
67
.github/dependabot.yml
vendored
@@ -1,21 +1,68 @@
|
|||||||
version: 2
|
version: 2
|
||||||
updates:
|
updates:
|
||||||
- package-ecosystem: cargo
|
- package-ecosystem: cargo
|
||||||
directory: "/"
|
directories:
|
||||||
|
- "/"
|
||||||
|
- "/fuzz"
|
||||||
schedule:
|
schedule:
|
||||||
interval: daily
|
interval: weekly
|
||||||
open-pull-requests-limit: 1
|
|
||||||
allow:
|
allow:
|
||||||
- dependency-type: direct
|
- dependency-name: "acpi_tables"
|
||||||
- dependency-type: indirect
|
- dependency-name: "kvm-bindings"
|
||||||
|
- dependency-name: "kvm-ioctls"
|
||||||
|
- dependency-name: "linux-loader"
|
||||||
|
- dependency-name: "micro_http"
|
||||||
|
- dependency-name: "mshv-bindings"
|
||||||
|
- dependency-name: "mshv-ioctls"
|
||||||
|
- dependency-name: "seccompiler"
|
||||||
|
- dependency-name: "vfio-bindings"
|
||||||
|
- dependency-name: "vfio-ioctls"
|
||||||
|
- dependency-name: "vfio_user"
|
||||||
|
- dependency-name: "vhost"
|
||||||
|
- dependency-name: "vhost-user-backend"
|
||||||
|
- dependency-name: "virtio-bindings"
|
||||||
|
- dependency-name: "virtio-queue"
|
||||||
|
- dependency-name: "vm-fdt"
|
||||||
|
- dependency-name: "vm-memory"
|
||||||
|
- dependency-name: "vmm-sys-util"
|
||||||
|
groups:
|
||||||
|
rust-vmm:
|
||||||
|
patterns:
|
||||||
|
- "*"
|
||||||
- package-ecosystem: cargo
|
- package-ecosystem: cargo
|
||||||
directory: "/fuzz"
|
directories:
|
||||||
|
- "/"
|
||||||
|
- "/fuzz"
|
||||||
schedule:
|
schedule:
|
||||||
interval: daily
|
interval: weekly
|
||||||
open-pull-requests-limit: 1
|
|
||||||
allow:
|
allow:
|
||||||
- dependency-type: direct
|
- dependency-type: all
|
||||||
- dependency-type: indirect
|
ignore:
|
||||||
|
- dependency-name: "acpi_tables"
|
||||||
|
- dependency-name: "kvm-bindings"
|
||||||
|
- dependency-name: "kvm-ioctls"
|
||||||
|
- dependency-name: "linux-loader"
|
||||||
|
- dependency-name: "micro_http"
|
||||||
|
- dependency-name: "mshv-bindings"
|
||||||
|
- dependency-name: "mshv-ioctls"
|
||||||
|
- dependency-name: "seccompiler"
|
||||||
|
- dependency-name: "vfio-bindings"
|
||||||
|
- dependency-name: "vfio-ioctls"
|
||||||
|
- dependency-name: "vfio_user"
|
||||||
|
- dependency-name: "vhost"
|
||||||
|
- dependency-name: "vhost-user-backend"
|
||||||
|
- dependency-name: "virtio-bindings"
|
||||||
|
- dependency-name: "virtio-queue"
|
||||||
|
- dependency-name: "vm-fdt"
|
||||||
|
- dependency-name: "vm-memory"
|
||||||
|
- dependency-name: "vmm-sys-util"
|
||||||
|
groups:
|
||||||
|
non-rust-vmm:
|
||||||
|
patterns:
|
||||||
|
- "*"
|
||||||
|
# Makes it possible to have another config for the same directory.
|
||||||
|
# https://github.com/dependabot/dependabot-core/issues/1778#issuecomment-1988140219
|
||||||
|
target-branch: main
|
||||||
- package-ecosystem: github-actions
|
- package-ecosystem: github-actions
|
||||||
directory: "/"
|
directory: "/"
|
||||||
schedule:
|
schedule:
|
||||||
|
|||||||
2
.github/workflows/audit.yaml
vendored
2
.github/workflows/audit.yaml
vendored
@@ -10,7 +10,7 @@ jobs:
|
|||||||
name: Audit
|
name: Audit
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v6
|
||||||
- uses: actions-rust-lang/audit@v1
|
- uses: actions-rust-lang/audit@v1
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
28
.github/workflows/build.yaml
vendored
28
.github/workflows/build.yaml
vendored
@@ -15,13 +15,13 @@ jobs:
|
|||||||
- stable
|
- stable
|
||||||
- beta
|
- beta
|
||||||
- nightly
|
- nightly
|
||||||
- "1.88.0"
|
- "1.89.0"
|
||||||
target:
|
target:
|
||||||
- x86_64-unknown-linux-gnu
|
- x86_64-unknown-linux-gnu
|
||||||
- x86_64-unknown-linux-musl
|
- x86_64-unknown-linux-musl
|
||||||
steps:
|
steps:
|
||||||
- name: Code checkout
|
- name: Code checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
@@ -35,40 +35,40 @@ jobs:
|
|||||||
target: ${{ matrix.target }}
|
target: ${{ matrix.target }}
|
||||||
|
|
||||||
- name: Build (default features)
|
- name: Build (default features)
|
||||||
run: cargo rustc --locked --bin cloud-hypervisor -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
run: cargo build --locked --bin cloud-hypervisor
|
||||||
|
|
||||||
- name: Build (kvm)
|
- name: Build (kvm)
|
||||||
run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
run: cargo build --locked --bin cloud-hypervisor --no-default-features --features "kvm"
|
||||||
|
|
||||||
- name: Build (default features + tdx)
|
- name: Build (default features + tdx)
|
||||||
run: cargo rustc --locked --bin cloud-hypervisor --features "tdx" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
run: cargo build --locked --bin cloud-hypervisor --features "tdx"
|
||||||
|
|
||||||
- name: Build (default features + dbus_api)
|
- name: Build (default features + dbus_api)
|
||||||
run: cargo rustc --locked --bin cloud-hypervisor --features "dbus_api" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
run: cargo build --locked --bin cloud-hypervisor --features "dbus_api"
|
||||||
|
|
||||||
- name: Build (default features + guest_debug)
|
- name: Build (default features + guest_debug)
|
||||||
run: cargo rustc --locked --bin cloud-hypervisor --features "guest_debug" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
run: cargo build --locked --bin cloud-hypervisor --features "guest_debug"
|
||||||
|
|
||||||
- name: Build (default features + pvmemcontrol)
|
- name: Build (default features + pvmemcontrol)
|
||||||
run: cargo rustc --locked --bin cloud-hypervisor --features "pvmemcontrol" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
run: cargo build --locked --bin cloud-hypervisor --features "pvmemcontrol"
|
||||||
|
|
||||||
- name: Build (default features + fw_cfg)
|
- 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
|
run: cargo build --locked --bin cloud-hypervisor --features "fw_cfg"
|
||||||
|
|
||||||
- name: Build (default features + ivshmem)
|
- 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
|
run: cargo build --locked --bin cloud-hypervisor --features "ivshmem"
|
||||||
|
|
||||||
- name: Build (mshv)
|
- name: Build (mshv)
|
||||||
run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "mshv" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
run: cargo build --locked --bin cloud-hypervisor --no-default-features --features "mshv"
|
||||||
|
|
||||||
- name: Build (sev_snp)
|
- name: Build (sev_snp)
|
||||||
run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "sev_snp" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
run: cargo build --locked --bin cloud-hypervisor --no-default-features --features "sev_snp"
|
||||||
|
|
||||||
- name: Build (igvm)
|
- name: Build (igvm)
|
||||||
run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "igvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
run: cargo build --locked --bin cloud-hypervisor --no-default-features --features "igvm"
|
||||||
|
|
||||||
- name: Build (mshv + kvm)
|
- name: Build (mshv + kvm)
|
||||||
run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "mshv,kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
run: cargo build --locked --bin cloud-hypervisor --no-default-features --features "mshv,kvm"
|
||||||
|
|
||||||
- name: Release Build (default features)
|
- name: Release Build (default features)
|
||||||
run: cargo build --locked --all --release --target=${{ matrix.target }}
|
run: cargo build --locked --all --release --target=${{ matrix.target }}
|
||||||
|
|||||||
4
.github/workflows/dco.yaml
vendored
4
.github/workflows/dco.yaml
vendored
@@ -3,10 +3,10 @@ on: [pull_request, merge_group]
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
check:
|
check:
|
||||||
name: DCO Check ("Signed-Off-By")
|
name: DCO Check ("Signed-off-by")
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v6
|
||||||
- name: Set up Python 3.x
|
- name: Set up Python 3.x
|
||||||
uses: actions/setup-python@v6
|
uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
|
|||||||
4
.github/workflows/docker-image.yaml
vendored
4
.github/workflows/docker-image.yaml
vendored
@@ -18,7 +18,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Code checkout
|
- name: Code checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v3
|
||||||
@@ -41,7 +41,7 @@ jobs:
|
|||||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||||
# generate Docker tags based on the following events/attributes
|
# generate Docker tags based on the following events/attributes
|
||||||
tags: |
|
tags: |
|
||||||
type=raw,value=20250815-0
|
type=raw,value=20251114-0
|
||||||
type=sha
|
type=sha
|
||||||
|
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
|
|||||||
2
.github/workflows/formatting.yaml
vendored
2
.github/workflows/formatting.yaml
vendored
@@ -19,7 +19,7 @@ jobs:
|
|||||||
RUSTFLAGS: -D warnings
|
RUSTFLAGS: -D warnings
|
||||||
steps:
|
steps:
|
||||||
- name: Code checkout
|
- name: Code checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
- name: Install Rust toolchain (${{ matrix.rust }})
|
- name: Install Rust toolchain (${{ matrix.rust }})
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
with:
|
with:
|
||||||
|
|||||||
2
.github/workflows/fuzz-build.yaml
vendored
2
.github/workflows/fuzz-build.yaml
vendored
@@ -18,7 +18,7 @@ jobs:
|
|||||||
RUSTFLAGS: -D warnings
|
RUSTFLAGS: -D warnings
|
||||||
steps:
|
steps:
|
||||||
- name: Code checkout
|
- name: Code checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
- name: Install Rust toolchain (${{ matrix.rust }})
|
- name: Install Rust toolchain (${{ matrix.rust }})
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
with:
|
with:
|
||||||
|
|||||||
2
.github/workflows/gitlint.yaml
vendored
2
.github/workflows/gitlint.yaml
vendored
@@ -8,7 +8,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event.pull_request.head.sha }}
|
ref: ${{ github.event.pull_request.head.sha }}
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|||||||
2
.github/workflows/hadolint.yaml
vendored
2
.github/workflows/hadolint.yaml
vendored
@@ -13,7 +13,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Lint Dockerfile
|
- name: Lint Dockerfile
|
||||||
uses: hadolint/hadolint-action@master
|
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
|
- name: Fix workspace permissions
|
||||||
run: sudo chown -R runner:runner ${GITHUB_WORKSPACE}
|
run: sudo chown -R runner:runner ${GITHUB_WORKSPACE}
|
||||||
- name: Code checkout
|
- name: Code checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Run unit tests (musl)
|
- 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 }}
|
METRICS_PUBLISH_KEY: ${{ secrets.METRICS_PUBLISH_KEY }}
|
||||||
steps:
|
steps:
|
||||||
- name: Code checkout
|
- name: Code checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Run metrics tests
|
- name: Run metrics tests
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Code checkout
|
- name: Code checkout
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
if: ${{ github.event_name != 'pull_request' }}
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Run rate-limiter integration tests
|
- 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}
|
run: sudo chown -R runner:runner ${GITHUB_WORKSPACE}
|
||||||
- name: Code checkout
|
- name: Code checkout
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
if: ${{ github.event_name != 'pull_request' }}
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Run VFIO integration tests
|
- 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:
|
steps:
|
||||||
- name: Code checkout
|
- name: Code checkout
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
if: ${{ github.event_name != 'pull_request' }}
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Install Docker
|
- 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:
|
steps:
|
||||||
- name: Code checkout
|
- name: Code checkout
|
||||||
if: ${{ github.event_name != 'pull_request' || (matrix.runner == 'garm-jammy' && matrix.libc == 'gnu') }}
|
if: ${{ github.event_name != 'pull_request' || (matrix.runner == 'garm-jammy' && matrix.libc == 'gnu') }}
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Install Docker
|
- name: Install Docker
|
||||||
|
|||||||
4
.github/workflows/lychee.yaml
vendored
4
.github/workflows/lychee.yaml
vendored
@@ -6,13 +6,13 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Code checkout
|
- name: Code checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
# Fetch the entire history so git diff can compare against the base branch
|
# Fetch the entire history so git diff can compare against the base branch
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Get changed files in PR
|
- name: Get changed files in PR
|
||||||
id: changed-files
|
id: changed-files
|
||||||
uses: tj-actions/changed-files@v46 # Using a dedicated action for robustness
|
uses: tj-actions/changed-files@v47 # Using a dedicated action for robustness
|
||||||
with:
|
with:
|
||||||
# Compare the HEAD of the PR with the merge-base (where the PR branches off)
|
# Compare the HEAD of the PR with the merge-base (where the PR branches off)
|
||||||
base_sha: ${{ github.event.pull_request.base.sha }}
|
base_sha: ${{ github.event.pull_request.base.sha }}
|
||||||
|
|||||||
249
.github/workflows/mshv-infra.yaml
vendored
Normal file
249
.github/workflows/mshv-infra.yaml
vendored
Normal file
@@ -0,0 +1,249 @@
|
|||||||
|
name: MSHV Infra Setup
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
ARCH:
|
||||||
|
description: 'Architecture for the VM'
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
KEY:
|
||||||
|
description: 'SSH Key Name'
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
OS_DISK_SIZE:
|
||||||
|
description: 'OS Disk Size in GB'
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
RG:
|
||||||
|
description: 'Resource Group Name'
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
VM_SKU:
|
||||||
|
description: 'VM SKU'
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
secrets:
|
||||||
|
MI_CLIENT_ID:
|
||||||
|
required: true
|
||||||
|
RUNNER_RG:
|
||||||
|
required: true
|
||||||
|
STORAGE_ACCOUNT_PATHS:
|
||||||
|
required: true
|
||||||
|
ARCH_SOURCE_PATH:
|
||||||
|
required: true
|
||||||
|
USERNAME:
|
||||||
|
required: true
|
||||||
|
outputs:
|
||||||
|
RG_NAME:
|
||||||
|
description: 'Resource group of the VM'
|
||||||
|
value: ${{ jobs.infra-setup.outputs.RG_NAME }}
|
||||||
|
VM_NAME:
|
||||||
|
description: 'Name of the VM'
|
||||||
|
value: ${{ jobs.infra-setup.outputs.VM_NAME }}
|
||||||
|
PRIVATE_IP:
|
||||||
|
description: 'Private IP of the VM'
|
||||||
|
value: ${{ jobs.infra-setup.outputs.PRIVATE_IP }}
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
jobs:
|
||||||
|
infra-setup:
|
||||||
|
name: ${{ inputs.ARCH }} VM Provision
|
||||||
|
runs-on: mshv
|
||||||
|
continue-on-error: true
|
||||||
|
outputs:
|
||||||
|
RG_NAME: ${{ steps.rg-setup.outputs.RG_NAME }}
|
||||||
|
VM_NAME: ${{ steps.vm-setup.outputs.VM_NAME }}
|
||||||
|
PRIVATE_IP: ${{ steps.get-vm-ip.outputs.PRIVATE_IP }}
|
||||||
|
steps:
|
||||||
|
- name: Install & login to AZ CLI
|
||||||
|
env:
|
||||||
|
MI_CLIENT_ID: ${{ secrets.MI_CLIENT_ID }}
|
||||||
|
run: |
|
||||||
|
set -e
|
||||||
|
echo "Installing Azure CLI if not already installed"
|
||||||
|
if ! command -v az &>/dev/null; then
|
||||||
|
curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash
|
||||||
|
else
|
||||||
|
echo "Azure CLI already installed"
|
||||||
|
fi
|
||||||
|
az --version
|
||||||
|
echo "Logging into Azure CLI using Managed Identity"
|
||||||
|
az login --identity --client-id ${MI_CLIENT_ID}
|
||||||
|
|
||||||
|
- name: Get Location
|
||||||
|
id: get-location
|
||||||
|
env:
|
||||||
|
SKU: ${{ inputs.VM_SKU }}
|
||||||
|
STORAGE_ACCOUNT_PATHS: ${{ secrets.STORAGE_ACCOUNT_PATHS }}
|
||||||
|
run: |
|
||||||
|
set -e
|
||||||
|
# Extract vCPU count from SKU (e.g., "Standard_D2s_v3" => 2)
|
||||||
|
vcpu=$(echo "$SKU" | sed -n 's/^Standard_[A-Za-z]\+\([0-9]\+\).*/\1/p')
|
||||||
|
if [[ -z "$vcpu" ]]; then
|
||||||
|
echo "Cannot extract vCPU count from SKU: $SKU"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
SUPPORTED_LOCATIONS=$(echo "$STORAGE_ACCOUNT_PATHS" | jq -r 'to_entries[] | .key')
|
||||||
|
|
||||||
|
for location in $SUPPORTED_LOCATIONS; do
|
||||||
|
family=$(az vm list-skus --size "$SKU" --location "$location" --resource-type "virtualMachines" --query '[0].family' -o tsv)
|
||||||
|
if [[ -z "$family" ]]; then
|
||||||
|
echo "Cannot determine VM family for SKU: $SKU in $location"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
usage=$(az vm list-usage --location "$location" --query "[?name.value=='$family'] | [0]" -o json)
|
||||||
|
current=$(echo "$usage" | jq -r '.currentValue')
|
||||||
|
limit=$(echo "$usage" | jq -r '.limit')
|
||||||
|
|
||||||
|
if [[ $((limit - current)) -ge $vcpu ]]; then
|
||||||
|
echo "Sufficient quota found in $location"
|
||||||
|
echo "location=$location" >> "$GITHUB_OUTPUT"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "No location found with sufficient vCPU quota for SKU: $SKU"
|
||||||
|
exit 1
|
||||||
|
|
||||||
|
- name: Create Resource Group
|
||||||
|
id: rg-setup
|
||||||
|
env:
|
||||||
|
LOCATION: ${{ steps.get-location.outputs.location }}
|
||||||
|
RG: ${{ inputs.RG }}
|
||||||
|
STORAGE_ACCOUNT_PATHS: ${{ secrets.STORAGE_ACCOUNT_PATHS }}
|
||||||
|
run: |
|
||||||
|
set -e
|
||||||
|
echo "Creating Resource Group: $RG"
|
||||||
|
# Create the resource group
|
||||||
|
echo "Creating resource group in location: ${LOCATION}"
|
||||||
|
az group create --name ${RG} --location ${LOCATION}
|
||||||
|
echo "RG_NAME=${RG}" >> $GITHUB_OUTPUT
|
||||||
|
echo "Resource group created successfully."
|
||||||
|
|
||||||
|
- name: Generate SSH Key
|
||||||
|
id: generate-ssh-key
|
||||||
|
env:
|
||||||
|
KEY: ${{ inputs.KEY }}
|
||||||
|
run: |
|
||||||
|
set -e
|
||||||
|
echo "Generating SSH key: $KEY"
|
||||||
|
mkdir -p ~/.ssh
|
||||||
|
ssh-keygen -t rsa -b 4096 -f ~/.ssh/${KEY} -N ""
|
||||||
|
|
||||||
|
- name: Create VM
|
||||||
|
id: vm-setup
|
||||||
|
env:
|
||||||
|
KEY: ${{ inputs.KEY }}
|
||||||
|
LOCATION: ${{ steps.get-location.outputs.location }}
|
||||||
|
OS_DISK_SIZE: ${{ inputs.OS_DISK_SIZE }}
|
||||||
|
RG: ${{ inputs.RG }}
|
||||||
|
RUNNER_RG: ${{ secrets.RUNNER_RG }}
|
||||||
|
USERNAME: ${{ secrets.USERNAME }}
|
||||||
|
VM_SKU: ${{ inputs.VM_SKU }}
|
||||||
|
VM_IMAGE_NAME: ${{ inputs.ARCH }}_${{ steps.get-location.outputs.location }}_image
|
||||||
|
VM_NAME: ${{ inputs.ARCH }}_${{ steps.get-location.outputs.location }}_${{ github.run_id }}
|
||||||
|
run: |
|
||||||
|
set -e
|
||||||
|
echo "Creating $VM_SKU VM: $VM_NAME"
|
||||||
|
|
||||||
|
# Extract subnet ID from the runner VM
|
||||||
|
echo "Retrieving subnet ID..."
|
||||||
|
SUBNET_ID=$(az network vnet list --resource-group ${RUNNER_RG} --query "[?contains(location, '${LOCATION}')].{SUBNETS:subnets}" | jq -r ".[0].SUBNETS[0].id")
|
||||||
|
if [[ -z "${SUBNET_ID}" ]]; then
|
||||||
|
echo "ERROR: Failed to retrieve Subnet ID."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Extract image ID from the runner VM
|
||||||
|
echo "Retrieving image ID..."
|
||||||
|
IMAGE_ID=$(az image show --resource-group ${RUNNER_RG} --name ${VM_IMAGE_NAME} --query "id" -o tsv)
|
||||||
|
if [[ -z "${IMAGE_ID}" ]]; then
|
||||||
|
echo "ERROR: Failed to retrieve Image ID."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create VM
|
||||||
|
az vm create \
|
||||||
|
--resource-group ${RG} \
|
||||||
|
--name ${VM_NAME} \
|
||||||
|
--subnet ${SUBNET_ID} \
|
||||||
|
--size ${VM_SKU} \
|
||||||
|
--location ${LOCATION} \
|
||||||
|
--image ${IMAGE_ID} \
|
||||||
|
--os-disk-size-gb ${OS_DISK_SIZE} \
|
||||||
|
--public-ip-sku Standard \
|
||||||
|
--storage-sku Premium_LRS \
|
||||||
|
--public-ip-address "" \
|
||||||
|
--admin-username ${USERNAME} \
|
||||||
|
--ssh-key-value ~/.ssh/${KEY}.pub \
|
||||||
|
--security-type Standard \
|
||||||
|
--output json
|
||||||
|
|
||||||
|
az vm boot-diagnostics enable --name ${VM_NAME} --resource-group ${RG}
|
||||||
|
|
||||||
|
echo "VM_NAME=${VM_NAME}" >> $GITHUB_OUTPUT
|
||||||
|
echo "VM creation process completed successfully."
|
||||||
|
|
||||||
|
- name: Get VM Private IP
|
||||||
|
id: get-vm-ip
|
||||||
|
env:
|
||||||
|
RG: ${{ inputs.RG }}
|
||||||
|
VM_NAME: ${{ inputs.ARCH }}_${{ steps.get-location.outputs.location }}_${{ github.run_id }}
|
||||||
|
run: |
|
||||||
|
set -e
|
||||||
|
echo "Retrieving VM Private IP address..."
|
||||||
|
# Retrieve VM Private IP address
|
||||||
|
PRIVATE_IP=$(az vm show -g ${RG} -n ${VM_NAME} -d --query privateIps -o tsv)
|
||||||
|
if [[ -z "$PRIVATE_IP" ]]; then
|
||||||
|
echo "ERROR: Failed to retrieve private IP address."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "PRIVATE_IP=$PRIVATE_IP" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Wait for SSH availability
|
||||||
|
env:
|
||||||
|
KEY: ${{ inputs.KEY }}
|
||||||
|
PRIVATE_IP: ${{ steps.get-vm-ip.outputs.PRIVATE_IP }}
|
||||||
|
USERNAME: ${{ secrets.USERNAME }}
|
||||||
|
run: |
|
||||||
|
echo "Waiting for SSH to be accessible..."
|
||||||
|
timeout 120 bash -c 'until ssh -o StrictHostKeyChecking=no -i ~/.ssh/${KEY} ${USERNAME}@${PRIVATE_IP} "exit" 2>/dev/null; do sleep 5; done'
|
||||||
|
echo "VM is accessible!"
|
||||||
|
|
||||||
|
- name: Remove Old Host Key
|
||||||
|
env:
|
||||||
|
PRIVATE_IP: ${{ steps.get-vm-ip.outputs.PRIVATE_IP }}
|
||||||
|
run: |
|
||||||
|
set -e
|
||||||
|
echo "Removing the old host key"
|
||||||
|
ssh-keygen -R $PRIVATE_IP
|
||||||
|
|
||||||
|
- name: SSH into VM and Install Dependencies
|
||||||
|
env:
|
||||||
|
KEY: ${{ inputs.KEY }}
|
||||||
|
PRIVATE_IP: ${{ steps.get-vm-ip.outputs.PRIVATE_IP }}
|
||||||
|
USERNAME: ${{ secrets.USERNAME }}
|
||||||
|
run: |
|
||||||
|
set -e
|
||||||
|
ssh -i ~/.ssh/${KEY} -o StrictHostKeyChecking=no ${USERNAME}@${PRIVATE_IP} << EOF
|
||||||
|
set -e
|
||||||
|
echo "Logged in successfully."
|
||||||
|
echo "Installing dependencies..."
|
||||||
|
sudo tdnf install -y git moby-engine moby-cli clang llvm pkg-config make gcc glibc-devel
|
||||||
|
echo "Installing Rust..."
|
||||||
|
curl -sSf https://sh.rustup.rs | sh -s -- --default-toolchain stable --profile default -y
|
||||||
|
export PATH="\$HOME/.cargo/bin:\$PATH"
|
||||||
|
cargo --version
|
||||||
|
sudo mkdir -p /etc/docker/
|
||||||
|
echo '{"default-ulimits":{"nofile":{"Hard":65535,"Name":"nofile","Soft":65535}}}' | sudo tee /etc/docker/daemon.json
|
||||||
|
sudo systemctl stop docker
|
||||||
|
sudo systemctl enable docker.service
|
||||||
|
sudo systemctl enable containerd.service
|
||||||
|
sudo systemctl start docker
|
||||||
|
sudo groupadd -f docker
|
||||||
|
sudo usermod -a -G docker ${USERNAME}
|
||||||
|
sudo systemctl restart docker
|
||||||
|
EOF
|
||||||
131
.github/workflows/mshv-integration.yaml
vendored
Normal file
131
.github/workflows/mshv-integration.yaml
vendored
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
name: Cloud Hypervisor Tests (MSHV) (x86_64)
|
||||||
|
on: [pull_request_target, merge_group]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
infra-setup:
|
||||||
|
name: MSHV Infra Setup (x86_64)
|
||||||
|
uses: ./.github/workflows/mshv-infra.yaml
|
||||||
|
with:
|
||||||
|
ARCH: x86_64
|
||||||
|
KEY: azure_key_${{ github.run_id }}
|
||||||
|
OS_DISK_SIZE: 512
|
||||||
|
RG: MSHV-INTEGRATION-${{ github.run_id }}
|
||||||
|
VM_SKU: Standard_D16s_v5
|
||||||
|
secrets:
|
||||||
|
MI_CLIENT_ID: ${{ secrets.MSHV_MI_CLIENT_ID }}
|
||||||
|
RUNNER_RG: ${{ secrets.MSHV_RUNNER_RG }}
|
||||||
|
STORAGE_ACCOUNT_PATHS: ${{ secrets.MSHV_STORAGE_ACCOUNT_PATHS }}
|
||||||
|
ARCH_SOURCE_PATH: ${{ secrets.MSHV_X86_SOURCE_PATH }}
|
||||||
|
USERNAME: ${{ secrets.MSHV_USERNAME }}
|
||||||
|
|
||||||
|
run-tests:
|
||||||
|
name: Integration Tests (x86_64)
|
||||||
|
needs: infra-setup
|
||||||
|
if: ${{ always() && needs.infra-setup.result == 'success' }}
|
||||||
|
runs-on: mshv
|
||||||
|
continue-on-error: true
|
||||||
|
steps:
|
||||||
|
- name: Run integration tests
|
||||||
|
timeout-minutes: 60
|
||||||
|
env:
|
||||||
|
KEY: azure_key_${{ github.run_id }}
|
||||||
|
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||||
|
REPO_URL: https://github.com/cloud-hypervisor/cloud-hypervisor.git
|
||||||
|
REPO_DIR: cloud-hypervisor
|
||||||
|
PRIVATE_IP: ${{ needs.infra-setup.outputs.PRIVATE_IP }}
|
||||||
|
RG: MSHV-${{ github.run_id }}
|
||||||
|
USERNAME: ${{ secrets.MSHV_USERNAME }}
|
||||||
|
run: |
|
||||||
|
set -e
|
||||||
|
echo "Connecting to the VM via SSH..."
|
||||||
|
ssh -i ~/.ssh/${KEY} -o StrictHostKeyChecking=no ${USERNAME}@${PRIVATE_IP} << EOF
|
||||||
|
set -e
|
||||||
|
echo "Logged in successfully."
|
||||||
|
export PATH="\$HOME/.cargo/bin:\$PATH"
|
||||||
|
|
||||||
|
if [[ "${{ github.event_name }}" == "pull_request_target" ]]; then
|
||||||
|
git clone --depth 1 "$REPO_URL" "$REPO_DIR"
|
||||||
|
cd "$REPO_DIR"
|
||||||
|
git fetch origin pull/${{ github.event.pull_request.number }}/merge
|
||||||
|
git checkout FETCH_HEAD
|
||||||
|
else
|
||||||
|
git clone --depth 1 --single-branch --branch "${{ github.ref_name }}" "$REPO_URL" "$REPO_DIR"
|
||||||
|
cd "$REPO_DIR"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Loading VDPA kernel modules..."
|
||||||
|
sudo modprobe vdpa
|
||||||
|
sudo modprobe vhost_vdpa
|
||||||
|
sudo modprobe vdpa_sim
|
||||||
|
sudo modprobe vdpa_sim_blk
|
||||||
|
sudo modprobe vdpa_sim_net
|
||||||
|
|
||||||
|
echo "Creating VDPA devices..."
|
||||||
|
sudo vdpa dev add name vdpa-blk0 mgmtdev vdpasim_blk
|
||||||
|
sudo vdpa dev add name vdpa-blk1 mgmtdev vdpasim_blk
|
||||||
|
sudo vdpa dev add name vdpa-blk2 mgmtdev vdpasim_net
|
||||||
|
|
||||||
|
echo "Setting permissions..."
|
||||||
|
for i in 0 1 2; do
|
||||||
|
dev="/dev/vhost-vdpa-$i"
|
||||||
|
if [ -e "$dev" ]; then
|
||||||
|
sudo chown $USER:$USER "$dev"
|
||||||
|
sudo chmod 660 "$dev"
|
||||||
|
else
|
||||||
|
echo "Warning: Device $dev not found"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
sudo ./scripts/dev_cli.sh tests --hypervisor mshv --integration
|
||||||
|
EOF
|
||||||
|
|
||||||
|
- name: Dump dmesg
|
||||||
|
if: always()
|
||||||
|
continue-on-error: true
|
||||||
|
env:
|
||||||
|
KEY: azure_key_${{ github.run_id }}
|
||||||
|
PRIVATE_IP: ${{ needs.infra-setup.outputs.PRIVATE_IP }}
|
||||||
|
USERNAME: ${{ secrets.MSHV_USERNAME }}
|
||||||
|
run: |
|
||||||
|
ssh -i ~/.ssh/${KEY} -o StrictHostKeyChecking=no ${USERNAME}@${PRIVATE_IP} << EOF
|
||||||
|
sudo dmesg
|
||||||
|
EOF
|
||||||
|
|
||||||
|
- name: Dump serial console logs
|
||||||
|
if: always()
|
||||||
|
continue-on-error: true
|
||||||
|
env:
|
||||||
|
RG_NAME: ${{ needs.infra-setup.outputs.RG_NAME }}
|
||||||
|
VM_NAME: ${{ needs.infra-setup.outputs.VM_NAME }}
|
||||||
|
run: |
|
||||||
|
set -e
|
||||||
|
az vm boot-diagnostics get-boot-log --name "${VM_NAME}" --resource-group "${RG_NAME}" | jq -r
|
||||||
|
|
||||||
|
cleanup:
|
||||||
|
name: Cleanup
|
||||||
|
needs: run-tests
|
||||||
|
if: always()
|
||||||
|
runs-on: mshv
|
||||||
|
steps:
|
||||||
|
- name: Delete RG
|
||||||
|
env:
|
||||||
|
RG: MSHV-INTEGRATION-${{ github.run_id }}
|
||||||
|
run: |
|
||||||
|
if az group exists --name ${RG}; then
|
||||||
|
az group delete --name ${RG} --yes --no-wait
|
||||||
|
else
|
||||||
|
echo "Resource Group ${RG} does not exist. Skipping deletion."
|
||||||
|
fi
|
||||||
|
echo "Cleanup process completed."
|
||||||
|
|
||||||
|
- name: Delete SSH Key
|
||||||
|
env:
|
||||||
|
KEY: azure_key_${{ github.run_id }}
|
||||||
|
run: |
|
||||||
|
if [ -f ~/.ssh/${KEY} ]; then
|
||||||
|
rm -f ~/.ssh/${KEY} ~/.ssh/${KEY}.pub
|
||||||
|
echo "SSH key deleted successfully."
|
||||||
|
else
|
||||||
|
echo "SSH key does not exist. Skipping deletion."
|
||||||
|
fi
|
||||||
|
echo "Cleanup process completed."
|
||||||
2
.github/workflows/openapi.yaml
vendored
2
.github/workflows/openapi.yaml
vendored
@@ -6,7 +6,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container: openapitools/openapi-generator-cli
|
container: openapitools/openapi-generator-cli
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v6
|
||||||
- name: Validate OpenAPI
|
- name: Validate OpenAPI
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
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
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Code checkout
|
- name: Code checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
|
|||||||
8
.github/workflows/preview-riscv64-build.yaml
vendored
8
.github/workflows/preview-riscv64-build.yaml
vendored
@@ -13,18 +13,18 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Code checkout
|
- name: Code checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Install Rust toolchain
|
- name: Install Rust toolchain
|
||||||
run: /opt/scripts/exec-in-qemu.sh rustup default 1.88.0
|
run: /opt/scripts/exec-in-qemu.sh rustup default 1.89.0
|
||||||
|
|
||||||
- name: Build test (kvm)
|
- name: Build test (kvm)
|
||||||
run: /opt/scripts/exec-in-qemu.sh cargo rustc --locked --no-default-features --features "kvm"
|
run: /opt/scripts/exec-in-qemu.sh cargo build --locked --no-default-features --features "kvm" -p cloud-hypervisor
|
||||||
|
|
||||||
- name: Clippy test (kvm)
|
- name: Clippy test (kvm)
|
||||||
run: /opt/scripts/exec-in-qemu.sh cargo clippy --locked --no-default-features --features "kvm"
|
run: /opt/scripts/exec-in-qemu.sh cargo clippy --locked --no-default-features --features "kvm" -p cloud-hypervisor
|
||||||
|
|
||||||
- name: Check no files were modified
|
- name: Check no files were modified
|
||||||
run: test -z "$(git status --porcelain)"
|
run: test -z "$(git status --porcelain)"
|
||||||
|
|||||||
@@ -19,18 +19,18 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Code checkout
|
- name: Code checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Install Rust toolchain
|
- name: Install Rust toolchain
|
||||||
run: /opt/scripts/exec-in-qemu.sh rustup default 1.88.0
|
run: /opt/scripts/exec-in-qemu.sh rustup default 1.89.0
|
||||||
|
|
||||||
- name: Build ${{ matrix.module }} Module (kvm)
|
- 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
|
run: /opt/scripts/exec-in-qemu.sh cargo build --locked -p ${{ matrix.module }} --no-default-features --features "kvm"
|
||||||
|
|
||||||
- name: Clippy ${{ matrix.module }} Module (kvm)
|
- name: Clippy ${{ matrix.module }} Module (kvm)
|
||||||
run: /opt/scripts/exec-in-qemu.sh cargo clippy --locked -p ${{ matrix.module }} --no-default-features --features "kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
run: /opt/scripts/exec-in-qemu.sh cargo clippy --locked -p ${{ matrix.module }} --no-default-features --features "kvm" -- -D warnings
|
||||||
|
|
||||||
- name: Test ${{ matrix.module }} Module (kvm)
|
- name: Test ${{ matrix.module }} Module (kvm)
|
||||||
run: /opt/scripts/exec-in-qemu.sh cargo test --locked -p ${{ matrix.module }} --no-default-features --features "kvm"
|
run: /opt/scripts/exec-in-qemu.sh cargo test --locked -p ${{ matrix.module }} --no-default-features --features "kvm"
|
||||||
|
|||||||
30
.github/workflows/quality.yaml
vendored
30
.github/workflows/quality.yaml
vendored
@@ -29,7 +29,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Code checkout
|
- name: Code checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ jobs:
|
|||||||
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
|
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
|
||||||
toolchain: ${{ matrix.rust }}
|
toolchain: ${{ matrix.rust }}
|
||||||
target: ${{ matrix.target }}
|
target: ${{ matrix.target }}
|
||||||
args: --locked --all --all-targets --no-default-features --tests --examples --features "kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
args: --locked --all --all-targets --no-default-features --tests --examples --features "kvm" -- -D warnings
|
||||||
|
|
||||||
- name: Clippy (mshv)
|
- name: Clippy (mshv)
|
||||||
uses: houseabsolute/actions-rust-cross@v1
|
uses: houseabsolute/actions-rust-cross@v1
|
||||||
@@ -65,7 +65,7 @@ jobs:
|
|||||||
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
|
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
|
||||||
toolchain: ${{ matrix.rust }}
|
toolchain: ${{ matrix.rust }}
|
||||||
target: ${{ matrix.target }}
|
target: ${{ matrix.target }}
|
||||||
args: --locked --all --all-targets --no-default-features --tests --examples --features "mshv" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
args: --locked --all --all-targets --no-default-features --tests --examples --features "mshv" -- -D warnings
|
||||||
|
|
||||||
- name: Clippy (mshv + kvm)
|
- name: Clippy (mshv + kvm)
|
||||||
uses: houseabsolute/actions-rust-cross@v1
|
uses: houseabsolute/actions-rust-cross@v1
|
||||||
@@ -74,7 +74,7 @@ jobs:
|
|||||||
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
|
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
|
||||||
toolchain: ${{ matrix.rust }}
|
toolchain: ${{ matrix.rust }}
|
||||||
target: ${{ matrix.target }}
|
target: ${{ matrix.target }}
|
||||||
args: --locked --all --all-targets --no-default-features --tests --examples --features "mshv,kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
args: --locked --all --all-targets --no-default-features --tests --examples --features "mshv,kvm" -- -D warnings
|
||||||
|
|
||||||
- name: Clippy (default features)
|
- name: Clippy (default features)
|
||||||
uses: houseabsolute/actions-rust-cross@v1
|
uses: houseabsolute/actions-rust-cross@v1
|
||||||
@@ -83,7 +83,7 @@ jobs:
|
|||||||
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
|
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
|
||||||
toolchain: ${{ matrix.rust }}
|
toolchain: ${{ matrix.rust }}
|
||||||
target: ${{ matrix.target }}
|
target: ${{ matrix.target }}
|
||||||
args: --locked --all --all-targets --tests --examples -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
args: --locked --all --all-targets --tests --examples -- -D warnings
|
||||||
|
|
||||||
- name: Clippy (default features + guest_debug)
|
- name: Clippy (default features + guest_debug)
|
||||||
uses: houseabsolute/actions-rust-cross@v1
|
uses: houseabsolute/actions-rust-cross@v1
|
||||||
@@ -92,7 +92,7 @@ jobs:
|
|||||||
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
|
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
|
||||||
toolchain: ${{ matrix.rust }}
|
toolchain: ${{ matrix.rust }}
|
||||||
target: ${{ matrix.target }}
|
target: ${{ matrix.target }}
|
||||||
args: --locked --all --all-targets --tests --examples --features "guest_debug" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
args: --locked --all --all-targets --tests --examples --features "guest_debug" -- -D warnings
|
||||||
|
|
||||||
- name: Clippy (default features + pvmemcontrol)
|
- name: Clippy (default features + pvmemcontrol)
|
||||||
uses: houseabsolute/actions-rust-cross@v1
|
uses: houseabsolute/actions-rust-cross@v1
|
||||||
@@ -101,7 +101,7 @@ jobs:
|
|||||||
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
|
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
|
||||||
toolchain: ${{ matrix.rust }}
|
toolchain: ${{ matrix.rust }}
|
||||||
target: ${{ matrix.target }}
|
target: ${{ matrix.target }}
|
||||||
args: --locked --all --all-targets --tests --examples --features "pvmemcontrol" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
args: --locked --all --all-targets --tests --examples --features "pvmemcontrol" -- -D warnings
|
||||||
|
|
||||||
- name: Clippy (default features + tracing)
|
- name: Clippy (default features + tracing)
|
||||||
uses: houseabsolute/actions-rust-cross@v1
|
uses: houseabsolute/actions-rust-cross@v1
|
||||||
@@ -110,13 +110,13 @@ jobs:
|
|||||||
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
|
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
|
||||||
toolchain: ${{ matrix.rust }}
|
toolchain: ${{ matrix.rust }}
|
||||||
target: ${{ matrix.target }}
|
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
|
args: --locked --all --all-targets --tests --examples --features "tracing" -- -D warnings
|
||||||
- name: Clippy (default features + fw_cfg)
|
- name: Clippy (default features + fw_cfg)
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
|
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
|
||||||
command: clippy
|
command: clippy
|
||||||
args: --target=${{ matrix.target }} --locked --all --all-targets --tests --examples --features "fw_cfg" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
args: --target=${{ matrix.target }} --locked --all --all-targets --tests --examples --features "fw_cfg" -- -D warnings
|
||||||
|
|
||||||
- name: Clippy (default features + ivshmem)
|
- name: Clippy (default features + ivshmem)
|
||||||
uses: houseabsolute/actions-rust-cross@v1
|
uses: houseabsolute/actions-rust-cross@v1
|
||||||
@@ -125,7 +125,7 @@ jobs:
|
|||||||
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
|
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
|
||||||
toolchain: ${{ matrix.rust }}
|
toolchain: ${{ matrix.rust }}
|
||||||
target: ${{ matrix.target }}
|
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
|
args: --locked --all --all-targets --tests --examples --features "ivshmem" -- -D warnings
|
||||||
|
|
||||||
- name: Clippy (sev_snp)
|
- name: Clippy (sev_snp)
|
||||||
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
|
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
|
||||||
@@ -135,7 +135,7 @@ jobs:
|
|||||||
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
|
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
|
||||||
toolchain: ${{ matrix.rust }}
|
toolchain: ${{ matrix.rust }}
|
||||||
target: ${{ matrix.target }}
|
target: ${{ matrix.target }}
|
||||||
args: --locked --all --all-targets --no-default-features --tests --examples --features "sev_snp" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
args: --locked --all --all-targets --no-default-features --tests --examples --features "sev_snp" -- -D warnings
|
||||||
|
|
||||||
- name: Clippy (igvm)
|
- name: Clippy (igvm)
|
||||||
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
|
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
|
||||||
@@ -145,7 +145,7 @@ jobs:
|
|||||||
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
|
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
|
||||||
toolchain: ${{ matrix.rust }}
|
toolchain: ${{ matrix.rust }}
|
||||||
target: ${{ matrix.target }}
|
target: ${{ matrix.target }}
|
||||||
args: --locked --all --all-targets --no-default-features --tests --examples --features "igvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
args: --locked --all --all-targets --no-default-features --tests --examples --features "igvm" -- -D warnings
|
||||||
|
|
||||||
- name: Clippy (kvm + tdx)
|
- name: Clippy (kvm + tdx)
|
||||||
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
|
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
|
||||||
@@ -155,7 +155,7 @@ jobs:
|
|||||||
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
|
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
|
||||||
toolchain: ${{ matrix.rust }}
|
toolchain: ${{ matrix.rust }}
|
||||||
target: ${{ matrix.target }}
|
target: ${{ matrix.target }}
|
||||||
args: --locked --all --all-targets --no-default-features --tests --examples --features "tdx,kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
args: --locked --all --all-targets --no-default-features --tests --examples --features "tdx,kvm" -- -D warnings
|
||||||
|
|
||||||
- name: Check build did not modify any files
|
- name: Check build did not modify any files
|
||||||
run: test -z "$(git status --porcelain)"
|
run: test -z "$(git status --porcelain)"
|
||||||
@@ -165,6 +165,6 @@ jobs:
|
|||||||
name: Typos / Spellcheck
|
name: Typos / Spellcheck
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v6
|
||||||
# Executes "typos ."
|
# Executes "typos ."
|
||||||
- uses: crate-ci/typos@v1.36.2
|
- uses: crate-ci/typos@v1.40.0
|
||||||
|
|||||||
8
.github/workflows/release.yaml
vendored
8
.github/workflows/release.yaml
vendored
@@ -29,7 +29,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Code checkout
|
- name: Code checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
- name: Install musl-gcc
|
- name: Install musl-gcc
|
||||||
if: contains(matrix.platform.target, 'musl')
|
if: contains(matrix.platform.target, 'musl')
|
||||||
run: sudo apt install -y musl-tools
|
run: sudo apt install -y musl-tools
|
||||||
@@ -45,7 +45,7 @@ jobs:
|
|||||||
target: ${{ matrix.platform.target }}
|
target: ${{ matrix.platform.target }}
|
||||||
args: ${{ matrix.platform.args }}
|
args: ${{ matrix.platform.args }}
|
||||||
strip: true
|
strip: true
|
||||||
toolchain: "1.88.0"
|
toolchain: "1.89.0"
|
||||||
- name: Copy Release Binaries
|
- name: Copy Release Binaries
|
||||||
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
||||||
shell: bash
|
shell: bash
|
||||||
@@ -54,7 +54,7 @@ jobs:
|
|||||||
cp target/${{ matrix.platform.target }}/release/ch-remote ./${{ matrix.platform.name_ch_remote }}
|
cp target/${{ matrix.platform.target }}/release/ch-remote ./${{ matrix.platform.name_ch_remote }}
|
||||||
- name: Upload Release Artifacts
|
- name: Upload Release Artifacts
|
||||||
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: Artifacts for ${{ matrix.platform.target }}
|
name: Artifacts for ${{ matrix.platform.target }}
|
||||||
path: |
|
path: |
|
||||||
@@ -80,7 +80,7 @@ jobs:
|
|||||||
github.event_name == 'create' && github.event.ref_type == 'tag' &&
|
github.event_name == 'create' && github.event.ref_type == 'tag' &&
|
||||||
matrix.platform.target == 'x86_64-unknown-linux-gnu'
|
matrix.platform.target == 'x86_64-unknown-linux-gnu'
|
||||||
id: upload-release-cloud-hypervisor-vendored-sources
|
id: upload-release-cloud-hypervisor-vendored-sources
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v6
|
||||||
with:
|
with:
|
||||||
path: cloud-hypervisor-${{ github.event.ref }}.tar.xz
|
path: cloud-hypervisor-${{ github.event.ref }}.tar.xz
|
||||||
name: 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
|
name: REUSE Compliance Check
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v6
|
||||||
- name: REUSE Compliance Check
|
- 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
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
- name: Run the shell script checkers
|
- name: Run the shell script checkers
|
||||||
uses: luizm/action-sh-checker@master
|
uses: luizm/action-sh-checker@master
|
||||||
env:
|
env:
|
||||||
|
|||||||
2
.github/workflows/taplo.yaml
vendored
2
.github/workflows/taplo.yaml
vendored
@@ -10,7 +10,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Code checkout
|
- name: Code checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
- name: Install Rust toolchain
|
- name: Install Rust toolchain
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
- name: Install build dependencies
|
- name: Install build dependencies
|
||||||
|
|||||||
2
.gitlint
2
.gitlint
@@ -1,7 +1,7 @@
|
|||||||
[general]
|
[general]
|
||||||
extra-path=scripts/gitlint/rules
|
extra-path=scripts/gitlint/rules
|
||||||
regex-style-search=true
|
regex-style-search=true
|
||||||
ignore=body-max-line-length
|
ignore=body-max-line-length,body-hard-tab
|
||||||
|
|
||||||
[ignore-by-author-name]
|
[ignore-by-author-name]
|
||||||
regex=dependabot
|
regex=dependabot
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ CLASSE = "CLASSE"
|
|||||||
Dake = "Dake"
|
Dake = "Dake"
|
||||||
EXTINT = "EXTINT"
|
EXTINT = "EXTINT"
|
||||||
INOUT = "INOUT"
|
INOUT = "INOUT"
|
||||||
|
MSIS = "MSIS" # MSIs (Message Signaled Interrupt)
|
||||||
SME = "SME" # Secure Memory Encryption
|
SME = "SME" # Secure Memory Encryption
|
||||||
THR = "THR" # Transmitter Holding Register
|
THR = "THR" # Transmitter Holding Register
|
||||||
TRANSLATER = "TRANSLATER"
|
TRANSLATER = "TRANSLATER"
|
||||||
|
|||||||
@@ -14,11 +14,33 @@ License](https://opensource.org/licenses/Apache-2.0).
|
|||||||
## Coding Style
|
## Coding Style
|
||||||
|
|
||||||
We follow the [Rust Style](https://github.com/rust-lang/rust/tree/HEAD/src/doc/style-guide/src)
|
We follow the [Rust Style](https://github.com/rust-lang/rust/tree/HEAD/src/doc/style-guide/src)
|
||||||
convention and enforce it through the Continuous Integration (CI) process calling into `rustfmt`
|
convention and enforce it through the Continuous Integration (CI) process calling into `rustfmt`,
|
||||||
for each submitted Pull Request (PR).
|
`clippy`, and other well-known code quality tool of the ecosystem for each submitted Pull Request (PR).
|
||||||
|
|
||||||
## Basic Checks
|
## Basic Checks
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# We currently rely on nightly-only formatting features
|
||||||
|
cargo +nightly fmt --all
|
||||||
|
cargo check --all-targets --tests
|
||||||
|
cargo clippy --all-targets --tests
|
||||||
|
# Please note that this will not execute integration tests.
|
||||||
|
cargo test --all-targets --tests
|
||||||
|
|
||||||
|
# To lint your last three commits
|
||||||
|
gitlint --commits "HEAD~3..HEAD"
|
||||||
|
```
|
||||||
|
|
||||||
|
### \[Optional\] Run Integration Tests
|
||||||
|
|
||||||
|
_Caution: These tests are taking a long time to complete (40+ mins) and need special setup._
|
||||||
|
|
||||||
|
```sh
|
||||||
|
bash ./scripts/dev_cli.sh tests --integration -- --test-filter '<optionally filter test by name pattern>'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Setup Commit Hook
|
||||||
|
|
||||||
Please consider creating the following hook as `.git/hooks/pre-commit` in order
|
Please consider creating the following hook as `.git/hooks/pre-commit` in order
|
||||||
to ensure basic correctness of your code. You can extend this further if you
|
to ensure basic correctness of your code. You can extend this further if you
|
||||||
have specific features that you regularly develop against.
|
have specific features that you regularly develop against.
|
||||||
@@ -26,9 +48,9 @@ have specific features that you regularly develop against.
|
|||||||
```sh
|
```sh
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
cargo fmt -- --check || exit 1
|
cargo +nightly fmt --all -- --check || exit 1
|
||||||
cargo check --locked --all --all-targets --tests || exit 1
|
cargo check --locked --all-targets --tests || exit 1
|
||||||
cargo clippy --locked --all --all-targets --tests -- -D warnings || exit 1
|
cargo clippy --locked --all-targets --tests -- -D warnings || exit 1
|
||||||
```
|
```
|
||||||
|
|
||||||
You will need to `chmod +x .git/hooks/pre-commit` to have it run on every
|
You will need to `chmod +x .git/hooks/pre-commit` to have it run on every
|
||||||
|
|||||||
1000
Cargo.lock
generated
1000
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
141
Cargo.toml
141
Cargo.toml
@@ -1,21 +1,6 @@
|
|||||||
[package]
|
# Cloud Hypervisor Workspace
|
||||||
authors = ["The Cloud Hypervisor Authors"]
|
#
|
||||||
build = "build.rs"
|
# The main crate producing the binaries is in `./cloud-hypervisor`.
|
||||||
default-run = "cloud-hypervisor"
|
|
||||||
description = "Open source Virtual Machine Monitor (VMM) that runs on top of KVM & MSHV"
|
|
||||||
edition = "2024"
|
|
||||||
homepage = "https://github.com/cloud-hypervisor/cloud-hypervisor"
|
|
||||||
license = "Apache-2.0 AND BSD-3-Clause"
|
|
||||||
name = "cloud-hypervisor"
|
|
||||||
version = "48.0.0"
|
|
||||||
# Minimum buildable version:
|
|
||||||
# Keep in sync with version in .github/workflows/build.yaml
|
|
||||||
# Policy on MSRV (see #4318):
|
|
||||||
# Can only be bumped if satisfying any of the following:
|
|
||||||
# a.) A dependency requires it,
|
|
||||||
# b.) If we want to use a new feature and that MSRV is at least 6 months old,
|
|
||||||
# c.) There is a security issue that is addressed by the toolchain update.
|
|
||||||
rust-version = "1.88.0"
|
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
codegen-units = 1
|
codegen-units = 1
|
||||||
@@ -28,59 +13,12 @@ debug = true
|
|||||||
inherits = "release"
|
inherits = "release"
|
||||||
strip = false
|
strip = false
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
anyhow = { workspace = true }
|
|
||||||
api_client = { path = "api_client" }
|
|
||||||
clap = { workspace = true, features = ["string"] }
|
|
||||||
dhat = { workspace = true, optional = true }
|
|
||||||
env_logger = { workspace = true }
|
|
||||||
epoll = { workspace = true }
|
|
||||||
event_monitor = { path = "event_monitor" }
|
|
||||||
hypervisor = { path = "hypervisor" }
|
|
||||||
libc = { workspace = true }
|
|
||||||
log = { workspace = true, features = ["std"] }
|
|
||||||
option_parser = { path = "option_parser" }
|
|
||||||
seccompiler = { workspace = true }
|
|
||||||
serde_json = { workspace = true }
|
|
||||||
signal-hook = { workspace = true }
|
|
||||||
thiserror = { workspace = true }
|
|
||||||
tpm = { path = "tpm" }
|
|
||||||
tracer = { path = "tracer" }
|
|
||||||
vm-memory = { workspace = true }
|
|
||||||
vmm = { path = "vmm" }
|
|
||||||
vmm-sys-util = { workspace = true }
|
|
||||||
zbus = { version = "5.7.1", optional = true }
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
dirs = { workspace = true }
|
|
||||||
net_util = { path = "net_util" }
|
|
||||||
serde_json = { workspace = true }
|
|
||||||
test_infra = { path = "test_infra" }
|
|
||||||
wait-timeout = { workspace = true }
|
|
||||||
|
|
||||||
# Please adjust `vmm::feature_list()` accordingly when changing the
|
|
||||||
# feature list below
|
|
||||||
[features]
|
|
||||||
dbus_api = ["vmm/dbus_api", "zbus"]
|
|
||||||
default = ["io_uring", "kvm"]
|
|
||||||
dhat-heap = ["dhat", "vmm/dhat-heap"] # For heap profiling
|
|
||||||
fw_cfg = ["vmm/fw_cfg"]
|
|
||||||
guest_debug = ["vmm/guest_debug"]
|
|
||||||
igvm = ["mshv", "vmm/igvm"]
|
|
||||||
io_uring = ["vmm/io_uring"]
|
|
||||||
ivshmem = ["vmm/ivshmem"]
|
|
||||||
kvm = ["vmm/kvm"]
|
|
||||||
mshv = ["vmm/mshv"]
|
|
||||||
pvmemcontrol = ["vmm/pvmemcontrol"]
|
|
||||||
sev_snp = ["igvm", "mshv", "vmm/sev_snp"]
|
|
||||||
tdx = ["vmm/tdx"]
|
|
||||||
tracing = ["tracer/tracing", "vmm/tracing"]
|
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
"api_client",
|
"api_client",
|
||||||
"arch",
|
"arch",
|
||||||
"block",
|
"block",
|
||||||
|
"cloud-hypervisor",
|
||||||
"devices",
|
"devices",
|
||||||
"event_monitor",
|
"event_monitor",
|
||||||
"hypervisor",
|
"hypervisor",
|
||||||
@@ -103,16 +41,16 @@ members = [
|
|||||||
"vmm",
|
"vmm",
|
||||||
]
|
]
|
||||||
package.edition = "2024"
|
package.edition = "2024"
|
||||||
|
resolver = "3"
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
# rust-vmm crates
|
# rust-vmm crates
|
||||||
acpi_tables = { git = "https://github.com/rust-vmm/acpi_tables", branch = "main" }
|
acpi_tables = { git = "https://github.com/rust-vmm/acpi_tables", branch = "main" }
|
||||||
kvm-bindings = "0.12.0"
|
kvm-bindings = "0.12.1"
|
||||||
kvm-ioctls = "0.22.0"
|
kvm-ioctls = "0.22.1"
|
||||||
# TODO: update to 0.13.1+
|
linux-loader = "0.13.1"
|
||||||
linux-loader = { git = "https://github.com/rust-vmm/linux-loader", branch = "main" }
|
mshv-bindings = "0.6.5"
|
||||||
mshv-bindings = "0.6.0"
|
mshv-ioctls = "0.6.5"
|
||||||
mshv-ioctls = "0.6.0"
|
|
||||||
seccompiler = "0.5.0"
|
seccompiler = "0.5.0"
|
||||||
vfio-bindings = { version = "0.6.0", default-features = false }
|
vfio-bindings = { version = "0.6.0", default-features = false }
|
||||||
vfio-ioctls = { version = "0.5.1", default-features = false }
|
vfio-ioctls = { version = "0.5.1", default-features = false }
|
||||||
@@ -121,7 +59,7 @@ vhost = { version = "0.14.0", default-features = false }
|
|||||||
vhost-user-backend = { version = "0.20.0", default-features = false }
|
vhost-user-backend = { version = "0.20.0", default-features = false }
|
||||||
virtio-bindings = "0.2.6"
|
virtio-bindings = "0.2.6"
|
||||||
virtio-queue = "0.16.0"
|
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"
|
vm-memory = "0.16.1"
|
||||||
vmm-sys-util = "0.14.0"
|
vmm-sys-util = "0.14.0"
|
||||||
|
|
||||||
@@ -131,25 +69,54 @@ igvm = { git = "https://github.com/microsoft/igvm", branch = "main" }
|
|||||||
igvm_defs = { git = "https://github.com/microsoft/igvm", branch = "main" }
|
igvm_defs = { git = "https://github.com/microsoft/igvm", branch = "main" }
|
||||||
|
|
||||||
# serde crates
|
# serde crates
|
||||||
serde = "1.0.208"
|
serde = "1.0.228"
|
||||||
serde_json = "1.0.143"
|
serde_json = "1.0.145"
|
||||||
serde_with = { version = "3.14.0", default-features = false }
|
serde_with = { version = "3.16.1", default-features = false }
|
||||||
|
|
||||||
# other crates
|
# other crates
|
||||||
anyhow = "1.0.98"
|
anyhow = "1.0.99"
|
||||||
bitflags = "2.9.4"
|
bitflags = "2.10.0"
|
||||||
byteorder = "1.5.0"
|
byteorder = "1.5.0"
|
||||||
cfg-if = "1.0.0"
|
cfg-if = "1.0.4"
|
||||||
clap = "4.5.47"
|
clap = "4.5.53"
|
||||||
dhat = "0.3.3"
|
dhat = "0.3.3"
|
||||||
dirs = "6.0.0"
|
dirs = "6.0.0"
|
||||||
env_logger = "0.11.8"
|
env_logger = "0.11.8"
|
||||||
epoll = "4.3.3"
|
epoll = "4.4.0"
|
||||||
flume = "0.11.1"
|
flume = "0.12.0"
|
||||||
libc = "0.2.167"
|
itertools = "0.14.0"
|
||||||
log = "0.4.22"
|
libc = "0.2.178"
|
||||||
|
log = "0.4.29"
|
||||||
signal-hook = "0.3.18"
|
signal-hook = "0.3.18"
|
||||||
thiserror = "2.0.12"
|
thiserror = "2.0.17"
|
||||||
uuid = { version = "1.18.1" }
|
uuid = { version = "1.19.0" }
|
||||||
wait-timeout = "0.2.1"
|
wait-timeout = "0.2.1"
|
||||||
zerocopy = { version = "0.8.26", default-features = false }
|
zerocopy = { version = "0.8.31", default-features = false }
|
||||||
|
|
||||||
|
[workspace.lints.clippy]
|
||||||
|
# Any clippy lint (group) in alphabetical order:
|
||||||
|
# https://rust-lang.github.io/rust-clippy/master/index.html
|
||||||
|
|
||||||
|
# Groups
|
||||||
|
all = "deny" # shorthand for the other groups but here for compleness
|
||||||
|
complexity = "deny"
|
||||||
|
correctness = "deny"
|
||||||
|
perf = "deny"
|
||||||
|
style = "deny"
|
||||||
|
suspicious = "deny"
|
||||||
|
|
||||||
|
# Individual Lints
|
||||||
|
assertions_on_result_states = "deny"
|
||||||
|
if_not_else = "deny"
|
||||||
|
manual_string_new = "deny"
|
||||||
|
map_unwrap_or = "deny"
|
||||||
|
needless_pass_by_value = "deny"
|
||||||
|
redundant_else = "deny"
|
||||||
|
semicolon_if_nothing_returned = "deny"
|
||||||
|
undocumented_unsafe_blocks = "deny"
|
||||||
|
uninlined_format_args = "deny"
|
||||||
|
unnecessary_semicolon = "deny"
|
||||||
|
|
||||||
|
[workspace.lints.rust]
|
||||||
|
# `level = warn` is irrelevant here but mandatory for rustc/cargo
|
||||||
|
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(devcli_testenv)'] }
|
||||||
|
|||||||
@@ -7,3 +7,6 @@ version = "0.1.0"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
vmm-sys-util = { workspace = true }
|
vmm-sys-util = { workspace = true }
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ pub fn simple_api_full_command_with_fds_and_response<T: Read + Write + ScmSocket
|
|||||||
method: &str,
|
method: &str,
|
||||||
full_command: &str,
|
full_command: &str,
|
||||||
request_body: Option<&str>,
|
request_body: Option<&str>,
|
||||||
request_fds: Vec<RawFd>,
|
request_fds: &[RawFd],
|
||||||
) -> Result<Option<String>, Error> {
|
) -> Result<Option<String>, Error> {
|
||||||
socket
|
socket
|
||||||
.send_with_fds(
|
.send_with_fds(
|
||||||
@@ -150,7 +150,7 @@ pub fn simple_api_full_command_with_fds_and_response<T: Read + Write + ScmSocket
|
|||||||
"{method} /api/v1/{full_command} HTTP/1.1\r\nHost: localhost\r\nAccept: */*\r\n"
|
"{method} /api/v1/{full_command} HTTP/1.1\r\nHost: localhost\r\nAccept: */*\r\n"
|
||||||
)
|
)
|
||||||
.as_bytes()],
|
.as_bytes()],
|
||||||
&request_fds,
|
request_fds,
|
||||||
)
|
)
|
||||||
.map_err(Error::SocketSendFds)?;
|
.map_err(Error::SocketSendFds)?;
|
||||||
|
|
||||||
@@ -178,7 +178,7 @@ pub fn simple_api_full_command_with_fds<T: Read + Write + ScmSocket>(
|
|||||||
method: &str,
|
method: &str,
|
||||||
full_command: &str,
|
full_command: &str,
|
||||||
request_body: Option<&str>,
|
request_body: Option<&str>,
|
||||||
request_fds: Vec<RawFd>,
|
request_fds: &[RawFd],
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let response = simple_api_full_command_with_fds_and_response(
|
let response = simple_api_full_command_with_fds_and_response(
|
||||||
socket,
|
socket,
|
||||||
@@ -188,8 +188,8 @@ pub fn simple_api_full_command_with_fds<T: Read + Write + ScmSocket>(
|
|||||||
request_fds,
|
request_fds,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
if response.is_some() {
|
if let Some(response) = response {
|
||||||
println!("{}", response.unwrap());
|
println!("{response}");
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -201,7 +201,7 @@ pub fn simple_api_full_command<T: Read + Write + ScmSocket>(
|
|||||||
full_command: &str,
|
full_command: &str,
|
||||||
request_body: Option<&str>,
|
request_body: Option<&str>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
simple_api_full_command_with_fds(socket, method, full_command, request_body, Vec::new())
|
simple_api_full_command_with_fds(socket, method, full_command, request_body, &[])
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn simple_api_full_command_and_response<T: Read + Write + ScmSocket>(
|
pub fn simple_api_full_command_and_response<T: Read + Write + ScmSocket>(
|
||||||
@@ -210,13 +210,7 @@ pub fn simple_api_full_command_and_response<T: Read + Write + ScmSocket>(
|
|||||||
full_command: &str,
|
full_command: &str,
|
||||||
request_body: Option<&str>,
|
request_body: Option<&str>,
|
||||||
) -> Result<Option<String>, Error> {
|
) -> Result<Option<String>, Error> {
|
||||||
simple_api_full_command_with_fds_and_response(
|
simple_api_full_command_with_fds_and_response(socket, method, full_command, request_body, &[])
|
||||||
socket,
|
|
||||||
method,
|
|
||||||
full_command,
|
|
||||||
request_body,
|
|
||||||
Vec::new(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn simple_api_command_with_fds<T: Read + Write + ScmSocket>(
|
pub fn simple_api_command_with_fds<T: Read + Write + ScmSocket>(
|
||||||
@@ -224,7 +218,7 @@ pub fn simple_api_command_with_fds<T: Read + Write + ScmSocket>(
|
|||||||
method: &str,
|
method: &str,
|
||||||
c: &str,
|
c: &str,
|
||||||
request_body: Option<&str>,
|
request_body: Option<&str>,
|
||||||
request_fds: Vec<RawFd>,
|
request_fds: &[RawFd],
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
// Create the full VM command. For VMM commands, use
|
// Create the full VM command. For VMM commands, use
|
||||||
// simple_api_full_command().
|
// simple_api_full_command().
|
||||||
@@ -239,5 +233,5 @@ pub fn simple_api_command<T: Read + Write + ScmSocket>(
|
|||||||
c: &str,
|
c: &str,
|
||||||
request_body: Option<&str>,
|
request_body: Option<&str>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
simple_api_command_with_fds(socket, method, c, request_body, Vec::new())
|
simple_api_command_with_fds(socket, method, c, request_body, &[])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,3 +27,6 @@ vmm-sys-util = { workspace = true, features = ["with-serde"] }
|
|||||||
[target.'cfg(any(target_arch = "aarch64", target_arch = "riscv64"))'.dependencies]
|
[target.'cfg(any(target_arch = "aarch64", target_arch = "riscv64"))'.dependencies]
|
||||||
fdt_parser = { version = "0.1.5", package = "fdt" }
|
fdt_parser = { version = "0.1.5", package = "fdt" }
|
||||||
vm-fdt = { workspace = true }
|
vm-fdt = { workspace = true }
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|||||||
@@ -19,14 +19,16 @@ use hypervisor::arch::aarch64::regs::{
|
|||||||
AARCH64_ARCH_TIMER_HYP_IRQ, AARCH64_ARCH_TIMER_PHYS_NONSECURE_IRQ,
|
AARCH64_ARCH_TIMER_HYP_IRQ, AARCH64_ARCH_TIMER_PHYS_NONSECURE_IRQ,
|
||||||
AARCH64_ARCH_TIMER_PHYS_SECURE_IRQ, AARCH64_ARCH_TIMER_VIRT_IRQ, AARCH64_PMU_IRQ,
|
AARCH64_ARCH_TIMER_PHYS_SECURE_IRQ, AARCH64_ARCH_TIMER_VIRT_IRQ, AARCH64_PMU_IRQ,
|
||||||
};
|
};
|
||||||
|
use log::{debug, warn};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use vm_fdt::{FdtWriter, FdtWriterResult};
|
use vm_fdt::{FdtWriter, FdtWriterResult};
|
||||||
use vm_memory::{Address, Bytes, GuestMemory, GuestMemoryError, GuestMemoryRegion};
|
use vm_memory::{Address, Bytes, GuestMemory, GuestMemoryError, GuestMemoryRegion};
|
||||||
|
|
||||||
use super::super::{DeviceType, GuestMemoryMmap, InitramfsConfig};
|
use super::super::{DeviceType, GuestMemoryMmap, InitramfsConfig};
|
||||||
use super::layout::{
|
use super::layout::{
|
||||||
GIC_V2M_COMPATIBLE, IRQ_BASE, MEM_32BIT_DEVICES_SIZE, MEM_32BIT_DEVICES_START, MEM_PCI_IO_SIZE,
|
GIC_V2M_COMPATIBLE, GICV2M_SPI_BASE, GICV2M_SPI_NUM, IRQ_BASE, MEM_32BIT_DEVICES_SIZE,
|
||||||
MEM_PCI_IO_START, PCI_HIGH_BASE, PCI_MMIO_CONFIG_SIZE_PER_SEGMENT, SPI_BASE, SPI_NUM,
|
MEM_32BIT_DEVICES_START, MEM_PCI_IO_SIZE, MEM_PCI_IO_START, PCI_HIGH_BASE,
|
||||||
|
PCI_MMIO_CONFIG_SIZE_PER_SEGMENT,
|
||||||
};
|
};
|
||||||
use crate::{NumaNodes, PciSpaceInfo};
|
use crate::{NumaNodes, PciSpaceInfo};
|
||||||
|
|
||||||
@@ -86,6 +88,7 @@ pub enum Error {
|
|||||||
}
|
}
|
||||||
type Result<T> = result::Result<T, Error>;
|
type Result<T> = result::Result<T, Error>;
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
pub enum CacheLevel {
|
pub enum CacheLevel {
|
||||||
/// L1 data cache
|
/// L1 data cache
|
||||||
L1D = 0,
|
L1D = 0,
|
||||||
@@ -109,9 +112,7 @@ pub fn get_cache_size(cache_level: CacheLevel) -> u32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let file_path = Path::new(&file_directory);
|
let file_path = Path::new(&file_directory);
|
||||||
if !file_path.exists() {
|
if file_path.exists() {
|
||||||
0
|
|
||||||
} else {
|
|
||||||
let src = fs::read_to_string(file_directory).expect("File not exists or file corrupted.");
|
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"
|
// The content of the file is as simple as a size, like: "32K"
|
||||||
let src = src.trim();
|
let src = src.trim();
|
||||||
@@ -125,6 +126,8 @@ pub fn get_cache_size(cache_level: CacheLevel) -> u32 {
|
|||||||
"G" => 1024u32.pow(3),
|
"G" => 1024u32.pow(3),
|
||||||
_ => 1,
|
_ => 1,
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,11 +143,11 @@ pub fn get_cache_coherency_line_size(cache_level: CacheLevel) -> u32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let file_path = Path::new(&file_directory);
|
let file_path = Path::new(&file_directory);
|
||||||
if !file_path.exists() {
|
if file_path.exists() {
|
||||||
0
|
|
||||||
} else {
|
|
||||||
let src = fs::read_to_string(file_directory).expect("File not exists or file corrupted.");
|
let src = fs::read_to_string(file_directory).expect("File not exists or file corrupted.");
|
||||||
src.trim().parse::<u32>().unwrap()
|
src.trim().parse::<u32>().unwrap()
|
||||||
|
} else {
|
||||||
|
0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,11 +163,11 @@ pub fn get_cache_number_of_sets(cache_level: CacheLevel) -> u32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let file_path = Path::new(&file_directory);
|
let file_path = Path::new(&file_directory);
|
||||||
if !file_path.exists() {
|
if file_path.exists() {
|
||||||
0
|
|
||||||
} else {
|
|
||||||
let src = fs::read_to_string(file_directory).expect("File not exists or file corrupted.");
|
let src = fs::read_to_string(file_directory).expect("File not exists or file corrupted.");
|
||||||
src.trim().parse::<u32>().unwrap()
|
src.trim().parse::<u32>().unwrap()
|
||||||
|
} else {
|
||||||
|
0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,9 +188,7 @@ pub fn get_cache_shared(cache_level: CacheLevel) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let file_path = Path::new(&file_directory);
|
let file_path = Path::new(&file_directory);
|
||||||
if !file_path.exists() {
|
if file_path.exists() {
|
||||||
result = false;
|
|
||||||
} else {
|
|
||||||
let src = fs::read_to_string(file_directory).expect("File not exists or file corrupted.");
|
let src = fs::read_to_string(file_directory).expect("File not exists or file corrupted.");
|
||||||
let src = src.trim();
|
let src = src.trim();
|
||||||
if src.is_empty() {
|
if src.is_empty() {
|
||||||
@@ -195,6 +196,8 @@ pub fn get_cache_shared(cache_level: CacheLevel) -> bool {
|
|||||||
} else {
|
} else {
|
||||||
result = src.contains('-') || src.contains(',');
|
result = src.contains('-') || src.contains(',');
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
result = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
result
|
result
|
||||||
@@ -205,7 +208,7 @@ pub fn get_cache_shared(cache_level: CacheLevel) -> bool {
|
|||||||
pub fn create_fdt<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHasher>(
|
pub fn create_fdt<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHasher>(
|
||||||
guest_mem: &GuestMemoryMmap,
|
guest_mem: &GuestMemoryMmap,
|
||||||
cmdline: &str,
|
cmdline: &str,
|
||||||
vcpu_mpidr: Vec<u64>,
|
vcpu_mpidr: &[u64],
|
||||||
vcpu_topology: Option<(u16, u16, u16, u16)>,
|
vcpu_topology: Option<(u16, u16, u16, u16)>,
|
||||||
device_info: &HashMap<(DeviceType, String), T, S>,
|
device_info: &HashMap<(DeviceType, String), T, S>,
|
||||||
gic_device: &Arc<Mutex<dyn Vgic>>,
|
gic_device: &Arc<Mutex<dyn Vgic>>,
|
||||||
@@ -232,7 +235,7 @@ pub fn create_fdt<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHash
|
|||||||
// This is not mandatory but we use it to point the root node to the node
|
// This is not mandatory but we use it to point the root node to the node
|
||||||
// containing description of the interrupt controller for this VM.
|
// containing description of the interrupt controller for this VM.
|
||||||
fdt.property_u32("interrupt-parent", GIC_PHANDLE)?;
|
fdt.property_u32("interrupt-parent", GIC_PHANDLE)?;
|
||||||
create_cpu_nodes(&mut fdt, &vcpu_mpidr, vcpu_topology, numa_nodes)?;
|
create_cpu_nodes(&mut fdt, vcpu_mpidr, vcpu_topology, numa_nodes)?;
|
||||||
create_memory_node(&mut fdt, guest_mem, numa_nodes)?;
|
create_memory_node(&mut fdt, guest_mem, numa_nodes)?;
|
||||||
create_chosen_node(&mut fdt, cmdline, initrd)?;
|
create_chosen_node(&mut fdt, cmdline, initrd)?;
|
||||||
create_gic_node(&mut fdt, gic_device)?;
|
create_gic_node(&mut fdt, gic_device)?;
|
||||||
@@ -256,10 +259,10 @@ pub fn create_fdt<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHash
|
|||||||
Ok(fdt_final)
|
Ok(fdt_final)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_fdt_to_memory(fdt_final: Vec<u8>, guest_mem: &GuestMemoryMmap) -> Result<()> {
|
pub fn write_fdt_to_memory(fdt_final: &[u8], guest_mem: &GuestMemoryMmap) -> Result<()> {
|
||||||
// Write FDT to memory.
|
// Write FDT to memory.
|
||||||
guest_mem
|
guest_mem
|
||||||
.write_slice(fdt_final.as_slice(), super::layout::FDT_START)
|
.write_slice(fdt_final, super::layout::FDT_START)
|
||||||
.map_err(Error::WriteFdtToMemory)?;
|
.map_err(Error::WriteFdtToMemory)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -310,9 +313,7 @@ fn create_cpu_nodes(
|
|||||||
|
|
||||||
let cache_path = Path::new("/sys/devices/system/cpu/cpu0/cache");
|
let cache_path = Path::new("/sys/devices/system/cpu/cpu0/cache");
|
||||||
let cache_exist: bool = cache_path.exists();
|
let cache_exist: bool = cache_path.exists();
|
||||||
if !cache_exist {
|
if cache_exist {
|
||||||
warn!("cache sysfs system does not exist.");
|
|
||||||
} else {
|
|
||||||
// L1 Data Cache Info.
|
// L1 Data Cache Info.
|
||||||
l1_d_cache_size = get_cache_size(CacheLevel::L1D);
|
l1_d_cache_size = get_cache_size(CacheLevel::L1D);
|
||||||
l1_d_cache_line_size = get_cache_coherency_line_size(CacheLevel::L1D);
|
l1_d_cache_line_size = get_cache_coherency_line_size(CacheLevel::L1D);
|
||||||
@@ -340,6 +341,8 @@ fn create_cpu_nodes(
|
|||||||
if l3_cache_size != 0 {
|
if l3_cache_size != 0 {
|
||||||
l3_cache_shared = get_cache_shared(CacheLevel::L3);
|
l3_cache_shared = get_cache_shared(CacheLevel::L3);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
warn!("cache sysfs system does not exist.");
|
||||||
}
|
}
|
||||||
|
|
||||||
for (cpu_id, mpidr) in vcpu_mpidr.iter().enumerate().take(num_cpus) {
|
for (cpu_id, mpidr) in vcpu_mpidr.iter().enumerate().take(num_cpus) {
|
||||||
@@ -666,8 +669,8 @@ fn create_gic_node(fdt: &mut FdtWriter, gic_device: &Arc<Mutex<dyn Vgic>>) -> Fd
|
|||||||
fdt.property_array_u64("reg", &msi_reg_prop)?;
|
fdt.property_array_u64("reg", &msi_reg_prop)?;
|
||||||
|
|
||||||
if msi_compatibility == GIC_V2M_COMPATIBLE {
|
if msi_compatibility == GIC_V2M_COMPATIBLE {
|
||||||
fdt.property_u32("arm,msi-base-spi", SPI_BASE)?;
|
fdt.property_u32("arm,msi-base-spi", GICV2M_SPI_BASE)?;
|
||||||
fdt.property_u32("arm,msi-num-spis", SPI_NUM)?;
|
fdt.property_u32("arm,msi-num-spis", GICV2M_SPI_NUM)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
fdt.end_node(msic_node)?;
|
fdt.end_node(msic_node)?;
|
||||||
@@ -1149,7 +1152,7 @@ fn print_node(node: fdt_parser::node::FdtNode<'_, '_>, n_spaces: usize) {
|
|||||||
array,
|
array,
|
||||||
indent = (n_spaces + 2)
|
indent = (n_spaces + 2)
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print children nodes if there is any
|
// Print children nodes if there is any
|
||||||
|
|||||||
@@ -139,11 +139,11 @@ pub const IRQ_BASE: u32 = 32;
|
|||||||
/// Number of supported interrupts
|
/// Number of supported interrupts
|
||||||
pub const IRQ_NUM: u32 = 256;
|
pub const IRQ_NUM: u32 = 256;
|
||||||
|
|
||||||
/// Base SPI interrupt number
|
/// Base SPI interrupt number for the GICv2M MSI frame
|
||||||
pub const SPI_BASE: u32 = 32;
|
pub const GICV2M_SPI_BASE: u32 = 128;
|
||||||
|
|
||||||
/// Total number of SPIs
|
/// Total number of SPIs for the GICv2M MSI frame
|
||||||
pub const SPI_NUM: u32 = 64;
|
pub const GICV2M_SPI_NUM: u32 = 64;
|
||||||
|
|
||||||
/// GICv2M compatible string
|
/// GICv2M compatible string
|
||||||
pub const GIC_V2M_COMPATIBLE: &str = "arm,gic-v2m-frame";
|
pub const GIC_V2M_COMPATIBLE: &str = "arm,gic-v2m-frame";
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ pub struct EntryPoint {
|
|||||||
|
|
||||||
/// Configure the specified VCPU, and return its MPIDR.
|
/// Configure the specified VCPU, and return its MPIDR.
|
||||||
pub fn configure_vcpu(
|
pub fn configure_vcpu(
|
||||||
vcpu: &Arc<dyn hypervisor::Vcpu>,
|
vcpu: &dyn hypervisor::Vcpu,
|
||||||
id: u32,
|
id: u32,
|
||||||
boot_setup: Option<(EntryPoint, &GuestMemoryAtomic<GuestMemoryMmap>)>,
|
boot_setup: Option<(EntryPoint, &GuestMemoryAtomic<GuestMemoryMmap>)>,
|
||||||
) -> super::Result<u64> {
|
) -> super::Result<u64> {
|
||||||
@@ -125,7 +125,7 @@ pub fn arch_memory_regions() -> Vec<(GuestAddress, usize, RegionType)> {
|
|||||||
pub fn configure_system<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHasher>(
|
pub fn configure_system<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHasher>(
|
||||||
guest_mem: &GuestMemoryMmap,
|
guest_mem: &GuestMemoryMmap,
|
||||||
cmdline: &str,
|
cmdline: &str,
|
||||||
vcpu_mpidr: Vec<u64>,
|
vcpu_mpidr: &[u64],
|
||||||
vcpu_topology: Option<(u16, u16, u16, u16)>,
|
vcpu_topology: Option<(u16, u16, u16, u16)>,
|
||||||
device_info: &HashMap<(DeviceType, String), T, S>,
|
device_info: &HashMap<(DeviceType, String), T, S>,
|
||||||
initrd: &Option<super::InitramfsConfig>,
|
initrd: &Option<super::InitramfsConfig>,
|
||||||
@@ -154,7 +154,7 @@ pub fn configure_system<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::Bui
|
|||||||
fdt::print_fdt(&fdt_final);
|
fdt::print_fdt(&fdt_final);
|
||||||
}
|
}
|
||||||
|
|
||||||
fdt::write_fdt_to_memory(fdt_final, guest_mem).map_err(Error::WriteFdtToMemory)?;
|
fdt::write_fdt_to_memory(&fdt_final, guest_mem).map_err(Error::WriteFdtToMemory)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -180,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();
|
let host_cpu_phys_bits = hypervisor.get_host_ipa_limit().try_into().unwrap();
|
||||||
if host_cpu_phys_bits == 0 {
|
if host_cpu_phys_bits == 0 {
|
||||||
// Host kernel does not support `get_host_ipa_limit`,
|
// Host kernel does not support `get_host_ipa_limit`,
|
||||||
@@ -192,7 +192,7 @@ pub fn get_host_cpu_phys_bits(hypervisor: &Arc<dyn hypervisor::Hypervisor>) -> u
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod unit_tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -8,9 +8,6 @@
|
|||||||
//! Implements platform specific functionality.
|
//! Implements platform specific functionality.
|
||||||
//! Supported platforms: x86_64, aarch64, riscv64.
|
//! Supported platforms: x86_64, aarch64, riscv64.
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
extern crate log;
|
|
||||||
|
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::{fmt, result};
|
use std::{fmt, result};
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ use std::{cmp, result, str};
|
|||||||
|
|
||||||
use byteorder::{BigEndian, ByteOrder};
|
use byteorder::{BigEndian, ByteOrder};
|
||||||
use hypervisor::arch::riscv64::aia::Vaia;
|
use hypervisor::arch::riscv64::aia::Vaia;
|
||||||
|
use log::debug;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use vm_fdt::{FdtWriter, FdtWriterResult};
|
use vm_fdt::{FdtWriter, FdtWriterResult};
|
||||||
use vm_memory::{Address, Bytes, GuestMemory, GuestMemoryError, GuestMemoryRegion};
|
use vm_memory::{Address, Bytes, GuestMemory, GuestMemoryError, GuestMemoryRegion};
|
||||||
@@ -65,6 +66,7 @@ pub fn create_fdt<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHash
|
|||||||
guest_mem: &GuestMemoryMmap,
|
guest_mem: &GuestMemoryMmap,
|
||||||
cmdline: &str,
|
cmdline: &str,
|
||||||
num_vcpu: u32,
|
num_vcpu: u32,
|
||||||
|
isa_string: &str,
|
||||||
device_info: &HashMap<(DeviceType, String), T, S>,
|
device_info: &HashMap<(DeviceType, String), T, S>,
|
||||||
aia_device: &Arc<Mutex<dyn Vaia>>,
|
aia_device: &Arc<Mutex<dyn Vaia>>,
|
||||||
initrd: &Option<InitramfsConfig>,
|
initrd: &Option<InitramfsConfig>,
|
||||||
@@ -84,7 +86,7 @@ pub fn create_fdt<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHash
|
|||||||
// Properties
|
// Properties
|
||||||
fdt.property_u32("#address-cells", ADDRESS_CELLS)?;
|
fdt.property_u32("#address-cells", ADDRESS_CELLS)?;
|
||||||
fdt.property_u32("#size-cells", SIZE_CELLS)?;
|
fdt.property_u32("#size-cells", SIZE_CELLS)?;
|
||||||
create_cpu_nodes(&mut fdt, num_vcpu)?;
|
create_cpu_nodes(&mut fdt, num_vcpu, isa_string)?;
|
||||||
create_memory_node(&mut fdt, guest_mem)?;
|
create_memory_node(&mut fdt, guest_mem)?;
|
||||||
create_chosen_node(&mut fdt, cmdline, initrd)?;
|
create_chosen_node(&mut fdt, cmdline, initrd)?;
|
||||||
create_aia_node(&mut fdt, aia_device)?;
|
create_aia_node(&mut fdt, aia_device)?;
|
||||||
@@ -99,16 +101,16 @@ pub fn create_fdt<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHash
|
|||||||
Ok(fdt_final)
|
Ok(fdt_final)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_fdt_to_memory(fdt_final: Vec<u8>, guest_mem: &GuestMemoryMmap) -> Result<()> {
|
pub fn write_fdt_to_memory(fdt_final: &[u8], guest_mem: &GuestMemoryMmap) -> Result<()> {
|
||||||
// Write FDT to memory.
|
// Write FDT to memory.
|
||||||
guest_mem
|
guest_mem
|
||||||
.write_slice(fdt_final.as_slice(), super::layout::FDT_START)
|
.write_slice(fdt_final, super::layout::FDT_START)
|
||||||
.map_err(Error::WriteFdtToMemory)?;
|
.map_err(Error::WriteFdtToMemory)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Following are the auxiliary function for creating the different nodes that we append to our FDT.
|
// Following are the auxiliary function for creating the different nodes that we append to our FDT.
|
||||||
fn create_cpu_nodes(fdt: &mut FdtWriter, num_cpus: u32) -> FdtWriterResult<()> {
|
fn create_cpu_nodes(fdt: &mut FdtWriter, num_cpus: u32, isa_string: &str) -> FdtWriterResult<()> {
|
||||||
// See https://elixir.bootlin.com/linux/v6.10/source/Documentation/devicetree/bindings/riscv/cpus.yaml
|
// See https://elixir.bootlin.com/linux/v6.10/source/Documentation/devicetree/bindings/riscv/cpus.yaml
|
||||||
let cpus = fdt.begin_node("cpus")?;
|
let cpus = fdt.begin_node("cpus")?;
|
||||||
// As per documentation, on RISC-V 64-bit systems value should be set to 1.
|
// As per documentation, on RISC-V 64-bit systems value should be set to 1.
|
||||||
@@ -123,7 +125,7 @@ fn create_cpu_nodes(fdt: &mut FdtWriter, num_cpus: u32) -> FdtWriterResult<()> {
|
|||||||
fdt.property_string("device_type", "cpu")?;
|
fdt.property_string("device_type", "cpu")?;
|
||||||
fdt.property_string("compatible", "riscv")?;
|
fdt.property_string("compatible", "riscv")?;
|
||||||
fdt.property_string("mmu-type", "sv48")?;
|
fdt.property_string("mmu-type", "sv48")?;
|
||||||
fdt.property_string("riscv,isa", "rv64imafdc_smaia_ssaia")?;
|
fdt.property_string("riscv,isa", isa_string)?;
|
||||||
fdt.property_string("status", "okay")?;
|
fdt.property_string("status", "okay")?;
|
||||||
fdt.property_u32("reg", cpu_index)?;
|
fdt.property_u32("reg", cpu_index)?;
|
||||||
fdt.property_u32("phandle", CPU_BASE_PHANDLE + cpu_index)?;
|
fdt.property_u32("phandle", CPU_BASE_PHANDLE + cpu_index)?;
|
||||||
@@ -471,7 +473,7 @@ fn print_node(node: fdt_parser::node::FdtNode<'_, '_>, n_spaces: usize) {
|
|||||||
array,
|
array,
|
||||||
indent = (n_spaces + 2)
|
indent = (n_spaces + 2)
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print children nodes if there is any
|
// Print children nodes if there is any
|
||||||
|
|||||||
@@ -98,7 +98,12 @@ pub const CMDLINE_MAX_SIZE: usize = 1024;
|
|||||||
pub const FDT_START: GuestAddress = RAM_START;
|
pub const FDT_START: GuestAddress = RAM_START;
|
||||||
pub const FDT_MAX_SIZE: u64 = 0x1_0000;
|
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);
|
pub const KERNEL_START: GuestAddress = GuestAddress(RAM_START.0 + FDT_MAX_SIZE);
|
||||||
|
|
||||||
/// Pci high memory base
|
/// Pci high memory base
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ pub mod uefi;
|
|||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::{BufRead, BufReader};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use hypervisor::arch::riscv64::aia::Vaia;
|
use hypervisor::arch::riscv64::aia::Vaia;
|
||||||
@@ -22,6 +24,11 @@ use vm_memory::{Address, GuestAddress, GuestMemory, GuestMemoryAtomic};
|
|||||||
pub use self::fdt::DeviceInfoForFdt;
|
pub use self::fdt::DeviceInfoForFdt;
|
||||||
use crate::{DeviceType, GuestMemoryMmap, PciSpaceInfo, RegionType};
|
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;
|
pub const _NSIG: i32 = 65;
|
||||||
|
|
||||||
/// Errors thrown while configuring riscv64 system.
|
/// Errors thrown while configuring riscv64 system.
|
||||||
@@ -46,6 +53,22 @@ pub enum Error {
|
|||||||
/// Error configuring the general purpose registers
|
/// Error configuring the general purpose registers
|
||||||
#[error("Error configuring the general purpose registers")]
|
#[error("Error configuring the general purpose registers")]
|
||||||
RegsConfiguration(#[source] hypervisor::HypervisorCpuError),
|
RegsConfiguration(#[source] hypervisor::HypervisorCpuError),
|
||||||
|
|
||||||
|
/// Error opening /proc/cpuinfo
|
||||||
|
#[error("Error opening /proc/cpuinfo")]
|
||||||
|
OpenCpuInfo(#[source] std::io::Error),
|
||||||
|
|
||||||
|
/// Error reading /proc/cpuinfo
|
||||||
|
#[error("Error reading /proc/cpuinfo")]
|
||||||
|
ReadCpuInfo(#[source] std::io::Error),
|
||||||
|
|
||||||
|
/// Invalid ISA string
|
||||||
|
#[error("Invalid ISA string: {0}")]
|
||||||
|
InvalidIsaString(String),
|
||||||
|
|
||||||
|
/// Error parsing /proc/cpuinfo
|
||||||
|
#[error("Error parsing /proc/cpuinfo")]
|
||||||
|
CpuInfoParsing,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
@@ -58,7 +81,7 @@ pub struct EntryPoint {
|
|||||||
|
|
||||||
/// Configure the specified VCPU, and return its MPIDR.
|
/// Configure the specified VCPU, and return its MPIDR.
|
||||||
pub fn configure_vcpu(
|
pub fn configure_vcpu(
|
||||||
vcpu: &Arc<dyn hypervisor::Vcpu>,
|
vcpu: &dyn hypervisor::Vcpu,
|
||||||
id: u32,
|
id: u32,
|
||||||
boot_setup: Option<(EntryPoint, &GuestMemoryAtomic<GuestMemoryMmap>)>,
|
boot_setup: Option<(EntryPoint, &GuestMemoryAtomic<GuestMemoryMmap>)>,
|
||||||
) -> super::Result<()> {
|
) -> super::Result<()> {
|
||||||
@@ -99,6 +122,43 @@ pub fn arch_memory_regions() -> Vec<(GuestAddress, usize, RegionType)> {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Read the first "isa" string from /proc/cpuinfo and filter out the H extension,
|
||||||
|
// while correctly preserving multi-letter extensions.
|
||||||
|
fn isa_string_from_host() -> Result<String, Error> {
|
||||||
|
let file = File::open("/proc/cpuinfo").map_err(Error::OpenCpuInfo)?;
|
||||||
|
let reader = BufReader::new(file);
|
||||||
|
|
||||||
|
for line in reader.lines() {
|
||||||
|
let line = line.map_err(Error::ReadCpuInfo)?;
|
||||||
|
let trimmed_line = line.trim();
|
||||||
|
|
||||||
|
if trimmed_line.starts_with("isa") {
|
||||||
|
let parts: Vec<&str> = trimmed_line.split(':').collect();
|
||||||
|
if parts.len() == 2 {
|
||||||
|
let isa_string = parts[1].trim();
|
||||||
|
|
||||||
|
// Split the string by underscores to separate single letter vs long-form
|
||||||
|
// extensions
|
||||||
|
let mut components: Vec<String> =
|
||||||
|
isa_string.split('_').map(|s| s.to_string()).collect();
|
||||||
|
|
||||||
|
if components.is_empty() {
|
||||||
|
return Err(Error::InvalidIsaString(isa_string.to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove H extension if present in single letter extensions
|
||||||
|
let first_component = components[0].chars().filter(|&c| c != 'h').collect();
|
||||||
|
|
||||||
|
components[0] = first_component;
|
||||||
|
|
||||||
|
return Ok(components.join("_"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(Error::CpuInfoParsing)
|
||||||
|
}
|
||||||
|
|
||||||
/// Configures the system and should be called once per vm before starting vcpu threads.
|
/// Configures the system and should be called once per vm before starting vcpu threads.
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn configure_system<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHasher>(
|
pub fn configure_system<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHasher>(
|
||||||
@@ -110,10 +170,12 @@ pub fn configure_system<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::Bui
|
|||||||
pci_space_info: &[PciSpaceInfo],
|
pci_space_info: &[PciSpaceInfo],
|
||||||
aia_device: &Arc<Mutex<dyn Vaia>>,
|
aia_device: &Arc<Mutex<dyn Vaia>>,
|
||||||
) -> super::Result<()> {
|
) -> super::Result<()> {
|
||||||
|
let isa_string = isa_string_from_host()?;
|
||||||
let fdt_final = fdt::create_fdt(
|
let fdt_final = fdt::create_fdt(
|
||||||
guest_mem,
|
guest_mem,
|
||||||
cmdline,
|
cmdline,
|
||||||
num_vcpu,
|
num_vcpu,
|
||||||
|
&isa_string,
|
||||||
device_info,
|
device_info,
|
||||||
aia_device,
|
aia_device,
|
||||||
initrd,
|
initrd,
|
||||||
@@ -125,7 +187,7 @@ pub fn configure_system<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::Bui
|
|||||||
fdt::print_fdt(&fdt_final);
|
fdt::print_fdt(&fdt_final);
|
||||||
}
|
}
|
||||||
|
|
||||||
fdt::write_fdt_to_memory(fdt_final, guest_mem).map_err(Error::WriteFdtToMemory)?;
|
fdt::write_fdt_to_memory(&fdt_final, guest_mem).map_err(Error::WriteFdtToMemory)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -151,12 +213,12 @@ 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
|
40
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod unit_tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
// found in the LICENSE-BSD-3-Clause file.
|
// found in the LICENSE-BSD-3-Clause file.
|
||||||
|
|
||||||
use std::result;
|
use std::result;
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
pub type Result<T> = result::Result<T, hypervisor::HypervisorCpuError>;
|
pub type Result<T> = result::Result<T, hypervisor::HypervisorCpuError>;
|
||||||
|
|
||||||
@@ -24,7 +23,7 @@ pub fn set_apic_delivery_mode(reg: u32, mode: u32) -> u32 {
|
|||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
/// * `vcpu` - The VCPU object to configure.
|
/// * `vcpu` - The VCPU object to configure.
|
||||||
pub fn set_lint(vcpu: &Arc<dyn hypervisor::Vcpu>) -> Result<()> {
|
pub fn set_lint(vcpu: &dyn hypervisor::Vcpu) -> Result<()> {
|
||||||
let mut klapic = vcpu.get_lapic()?;
|
let mut klapic = vcpu.get_lapic()?;
|
||||||
|
|
||||||
let lvt_lint0 = klapic.get_klapic_reg(APIC_LVT0);
|
let lvt_lint0 = klapic.get_klapic_reg(APIC_LVT0);
|
||||||
|
|||||||
@@ -6,12 +6,19 @@
|
|||||||
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE-BSD-3-Clause file.
|
// found in the LICENSE-BSD-3-Clause file.
|
||||||
use std::sync::Arc;
|
|
||||||
pub mod interrupts;
|
pub mod interrupts;
|
||||||
pub mod layout;
|
pub mod layout;
|
||||||
|
pub mod regs;
|
||||||
|
|
||||||
|
#[cfg(feature = "tdx")]
|
||||||
|
pub mod tdx;
|
||||||
|
|
||||||
mod mpspec;
|
mod mpspec;
|
||||||
mod mptable;
|
mod mptable;
|
||||||
pub mod regs;
|
mod smbios;
|
||||||
|
|
||||||
|
use std::arch::x86_64;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
|
|
||||||
use hypervisor::arch::x86::{CPUID_FLAG_VALID_INDEX, CpuIdEntry};
|
use hypervisor::arch::x86::{CPUID_FLAG_VALID_INDEX, CpuIdEntry};
|
||||||
@@ -20,6 +27,7 @@ use linux_loader::loader::bootparam::{boot_params, setup_header};
|
|||||||
use linux_loader::loader::elf::start_info::{
|
use linux_loader::loader::elf::start_info::{
|
||||||
hvm_memmap_table_entry, hvm_modlist_entry, hvm_start_info,
|
hvm_memmap_table_entry, hvm_modlist_entry, hvm_start_info,
|
||||||
};
|
};
|
||||||
|
use log::{debug, error, info};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use vm_memory::{
|
use vm_memory::{
|
||||||
Address, Bytes, GuestAddress, GuestAddressSpace, GuestMemory, GuestMemoryAtomic,
|
Address, Bytes, GuestAddress, GuestAddressSpace, GuestMemory, GuestMemoryAtomic,
|
||||||
@@ -27,10 +35,6 @@ use vm_memory::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use crate::{GuestMemoryMmap, InitramfsConfig, RegionType};
|
use crate::{GuestMemoryMmap, InitramfsConfig, RegionType};
|
||||||
mod smbios;
|
|
||||||
use std::arch::x86_64;
|
|
||||||
#[cfg(feature = "tdx")]
|
|
||||||
pub mod tdx;
|
|
||||||
|
|
||||||
// While modern architectures support more than 255 CPUs via x2APIC,
|
// While modern architectures support more than 255 CPUs via x2APIC,
|
||||||
// legacy devices such as mptable support at most 254 CPUs.
|
// legacy devices such as mptable support at most 254 CPUs.
|
||||||
@@ -40,12 +44,17 @@ pub const MAX_SUPPORTED_CPUS_LEGACY: u32 = 254;
|
|||||||
#[cfg(feature = "kvm")]
|
#[cfg(feature = "kvm")]
|
||||||
const TSC_DEADLINE_TIMER_ECX_BIT: u8 = 24; // tsc deadline timer ecx bit.
|
const TSC_DEADLINE_TIMER_ECX_BIT: u8 = 24; // tsc deadline timer ecx bit.
|
||||||
const HYPERVISOR_ECX_BIT: u8 = 31; // Hypervisor ecx bit.
|
const HYPERVISOR_ECX_BIT: u8 = 31; // Hypervisor ecx bit.
|
||||||
|
const VMX_ECX_BIT: u8 = 5; // VMX for Intel
|
||||||
|
const SVM_ECX_BIT: u8 = 2; // SVM for AMD
|
||||||
const MTRR_EDX_BIT: u8 = 12; // Hypervisor ecx bit.
|
const MTRR_EDX_BIT: u8 = 12; // Hypervisor ecx bit.
|
||||||
const INVARIANT_TSC_EDX_BIT: u8 = 8; // Invariant TSC bit on 0x8000_0007 EDX
|
const INVARIANT_TSC_EDX_BIT: u8 = 8; // Invariant TSC bit on 0x8000_0007 EDX
|
||||||
const AMX_BF16: u8 = 22; // AMX tile computation on bfloat16 numbers
|
const AMX_BF16: u8 = 22; // AMX tile computation on bfloat16 numbers
|
||||||
const AMX_TILE: u8 = 24; // AMX tile load/store instructions
|
const AMX_TILE: u8 = 24; // AMX tile load/store instructions
|
||||||
const AMX_INT8: u8 = 25; // AMX tile computation on 8-bit integers
|
const AMX_INT8: u8 = 25; // AMX tile computation on 8-bit integers
|
||||||
|
|
||||||
|
const AMX_FP16: u8 = 21; // AMX tile computation on fp16 numbers
|
||||||
|
const AMX_COMPLEX: u8 = 8; // AMX tile computation on complex numbers
|
||||||
|
|
||||||
// KVM feature bits
|
// KVM feature bits
|
||||||
#[cfg(feature = "tdx")]
|
#[cfg(feature = "tdx")]
|
||||||
const KVM_FEATURE_CLOCKSOURCE_BIT: u8 = 0;
|
const KVM_FEATURE_CLOCKSOURCE_BIT: u8 = 0;
|
||||||
@@ -279,7 +288,7 @@ impl CpuidPatch {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn patch_cpuid(cpuid: &mut [CpuIdEntry], patches: Vec<CpuidPatch>) {
|
pub fn patch_cpuid(cpuid: &mut [CpuIdEntry], patches: &[CpuidPatch]) {
|
||||||
for entry in cpuid {
|
for entry in cpuid {
|
||||||
for patch in patches.iter() {
|
for patch in patches.iter() {
|
||||||
if entry.function == patch.function && entry.index == patch.index {
|
if entry.function == patch.function && entry.index == patch.index {
|
||||||
@@ -547,9 +556,10 @@ impl CpuidFeatureEntry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate_common_cpuid(
|
pub fn generate_common_cpuid(
|
||||||
hypervisor: &Arc<dyn hypervisor::Hypervisor>,
|
hypervisor: &dyn hypervisor::Hypervisor,
|
||||||
config: &CpuidConfig,
|
config: &CpuidConfig,
|
||||||
) -> super::Result<Vec<CpuIdEntry>> {
|
) -> super::Result<Vec<CpuIdEntry>> {
|
||||||
|
#[allow(unused_unsafe)]
|
||||||
// SAFETY: cpuid called with valid leaves
|
// SAFETY: cpuid called with valid leaves
|
||||||
if unsafe { x86_64::__cpuid(1) }.ecx & (1 << HYPERVISOR_ECX_BIT) == 1 << HYPERVISOR_ECX_BIT {
|
if unsafe { x86_64::__cpuid(1) }.ecx & (1 << HYPERVISOR_ECX_BIT) == 1 << HYPERVISOR_ECX_BIT {
|
||||||
// SAFETY: cpuid called with valid leaves
|
// SAFETY: cpuid called with valid leaves
|
||||||
@@ -616,14 +626,14 @@ pub fn generate_common_cpuid(
|
|||||||
.get_supported_cpuid()
|
.get_supported_cpuid()
|
||||||
.map_err(Error::CpuidGetSupported)?;
|
.map_err(Error::CpuidGetSupported)?;
|
||||||
|
|
||||||
CpuidPatch::patch_cpuid(&mut cpuid, cpuid_patches);
|
CpuidPatch::patch_cpuid(&mut cpuid, &cpuid_patches);
|
||||||
|
|
||||||
#[cfg(feature = "tdx")]
|
#[cfg(feature = "tdx")]
|
||||||
let tdx_capabilities = if config.tdx {
|
let tdx_capabilities = if config.tdx {
|
||||||
let caps = hypervisor
|
let caps = hypervisor
|
||||||
.tdx_capabilities()
|
.tdx_capabilities()
|
||||||
.map_err(Error::TdxCapabilities)?;
|
.map_err(Error::TdxCapabilities)?;
|
||||||
info!("TDX capabilities {:#?}", caps);
|
info!("TDX capabilities {caps:#?}");
|
||||||
Some(caps)
|
Some(caps)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
@@ -634,8 +644,14 @@ pub fn generate_common_cpuid(
|
|||||||
match entry.function {
|
match entry.function {
|
||||||
// Clear AMX related bits if the AMX feature is not enabled
|
// Clear AMX related bits if the AMX feature is not enabled
|
||||||
0x7 => {
|
0x7 => {
|
||||||
if !config.amx && entry.index == 0 {
|
if !config.amx {
|
||||||
entry.edx &= !((1 << AMX_BF16) | (1 << AMX_TILE) | (1 << AMX_INT8))
|
if entry.index == 0 {
|
||||||
|
entry.edx &= !((1 << AMX_BF16) | (1 << AMX_TILE) | (1 << AMX_INT8));
|
||||||
|
}
|
||||||
|
if entry.index == 1 {
|
||||||
|
entry.eax &= !(1 << AMX_FP16);
|
||||||
|
entry.edx &= !(1 << AMX_COMPLEX);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
0xd =>
|
0xd =>
|
||||||
@@ -657,9 +673,29 @@ pub fn generate_common_cpuid(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
0x1d => {
|
||||||
|
// Tile Information (purely AMX related).
|
||||||
|
if !config.amx {
|
||||||
|
entry.eax = 0;
|
||||||
|
entry.ebx = 0;
|
||||||
|
entry.ecx = 0;
|
||||||
|
entry.edx = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
0x1e => {
|
||||||
|
// TMUL information (purely AMX related)
|
||||||
|
if !config.amx {
|
||||||
|
entry.eax = 0;
|
||||||
|
entry.ebx = 0;
|
||||||
|
entry.ecx = 0;
|
||||||
|
entry.edx = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Copy host L1 cache details if not populated by KVM
|
// Copy host L1 cache details if not populated by KVM
|
||||||
0x8000_0005 => {
|
0x8000_0005 => {
|
||||||
if entry.eax == 0 && entry.ebx == 0 && entry.ecx == 0 && entry.edx == 0 {
|
if entry.eax == 0 && entry.ebx == 0 && entry.ecx == 0 && entry.edx == 0 {
|
||||||
|
#[allow(unused_unsafe)]
|
||||||
// SAFETY: cpuid called with valid leaves
|
// SAFETY: cpuid called with valid leaves
|
||||||
if unsafe { std::arch::x86_64::__cpuid(0x8000_0000).eax } >= 0x8000_0005 {
|
if unsafe { std::arch::x86_64::__cpuid(0x8000_0000).eax } >= 0x8000_0005 {
|
||||||
// SAFETY: cpuid called with valid leaves
|
// SAFETY: cpuid called with valid leaves
|
||||||
@@ -674,8 +710,10 @@ pub fn generate_common_cpuid(
|
|||||||
// Copy host L2 cache details if not populated by KVM
|
// Copy host L2 cache details if not populated by KVM
|
||||||
0x8000_0006 => {
|
0x8000_0006 => {
|
||||||
if entry.eax == 0 && entry.ebx == 0 && entry.ecx == 0 && entry.edx == 0 {
|
if entry.eax == 0 && entry.ebx == 0 && entry.ecx == 0 && entry.edx == 0 {
|
||||||
|
#[allow(unused_unsafe)]
|
||||||
// SAFETY: cpuid called with valid leaves
|
// SAFETY: cpuid called with valid leaves
|
||||||
if unsafe { std::arch::x86_64::__cpuid(0x8000_0000).eax } >= 0x8000_0006 {
|
if unsafe { std::arch::x86_64::__cpuid(0x8000_0000).eax } >= 0x8000_0006 {
|
||||||
|
#[allow(unused_unsafe)]
|
||||||
// SAFETY: cpuid called with valid leaves
|
// SAFETY: cpuid called with valid leaves
|
||||||
let leaf = unsafe { std::arch::x86_64::__cpuid(0x8000_0006) };
|
let leaf = unsafe { std::arch::x86_64::__cpuid(0x8000_0006) };
|
||||||
entry.eax = leaf.eax;
|
entry.eax = leaf.eax;
|
||||||
@@ -702,7 +740,7 @@ pub fn generate_common_cpuid(
|
|||||||
| (1 << KVM_FEATURE_CLOCKSOURCE_STABLE_BIT)
|
| (1 << KVM_FEATURE_CLOCKSOURCE_STABLE_BIT)
|
||||||
| (1 << KVM_FEATURE_ASYNC_PF_BIT)
|
| (1 << KVM_FEATURE_ASYNC_PF_BIT)
|
||||||
| (1 << KVM_FEATURE_ASYNC_PF_VMEXIT_BIT)
|
| (1 << KVM_FEATURE_ASYNC_PF_VMEXIT_BIT)
|
||||||
| (1 << KVM_FEATURE_STEAL_TIME_BIT))
|
| (1 << KVM_FEATURE_STEAL_TIME_BIT));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
@@ -713,6 +751,7 @@ pub fn generate_common_cpuid(
|
|||||||
for i in 0x8000_0002..=0x8000_0004 {
|
for i in 0x8000_0002..=0x8000_0004 {
|
||||||
cpuid.retain(|c| c.function != i);
|
cpuid.retain(|c| c.function != i);
|
||||||
// SAFETY: call cpuid with valid leaves
|
// SAFETY: call cpuid with valid leaves
|
||||||
|
#[allow(unused_unsafe)]
|
||||||
let leaf = unsafe { std::arch::x86_64::__cpuid(i) };
|
let leaf = unsafe { std::arch::x86_64::__cpuid(i) };
|
||||||
cpuid.push(CpuIdEntry {
|
cpuid.push(CpuIdEntry {
|
||||||
function: i,
|
function: i,
|
||||||
@@ -774,14 +813,16 @@ pub fn generate_common_cpuid(
|
|||||||
Ok(cpuid)
|
Ok(cpuid)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn configure_vcpu(
|
pub fn configure_vcpu(
|
||||||
vcpu: &Arc<dyn hypervisor::Vcpu>,
|
vcpu: &dyn hypervisor::Vcpu,
|
||||||
id: u32,
|
id: u32,
|
||||||
boot_setup: Option<(EntryPoint, &GuestMemoryAtomic<GuestMemoryMmap>)>,
|
boot_setup: Option<(EntryPoint, &GuestMemoryAtomic<GuestMemoryMmap>)>,
|
||||||
cpuid: Vec<CpuIdEntry>,
|
cpuid: Vec<CpuIdEntry>,
|
||||||
kvm_hyperv: bool,
|
kvm_hyperv: bool,
|
||||||
cpu_vendor: CpuVendor,
|
cpu_vendor: CpuVendor,
|
||||||
topology: (u16, u16, u16, u16),
|
topology: (u16, u16, u16, u16),
|
||||||
|
nested: bool,
|
||||||
) -> super::Result<()> {
|
) -> super::Result<()> {
|
||||||
let x2apic_id = get_x2apic_id(id, Some(topology));
|
let x2apic_id = get_x2apic_id(id, Some(topology));
|
||||||
|
|
||||||
@@ -800,6 +841,17 @@ pub fn configure_vcpu(
|
|||||||
entry.ebx &= 0xffffff;
|
entry.ebx &= 0xffffff;
|
||||||
entry.ebx |= x2apic_id << 24;
|
entry.ebx |= x2apic_id << 24;
|
||||||
apic_id_patched = true;
|
apic_id_patched = true;
|
||||||
|
if !nested {
|
||||||
|
// Disable nested virtualization for Intel
|
||||||
|
entry.ecx &= !(1 << VMX_ECX_BIT);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if entry.function == 0x8000_0001 {
|
||||||
|
if !nested {
|
||||||
|
// Disable the nested virtualization for AMD
|
||||||
|
entry.ecx &= !(1 << SVM_ECX_BIT);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -812,6 +864,7 @@ pub fn configure_vcpu(
|
|||||||
// The TSC frequency CPUID leaf should not be included when running with HyperV emulation
|
// The TSC frequency CPUID leaf should not be included when running with HyperV emulation
|
||||||
if !kvm_hyperv && let Some(tsc_khz) = vcpu.tsc_khz().map_err(Error::GetTscFrequency)? {
|
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
|
// Need to check that the TSC doesn't vary with dynamic frequency
|
||||||
|
#[allow(unused_unsafe)]
|
||||||
// SAFETY: cpuid called with valid leaves
|
// SAFETY: cpuid called with valid leaves
|
||||||
if unsafe { std::arch::x86_64::__cpuid(0x8000_0007) }.edx & (1u32 << INVARIANT_TSC_EDX_BIT)
|
if unsafe { std::arch::x86_64::__cpuid(0x8000_0007) }.edx & (1u32 << INVARIANT_TSC_EDX_BIT)
|
||||||
> 0
|
> 0
|
||||||
@@ -825,11 +878,11 @@ pub fn configure_vcpu(
|
|||||||
* APIC_BUS_CYCLE_NS */
|
* APIC_BUS_CYCLE_NS */
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for c in &cpuid {
|
for c in &cpuid {
|
||||||
debug!("{}", c);
|
debug!("{c}");
|
||||||
}
|
}
|
||||||
|
|
||||||
vcpu.set_cpuid2(&cpuid)
|
vcpu.set_cpuid2(&cpuid)
|
||||||
@@ -995,17 +1048,15 @@ pub fn generate_ram_ranges(guest_mem: &GuestMemoryMmap) -> super::Result<Vec<Ram
|
|||||||
&& (first_region_end <= &mem_32bit_reserved_start))
|
&& (first_region_end <= &mem_32bit_reserved_start))
|
||||||
{
|
{
|
||||||
error!(
|
error!(
|
||||||
"Unexpected first memory region layout: (start: 0x{:08x}, end: 0x{:08x}).
|
"Unexpected first memory region layout: (start: 0x{first_region_start:08x}, end: 0x{first_region_end:08x}).
|
||||||
high_ram_start: 0x{:08x}, mem_32bit_reserved_start: 0x{:08x}",
|
high_ram_start: 0x{high_ram_start:08x}, mem_32bit_reserved_start: 0x{mem_32bit_reserved_start:08x}"
|
||||||
first_region_start, first_region_end, high_ram_start, mem_32bit_reserved_start
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return Err(super::Error::MemmapTableSetup);
|
return Err(super::Error::MemmapTableSetup);
|
||||||
}
|
}
|
||||||
|
|
||||||
info!(
|
info!(
|
||||||
"first usable physical memory range, start: 0x{:08x}, end: 0x{:08x}",
|
"first usable physical memory range, start: 0x{high_ram_start:08x}, end: 0x{first_region_end:08x}"
|
||||||
high_ram_start, first_region_end
|
|
||||||
);
|
);
|
||||||
|
|
||||||
(high_ram_start, *first_region_end)
|
(high_ram_start, *first_region_end)
|
||||||
@@ -1260,8 +1311,9 @@ pub fn initramfs_load_addr(
|
|||||||
Ok(aligned_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
|
// SAFETY: call cpuid with valid leaves
|
||||||
|
#[allow(unused_unsafe)]
|
||||||
unsafe {
|
unsafe {
|
||||||
let leaf = x86_64::__cpuid(0x8000_0000);
|
let leaf = x86_64::__cpuid(0x8000_0000);
|
||||||
|
|
||||||
@@ -1306,9 +1358,10 @@ fn update_cpuid_topology(
|
|||||||
let die_width = u16::BITS - (dies_per_package - 1).leading_zeros() + core_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.
|
// The very old way: a flat number of logical CPUs per package: CPUID.1H:EBX[23:16] bits.
|
||||||
|
let core_count = dies_per_package as u32 * cores_per_die as u32 * threads_per_core as u32;
|
||||||
let mut cpu_ebx = CpuidPatch::get_cpuid_reg(cpuid, 0x1, None, CpuidReg::EBX).unwrap_or(0);
|
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))
|
cpu_ebx &= !(0xff << 16);
|
||||||
& (0xff << 16);
|
cpu_ebx |= (core_count & 0xff) << 16;
|
||||||
CpuidPatch::set_cpuid_reg(cpuid, 0x1, None, CpuidReg::EBX, cpu_ebx);
|
CpuidPatch::set_cpuid_reg(cpuid, 0x1, None, CpuidReg::EBX, cpu_ebx);
|
||||||
|
|
||||||
let mut cpu_edx = CpuidPatch::get_cpuid_reg(cpuid, 0x1, None, CpuidReg::EDX).unwrap_or(0);
|
let mut cpu_edx = CpuidPatch::get_cpuid_reg(cpuid, 0x1, None, CpuidReg::EDX).unwrap_or(0);
|
||||||
@@ -1349,6 +1402,7 @@ fn update_cpuid_topology(
|
|||||||
u32::from(threads_per_core),
|
u32::from(threads_per_core),
|
||||||
);
|
);
|
||||||
CpuidPatch::set_cpuid_reg(cpuid, 0x1f, Some(0), CpuidReg::ECX, 1 << 8);
|
CpuidPatch::set_cpuid_reg(cpuid, 0x1f, Some(0), CpuidReg::ECX, 1 << 8);
|
||||||
|
CpuidPatch::set_cpuid_reg(cpuid, 0x1f, Some(0), CpuidReg::EDX, x2apic_id);
|
||||||
|
|
||||||
CpuidPatch::set_cpuid_reg(cpuid, 0x1f, Some(1), CpuidReg::EAX, core_width);
|
CpuidPatch::set_cpuid_reg(cpuid, 0x1f, Some(1), CpuidReg::EAX, core_width);
|
||||||
CpuidPatch::set_cpuid_reg(
|
CpuidPatch::set_cpuid_reg(
|
||||||
@@ -1359,6 +1413,7 @@ fn update_cpuid_topology(
|
|||||||
u32::from(cores_per_die * threads_per_core),
|
u32::from(cores_per_die * threads_per_core),
|
||||||
);
|
);
|
||||||
CpuidPatch::set_cpuid_reg(cpuid, 0x1f, Some(1), CpuidReg::ECX, 2 << 8);
|
CpuidPatch::set_cpuid_reg(cpuid, 0x1f, Some(1), CpuidReg::ECX, 2 << 8);
|
||||||
|
CpuidPatch::set_cpuid_reg(cpuid, 0x1f, Some(1), CpuidReg::EDX, x2apic_id);
|
||||||
|
|
||||||
CpuidPatch::set_cpuid_reg(cpuid, 0x1f, Some(2), CpuidReg::EAX, die_width);
|
CpuidPatch::set_cpuid_reg(cpuid, 0x1f, Some(2), CpuidReg::EAX, die_width);
|
||||||
CpuidPatch::set_cpuid_reg(
|
CpuidPatch::set_cpuid_reg(
|
||||||
@@ -1369,6 +1424,7 @@ fn update_cpuid_topology(
|
|||||||
u32::from(dies_per_package * cores_per_die * threads_per_core),
|
u32::from(dies_per_package * cores_per_die * threads_per_core),
|
||||||
);
|
);
|
||||||
CpuidPatch::set_cpuid_reg(cpuid, 0x1f, Some(2), CpuidReg::ECX, 5 << 8);
|
CpuidPatch::set_cpuid_reg(cpuid, 0x1f, Some(2), CpuidReg::ECX, 5 << 8);
|
||||||
|
CpuidPatch::set_cpuid_reg(cpuid, 0x1f, Some(2), CpuidReg::EDX, x2apic_id);
|
||||||
|
|
||||||
if matches!(cpu_vendor, CpuVendor::AMD) {
|
if matches!(cpu_vendor, CpuVendor::AMD) {
|
||||||
CpuidPatch::set_cpuid_reg(
|
CpuidPatch::set_cpuid_reg(
|
||||||
@@ -1415,7 +1471,7 @@ fn update_cpuid_topology(
|
|||||||
edx_bit: Some(28),
|
edx_bit: Some(28),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
CpuidPatch::patch_cpuid(cpuid, cpuid_patches);
|
CpuidPatch::patch_cpuid(cpuid, &cpuid_patches);
|
||||||
CpuidPatch::set_cpuid_reg(
|
CpuidPatch::set_cpuid_reg(
|
||||||
cpuid,
|
cpuid,
|
||||||
0x8000_0008,
|
0x8000_0008,
|
||||||
@@ -1430,7 +1486,7 @@ fn update_cpuid_topology(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod unit_tests {
|
||||||
use linux_loader::loader::bootparam::boot_e820_entry;
|
use linux_loader::loader::bootparam::boot_e820_entry;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
use std::{mem, result, slice};
|
use std::{mem, result, slice};
|
||||||
|
|
||||||
use libc::c_uchar;
|
use libc::c_uchar;
|
||||||
|
use log::{info, warn};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use vm_memory::{Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryError};
|
use vm_memory::{Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryError};
|
||||||
|
|
||||||
@@ -297,7 +298,7 @@ pub fn setup_mptable(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod unit_tests {
|
||||||
use vm_memory::bitmap::BitmapSlice;
|
use vm_memory::bitmap::BitmapSlice;
|
||||||
use vm_memory::{GuestUsize, VolatileMemoryError, VolatileSlice, WriteVolatile};
|
use vm_memory::{GuestUsize, VolatileMemoryError, VolatileSlice, WriteVolatile};
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE-BSD-3-Clause file.
|
// found in the LICENSE-BSD-3-Clause file.
|
||||||
use std::sync::Arc;
|
|
||||||
use std::{mem, result};
|
use std::{mem, result};
|
||||||
|
|
||||||
use hypervisor::arch::x86::gdt::{gdt_entry, segment_from_gdt};
|
use hypervisor::arch::x86::gdt::{gdt_entry, segment_from_gdt};
|
||||||
@@ -67,7 +66,7 @@ pub type Result<T> = result::Result<T, Error>;
|
|||||||
/// # Arguments
|
/// # Arguments
|
||||||
///
|
///
|
||||||
/// * `vcpu` - Structure for the VCPU that holds the VCPU's fd.
|
/// * `vcpu` - Structure for the VCPU that holds the VCPU's fd.
|
||||||
pub fn setup_fpu(vcpu: &Arc<dyn hypervisor::Vcpu>) -> Result<()> {
|
pub fn setup_fpu(vcpu: &dyn hypervisor::Vcpu) -> Result<()> {
|
||||||
let fpu: FpuState = FpuState {
|
let fpu: FpuState = FpuState {
|
||||||
fcw: 0x37f,
|
fcw: 0x37f,
|
||||||
mxcsr: 0x1f80,
|
mxcsr: 0x1f80,
|
||||||
@@ -82,8 +81,8 @@ pub fn setup_fpu(vcpu: &Arc<dyn hypervisor::Vcpu>) -> Result<()> {
|
|||||||
/// # Arguments
|
/// # Arguments
|
||||||
///
|
///
|
||||||
/// * `vcpu` - Structure for the VCPU that holds the VCPU's fd.
|
/// * `vcpu` - Structure for the VCPU that holds the VCPU's fd.
|
||||||
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())
|
vcpu.set_msrs(vcpu.boot_msr_entries())
|
||||||
.map_err(Error::SetModelSpecificRegisters)?;
|
.map_err(Error::SetModelSpecificRegisters)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -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.
|
/// * `vcpu` - Structure for the VCPU that holds the VCPU's fd.
|
||||||
/// * `entry_point` - Description of the boot entry to set up.
|
/// * `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();
|
let mut regs = vcpu.create_standard_regs();
|
||||||
match entry_point.setup_header {
|
match entry_point.setup_header {
|
||||||
None => {
|
None => {
|
||||||
@@ -109,7 +108,7 @@ pub fn setup_regs(vcpu: &Arc<dyn hypervisor::Vcpu>, entry_point: EntryPoint) ->
|
|||||||
regs.set_rsp(BOOT_STACK_POINTER.raw_value());
|
regs.set_rsp(BOOT_STACK_POINTER.raw_value());
|
||||||
regs.set_rsi(ZERO_PAGE_START.raw_value());
|
regs.set_rsi(ZERO_PAGE_START.raw_value());
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
vcpu.set_regs(®s).map_err(Error::SetBaseRegisters)
|
vcpu.set_regs(®s).map_err(Error::SetBaseRegisters)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,7 +120,7 @@ pub fn setup_regs(vcpu: &Arc<dyn hypervisor::Vcpu>, entry_point: EntryPoint) ->
|
|||||||
/// * `vcpu` - Structure for the VCPU that holds the VCPU's fd.
|
/// * `vcpu` - Structure for the VCPU that holds the VCPU's fd.
|
||||||
pub fn setup_sregs(
|
pub fn setup_sregs(
|
||||||
mem: &GuestMemoryMmap,
|
mem: &GuestMemoryMmap,
|
||||||
vcpu: &Arc<dyn hypervisor::Vcpu>,
|
vcpu: &dyn hypervisor::Vcpu,
|
||||||
enable_x2_apic_mode: bool,
|
enable_x2_apic_mode: bool,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let mut sregs: SpecialRegisters = vcpu.get_sregs().map_err(Error::GetStatusRegisters)?;
|
let mut sregs: SpecialRegisters = vcpu.get_sregs().map_err(Error::GetStatusRegisters)?;
|
||||||
@@ -197,7 +196,7 @@ pub fn configure_segments_and_sregs(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod unit_tests {
|
||||||
use vm_memory::GuestAddress;
|
use vm_memory::GuestAddress;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
@@ -272,7 +272,7 @@ pub fn setup_smbios(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod unit_tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ use std::fs::File;
|
|||||||
use std::io::{Read, Seek, SeekFrom};
|
use std::io::{Read, Seek, SeekFrom};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use log::{debug, info};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use vm_memory::{ByteValued, Bytes, GuestAddress, GuestMemoryError};
|
use vm_memory::{ByteValued, Bytes, GuestAddress, GuestMemoryError};
|
||||||
@@ -108,7 +109,7 @@ fn tdvf_descriptor_offset(file: &mut File) -> Result<(SeekFrom, bool), TdvfError
|
|||||||
u16::from_le_bytes(table[offset - 18..offset - 16].try_into().unwrap()) as usize;
|
u16::from_le_bytes(table[offset - 18..offset - 16].try_into().unwrap()) as usize;
|
||||||
debug!(
|
debug!(
|
||||||
"Entry GUID = {}, size = {}",
|
"Entry GUID = {}, size = {}",
|
||||||
entry_uuid.hyphenated().to_string(),
|
entry_uuid.hyphenated(),
|
||||||
entry_size
|
entry_size
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -304,7 +305,7 @@ fn align_hob(v: u64) -> u64 {
|
|||||||
|
|
||||||
impl TdHob {
|
impl TdHob {
|
||||||
fn update_offset<T>(&mut self) {
|
fn update_offset<T>(&mut self) {
|
||||||
self.current_offset = align_hob(self.current_offset + std::mem::size_of::<T>() as u64)
|
self.current_offset = align_hob(self.current_offset + std::mem::size_of::<T>() as u64);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start(offset: u64) -> TdHob {
|
pub fn start(offset: u64) -> TdHob {
|
||||||
@@ -519,7 +520,7 @@ impl TdHob {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod unit_tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -528,7 +529,7 @@ mod tests {
|
|||||||
let mut f = std::fs::File::open("tdvf.fd").unwrap();
|
let mut f = std::fs::File::open("tdvf.fd").unwrap();
|
||||||
let (sections, _) = parse_tdvf_sections(&mut f).unwrap();
|
let (sections, _) = parse_tdvf_sections(&mut f).unwrap();
|
||||||
for section in sections {
|
for section in sections {
|
||||||
eprintln!("{section:x?}")
|
eprintln!("{section:x?}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ io_uring = ["dep:io-uring"]
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
byteorder = { workspace = true }
|
byteorder = { workspace = true }
|
||||||
crc-any = "2.5.0"
|
crc-any = "2.5.0"
|
||||||
io-uring = { version = "0.7.10", optional = true }
|
flate2 = "1.0"
|
||||||
|
io-uring = { version = "0.7.11", optional = true }
|
||||||
libc = { workspace = true }
|
libc = { workspace = true }
|
||||||
log = { workspace = true }
|
log = { workspace = true }
|
||||||
remain = "0.2.15"
|
remain = "0.2.15"
|
||||||
@@ -28,3 +29,7 @@ vm-memory = { workspace = true, features = [
|
|||||||
] }
|
] }
|
||||||
vm-virtio = { path = "../vm-virtio" }
|
vm-virtio = { path = "../vm-virtio" }
|
||||||
vmm-sys-util = { workspace = true }
|
vmm-sys-util = { workspace = true }
|
||||||
|
zstd = "0.13"
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|||||||
@@ -18,6 +18,12 @@ pub enum DiskFileError {
|
|||||||
/// Failed creating a new AsyncIo.
|
/// Failed creating a new AsyncIo.
|
||||||
#[error("Failed creating a new AsyncIo")]
|
#[error("Failed creating a new AsyncIo")]
|
||||||
NewAsyncIo(#[source] std::io::Error),
|
NewAsyncIo(#[source] std::io::Error),
|
||||||
|
/// Unsupported operation.
|
||||||
|
#[error("Unsupported operation")]
|
||||||
|
Unsupported,
|
||||||
|
/// Resize failed
|
||||||
|
#[error("Resize failed")]
|
||||||
|
ResizeError(#[source] std::io::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type DiskFileResult<T> = std::result::Result<T, DiskFileError>;
|
pub type DiskFileResult<T> = std::result::Result<T, DiskFileError>;
|
||||||
@@ -56,11 +62,22 @@ impl AsRawFd for BorrowedDiskFd<'_> {
|
|||||||
/// This allows abstracting over raw image formats as well as structured
|
/// This allows abstracting over raw image formats as well as structured
|
||||||
/// image formats.
|
/// image formats.
|
||||||
pub trait DiskFile: Send {
|
pub trait DiskFile: Send {
|
||||||
fn size(&mut self) -> DiskFileResult<u64>;
|
/// Returns the logical disk size a guest will see.
|
||||||
|
///
|
||||||
|
/// For raw formats, this is equal to [`Self::physical_size`]. For file formats
|
||||||
|
/// that wrap disk images in a container (e.g. QCOW2), this refers to the
|
||||||
|
/// effective size that the guest will see.
|
||||||
|
fn logical_size(&mut self) -> DiskFileResult<u64>;
|
||||||
|
/// Returns the physical size of the underlying file.
|
||||||
|
fn physical_size(&mut self) -> DiskFileResult<u64>;
|
||||||
fn new_async_io(&self, ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>>;
|
fn new_async_io(&self, ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>>;
|
||||||
fn topology(&mut self) -> DiskTopology {
|
fn topology(&mut self) -> DiskTopology {
|
||||||
DiskTopology::default()
|
DiskTopology::default()
|
||||||
}
|
}
|
||||||
|
fn resize(&mut self, _size: u64) -> DiskFileResult<()> {
|
||||||
|
Err(DiskFileError::Unsupported)
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the file descriptor of the underlying disk image file.
|
/// Returns the file descriptor of the underlying disk image file.
|
||||||
///
|
///
|
||||||
/// The file descriptor is supposed to be used for `fcntl()` calls but no
|
/// The file descriptor is supposed to be used for `fcntl()` calls but no
|
||||||
@@ -80,7 +97,7 @@ pub enum AsyncIoError {
|
|||||||
#[error("Failed synchronizing file")]
|
#[error("Failed synchronizing file")]
|
||||||
Fsync(#[source] std::io::Error),
|
Fsync(#[source] std::io::Error),
|
||||||
/// Failed submitting batch requests.
|
/// Failed submitting batch requests.
|
||||||
#[error("Failed submitting batch requests: {0}")]
|
#[error("Failed submitting batch requests")]
|
||||||
SubmitBatchRequests(#[source] std::io::Error),
|
SubmitBatchRequests(#[source] std::io::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -101,13 +101,52 @@ impl LockState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The granularity of the advisory lock.
|
||||||
|
///
|
||||||
|
/// The granularity has significant implications in typical cloud deployments
|
||||||
|
/// with network storage. The Linux kernel will sync advisory locks to network
|
||||||
|
/// file systems, but these backends may have different policies and handle
|
||||||
|
/// locks differently. For example, Netapp speaks a NFS API but will treat
|
||||||
|
/// advisory OFD locks for the whole file as mandatory locks, whereas byte-range
|
||||||
|
/// locks for the whole file will remain advisory [0].
|
||||||
|
///
|
||||||
|
/// As it is a valid use case to prevent multiple CHV instances from accessing
|
||||||
|
/// the same disk but disk management software (e.g., Cinder in OpenStack)
|
||||||
|
/// should be able to snapshot disks while VMs are running, we need special
|
||||||
|
/// control over the lock granularity. Therefore, it is a valid use case to lock
|
||||||
|
/// the whole byte range of a disk image without technically locking the whole
|
||||||
|
/// file - to get the best of both worlds.
|
||||||
|
///
|
||||||
|
/// [0] https://kb.netapp.com/on-prem/ontap/da/NAS/NAS-KBs/How_is_Mandatory_Locking_supported_for_NFSv4_on_ONTAP_9
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub enum LockGranularity {
|
||||||
|
WholeFile,
|
||||||
|
ByteRange(u64 /* from, inclusive */, u64 /* len */),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LockGranularity {
|
||||||
|
const fn l_start(self) -> u64 {
|
||||||
|
match self {
|
||||||
|
LockGranularity::WholeFile => 0,
|
||||||
|
LockGranularity::ByteRange(start, _) => start,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn l_len(self) -> u64 {
|
||||||
|
match self {
|
||||||
|
LockGranularity::WholeFile => 0, /* EOF */
|
||||||
|
LockGranularity::ByteRange(_, len) => len,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns a [`struct@libc::flock`] structure for the whole file.
|
/// Returns a [`struct@libc::flock`] structure for the whole file.
|
||||||
const fn get_flock(lock_type: LockType) -> libc::flock {
|
const fn get_flock(lock_type: LockType, granularity: LockGranularity) -> libc::flock {
|
||||||
libc::flock {
|
libc::flock {
|
||||||
l_type: lock_type.to_libc_val() as libc::c_short,
|
l_type: lock_type.to_libc_val() as libc::c_short,
|
||||||
l_whence: libc::SEEK_SET as libc::c_short,
|
l_whence: libc::SEEK_SET as libc::c_short,
|
||||||
l_start: 0,
|
l_start: granularity.l_start() as libc::c_long,
|
||||||
l_len: 0, /* EOF */
|
l_len: granularity.l_len() as libc::c_long,
|
||||||
l_pid: 0, /* filled by callee */
|
l_pid: 0, /* filled by callee */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -122,8 +161,13 @@ const fn get_flock(lock_type: LockType) -> libc::flock {
|
|||||||
/// - `file`: The file to acquire a lock for [`LockType`]. The file's state will
|
/// - `file`: The file to acquire a lock for [`LockType`]. The file's state will
|
||||||
/// be logically mutated, but not technically.
|
/// be logically mutated, but not technically.
|
||||||
/// - `lock_type`: The [`LockType`]
|
/// - `lock_type`: The [`LockType`]
|
||||||
pub fn try_acquire_lock<Fd: AsRawFd>(file: Fd, lock_type: LockType) -> Result<(), LockError> {
|
/// - `granularity`: The [`LockGranularity`].
|
||||||
let flock = get_flock(lock_type);
|
pub fn try_acquire_lock<Fd: AsRawFd>(
|
||||||
|
file: &Fd,
|
||||||
|
lock_type: LockType,
|
||||||
|
granularity: LockGranularity,
|
||||||
|
) -> Result<(), LockError> {
|
||||||
|
let flock = get_flock(lock_type, granularity);
|
||||||
|
|
||||||
let res = fcntl(file.as_raw_fd(), FcntlArg::F_OFD_SETLK(&flock));
|
let res = fcntl(file.as_raw_fd(), FcntlArg::F_OFD_SETLK(&flock));
|
||||||
match res {
|
match res {
|
||||||
@@ -146,8 +190,9 @@ pub fn try_acquire_lock<Fd: AsRawFd>(file: Fd, lock_type: LockType) -> Result<()
|
|||||||
///
|
///
|
||||||
/// # Parameters
|
/// # Parameters
|
||||||
/// - `file`: The file to clear all locks for [`LockType`].
|
/// - `file`: The file to clear all locks for [`LockType`].
|
||||||
pub fn clear_lock<Fd: AsRawFd>(file: Fd) -> Result<(), LockError> {
|
/// - `granularity`: The [`LockGranularity`].
|
||||||
try_acquire_lock(file, LockType::Unlock)
|
pub fn clear_lock<Fd: AsRawFd>(file: &Fd, granularity: LockGranularity) -> Result<(), LockError> {
|
||||||
|
try_acquire_lock(file, LockType::Unlock, granularity)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the current lock state using [`fcntl`] with respect to the given
|
/// Returns the current lock state using [`fcntl`] with respect to the given
|
||||||
@@ -155,8 +200,12 @@ pub fn clear_lock<Fd: AsRawFd>(file: Fd) -> Result<(), LockError> {
|
|||||||
///
|
///
|
||||||
/// # Parameters
|
/// # Parameters
|
||||||
/// - `file`: The file for which to get the lock state.
|
/// - `file`: The file for which to get the lock state.
|
||||||
pub fn get_lock_state<Fd: AsRawFd>(file: Fd) -> Result<LockState, LockError> {
|
/// - `granularity`: The [`LockGranularity`].
|
||||||
let mut flock = get_flock(LockType::Write);
|
pub fn get_lock_state<Fd: AsRawFd>(
|
||||||
|
file: &Fd,
|
||||||
|
granularity: LockGranularity,
|
||||||
|
) -> Result<LockState, LockError> {
|
||||||
|
let mut flock = get_flock(LockType::Write, granularity);
|
||||||
let res = fcntl(file.as_raw_fd(), FcntlArg::F_OFD_GETLK(&mut flock));
|
let res = fcntl(file.as_raw_fd(), FcntlArg::F_OFD_GETLK(&mut flock));
|
||||||
match res {
|
match res {
|
||||||
0 => {
|
0 => {
|
||||||
|
|||||||
@@ -75,9 +75,17 @@ impl Seek for FixedVhd {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl BlockBackend for FixedVhd {
|
impl BlockBackend for FixedVhd {
|
||||||
fn size(&self) -> std::result::Result<u64, crate::Error> {
|
fn logical_size(&self) -> Result<u64, crate::Error> {
|
||||||
Ok(self.size)
|
Ok(self.size)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the physical size of the underlying file.
|
||||||
|
fn physical_size(&self) -> Result<u64, crate::Error> {
|
||||||
|
self.file
|
||||||
|
.metadata()
|
||||||
|
.map(|m| m.len())
|
||||||
|
.map_err(crate::Error::GetFileMetadata)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Clone for FixedVhd {
|
impl Clone for FixedVhd {
|
||||||
|
|||||||
@@ -23,14 +23,22 @@ impl FixedVhdDiskAsync {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl DiskFile for FixedVhdDiskAsync {
|
impl DiskFile for FixedVhdDiskAsync {
|
||||||
fn size(&mut self) -> DiskFileResult<u64> {
|
fn logical_size(&mut self) -> DiskFileResult<u64> {
|
||||||
Ok(self.0.size().unwrap())
|
Ok(self.0.logical_size().unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn physical_size(&mut self) -> DiskFileResult<u64> {
|
||||||
|
Ok(self.0.physical_size().unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_async_io(&self, ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
|
fn new_async_io(&self, ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
|
||||||
Ok(Box::new(
|
Ok(Box::new(
|
||||||
FixedVhdAsync::new(self.0.as_raw_fd(), ring_depth, self.0.size().unwrap())
|
FixedVhdAsync::new(
|
||||||
.map_err(DiskFileError::NewAsyncIo)?,
|
self.0.as_raw_fd(),
|
||||||
|
ring_depth,
|
||||||
|
self.0.logical_size().unwrap(),
|
||||||
|
)
|
||||||
|
.map_err(DiskFileError::NewAsyncIo)?,
|
||||||
) as Box<dyn AsyncIo>)
|
) as Box<dyn AsyncIo>)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,13 +23,23 @@ impl FixedVhdDiskSync {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl DiskFile for FixedVhdDiskSync {
|
impl DiskFile for FixedVhdDiskSync {
|
||||||
fn size(&mut self) -> DiskFileResult<u64> {
|
fn logical_size(&mut self) -> DiskFileResult<u64> {
|
||||||
Ok(self.0.size().unwrap())
|
Ok(self.0.logical_size().unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn physical_size(&mut self) -> DiskFileResult<u64> {
|
||||||
|
self.0.physical_size().map_err(|e| {
|
||||||
|
let io_inner = match e {
|
||||||
|
crate::Error::GetFileMetadata(e) => e,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
DiskFileError::Size(io_inner)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_async_io(&self, _ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
|
fn new_async_io(&self, _ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
|
||||||
Ok(Box::new(
|
Ok(Box::new(
|
||||||
FixedVhdSync::new(self.0.as_raw_fd(), self.0.size().unwrap())
|
FixedVhdSync::new(self.0.as_raw_fd(), self.0.logical_size().unwrap())
|
||||||
.map_err(DiskFileError::NewAsyncIo)?,
|
.map_err(DiskFileError::NewAsyncIo)?,
|
||||||
) as Box<dyn AsyncIo>)
|
) as Box<dyn AsyncIo>)
|
||||||
}
|
}
|
||||||
|
|||||||
114
block/src/lib.rs
114
block/src/lib.rs
@@ -8,9 +8,6 @@
|
|||||||
//
|
//
|
||||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
extern crate log;
|
|
||||||
|
|
||||||
pub mod async_io;
|
pub mod async_io;
|
||||||
pub mod fcntl;
|
pub mod fcntl;
|
||||||
pub mod fixed_vhd;
|
pub mod fixed_vhd;
|
||||||
@@ -33,19 +30,20 @@ pub mod vhdx_sync;
|
|||||||
|
|
||||||
use std::alloc::{Layout, alloc_zeroed, dealloc};
|
use std::alloc::{Layout, alloc_zeroed, dealloc};
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
use std::fmt::Debug;
|
use std::fmt::{self, Debug};
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{self, IoSlice, IoSliceMut, Read, Seek, SeekFrom, Write};
|
use std::io::{self, IoSlice, IoSliceMut, Read, Seek, SeekFrom, Write};
|
||||||
use std::os::linux::fs::MetadataExt;
|
use std::os::linux::fs::MetadataExt;
|
||||||
use std::os::unix::io::AsRawFd;
|
use std::os::unix::io::AsRawFd;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::sync::Arc;
|
use std::str::FromStr;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
use std::{cmp, result};
|
use std::{cmp, result};
|
||||||
|
|
||||||
#[cfg(feature = "io_uring")]
|
#[cfg(feature = "io_uring")]
|
||||||
use io_uring::{IoUring, Probe, opcode};
|
use io_uring::{IoUring, Probe, opcode};
|
||||||
use libc::{S_IFBLK, S_IFMT, ioctl};
|
use libc::{S_IFBLK, S_IFMT, ioctl};
|
||||||
|
use log::{error, info, warn};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
@@ -83,8 +81,8 @@ pub enum Error {
|
|||||||
DetectImageType(#[source] std::io::Error),
|
DetectImageType(#[source] std::io::Error),
|
||||||
#[error("Failure in fixed vhd")]
|
#[error("Failure in fixed vhd")]
|
||||||
FixedVhdError(#[source] std::io::Error),
|
FixedVhdError(#[source] std::io::Error),
|
||||||
#[error("Getting a block's metadata fails for any reason")]
|
#[error("Getting a block's metadata failed")]
|
||||||
GetFileMetadata,
|
GetFileMetadata(#[source] std::io::Error),
|
||||||
#[error("The requested operation would cause a seek beyond disk end")]
|
#[error("The requested operation would cause a seek beyond disk end")]
|
||||||
InvalidOffset,
|
InvalidOffset,
|
||||||
#[error("Failure in qcow")]
|
#[error("Failure in qcow")]
|
||||||
@@ -99,7 +97,7 @@ pub enum Error {
|
|||||||
|
|
||||||
fn build_device_id(disk_path: &Path) -> result::Result<String, Error> {
|
fn build_device_id(disk_path: &Path) -> result::Result<String, Error> {
|
||||||
let blk_metadata = match disk_path.metadata() {
|
let blk_metadata = match disk_path.metadata() {
|
||||||
Err(_) => return Err(Error::GetFileMetadata),
|
Err(e) => return Err(Error::GetFileMetadata(e)),
|
||||||
Ok(m) => m,
|
Ok(m) => m,
|
||||||
};
|
};
|
||||||
// This is how kvmtool does it.
|
// This is how kvmtool does it.
|
||||||
@@ -123,7 +121,7 @@ pub fn build_serial(disk_path: &Path) -> Vec<u8> {
|
|||||||
// This will also zero out any leftover bytes.
|
// This will also zero out any leftover bytes.
|
||||||
let disk_id = m.as_bytes();
|
let disk_id = m.as_bytes();
|
||||||
let bytes_to_copy = cmp::min(disk_id.len(), VIRTIO_BLK_ID_BYTES as usize);
|
let bytes_to_copy = cmp::min(disk_id.len(), VIRTIO_BLK_ID_BYTES as usize);
|
||||||
default_serial[..bytes_to_copy].clone_from_slice(&disk_id[..bytes_to_copy])
|
default_serial[..bytes_to_copy].clone_from_slice(&disk_id[..bytes_to_copy]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default_serial
|
default_serial
|
||||||
@@ -139,7 +137,7 @@ pub enum ExecuteError {
|
|||||||
Read(#[source] GuestMemoryError),
|
Read(#[source] GuestMemoryError),
|
||||||
#[error("Failed to read_exact")]
|
#[error("Failed to read_exact")]
|
||||||
ReadExact(#[source] io::Error),
|
ReadExact(#[source] io::Error),
|
||||||
#[error("Can't execute an operation other than `read` on a read-only device")]
|
#[error("Can't execute an operation other than `read` or `get_id` on a read-only device")]
|
||||||
ReadOnly,
|
ReadOnly,
|
||||||
#[error("Failed to seek")]
|
#[error("Failed to seek")]
|
||||||
Seek(#[source] io::Error),
|
Seek(#[source] io::Error),
|
||||||
@@ -260,7 +258,7 @@ pub struct Request {
|
|||||||
impl Request {
|
impl Request {
|
||||||
pub fn parse<B: Bitmap + 'static>(
|
pub fn parse<B: Bitmap + 'static>(
|
||||||
desc_chain: &mut DescriptorChain<GuestMemoryLoadGuard<vm_memory::GuestMemoryMmap<B>>>,
|
desc_chain: &mut DescriptorChain<GuestMemoryLoadGuard<vm_memory::GuestMemoryMmap<B>>>,
|
||||||
access_platform: Option<&Arc<dyn AccessPlatform>>,
|
access_platform: Option<&dyn AccessPlatform>,
|
||||||
) -> result::Result<Request, Error> {
|
) -> result::Result<Request, Error> {
|
||||||
let hdr_desc = desc_chain
|
let hdr_desc = desc_chain
|
||||||
.next()
|
.next()
|
||||||
@@ -293,17 +291,10 @@ impl Request {
|
|||||||
.next()
|
.next()
|
||||||
.ok_or(Error::DescriptorChainTooShort)
|
.ok_or(Error::DescriptorChainTooShort)
|
||||||
.inspect_err(|_| {
|
.inspect_err(|_| {
|
||||||
error!("Only head descriptor present: request = {:?}", req);
|
error!("Only head descriptor present: request = {req:?}");
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
if !desc.has_next() {
|
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);
|
|
||||||
return Err(Error::DescriptorChainTooShort);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
req.data_descriptors.reserve_exact(1);
|
req.data_descriptors.reserve_exact(1);
|
||||||
while desc.has_next() {
|
while desc.has_next() {
|
||||||
if desc.is_write_only() && req.request_type == RequestType::Out {
|
if desc.is_write_only() && req.request_type == RequestType::Out {
|
||||||
@@ -325,10 +316,17 @@ impl Request {
|
|||||||
.next()
|
.next()
|
||||||
.ok_or(Error::DescriptorChainTooShort)
|
.ok_or(Error::DescriptorChainTooShort)
|
||||||
.inspect_err(|_| {
|
.inspect_err(|_| {
|
||||||
error!("DescriptorChain corrupted: request = {:?}", req);
|
error!("DescriptorChain corrupted: request = {req:?}");
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
status_desc = desc;
|
status_desc = desc;
|
||||||
|
} else {
|
||||||
|
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:?}");
|
||||||
|
return Err(Error::DescriptorChainTooShort);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The status MUST always be writable.
|
// The status MUST always be writable.
|
||||||
@@ -399,7 +397,7 @@ impl Request {
|
|||||||
.map_err(ExecuteError::Write)?;
|
.map_err(ExecuteError::Write)?;
|
||||||
}
|
}
|
||||||
RequestType::Unsupported(t) => return Err(ExecuteError::Unsupported(t)),
|
RequestType::Unsupported(t) => return Err(ExecuteError::Unsupported(t)),
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
Ok(len)
|
Ok(len)
|
||||||
}
|
}
|
||||||
@@ -441,14 +439,17 @@ impl Request {
|
|||||||
|
|
||||||
let origin_ptr = mem
|
let origin_ptr = mem
|
||||||
.get_slice(data_addr, data_len)
|
.get_slice(data_addr, data_len)
|
||||||
.map_err(ExecuteError::GetHostAddress)?
|
.map_err(ExecuteError::GetHostAddress)?;
|
||||||
.ptr_guard();
|
assert!(origin_ptr.len() >= data_len);
|
||||||
|
let origin_ptr = origin_ptr.ptr_guard();
|
||||||
|
|
||||||
// Verify the buffer alignment.
|
// Verify the buffer alignment.
|
||||||
// In case it's not properly aligned, an intermediate buffer is
|
// In case it's not properly aligned, an intermediate buffer is
|
||||||
// created with the correct alignment, and a copy from/to the
|
// created with the correct alignment, and a copy from/to the
|
||||||
// origin buffer is performed, depending on the type of operation.
|
// origin buffer is performed, depending on the type of operation.
|
||||||
let iov_base = if !(origin_ptr.as_ptr() as u64).is_multiple_of(SECTOR_SIZE) {
|
let iov_base = if (origin_ptr.as_ptr() as u64).is_multiple_of(SECTOR_SIZE) {
|
||||||
|
origin_ptr.as_ptr() as *mut libc::c_void
|
||||||
|
} else {
|
||||||
let layout = Layout::from_size_align(data_len, SECTOR_SIZE as usize).unwrap();
|
let layout = Layout::from_size_align(data_len, SECTOR_SIZE as usize).unwrap();
|
||||||
// SAFETY: layout has non-zero size
|
// SAFETY: layout has non-zero size
|
||||||
let aligned_ptr = unsafe { alloc_zeroed(layout) };
|
let aligned_ptr = unsafe { alloc_zeroed(layout) };
|
||||||
@@ -476,8 +477,6 @@ impl Request {
|
|||||||
});
|
});
|
||||||
|
|
||||||
aligned_ptr as *mut libc::c_void
|
aligned_ptr as *mut libc::c_void
|
||||||
} else {
|
|
||||||
origin_ptr.as_ptr() as *mut libc::c_void
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let iovec = libc::iovec {
|
let iovec = libc::iovec {
|
||||||
@@ -564,7 +563,7 @@ impl Request {
|
|||||||
aligned_operation.aligned_ptr as *const u8,
|
aligned_operation.aligned_ptr as *const u8,
|
||||||
aligned_operation.origin_ptr as *mut u8,
|
aligned_operation.origin_ptr as *mut u8,
|
||||||
aligned_operation.size,
|
aligned_operation.size,
|
||||||
)
|
);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -575,7 +574,7 @@ impl Request {
|
|||||||
dealloc(
|
dealloc(
|
||||||
aligned_operation.aligned_ptr as *mut u8,
|
aligned_operation.aligned_ptr as *mut u8,
|
||||||
aligned_operation.layout,
|
aligned_operation.layout,
|
||||||
)
|
);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -583,7 +582,7 @@ impl Request {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_writeback(&mut self, writeback: bool) {
|
pub fn set_writeback(&mut self, writeback: bool) {
|
||||||
self.writeback = writeback
|
self.writeback = writeback;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -646,7 +645,7 @@ pub fn block_io_uring_is_supported() -> bool {
|
|||||||
let io_uring = match IoUring::new(1) {
|
let io_uring = match IoUring::new(1) {
|
||||||
Ok(io_uring) => io_uring,
|
Ok(io_uring) => io_uring,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
info!("{} failed to create io_uring instance: {}", error_msg, e);
|
info!("{error_msg} failed to create io_uring instance: {e}");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -659,26 +658,26 @@ pub fn block_io_uring_is_supported() -> bool {
|
|||||||
match submitter.register_probe(&mut probe) {
|
match submitter.register_probe(&mut probe) {
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
info!("{} failed to register a probe: {}", error_msg, e);
|
info!("{error_msg} failed to register a probe: {e}");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check IORING_OP_FSYNC is supported
|
// Check IORING_OP_FSYNC is supported
|
||||||
if !probe.is_supported(opcode::Fsync::CODE) {
|
if !probe.is_supported(opcode::Fsync::CODE) {
|
||||||
info!("{} IORING_OP_FSYNC operation not supported", error_msg);
|
info!("{error_msg} IORING_OP_FSYNC operation not supported");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check IORING_OP_READV is supported
|
// Check IORING_OP_READV is supported
|
||||||
if !probe.is_supported(opcode::Readv::CODE) {
|
if !probe.is_supported(opcode::Readv::CODE) {
|
||||||
info!("{} IORING_OP_READV operation not supported", error_msg);
|
info!("{error_msg} IORING_OP_READV operation not supported");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check IORING_OP_WRITEV is supported
|
// Check IORING_OP_WRITEV is supported
|
||||||
if !probe.is_supported(opcode::Writev::CODE) {
|
if !probe.is_supported(opcode::Writev::CODE) {
|
||||||
info!("{} IORING_OP_WRITEV operation not supported", error_msg);
|
info!("{error_msg} IORING_OP_WRITEV operation not supported");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -790,11 +789,44 @@ pub trait AsyncAdaptor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, Default)]
|
||||||
pub enum ImageType {
|
pub enum ImageType {
|
||||||
FixedVhd,
|
FixedVhd,
|
||||||
Qcow2,
|
Qcow2,
|
||||||
Raw,
|
Raw,
|
||||||
Vhdx,
|
Vhdx,
|
||||||
|
#[default]
|
||||||
|
Unknown,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for ImageType {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
ImageType::FixedVhd => write!(f, "vhd"),
|
||||||
|
ImageType::Qcow2 => write!(f, "qcow2"),
|
||||||
|
ImageType::Raw => write!(f, "raw"),
|
||||||
|
ImageType::Vhdx => write!(f, "vhdx"),
|
||||||
|
ImageType::Unknown => write!(f, "unknown"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum ImageTypeParseError {
|
||||||
|
InvalidValue(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for ImageType {
|
||||||
|
type Err = ImageTypeParseError;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
match s.to_lowercase().as_str() {
|
||||||
|
"vhd" => Ok(ImageType::FixedVhd),
|
||||||
|
"qcow2" => Ok(ImageType::Qcow2),
|
||||||
|
"raw" => Ok(ImageType::Raw),
|
||||||
|
"vhdx" => Ok(ImageType::Vhdx),
|
||||||
|
_ => Err(ImageTypeParseError::InvalidValue(s.to_string())),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const QCOW_MAGIC: u32 = 0x5146_49fb;
|
const QCOW_MAGIC: u32 = 0x5146_49fb;
|
||||||
@@ -836,7 +868,14 @@ pub fn detect_image_type(f: &mut File) -> std::io::Result<ImageType> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub trait BlockBackend: Read + Write + Seek + Send + Debug {
|
pub trait BlockBackend: Read + Write + Seek + Send + Debug {
|
||||||
fn size(&self) -> Result<u64, Error>;
|
/// Returns the logical disk size a guest will see.
|
||||||
|
///
|
||||||
|
/// For raw formats, this is equal to [`Self::physical_size`]. For file formats
|
||||||
|
/// that wrap disk images in a container (e.g. QCOW2), this refers to the
|
||||||
|
/// effective size that the guest will see.
|
||||||
|
fn logical_size(&self) -> Result<u64, Error>;
|
||||||
|
/// Returns the physical size of the underlying file.
|
||||||
|
fn physical_size(&self) -> Result<u64, Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -863,6 +902,7 @@ ioctl_io_nr!(BLKPBSZGET, 0x12, 123);
|
|||||||
ioctl_io_nr!(BLKIOMIN, 0x12, 120);
|
ioctl_io_nr!(BLKIOMIN, 0x12, 120);
|
||||||
ioctl_io_nr!(BLKIOOPT, 0x12, 121);
|
ioctl_io_nr!(BLKIOOPT, 0x12, 121);
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
enum BlockSize {
|
enum BlockSize {
|
||||||
LogicalBlock,
|
LogicalBlock,
|
||||||
PhysicalBlock,
|
PhysicalBlock,
|
||||||
@@ -902,7 +942,7 @@ impl DiskTopology {
|
|||||||
};
|
};
|
||||||
if ret != 0 {
|
if ret != 0 {
|
||||||
return Err(std::io::Error::last_os_error());
|
return Err(std::io::Error::last_os_error());
|
||||||
};
|
}
|
||||||
|
|
||||||
Ok(block_size)
|
Ok(block_size)
|
||||||
}
|
}
|
||||||
|
|||||||
87
block/src/qcow/decoder.rs
Normal file
87
block/src/qcow/decoder.rs
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
// Copyright 2025 The Cloud Hypervisor Authors. All rights reserved.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum Error {
|
||||||
|
#[error("Zlib decompress error")]
|
||||||
|
ZlibDecompress(#[source] flate2::DecompressError),
|
||||||
|
#[error("Zlib unexpected status: {0:?}")]
|
||||||
|
ZlibUnexpectedStatus(flate2::Status),
|
||||||
|
#[error("Zstd decompress error")]
|
||||||
|
ZstdDecompress(#[source] std::io::Error),
|
||||||
|
#[error("Zstd: failed to fill buffer")]
|
||||||
|
ZstdFillBuffer(#[source] std::io::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
|
/// Generic trait for decoding zlib/zstd formats
|
||||||
|
pub trait Decoder {
|
||||||
|
fn decode(&self, input: &[u8], output: &mut [u8]) -> Result<usize>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct ZlibDecoder {}
|
||||||
|
|
||||||
|
impl Decoder for ZlibDecoder {
|
||||||
|
fn decode(&self, input: &[u8], output: &mut [u8]) -> Result<usize> {
|
||||||
|
use flate2::{Decompress, FlushDecompress, Status};
|
||||||
|
|
||||||
|
let mut decompressor = Decompress::new(false);
|
||||||
|
let status = decompressor
|
||||||
|
.decompress(input, output, FlushDecompress::Finish)
|
||||||
|
.map_err(Error::ZlibDecompress)?;
|
||||||
|
if status == Status::StreamEnd {
|
||||||
|
Ok(decompressor.total_out() as usize)
|
||||||
|
} else {
|
||||||
|
Err(Error::ZlibUnexpectedStatus(status))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct ZstdDecoder {}
|
||||||
|
|
||||||
|
impl Decoder for ZstdDecoder {
|
||||||
|
fn decode(&self, input: &[u8], output: &mut [u8]) -> Result<usize> {
|
||||||
|
use std::io::Read;
|
||||||
|
|
||||||
|
let mut decoder = zstd::stream::read::Decoder::new(input).map_err(Error::ZstdDecompress)?;
|
||||||
|
let decoded_size = decoder.read(output).map_err(Error::ZstdFillBuffer)?;
|
||||||
|
Ok(decoded_size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_zlib_decode() {
|
||||||
|
let d = ZlibDecoder::default();
|
||||||
|
let valid_input = vec![99, 96, 100, 98, 6, 0];
|
||||||
|
let mut output1 = vec![0; 4];
|
||||||
|
d.decode(&valid_input, &mut output1).unwrap();
|
||||||
|
assert_eq!(&output1, b"\x00\x01\x02\x03");
|
||||||
|
|
||||||
|
let invalid_input = vec![1, 2, 3, 4];
|
||||||
|
let mut output2 = vec![0; 1024];
|
||||||
|
d.decode(&invalid_input, &mut output2).unwrap_err();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_zstd_decode() {
|
||||||
|
let d = ZstdDecoder::default();
|
||||||
|
let valid_input = vec![40, 181, 47, 253, 32, 2, 17, 0, 0, 1, 254];
|
||||||
|
let mut output1 = vec![0; 2];
|
||||||
|
d.decode(&valid_input, &mut output1).unwrap();
|
||||||
|
assert_eq!(&output1, b"\x01\xfe");
|
||||||
|
|
||||||
|
let invalid_input = vec![1, 2, 3, 4];
|
||||||
|
let mut output2 = vec![0; 1024];
|
||||||
|
d.decode(&invalid_input, &mut output2).unwrap_err();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
//
|
//
|
||||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||||
|
|
||||||
|
mod decoder;
|
||||||
mod qcow_raw_file;
|
mod qcow_raw_file;
|
||||||
mod raw_file;
|
mod raw_file;
|
||||||
mod refcount;
|
mod refcount;
|
||||||
@@ -17,7 +18,8 @@ use std::os::fd::{AsRawFd, RawFd};
|
|||||||
use std::str;
|
use std::str;
|
||||||
|
|
||||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||||
use libc::{EINVAL, ENOSPC, ENOTSUP};
|
use libc::{EINVAL, EIO, ENOSPC};
|
||||||
|
use log::error;
|
||||||
use remain::sorted;
|
use remain::sorted;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use vmm_sys_util::file_traits::{FileSetLen, FileSync};
|
use vmm_sys_util::file_traits::{FileSetLen, FileSync};
|
||||||
@@ -25,6 +27,7 @@ use vmm_sys_util::seek_hole::SeekHole;
|
|||||||
use vmm_sys_util::write_zeroes::{PunchHole, WriteZeroesAt};
|
use vmm_sys_util::write_zeroes::{PunchHole, WriteZeroesAt};
|
||||||
|
|
||||||
use crate::BlockBackend;
|
use crate::BlockBackend;
|
||||||
|
use crate::qcow::decoder::{Decoder, ZlibDecoder, ZstdDecoder};
|
||||||
use crate::qcow::qcow_raw_file::QcowRawFile;
|
use crate::qcow::qcow_raw_file::QcowRawFile;
|
||||||
pub use crate::qcow::raw_file::RawFile;
|
pub use crate::qcow::raw_file::RawFile;
|
||||||
use crate::qcow::refcount::RefCount;
|
use crate::qcow::refcount::RefCount;
|
||||||
@@ -42,8 +45,6 @@ pub enum Error {
|
|||||||
BackingFileOpen(#[source] Box<Error>),
|
BackingFileOpen(#[source] Box<Error>),
|
||||||
#[error("Backing file name is too long: {0} bytes over")]
|
#[error("Backing file name is too long: {0} bytes over")]
|
||||||
BackingFileTooLong(usize),
|
BackingFileTooLong(usize),
|
||||||
#[error("Compressed blocks not supported")]
|
|
||||||
CompressedBlocksNotSupported,
|
|
||||||
#[error("Failed to evict cache")]
|
#[error("Failed to evict cache")]
|
||||||
EvictingCache(#[source] io::Error),
|
EvictingCache(#[source] io::Error),
|
||||||
#[error("File larger than max of {MAX_QCOW_FILE_SIZE}: {0}")]
|
#[error("File larger than max of {MAX_QCOW_FILE_SIZE}: {0}")]
|
||||||
@@ -110,6 +111,8 @@ pub enum Error {
|
|||||||
TooManyL1Entries(u64),
|
TooManyL1Entries(u64),
|
||||||
#[error("Ref count table too large: {0}")]
|
#[error("Ref count table too large: {0}")]
|
||||||
TooManyRefcounts(u64),
|
TooManyRefcounts(u64),
|
||||||
|
#[error("Unsupported compression type")]
|
||||||
|
UnsupportedCompressionType,
|
||||||
#[error("Unsupported refcount order")]
|
#[error("Unsupported refcount order")]
|
||||||
UnsupportedRefcountOrder,
|
UnsupportedRefcountOrder,
|
||||||
#[error("Unsupported version: {0}")]
|
#[error("Unsupported version: {0}")]
|
||||||
@@ -122,11 +125,18 @@ pub enum Error {
|
|||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, Error>;
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
pub enum ImageType {
|
pub enum ImageType {
|
||||||
Raw,
|
Raw,
|
||||||
Qcow2,
|
Qcow2,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum CompressionType {
|
||||||
|
Zlib,
|
||||||
|
Zstd,
|
||||||
|
}
|
||||||
|
|
||||||
// Maximum data size supported.
|
// Maximum data size supported.
|
||||||
const MAX_QCOW_FILE_SIZE: u64 = 0x01 << 44; // 16 TB.
|
const MAX_QCOW_FILE_SIZE: u64 = 0x01 << 44; // 16 TB.
|
||||||
|
|
||||||
@@ -153,15 +163,55 @@ const L1_TABLE_OFFSET_MASK: u64 = 0x00ff_ffff_ffff_fe00;
|
|||||||
const L2_TABLE_OFFSET_MASK: u64 = 0x00ff_ffff_ffff_fe00;
|
const L2_TABLE_OFFSET_MASK: u64 = 0x00ff_ffff_ffff_fe00;
|
||||||
// Flags
|
// Flags
|
||||||
const COMPRESSED_FLAG: u64 = 1 << 62;
|
const COMPRESSED_FLAG: u64 = 1 << 62;
|
||||||
|
const COMPRESSED_SECTOR_SIZE: u64 = 512;
|
||||||
const CLUSTER_USED_FLAG: u64 = 1 << 63;
|
const CLUSTER_USED_FLAG: u64 = 1 << 63;
|
||||||
const COMPATIBLE_FEATURES_LAZY_REFCOUNTS: u64 = 1;
|
const COMPATIBLE_FEATURES_LAZY_REFCOUNTS: u64 = 1;
|
||||||
|
|
||||||
|
// Compression types as defined in https://www.qemu.org/docs/master/interop/qcow2.html
|
||||||
|
const COMPRESSION_TYPE_ZLIB: u64 = 0; // zlib/deflate <https://www.ietf.org/rfc/rfc1951.txt>
|
||||||
|
const COMPRESSION_TYPE_ZSTD: u64 = 1; // zstd <http://github.com/facebook/zstd>
|
||||||
|
|
||||||
// The format supports a "header extension area", that crosvm does not use.
|
// The format supports a "header extension area", that crosvm does not use.
|
||||||
const QCOW_EMPTY_HEADER_EXTENSION_SIZE: u32 = 8;
|
const QCOW_EMPTY_HEADER_EXTENSION_SIZE: u32 = 8;
|
||||||
|
|
||||||
// Defined by the specification
|
// Defined by the specification
|
||||||
const MAX_BACKING_FILE_SIZE: u32 = 1023;
|
const MAX_BACKING_FILE_SIZE: u32 = 1023;
|
||||||
|
|
||||||
|
fn l2_entry_is_empty(l2_entry: u64) -> bool {
|
||||||
|
l2_entry == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn l2_entry_is_compressed(l2_entry: u64) -> bool {
|
||||||
|
l2_entry & COMPRESSED_FLAG != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get file offset and size of compressed cluster data
|
||||||
|
fn l2_entry_compressed_cluster_layout(l2_entry: u64, cluster_bits: u32) -> (u64, usize) {
|
||||||
|
let compressed_size_shift = 62 - (cluster_bits - 8);
|
||||||
|
let compressed_size_mask = (1 << (cluster_bits - 8)) - 1;
|
||||||
|
let compressed_cluster_addr = l2_entry & ((1 << compressed_size_shift) - 1);
|
||||||
|
let nsectors = (l2_entry >> compressed_size_shift & compressed_size_mask) + 1;
|
||||||
|
let compressed_cluster_size = ((nsectors * COMPRESSED_SECTOR_SIZE)
|
||||||
|
- (compressed_cluster_addr & (COMPRESSED_SECTOR_SIZE - 1)))
|
||||||
|
as usize;
|
||||||
|
(compressed_cluster_addr, compressed_cluster_size)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get file offset of standard (non-compressed) cluster
|
||||||
|
fn l2_entry_std_cluster_addr(l2_entry: u64) -> u64 {
|
||||||
|
l2_entry & L2_TABLE_OFFSET_MASK
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make L2 entry for standard (non-compressed) cluster
|
||||||
|
fn l2_entry_make_std(cluster_addr: u64) -> u64 {
|
||||||
|
(cluster_addr & L2_TABLE_OFFSET_MASK) | CLUSTER_USED_FLAG
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make L1 entry with optional flags
|
||||||
|
fn l1_entry_make(cluster_addr: u64, refcount_is_one: bool) -> u64 {
|
||||||
|
(cluster_addr & L1_TABLE_OFFSET_MASK) | (refcount_is_one as u64 * CLUSTER_USED_FLAG)
|
||||||
|
}
|
||||||
|
|
||||||
/// Contains the information from the header of a qcow file.
|
/// Contains the information from the header of a qcow file.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct QcowHeader {
|
pub struct QcowHeader {
|
||||||
@@ -190,6 +240,7 @@ pub struct QcowHeader {
|
|||||||
pub autoclear_features: u64,
|
pub autoclear_features: u64,
|
||||||
pub refcount_order: u32,
|
pub refcount_order: u32,
|
||||||
pub header_size: u32,
|
pub header_size: u32,
|
||||||
|
pub compression_type: CompressionType,
|
||||||
|
|
||||||
// Post-header entries
|
// Post-header entries
|
||||||
pub backing_file_path: Option<String>,
|
pub backing_file_path: Option<String>,
|
||||||
@@ -255,8 +306,19 @@ impl QcowHeader {
|
|||||||
} else {
|
} else {
|
||||||
read_u32_from_file(f)?
|
read_u32_from_file(f)?
|
||||||
},
|
},
|
||||||
|
compression_type: CompressionType::Zlib,
|
||||||
backing_file_path: None,
|
backing_file_path: None,
|
||||||
};
|
};
|
||||||
|
if version == 3 && header.header_size > V3_BARE_HEADER_SIZE {
|
||||||
|
let raw_compression_type = read_u64_from_file(f)? >> (64 - 8);
|
||||||
|
header.compression_type = if raw_compression_type == COMPRESSION_TYPE_ZLIB {
|
||||||
|
Ok(CompressionType::Zlib)
|
||||||
|
} else if raw_compression_type == COMPRESSION_TYPE_ZSTD {
|
||||||
|
Ok(CompressionType::Zstd)
|
||||||
|
} else {
|
||||||
|
Err(Error::UnsupportedCompressionType)
|
||||||
|
}?;
|
||||||
|
}
|
||||||
if header.backing_file_size > MAX_BACKING_FILE_SIZE {
|
if header.backing_file_size > MAX_BACKING_FILE_SIZE {
|
||||||
return Err(Error::BackingFileTooLong(header.backing_file_size as usize));
|
return Err(Error::BackingFileTooLong(header.backing_file_size as usize));
|
||||||
}
|
}
|
||||||
@@ -274,6 +336,13 @@ impl QcowHeader {
|
|||||||
Ok(header)
|
Ok(header)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_decoder(&self) -> Box<dyn Decoder> {
|
||||||
|
match self.compression_type {
|
||||||
|
CompressionType::Zlib => Box::new(ZlibDecoder {}),
|
||||||
|
CompressionType::Zstd => Box::new(ZstdDecoder {}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn create_for_size_and_path(
|
pub fn create_for_size_and_path(
|
||||||
version: u32,
|
version: u32,
|
||||||
size: u64,
|
size: u64,
|
||||||
@@ -337,6 +406,7 @@ impl QcowHeader {
|
|||||||
autoclear_features: 0,
|
autoclear_features: 0,
|
||||||
refcount_order: DEFAULT_REFCOUNT_ORDER,
|
refcount_order: DEFAULT_REFCOUNT_ORDER,
|
||||||
header_size,
|
header_size,
|
||||||
|
compression_type: CompressionType::Zlib,
|
||||||
backing_file_path: backing_file.map(String::from),
|
backing_file_path: backing_file.map(String::from),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -588,17 +658,6 @@ impl QcowFile {
|
|||||||
|
|
||||||
let l2_entries = cluster_size / size_of::<u64>() as u64;
|
let l2_entries = cluster_size / size_of::<u64>() as u64;
|
||||||
|
|
||||||
// Check for compressed blocks
|
|
||||||
for l2_addr_disk in l1_table.get_values() {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut qcow = QcowFile {
|
let mut qcow = QcowFile {
|
||||||
raw_file,
|
raw_file,
|
||||||
header,
|
header,
|
||||||
@@ -630,7 +689,7 @@ impl QcowFile {
|
|||||||
/// Creates a new QcowFile at the given path.
|
/// Creates a new QcowFile at the given path.
|
||||||
pub fn new(file: RawFile, version: u32, virtual_size: u64) -> Result<QcowFile> {
|
pub fn new(file: RawFile, version: u32, virtual_size: u64) -> Result<QcowFile> {
|
||||||
let header = QcowHeader::create_for_size_and_path(version, virtual_size, None)?;
|
let header = QcowHeader::create_for_size_and_path(version, virtual_size, None)?;
|
||||||
QcowFile::new_from_header(file, header)
|
QcowFile::new_from_header(file, &header)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new QcowFile at the given path.
|
/// Creates a new QcowFile at the given path.
|
||||||
@@ -652,12 +711,12 @@ impl QcowFile {
|
|||||||
.map_err(|e| Error::BackingFileOpen(Box::new(e)))?;
|
.map_err(|e| Error::BackingFileOpen(Box::new(e)))?;
|
||||||
let size = backing_file.virtual_size();
|
let size = backing_file.virtual_size();
|
||||||
let header = QcowHeader::create_for_size_and_path(version, size, Some(backing_file_name))?;
|
let header = QcowHeader::create_for_size_and_path(version, size, Some(backing_file_name))?;
|
||||||
let mut result = QcowFile::new_from_header(file, header)?;
|
let mut result = QcowFile::new_from_header(file, &header)?;
|
||||||
result.backing_file = Some(Box::new(backing_file));
|
result.backing_file = Some(Box::new(backing_file));
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_from_header(mut file: RawFile, header: QcowHeader) -> Result<QcowFile> {
|
fn new_from_header(mut file: RawFile, header: &QcowHeader) -> Result<QcowFile> {
|
||||||
file.rewind().map_err(Error::SeekingFile)?;
|
file.rewind().map_err(Error::SeekingFile)?;
|
||||||
header.write_to(&mut file)?;
|
header.write_to(&mut file)?;
|
||||||
|
|
||||||
@@ -714,11 +773,7 @@ impl QcowFile {
|
|||||||
let raw_file = &mut self.raw_file;
|
let raw_file = &mut self.raw_file;
|
||||||
self.l2_cache
|
self.l2_cache
|
||||||
.insert(l1_index, table, |index, evicted| {
|
.insert(l1_index, table, |index, evicted| {
|
||||||
raw_file.write_pointer_table(
|
raw_file.write_pointer_table_direct(l1_table[index], evicted.iter())
|
||||||
l1_table[index],
|
|
||||||
evicted.get_values(),
|
|
||||||
CLUSTER_USED_FLAG,
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
.map_err(Error::EvictingCache)?;
|
.map_err(Error::EvictingCache)?;
|
||||||
}
|
}
|
||||||
@@ -805,7 +860,7 @@ impl QcowFile {
|
|||||||
// Add references to the L1 table clusters.
|
// Add references to the L1 table clusters.
|
||||||
fn set_l1_refcounts(
|
fn set_l1_refcounts(
|
||||||
refcounts: &mut [u16],
|
refcounts: &mut [u16],
|
||||||
header: QcowHeader,
|
header: &QcowHeader,
|
||||||
cluster_size: u64,
|
cluster_size: u64,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let entries_per_cluster = cluster_size / size_of::<u64>() as u64;
|
let entries_per_cluster = cluster_size / size_of::<u64>() as u64;
|
||||||
@@ -820,7 +875,7 @@ impl QcowFile {
|
|||||||
// Traverse the L1 and L2 tables to find all reachable data clusters.
|
// Traverse the L1 and L2 tables to find all reachable data clusters.
|
||||||
fn set_data_refcounts(
|
fn set_data_refcounts(
|
||||||
refcounts: &mut [u16],
|
refcounts: &mut [u16],
|
||||||
header: QcowHeader,
|
header: &QcowHeader,
|
||||||
cluster_size: u64,
|
cluster_size: u64,
|
||||||
raw_file: &mut QcowRawFile,
|
raw_file: &mut QcowRawFile,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
@@ -859,7 +914,7 @@ impl QcowFile {
|
|||||||
// Add references to the top-level refcount table clusters.
|
// Add references to the top-level refcount table clusters.
|
||||||
fn set_refcount_table_refcounts(
|
fn set_refcount_table_refcounts(
|
||||||
refcounts: &mut [u16],
|
refcounts: &mut [u16],
|
||||||
header: QcowHeader,
|
header: &QcowHeader,
|
||||||
cluster_size: u64,
|
cluster_size: u64,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let refcount_table_offset = header.refcount_table_offset;
|
let refcount_table_offset = header.refcount_table_offset;
|
||||||
@@ -943,7 +998,7 @@ impl QcowFile {
|
|||||||
|
|
||||||
// Rewrite the top-level refcount table.
|
// Rewrite the top-level refcount table.
|
||||||
raw_file
|
raw_file
|
||||||
.write_pointer_table(header.refcount_table_offset, ref_table, 0)
|
.write_pointer_table_direct(header.refcount_table_offset, ref_table.iter())
|
||||||
.map_err(Error::WritingHeader)?;
|
.map_err(Error::WritingHeader)?;
|
||||||
|
|
||||||
// Rewrite the header again, now with lazy refcounts disabled.
|
// Rewrite the header again, now with lazy refcounts disabled.
|
||||||
@@ -997,9 +1052,9 @@ impl QcowFile {
|
|||||||
|
|
||||||
// Find all references clusters and rebuild refcounts.
|
// Find all references clusters and rebuild refcounts.
|
||||||
set_header_refcount(&mut refcounts, cluster_size)?;
|
set_header_refcount(&mut refcounts, cluster_size)?;
|
||||||
set_l1_refcounts(&mut refcounts, header.clone(), cluster_size)?;
|
set_l1_refcounts(&mut refcounts, &header, cluster_size)?;
|
||||||
set_data_refcounts(&mut refcounts, header.clone(), cluster_size, raw_file)?;
|
set_data_refcounts(&mut refcounts, &header, cluster_size, raw_file)?;
|
||||||
set_refcount_table_refcounts(&mut refcounts, header.clone(), cluster_size)?;
|
set_refcount_table_refcounts(&mut refcounts, &header, cluster_size)?;
|
||||||
|
|
||||||
// Allocate clusters to store the new reference count blocks.
|
// Allocate clusters to store the new reference count blocks.
|
||||||
let ref_table = alloc_refblocks(&mut refcounts, cluster_size, refblock_clusters)?;
|
let ref_table = alloc_refblocks(&mut refcounts, cluster_size, refblock_clusters)?;
|
||||||
@@ -1050,11 +1105,40 @@ impl QcowFile {
|
|||||||
(address / self.raw_file.cluster_size()) % self.l2_entries
|
(address / self.raw_file.cluster_size()) % self.l2_entries
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gets the offset of the given guest address in the host file. If L1, L2, or data clusters have
|
// Decompress the cluster, return EIO on failure
|
||||||
// yet to be allocated, return None.
|
fn decompress_l2_cluster(&mut self, l2_entry: u64) -> std::io::Result<Vec<u8>> {
|
||||||
fn file_offset_read(&mut self, address: u64) -> std::io::Result<Option<u64>> {
|
let (compressed_cluster_addr, compressed_cluster_size) =
|
||||||
|
l2_entry_compressed_cluster_layout(l2_entry, self.header.cluster_bits);
|
||||||
|
// Read compressed cluster from raw file
|
||||||
|
self.raw_file
|
||||||
|
.file_mut()
|
||||||
|
.seek(SeekFrom::Start(compressed_cluster_addr))?;
|
||||||
|
let mut compressed_cluster = vec![0; compressed_cluster_size];
|
||||||
|
self.raw_file
|
||||||
|
.file_mut()
|
||||||
|
.read_exact(&mut compressed_cluster)?;
|
||||||
|
let decoder = self.header.get_decoder();
|
||||||
|
// Decompress
|
||||||
|
let cluster_size = self.raw_file.cluster_size() as usize;
|
||||||
|
let mut decompressed_cluster = vec![0; cluster_size];
|
||||||
|
let decompressed_size = decoder
|
||||||
|
.decode(&compressed_cluster, &mut decompressed_cluster)
|
||||||
|
.map_err(|_| std::io::Error::from_raw_os_error(EIO))?;
|
||||||
|
if decompressed_size as u64 != self.raw_file.cluster_size() {
|
||||||
|
return Err(std::io::Error::from_raw_os_error(EIO));
|
||||||
|
}
|
||||||
|
Ok(decompressed_cluster)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn file_read(
|
||||||
|
&mut self,
|
||||||
|
address: u64,
|
||||||
|
count: usize,
|
||||||
|
buf: &mut [u8],
|
||||||
|
) -> std::io::Result<Option<()>> {
|
||||||
|
let err_inval = std::io::Error::from_raw_os_error(EINVAL);
|
||||||
if address >= self.virtual_size() {
|
if address >= self.virtual_size() {
|
||||||
return Err(std::io::Error::from_raw_os_error(EINVAL));
|
return Err(err_inval);
|
||||||
}
|
}
|
||||||
|
|
||||||
let l1_index = self.l1_table_index(address) as usize;
|
let l1_index = self.l1_table_index(address) as usize;
|
||||||
@@ -1070,27 +1154,30 @@ impl QcowFile {
|
|||||||
|
|
||||||
let l2_index = self.l2_table_index(address) as usize;
|
let l2_index = self.l2_table_index(address) as usize;
|
||||||
|
|
||||||
if !self.l2_cache.contains_key(l1_index) {
|
self.cache_l2_cluster(l1_index, l2_addr_disk, false)?;
|
||||||
// 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 l2_entry = self.l2_cache.get(l1_index).unwrap()[l2_index];
|
||||||
let raw_file = &mut self.raw_file;
|
if l2_entry_is_empty(l2_entry) {
|
||||||
self.l2_cache.insert(l1_index, table, |index, evicted| {
|
// Reading from an unallocated cluster will return zeros.
|
||||||
raw_file.write_pointer_table(
|
|
||||||
l1_table[index],
|
|
||||||
evicted.get_values(),
|
|
||||||
CLUSTER_USED_FLAG,
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
};
|
|
||||||
|
|
||||||
let cluster_addr = self.l2_cache.get(l1_index).unwrap()[l2_index];
|
|
||||||
if cluster_addr == 0 {
|
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
|
} else if l2_entry_is_compressed(l2_entry) {
|
||||||
|
// Compressed cluster.
|
||||||
|
// Read it, decompress, then return slice from decompressed data.
|
||||||
|
let mut decompressed_cluster = self.decompress_l2_cluster(l2_entry)?;
|
||||||
|
decompressed_cluster.resize(self.raw_file.cluster_size() as usize, 0);
|
||||||
|
let start = self.raw_file.cluster_offset(address) as usize;
|
||||||
|
let end = start.checked_add(count);
|
||||||
|
if end.is_none() || end.unwrap() > decompressed_cluster.len() {
|
||||||
|
return Err(err_inval);
|
||||||
|
}
|
||||||
|
buf[..count].copy_from_slice(&decompressed_cluster[start..end.unwrap()]);
|
||||||
|
} else {
|
||||||
|
let start = l2_entry_std_cluster_addr(l2_entry) + self.raw_file.cluster_offset(address);
|
||||||
|
let raw_file = self.raw_file.file_mut();
|
||||||
|
raw_file.seek(SeekFrom::Start(start))?;
|
||||||
|
raw_file.read_exact(buf)?;
|
||||||
}
|
}
|
||||||
Ok(Some(cluster_addr + self.raw_file.cluster_offset(address)))
|
Ok(Some(()))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gets the offset of the given guest address in the host file. If L1, L2, or data clusters need
|
// Gets the offset of the given guest address in the host file. If L1, L2, or data clusters need
|
||||||
@@ -1109,53 +1196,72 @@ impl QcowFile {
|
|||||||
|
|
||||||
let mut set_refcounts = Vec::new();
|
let mut set_refcounts = Vec::new();
|
||||||
|
|
||||||
if !self.l2_cache.contains_key(l1_index) {
|
if let Some(new_addr) = self.cache_l2_cluster(l1_index, l2_addr_disk, true)? {
|
||||||
// Not in the cache.
|
// The cluster refcount starts at one meaning it is used but doesn't need COW.
|
||||||
let l2_table = if l2_addr_disk == 0 {
|
set_refcounts.push((new_addr, 1));
|
||||||
// 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,
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let cluster_addr = match self.l2_cache.get(l1_index).unwrap()[l2_index] {
|
let l2_entry = self.l2_cache.get(l1_index).unwrap()[l2_index];
|
||||||
0 => {
|
let cluster_addr = if l2_entry_is_compressed(l2_entry) {
|
||||||
let initial_data = if let Some(backing) = self.backing_file.as_mut() {
|
// Writing to compressed cluster.
|
||||||
let cluster_size = self.raw_file.cluster_size();
|
|
||||||
let cluster_begin = address - (address % cluster_size);
|
let (compressed_cluster_addr, compressed_cluster_size) =
|
||||||
let mut cluster_data = vec![0u8; cluster_size as usize];
|
l2_entry_compressed_cluster_layout(l2_entry, self.header.cluster_bits);
|
||||||
backing.seek(SeekFrom::Start(cluster_begin))?;
|
|
||||||
backing.read_exact(&mut cluster_data)?;
|
// Allocate new cluster, decompress into new cluster, then use
|
||||||
Some(cluster_data)
|
// offset of new cluster.
|
||||||
} else {
|
let decompressed_cluster = self.decompress_l2_cluster(l2_entry)?;
|
||||||
None
|
let cluster_addr = self.append_data_cluster(None)?;
|
||||||
};
|
self.update_cluster_addr(l1_index, l2_index, cluster_addr, &mut set_refcounts)?;
|
||||||
// Need to allocate a data cluster
|
self.raw_file
|
||||||
let cluster_addr = self.append_data_cluster(initial_data)?;
|
.file_mut()
|
||||||
self.update_cluster_addr(l1_index, l2_index, cluster_addr, &mut set_refcounts)?;
|
.seek(SeekFrom::Start(cluster_addr))?;
|
||||||
cluster_addr
|
let nwritten = self.raw_file.file_mut().write(&decompressed_cluster)?;
|
||||||
|
if nwritten != decompressed_cluster.len() {
|
||||||
|
return Err(std::io::Error::from_raw_os_error(EIO));
|
||||||
}
|
}
|
||||||
a => a,
|
|
||||||
|
// Decrement refcount for each cluster spanned by the old compressed data
|
||||||
|
let compressed_clusters_end = self.raw_file.cluster_address(
|
||||||
|
compressed_cluster_addr // Start of compressed data
|
||||||
|
+ compressed_cluster_size as u64 // Add size to get end address
|
||||||
|
+ self.raw_file.cluster_size()
|
||||||
|
- 1, // Catch possibly partially used last cluster
|
||||||
|
);
|
||||||
|
let mut addr = self.raw_file.cluster_address(compressed_cluster_addr);
|
||||||
|
while addr < compressed_clusters_end {
|
||||||
|
let refcount = self
|
||||||
|
.refcounts
|
||||||
|
.get_cluster_refcount(&mut self.raw_file, addr)
|
||||||
|
.map_err(|e| std::io::Error::other(Error::GettingRefcount(e)))?;
|
||||||
|
if refcount > 0 {
|
||||||
|
self.set_cluster_refcount_track_freed(addr, refcount - 1)?;
|
||||||
|
}
|
||||||
|
addr += self.raw_file.cluster_size();
|
||||||
|
}
|
||||||
|
|
||||||
|
cluster_addr
|
||||||
|
} else if l2_entry_is_empty(l2_entry) {
|
||||||
|
let initial_data = if let Some(backing) = self.backing_file.as_mut() {
|
||||||
|
let cluster_size = self.raw_file.cluster_size();
|
||||||
|
let cluster_begin = address - (address % cluster_size);
|
||||||
|
let mut cluster_data = vec![0u8; cluster_size as usize];
|
||||||
|
backing.seek(SeekFrom::Start(cluster_begin))?;
|
||||||
|
backing.read_exact(&mut cluster_data)?;
|
||||||
|
Some(cluster_data)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
// Need to allocate a data cluster
|
||||||
|
let cluster_addr = self.append_data_cluster(initial_data)?;
|
||||||
|
self.update_cluster_addr(l1_index, l2_index, cluster_addr, &mut set_refcounts)?;
|
||||||
|
cluster_addr
|
||||||
|
} else {
|
||||||
|
l2_entry_std_cluster_addr(l2_entry)
|
||||||
};
|
};
|
||||||
|
|
||||||
for (addr, count) in set_refcounts {
|
for (addr, count) in set_refcounts {
|
||||||
let mut newly_unref = self.set_cluster_refcount(addr, count)?;
|
self.set_cluster_refcount_track_freed(addr, count)?;
|
||||||
self.unref_clusters.append(&mut newly_unref);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(cluster_addr + self.raw_file.cluster_offset(address))
|
Ok(cluster_addr + self.raw_file.cluster_offset(address))
|
||||||
@@ -1190,7 +1296,7 @@ impl QcowFile {
|
|||||||
self.l1_table[l1_index] = new_addr;
|
self.l1_table[l1_index] = new_addr;
|
||||||
}
|
}
|
||||||
// 'unwrap' is OK because it was just added.
|
// 'unwrap' is OK because it was just added.
|
||||||
self.l2_cache.get_mut(l1_index).unwrap()[l2_index] = cluster_addr;
|
self.l2_cache.get_mut(l1_index).unwrap()[l2_index] = l2_entry_make_std(cluster_addr);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1199,7 +1305,7 @@ impl QcowFile {
|
|||||||
// First use a pre allocated cluster if one is available.
|
// First use a pre allocated cluster if one is available.
|
||||||
if let Some(free_cluster) = self.avail_clusters.pop() {
|
if let Some(free_cluster) = self.avail_clusters.pop() {
|
||||||
if let Some(initial_data) = initial_data {
|
if let Some(initial_data) = initial_data {
|
||||||
self.raw_file.write_cluster(free_cluster, initial_data)?;
|
self.raw_file.write_cluster(free_cluster, &initial_data)?;
|
||||||
} else {
|
} else {
|
||||||
self.raw_file.zero_cluster(free_cluster)?;
|
self.raw_file.zero_cluster(free_cluster)?;
|
||||||
}
|
}
|
||||||
@@ -1209,7 +1315,7 @@ impl QcowFile {
|
|||||||
let max_valid_cluster_offset = self.refcounts.max_valid_cluster_offset();
|
let max_valid_cluster_offset = self.refcounts.max_valid_cluster_offset();
|
||||||
if let Some(new_cluster) = self.raw_file.add_cluster_end(max_valid_cluster_offset)? {
|
if let Some(new_cluster) = self.raw_file.add_cluster_end(max_valid_cluster_offset)? {
|
||||||
if let Some(initial_data) = initial_data {
|
if let Some(initial_data) = initial_data {
|
||||||
self.raw_file.write_cluster(new_cluster, initial_data)?;
|
self.raw_file.write_cluster(new_cluster, &initial_data)?;
|
||||||
}
|
}
|
||||||
Ok(new_cluster)
|
Ok(new_cluster)
|
||||||
} else {
|
} else {
|
||||||
@@ -1223,8 +1329,7 @@ impl QcowFile {
|
|||||||
fn append_data_cluster(&mut self, initial_data: Option<Vec<u8>>) -> std::io::Result<u64> {
|
fn append_data_cluster(&mut self, initial_data: Option<Vec<u8>>) -> std::io::Result<u64> {
|
||||||
let new_addr: u64 = self.get_new_cluster(initial_data)?;
|
let new_addr: u64 = self.get_new_cluster(initial_data)?;
|
||||||
// The cluster refcount starts at one indicating it is used but doesn't need COW.
|
// The cluster refcount starts at one indicating it is used but doesn't need COW.
|
||||||
let mut newly_unref = self.set_cluster_refcount(new_addr, 1)?;
|
self.set_cluster_refcount_track_freed(new_addr, 1)?;
|
||||||
self.unref_clusters.append(&mut newly_unref);
|
|
||||||
Ok(new_addr)
|
Ok(new_addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1247,20 +1352,7 @@ impl QcowFile {
|
|||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if !self.l2_cache.contains_key(l1_index) {
|
self.cache_l2_cluster(l1_index, l2_addr_disk, false)?;
|
||||||
// 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,
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let cluster_addr = self.l2_cache.get(l1_index).unwrap()[l2_index];
|
let cluster_addr = self.l2_cache.get(l1_index).unwrap()[l2_index];
|
||||||
// If cluster_addr != 0, the cluster is allocated.
|
// If cluster_addr != 0, the cluster is allocated.
|
||||||
@@ -1319,20 +1411,7 @@ impl QcowFile {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
if !self.l2_cache.contains_key(l1_index) {
|
self.cache_l2_cluster(l1_index, l2_addr_disk, false)?;
|
||||||
// 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,
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let cluster_addr = self.l2_cache.get(l1_index).unwrap()[l2_index];
|
let cluster_addr = self.l2_cache.get(l1_index).unwrap()[l2_index];
|
||||||
if cluster_addr == 0 {
|
if cluster_addr == 0 {
|
||||||
@@ -1355,8 +1434,7 @@ impl QcowFile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let new_refcount = refcount - 1;
|
let new_refcount = refcount - 1;
|
||||||
let mut newly_unref = self.set_cluster_refcount(cluster_addr, new_refcount)?;
|
self.set_cluster_refcount_track_freed(cluster_addr, new_refcount)?;
|
||||||
self.unref_clusters.append(&mut newly_unref);
|
|
||||||
|
|
||||||
// Rewrite the L2 entry to remove the cluster mapping.
|
// Rewrite the L2 entry to remove the cluster mapping.
|
||||||
// unwrap is safe as we just checked/inserted this entry.
|
// unwrap is safe as we just checked/inserted this entry.
|
||||||
@@ -1393,10 +1471,9 @@ impl QcowFile {
|
|||||||
// Partial cluster - zero out the relevant bytes if it was allocated.
|
// Partial cluster - zero out the relevant bytes if it was allocated.
|
||||||
// Any space in unallocated clusters can be left alone, since
|
// Any space in unallocated clusters can be left alone, since
|
||||||
// unallocated clusters already read back as zeroes.
|
// unallocated clusters already read back as zeroes.
|
||||||
if let Some(offset) = self.file_offset_read(curr_addr)? {
|
let offset = self.file_offset_write(curr_addr)?;
|
||||||
// Partial cluster - zero it out.
|
// Partial cluster - zero it out.
|
||||||
self.raw_file.file_mut().write_zeroes_at(offset, count)?;
|
self.raw_file.file_mut().write_zeroes_at(offset, count)?;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
nwritten += count;
|
nwritten += count;
|
||||||
@@ -1407,14 +1484,50 @@ impl QcowFile {
|
|||||||
// Reads an L2 cluster from the disk, returning an error if the file can't be read or if any
|
// Reads an L2 cluster from the disk, returning an error if the file can't be read or if any
|
||||||
// cluster is compressed.
|
// cluster is compressed.
|
||||||
fn read_l2_cluster(raw_file: &mut QcowRawFile, cluster_addr: u64) -> std::io::Result<Vec<u64>> {
|
fn read_l2_cluster(raw_file: &mut QcowRawFile, cluster_addr: u64) -> std::io::Result<Vec<u64>> {
|
||||||
let file_values = raw_file.read_pointer_cluster(cluster_addr, None)?;
|
let l2_table = raw_file.read_pointer_cluster(cluster_addr, None)?;
|
||||||
if file_values.iter().any(|entry| entry & COMPRESSED_FLAG != 0) {
|
Ok(l2_table)
|
||||||
return Err(std::io::Error::from_raw_os_error(ENOTSUP));
|
}
|
||||||
|
|
||||||
|
// 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_direct(l1_table[index], evicted.iter())
|
||||||
|
})?;
|
||||||
}
|
}
|
||||||
Ok(file_values
|
Ok(new_cluster)
|
||||||
.iter()
|
}
|
||||||
.map(|entry| *entry & L2_TABLE_OFFSET_MASK)
|
|
||||||
.collect())
|
// Set the refcount for a cluster and add any unreferenced clusters to the unref list.
|
||||||
|
fn set_cluster_refcount_track_freed(
|
||||||
|
&mut self,
|
||||||
|
address: u64,
|
||||||
|
refcount: u16,
|
||||||
|
) -> std::io::Result<()> {
|
||||||
|
let mut newly_unref = self.set_cluster_refcount(address, refcount)?;
|
||||||
|
self.unref_clusters.append(&mut newly_unref);
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the refcount for a cluster with the given address.
|
// Set the refcount for a cluster with the given address.
|
||||||
@@ -1437,7 +1550,9 @@ impl QcowFile {
|
|||||||
refcount_set = true;
|
refcount_set = true;
|
||||||
}
|
}
|
||||||
Ok(Some(freed_cluster)) => {
|
Ok(Some(freed_cluster)) => {
|
||||||
unref_clusters.push(freed_cluster);
|
// Recursively set the freed refcount block's refcount to 0
|
||||||
|
let mut freed = self.set_cluster_refcount(freed_cluster, 0)?;
|
||||||
|
unref_clusters.append(&mut freed);
|
||||||
refcount_set = true;
|
refcount_set = true;
|
||||||
}
|
}
|
||||||
Err(refcount::Error::EvictingRefCounts(e)) => {
|
Err(refcount::Error::EvictingRefCounts(e)) => {
|
||||||
@@ -1480,11 +1595,8 @@ impl QcowFile {
|
|||||||
// The index must be valid from when we inserted it.
|
// The index must be valid from when we inserted it.
|
||||||
let addr = self.l1_table[*l1_index];
|
let addr = self.l1_table[*l1_index];
|
||||||
if addr != 0 {
|
if addr != 0 {
|
||||||
self.raw_file.write_pointer_table(
|
self.raw_file
|
||||||
addr,
|
.write_pointer_table_direct(addr, l2_table.iter())?;
|
||||||
l2_table.get_values(),
|
|
||||||
CLUSTER_USED_FLAG,
|
|
||||||
)?;
|
|
||||||
} else {
|
} else {
|
||||||
return Err(std::io::Error::from_raw_os_error(EINVAL));
|
return Err(std::io::Error::from_raw_os_error(EINVAL));
|
||||||
}
|
}
|
||||||
@@ -1498,10 +1610,21 @@ impl QcowFile {
|
|||||||
// Push L1 table and refcount table last as all the clusters they point to are now
|
// Push L1 table and refcount table last as all the clusters they point to are now
|
||||||
// guaranteed to be valid.
|
// guaranteed to be valid.
|
||||||
let mut sync_required = if self.l1_table.dirty() {
|
let mut sync_required = if self.l1_table.dirty() {
|
||||||
|
// Write L1 table with OFLAG_COPIED bits
|
||||||
|
let refcounts = &mut self.refcounts;
|
||||||
self.raw_file.write_pointer_table(
|
self.raw_file.write_pointer_table(
|
||||||
self.header.l1_table_offset,
|
self.header.l1_table_offset,
|
||||||
self.l1_table.get_values(),
|
self.l1_table.iter(),
|
||||||
0,
|
|raw_file, l2_addr| {
|
||||||
|
if l2_addr == 0 {
|
||||||
|
Ok(0)
|
||||||
|
} else {
|
||||||
|
let refcount = refcounts
|
||||||
|
.get_cluster_refcount(raw_file, l2_addr)
|
||||||
|
.map_err(|e| std::io::Error::other(Error::GettingRefcount(e)))?;
|
||||||
|
Ok(l1_entry_make(l2_addr, refcount == 1))
|
||||||
|
}
|
||||||
|
},
|
||||||
)?;
|
)?;
|
||||||
self.l1_table.mark_clean();
|
self.l1_table.mark_clean();
|
||||||
true
|
true
|
||||||
@@ -1536,14 +1659,10 @@ impl Read for QcowFile {
|
|||||||
let mut nread: usize = 0;
|
let mut nread: usize = 0;
|
||||||
while nread < read_count {
|
while nread < read_count {
|
||||||
let curr_addr = address + nread as u64;
|
let curr_addr = address + nread as u64;
|
||||||
let file_offset = self.file_offset_read(curr_addr)?;
|
|
||||||
let count = self.limit_range_cluster(curr_addr, read_count - nread);
|
let count = self.limit_range_cluster(curr_addr, read_count - nread);
|
||||||
|
|
||||||
if let Some(offset) = file_offset {
|
if (self.file_read(curr_addr, count, &mut buf[nread..(nread + count)])?).is_some() {
|
||||||
self.raw_file.file_mut().seek(SeekFrom::Start(offset))?;
|
// Data is successfully read from the cluster
|
||||||
self.raw_file
|
|
||||||
.file_mut()
|
|
||||||
.read_exact(&mut buf[nread..(nread + count)])?;
|
|
||||||
} else if let Some(backing) = self.backing_file.as_mut() {
|
} else if let Some(backing) = self.backing_file.as_mut() {
|
||||||
backing.seek(SeekFrom::Start(curr_addr))?;
|
backing.seek(SeekFrom::Start(curr_addr))?;
|
||||||
backing.read_exact(&mut buf[nread..(nread + count)])?;
|
backing.read_exact(&mut buf[nread..(nread + count)])?;
|
||||||
@@ -1689,9 +1808,15 @@ impl SeekHole for QcowFile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl BlockBackend for QcowFile {
|
impl BlockBackend for QcowFile {
|
||||||
fn size(&self) -> std::result::Result<u64, crate::Error> {
|
fn logical_size(&self) -> std::result::Result<u64, crate::Error> {
|
||||||
Ok(self.virtual_size())
|
Ok(self.virtual_size())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn physical_size(&self) -> std::result::Result<u64, crate::Error> {
|
||||||
|
self.raw_file
|
||||||
|
.physical_size()
|
||||||
|
.map_err(crate::Error::GetFileMetadata)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns an Error if the given offset doesn't align to a cluster boundary.
|
// Returns an Error if the given offset doesn't align to a cluster boundary.
|
||||||
@@ -1839,7 +1964,7 @@ pub fn detect_image_type(file: &mut RawFile) -> Result<ImageType> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod unit_tests {
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
@@ -2767,8 +2892,6 @@ mod tests {
|
|||||||
assert_eq!(orig, read);
|
assert_eq!(orig, read);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
assert_eq!(qcow_file.first_zero_refcount().unwrap(), None);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -61,25 +61,49 @@ impl QcowRawFile {
|
|||||||
self.read_pointer_table(offset, count, mask)
|
self.read_pointer_table(offset, count, mask)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Writes `table` of u64 pointers to `offset` in the file.
|
/// Internal helper for creating a buffered writer for pointer tables.
|
||||||
/// `non_zero_flags` will be ORed with all non-zero values in `table`.
|
#[inline]
|
||||||
/// writing.
|
fn setup_pointer_table_writer<T>(
|
||||||
pub fn write_pointer_table(
|
|
||||||
&mut self,
|
&mut self,
|
||||||
offset: u64,
|
offset: u64,
|
||||||
table: &[u64],
|
entries: &impl Iterator<Item = T>,
|
||||||
non_zero_flags: u64,
|
) -> io::Result<BufWriter<RawFile>> {
|
||||||
) -> io::Result<()> {
|
|
||||||
self.file.seek(SeekFrom::Start(offset))?;
|
self.file.seek(SeekFrom::Start(offset))?;
|
||||||
let mut buffer = BufWriter::with_capacity(std::mem::size_of_val(table), &mut self.file);
|
let my_file = self.file.try_clone()?;
|
||||||
for addr in table {
|
let capacity = entries.size_hint().0 * size_of::<u64>();
|
||||||
let val = if *addr == 0 {
|
Ok(BufWriter::with_capacity(capacity, my_file))
|
||||||
0
|
}
|
||||||
} else {
|
|
||||||
*addr | non_zero_flags
|
/// Writes a pointer table to `offset` in the file.
|
||||||
};
|
/// Entries are computed on-the-fly by the callback.
|
||||||
buffer.write_u64::<BigEndian>(val)?;
|
pub fn write_pointer_table<'a, T: Copy + 'a>(
|
||||||
|
&mut self,
|
||||||
|
offset: u64,
|
||||||
|
entries: impl Iterator<Item = &'a T>,
|
||||||
|
mut f: impl FnMut(&mut QcowRawFile, T) -> io::Result<u64>,
|
||||||
|
) -> io::Result<()> {
|
||||||
|
let mut buffer = self.setup_pointer_table_writer(offset, &entries)?;
|
||||||
|
|
||||||
|
for addr in entries {
|
||||||
|
let entry = f(self, *addr)?;
|
||||||
|
buffer.write_u64::<BigEndian>(entry)?;
|
||||||
}
|
}
|
||||||
|
buffer.flush()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Writes a pointer table directly without transforming values.
|
||||||
|
pub fn write_pointer_table_direct<'a>(
|
||||||
|
&mut self,
|
||||||
|
offset: u64,
|
||||||
|
entries: impl Iterator<Item = &'a u64>,
|
||||||
|
) -> io::Result<()> {
|
||||||
|
let mut buffer = self.setup_pointer_table_writer(offset, &entries)?;
|
||||||
|
|
||||||
|
for &entry in entries {
|
||||||
|
buffer.write_u64::<BigEndian>(entry)?;
|
||||||
|
}
|
||||||
|
buffer.flush()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,6 +125,7 @@ impl QcowRawFile {
|
|||||||
for count in table {
|
for count in table {
|
||||||
buffer.write_u16::<BigEndian>(*count)?;
|
buffer.write_u16::<BigEndian>(*count)?;
|
||||||
}
|
}
|
||||||
|
buffer.flush()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,6 +160,11 @@ impl QcowRawFile {
|
|||||||
address & self.cluster_mask
|
address & self.cluster_mask
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the base address of the cluster containing `address`.
|
||||||
|
pub fn cluster_address(&self, address: u64) -> u64 {
|
||||||
|
address & !self.cluster_mask
|
||||||
|
}
|
||||||
|
|
||||||
/// Zeros out a cluster in the file.
|
/// Zeros out a cluster in the file.
|
||||||
pub fn zero_cluster(&mut self, address: u64) -> io::Result<()> {
|
pub fn zero_cluster(&mut self, address: u64) -> io::Result<()> {
|
||||||
let cluster_size = self.cluster_size as usize;
|
let cluster_size = self.cluster_size as usize;
|
||||||
@@ -144,11 +174,15 @@ impl QcowRawFile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Writes
|
/// Writes
|
||||||
pub fn write_cluster(&mut self, address: u64, data: Vec<u8>) -> io::Result<()> {
|
pub fn write_cluster(&mut self, address: u64, data: &[u8]) -> io::Result<()> {
|
||||||
let cluster_size = self.cluster_size as usize;
|
let cluster_size = self.cluster_size as usize;
|
||||||
self.file.seek(SeekFrom::Start(address))?;
|
self.file.seek(SeekFrom::Start(address))?;
|
||||||
self.file.write_all(&data[0..cluster_size])
|
self.file.write_all(&data[0..cluster_size])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn physical_size(&self) -> Result<u64, std::io::Error> {
|
||||||
|
self.file.metadata().map(|m| m.len())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Clone for QcowRawFile {
|
impl Clone for QcowRawFile {
|
||||||
|
|||||||
@@ -259,7 +259,7 @@ impl Write for RawFile {
|
|||||||
// SAFETY: tmp_ptr was allocated by alloc_zeroed with layout
|
// SAFETY: tmp_ptr was allocated by alloc_zeroed with layout
|
||||||
unsafe { dealloc(tmp_ptr, layout) };
|
unsafe { dealloc(tmp_ptr, layout) };
|
||||||
return Err(io::Error::last_os_error());
|
return Err(io::Error::last_os_error());
|
||||||
};
|
}
|
||||||
|
|
||||||
tmp_buf[file_offset..(file_offset + buf_len)].copy_from_slice(buf);
|
tmp_buf[file_offset..(file_offset + buf_len)].copy_from_slice(buf);
|
||||||
|
|
||||||
@@ -354,7 +354,11 @@ impl SeekHole for RawFile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl BlockBackend for RawFile {
|
impl BlockBackend for RawFile {
|
||||||
fn size(&self) -> std::result::Result<u64, crate::Error> {
|
fn logical_size(&self) -> std::result::Result<u64, crate::Error> {
|
||||||
|
Ok(self.metadata().map_err(crate::Error::RawFileError)?.len())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn physical_size(&self) -> std::result::Result<u64, crate::Error> {
|
||||||
Ok(self.metadata().map_err(crate::Error::RawFileError)?.len())
|
Ok(self.metadata().map_err(crate::Error::RawFileError)?.len())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -119,7 +119,9 @@ impl RefCount {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Unwrap is safe here as the entry was filled directly above.
|
// Unwrap is safe here as the entry was filled directly above.
|
||||||
let dropped_cluster = if !self.refblock_cache.get(table_index).unwrap().dirty() {
|
let dropped_cluster = if self.refblock_cache.get(table_index).unwrap().dirty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
// Free the previously used block and use a new one. Writing modified counts to new
|
// Free the previously used block and use a new one. Writing modified counts to new
|
||||||
// blocks keeps the on-disk state consistent even if it's out of date.
|
// blocks keeps the on-disk state consistent even if it's out of date.
|
||||||
if let Some((addr, _)) = new_cluster.take() {
|
if let Some((addr, _)) = new_cluster.take() {
|
||||||
@@ -128,8 +130,6 @@ impl RefCount {
|
|||||||
} else {
|
} else {
|
||||||
return Err(Error::NeedNewCluster);
|
return Err(Error::NeedNewCluster);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
};
|
||||||
|
|
||||||
self.refblock_cache.get_mut(table_index).unwrap()[block_index] = refcount;
|
self.refblock_cache.get_mut(table_index).unwrap()[block_index] = refcount;
|
||||||
@@ -156,11 +156,8 @@ impl RefCount {
|
|||||||
/// Returns true if the table changed since the previous `flush_table()` call.
|
/// Returns true if the table changed since the previous `flush_table()` call.
|
||||||
pub fn flush_table(&mut self, raw_file: &mut QcowRawFile) -> io::Result<bool> {
|
pub fn flush_table(&mut self, raw_file: &mut QcowRawFile) -> io::Result<bool> {
|
||||||
if self.ref_table.dirty() {
|
if self.ref_table.dirty() {
|
||||||
raw_file.write_pointer_table(
|
raw_file
|
||||||
self.refcount_table_offset,
|
.write_pointer_table_direct(self.refcount_table_offset, self.ref_table.iter())?;
|
||||||
self.ref_table.get_values(),
|
|
||||||
0,
|
|
||||||
)?;
|
|
||||||
self.ref_table.mark_clean();
|
self.ref_table.mark_clean();
|
||||||
Ok(true)
|
Ok(true)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::collections::hash_map::IterMut;
|
use std::collections::hash_map::IterMut;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::ops::{Index, IndexMut};
|
use std::ops::{Deref, Index, IndexMut};
|
||||||
use std::slice::SliceIndex;
|
use std::slice::SliceIndex;
|
||||||
|
|
||||||
/// Trait that allows for checking if an implementor is dirty. Useful for types that are cached so
|
/// Trait that allows for checking if an implementor is dirty. Useful for types that are cached so
|
||||||
@@ -85,6 +85,14 @@ impl<T: 'static + Copy + Default> IndexMut<usize> for VecCache<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T: 'static + Copy + Default> Deref for VecCache<T> {
|
||||||
|
type Target = [T];
|
||||||
|
|
||||||
|
fn deref(&self) -> &[T] {
|
||||||
|
&self.vec
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct CacheMap<T: Cacheable> {
|
pub struct CacheMap<T: Cacheable> {
|
||||||
capacity: usize,
|
capacity: usize,
|
||||||
@@ -135,7 +143,7 @@ impl<T: Cacheable> CacheMap<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod unit_tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
struct NumCache(());
|
struct NumCache(());
|
||||||
|
|||||||
@@ -6,51 +6,81 @@ use std::collections::VecDeque;
|
|||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{Seek, SeekFrom};
|
use std::io::{Seek, SeekFrom};
|
||||||
use std::os::fd::AsRawFd;
|
use std::os::fd::AsRawFd;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
use crate::AsyncAdaptor;
|
|
||||||
use crate::async_io::{
|
use crate::async_io::{
|
||||||
AsyncIo, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult,
|
AsyncIo, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult,
|
||||||
};
|
};
|
||||||
use crate::qcow::{QcowFile, RawFile, Result as QcowResult};
|
use crate::qcow::{QcowFile, RawFile, Result as QcowResult};
|
||||||
|
use crate::{AsyncAdaptor, BlockBackend};
|
||||||
|
|
||||||
pub struct QcowDiskSync {
|
pub struct QcowDiskSync {
|
||||||
qcow_file: QcowFile,
|
// FIXME: The Mutex serializes all QCOW2 I/O operations across queues, which
|
||||||
|
// is necessary for correctness but eliminates any parallelism benefit from
|
||||||
|
// multiqueue. QcowFile has internal mutable state (L2 cache, refcounts, file
|
||||||
|
// position) that is not safe to share across threads via Clone.
|
||||||
|
//
|
||||||
|
// A proper fix would require restructuring QcowFile to separate metadata
|
||||||
|
// operations (which need synchronization) from data I/O (which could be
|
||||||
|
// parallelized with per queue file descriptors). See #7560 for details.
|
||||||
|
qcow_file: Arc<Mutex<QcowFile>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl QcowDiskSync {
|
impl QcowDiskSync {
|
||||||
pub fn new(file: File, direct_io: bool) -> QcowResult<Self> {
|
pub fn new(file: File, direct_io: bool, backing_files: bool) -> QcowResult<Self> {
|
||||||
Ok(QcowDiskSync {
|
if backing_files {
|
||||||
qcow_file: QcowFile::from(RawFile::new(file, direct_io))?,
|
Ok(QcowDiskSync {
|
||||||
})
|
qcow_file: Arc::new(Mutex::new(QcowFile::from(RawFile::new(file, direct_io))?)),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Ok(QcowDiskSync {
|
||||||
|
qcow_file: Arc::new(Mutex::new(QcowFile::from_with_nesting_depth(
|
||||||
|
RawFile::new(file, direct_io),
|
||||||
|
0,
|
||||||
|
)?)),
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DiskFile for QcowDiskSync {
|
impl DiskFile for QcowDiskSync {
|
||||||
fn size(&mut self) -> DiskFileResult<u64> {
|
fn logical_size(&mut self) -> DiskFileResult<u64> {
|
||||||
self.qcow_file
|
self.qcow_file
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
.seek(SeekFrom::End(0))
|
.seek(SeekFrom::End(0))
|
||||||
.map_err(DiskFileError::Size)
|
.map_err(DiskFileError::Size)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn physical_size(&mut self) -> DiskFileResult<u64> {
|
||||||
|
self.qcow_file.lock().unwrap().physical_size().map_err(|e| {
|
||||||
|
let io_inner = match e {
|
||||||
|
crate::Error::GetFileMetadata(e) => e,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
DiskFileError::Size(io_inner)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn new_async_io(&self, _ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
|
fn new_async_io(&self, _ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
|
||||||
Ok(Box::new(QcowSync::new(self.qcow_file.clone())) as Box<dyn AsyncIo>)
|
Ok(Box::new(QcowSync::new(Arc::clone(&self.qcow_file))) as Box<dyn AsyncIo>)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fd(&mut self) -> BorrowedDiskFd<'_> {
|
fn fd(&mut self) -> BorrowedDiskFd<'_> {
|
||||||
BorrowedDiskFd::new(self.qcow_file.as_raw_fd())
|
BorrowedDiskFd::new(self.qcow_file.lock().unwrap().as_raw_fd())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct QcowSync {
|
pub struct QcowSync {
|
||||||
qcow_file: QcowFile,
|
qcow_file: Arc<Mutex<QcowFile>>,
|
||||||
eventfd: EventFd,
|
eventfd: EventFd,
|
||||||
completion_list: VecDeque<(u64, i32)>,
|
completion_list: VecDeque<(u64, i32)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl QcowSync {
|
impl QcowSync {
|
||||||
pub fn new(qcow_file: QcowFile) -> Self {
|
pub fn new(qcow_file: Arc<Mutex<QcowFile>>) -> Self {
|
||||||
QcowSync {
|
QcowSync {
|
||||||
qcow_file,
|
qcow_file,
|
||||||
eventfd: EventFd::new(libc::EFD_NONBLOCK)
|
eventfd: EventFd::new(libc::EFD_NONBLOCK)
|
||||||
@@ -73,7 +103,7 @@ impl AsyncIo for QcowSync {
|
|||||||
iovecs: &[libc::iovec],
|
iovecs: &[libc::iovec],
|
||||||
user_data: u64,
|
user_data: u64,
|
||||||
) -> AsyncIoResult<()> {
|
) -> AsyncIoResult<()> {
|
||||||
self.qcow_file.read_vectored_sync(
|
self.qcow_file.lock().unwrap().read_vectored_sync(
|
||||||
offset,
|
offset,
|
||||||
iovecs,
|
iovecs,
|
||||||
user_data,
|
user_data,
|
||||||
@@ -88,7 +118,7 @@ impl AsyncIo for QcowSync {
|
|||||||
iovecs: &[libc::iovec],
|
iovecs: &[libc::iovec],
|
||||||
user_data: u64,
|
user_data: u64,
|
||||||
) -> AsyncIoResult<()> {
|
) -> AsyncIoResult<()> {
|
||||||
self.qcow_file.write_vectored_sync(
|
self.qcow_file.lock().unwrap().write_vectored_sync(
|
||||||
offset,
|
offset,
|
||||||
iovecs,
|
iovecs,
|
||||||
user_data,
|
user_data,
|
||||||
@@ -98,8 +128,11 @@ impl AsyncIo for QcowSync {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn fsync(&mut self, user_data: Option<u64>) -> AsyncIoResult<()> {
|
fn fsync(&mut self, user_data: Option<u64>) -> AsyncIoResult<()> {
|
||||||
self.qcow_file
|
self.qcow_file.lock().unwrap().fsync_sync(
|
||||||
.fsync_sync(user_data, &self.eventfd, &mut self.completion_list)
|
user_data,
|
||||||
|
&self.eventfd,
|
||||||
|
&mut self.completion_list,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn next_completed_request(&mut self) -> Option<(u64, i32)> {
|
fn next_completed_request(&mut self) -> Option<(u64, i32)> {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use std::io::{Error, Seek, SeekFrom};
|
|||||||
use std::os::unix::io::{AsRawFd, RawFd};
|
use std::os::unix::io::{AsRawFd, RawFd};
|
||||||
|
|
||||||
use io_uring::{IoUring, opcode, types};
|
use io_uring::{IoUring, opcode, types};
|
||||||
|
use log::warn;
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
use crate::async_io::{
|
use crate::async_io::{
|
||||||
@@ -25,12 +26,19 @@ impl RawFileDisk {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl DiskFile for RawFileDisk {
|
impl DiskFile for RawFileDisk {
|
||||||
fn size(&mut self) -> DiskFileResult<u64> {
|
fn logical_size(&mut self) -> DiskFileResult<u64> {
|
||||||
self.file
|
self.file
|
||||||
.seek(SeekFrom::End(0))
|
.seek(SeekFrom::End(0))
|
||||||
.map_err(DiskFileError::Size)
|
.map_err(DiskFileError::Size)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn physical_size(&mut self) -> DiskFileResult<u64> {
|
||||||
|
self.file
|
||||||
|
.metadata()
|
||||||
|
.map(|m| m.len())
|
||||||
|
.map_err(DiskFileError::Size)
|
||||||
|
}
|
||||||
|
|
||||||
fn new_async_io(&self, ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
|
fn new_async_io(&self, ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
|
||||||
Ok(Box::new(
|
Ok(Box::new(
|
||||||
RawFileAsync::new(self.file.as_raw_fd(), ring_depth)
|
RawFileAsync::new(self.file.as_raw_fd(), ring_depth)
|
||||||
@@ -47,6 +55,10 @@ impl DiskFile for RawFileDisk {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn resize(&mut self, size: u64) -> DiskFileResult<()> {
|
||||||
|
self.file.set_len(size).map_err(DiskFileError::ResizeError)
|
||||||
|
}
|
||||||
|
|
||||||
fn fd(&mut self) -> BorrowedDiskFd<'_> {
|
fn fd(&mut self) -> BorrowedDiskFd<'_> {
|
||||||
BorrowedDiskFd::new(self.file.as_raw_fd())
|
BorrowedDiskFd::new(self.file.as_raw_fd())
|
||||||
}
|
}
|
||||||
@@ -97,7 +109,7 @@ impl AsyncIo for RawFileAsync {
|
|||||||
.build()
|
.build()
|
||||||
.user_data(user_data),
|
.user_data(user_data),
|
||||||
)
|
)
|
||||||
.map_err(|_| AsyncIoError::ReadVectored(Error::other("Submission queue is full")))?
|
.map_err(|_| AsyncIoError::ReadVectored(Error::other("Submission queue is full")))?;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Update the submission queue and submit new operations to the
|
// Update the submission queue and submit new operations to the
|
||||||
@@ -125,7 +137,7 @@ impl AsyncIo for RawFileAsync {
|
|||||||
.build()
|
.build()
|
||||||
.user_data(user_data),
|
.user_data(user_data),
|
||||||
)
|
)
|
||||||
.map_err(|_| AsyncIoError::WriteVectored(Error::other("Submission queue is full")))?
|
.map_err(|_| AsyncIoError::WriteVectored(Error::other("Submission queue is full")))?;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Update the submission queue and submit new operations to the
|
// Update the submission queue and submit new operations to the
|
||||||
@@ -147,7 +159,7 @@ impl AsyncIo for RawFileAsync {
|
|||||||
.build()
|
.build()
|
||||||
.user_data(user_data),
|
.user_data(user_data),
|
||||||
)
|
)
|
||||||
.map_err(|_| AsyncIoError::Fsync(Error::other("Submission queue is full")))?
|
.map_err(|_| AsyncIoError::Fsync(Error::other("Submission queue is full")))?;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Update the submission queue and submit new operations to the
|
// Update the submission queue and submit new operations to the
|
||||||
@@ -199,7 +211,7 @@ impl AsyncIo for RawFileAsync {
|
|||||||
)
|
)
|
||||||
.map_err(|_| {
|
.map_err(|_| {
|
||||||
AsyncIoError::ReadVectored(Error::other("Submission queue is full"))
|
AsyncIoError::ReadVectored(Error::other("Submission queue is full"))
|
||||||
})?
|
})?;
|
||||||
};
|
};
|
||||||
submitted = true;
|
submitted = true;
|
||||||
}
|
}
|
||||||
@@ -219,7 +231,7 @@ impl AsyncIo for RawFileAsync {
|
|||||||
)
|
)
|
||||||
.map_err(|_| {
|
.map_err(|_| {
|
||||||
AsyncIoError::WriteVectored(Error::other("Submission queue is full"))
|
AsyncIoError::WriteVectored(Error::other("Submission queue is full"))
|
||||||
})?
|
})?;
|
||||||
};
|
};
|
||||||
submitted = true;
|
submitted = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ use std::fs::File;
|
|||||||
use std::io::{Seek, SeekFrom};
|
use std::io::{Seek, SeekFrom};
|
||||||
use std::os::unix::io::{AsRawFd, RawFd};
|
use std::os::unix::io::{AsRawFd, RawFd};
|
||||||
|
|
||||||
|
use log::warn;
|
||||||
use vmm_sys_util::aio;
|
use vmm_sys_util::aio;
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
@@ -28,12 +29,19 @@ impl RawFileDiskAio {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl DiskFile for RawFileDiskAio {
|
impl DiskFile for RawFileDiskAio {
|
||||||
fn size(&mut self) -> DiskFileResult<u64> {
|
fn logical_size(&mut self) -> DiskFileResult<u64> {
|
||||||
self.file
|
self.file
|
||||||
.seek(SeekFrom::End(0))
|
.seek(SeekFrom::End(0))
|
||||||
.map_err(DiskFileError::Size)
|
.map_err(DiskFileError::Size)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn physical_size(&mut self) -> DiskFileResult<u64> {
|
||||||
|
self.file
|
||||||
|
.metadata()
|
||||||
|
.map(|m| m.len())
|
||||||
|
.map_err(DiskFileError::Size)
|
||||||
|
}
|
||||||
|
|
||||||
fn new_async_io(&self, ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
|
fn new_async_io(&self, ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
|
||||||
Ok(Box::new(
|
Ok(Box::new(
|
||||||
RawFileAsyncAio::new(self.file.as_raw_fd(), ring_depth)
|
RawFileAsyncAio::new(self.file.as_raw_fd(), ring_depth)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use std::fs::File;
|
|||||||
use std::io::{Seek, SeekFrom};
|
use std::io::{Seek, SeekFrom};
|
||||||
use std::os::unix::io::{AsRawFd, RawFd};
|
use std::os::unix::io::{AsRawFd, RawFd};
|
||||||
|
|
||||||
|
use log::warn;
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
use crate::DiskTopology;
|
use crate::DiskTopology;
|
||||||
@@ -25,12 +26,19 @@ impl RawFileDiskSync {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl DiskFile for RawFileDiskSync {
|
impl DiskFile for RawFileDiskSync {
|
||||||
fn size(&mut self) -> DiskFileResult<u64> {
|
fn logical_size(&mut self) -> DiskFileResult<u64> {
|
||||||
self.file
|
self.file
|
||||||
.seek(SeekFrom::End(0))
|
.seek(SeekFrom::End(0))
|
||||||
.map_err(DiskFileError::Size)
|
.map_err(DiskFileError::Size)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn physical_size(&mut self) -> DiskFileResult<u64> {
|
||||||
|
self.file
|
||||||
|
.metadata()
|
||||||
|
.map(|m| m.len())
|
||||||
|
.map_err(DiskFileError::Size)
|
||||||
|
}
|
||||||
|
|
||||||
fn new_async_io(&self, _ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
|
fn new_async_io(&self, _ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
|
||||||
Ok(Box::new(RawFileSync::new(self.file.as_raw_fd())) as Box<dyn AsyncIo>)
|
Ok(Box::new(RawFileSync::new(self.file.as_raw_fd())) as Box<dyn AsyncIo>)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ pub fn is_fixed_vhd(f: &mut File) -> std::io::Result<bool> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod unit_tests {
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{Seek, SeekFrom, Write};
|
use std::io::{Seek, SeekFrom, Write};
|
||||||
|
|
||||||
|
|||||||
@@ -202,9 +202,16 @@ impl Seek for Vhdx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl BlockBackend for Vhdx {
|
impl BlockBackend for Vhdx {
|
||||||
fn size(&self) -> std::result::Result<u64, crate::Error> {
|
fn logical_size(&self) -> std::result::Result<u64, crate::Error> {
|
||||||
Ok(self.virtual_disk_size())
|
Ok(self.virtual_disk_size())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn physical_size(&self) -> std::result::Result<u64, crate::Error> {
|
||||||
|
self.file
|
||||||
|
.metadata()
|
||||||
|
.map(|m| m.len())
|
||||||
|
.map_err(crate::Error::GetFileMetadata)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Clone for Vhdx {
|
impl Clone for Vhdx {
|
||||||
|
|||||||
@@ -2,8 +2,6 @@
|
|||||||
//
|
//
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
extern crate log;
|
|
||||||
|
|
||||||
use std::collections::btree_map::BTreeMap;
|
use std::collections::btree_map::BTreeMap;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{self, Read, Seek, SeekFrom, Write};
|
use std::io::{self, Read, Seek, SeekFrom, Write};
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ pub fn read(
|
|||||||
_ => {
|
_ => {
|
||||||
return Err(VhdxIoError::InvalidBatEntryState);
|
return Err(VhdxIoError::InvalidBatEntryState);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
sector_count -= sector.free_sectors;
|
sector_count -= sector.free_sectors;
|
||||||
sector_index += sector.free_sectors;
|
sector_index += sector.free_sectors;
|
||||||
read_count += sector.free_bytes as usize;
|
read_count += sector.free_bytes as usize;
|
||||||
@@ -210,7 +210,7 @@ pub fn write(
|
|||||||
_ => {
|
_ => {
|
||||||
return Err(VhdxIoError::InvalidBatEntryState);
|
return Err(VhdxIoError::InvalidBatEntryState);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
sector_count -= sector.free_sectors;
|
sector_count -= sector.free_sectors;
|
||||||
sector_index += sector.free_sectors;
|
sector_index += sector.free_sectors;
|
||||||
write_count += sector.free_bytes as usize;
|
write_count += sector.free_bytes as usize;
|
||||||
|
|||||||
@@ -8,11 +8,11 @@ use std::os::fd::AsRawFd;
|
|||||||
|
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
use crate::AsyncAdaptor;
|
|
||||||
use crate::async_io::{
|
use crate::async_io::{
|
||||||
AsyncIo, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult,
|
AsyncIo, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult,
|
||||||
};
|
};
|
||||||
use crate::vhdx::{Result as VhdxResult, Vhdx};
|
use crate::vhdx::{Result as VhdxResult, Vhdx};
|
||||||
|
use crate::{AsyncAdaptor, BlockBackend, Error};
|
||||||
|
|
||||||
pub struct VhdxDiskSync {
|
pub struct VhdxDiskSync {
|
||||||
vhdx_file: Vhdx,
|
vhdx_file: Vhdx,
|
||||||
@@ -27,10 +27,20 @@ impl VhdxDiskSync {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl DiskFile for VhdxDiskSync {
|
impl DiskFile for VhdxDiskSync {
|
||||||
fn size(&mut self) -> DiskFileResult<u64> {
|
fn logical_size(&mut self) -> DiskFileResult<u64> {
|
||||||
Ok(self.vhdx_file.virtual_disk_size())
|
Ok(self.vhdx_file.virtual_disk_size())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn physical_size(&mut self) -> DiskFileResult<u64> {
|
||||||
|
self.vhdx_file.physical_size().map_err(|e| {
|
||||||
|
let io_inner = match e {
|
||||||
|
Error::GetFileMetadata(e) => e,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
DiskFileError::Size(io_inner)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn new_async_io(&self, _ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
|
fn new_async_io(&self, _ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
|
||||||
Ok(
|
Ok(
|
||||||
Box::new(VhdxSync::new(self.vhdx_file.clone()).map_err(DiskFileError::NewAsyncIo)?)
|
Box::new(VhdxSync::new(self.vhdx_file.clone()).map_err(DiskFileError::NewAsyncIo)?)
|
||||||
|
|||||||
70
cloud-hypervisor/Cargo.toml
Normal file
70
cloud-hypervisor/Cargo.toml
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
[package]
|
||||||
|
authors = ["The Cloud Hypervisor Authors"]
|
||||||
|
build = "build.rs"
|
||||||
|
default-run = "cloud-hypervisor"
|
||||||
|
description = "Open source Virtual Machine Monitor (VMM) that runs on top of KVM & MSHV"
|
||||||
|
edition = "2024"
|
||||||
|
homepage = "https://github.com/cloud-hypervisor/cloud-hypervisor"
|
||||||
|
license = "Apache-2.0 AND BSD-3-Clause"
|
||||||
|
name = "cloud-hypervisor"
|
||||||
|
version = "50.2.0"
|
||||||
|
# Minimum buildable version:
|
||||||
|
# Keep in sync with version in .github/workflows/build.yaml
|
||||||
|
# Policy on MSRV (see #4318):
|
||||||
|
# Can only be bumped if satisfying any of the following:
|
||||||
|
# a.) A dependency requires it,
|
||||||
|
# b.) If we want to use a new feature and that MSRV is at least 6 months old,
|
||||||
|
# c.) There is a security issue that is addressed by the toolchain update.
|
||||||
|
rust-version = "1.89.0"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = { workspace = true }
|
||||||
|
api_client = { path = "../api_client" }
|
||||||
|
clap = { workspace = true, features = ["string"] }
|
||||||
|
dhat = { workspace = true, optional = true }
|
||||||
|
env_logger = { workspace = true }
|
||||||
|
epoll = { workspace = true }
|
||||||
|
event_monitor = { path = "../event_monitor" }
|
||||||
|
hypervisor = { path = "../hypervisor" }
|
||||||
|
libc = { workspace = true }
|
||||||
|
log = { workspace = true, features = ["std"] }
|
||||||
|
option_parser = { path = "../option_parser" }
|
||||||
|
seccompiler = { workspace = true }
|
||||||
|
serde_json = { workspace = true }
|
||||||
|
signal-hook = { workspace = true }
|
||||||
|
thiserror = { workspace = true }
|
||||||
|
tpm = { path = "../tpm" }
|
||||||
|
tracer = { path = "../tracer" }
|
||||||
|
vm-memory = { workspace = true }
|
||||||
|
vmm = { path = "../vmm" }
|
||||||
|
vmm-sys-util = { workspace = true }
|
||||||
|
zbus = { version = "5.7.1", optional = true }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
block = { path = "../block" }
|
||||||
|
dirs = { workspace = true }
|
||||||
|
net_util = { path = "../net_util" }
|
||||||
|
serde_json = { workspace = true }
|
||||||
|
test_infra = { path = "../test_infra" }
|
||||||
|
wait-timeout = { workspace = true }
|
||||||
|
|
||||||
|
# Please adjust `vmm::feature_list()` accordingly when changing the
|
||||||
|
# feature list below
|
||||||
|
[features]
|
||||||
|
dbus_api = ["vmm/dbus_api", "zbus"]
|
||||||
|
default = ["io_uring", "kvm"]
|
||||||
|
dhat-heap = ["dhat", "vmm/dhat-heap"] # For heap profiling
|
||||||
|
fw_cfg = ["vmm/fw_cfg"]
|
||||||
|
guest_debug = ["vmm/guest_debug"]
|
||||||
|
igvm = ["mshv", "vmm/igvm"]
|
||||||
|
io_uring = ["vmm/io_uring"]
|
||||||
|
ivshmem = ["vmm/ivshmem"]
|
||||||
|
kvm = ["vmm/kvm"]
|
||||||
|
mshv = ["vmm/mshv"]
|
||||||
|
pvmemcontrol = ["vmm/pvmemcontrol"]
|
||||||
|
sev_snp = ["igvm", "mshv", "vmm/sev_snp"]
|
||||||
|
tdx = ["vmm/tdx"]
|
||||||
|
tracing = ["tracer/tracing", "vmm/tracing"]
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
@@ -65,6 +65,8 @@ enum Error {
|
|||||||
ReadingStdin(#[source] std::io::Error),
|
ReadingStdin(#[source] std::io::Error),
|
||||||
#[error("Error reading from file")]
|
#[error("Error reading from file")]
|
||||||
ReadingFile(#[source] std::io::Error),
|
ReadingFile(#[source] std::io::Error),
|
||||||
|
#[error("Invalid disk size")]
|
||||||
|
InvalidDiskSize(#[source] ByteSizedParseError),
|
||||||
}
|
}
|
||||||
|
|
||||||
enum TargetApi<'a> {
|
enum TargetApi<'a> {
|
||||||
@@ -320,6 +322,22 @@ fn rest_api_do_command(matches: &ArgMatches, socket: &mut UnixStream) -> ApiResu
|
|||||||
)?;
|
)?;
|
||||||
simple_api_command(socket, "PUT", "resize", Some(&resize)).map_err(Error::HttpApiClient)
|
simple_api_command(socket, "PUT", "resize", Some(&resize)).map_err(Error::HttpApiClient)
|
||||||
}
|
}
|
||||||
|
Some("resize-disk") => {
|
||||||
|
let resize_disk = resize_disk_config(
|
||||||
|
matches
|
||||||
|
.subcommand_matches("resize-disk")
|
||||||
|
.unwrap()
|
||||||
|
.get_one::<String>("disk")
|
||||||
|
.unwrap(),
|
||||||
|
matches
|
||||||
|
.subcommand_matches("resize-disk")
|
||||||
|
.unwrap()
|
||||||
|
.get_one::<String>("size")
|
||||||
|
.unwrap(),
|
||||||
|
)?;
|
||||||
|
simple_api_command(socket, "PUT", "resize-disk", Some(&resize_disk))
|
||||||
|
.map_err(Error::HttpApiClient)
|
||||||
|
}
|
||||||
Some("resize-zone") => {
|
Some("resize-zone") => {
|
||||||
let resize_zone = resize_zone_config(
|
let resize_zone = resize_zone_config(
|
||||||
matches
|
matches
|
||||||
@@ -399,7 +417,7 @@ fn rest_api_do_command(matches: &ArgMatches, socket: &mut UnixStream) -> ApiResu
|
|||||||
.get_one::<String>("net_config")
|
.get_one::<String>("net_config")
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
)?;
|
)?;
|
||||||
simple_api_command_with_fds(socket, "PUT", "add-net", Some(&net_config), fds)
|
simple_api_command_with_fds(socket, "PUT", "add-net", Some(&net_config), &fds)
|
||||||
.map_err(Error::HttpApiClient)
|
.map_err(Error::HttpApiClient)
|
||||||
}
|
}
|
||||||
Some("add-user-device") => {
|
Some("add-user-device") => {
|
||||||
@@ -454,7 +472,7 @@ fn rest_api_do_command(matches: &ArgMatches, socket: &mut UnixStream) -> ApiResu
|
|||||||
.get_one::<String>("restore_config")
|
.get_one::<String>("restore_config")
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
)?;
|
)?;
|
||||||
simple_api_command_with_fds(socket, "PUT", "restore", Some(&restore_config), fds)
|
simple_api_command_with_fds(socket, "PUT", "restore", Some(&restore_config), &fds)
|
||||||
.map_err(Error::HttpApiClient)
|
.map_err(Error::HttpApiClient)
|
||||||
}
|
}
|
||||||
Some("coredump") => {
|
Some("coredump") => {
|
||||||
@@ -762,6 +780,15 @@ fn resize_config(
|
|||||||
Ok(serde_json::to_string(&resize).unwrap())
|
Ok(serde_json::to_string(&resize).unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn resize_disk_config(id: &str, size: &str) -> Result<String, Error> {
|
||||||
|
let resize_disk = vmm::api::VmResizeDiskData {
|
||||||
|
id: id.to_owned(),
|
||||||
|
desired_size: size.parse::<ByteSized>().map_err(Error::InvalidDiskSize)?.0,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(serde_json::to_string(&resize_disk).unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
fn resize_zone_config(id: &str, size: &str) -> Result<String, Error> {
|
fn resize_zone_config(id: &str, size: &str) -> Result<String, Error> {
|
||||||
let resize_zone = vmm::api::VmResizeZoneData {
|
let resize_zone = vmm::api::VmResizeZoneData {
|
||||||
id: id.to_owned(),
|
id: id.to_owned(),
|
||||||
@@ -1022,6 +1049,20 @@ fn get_cli_commands_sorted() -> Box<[Command]> {
|
|||||||
.help("New memory size in bytes (supports K/M/G suffix)")
|
.help("New memory size in bytes (supports K/M/G suffix)")
|
||||||
.num_args(1),
|
.num_args(1),
|
||||||
),
|
),
|
||||||
|
Command::new("resize-disk")
|
||||||
|
.about("Resize an attached disk")
|
||||||
|
.arg(
|
||||||
|
Arg::new("disk")
|
||||||
|
.long("disk")
|
||||||
|
.help("Disk identifier")
|
||||||
|
.num_args(1),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::new("size")
|
||||||
|
.long("size")
|
||||||
|
.help("New disk size")
|
||||||
|
.num_args(1),
|
||||||
|
),
|
||||||
Command::new("resize-zone")
|
Command::new("resize-zone")
|
||||||
.about("Resize a memory zone")
|
.about("Resize a memory zone")
|
||||||
.arg(
|
.arg(
|
||||||
@@ -1137,6 +1178,7 @@ fn main() {
|
|||||||
|
|
||||||
if let Err(top_error) = target_api.do_command(&matches) {
|
if let Err(top_error) = target_api.do_command(&matches) {
|
||||||
// Helper to join strings with a newline.
|
// Helper to join strings with a newline.
|
||||||
|
#[allow(clippy::needless_pass_by_value)]
|
||||||
fn join_strs(mut acc: String, next: String) -> String {
|
fn join_strs(mut acc: String, next: String) -> String {
|
||||||
if !acc.is_empty() {
|
if !acc.is_empty() {
|
||||||
acc.push('\n');
|
acc.push('\n');
|
||||||
@@ -1161,7 +1203,7 @@ fn main() {
|
|||||||
if let Some(api_client::Error::ServerResponse(status_code, body)) =
|
if let Some(api_client::Error::ServerResponse(status_code, body)) =
|
||||||
error.downcast_ref::<api_client::Error>()
|
error.downcast_ref::<api_client::Error>()
|
||||||
{
|
{
|
||||||
let body = body.as_ref().map(|body| body.as_str()).unwrap_or("");
|
let body = body.as_ref().map_or("", |body| body.as_str());
|
||||||
|
|
||||||
// Retrieve the list of error messages back.
|
// Retrieve the list of error messages back.
|
||||||
let lines: Vec<&str> = match serde_json::from_str(body) {
|
let lines: Vec<&str> = match serde_json::from_str(body) {
|
||||||
@@ -1201,11 +1243,11 @@ fn main() {
|
|||||||
server_api_error_display_modifier,
|
server_api_error_display_modifier,
|
||||||
);
|
);
|
||||||
process::exit(1)
|
process::exit(1)
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod unit_tests {
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -15,7 +15,7 @@ use std::{env, io};
|
|||||||
use clap::{Arg, ArgAction, ArgGroup, ArgMatches, Command};
|
use clap::{Arg, ArgAction, ArgGroup, ArgMatches, Command};
|
||||||
use event_monitor::event;
|
use event_monitor::event;
|
||||||
use libc::EFD_NONBLOCK;
|
use libc::EFD_NONBLOCK;
|
||||||
use log::{LevelFilter, error, warn};
|
use log::{LevelFilter, error, info, warn};
|
||||||
use option_parser::OptionParser;
|
use option_parser::OptionParser;
|
||||||
use seccompiler::SeccompAction;
|
use seccompiler::SeccompAction;
|
||||||
use signal_hook::consts::SIGSYS;
|
use signal_hook::consts::SIGSYS;
|
||||||
@@ -133,29 +133,25 @@ impl log::Log for Logger {
|
|||||||
|
|
||||||
let now = std::time::Instant::now();
|
let now = std::time::Instant::now();
|
||||||
let duration = now.duration_since(self.start);
|
let duration = now.duration_since(self.start);
|
||||||
|
let duration_s = duration.as_secs_f32();
|
||||||
|
|
||||||
if record.file().is_some() && record.line().is_some() {
|
let location = if let (Some(file), Some(line)) = (record.file(), record.line()) {
|
||||||
write!(
|
format!("{file}:{line}")
|
||||||
*(*(self.output.lock().unwrap())),
|
|
||||||
"cloud-hypervisor: {:.6?}: <{}> {}:{}:{} -- {}\r\n",
|
|
||||||
duration,
|
|
||||||
std::thread::current().name().unwrap_or("anonymous"),
|
|
||||||
record.level(),
|
|
||||||
record.file().unwrap(),
|
|
||||||
record.line().unwrap(),
|
|
||||||
record.args()
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
write!(
|
record.target().to_string()
|
||||||
*(*(self.output.lock().unwrap())),
|
};
|
||||||
"cloud-hypervisor: {:.6?}: <{}> {}:{} -- {}\r\n",
|
|
||||||
duration,
|
let mut out = self.output.lock().unwrap();
|
||||||
std::thread::current().name().unwrap_or("anonymous"),
|
write!(
|
||||||
record.level(),
|
&mut *out,
|
||||||
record.target(),
|
// 10: 6 decimal places + sep => whole seconds in range `0..=999` properly aligned
|
||||||
record.args()
|
"cloud-hypervisor: {:>10.6?}s: <{}> {}:{} -- {}\r\n",
|
||||||
)
|
duration_s,
|
||||||
}
|
std::thread::current().name().unwrap_or("anonymous"),
|
||||||
|
record.level(),
|
||||||
|
location,
|
||||||
|
record.args(),
|
||||||
|
)
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
fn flush(&self) {}
|
fn flush(&self) {}
|
||||||
@@ -217,7 +213,8 @@ fn get_cli_options_sorted(
|
|||||||
topology=<threads_per_core>:<cores_per_die>:<dies_per_package>:<packages>,\
|
topology=<threads_per_core>:<cores_per_die>:<dies_per_package>:<packages>,\
|
||||||
kvm_hyperv=on|off,max_phys_bits=<maximum_number_of_physical_bits>,\
|
kvm_hyperv=on|off,max_phys_bits=<maximum_number_of_physical_bits>,\
|
||||||
affinity=<list_of_vcpus_with_their_associated_cpuset>,\
|
affinity=<list_of_vcpus_with_their_associated_cpuset>,\
|
||||||
features=<list_of_features_to_enable>",
|
features=<list_of_features_to_enable>,\
|
||||||
|
nested=on|off",
|
||||||
)
|
)
|
||||||
.default_value(default_vcpus)
|
.default_value(default_vcpus)
|
||||||
.group("vm-config"),
|
.group("vm-config"),
|
||||||
@@ -490,7 +487,7 @@ fn create_app(default_vcpus: String, default_memory: String, default_rng: String
|
|||||||
.args(args)
|
.args(args)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start_vmm(cmd_arguments: ArgMatches) -> Result<Option<String>, Error> {
|
fn start_vmm(cmd_arguments: &ArgMatches) -> Result<Option<String>, Error> {
|
||||||
let log_level = match cmd_arguments.get_count("v") {
|
let log_level = match cmd_arguments.get_count("v") {
|
||||||
0 => LevelFilter::Warn,
|
0 => LevelFilter::Warn,
|
||||||
1 => LevelFilter::Info,
|
1 => LevelFilter::Info,
|
||||||
@@ -726,7 +723,7 @@ fn start_vmm(cmd_arguments: ArgMatches) -> Result<Option<String>, Error> {
|
|||||||
cmd_arguments.contains_id("kernel") || cmd_arguments.contains_id("firmware");
|
cmd_arguments.contains_id("kernel") || cmd_arguments.contains_id("firmware");
|
||||||
|
|
||||||
if payload_present {
|
if payload_present {
|
||||||
let vm_params = VmParams::from_arg_matches(&cmd_arguments);
|
let vm_params = VmParams::from_arg_matches(cmd_arguments);
|
||||||
let vm_config = VmConfig::parse(vm_params).map_err(Error::ParsingConfig)?;
|
let vm_config = VmConfig::parse(vm_params).map_err(Error::ParsingConfig)?;
|
||||||
|
|
||||||
// Create and boot the VM based off the VM config we just built.
|
// Create and boot the VM based off the VM config we just built.
|
||||||
@@ -774,7 +771,7 @@ fn start_vmm(cmd_arguments: ArgMatches) -> Result<Option<String>, Error> {
|
|||||||
.map_err(Error::VmmThread)?;
|
.map_err(Error::VmmThread)?;
|
||||||
|
|
||||||
if let Some(api_handle) = vmm_thread_handle.http_api_handle {
|
if let Some(api_handle) = vmm_thread_handle.http_api_handle {
|
||||||
http_api_graceful_shutdown(api_handle).map_err(Error::HttpApiShutdown)?
|
http_api_graceful_shutdown(api_handle).map_err(Error::HttpApiShutdown)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "dbus_api")]
|
#[cfg(feature = "dbus_api")]
|
||||||
@@ -888,9 +885,10 @@ fn main() {
|
|||||||
warn!("Error expanding FD table: {e}");
|
warn!("Error expanding FD table: {e}");
|
||||||
}
|
}
|
||||||
|
|
||||||
let exit_code = match start_vmm(cmd_arguments) {
|
let exit_code = match start_vmm(&cmd_arguments) {
|
||||||
Ok(path) => {
|
Ok(path) => {
|
||||||
path.map(|s| std::fs::remove_file(s).ok());
|
path.map(|s| std::fs::remove_file(s).ok());
|
||||||
|
info!("Cloud Hypervisor exited successfully");
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
Err(top_error) => {
|
Err(top_error) => {
|
||||||
@@ -964,6 +962,7 @@ mod unit_tests {
|
|||||||
max_phys_bits: 46,
|
max_phys_bits: 46,
|
||||||
affinity: None,
|
affinity: None,
|
||||||
features: CpuFeatures::default(),
|
features: CpuFeatures::default(),
|
||||||
|
nested: true,
|
||||||
},
|
},
|
||||||
memory: MemoryConfig {
|
memory: MemoryConfig {
|
||||||
size: 536_870_912,
|
size: 536_870_912,
|
||||||
@@ -1093,8 +1092,7 @@ mod unit_tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_valid_vm_config_memory() {
|
fn test_valid_vm_config_memory() {
|
||||||
vec![
|
[(
|
||||||
(
|
|
||||||
vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--memory", "size=1073741824"],
|
vec!["cloud-hypervisor", "--kernel", "/path/to/kernel", "--memory", "size=1073741824"],
|
||||||
r#"{
|
r#"{
|
||||||
"payload": {"kernel": "/path/to/kernel"},
|
"payload": {"kernel": "/path/to/kernel"},
|
||||||
@@ -1149,8 +1147,7 @@ mod unit_tests {
|
|||||||
"memory": {"size": 1073741824, "hotplug_method": "VirtioMem", "hotplug_size": 1073741824}
|
"memory": {"size": 1073741824, "hotplug_method": "VirtioMem", "hotplug_size": 1073741824}
|
||||||
}"#,
|
}"#,
|
||||||
true,
|
true,
|
||||||
),
|
)]
|
||||||
]
|
|
||||||
.iter()
|
.iter()
|
||||||
.for_each(|(cli, openapi, equal)| {
|
.for_each(|(cli, openapi, equal)| {
|
||||||
compare_vm_config_cli_vs_json(cli, openapi, *equal);
|
compare_vm_config_cli_vs_json(cli, openapi, *equal);
|
||||||
@@ -1202,14 +1199,14 @@ mod unit_tests {
|
|||||||
"--kernel",
|
"--kernel",
|
||||||
"/path/to/kernel",
|
"/path/to/kernel",
|
||||||
"--disk",
|
"--disk",
|
||||||
"path=/path/to/disk/1",
|
"path=/path/to/disk/1,image_type=raw",
|
||||||
"path=/path/to/disk/2",
|
"path=/path/to/disk/2",
|
||||||
],
|
],
|
||||||
r#"{
|
r#"{
|
||||||
"payload": {"kernel": "/path/to/kernel"},
|
"payload": {"kernel": "/path/to/kernel"},
|
||||||
"disks": [
|
"disks": [
|
||||||
{"path": "/path/to/disk/1"},
|
{"path": "/path/to/disk/1", "image_type": "Raw"},
|
||||||
{"path": "/path/to/disk/2"}
|
{"path": "/path/to/disk/2", "image_type": "Unknown"}
|
||||||
]
|
]
|
||||||
}"#,
|
}"#,
|
||||||
true,
|
true,
|
||||||
@@ -1220,8 +1217,8 @@ mod unit_tests {
|
|||||||
"--kernel",
|
"--kernel",
|
||||||
"/path/to/kernel",
|
"/path/to/kernel",
|
||||||
"--disk",
|
"--disk",
|
||||||
"path=/path/to/disk/1",
|
"path=/path/to/disk/1,image_type=raw",
|
||||||
"path=/path/to/disk/2",
|
"path=/path/to/disk/2,image_type=qcow2",
|
||||||
],
|
],
|
||||||
r#"{
|
r#"{
|
||||||
"payload": {"kernel": "/path/to/kernel"},
|
"payload": {"kernel": "/path/to/kernel"},
|
||||||
@@ -1283,8 +1280,8 @@ mod unit_tests {
|
|||||||
r#"{
|
r#"{
|
||||||
"payload": {"kernel": "/path/to/kernel"},
|
"payload": {"kernel": "/path/to/kernel"},
|
||||||
"disks": [
|
"disks": [
|
||||||
{"path": "/path/to/disk/1", "rate_limit_group": "group0"},
|
{"path": "/path/to/disk/1", "rate_limit_group": "group0", "image_type": "Unknown"},
|
||||||
{"path": "/path/to/disk/2", "rate_limit_group": "group0"}
|
{"path": "/path/to/disk/2", "rate_limit_group": "group0", "image_type": "Unknown"}
|
||||||
],
|
],
|
||||||
"rate_limit_groups": [
|
"rate_limit_groups": [
|
||||||
{"id": "group0", "rate_limiter_config": {"bandwidth": {"size": 1000, "one_time_burst": 0, "refill_time": 100}}}
|
{"id": "group0", "rate_limiter_config": {"bandwidth": {"size": 1000, "one_time_burst": 0, "refill_time": 100}}}
|
||||||
@@ -1301,7 +1298,7 @@ mod unit_tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_valid_vm_config_net() {
|
fn test_valid_vm_config_net() {
|
||||||
vec![
|
[
|
||||||
// This test is expected to fail because the default MAC address is
|
// This test is expected to fail because the default MAC address is
|
||||||
// randomly generated. There's no way we can have twice the same
|
// randomly generated. There's no way we can have twice the same
|
||||||
// default value.
|
// default value.
|
||||||
@@ -1337,20 +1334,6 @@ mod unit_tests {
|
|||||||
}"#,
|
}"#,
|
||||||
true,
|
true,
|
||||||
),
|
),
|
||||||
(
|
|
||||||
vec![
|
|
||||||
"cloud-hypervisor", "--kernel", "/path/to/kernel",
|
|
||||||
"--net",
|
|
||||||
"mac=12:34:56:78:90:ab,host_mac=34:56:78:90:ab:cd,tap=tap0,ip=1.2.3.4",
|
|
||||||
],
|
|
||||||
r#"{
|
|
||||||
"payload": {"kernel": "/path/to/kernel"},
|
|
||||||
"net": [
|
|
||||||
{"mac": "12:34:56:78:90:ab", "host_mac": "34:56:78:90:ab:cd", "tap": "tap0", "ip": "1.2.3.4"}
|
|
||||||
]
|
|
||||||
}"#,
|
|
||||||
true,
|
|
||||||
),
|
|
||||||
(
|
(
|
||||||
vec![
|
vec![
|
||||||
"cloud-hypervisor", "--kernel", "/path/to/kernel",
|
"cloud-hypervisor", "--kernel", "/path/to/kernel",
|
||||||
@@ -1779,7 +1762,7 @@ mod unit_tests {
|
|||||||
#[test]
|
#[test]
|
||||||
#[cfg(target_arch = "x86_64")]
|
#[cfg(target_arch = "x86_64")]
|
||||||
fn test_valid_vm_config_devices() {
|
fn test_valid_vm_config_devices() {
|
||||||
vec![
|
[
|
||||||
(
|
(
|
||||||
vec![
|
vec![
|
||||||
"cloud-hypervisor",
|
"cloud-hypervisor",
|
||||||
@@ -2029,6 +2012,6 @@ mod unit_tests {
|
|||||||
let (default_vcpus, default_memory, default_rng) = prepare_default_values();
|
let (default_vcpus, default_memory, default_rng) = prepare_default_values();
|
||||||
let args = get_cli_options_sorted(default_vcpus, default_memory, default_rng);
|
let args = get_cli_options_sorted(default_vcpus, default_memory, default_rng);
|
||||||
|
|
||||||
assert_args_sorted(|| args.iter())
|
assert_args_sorted(|| args.iter());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,7 @@ version = "0.1.0"
|
|||||||
acpi_tables = { workspace = true }
|
acpi_tables = { workspace = true }
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
arch = { path = "../arch" }
|
arch = { path = "../arch" }
|
||||||
bitfield-struct = { version = "0.10.1", optional = true }
|
bitfield-struct = { version = "0.12.0", optional = true }
|
||||||
bitflags = { workspace = true }
|
bitflags = { workspace = true }
|
||||||
byteorder = { workspace = true }
|
byteorder = { workspace = true }
|
||||||
event_monitor = { path = "../event_monitor" }
|
event_monitor = { path = "../event_monitor" }
|
||||||
@@ -20,7 +20,7 @@ linux-loader = { workspace = true, features = [
|
|||||||
"pe",
|
"pe",
|
||||||
], optional = true }
|
], optional = true }
|
||||||
log = { workspace = true }
|
log = { workspace = true }
|
||||||
num_enum = "0.7.2"
|
num_enum = "0.7.5"
|
||||||
pci = { path = "../pci" }
|
pci = { path = "../pci" }
|
||||||
serde = { workspace = true, features = ["derive"] }
|
serde = { workspace = true, features = ["derive"] }
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
@@ -34,7 +34,7 @@ vm-memory = { workspace = true, features = [
|
|||||||
] }
|
] }
|
||||||
vm-migration = { path = "../vm-migration" }
|
vm-migration = { path = "../vm-migration" }
|
||||||
vmm-sys-util = { workspace = true }
|
vmm-sys-util = { workspace = true }
|
||||||
zerocopy = { version = "0.8.26", features = [
|
zerocopy = { version = "0.8.31", features = [
|
||||||
"alloc",
|
"alloc",
|
||||||
"derive",
|
"derive",
|
||||||
], optional = true }
|
], optional = true }
|
||||||
@@ -48,3 +48,6 @@ fw_cfg = ["arch/fw_cfg", "bitfield-struct", "linux-loader", "zerocopy"]
|
|||||||
ivshmem = []
|
ivshmem = []
|
||||||
kvm = ["arch/kvm"]
|
kvm = ["arch/kvm"]
|
||||||
pvmemcontrol = []
|
pvmemcontrol = []
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ use std::thread;
|
|||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
use acpi_tables::{Aml, AmlSink, aml};
|
use acpi_tables::{Aml, AmlSink, aml};
|
||||||
|
use log::{error, info, warn};
|
||||||
use vm_device::BusDevice;
|
use vm_device::BusDevice;
|
||||||
use vm_device::interrupt::InterruptSourceGroup;
|
use vm_device::interrupt::InterruptSourceGroup;
|
||||||
use vm_memory::GuestAddress;
|
use vm_memory::GuestAddress;
|
||||||
@@ -44,14 +45,14 @@ impl AcpiShutdownDevice {
|
|||||||
impl BusDevice for AcpiShutdownDevice {
|
impl BusDevice for AcpiShutdownDevice {
|
||||||
// Spec has all fields as zero
|
// Spec has all fields as zero
|
||||||
fn read(&mut self, _base: u64, _offset: u64, data: &mut [u8]) {
|
fn read(&mut self, _base: u64, _offset: u64, data: &mut [u8]) {
|
||||||
data.fill(0)
|
data.fill(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write(&mut self, _base: u64, _offset: u64, data: &[u8]) -> Option<Arc<Barrier>> {
|
fn write(&mut self, _base: u64, _offset: u64, data: &[u8]) -> Option<Arc<Barrier>> {
|
||||||
if data[0] == 1 {
|
if data[0] == 1 {
|
||||||
info!("ACPI Reboot signalled");
|
info!("ACPI Reboot signalled");
|
||||||
if let Err(e) = self.reset_evt.write(1) {
|
if let Err(e) = self.reset_evt.write(1) {
|
||||||
error!("Error triggering ACPI reset event: {}", e);
|
error!("Error triggering ACPI reset event: {e}");
|
||||||
}
|
}
|
||||||
// Spin until we are sure the reset_evt has been handled and that when
|
// 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.
|
// we return from the KVM_RUN we will exit rather than re-enter the guest.
|
||||||
@@ -68,7 +69,7 @@ impl BusDevice for AcpiShutdownDevice {
|
|||||||
if data[0] == (S5_SLEEP_VALUE << SLEEP_VALUE_BIT) | (1 << SLEEP_STATUS_EN_BIT) {
|
if data[0] == (S5_SLEEP_VALUE << SLEEP_VALUE_BIT) | (1 << SLEEP_STATUS_EN_BIT) {
|
||||||
info!("ACPI Shutdown signalled");
|
info!("ACPI Shutdown signalled");
|
||||||
if let Err(e) = self.exit_evt.write(1) {
|
if let Err(e) = self.exit_evt.write(1) {
|
||||||
error!("Error triggering ACPI shutdown event: {}", e);
|
error!("Error triggering ACPI shutdown event: {e}");
|
||||||
}
|
}
|
||||||
// Spin until we are sure the reset_evt has been handled and that when
|
// 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.
|
// we return from the KVM_RUN we will exit rather than re-enter the guest.
|
||||||
@@ -213,7 +214,7 @@ impl Aml for AcpiGedDevice {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
.to_aml_bytes(sink)
|
.to_aml_bytes(sink);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,6 @@
|
|||||||
//
|
//
|
||||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||||
|
|
||||||
use super::interrupt_controller::{Error, InterruptController};
|
|
||||||
extern crate arch;
|
|
||||||
use std::result;
|
use std::result;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
@@ -19,6 +17,8 @@ use vm_memory::address::Address;
|
|||||||
use vm_migration::{Migratable, Pausable, Snapshottable, Transportable};
|
use vm_migration::{Migratable, Pausable, Snapshottable, Transportable};
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
|
use super::interrupt_controller::{Error, InterruptController};
|
||||||
|
|
||||||
type Result<T> = result::Result<T, Error>;
|
type Result<T> = result::Result<T, Error>;
|
||||||
|
|
||||||
// Reserve 32 IRQs for legacy devices.
|
// Reserve 32 IRQs for legacy devices.
|
||||||
@@ -39,6 +39,7 @@ pub struct Aia {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Aia {
|
impl Aia {
|
||||||
|
#[allow(clippy::needless_pass_by_value)]
|
||||||
pub fn new(
|
pub fn new(
|
||||||
vcpu_count: u32,
|
vcpu_count: u32,
|
||||||
interrupt_manager: Arc<dyn InterruptManager<GroupConfig = MsiIrqGroupConfig>>,
|
interrupt_manager: Arc<dyn InterruptManager<GroupConfig = MsiIrqGroupConfig>>,
|
||||||
@@ -51,9 +52,8 @@ impl Aia {
|
|||||||
})
|
})
|
||||||
.map_err(Error::CreateInterruptSourceGroup)?;
|
.map_err(Error::CreateInterruptSourceGroup)?;
|
||||||
|
|
||||||
let vaia = vm
|
let config = Aia::create_default_config(vcpu_count as u64);
|
||||||
.create_vaia(Aia::create_default_config(vcpu_count as u64))
|
let vaia = vm.create_vaia(&config).map_err(Error::CreateAia)?;
|
||||||
.map_err(Error::CreateAia)?;
|
|
||||||
|
|
||||||
let aia = Aia {
|
let aia = Aia {
|
||||||
interrupt_source_group,
|
interrupt_source_group,
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ use std::io;
|
|||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::sync::{Arc, Barrier};
|
use std::sync::{Arc, Barrier};
|
||||||
|
|
||||||
|
use log::error;
|
||||||
use vm_device::BusDevice;
|
use vm_device::BusDevice;
|
||||||
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
|
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,6 @@
|
|||||||
//
|
//
|
||||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||||
|
|
||||||
use super::interrupt_controller::{Error, InterruptController};
|
|
||||||
extern crate arch;
|
|
||||||
use std::result;
|
use std::result;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
@@ -19,6 +17,8 @@ use vm_memory::address::Address;
|
|||||||
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
|
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
|
use super::interrupt_controller::{Error, InterruptController};
|
||||||
|
|
||||||
type Result<T> = result::Result<T, Error>;
|
type Result<T> = result::Result<T, Error>;
|
||||||
|
|
||||||
// Reserve 32 IRQs for legacy devices.
|
// Reserve 32 IRQs for legacy devices.
|
||||||
@@ -38,6 +38,7 @@ pub struct Gic {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Gic {
|
impl Gic {
|
||||||
|
#[allow(clippy::needless_pass_by_value)]
|
||||||
pub fn new(
|
pub fn new(
|
||||||
vcpu_count: u32,
|
vcpu_count: u32,
|
||||||
interrupt_manager: Arc<dyn InterruptManager<GroupConfig = MsiIrqGroupConfig>>,
|
interrupt_manager: Arc<dyn InterruptManager<GroupConfig = MsiIrqGroupConfig>>,
|
||||||
@@ -50,9 +51,8 @@ impl Gic {
|
|||||||
})
|
})
|
||||||
.map_err(Error::CreateInterruptSourceGroup)?;
|
.map_err(Error::CreateInterruptSourceGroup)?;
|
||||||
|
|
||||||
let vgic = vm
|
let config = Gic::create_default_config(vcpu_count as u64);
|
||||||
.create_vgic(Gic::create_default_config(vcpu_count as u64))
|
let vgic = vm.create_vgic(&config).map_err(Error::CreateGic)?;
|
||||||
.map_err(Error::CreateGic)?;
|
|
||||||
|
|
||||||
let gic = Gic {
|
let gic = Gic {
|
||||||
interrupt_source_group,
|
interrupt_source_group,
|
||||||
@@ -167,10 +167,7 @@ impl Pausable for Gic {
|
|||||||
// Flush tables to guest RAM
|
// Flush tables to guest RAM
|
||||||
let vgic = self.vgic.as_ref().unwrap().clone();
|
let vgic = self.vgic.as_ref().unwrap().clone();
|
||||||
vgic.lock().unwrap().save_data_tables().map_err(|e| {
|
vgic.lock().unwrap().save_data_tables().map_err(|e| {
|
||||||
MigratableError::Pause(anyhow!(
|
MigratableError::Pause(anyhow!("Could not save GICv3ITS GIC pending tables {e:?}",))
|
||||||
"Could not save GICv3ITS GIC pending tables {:?}",
|
|
||||||
e
|
|
||||||
))
|
|
||||||
})?;
|
})?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ use std::result;
|
|||||||
use std::sync::{Arc, Barrier};
|
use std::sync::{Arc, Barrier};
|
||||||
|
|
||||||
use byteorder::{ByteOrder, LittleEndian};
|
use byteorder::{ByteOrder, LittleEndian};
|
||||||
|
use log::{debug, error, trace, warn};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use vm_device::BusDevice;
|
use vm_device::BusDevice;
|
||||||
use vm_device::interrupt::{
|
use vm_device::interrupt::{
|
||||||
@@ -151,13 +152,13 @@ impl BusDevice for Ioapic {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
debug!("IOAPIC_R @ offset 0x{:x}", offset);
|
debug!("IOAPIC_R @ offset 0x{offset:x}");
|
||||||
|
|
||||||
let value: u32 = match offset as u8 {
|
let value: u32 = match offset as u8 {
|
||||||
IOREGSEL_OFF => self.reg_sel,
|
IOREGSEL_OFF => self.reg_sel,
|
||||||
IOWIN_OFF => self.ioapic_read(),
|
IOWIN_OFF => self.ioapic_read(),
|
||||||
_ => {
|
_ => {
|
||||||
error!("IOAPIC: failed reading at offset {}", offset);
|
error!("IOAPIC: failed reading at offset {offset}");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -171,7 +172,7 @@ impl BusDevice for Ioapic {
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
debug!("IOAPIC_W @ offset 0x{:x}", offset);
|
debug!("IOAPIC_W @ offset 0x{offset:x}");
|
||||||
|
|
||||||
let value = LittleEndian::read_u32(data);
|
let value = LittleEndian::read_u32(data);
|
||||||
|
|
||||||
@@ -179,7 +180,7 @@ impl BusDevice for Ioapic {
|
|||||||
IOREGSEL_OFF => self.reg_sel = value,
|
IOREGSEL_OFF => self.reg_sel = value,
|
||||||
IOWIN_OFF => self.ioapic_write(value),
|
IOWIN_OFF => self.ioapic_write(value),
|
||||||
_ => {
|
_ => {
|
||||||
error!("IOAPIC: failed writing at offset {}", offset);
|
error!("IOAPIC: failed writing at offset {offset}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
@@ -190,8 +191,8 @@ impl Ioapic {
|
|||||||
pub fn new(
|
pub fn new(
|
||||||
id: String,
|
id: String,
|
||||||
apic_address: GuestAddress,
|
apic_address: GuestAddress,
|
||||||
interrupt_manager: Arc<dyn InterruptManager<GroupConfig = MsiIrqGroupConfig>>,
|
interrupt_manager: &dyn InterruptManager<GroupConfig = MsiIrqGroupConfig>,
|
||||||
state: Option<IoapicState>,
|
state: Option<&IoapicState>,
|
||||||
) -> Result<Ioapic> {
|
) -> Result<Ioapic> {
|
||||||
let interrupt_source_group = interrupt_manager
|
let interrupt_source_group = interrupt_manager
|
||||||
.create_group(MsiIrqGroupConfig {
|
.create_group(MsiIrqGroupConfig {
|
||||||
@@ -266,7 +267,7 @@ impl Ioapic {
|
|||||||
IOWIN_OFF..=REG_MAX_OFFSET => {
|
IOWIN_OFF..=REG_MAX_OFFSET => {
|
||||||
let (index, is_high_bits) = decode_irq_from_selector(self.reg_sel as u8);
|
let (index, is_high_bits) = decode_irq_from_selector(self.reg_sel as u8);
|
||||||
if index > NUM_IOAPIC_PINS {
|
if index > NUM_IOAPIC_PINS {
|
||||||
warn!("IOAPIC index out of range: {}", index);
|
warn!("IOAPIC index out of range: {index}");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if is_high_bits {
|
if is_high_bits {
|
||||||
@@ -282,7 +283,7 @@ impl Ioapic {
|
|||||||
// The entry must be updated through the interrupt source
|
// The entry must be updated through the interrupt source
|
||||||
// group.
|
// group.
|
||||||
if let Err(e) = self.update_entry(index, true) {
|
if let Err(e) = self.update_entry(index, true) {
|
||||||
error!("Failed updating IOAPIC entry: {:?}", e);
|
error!("Failed updating IOAPIC entry: {e:?}");
|
||||||
}
|
}
|
||||||
// Store the information this IRQ is now being used.
|
// Store the information this IRQ is now being used.
|
||||||
self.used_entries[index] = true;
|
self.used_entries[index] = true;
|
||||||
@@ -303,7 +304,7 @@ impl Ioapic {
|
|||||||
IOWIN_OFF..=REG_MAX_OFFSET => {
|
IOWIN_OFF..=REG_MAX_OFFSET => {
|
||||||
let (index, is_high_bits) = decode_irq_from_selector(self.reg_sel as u8);
|
let (index, is_high_bits) = decode_irq_from_selector(self.reg_sel as u8);
|
||||||
if index > NUM_IOAPIC_PINS {
|
if index > NUM_IOAPIC_PINS {
|
||||||
warn!("IOAPIC index out of range: {}", index);
|
warn!("IOAPIC index out of range: {index}");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
if is_high_bits {
|
if is_high_bits {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ use std::sync::{Arc, Barrier, Mutex};
|
|||||||
|
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use byteorder::{ByteOrder, LittleEndian};
|
use byteorder::{ByteOrder, LittleEndian};
|
||||||
|
use log::{debug, error, warn};
|
||||||
use pci::{
|
use pci::{
|
||||||
BarReprogrammingParams, PCI_CONFIGURATION_ID, PciBarConfiguration, PciBarPrefetchable,
|
BarReprogrammingParams, PCI_CONFIGURATION_ID, PciBarConfiguration, PciBarPrefetchable,
|
||||||
PciBarRegionType, PciClassCode, PciConfiguration, PciDevice, PciDeviceError, PciHeaderType,
|
PciBarRegionType, PciClassCode, PciConfiguration, PciDevice, PciDeviceError, PciHeaderType,
|
||||||
@@ -33,7 +34,7 @@ const IVSHMEM_DEVICE_ID: u16 = 0x1110;
|
|||||||
|
|
||||||
const IVSHMEM_REG_BAR_SIZE: u64 = 0x100;
|
const IVSHMEM_REG_BAR_SIZE: u64 = 0x100;
|
||||||
|
|
||||||
type GuestRegionMmap = vm_memory::GuestRegionMmap<AtomicBitmap>;
|
type MmapRegion = vm_memory::MmapRegion<AtomicBitmap>;
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum IvshmemError {
|
pub enum IvshmemError {
|
||||||
@@ -68,7 +69,7 @@ pub trait IvshmemOps: Send + Sync {
|
|||||||
start_addr: u64,
|
start_addr: u64,
|
||||||
size: usize,
|
size: usize,
|
||||||
backing_file: Option<PathBuf>,
|
backing_file: Option<PathBuf>,
|
||||||
) -> Result<(Arc<GuestRegionMmap>, UserspaceMapping), IvshmemError>;
|
) -> Result<(Arc<MmapRegion>, UserspaceMapping), IvshmemError>;
|
||||||
|
|
||||||
fn unmap_ram_region(&mut self, mapping: UserspaceMapping) -> Result<(), IvshmemError>;
|
fn unmap_ram_region(&mut self, mapping: UserspaceMapping) -> Result<(), IvshmemError>;
|
||||||
}
|
}
|
||||||
@@ -95,7 +96,7 @@ pub struct IvshmemDevice {
|
|||||||
region_size: u64,
|
region_size: u64,
|
||||||
ivshmem_ops: Arc<Mutex<dyn IvshmemOps>>,
|
ivshmem_ops: Arc<Mutex<dyn IvshmemOps>>,
|
||||||
backend_file: Option<PathBuf>,
|
backend_file: Option<PathBuf>,
|
||||||
region: Option<Arc<GuestRegionMmap>>,
|
region: Option<Arc<MmapRegion>>,
|
||||||
userspace_mapping: Option<UserspaceMapping>,
|
userspace_mapping: Option<UserspaceMapping>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,14 +114,14 @@ impl IvshmemDevice {
|
|||||||
region_size: u64,
|
region_size: u64,
|
||||||
backend_file: Option<PathBuf>,
|
backend_file: Option<PathBuf>,
|
||||||
ivshmem_ops: Arc<Mutex<dyn IvshmemOps>>,
|
ivshmem_ops: Arc<Mutex<dyn IvshmemOps>>,
|
||||||
snapshot: Option<Snapshot>,
|
snapshot: Option<&Snapshot>,
|
||||||
) -> Result<Self, IvshmemError> {
|
) -> Result<Self, IvshmemError> {
|
||||||
let pci_configuration_state =
|
let pci_configuration_state = vm_migration::state_from_id(snapshot, PCI_CONFIGURATION_ID)
|
||||||
vm_migration::state_from_id(snapshot.as_ref(), PCI_CONFIGURATION_ID).map_err(|e| {
|
.map_err(|e| {
|
||||||
IvshmemError::RetrievePciConfigurationState(anyhow!(
|
IvshmemError::RetrievePciConfigurationState(anyhow!(
|
||||||
"Failed to get PciConfigurationState from Snapshot: {e}",
|
"Failed to get PciConfigurationState from Snapshot: {e}",
|
||||||
))
|
))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let state: Option<IvshmemDeviceState> = snapshot
|
let state: Option<IvshmemDeviceState> = snapshot
|
||||||
.as_ref()
|
.as_ref()
|
||||||
@@ -180,11 +181,7 @@ impl IvshmemDevice {
|
|||||||
Ok(device)
|
Ok(device)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_region(
|
pub fn set_region(&mut self, region: Arc<MmapRegion>, userspace_mapping: UserspaceMapping) {
|
||||||
&mut self,
|
|
||||||
region: Arc<GuestRegionMmap>,
|
|
||||||
userspace_mapping: UserspaceMapping,
|
|
||||||
) {
|
|
||||||
self.region = Some(region);
|
self.region = Some(region);
|
||||||
self.userspace_mapping = Some(userspace_mapping);
|
self.userspace_mapping = Some(userspace_mapping);
|
||||||
}
|
}
|
||||||
@@ -209,7 +206,7 @@ impl IvshmemDevice {
|
|||||||
|
|
||||||
impl BusDevice for IvshmemDevice {
|
impl BusDevice for IvshmemDevice {
|
||||||
fn read(&mut self, base: u64, offset: u64, data: &mut [u8]) {
|
fn read(&mut self, base: u64, offset: u64, data: &mut [u8]) {
|
||||||
self.read_bar(base, offset, data)
|
self.read_bar(base, offset, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write(&mut self, base: u64, offset: u64, data: &[u8]) -> Option<Arc<Barrier>> {
|
fn write(&mut self, base: u64, offset: u64, data: &[u8]) -> Option<Arc<Barrier>> {
|
||||||
@@ -233,20 +230,18 @@ impl PciDevice for IvshmemDevice {
|
|||||||
if let Some(resources) = resources {
|
if let Some(resources) = resources {
|
||||||
for resource in resources {
|
for resource in resources {
|
||||||
match resource {
|
match resource {
|
||||||
Resource::PciBar { index, base, .. } => {
|
Resource::PciBar { index, base, .. } => match index {
|
||||||
match index {
|
IVSHMEM_BAR0_IDX => {
|
||||||
IVSHMEM_BAR0_IDX => {
|
bar0_addr = Some(GuestAddress(base));
|
||||||
bar0_addr = Some(GuestAddress(base));
|
}
|
||||||
}
|
IVSHMEM_BAR1_IDX => {}
|
||||||
IVSHMEM_BAR1_IDX => {}
|
IVSHMEM_BAR2_IDX => {
|
||||||
IVSHMEM_BAR2_IDX => {
|
bar2_addr = Some(GuestAddress(base));
|
||||||
bar2_addr = Some(GuestAddress(base));
|
}
|
||||||
}
|
_ => {
|
||||||
_ => {
|
error!("Unexpected pci bar index {index}");
|
||||||
error!("Unexpected pci bar index {index}");
|
}
|
||||||
}
|
},
|
||||||
};
|
|
||||||
}
|
|
||||||
_ => {
|
_ => {
|
||||||
error!("Unexpected resource {resource:?}");
|
error!("Unexpected resource {resource:?}");
|
||||||
}
|
}
|
||||||
@@ -348,7 +343,7 @@ impl PciDevice for IvshmemDevice {
|
|||||||
_ => {
|
_ => {
|
||||||
warn!("Invalid bar_idx: {bar_idx}");
|
warn!("Invalid bar_idx: {bar_idx}");
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_bar(&mut self, base: u64, offset: u64, _data: &[u8]) -> Option<Arc<Barrier>> {
|
fn write_bar(&mut self, base: u64, offset: u64, _data: &[u8]) -> Option<Arc<Barrier>> {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ use std::{mem, thread};
|
|||||||
#[cfg_attr(target_env = "musl", allow(deprecated))]
|
#[cfg_attr(target_env = "musl", allow(deprecated))]
|
||||||
use libc::time_t;
|
use libc::time_t;
|
||||||
use libc::{CLOCK_REALTIME, clock_gettime, gmtime_r, timespec, tm};
|
use libc::{CLOCK_REALTIME, clock_gettime, gmtime_r, timespec, tm};
|
||||||
|
use log::{info, warn};
|
||||||
use vm_device::BusDevice;
|
use vm_device::BusDevice;
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
@@ -87,11 +88,11 @@ impl BusDevice for Cmos {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.data[(self.index & INDEX_MASK) as usize] = data[0]
|
self.data[(self.index & INDEX_MASK) as usize] = data[0];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
o => warn!("bad write offset on CMOS device: {}", o),
|
o => warn!("bad write offset on CMOS device: {o}"),
|
||||||
};
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,7 +165,7 @@ impl BusDevice for Cmos {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
o => {
|
o => {
|
||||||
warn!("bad read offset on CMOS device: {}", o);
|
warn!("bad read offset on CMOS device: {o}");
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
|
use log::{error, warn};
|
||||||
use vm_device::BusDevice;
|
use vm_device::BusDevice;
|
||||||
|
|
||||||
/// Debug I/O port, see:
|
/// Debug I/O port, see:
|
||||||
@@ -62,7 +63,7 @@ impl DebugPort {
|
|||||||
|
|
||||||
impl BusDevice for DebugPort {
|
impl BusDevice for DebugPort {
|
||||||
fn read(&mut self, _base: u64, _offset: u64, _data: &mut [u8]) {
|
fn read(&mut self, _base: u64, _offset: u64, _data: &mut [u8]) {
|
||||||
error!("Invalid read to debug port")
|
error!("Invalid read to debug port");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write(
|
fn write(
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ use bitfield_struct::bitfield;
|
|||||||
use linux_loader::bootparam::boot_params;
|
use linux_loader::bootparam::boot_params;
|
||||||
#[cfg(target_arch = "aarch64")]
|
#[cfg(target_arch = "aarch64")]
|
||||||
use linux_loader::loader::pe::arm64_image_header as boot_params;
|
use linux_loader::loader::pe::arm64_image_header as boot_params;
|
||||||
|
use log::{debug, error};
|
||||||
use vm_device::BusDevice;
|
use vm_device::BusDevice;
|
||||||
use vm_memory::bitmap::AtomicBitmap;
|
use vm_memory::bitmap::AtomicBitmap;
|
||||||
use vm_memory::{
|
use vm_memory::{
|
||||||
@@ -442,7 +443,7 @@ impl FwCfg {
|
|||||||
fw_cfg_item_list: Option<Vec<FwCfgItem>>,
|
fw_cfg_item_list: Option<Vec<FwCfgItem>>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
if let Some(mem_size) = mem_size {
|
if let Some(mem_size) = mem_size {
|
||||||
self.add_e820(mem_size)?
|
self.add_e820(mem_size)?;
|
||||||
}
|
}
|
||||||
if let Some(kernel) = kernel {
|
if let Some(kernel) = kernel {
|
||||||
self.add_kernel_data(&kernel)?;
|
self.add_kernel_data(&kernel)?;
|
||||||
@@ -451,7 +452,7 @@ impl FwCfg {
|
|||||||
self.add_kernel_cmdline(cmdline);
|
self.add_kernel_cmdline(cmdline);
|
||||||
}
|
}
|
||||||
if let Some(initramfs) = initramfs {
|
if let Some(initramfs) = initramfs {
|
||||||
self.add_initramfs_data(&initramfs)?
|
self.add_initramfs_data(&initramfs)?;
|
||||||
}
|
}
|
||||||
if let Some(fw_cfg_item_list) = fw_cfg_item_list {
|
if let Some(fw_cfg_item_list) = fw_cfg_item_list {
|
||||||
for item in fw_cfg_item_list {
|
for item in fw_cfg_item_list {
|
||||||
@@ -626,7 +627,7 @@ impl FwCfg {
|
|||||||
&access_resp.0.to_be_bytes(),
|
&access_resp.0.to_be_bytes(),
|
||||||
GuestAddress(dma_address + core::mem::offset_of!(FwCfgDmaAccess, control_be) as u64),
|
GuestAddress(dma_address + core::mem::offset_of!(FwCfgDmaAccess, control_be) as u64),
|
||||||
) {
|
) {
|
||||||
error!("fw_cfg: finishing dma: {e:?}")
|
error!("fw_cfg: finishing dma: {e:?}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -710,7 +711,7 @@ impl FwCfg {
|
|||||||
let bytes = n.to_le_bytes();
|
let bytes = n.to_le_bytes();
|
||||||
data.copy_from_slice(&bytes[start..end]);
|
data.copy_from_slice(&bytes[start..end]);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
Some(size as u8)
|
Some(size as u8)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -756,7 +757,7 @@ impl BusDevice for FwCfg {
|
|||||||
"fw_cfg: read from unknown port {port:#x}: {size:#x} bytes and offset {offset:#x}."
|
"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>> {
|
fn write(&mut self, _base: u64, offset: u64, data: &[u8]) -> Option<Arc<Barrier>> {
|
||||||
@@ -792,13 +793,13 @@ impl BusDevice for FwCfg {
|
|||||||
_ => debug!(
|
_ => debug!(
|
||||||
"fw_cfg: write to unknown port {port:#x}: {size:#x} bytes and offset {offset:#x} ."
|
"fw_cfg: write to unknown port {port:#x}: {size:#x} bytes and offset {offset:#x} ."
|
||||||
),
|
),
|
||||||
};
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod unit_tests {
|
||||||
use std::ffi::CString;
|
use std::ffi::CString;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
use std::sync::{Arc, Barrier};
|
use std::sync::{Arc, Barrier};
|
||||||
|
|
||||||
|
use log::error;
|
||||||
use vm_device::BusDevice;
|
use vm_device::BusDevice;
|
||||||
|
|
||||||
/// Provides firmware debug output via I/O port controls
|
/// Provides firmware debug output via I/O port controls
|
||||||
@@ -26,9 +27,9 @@ impl BusDevice for FwDebugDevice {
|
|||||||
/// Upon read return the magic value to indicate that there is a debug port
|
/// Upon read return the magic value to indicate that there is a debug port
|
||||||
fn read(&mut self, _base: u64, _offset: u64, data: &mut [u8]) {
|
fn read(&mut self, _base: u64, _offset: u64, data: &mut [u8]) {
|
||||||
if data.len() == 1 {
|
if data.len() == 1 {
|
||||||
data[0] = 0xe9
|
data[0] = 0xe9;
|
||||||
} else {
|
} else {
|
||||||
error!("Invalid read size on debug port: {}", data.len())
|
error!("Invalid read size on debug port: {}", data.len());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,7 +37,7 @@ impl BusDevice for FwDebugDevice {
|
|||||||
if data.len() == 1 {
|
if data.len() == 1 {
|
||||||
print!("{}", data[0] as char);
|
print!("{}", data[0] as char);
|
||||||
} else {
|
} else {
|
||||||
error!("Invalid write size on debug port: {}", data.len())
|
error!("Invalid write size on debug port: {}", data.len());
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
None
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
use std::sync::{Arc, Barrier};
|
use std::sync::{Arc, Barrier};
|
||||||
use std::{io, result};
|
use std::{io, result};
|
||||||
|
|
||||||
|
use log::warn;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use vm_device::BusDevice;
|
use vm_device::BusDevice;
|
||||||
@@ -261,7 +262,7 @@ impl BusDevice for Gpio {
|
|||||||
let index = ((offset - GPIO_ID_LOW) >> 2) as usize;
|
let index = ((offset - GPIO_ID_LOW) >> 2) as usize;
|
||||||
value = u32::from(GPIO_ID[index]);
|
value = u32::from(GPIO_ID[index]);
|
||||||
} else if offset < OFS_DATA {
|
} else if offset < OFS_DATA {
|
||||||
value = self.data & ((offset >> 2) as u32)
|
value = self.data & ((offset >> 2) as u32);
|
||||||
} else {
|
} else {
|
||||||
value = match offset {
|
value = match offset {
|
||||||
GPIODIR => self.dir,
|
GPIODIR => self.dir,
|
||||||
@@ -294,7 +295,7 @@ impl BusDevice for Gpio {
|
|||||||
if data.len() <= 4 {
|
if data.len() <= 4 {
|
||||||
let value = read_le_u32(data);
|
let value = read_le_u32(data);
|
||||||
if let Err(e) = self.handle_write(offset, value) {
|
if let Err(e) = self.handle_write(offset, value) {
|
||||||
warn!("Failed to write to GPIO PL061 device: {}", e);
|
warn!("Failed to write to GPIO PL061 device: {e}");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
warn!(
|
warn!(
|
||||||
@@ -323,7 +324,7 @@ impl Transportable for Gpio {}
|
|||||||
impl Migratable for Gpio {}
|
impl Migratable for Gpio {}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod unit_tests {
|
||||||
use vm_device::interrupt::{InterruptIndex, InterruptSourceConfig};
|
use vm_device::interrupt::{InterruptIndex, InterruptSourceConfig};
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ use std::sync::atomic::{AtomicBool, Ordering};
|
|||||||
use std::sync::{Arc, Barrier};
|
use std::sync::{Arc, Barrier};
|
||||||
use std::thread;
|
use std::thread;
|
||||||
|
|
||||||
|
use log::{error, info};
|
||||||
use vm_device::BusDevice;
|
use vm_device::BusDevice;
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
@@ -45,7 +46,7 @@ impl BusDevice for I8042Device {
|
|||||||
if data.len() == 1 && data[0] == 0xfe && offset == 3 {
|
if data.len() == 1 && data[0] == 0xfe && offset == 3 {
|
||||||
info!("i8042 reset signalled");
|
info!("i8042 reset signalled");
|
||||||
if let Err(e) = self.reset_evt.write(1) {
|
if let Err(e) = self.reset_evt.write(1) {
|
||||||
error!("Error triggering i8042 reset event: {}", e);
|
error!("Error triggering i8042 reset event: {e}");
|
||||||
}
|
}
|
||||||
// Spin until we are sure the reset_evt has been handled and that when
|
// 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.
|
// we return from the KVM_RUN we will exit rather than re-enter the guest.
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ use std::result;
|
|||||||
use std::sync::{Arc, Barrier};
|
use std::sync::{Arc, Barrier};
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
|
use log::warn;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use vm_device::BusDevice;
|
use vm_device::BusDevice;
|
||||||
|
|
||||||
@@ -201,12 +202,11 @@ impl BusDevice for Rtc {
|
|||||||
if data.len() <= 4 {
|
if data.len() <= 4 {
|
||||||
let v = read_le_u32(data);
|
let v = read_le_u32(data);
|
||||||
if let Err(e) = self.handle_write(offset, v) {
|
if let Err(e) = self.handle_write(offset, v) {
|
||||||
warn!("Failed to write to RTC PL031 device: {}", e);
|
warn!("Failed to write to RTC PL031 device: {e}");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
warn!(
|
warn!(
|
||||||
"Invalid RTC PL031 write: offset {}, data length {}",
|
"Invalid RTC PL031 write: offset {offset}, data length {}",
|
||||||
offset,
|
|
||||||
data.len()
|
data.len()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -216,7 +216,7 @@ impl BusDevice for Rtc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod unit_tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{
|
use crate::{
|
||||||
read_be_u16, read_be_u32, read_le_i32, read_le_u16, read_le_u64, write_be_u16,
|
read_be_u16, read_be_u32, read_le_i32, read_le_u16, read_le_u64, write_be_u16,
|
||||||
|
|||||||
@@ -215,7 +215,7 @@ impl Serial {
|
|||||||
fn thr_empty(&mut self) -> Result<()> {
|
fn thr_empty(&mut self) -> Result<()> {
|
||||||
if self.is_thr_intr_enabled() {
|
if self.is_thr_intr_enabled() {
|
||||||
self.add_intr_bit(IIR_THR_BIT);
|
self.add_intr_bit(IIR_THR_BIT);
|
||||||
self.trigger_interrupt()?
|
self.trigger_interrupt()?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -223,7 +223,7 @@ impl Serial {
|
|||||||
fn recv_data(&mut self) -> Result<()> {
|
fn recv_data(&mut self) -> Result<()> {
|
||||||
if self.is_recv_intr_enabled() {
|
if self.is_recv_intr_enabled() {
|
||||||
self.add_intr_bit(IIR_RECV_BIT);
|
self.add_intr_bit(IIR_RECV_BIT);
|
||||||
self.trigger_interrupt()?
|
self.trigger_interrupt()?;
|
||||||
}
|
}
|
||||||
self.line_status |= LSR_DATA_BIT;
|
self.line_status |= LSR_DATA_BIT;
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -240,10 +240,10 @@ impl Serial {
|
|||||||
fn handle_write(&mut self, offset: u8, v: u8) -> Result<()> {
|
fn handle_write(&mut self, offset: u8, v: u8) -> Result<()> {
|
||||||
match offset {
|
match offset {
|
||||||
DLAB_LOW if self.is_dlab_set() => {
|
DLAB_LOW if self.is_dlab_set() => {
|
||||||
self.baud_divisor = (self.baud_divisor & 0xff00) | u16::from(v)
|
self.baud_divisor = (self.baud_divisor & 0xff00) | u16::from(v);
|
||||||
}
|
}
|
||||||
DLAB_HIGH if self.is_dlab_set() => {
|
DLAB_HIGH if self.is_dlab_set() => {
|
||||||
self.baud_divisor = (self.baud_divisor & 0x00ff) | ((u16::from(v)) << 8)
|
self.baud_divisor = (self.baud_divisor & 0x00ff) | ((u16::from(v)) << 8);
|
||||||
}
|
}
|
||||||
DATA => {
|
DATA => {
|
||||||
if self.is_loop() {
|
if self.is_loop() {
|
||||||
@@ -340,7 +340,7 @@ impl Transportable for Serial {}
|
|||||||
impl Migratable for Serial {}
|
impl Migratable for Serial {}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod unit_tests {
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
|
||||||
use vm_device::interrupt::{InterruptIndex, InterruptSourceConfig};
|
use vm_device::interrupt::{InterruptIndex, InterruptSourceConfig};
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ use std::sync::{Arc, Barrier};
|
|||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
use std::{io, result};
|
use std::{io, result};
|
||||||
|
|
||||||
|
use log::{debug, warn};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use vm_device::BusDevice;
|
use vm_device::BusDevice;
|
||||||
@@ -322,7 +323,7 @@ impl Pl011 {
|
|||||||
self.handle_debug();
|
self.handle_debug();
|
||||||
}
|
}
|
||||||
off => {
|
off => {
|
||||||
debug!("PL011: Bad write offset, offset: {}", off);
|
debug!("PL011: Bad write offset, offset: {off}");
|
||||||
return Err(Error::BadWriteOffset(off));
|
return Err(Error::BadWriteOffset(off));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -424,12 +425,11 @@ impl BusDevice for Pl011 {
|
|||||||
if data.len() <= 4 {
|
if data.len() <= 4 {
|
||||||
let v = read_le_u32(data);
|
let v = read_le_u32(data);
|
||||||
if let Err(e) = self.handle_write(offset, v) {
|
if let Err(e) = self.handle_write(offset, v) {
|
||||||
warn!("Failed to write to PL011 device: {}", e);
|
warn!("Failed to write to PL011 device: {e}");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
warn!(
|
warn!(
|
||||||
"Invalid PL011 write: offset {}, data length {}",
|
"Invalid PL011 write: offset {offset}, data length {}",
|
||||||
offset,
|
|
||||||
data.len()
|
data.len()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -453,7 +453,7 @@ impl Transportable for Pl011 {}
|
|||||||
impl Migratable for Pl011 {}
|
impl Migratable for Pl011 {}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod unit_tests {
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
|
||||||
use vm_device::interrupt::{InterruptIndex, InterruptSourceConfig};
|
use vm_device::interrupt::{InterruptIndex, InterruptSourceConfig};
|
||||||
|
|||||||
@@ -7,13 +7,6 @@
|
|||||||
|
|
||||||
//! Emulates virtual and hardware devices.
|
//! Emulates virtual and hardware devices.
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
extern crate bitflags;
|
|
||||||
#[macro_use]
|
|
||||||
extern crate event_monitor;
|
|
||||||
#[macro_use]
|
|
||||||
extern crate log;
|
|
||||||
|
|
||||||
pub mod acpi;
|
pub mod acpi;
|
||||||
#[cfg(target_arch = "riscv64")]
|
#[cfg(target_arch = "riscv64")]
|
||||||
pub mod aia;
|
pub mod aia;
|
||||||
@@ -34,6 +27,8 @@ pub mod pvpanic;
|
|||||||
#[cfg(not(target_arch = "riscv64"))]
|
#[cfg(not(target_arch = "riscv64"))]
|
||||||
pub mod tpm;
|
pub mod tpm;
|
||||||
|
|
||||||
|
use bitflags::bitflags;
|
||||||
|
|
||||||
pub use self::acpi::{AcpiGedDevice, AcpiPmTimerDevice, AcpiShutdownDevice};
|
pub use self::acpi::{AcpiGedDevice, AcpiPmTimerDevice, AcpiShutdownDevice};
|
||||||
#[cfg(feature = "ivshmem")]
|
#[cfg(feature = "ivshmem")]
|
||||||
pub use self::ivshmem::IvshmemDevice;
|
pub use self::ivshmem::IvshmemDevice;
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ use std::ffi::CString;
|
|||||||
use std::sync::{Arc, Barrier, Mutex, RwLock};
|
use std::sync::{Arc, Barrier, Mutex, RwLock};
|
||||||
use std::{io, result};
|
use std::{io, result};
|
||||||
|
|
||||||
|
use log::{debug, warn};
|
||||||
use num_enum::TryFromPrimitive;
|
use num_enum::TryFromPrimitive;
|
||||||
use pci::{
|
use pci::{
|
||||||
BarReprogrammingParams, PciBarConfiguration, PciBarPrefetchable, PciBarRegionType,
|
BarReprogrammingParams, PciBarConfiguration, PciBarPrefetchable, PciBarRegionType,
|
||||||
@@ -390,7 +391,7 @@ impl PvmemcontrolDevice {
|
|||||||
.iter()
|
.iter()
|
||||||
.skip(offset as usize)
|
.skip(offset as usize)
|
||||||
.zip(data.iter_mut())
|
.zip(data.iter_mut())
|
||||||
.for_each(|(src, dest)| *dest = *src)
|
.for_each(|(src, dest)| *dest = *src);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// can only write to transport payload
|
/// can only write to transport payload
|
||||||
@@ -402,7 +403,7 @@ impl PvmemcontrolDevice {
|
|||||||
.iter_mut()
|
.iter_mut()
|
||||||
.skip(offset as usize)
|
.skip(offset as usize)
|
||||||
.zip(data.iter())
|
.zip(data.iter())
|
||||||
.for_each(|(dest, src)| *dest = *src)
|
.for_each(|(dest, src)| *dest = *src);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_connection(&self, conn: GuestConnection) -> Option<GuestAddress> {
|
fn find_connection(&self, conn: GuestConnection) -> Option<GuestAddress> {
|
||||||
@@ -429,22 +430,20 @@ impl PvmemcontrolBusDevice {
|
|||||||
/// [`range_base`, `range_base` + `range_len`) is present in the guest
|
/// [`range_base`, `range_base` + `range_len`) is present in the guest
|
||||||
fn operate_on_memory_range<F>(&self, addr: u64, length: u64, f: F) -> result::Result<(), Error>
|
fn operate_on_memory_range<F>(&self, addr: u64, length: u64, f: F) -> result::Result<(), Error>
|
||||||
where
|
where
|
||||||
F: FnOnce(*mut libc::c_void, libc::size_t) -> libc::c_int,
|
F: FnOnce(*mut libc::c_void, usize) -> libc::c_int,
|
||||||
{
|
{
|
||||||
let memory = self.mem.memory();
|
let memory = self.mem.memory();
|
||||||
let range_base = GuestAddress(addr);
|
let range_base = GuestAddress(addr);
|
||||||
let range_len = usize::try_from(length).map_err(|_| Error::InvalidRequest)?;
|
let range_len = usize::try_from(length).map_err(|_| Error::InvalidRequest)?;
|
||||||
|
|
||||||
// assume guest memory is not interleaved with vmm memory on the host.
|
// assume guest memory is not interleaved with vmm memory on the host.
|
||||||
if !memory.check_range(range_base, range_len) {
|
let Ok(slice) = memory.get_slice(range_base, range_len) else {
|
||||||
return Err(Error::GuestMemory(GuestMemoryError::InvalidGuestAddress(
|
return Err(Error::GuestMemory(GuestMemoryError::InvalidGuestAddress(
|
||||||
range_base,
|
range_base,
|
||||||
)));
|
)));
|
||||||
}
|
};
|
||||||
let hva = memory
|
assert!(slice.len() >= range_len);
|
||||||
.get_host_address(range_base)
|
let res = f(slice.ptr_guard_mut().as_ptr() as _, slice.len());
|
||||||
.map_err(Error::GuestMemory)?;
|
|
||||||
let res = f(hva as *mut libc::c_void, range_len as libc::size_t);
|
|
||||||
if res != 0 {
|
if res != 0 {
|
||||||
return Err(Error::LibcFail(io::Error::last_os_error()));
|
return Err(Error::LibcFail(io::Error::last_os_error()));
|
||||||
}
|
}
|
||||||
@@ -491,7 +490,7 @@ impl PvmemcontrolBusDevice {
|
|||||||
} else {
|
} else {
|
||||||
std::ptr::null()
|
std::ptr::null()
|
||||||
};
|
};
|
||||||
debug!("addr {:X} length {} name {:?}", addr, length, name);
|
debug!("addr {addr:X} length {length} name {name:?}");
|
||||||
|
|
||||||
// SAFETY: [`base`, `base` + `len`) is guest memory
|
// SAFETY: [`base`, `base` + `len`) is guest memory
|
||||||
self.operate_on_memory_range(addr, length, |base, len| unsafe {
|
self.operate_on_memory_range(addr, length, |base, len| unsafe {
|
||||||
@@ -581,7 +580,7 @@ impl PvmemcontrolBusDevice {
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
Error::GuestMemory(err) => {
|
Error::GuestMemory(err) => {
|
||||||
warn!("{}", err);
|
warn!("{err}");
|
||||||
PvmemcontrolResp {
|
PvmemcontrolResp {
|
||||||
ret_errno: (libc::EINVAL as u32).into(),
|
ret_errno: (libc::EINVAL as u32).into(),
|
||||||
ret_code: (func_code as u32).into(),
|
ret_code: (func_code as u32).into(),
|
||||||
@@ -606,7 +605,7 @@ impl PvmemcontrolBusDevice {
|
|||||||
let response: PvmemcontrolResp = match self.handle_request(request) {
|
let response: PvmemcontrolResp = match self.handle_request(request) {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
warn!("cannot process request {:?} with error {}", request, e);
|
warn!("cannot process request {request:?} with error {e}");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -649,14 +648,16 @@ impl PvmemcontrolBusDevice {
|
|||||||
.find_connection(conn)
|
.find_connection(conn)
|
||||||
.ok_or(Error::InvalidConnection(conn.command))
|
.ok_or(Error::InvalidConnection(conn.command))
|
||||||
})
|
})
|
||||||
.map(|gpa| self.handle_pvmemcontrol_request(gpa))
|
.map_or_else(
|
||||||
.unwrap_or_else(|err| warn!("{:?}", err));
|
|err| warn!("{err:?}"),
|
||||||
|
|gpa| self.handle_pvmemcontrol_request(gpa),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_guest_read(&self, offset: u64, data: &mut [u8]) {
|
fn handle_guest_read(&self, offset: u64, data: &mut [u8]) {
|
||||||
self.dev.read().unwrap().read_transport(offset, data)
|
self.dev.read().unwrap().read_transport(offset, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -760,7 +761,7 @@ impl PciDevice for PvmemcontrolPciDevice {
|
|||||||
_mmio64_allocator: &mut AddressAllocator,
|
_mmio64_allocator: &mut AddressAllocator,
|
||||||
) -> Result<(), PciDeviceError> {
|
) -> Result<(), PciDeviceError> {
|
||||||
for bar in self.bar_regions.drain(..) {
|
for bar in self.bar_regions.drain(..) {
|
||||||
mmio32_allocator.free(GuestAddress(bar.addr()), bar.size())
|
mmio32_allocator.free(GuestAddress(bar.addr()), bar.size());
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -805,7 +806,7 @@ impl Migratable for PvmemcontrolPciDevice {}
|
|||||||
|
|
||||||
impl BusDeviceSync for PvmemcontrolBusDevice {
|
impl BusDeviceSync for PvmemcontrolBusDevice {
|
||||||
fn read(&self, _base: u64, offset: u64, data: &mut [u8]) {
|
fn read(&self, _base: u64, offset: u64, data: &mut [u8]) {
|
||||||
self.handle_guest_read(offset, data)
|
self.handle_guest_read(offset, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write(&self, _base: u64, offset: u64, data: &[u8]) -> Option<Arc<Barrier>> {
|
fn write(&self, _base: u64, offset: u64, data: &[u8]) -> Option<Arc<Barrier>> {
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ use std::result;
|
|||||||
use std::sync::{Arc, Barrier, Mutex};
|
use std::sync::{Arc, Barrier, Mutex};
|
||||||
|
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
|
use event_monitor::event;
|
||||||
|
use log::{debug, info};
|
||||||
use pci::{
|
use pci::{
|
||||||
BarReprogrammingParams, PCI_CONFIGURATION_ID, PciBarConfiguration, PciBarPrefetchable,
|
BarReprogrammingParams, PCI_CONFIGURATION_ID, PciBarConfiguration, PciBarPrefetchable,
|
||||||
PciBarRegionType, PciClassCode, PciConfiguration, PciDevice, PciDeviceError, PciHeaderType,
|
PciBarRegionType, PciClassCode, PciConfiguration, PciDevice, PciDeviceError, PciHeaderType,
|
||||||
@@ -64,14 +66,13 @@ pub struct PvPanicDeviceState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl PvPanicDevice {
|
impl PvPanicDevice {
|
||||||
pub fn new(id: String, snapshot: Option<Snapshot>) -> Result<Self, PvPanicError> {
|
pub fn new(id: String, snapshot: Option<&Snapshot>) -> Result<Self, PvPanicError> {
|
||||||
let pci_configuration_state =
|
let pci_configuration_state = vm_migration::state_from_id(snapshot, PCI_CONFIGURATION_ID)
|
||||||
vm_migration::state_from_id(snapshot.as_ref(), PCI_CONFIGURATION_ID).map_err(|e| {
|
.map_err(|e| {
|
||||||
PvPanicError::RetrievePciConfigurationState(anyhow!(
|
PvPanicError::RetrievePciConfigurationState(anyhow!(
|
||||||
"Failed to get PciConfigurationState from Snapshot: {}",
|
"Failed to get PciConfigurationState from Snapshot: {e}"
|
||||||
e
|
))
|
||||||
))
|
})?;
|
||||||
})?;
|
|
||||||
|
|
||||||
let mut configuration = PciConfiguration::new(
|
let mut configuration = PciConfiguration::new(
|
||||||
PVPANIC_VENDOR_ID,
|
PVPANIC_VENDOR_ID,
|
||||||
@@ -100,8 +101,7 @@ impl PvPanicDevice {
|
|||||||
.transpose()
|
.transpose()
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
PvPanicError::CreatePvPanicDevice(anyhow!(
|
PvPanicError::CreatePvPanicDevice(anyhow!(
|
||||||
"Failed to get PvPanicDeviceState from Snapshot: {}",
|
"Failed to get PvPanicDeviceState from Snapshot: {e}"
|
||||||
e
|
|
||||||
))
|
))
|
||||||
})?;
|
})?;
|
||||||
let events = if let Some(state) = state {
|
let events = if let Some(state) = state {
|
||||||
@@ -143,12 +143,12 @@ impl PvPanicDevice {
|
|||||||
|
|
||||||
impl BusDevice for PvPanicDevice {
|
impl BusDevice for PvPanicDevice {
|
||||||
fn read(&mut self, base: u64, offset: u64, data: &mut [u8]) {
|
fn read(&mut self, base: u64, offset: u64, data: &mut [u8]) {
|
||||||
self.read_bar(base, offset, data)
|
self.read_bar(base, offset, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write(&mut self, _base: u64, _offset: u64, data: &[u8]) -> Option<Arc<Barrier>> {
|
fn write(&mut self, _base: u64, _offset: u64, data: &[u8]) -> Option<Arc<Barrier>> {
|
||||||
let event = self.event_to_string(data[0]);
|
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);
|
event!("guest", "panic", "event", &event);
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
use std::cmp;
|
use std::cmp;
|
||||||
|
use std::path::Path;
|
||||||
use std::sync::{Arc, Barrier};
|
use std::sync::{Arc, Barrier};
|
||||||
|
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
@@ -11,6 +12,7 @@ use anyhow::anyhow;
|
|||||||
use arch::aarch64::layout::{TPM_SIZE, TPM_START};
|
use arch::aarch64::layout::{TPM_SIZE, TPM_START};
|
||||||
#[cfg(target_arch = "x86_64")]
|
#[cfg(target_arch = "x86_64")]
|
||||||
use arch::x86_64::layout::{TPM_SIZE, TPM_START};
|
use arch::x86_64::layout::{TPM_SIZE, TPM_START};
|
||||||
|
use log::{debug, error, warn};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use tpm::TPM_CRB_BUFFER_MAX;
|
use tpm::TPM_CRB_BUFFER_MAX;
|
||||||
use tpm::emulator::{BackendCmd, Emulator};
|
use tpm::emulator::{BackendCmd, Emulator};
|
||||||
@@ -26,6 +28,7 @@ pub enum Error {
|
|||||||
type Result<T> = anyhow::Result<T, Error>;
|
type Result<T> = anyhow::Result<T, Error>;
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
enum LocStateFields {
|
enum LocStateFields {
|
||||||
TpmEstablished,
|
TpmEstablished,
|
||||||
LocAssigned,
|
LocAssigned,
|
||||||
@@ -34,12 +37,14 @@ enum LocStateFields {
|
|||||||
TpmRegValidSts,
|
TpmRegValidSts,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
enum LocStsFields {
|
enum LocStsFields {
|
||||||
Granted,
|
Granted,
|
||||||
BeenSeized,
|
BeenSeized,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
enum IntfIdFields {
|
enum IntfIdFields {
|
||||||
InterfaceType,
|
InterfaceType,
|
||||||
InterfaceVersion,
|
InterfaceVersion,
|
||||||
@@ -57,16 +62,19 @@ enum IntfIdFields {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
enum IntfId2Fields {
|
enum IntfId2Fields {
|
||||||
Vid,
|
Vid,
|
||||||
Did,
|
Did,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
enum CtrlStsFields {
|
enum CtrlStsFields {
|
||||||
TpmSts,
|
TpmSts,
|
||||||
TpmIdle,
|
TpmIdle,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
enum CrbRegister {
|
enum CrbRegister {
|
||||||
LocState(LocStateFields),
|
LocState(LocStateFields),
|
||||||
LocSts(LocStsFields),
|
LocSts(LocStsFields),
|
||||||
@@ -99,6 +107,7 @@ const CRB_LOC_CTRL_REQUEST_ACCESS: u32 = 1 << 0;
|
|||||||
const CRB_LOC_CTRL_RELINQUISH: u32 = 1 << 1;
|
const CRB_LOC_CTRL_RELINQUISH: u32 = 1 << 1;
|
||||||
const CRB_LOC_CTRL_RESET_ESTABLISHMENT_BIT: u32 = 1 << 3;
|
const CRB_LOC_CTRL_RESET_ESTABLISHMENT_BIT: u32 = 1 << 3;
|
||||||
const CRB_LOC_STS: u32 = 0x0C;
|
const CRB_LOC_STS: u32 = 0x0C;
|
||||||
|
|
||||||
const fn get_crb_loc_sts_field(f: LocStsFields) -> (u32, u32, u32) {
|
const fn get_crb_loc_sts_field(f: LocStsFields) -> (u32, u32, u32) {
|
||||||
let (offset, len) = match f {
|
let (offset, len) = match f {
|
||||||
LocStsFields::Granted => (0, 1),
|
LocStsFields::Granted => (0, 1),
|
||||||
@@ -219,9 +228,9 @@ pub struct Tpm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Tpm {
|
impl Tpm {
|
||||||
pub fn new(path: String) -> Result<Self> {
|
pub fn new(path: impl AsRef<Path>) -> Result<Self> {
|
||||||
let emulator = Emulator::new(path)
|
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 {
|
let mut tpm = Tpm {
|
||||||
emulator,
|
emulator,
|
||||||
regs: [0; TPM_CRB_R_MAX],
|
regs: [0; TPM_CRB_R_MAX],
|
||||||
@@ -331,8 +340,7 @@ impl Tpm {
|
|||||||
|
|
||||||
if let Err(e) = self.emulator.startup_tpm(self.backend_buff_size) {
|
if let Err(e) = self.emulator.startup_tpm(self.backend_buff_size) {
|
||||||
return Err(Error::Init(anyhow!(
|
return Err(Error::Init(anyhow!(
|
||||||
"Failed while running Startup TPM. Error: {:?}",
|
"Failed while running Startup TPM. Error: {e:?}"
|
||||||
e
|
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -460,7 +468,7 @@ impl BusDevice for Tpm {
|
|||||||
&& (self.regs[CRB_CTRL_START as usize] & CRB_START_INVOKE != 0)
|
&& (self.regs[CRB_CTRL_START as usize] & CRB_START_INVOKE != 0)
|
||||||
&& let Err(e) = self.emulator.cancel_cmd()
|
&& 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 => {
|
CRB_CTRL_START => {
|
||||||
@@ -481,10 +489,7 @@ impl BusDevice for Tpm {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
CRB_LOC_CTRL => {
|
CRB_LOC_CTRL => {
|
||||||
warn!(
|
warn!("CRB_LOC_CTRL locality to write = {locality:?} val = {v:?}");
|
||||||
"CRB_LOC_CTRL locality to write = {:?} val = {:?}",
|
|
||||||
locality, v
|
|
||||||
);
|
|
||||||
match v {
|
match v {
|
||||||
CRB_LOC_CTRL_RESET_ESTABLISHMENT_BIT => {}
|
CRB_LOC_CTRL_RESET_ESTABLISHMENT_BIT => {}
|
||||||
CRB_LOC_CTRL_RELINQUISH => {
|
CRB_LOC_CTRL_RELINQUISH => {
|
||||||
@@ -517,7 +522,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} ");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -535,7 +540,7 @@ impl BusDevice for Tpm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod unit_tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
15
docs/api.md
15
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
|
$ ./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
|
#### REST API Endpoints
|
||||||
@@ -93,6 +86,7 @@ The Cloud Hypervisor API exposes the following actions through its endpoints:
|
|||||||
| Restore the VM from a snapshot | `/vm.restore` | `/schemas/RestoreConfig` | N/A | The VM is created but not booted |
|
| Restore the VM from a snapshot | `/vm.restore` | `/schemas/RestoreConfig` | N/A | The VM is created but not booted |
|
||||||
| Add/remove CPUs to/from the VM | `/vm.resize` | `/schemas/VmResize` | N/A | The VM is booted |
|
| Add/remove CPUs to/from the VM | `/vm.resize` | `/schemas/VmResize` | N/A | The VM is booted |
|
||||||
| Add/remove memory from the VM | `/vm.resize` | `/schemas/VmResize` | N/A | The VM is booted |
|
| Add/remove memory from the VM | `/vm.resize` | `/schemas/VmResize` | N/A | The VM is booted |
|
||||||
|
| Resize a disk attached to the VM | `/vm.resize-disk` | `/schemas/VmResizeDisk` | N/A | The VM is created |
|
||||||
| Add/remove memory from a zone | `/vm.resize-zone` | `/schemas/VmResizeZone` | N/A | The VM is booted |
|
| Add/remove memory from a zone | `/vm.resize-zone` | `/schemas/VmResizeZone` | N/A | The VM is booted |
|
||||||
| Dump the VM information | `/vm.info` | N/A | `/schemas/VmInfo` | The VM is created |
|
| Dump the VM information | `/vm.info` | N/A | `/schemas/VmInfo` | The VM is created |
|
||||||
| Add VFIO PCI device to the VM | `/vm.add-device` | `/schemas/VmAddDevice` | `/schemas/PciDeviceInfo` | The VM is booted |
|
| Add VFIO PCI device to the VM | `/vm.add-device` | `/schemas/VmAddDevice` | `/schemas/PciDeviceInfo` | The VM is booted |
|
||||||
@@ -121,13 +115,6 @@ the REST API available at `/tmp/cloud-hypervisor.sock`:
|
|||||||
|
|
||||||
```
|
```
|
||||||
$ ./target/debug/cloud-hypervisor --api-socket /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
|
##### Create a Virtual Machine
|
||||||
|
|||||||
14
docs/cpu.md
14
docs/cpu.md
@@ -18,11 +18,12 @@ struct CpusConfig {
|
|||||||
max_phys_bits: u8,
|
max_phys_bits: u8,
|
||||||
affinity: Option<Vec<CpuAffinity>>,
|
affinity: Option<Vec<CpuAffinity>>,
|
||||||
features: CpuFeatures,
|
features: CpuFeatures,
|
||||||
|
nested: bool,
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
```
|
```
|
||||||
--cpus boot=<boot_vcpus>,max=<max_vcpus>,topology=<threads_per_core>:<cores_per_die>:<dies_per_package>:<packages>,kvm_hyperv=on|off,max_phys_bits=<maximum_number_of_physical_bits>,affinity=<list_of_vcpus_with_their_associated_cpuset>,features=<list_of_features_to_enable>
|
--cpus boot=<boot_vcpus>,max=<max_vcpus>,topology=<threads_per_core>:<cores_per_die>:<dies_per_package>:<packages>,kvm_hyperv=on|off,max_phys_bits=<maximum_number_of_physical_bits>,affinity=<list_of_vcpus_with_their_associated_cpuset>,features=<list_of_features_to_enable>,nested=on|off
|
||||||
```
|
```
|
||||||
|
|
||||||
### `boot`
|
### `boot`
|
||||||
@@ -209,3 +210,14 @@ _Example_
|
|||||||
```
|
```
|
||||||
|
|
||||||
In this example the amx CPU feature will be enabled for the VMM.
|
In this example the amx CPU feature will be enabled for the VMM.
|
||||||
|
|
||||||
|
|
||||||
|
### `nested`
|
||||||
|
|
||||||
|
Enable nested virtualization (default on). Nested virtualization is needed to access hardware virtualization by this guest. This option can only be changed on x86-64.
|
||||||
|
|
||||||
|
_Example_
|
||||||
|
|
||||||
|
```
|
||||||
|
--cpus nested=on
|
||||||
|
```
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user