mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
Compare commits
246 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d88996e5b | ||
|
|
3899cd0e30 | ||
|
|
9d93df4c8f | ||
|
|
4c27572b87 | ||
|
|
19fea1ad88 | ||
|
|
9a96ea44be | ||
|
|
e11b9d6449 | ||
|
|
ce5fe7f89d | ||
|
|
0dd0364bf8 | ||
|
|
3081d01fc3 | ||
|
|
fa58b725cb | ||
|
|
a38b4c7f17 | ||
|
|
5aa1540c5d | ||
|
|
7da8ae9c0f | ||
|
|
108e8f9dff | ||
|
|
5ab2feae70 | ||
|
|
c99660a8f9 | ||
|
|
08c1bb4b00 | ||
|
|
5bd177dde8 | ||
|
|
52ed1082fb | ||
|
|
03a23309b9 | ||
|
|
fa0965a11a | ||
|
|
524f26abef | ||
|
|
846d0976e3 | ||
|
|
4718dc1b72 | ||
|
|
0a9895e009 | ||
|
|
297b41d615 | ||
|
|
396aba7a52 | ||
|
|
35aaa1333a | ||
|
|
6768a13d95 | ||
|
|
0d50d0b257 | ||
|
|
f5a2f8473b | ||
|
|
03bb59db69 | ||
|
|
9e1fb3bfa1 | ||
|
|
4de422ad69 | ||
|
|
c441bb2968 | ||
|
|
5cb5115456 | ||
|
|
0fbba66b21 | ||
|
|
5388fa1ced | ||
|
|
655d512523 | ||
|
|
7d45473ff7 | ||
|
|
294d5fbb08 | ||
|
|
8c796e6d5d | ||
|
|
630f5c1f14 | ||
|
|
5cbf907200 | ||
|
|
bfeab76059 | ||
|
|
12143ca72f | ||
|
|
ba05bdd085 | ||
|
|
171b28ce52 | ||
|
|
5b929cb277 | ||
|
|
ee0b0d43d8 | ||
|
|
061482340e | ||
|
|
c43ae1dc9d | ||
|
|
7db3002e1d | ||
|
|
07b7457c2e | ||
|
|
dc9142f86c | ||
|
|
6fa7c84d2e | ||
|
|
cc734e2e44 | ||
|
|
e3e83362d6 | ||
|
|
730cf1e944 | ||
|
|
1caa69d0cc | ||
|
|
abcbf43433 | ||
|
|
af74de2779 | ||
|
|
dfebc8929d | ||
|
|
9988379f6b | ||
|
|
4d12192490 | ||
|
|
9f9cfeb5be | ||
|
|
3fa1e77adc | ||
|
|
b8424ea506 | ||
|
|
0c2fe29f61 | ||
|
|
b445d219ca | ||
|
|
6de283f42c | ||
|
|
f892789481 | ||
|
|
f96554cbe9 | ||
|
|
3509b5bf4a | ||
|
|
cf463b88b7 | ||
|
|
ca8eda5399 | ||
|
|
45b770ea9f | ||
|
|
d2c4ceb99a | ||
|
|
caf7cae2fa | ||
|
|
81eca69b40 | ||
|
|
8c07294691 | ||
|
|
2f9436bc12 | ||
|
|
62fade97d1 | ||
|
|
5a8df3622c | ||
|
|
27fda753e1 | ||
|
|
74ca38f7a9 | ||
|
|
fa686fdfc7 | ||
|
|
c827a1c009 | ||
|
|
eefd7fcaaf | ||
|
|
62303d8c33 | ||
|
|
293cf90e3d | ||
|
|
8f98fabd0b | ||
|
|
07821c08c0 | ||
|
|
0cb2c86ff4 | ||
|
|
d359c8cdce | ||
|
|
c801929fd1 | ||
|
|
66da3b9970 | ||
|
|
6ddbd60d9d | ||
|
|
2fc4de6c65 | ||
|
|
03eeb36b74 | ||
|
|
2fe7f54ece | ||
|
|
1cc540c880 | ||
|
|
a1af4238ae | ||
|
|
d2e798944a | ||
|
|
d99f294281 | ||
|
|
44c9ce598d | ||
|
|
5b42aa0bcf | ||
|
|
703e0cab04 | ||
|
|
abb88cf47f | ||
|
|
2e22b8bc3a | ||
|
|
778c05d678 | ||
|
|
5b29eba636 | ||
|
|
e6e78e5986 | ||
|
|
a2df4d7660 | ||
|
|
bc4a1fd16c | ||
|
|
463c9b8e56 | ||
|
|
835847bea5 | ||
|
|
3e4ed5621e | ||
|
|
2624f17ffe | ||
|
|
21f05ebb4f | ||
|
|
1485e17268 | ||
|
|
b57cc3d79f | ||
|
|
a322e2d6f4 | ||
|
|
dd0b95ba5c | ||
|
|
b6667f948e | ||
|
|
4f22f67307 | ||
|
|
eeae63b459 | ||
|
|
abf5748f64 | ||
|
|
23b5f7b1c5 | ||
|
|
a66fef407c | ||
|
|
7c39f37855 | ||
|
|
fe24a7a24f | ||
|
|
73e1451a12 | ||
|
|
1180f757b3 | ||
|
|
2932658acd | ||
|
|
1f7b809619 | ||
|
|
32482f6634 | ||
|
|
6fd5b0f696 | ||
|
|
ef88b2778e | ||
|
|
8b55d1e2d4 | ||
|
|
2df285effd | ||
|
|
4ea4391f45 | ||
|
|
8f8d3c0139 | ||
|
|
408467a05f | ||
|
|
f27b028c18 | ||
|
|
eaa2194699 | ||
|
|
9973d28849 | ||
|
|
1cdb91a40f | ||
|
|
57e331db0e | ||
|
|
090fcebfec | ||
|
|
b2f40afc69 | ||
|
|
72452707ee | ||
|
|
49a389ff12 | ||
|
|
2a9b60dcce | ||
|
|
bbd8d3b71d | ||
|
|
b9163bf431 | ||
|
|
909e1bc3f0 | ||
|
|
4f18476cca | ||
|
|
3d7661664d | ||
|
|
50bac1694f | ||
|
|
dd8a5a7de8 | ||
|
|
dba92c28f7 | ||
|
|
4e298d1abf | ||
|
|
149c342867 | ||
|
|
4054a49e2d | ||
|
|
1001d807ff | ||
|
|
e4a5219f53 | ||
|
|
a458351d8b | ||
|
|
5a27bf878c | ||
|
|
310dafb4d7 | ||
|
|
78895dcc37 | ||
|
|
1757d83db3 | ||
|
|
9cef779cc7 | ||
|
|
a10d1ed78e | ||
|
|
dc7f0a42bc | ||
|
|
53098ae40f | ||
|
|
51cf346828 | ||
|
|
0042447fb9 | ||
|
|
e8c330e220 | ||
|
|
ba78e331c6 | ||
|
|
b6d2ac2c2e | ||
|
|
f6fb442fa3 | ||
|
|
0c2f2d3ec1 | ||
|
|
584c088094 | ||
|
|
5b715f483d | ||
|
|
1e602bd9a6 | ||
|
|
6b57f301db | ||
|
|
cd0db3146f | ||
|
|
ac3ef0849e | ||
|
|
bd8db86b0c | ||
|
|
7b5f06788a | ||
|
|
9a7f278716 | ||
|
|
fb457954bb | ||
|
|
c4063d26be | ||
|
|
9006013c60 | ||
|
|
30cf1eed5e | ||
|
|
c6b023e58d | ||
|
|
46f0e0d14a | ||
|
|
261bfac4d4 | ||
|
|
ec9b9ab542 | ||
|
|
8c9b34c9f1 | ||
|
|
5b822191c0 | ||
|
|
ab7b294688 | ||
|
|
7e419784cd | ||
|
|
48bed965c0 | ||
|
|
0d6cef4521 | ||
|
|
81f8a27ef6 | ||
|
|
e3bd5e9b35 | ||
|
|
80b2c98a68 | ||
|
|
3c05626ad1 | ||
|
|
906580ee92 | ||
|
|
c6889388a3 | ||
|
|
f38fc94115 | ||
|
|
6ecca29aa6 | ||
|
|
2ef04671be | ||
|
|
453bc31994 | ||
|
|
6e6966df8f | ||
|
|
f32c2638f6 | ||
|
|
fbe1cd6404 | ||
|
|
778a72f52f | ||
|
|
76256230d6 | ||
|
|
8cd80ea36b | ||
|
|
5e937c8b88 | ||
|
|
710535343b | ||
|
|
59c5b0a1cd | ||
|
|
c13019d5b5 | ||
|
|
c5774685f4 | ||
|
|
2df8d2fa1e | ||
|
|
dd00bd6ef2 | ||
|
|
2752149bb0 | ||
|
|
1bc74e43f8 | ||
|
|
8acaeb5c82 | ||
|
|
5eaf510f90 | ||
|
|
df1d6eaaee | ||
|
|
a6a3d247da | ||
|
|
838a857311 | ||
|
|
95229b24ab | ||
|
|
71b0eb3c21 | ||
|
|
337cbf3d33 | ||
|
|
675140f627 | ||
|
|
894a4dee6e | ||
|
|
f57d00cd66 | ||
|
|
59a1719b6e | ||
|
|
39ab10244b | ||
|
|
b5d856b31a |
2
.github/workflows/build.yaml
vendored
2
.github/workflows/build.yaml
vendored
@@ -15,7 +15,7 @@ jobs:
|
||||
- stable
|
||||
- beta
|
||||
- nightly
|
||||
- "1.77.0"
|
||||
- "1.82.0"
|
||||
target:
|
||||
- x86_64-unknown-linux-gnu
|
||||
- x86_64-unknown-linux-musl
|
||||
|
||||
14
.github/workflows/docker-image.yaml
vendored
14
.github/workflows/docker-image.yaml
vendored
@@ -21,13 +21,13 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to ghcr
|
||||
uses: docker/login-action@v2
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
@@ -36,17 +36,17 @@ jobs:
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
# generate Docker tags based on the following events/attributes
|
||||
tags: |
|
||||
type=raw,value=20241022-0
|
||||
type=raw,value=20250307-2
|
||||
type=sha
|
||||
|
||||
- name: Build and push
|
||||
if: ${{ github.event_name == 'push' }}
|
||||
uses: docker/build-push-action@v2
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
file: ./resources/Dockerfile
|
||||
platforms: linux/amd64,linux/arm64
|
||||
@@ -55,7 +55,7 @@ jobs:
|
||||
|
||||
- name: Build only
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
uses: docker/build-push-action@v2
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
file: ./resources/Dockerfile
|
||||
platforms: linux/amd64,linux/arm64
|
||||
|
||||
4
.github/workflows/integration-arm64.yaml
vendored
4
.github/workflows/integration-arm64.yaml
vendored
@@ -6,7 +6,7 @@ concurrency:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
timeout-minutes: 60
|
||||
timeout-minutes: 120
|
||||
name: Tests (ARM64)
|
||||
runs-on: bookworm-arm64
|
||||
steps:
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
- name: Load openvswitch module
|
||||
run: sudo modprobe openvswitch
|
||||
- name: Run integration tests (musl)
|
||||
timeout-minutes: 30
|
||||
timeout-minutes: 60
|
||||
run: scripts/dev_cli.sh tests --integration --libc musl
|
||||
- name: Install Azure CLI
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
|
||||
11
.github/workflows/integration-metrics.yaml
vendored
11
.github/workflows/integration-metrics.yaml
vendored
@@ -7,7 +7,7 @@ on:
|
||||
jobs:
|
||||
build:
|
||||
name: Tests (Metrics)
|
||||
runs-on: garm-jammy-16
|
||||
runs-on: bare-metal-9950x
|
||||
env:
|
||||
METRICS_PUBLISH_KEY: ${{ secrets.METRICS_PUBLISH_KEY }}
|
||||
steps:
|
||||
@@ -15,15 +15,6 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Install Docker
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get -y install ca-certificates curl gnupg
|
||||
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
|
||||
sudo chmod a+r /usr/share/keyrings/docker-archive-keyring.gpg
|
||||
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
|
||||
sudo apt-get update
|
||||
sudo apt install -y docker-ce docker-ce-cli
|
||||
- name: Run metrics tests
|
||||
timeout-minutes: 60
|
||||
run: scripts/dev_cli.sh tests --metrics -- -- --report-file /root/workloads/metrics.json
|
||||
|
||||
14
.github/workflows/integration-rate-limiter.yaml
vendored
14
.github/workflows/integration-rate-limiter.yaml
vendored
@@ -7,7 +7,7 @@ concurrency:
|
||||
jobs:
|
||||
build:
|
||||
name: Tests (Rate-Limiter)
|
||||
runs-on: ${{ github.event_name == 'pull_request' && 'ubuntu-latest' || 'garm-jammy-16' }}
|
||||
runs-on: ${{ github.event_name == 'pull_request' && 'ubuntu-latest' || 'bare-metal-9950x' }}
|
||||
env:
|
||||
AUTH_DOWNLOAD_TOKEN: ${{ secrets.AUTH_DOWNLOAD_TOKEN }}
|
||||
steps:
|
||||
@@ -16,19 +16,9 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Install Docker
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get -y install ca-certificates curl gnupg
|
||||
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
|
||||
sudo chmod a+r /usr/share/keyrings/docker-archive-keyring.gpg
|
||||
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
|
||||
sudo apt-get update
|
||||
sudo apt install -y docker-ce docker-ce-cli
|
||||
- name: Run rate-limiter integration tests
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
timeout-minutes: 10
|
||||
timeout-minutes: 20
|
||||
run: scripts/dev_cli.sh tests --integration-rate-limiter
|
||||
- name: Skipping build for PR
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
16
.github/workflows/lychee.yaml
vendored
Normal file
16
.github/workflows/lychee.yaml
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
name: Link Check (lychee)
|
||||
on:
|
||||
pull_request
|
||||
|
||||
jobs:
|
||||
link_check:
|
||||
name: Link Check
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Link Availability Check
|
||||
uses: lycheeverse/lychee-action@master
|
||||
with:
|
||||
args: --verbose --config .lychee.toml .
|
||||
32
.github/workflows/package-consistency.yaml
vendored
Normal file
32
.github/workflows/package-consistency.yaml
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
name: Cloud Hypervisor Consistency
|
||||
on: [pull_request, merge_group]
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Rust VMM Consistency Check
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install dependencies
|
||||
run: sudo apt install -y python3
|
||||
|
||||
- name: Install Rust toolchain stable
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: stable
|
||||
|
||||
- name: Check Rust VMM Package Consistency of root Workspace
|
||||
run: python3 scripts/package-consistency-check.py github.com/rust-vmm
|
||||
|
||||
- name: Check Rust VMM Package Consistency of fuzz Workspace
|
||||
run: |
|
||||
pushd fuzz
|
||||
python3 ../scripts/package-consistency-check.py github.com/rust-vmm
|
||||
popd
|
||||
39
.github/workflows/preview-riscv64.yaml
vendored
Normal file
39
.github/workflows/preview-riscv64.yaml
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
name: Cloud Hypervisor RISC-V 64-bit Preview
|
||||
on: [pull_request, merge_group]
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Cargo
|
||||
runs-on: riscv64-qemu-host
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
module:
|
||||
- hypervisor
|
||||
- arch
|
||||
- vm-allocator
|
||||
- devices
|
||||
|
||||
steps:
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install Rust toolchain
|
||||
run: /opt/scripts/exec-in-qemu.sh rustup default 1.82.0
|
||||
|
||||
- name: Build ${{ matrix.module }} Module (kvm)
|
||||
run: /opt/scripts/exec-in-qemu.sh cargo rustc --locked -p ${{ matrix.module }} --no-default-features --features "kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
||||
|
||||
- name: Clippy ${{ matrix.module }} Module (kvm)
|
||||
run: /opt/scripts/exec-in-qemu.sh cargo clippy --locked -p ${{ matrix.module }} --no-default-features --features "kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
|
||||
|
||||
- name: Test ${{ matrix.module }} Module (kvm)
|
||||
run: /opt/scripts/exec-in-qemu.sh cargo test --locked -p ${{ matrix.module }} --no-default-features --features "kvm"
|
||||
|
||||
- name: Check no files were modified
|
||||
run: test -z "$(git status --porcelain)"
|
||||
6
.github/workflows/release.yaml
vendored
6
.github/workflows/release.yaml
vendored
@@ -45,7 +45,7 @@ jobs:
|
||||
target: ${{ matrix.platform.target }}
|
||||
args: ${{ matrix.platform.args }}
|
||||
strip: true
|
||||
toolchain: "1.77.0"
|
||||
toolchain: "1.82.0"
|
||||
- name: Copy Release Binaries
|
||||
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
||||
shell: bash
|
||||
@@ -54,7 +54,7 @@ jobs:
|
||||
cp target/${{ matrix.platform.target }}/release/ch-remote ./${{ matrix.platform.name_ch_remote }}
|
||||
- name: Upload Release Artifacts
|
||||
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Artifacts for ${{ matrix.platform.target }}
|
||||
path: |
|
||||
@@ -80,7 +80,7 @@ jobs:
|
||||
github.event_name == 'create' && github.event.ref_type == 'tag' &&
|
||||
matrix.platform.target == 'x86_64-unknown-linux-gnu'
|
||||
id: upload-release-cloud-hypervisor-vendored-sources
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
path: cloud-hypervisor-${{ github.event.ref }}.tar.xz
|
||||
name: cloud-hypervisor-${{ github.event.ref }}.tar.xz
|
||||
|
||||
5
.gitlint
5
.gitlint
@@ -13,3 +13,8 @@ line-length=72
|
||||
# default 80
|
||||
[body-max-line-length]
|
||||
line-length=72
|
||||
|
||||
# Allow developers to add long links to useful resources
|
||||
[ignore-by-body]
|
||||
regex=^https?:\/\/
|
||||
ignore=body-max-line-length
|
||||
|
||||
18
.lychee.toml
Normal file
18
.lychee.toml
Normal file
@@ -0,0 +1,18 @@
|
||||
verbose = "info"
|
||||
|
||||
exclude = [
|
||||
# Availability of links below should be manually verified.
|
||||
# Page for intel SGX support, returns 403 while querying.
|
||||
'^https://www.intel.com/content/www/us/en/developer/tools/software-guard-extensions/linux-overview.html',
|
||||
# Page for intel TDX support, returns 403 while querying.
|
||||
'^https://www.intel.com/content/www/us/en/developer/tools/trust-domain-extensions/overview.html',
|
||||
# Page for TPM, returns 403 while querying.
|
||||
'^https://trustedcomputinggroup.org/wp-content/uploads/PC-Client-Specific-Platform-TPM-Profile-for-TPM-2p0-v1p05p_r14_pub.pdf',
|
||||
|
||||
# GitHub user smibarber referenced in `CREDITS.md` no longer exist
|
||||
'^https://github.com/smibarber',
|
||||
]
|
||||
|
||||
max_retries = 3
|
||||
|
||||
retry_wait_time = 5
|
||||
@@ -13,7 +13,7 @@ License](https://opensource.org/licenses/Apache-2.0).
|
||||
|
||||
## Coding Style
|
||||
|
||||
We follow the [Rust Style](https://github.com/rust-dev-tools/fmt-rfcs/blob/master/guide/guide.md)
|
||||
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`
|
||||
for each submitted Pull Request (PR).
|
||||
|
||||
@@ -36,7 +36,7 @@ commit you make.
|
||||
|
||||
## Certificate of Origin
|
||||
|
||||
In order to get a clear contribution chain of trust we use the [signed-off-by language](https://01.org/community/signed-process)
|
||||
In order to get a clear contribution chain of trust we use the [signed-off-by language](https://web.archive.org/web/20230406041855/https://01.org/community/signed-process)
|
||||
used by the Linux kernel project.
|
||||
|
||||
## Patch format
|
||||
@@ -79,12 +79,12 @@ you want to merge your changes to `cloud-hypervisor`:
|
||||
|
||||
1. Fork the [cloud-hypervisor](https://github.com/cloud-hypervisor/cloud-hypervisor) project
|
||||
into your github organization.
|
||||
2. Within your fork, create a branch for your contribution.
|
||||
3. [Create a pull request](https://help.github.com/articles/creating-a-pull-request-from-a-fork/)
|
||||
1. Within your fork, create a branch for your contribution.
|
||||
1. [Create a pull request](https://help.github.com/articles/creating-a-pull-request-from-a-fork/)
|
||||
against the main branch of the Cloud Hypervisor repository.
|
||||
4. To update your pull request amend existing commits whenever applicable and
|
||||
1. To update your pull request amend existing commits whenever applicable and
|
||||
then push the new changes to your pull request branch.
|
||||
5. Once the pull request is approved it can be integrated.
|
||||
1. Once the pull request is approved it can be integrated.
|
||||
|
||||
## Issue tracking
|
||||
|
||||
|
||||
535
Cargo.lock
generated
535
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
38
Cargo.toml
38
Cargo.toml
@@ -7,7 +7,7 @@ edition = "2021"
|
||||
homepage = "https://github.com/cloud-hypervisor/cloud-hypervisor"
|
||||
license = "Apache-2.0 AND BSD-3-Clause"
|
||||
name = "cloud-hypervisor"
|
||||
version = "42.0.0"
|
||||
version = "45.0.0"
|
||||
# Minimum buildable version:
|
||||
# Keep in sync with version in .github/workflows/build.yaml
|
||||
# Policy on MSRV (see #4318):
|
||||
@@ -15,7 +15,7 @@ version = "42.0.0"
|
||||
# a.) A dependency requires it,
|
||||
# b.) If we want to use a new feature and that MSRV is at least 6 months old,
|
||||
# c.) There is a security issue that is addressed by the toolchain update.
|
||||
rust-version = "1.77.0"
|
||||
rust-version = "1.82.0"
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
@@ -29,20 +29,20 @@ inherits = "release"
|
||||
strip = false
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.87"
|
||||
anyhow = "1.0.94"
|
||||
api_client = { path = "api_client" }
|
||||
clap = { version = "4.5.13", features = ["string"] }
|
||||
dhat = { version = "0.3.3", optional = true }
|
||||
epoll = "4.3.3"
|
||||
event_monitor = { path = "event_monitor" }
|
||||
hypervisor = { path = "hypervisor" }
|
||||
libc = "0.2.158"
|
||||
libc = "0.2.167"
|
||||
log = { version = "0.4.22", features = ["std"] }
|
||||
option_parser = { path = "option_parser" }
|
||||
seccompiler = { workspace = true }
|
||||
serde_json = "1.0.120"
|
||||
signal-hook = "0.3.17"
|
||||
thiserror = "1.0.62"
|
||||
thiserror = "2.0.6"
|
||||
tpm = { path = "tpm" }
|
||||
tracer = { path = "tracer" }
|
||||
vm-memory = { workspace = true }
|
||||
@@ -51,7 +51,7 @@ vmm-sys-util = { workspace = true }
|
||||
zbus = { version = "4.4.0", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
dirs = "5.0.1"
|
||||
dirs = "6.0.0"
|
||||
net_util = { path = "net_util" }
|
||||
once_cell = "1.20.2"
|
||||
serde_json = "1.0.120"
|
||||
@@ -102,19 +102,25 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.dependencies]
|
||||
# rust-vmm crates
|
||||
acpi_tables = { git = "https://github.com/rust-vmm/acpi_tables", branch = "main" }
|
||||
kvm-bindings = "0.9.1"
|
||||
kvm-ioctls = "0.18.0"
|
||||
linux-loader = "0.12.0"
|
||||
mshv-bindings = { git = "https://github.com/rust-vmm/mshv", tag = "v0.3.0" }
|
||||
mshv-ioctls = { git = "https://github.com/rust-vmm/mshv", tag = "v0.3.0" }
|
||||
seccompiler = "0.4.0"
|
||||
kvm-bindings = "0.10.0"
|
||||
kvm-ioctls = "0.19.1"
|
||||
linux-loader = "0.13.0"
|
||||
mshv-bindings = "0.3.3"
|
||||
mshv-ioctls = "0.3.3"
|
||||
seccompiler = "0.5.0"
|
||||
vfio-bindings = { git = "https://github.com/rust-vmm/vfio", branch = "main" }
|
||||
vfio-ioctls = { git = "https://github.com/rust-vmm/vfio", branch = "main", default-features = false }
|
||||
vfio_user = { git = "https://github.com/rust-vmm/vfio-user", branch = "main" }
|
||||
vhost = "0.12.0"
|
||||
virtio-bindings = "0.2.2"
|
||||
virtio-queue = "0.13.0"
|
||||
vhost = { git = "https://github.com/rust-vmm/vhost", rev = "d983ae0" }
|
||||
vhost-user-backend = { git = "https://github.com/rust-vmm/vhost", rev = "d983ae0" }
|
||||
virtio-bindings = "0.2.4"
|
||||
virtio-queue = "0.14.0"
|
||||
vm-fdt = { git = "https://github.com/rust-vmm/vm-fdt", branch = "main" }
|
||||
vm-memory = "0.15.0"
|
||||
vm-memory = "0.16.1"
|
||||
vmm-sys-util = "0.12.1"
|
||||
|
||||
# igvm crates
|
||||
igvm = "0.3.4"
|
||||
igvm_defs = "0.3.1"
|
||||
|
||||
21
README.md
21
README.md
@@ -59,9 +59,13 @@ based on the [Rust VMM](https://github.com/rust-vmm) crates.
|
||||
|
||||
### Architectures
|
||||
|
||||
Cloud Hypervisor supports the `x86-64` and `AArch64` architectures. There are
|
||||
minor differences in functionality between the two architectures
|
||||
(see [#1125](https://github.com/cloud-hypervisor/cloud-hypervisor/issues/1125)).
|
||||
Cloud Hypervisor supports the `x86-64`, `AArch64` and `riscv64`
|
||||
architectures, with functionality varying across these platforms. The
|
||||
functionality differences between `x86-64` and `AArch64` are documented
|
||||
in [#1125](https://github.com/cloud-hypervisor/cloud-hypervisor/issues/1125).
|
||||
The `riscv64` architecture support is experimental and offers limited
|
||||
functionality. For more details and instructions, please refer to [riscv
|
||||
documentation](docs/riscv.md).
|
||||
|
||||
### Guest OS
|
||||
|
||||
@@ -181,14 +185,9 @@ To build the kernel:
|
||||
|
||||
```shell
|
||||
# Clone the Cloud Hypervisor Linux branch
|
||||
$ git clone --depth 1 https://github.com/cloud-hypervisor/linux.git -b ch-6.2 linux-cloud-hypervisor
|
||||
$ git clone --depth 1 https://github.com/cloud-hypervisor/linux.git -b ch-6.12.8 linux-cloud-hypervisor
|
||||
$ pushd linux-cloud-hypervisor
|
||||
# Use the x86-64 cloud-hypervisor kernel config to build your kernel for x86-64
|
||||
$ wget https://raw.githubusercontent.com/cloud-hypervisor/cloud-hypervisor/main/resources/linux-config-x86_64
|
||||
# Use the AArch64 cloud-hypervisor kernel config to build your kernel for AArch64
|
||||
$ wget https://raw.githubusercontent.com/cloud-hypervisor/cloud-hypervisor/main/resources/linux-config-aarch64
|
||||
$ cp linux-config-x86_64 .config # x86-64
|
||||
$ cp linux-config-aarch64 .config # AArch64
|
||||
$ make ch_defconfig
|
||||
# Do native build of the x86-64 kernel
|
||||
$ KCFLAGS="-Wa,-mx86-used-note=no" make bzImage -j `nproc`
|
||||
# Do native build of the AArch64 kernel
|
||||
@@ -303,7 +302,7 @@ As of 2023-01-03, the following cloud images are supported:
|
||||
|
||||
- [Ubuntu Focal](https://cloud-images.ubuntu.com/focal/current/) (focal-server-cloudimg-{amd64,arm64}.img)
|
||||
- [Ubuntu Jammy](https://cloud-images.ubuntu.com/jammy/current/) (jammy-server-cloudimg-{amd64,arm64}.img )
|
||||
- [Fedora 36](https://fedora.mirrorservice.org/fedora/linux/releases/36/Cloud/) ([Fedora-Cloud-Base-36-1.5.x86_64.raw.xz](https://fedora.mirrorservice.org/fedora/linux/releases/36/Cloud/x86_64/images/) / [Fedora-Cloud-Base-36-1.5.aarch64.raw.xz](https://fedora.mirrorservice.org/fedora/linux/releases/36/Cloud/aarch64/images/))
|
||||
- [Fedora 36](https://archives.fedoraproject.org/pub/archive/fedora/linux/releases/36/Cloud/) ([Fedora-Cloud-Base-36-1.5.x86_64.raw.xz](https://archives.fedoraproject.org/pub/archive/fedora/linux/releases/36/Cloud/x86_64/images/) / [Fedora-Cloud-Base-36-1.5.aarch64.raw.xz](https://archives.fedoraproject.org/pub/archive/fedora/linux/releases/36/Cloud/aarch64/images/))
|
||||
|
||||
Direct kernel boot to userspace should work with a rootfs from most
|
||||
distributions although you may need to enable exotic filesystem types in the
|
||||
|
||||
@@ -5,5 +5,5 @@ name = "api_client"
|
||||
version = "0.1.0"
|
||||
|
||||
[dependencies]
|
||||
thiserror = "1.0.62"
|
||||
thiserror = "2.0.6"
|
||||
vmm-sys-util = { workspace = true }
|
||||
|
||||
@@ -11,19 +11,19 @@ sev_snp = []
|
||||
tdx = []
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.87"
|
||||
anyhow = "1.0.94"
|
||||
byteorder = "1.5.0"
|
||||
hypervisor = { path = "../hypervisor" }
|
||||
libc = "0.2.158"
|
||||
libc = "0.2.167"
|
||||
linux-loader = { workspace = true, features = ["bzimage", "elf", "pe"] }
|
||||
log = "0.4.22"
|
||||
serde = { version = "1.0.208", features = ["derive", "rc"] }
|
||||
thiserror = "1.0.62"
|
||||
uuid = "1.8.0"
|
||||
thiserror = "2.0.6"
|
||||
uuid = "1.12.1"
|
||||
vm-memory = { workspace = true, features = ["backend-bitmap", "backend-mmap"] }
|
||||
vm-migration = { path = "../vm-migration" }
|
||||
vmm-sys-util = { workspace = true, features = ["with-serde"] }
|
||||
|
||||
[target.'cfg(target_arch = "aarch64")'.dependencies]
|
||||
[target.'cfg(any(target_arch = "aarch64", target_arch = "riscv64"))'.dependencies]
|
||||
fdt_parser = { version = "0.1.5", package = "fdt" }
|
||||
vm-fdt = { workspace = true }
|
||||
|
||||
@@ -1109,10 +1109,7 @@ fn print_node(node: fdt_parser::node::FdtNode<'_, '_>, n_spaces: usize) {
|
||||
// - At first, try to convert it to CStr and print,
|
||||
// - If failed, print it as u32 array.
|
||||
let value_result = match CStr::from_bytes_with_nul(value) {
|
||||
Ok(value_cstr) => match value_cstr.to_str() {
|
||||
Ok(value_str) => Some(value_str),
|
||||
Err(_e) => None,
|
||||
},
|
||||
Ok(value_cstr) => value_cstr.to_str().ok(),
|
||||
Err(_e) => None,
|
||||
};
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// Copyright © 2024 Institute of Software, CAS. All rights reserved.
|
||||
// Copyright 2020 Arm Limited (or its affiliates). All rights reserved.
|
||||
// Copyright © 2020, Oracle and/or its affiliates.
|
||||
//
|
||||
@@ -5,7 +6,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//! Implements platform specific functionality.
|
||||
//! Supported platforms: x86_64, aarch64.
|
||||
//! Supported platforms: x86_64, aarch64, riscv64.
|
||||
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
@@ -32,6 +33,9 @@ pub enum Error {
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
#[error("Platform specific error (aarch64): {0:?}")]
|
||||
PlatformSpecific(aarch64::Error),
|
||||
#[cfg(target_arch = "riscv64")]
|
||||
#[error("Platform specific error (riscv64): {0:?}")]
|
||||
PlatformSpecific(riscv64::Error),
|
||||
#[error("The memory map table extends past the end of guest memory")]
|
||||
MemmapTablePastRamEnd,
|
||||
#[error("Error writing memory map table to guest memory")]
|
||||
@@ -85,6 +89,17 @@ pub use aarch64::{
|
||||
layout::IRQ_BASE, uefi, EntryPoint, _NSIG,
|
||||
};
|
||||
|
||||
/// Module for riscv64 related functionality.
|
||||
#[cfg(target_arch = "riscv64")]
|
||||
pub mod riscv64;
|
||||
|
||||
#[cfg(target_arch = "riscv64")]
|
||||
pub use riscv64::{
|
||||
arch_memory_regions, configure_system, configure_vcpu, fdt::DeviceInfoForFdt,
|
||||
get_host_cpu_phys_bits, initramfs_load_addr, layout, layout::CMDLINE_MAX_SIZE,
|
||||
layout::IRQ_BASE, EntryPoint, _NSIG,
|
||||
};
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub mod x86_64;
|
||||
|
||||
@@ -132,7 +147,7 @@ pub enum DeviceType {
|
||||
/// Device Type: Virtio.
|
||||
Virtio(u32),
|
||||
/// Device Type: Serial.
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
|
||||
Serial,
|
||||
/// Device Type: RTC.
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
@@ -153,7 +168,7 @@ impl fmt::Display for DeviceType {
|
||||
|
||||
/// Structure to describe MMIO device information
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
|
||||
pub struct MmioDeviceInfo {
|
||||
pub addr: u64,
|
||||
pub len: u64,
|
||||
@@ -162,7 +177,7 @@ pub struct MmioDeviceInfo {
|
||||
|
||||
/// Structure to describe PCI space information
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
|
||||
pub struct PciSpaceInfo {
|
||||
pub pci_segment_id: u16,
|
||||
pub mmio_config_address: u64,
|
||||
@@ -170,7 +185,7 @@ pub struct PciSpaceInfo {
|
||||
pub pci_device_space_size: u64,
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
|
||||
impl DeviceInfoForFdt for MmioDeviceInfo {
|
||||
fn addr(&self) -> u64 {
|
||||
self.addr
|
||||
|
||||
484
arch/src/riscv64/fdt.rs
Normal file
484
arch/src/riscv64/fdt.rs
Normal file
@@ -0,0 +1,484 @@
|
||||
// Copyright © 2024 Institute of Software, CAS. All rights reserved.
|
||||
// Copyright 2020 Arm Limited (or its affiliates). All rights reserved.
|
||||
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the THIRD-PARTY file.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::CStr;
|
||||
use std::fmt::Debug;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::{cmp, result, str};
|
||||
|
||||
use byteorder::{BigEndian, ByteOrder};
|
||||
use hypervisor::arch::riscv64::aia::Vaia;
|
||||
use thiserror::Error;
|
||||
use vm_fdt::{FdtWriter, FdtWriterResult};
|
||||
use vm_memory::{Address, Bytes, GuestMemory, GuestMemoryError, GuestMemoryRegion};
|
||||
|
||||
use super::super::{DeviceType, GuestMemoryMmap, InitramfsConfig};
|
||||
use super::layout::{
|
||||
IRQ_BASE, MEM_32BIT_DEVICES_SIZE, MEM_32BIT_DEVICES_START, MEM_PCI_IO_SIZE, MEM_PCI_IO_START,
|
||||
PCI_HIGH_BASE, PCI_MMIO_CONFIG_SIZE_PER_SEGMENT,
|
||||
};
|
||||
use crate::PciSpaceInfo;
|
||||
|
||||
const AIA_APLIC_PHANDLE: u32 = 1;
|
||||
const AIA_IMSIC_PHANDLE: u32 = 2;
|
||||
const CPU_INTC_BASE_PHANDLE: u32 = 3;
|
||||
const CPU_BASE_PHANDLE: u32 = 256 + CPU_INTC_BASE_PHANDLE;
|
||||
// Read the documentation specified when appending the root node to the FDT.
|
||||
const ADDRESS_CELLS: u32 = 0x2;
|
||||
const SIZE_CELLS: u32 = 0x2;
|
||||
|
||||
// From https://elixir.bootlin.com/linux/v6.10/source/include/dt-bindings/interrupt-controller/irq.h#L14
|
||||
const _IRQ_TYPE_EDGE_RISING: u32 = 1;
|
||||
const IRQ_TYPE_LEVEL_HI: u32 = 4;
|
||||
|
||||
const S_MODE_EXT_IRQ: u32 = 9;
|
||||
|
||||
/// Trait for devices to be added to the Flattened Device Tree.
|
||||
pub trait DeviceInfoForFdt {
|
||||
/// Returns the address where this device will be loaded.
|
||||
fn addr(&self) -> u64;
|
||||
/// Returns the associated interrupt for this device.
|
||||
fn irq(&self) -> u32;
|
||||
/// Returns the amount of memory that needs to be reserved for this device.
|
||||
fn length(&self) -> u64;
|
||||
}
|
||||
|
||||
/// Errors thrown while configuring the Flattened Device Tree for riscv64.
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
/// Failure in writing FDT in memory.
|
||||
#[error("Failure in writing FDT in memory: {0}")]
|
||||
WriteFdtToMemory(GuestMemoryError),
|
||||
}
|
||||
type Result<T> = result::Result<T, Error>;
|
||||
|
||||
/// Creates the flattened device tree for this riscv64 VM.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn create_fdt<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHasher>(
|
||||
guest_mem: &GuestMemoryMmap,
|
||||
cmdline: &str,
|
||||
num_vcpu: u32,
|
||||
device_info: &HashMap<(DeviceType, String), T, S>,
|
||||
aia_device: &Arc<Mutex<dyn Vaia>>,
|
||||
initrd: &Option<InitramfsConfig>,
|
||||
pci_space_info: &[PciSpaceInfo],
|
||||
) -> FdtWriterResult<Vec<u8>> {
|
||||
// Allocate stuff necessary for the holding the blob.
|
||||
let mut fdt = FdtWriter::new()?;
|
||||
|
||||
// For an explanation why these nodes were introduced in the blob take a look at
|
||||
// https://github.com/devicetree-org/devicetree-specification/releases/tag/v0.4
|
||||
// In chapter 3.
|
||||
|
||||
// Header or the root node as per above mentioned documentation.
|
||||
let root_node = fdt.begin_node("")?;
|
||||
fdt.property_string("compatible", "linux,dummy-virt")?;
|
||||
// For info on #address-cells and size-cells resort to Table 3.1 Root Node
|
||||
// Properties
|
||||
fdt.property_u32("#address-cells", ADDRESS_CELLS)?;
|
||||
fdt.property_u32("#size-cells", SIZE_CELLS)?;
|
||||
create_cpu_nodes(&mut fdt, num_vcpu)?;
|
||||
create_memory_node(&mut fdt, guest_mem)?;
|
||||
create_chosen_node(&mut fdt, cmdline, initrd)?;
|
||||
create_aia_node(&mut fdt, aia_device)?;
|
||||
create_devices_node(&mut fdt, device_info)?;
|
||||
create_pci_nodes(&mut fdt, pci_space_info)?;
|
||||
|
||||
// End Header node.
|
||||
fdt.end_node(root_node)?;
|
||||
|
||||
let fdt_final = fdt.finish()?;
|
||||
|
||||
Ok(fdt_final)
|
||||
}
|
||||
|
||||
pub fn write_fdt_to_memory(fdt_final: Vec<u8>, guest_mem: &GuestMemoryMmap) -> Result<()> {
|
||||
// Write FDT to memory.
|
||||
guest_mem
|
||||
.write_slice(fdt_final.as_slice(), super::layout::FDT_START)
|
||||
.map_err(Error::WriteFdtToMemory)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Following are the auxiliary function for creating the different nodes that we append to our FDT.
|
||||
fn create_cpu_nodes(fdt: &mut FdtWriter, num_cpus: u32) -> FdtWriterResult<()> {
|
||||
// See https://elixir.bootlin.com/linux/v6.10/source/Documentation/devicetree/bindings/riscv/cpus.yaml
|
||||
let cpus = fdt.begin_node("cpus")?;
|
||||
// As per documentation, on RISC-V 64-bit systems value should be set to 1.
|
||||
fdt.property_u32("#address-cells", 0x01)?;
|
||||
fdt.property_u32("#size-cells", 0x0)?;
|
||||
// TODO: Retrieve CPU frequency from cpu timer regs
|
||||
let timebase_frequency: u32 = 0x989680;
|
||||
fdt.property_u32("timebase-frequency", timebase_frequency)?;
|
||||
|
||||
for cpu_index in 0..num_cpus {
|
||||
let cpu = fdt.begin_node(&format!("cpu@{:x}", cpu_index))?;
|
||||
fdt.property_string("device_type", "cpu")?;
|
||||
fdt.property_string("compatible", "riscv")?;
|
||||
fdt.property_string("mmu-type", "sv48")?;
|
||||
fdt.property_string("riscv,isa", "rv64imafdc_smaia_ssaia")?;
|
||||
fdt.property_string("status", "okay")?;
|
||||
fdt.property_u32("reg", cpu_index)?;
|
||||
fdt.property_u32("phandle", CPU_BASE_PHANDLE + cpu_index)?;
|
||||
|
||||
// interrupt controller node
|
||||
let intc_node = fdt.begin_node("interrupt-controller")?;
|
||||
fdt.property_string("compatible", "riscv,cpu-intc")?;
|
||||
fdt.property_u32("#interrupt-cells", 1u32)?;
|
||||
fdt.property_null("interrupt-controller")?;
|
||||
fdt.property_u32("phandle", CPU_INTC_BASE_PHANDLE + cpu_index)?;
|
||||
fdt.end_node(intc_node)?;
|
||||
|
||||
fdt.end_node(cpu)?;
|
||||
}
|
||||
|
||||
fdt.end_node(cpus)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_memory_node(fdt: &mut FdtWriter, guest_mem: &GuestMemoryMmap) -> FdtWriterResult<()> {
|
||||
// Note: memory regions from "GuestMemory" are sorted and non-zero sized.
|
||||
let ram_regions = {
|
||||
let mut ram_regions = Vec::new();
|
||||
let mut current_start = guest_mem
|
||||
.iter()
|
||||
.next()
|
||||
.map(GuestMemoryRegion::start_addr)
|
||||
.expect("GuestMemory must have one memory region at least")
|
||||
.raw_value();
|
||||
let mut current_end = current_start;
|
||||
|
||||
for (start, size) in guest_mem
|
||||
.iter()
|
||||
.map(|m| (m.start_addr().raw_value(), m.len()))
|
||||
{
|
||||
if current_end == start {
|
||||
// This zone is continuous with the previous one.
|
||||
current_end += size;
|
||||
} else {
|
||||
ram_regions.push((current_start, current_end));
|
||||
|
||||
current_start = start;
|
||||
current_end = start + size;
|
||||
}
|
||||
}
|
||||
|
||||
ram_regions.push((current_start, current_end));
|
||||
|
||||
ram_regions
|
||||
};
|
||||
|
||||
let mut mem_reg_property = Vec::new();
|
||||
for region in ram_regions {
|
||||
let mem_size = region.1 - region.0;
|
||||
mem_reg_property.push(region.0);
|
||||
mem_reg_property.push(mem_size);
|
||||
}
|
||||
|
||||
let ram_start = super::layout::RAM_START.raw_value();
|
||||
let memory_node_name = format!("memory@{:x}", ram_start);
|
||||
let memory_node = fdt.begin_node(&memory_node_name)?;
|
||||
fdt.property_string("device_type", "memory")?;
|
||||
fdt.property_array_u64("reg", &mem_reg_property)?;
|
||||
fdt.end_node(memory_node)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_chosen_node(
|
||||
fdt: &mut FdtWriter,
|
||||
cmdline: &str,
|
||||
initrd: &Option<InitramfsConfig>,
|
||||
) -> FdtWriterResult<()> {
|
||||
let chosen_node = fdt.begin_node("chosen")?;
|
||||
fdt.property_string("bootargs", cmdline)?;
|
||||
|
||||
if let Some(initrd_config) = initrd {
|
||||
let initrd_start = initrd_config.address.raw_value();
|
||||
let initrd_end = initrd_config.address.raw_value() + initrd_config.size as u64;
|
||||
fdt.property_u64("linux,initrd-start", initrd_start)?;
|
||||
fdt.property_u64("linux,initrd-end", initrd_end)?;
|
||||
}
|
||||
|
||||
fdt.end_node(chosen_node)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_aia_node(fdt: &mut FdtWriter, aia_device: &Arc<Mutex<dyn Vaia>>) -> FdtWriterResult<()> {
|
||||
// IMSIC
|
||||
if aia_device.lock().unwrap().msi_compatible() {
|
||||
use super::layout::IMSIC_START;
|
||||
let imsic_name = format!("imsics@{:x}", IMSIC_START.0);
|
||||
let imsic_node = fdt.begin_node(&imsic_name)?;
|
||||
|
||||
fdt.property_string(
|
||||
"compatible",
|
||||
aia_device.lock().unwrap().imsic_compatibility(),
|
||||
)?;
|
||||
let imsic_reg_prop = aia_device.lock().unwrap().imsic_properties();
|
||||
fdt.property_array_u32("reg", &imsic_reg_prop)?;
|
||||
fdt.property_u32("#interrupt-cells", 0u32)?;
|
||||
fdt.property_null("interrupt-controller")?;
|
||||
fdt.property_null("msi-controller")?;
|
||||
// TODO complete num-ids
|
||||
fdt.property_u32("riscv,num-ids", 2047u32)?;
|
||||
fdt.property_u32("phandle", AIA_IMSIC_PHANDLE)?;
|
||||
|
||||
let mut irq_cells = Vec::new();
|
||||
let num_cpus = aia_device.lock().unwrap().vcpu_count();
|
||||
for i in 0..num_cpus {
|
||||
irq_cells.push(CPU_INTC_BASE_PHANDLE + i);
|
||||
irq_cells.push(S_MODE_EXT_IRQ);
|
||||
}
|
||||
fdt.property_array_u32("interrupts-extended", &irq_cells)?;
|
||||
|
||||
fdt.end_node(imsic_node)?;
|
||||
}
|
||||
|
||||
// APLIC
|
||||
use super::layout::APLIC_START;
|
||||
let aplic_name = format!("aplic@{:x}", APLIC_START.0);
|
||||
let aplic_node = fdt.begin_node(&aplic_name)?;
|
||||
|
||||
fdt.property_string(
|
||||
"compatible",
|
||||
aia_device.lock().unwrap().aplic_compatibility(),
|
||||
)?;
|
||||
let reg_cells = aia_device.lock().unwrap().aplic_properties();
|
||||
fdt.property_array_u32("reg", ®_cells)?;
|
||||
fdt.property_u32("#interrupt-cells", 2u32)?;
|
||||
fdt.property_null("interrupt-controller")?;
|
||||
// TODO complete num-srcs
|
||||
fdt.property_u32("riscv,num-sources", 96u32)?;
|
||||
fdt.property_u32("phandle", AIA_APLIC_PHANDLE)?;
|
||||
fdt.property_u32("msi-parent", AIA_IMSIC_PHANDLE)?;
|
||||
|
||||
fdt.end_node(aplic_node)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_serial_node<T: DeviceInfoForFdt + Clone + Debug>(
|
||||
fdt: &mut FdtWriter,
|
||||
dev_info: &T,
|
||||
) -> FdtWriterResult<()> {
|
||||
let serial_reg_prop = [dev_info.addr(), dev_info.length()];
|
||||
let irq = [dev_info.irq() - IRQ_BASE, IRQ_TYPE_LEVEL_HI];
|
||||
|
||||
let serial_node = fdt.begin_node(&format!("serial@{:x}", dev_info.addr()))?;
|
||||
fdt.property_string("compatible", "ns16550a")?;
|
||||
fdt.property_array_u64("reg", &serial_reg_prop)?;
|
||||
fdt.property_u32("clock-frequency", 3686400)?;
|
||||
fdt.property_u32("interrupt-parent", AIA_APLIC_PHANDLE)?;
|
||||
fdt.property_array_u32("interrupts", &irq)?;
|
||||
fdt.end_node(serial_node)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_devices_node<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHasher>(
|
||||
fdt: &mut FdtWriter,
|
||||
dev_info: &HashMap<(DeviceType, String), T, S>,
|
||||
) -> FdtWriterResult<()> {
|
||||
for ((device_type, _device_id), info) in dev_info {
|
||||
match device_type {
|
||||
DeviceType::Serial => create_serial_node(fdt, info)?,
|
||||
DeviceType::Virtio(_) => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_pci_nodes(fdt: &mut FdtWriter, pci_device_info: &[PciSpaceInfo]) -> FdtWriterResult<()> {
|
||||
// Add node for PCIe controller.
|
||||
// See Documentation/devicetree/bindings/pci/host-generic-pci.txt in the kernel
|
||||
// and https://elinux.org/Device_Tree_Usage.
|
||||
// In multiple PCI segments setup, each PCI segment needs a PCI node.
|
||||
for pci_device_info_elem in pci_device_info.iter() {
|
||||
// EDK2 requires the PCIe high space above 4G address.
|
||||
// The actual space in CLH follows the RAM. If the RAM space is small, the PCIe high space
|
||||
// could fall below 4G.
|
||||
// Here we cut off PCI device space below 8G in FDT to workaround the EDK2 check.
|
||||
// But the address written in ACPI is not impacted.
|
||||
let (pci_device_base_64bit, pci_device_size_64bit) =
|
||||
if pci_device_info_elem.pci_device_space_start < PCI_HIGH_BASE.raw_value() {
|
||||
(
|
||||
PCI_HIGH_BASE.raw_value(),
|
||||
pci_device_info_elem.pci_device_space_size
|
||||
- (PCI_HIGH_BASE.raw_value() - pci_device_info_elem.pci_device_space_start),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
pci_device_info_elem.pci_device_space_start,
|
||||
pci_device_info_elem.pci_device_space_size,
|
||||
)
|
||||
};
|
||||
// There is no specific requirement of the 32bit MMIO range, and
|
||||
// therefore at least we can make these ranges 4K aligned.
|
||||
let pci_device_size_32bit: u64 =
|
||||
MEM_32BIT_DEVICES_SIZE / ((1 << 12) * pci_device_info.len() as u64) * (1 << 12);
|
||||
let pci_device_base_32bit: u64 = MEM_32BIT_DEVICES_START.0
|
||||
+ pci_device_size_32bit * pci_device_info_elem.pci_segment_id as u64;
|
||||
|
||||
let ranges = [
|
||||
// io addresses. Since AArch64 will not use IO address,
|
||||
// we can set the same IO address range for every segment.
|
||||
0x1000000,
|
||||
0_u32,
|
||||
0_u32,
|
||||
(MEM_PCI_IO_START.0 >> 32) as u32,
|
||||
MEM_PCI_IO_START.0 as u32,
|
||||
(MEM_PCI_IO_SIZE >> 32) as u32,
|
||||
MEM_PCI_IO_SIZE as u32,
|
||||
// mmio addresses
|
||||
0x2000000, // (ss = 10: 32-bit memory space)
|
||||
(pci_device_base_32bit >> 32) as u32, // PCI address
|
||||
pci_device_base_32bit as u32,
|
||||
(pci_device_base_32bit >> 32) as u32, // CPU address
|
||||
pci_device_base_32bit as u32,
|
||||
(pci_device_size_32bit >> 32) as u32, // size
|
||||
pci_device_size_32bit as u32,
|
||||
// device addresses
|
||||
0x3000000, // (ss = 11: 64-bit memory space)
|
||||
(pci_device_base_64bit >> 32) as u32, // PCI address
|
||||
pci_device_base_64bit as u32,
|
||||
(pci_device_base_64bit >> 32) as u32, // CPU address
|
||||
pci_device_base_64bit as u32,
|
||||
(pci_device_size_64bit >> 32) as u32, // size
|
||||
pci_device_size_64bit as u32,
|
||||
];
|
||||
let bus_range = [0, 0]; // Only bus 0
|
||||
let reg = [
|
||||
pci_device_info_elem.mmio_config_address,
|
||||
PCI_MMIO_CONFIG_SIZE_PER_SEGMENT,
|
||||
];
|
||||
// See kernel document Documentation/devicetree/bindings/pci/pci-msi.txt
|
||||
let msi_map = [
|
||||
// rid-base: A single cell describing the first RID matched by the entry.
|
||||
0x0,
|
||||
// msi-controller: A single phandle to an MSI controller.
|
||||
AIA_IMSIC_PHANDLE,
|
||||
// msi-base: An msi-specifier describing the msi-specifier produced for the
|
||||
// first RID matched by the entry.
|
||||
(pci_device_info_elem.pci_segment_id as u32) << 8,
|
||||
// length: A single cell describing how many consecutive RIDs are matched
|
||||
// following the rid-base.
|
||||
0x100,
|
||||
];
|
||||
|
||||
let pci_node_name = format!("pci@{:x}", pci_device_info_elem.mmio_config_address);
|
||||
let pci_node = fdt.begin_node(&pci_node_name)?;
|
||||
|
||||
fdt.property_string("compatible", "pci-host-ecam-generic")?;
|
||||
fdt.property_string("device_type", "pci")?;
|
||||
fdt.property_array_u32("ranges", &ranges)?;
|
||||
fdt.property_array_u32("bus-range", &bus_range)?;
|
||||
fdt.property_u32(
|
||||
"linux,pci-domain",
|
||||
pci_device_info_elem.pci_segment_id as u32,
|
||||
)?;
|
||||
fdt.property_u32("#address-cells", 3)?;
|
||||
fdt.property_u32("#size-cells", 2)?;
|
||||
fdt.property_array_u64("reg", ®)?;
|
||||
fdt.property_u32("#interrupt-cells", 1)?;
|
||||
fdt.property_null("interrupt-map")?;
|
||||
fdt.property_null("interrupt-map-mask")?;
|
||||
fdt.property_null("dma-coherent")?;
|
||||
fdt.property_array_u32("msi-map", &msi_map)?;
|
||||
fdt.property_u32("msi-parent", AIA_IMSIC_PHANDLE)?;
|
||||
|
||||
fdt.end_node(pci_node)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Parse the DTB binary and print for debugging
|
||||
pub fn print_fdt(dtb: &[u8]) {
|
||||
match fdt_parser::Fdt::new(dtb) {
|
||||
Ok(fdt) => {
|
||||
if let Some(root) = fdt.find_node("/") {
|
||||
debug!("Printing the FDT:");
|
||||
print_node(root, 0);
|
||||
} else {
|
||||
debug!("Failed to find root node in FDT for debugging.");
|
||||
}
|
||||
}
|
||||
Err(_) => debug!("Failed to parse FDT for debugging."),
|
||||
}
|
||||
}
|
||||
|
||||
fn print_node(node: fdt_parser::node::FdtNode<'_, '_>, n_spaces: usize) {
|
||||
debug!("{:indent$}{}/", "", node.name, indent = n_spaces);
|
||||
for property in node.properties() {
|
||||
let name = property.name;
|
||||
|
||||
// If the property is 'compatible', its value requires special handling.
|
||||
// The u8 array could contain multiple null-terminated strings.
|
||||
// We copy the original array and simply replace all 'null' characters with spaces.
|
||||
let value = if name == "compatible" {
|
||||
let mut compatible = vec![0u8; 256];
|
||||
let handled_value = property
|
||||
.value
|
||||
.iter()
|
||||
.map(|&c| if c == 0 { b' ' } else { c })
|
||||
.collect::<Vec<_>>();
|
||||
let len = cmp::min(255, handled_value.len());
|
||||
compatible[..len].copy_from_slice(&handled_value[..len]);
|
||||
compatible[..(len + 1)].to_vec()
|
||||
} else {
|
||||
property.value.to_vec()
|
||||
};
|
||||
let value = &value;
|
||||
|
||||
// Now the value can be either:
|
||||
// - A null-terminated C string, or
|
||||
// - Binary data
|
||||
// We follow a very simple logic to present the value:
|
||||
// - At first, try to convert it to CStr and print,
|
||||
// - If failed, print it as u32 array.
|
||||
let value_result = match CStr::from_bytes_with_nul(value) {
|
||||
Ok(value_cstr) => match value_cstr.to_str() {
|
||||
Ok(value_str) => Some(value_str),
|
||||
Err(_e) => None,
|
||||
},
|
||||
Err(_e) => None,
|
||||
};
|
||||
|
||||
if let Some(value_str) = value_result {
|
||||
debug!(
|
||||
"{:indent$}{} : {:#?}",
|
||||
"",
|
||||
name,
|
||||
value_str,
|
||||
indent = (n_spaces + 2)
|
||||
);
|
||||
} else {
|
||||
let mut array = Vec::with_capacity(256);
|
||||
array.resize(value.len() / 4, 0u32);
|
||||
BigEndian::read_u32_into(value, &mut array);
|
||||
debug!(
|
||||
"{:indent$}{} : {:X?}",
|
||||
"",
|
||||
name,
|
||||
array,
|
||||
indent = (n_spaces + 2)
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
// Print children nodes if there is any
|
||||
for child in node.children() {
|
||||
print_node(child, n_spaces + 2);
|
||||
}
|
||||
}
|
||||
105
arch/src/riscv64/layout.rs
Normal file
105
arch/src/riscv64/layout.rs
Normal file
@@ -0,0 +1,105 @@
|
||||
// Copyright © 2024 Institute of Software, CAS. All rights reserved.
|
||||
// Copyright 2020 Arm Limited (or its affiliates). All rights reserved.
|
||||
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//
|
||||
// Memory layout of RISC-V 64-bit guest:
|
||||
//
|
||||
// Physical +---------------------------------------------------------------+
|
||||
// address | |
|
||||
// end | |
|
||||
// ~ ~ ~ ~
|
||||
// | |
|
||||
// | Highmem PCI MMIO space |
|
||||
// | |
|
||||
// RAM end +---------------------------------------------------------------+
|
||||
// (dynamic, | |
|
||||
// including | |
|
||||
// hotplug ~ ~ ~ ~
|
||||
// memory) | |
|
||||
// | DRAM |
|
||||
// | |
|
||||
// | |
|
||||
// | |
|
||||
// | |
|
||||
// 1 GB +---------------------------------------------------------------+
|
||||
// | |
|
||||
// | PCI MMCONFIG space |
|
||||
// | |
|
||||
// 768 MB +---------------------------------------------------------------+
|
||||
// | |
|
||||
// | |
|
||||
// | PCI MMIO space |
|
||||
// | |
|
||||
// 256 MB +---------------------------------------------------------------|
|
||||
// | |
|
||||
// | Legacy devices space |
|
||||
// | |
|
||||
// 128 MB +---------------------------------------------------------------|
|
||||
// | |
|
||||
// | IMSICs |
|
||||
// | |
|
||||
// 64 MB +---------------------------------------------------------------+
|
||||
// | |
|
||||
// | APLICs |
|
||||
// | |
|
||||
// 0 GB +---------------------------------------------------------------+
|
||||
//
|
||||
//
|
||||
|
||||
use vm_memory::GuestAddress;
|
||||
|
||||
/// AIA related devices
|
||||
/// See https://elixir.bootlin.com/linux/v6.10/source/arch/riscv/include/uapi/asm/kvm.h
|
||||
/// 0x0 ~ 0x0400_0000 (64 MiB) resides APLICs
|
||||
pub const APLIC_START: GuestAddress = GuestAddress(0);
|
||||
pub const APLIC_SIZE: u64 = 0x4000;
|
||||
|
||||
/// 0x0400_0000 ~ 0x0800_0000 (64 MiB) resides IMSICs
|
||||
pub const IMSIC_START: GuestAddress = GuestAddress(0x0400_0000);
|
||||
pub const IMSIC_SIZE: u64 = 0x1000;
|
||||
|
||||
/// Below this address will reside the AIA, above this address will reside the MMIO devices.
|
||||
const MAPPED_IO_START: GuestAddress = GuestAddress(0x0800_0000);
|
||||
|
||||
/// Space 0x0800_0000 ~ 0x1000_0000 is reserved for legacy devices.
|
||||
pub const LEGACY_SERIAL_MAPPED_IO_START: GuestAddress = MAPPED_IO_START;
|
||||
|
||||
/// Space 0x0905_0000 ~ 0x0906_0000 is reserved for pcie io address
|
||||
pub const MEM_PCI_IO_START: GuestAddress = GuestAddress(0x0905_0000);
|
||||
pub const MEM_PCI_IO_SIZE: u64 = 0x1_0000;
|
||||
|
||||
/// Starting from 0x1000_0000 (256MiB) to 0x3000_0000 (768MiB) is used for PCIE MMIO
|
||||
pub const MEM_32BIT_DEVICES_START: GuestAddress = GuestAddress(0x1000_0000);
|
||||
pub const MEM_32BIT_DEVICES_SIZE: u64 = 0x2000_0000;
|
||||
|
||||
/// PCI MMCONFIG space (start: after the device space at 768MiB, length: 256MiB)
|
||||
pub const PCI_MMCONFIG_START: GuestAddress = GuestAddress(0x3000_0000);
|
||||
pub const PCI_MMCONFIG_SIZE: u64 = 256 << 20;
|
||||
// One bus with potentially 256 devices (32 slots x 8 functions).
|
||||
pub const PCI_MMIO_CONFIG_SIZE_PER_SEGMENT: u64 = 4096 * 256;
|
||||
|
||||
/// Start of RAM.
|
||||
pub const RAM_START: GuestAddress = GuestAddress(0x4000_0000);
|
||||
|
||||
/// Kernel command line maximum size on RISC-V.
|
||||
/// See https://elixir.bootlin.com/linux/v6.10/source/arch/riscv/include/uapi/asm/setup.h
|
||||
pub const CMDLINE_MAX_SIZE: usize = 1024;
|
||||
|
||||
/// FDT is at the beginning of RAM.
|
||||
pub const FDT_START: GuestAddress = RAM_START;
|
||||
pub const FDT_MAX_SIZE: u64 = 0x1_0000;
|
||||
|
||||
/// Kernel start after FDT
|
||||
pub const KERNEL_START: GuestAddress = GuestAddress(RAM_START.0 + FDT_MAX_SIZE);
|
||||
|
||||
/// Pci high memory base
|
||||
pub const PCI_HIGH_BASE: GuestAddress = GuestAddress(0x2_0000_0000);
|
||||
|
||||
/// First usable interrupt on riscv64
|
||||
pub const IRQ_BASE: u32 = 0;
|
||||
|
||||
// As per https://elixir.bootlin.com/linux/v6.10/source/arch/riscv/include/asm/kvm_host.h#L31
|
||||
/// Number of supported interrupts
|
||||
pub const IRQ_NUM: u32 = 1023;
|
||||
173
arch/src/riscv64/mod.rs
Normal file
173
arch/src/riscv64/mod.rs
Normal file
@@ -0,0 +1,173 @@
|
||||
// Copyright © 2024 Institute of Software, CAS. All rights reserved.
|
||||
// Copyright 2020 Arm Limited (or its affiliates). All rights reserved.
|
||||
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// Module for the flattened device tree.
|
||||
pub mod fdt;
|
||||
/// Layout for this riscv64 system.
|
||||
pub mod layout;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Debug;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use hypervisor::arch::riscv64::aia::Vaia;
|
||||
use log::{log_enabled, Level};
|
||||
use thiserror::Error;
|
||||
use vm_memory::{Address, GuestAddress, GuestMemory, GuestMemoryAtomic};
|
||||
|
||||
pub use self::fdt::DeviceInfoForFdt;
|
||||
use crate::{DeviceType, GuestMemoryMmap, PciSpaceInfo, RegionType};
|
||||
|
||||
pub const _NSIG: i32 = 65;
|
||||
|
||||
/// Errors thrown while configuring riscv64 system.
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
/// Failed to create a FDT.
|
||||
#[error("Failed to create a FDT")]
|
||||
SetupFdt,
|
||||
|
||||
/// Failed to write FDT to memory.
|
||||
#[error("Failed to write FDT to memory: {0}")]
|
||||
WriteFdtToMemory(fdt::Error),
|
||||
|
||||
/// Failed to create a AIA.
|
||||
#[error("Failed to create a AIA")]
|
||||
SetupAia,
|
||||
|
||||
/// Failed to compute the initramfs address.
|
||||
#[error("Failed to compute the initramfs address")]
|
||||
InitramfsAddress,
|
||||
|
||||
/// Error configuring the general purpose registers
|
||||
#[error("Error configuring the general purpose registers: {0}")]
|
||||
RegsConfiguration(hypervisor::HypervisorCpuError),
|
||||
}
|
||||
|
||||
impl From<Error> for super::Error {
|
||||
fn from(e: Error) -> super::Error {
|
||||
super::Error::PlatformSpecific(e)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
/// Specifies the entry point address where the guest must start
|
||||
/// executing code.
|
||||
pub struct EntryPoint {
|
||||
/// Address in guest memory where the guest must start execution
|
||||
pub entry_addr: GuestAddress,
|
||||
}
|
||||
|
||||
/// Configure the specified VCPU, and return its MPIDR.
|
||||
pub fn configure_vcpu(
|
||||
vcpu: &Arc<dyn hypervisor::Vcpu>,
|
||||
id: u8,
|
||||
boot_setup: Option<(EntryPoint, &GuestMemoryAtomic<GuestMemoryMmap>)>,
|
||||
) -> super::Result<()> {
|
||||
if let Some((kernel_entry_point, _guest_memory)) = boot_setup {
|
||||
vcpu.setup_regs(
|
||||
id,
|
||||
kernel_entry_point.entry_addr.raw_value(),
|
||||
layout::FDT_START.raw_value(),
|
||||
)
|
||||
.map_err(Error::RegsConfiguration)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn arch_memory_regions() -> Vec<(GuestAddress, usize, RegionType)> {
|
||||
vec![
|
||||
// 0 MiB ~ 256 MiB: AIA and legacy devices
|
||||
(
|
||||
GuestAddress(0),
|
||||
layout::MEM_32BIT_DEVICES_START.0 as usize,
|
||||
RegionType::Reserved,
|
||||
),
|
||||
// 256 MiB ~ 768 MiB: MMIO space
|
||||
(
|
||||
layout::MEM_32BIT_DEVICES_START,
|
||||
layout::MEM_32BIT_DEVICES_SIZE as usize,
|
||||
RegionType::SubRegion,
|
||||
),
|
||||
// 768 MiB ~ 1 GiB: reserved. The leading 256M for PCIe MMCONFIG space
|
||||
(
|
||||
layout::PCI_MMCONFIG_START,
|
||||
layout::PCI_MMCONFIG_SIZE as usize,
|
||||
RegionType::Reserved,
|
||||
),
|
||||
// 1GiB ~ inf: RAM
|
||||
(layout::RAM_START, usize::MAX, RegionType::Ram),
|
||||
]
|
||||
}
|
||||
|
||||
/// Configures the system and should be called once per vm before starting vcpu threads.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn configure_system<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHasher>(
|
||||
guest_mem: &GuestMemoryMmap,
|
||||
cmdline: &str,
|
||||
num_vcpu: u32,
|
||||
device_info: &HashMap<(DeviceType, String), T, S>,
|
||||
initrd: &Option<super::InitramfsConfig>,
|
||||
pci_space_info: &[PciSpaceInfo],
|
||||
aia_device: &Arc<Mutex<dyn Vaia>>,
|
||||
) -> super::Result<()> {
|
||||
let fdt_final = fdt::create_fdt(
|
||||
guest_mem,
|
||||
cmdline,
|
||||
num_vcpu,
|
||||
device_info,
|
||||
aia_device,
|
||||
initrd,
|
||||
pci_space_info,
|
||||
)
|
||||
.map_err(|_| Error::SetupFdt)?;
|
||||
|
||||
if log_enabled!(Level::Debug) {
|
||||
fdt::print_fdt(&fdt_final);
|
||||
}
|
||||
|
||||
fdt::write_fdt_to_memory(fdt_final, guest_mem).map_err(Error::WriteFdtToMemory)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns the memory address where the initramfs could be loaded.
|
||||
pub fn initramfs_load_addr(
|
||||
guest_mem: &GuestMemoryMmap,
|
||||
initramfs_size: usize,
|
||||
) -> super::Result<u64> {
|
||||
let round_to_pagesize = |size| (size + (super::PAGE_SIZE - 1)) & !(super::PAGE_SIZE - 1);
|
||||
match guest_mem
|
||||
.last_addr()
|
||||
.checked_sub(round_to_pagesize(initramfs_size) as u64 - 1)
|
||||
{
|
||||
Some(offset) => {
|
||||
if guest_mem.address_in_range(offset) {
|
||||
Ok(offset.raw_value())
|
||||
} else {
|
||||
Err(super::Error::PlatformSpecific(Error::InitramfsAddress))
|
||||
}
|
||||
}
|
||||
None => Err(super::Error::PlatformSpecific(Error::InitramfsAddress)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_host_cpu_phys_bits(_hypervisor: &Arc<dyn hypervisor::Hypervisor>) -> u8 {
|
||||
40
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_arch_memory_regions_dram() {
|
||||
let regions = arch_memory_regions();
|
||||
assert_eq!(4, regions.len());
|
||||
assert_eq!(layout::RAM_START, regions[3].0);
|
||||
assert_eq!(RegionType::Ram, regions[3].2);
|
||||
}
|
||||
}
|
||||
@@ -601,7 +601,7 @@ pub fn generate_common_cpuid(
|
||||
config: &CpuidConfig,
|
||||
) -> super::Result<Vec<CpuIdEntry>> {
|
||||
// SAFETY: cpuid called with valid leaves
|
||||
if unsafe { x86_64::__cpuid(1) }.ecx & 1 << HYPERVISOR_ECX_BIT == 1 << HYPERVISOR_ECX_BIT {
|
||||
if unsafe { x86_64::__cpuid(1) }.ecx & (1 << HYPERVISOR_ECX_BIT) == 1 << HYPERVISOR_ECX_BIT {
|
||||
// SAFETY: cpuid called with valid leaves
|
||||
let hypervisor_cpuid = unsafe { x86_64::__cpuid(0x4000_0000) };
|
||||
|
||||
@@ -689,7 +689,7 @@ pub fn generate_common_cpuid(
|
||||
// Clear AMX related bits if the AMX feature is not enabled
|
||||
0x7 => {
|
||||
if !config.amx && entry.index == 0 {
|
||||
entry.edx &= !(1 << AMX_BF16 | 1 << AMX_TILE | 1 << AMX_INT8)
|
||||
entry.edx &= !((1 << AMX_BF16) | (1 << AMX_TILE) | (1 << AMX_INT8))
|
||||
}
|
||||
}
|
||||
0xd =>
|
||||
@@ -747,12 +747,12 @@ pub fn generate_common_cpuid(
|
||||
// These features are not supported by TDX
|
||||
#[cfg(feature = "tdx")]
|
||||
if config.tdx {
|
||||
entry.eax &= !(1 << KVM_FEATURE_CLOCKSOURCE_BIT
|
||||
| 1 << KVM_FEATURE_CLOCKSOURCE2_BIT
|
||||
| 1 << KVM_FEATURE_CLOCKSOURCE_STABLE_BIT
|
||||
| 1 << KVM_FEATURE_ASYNC_PF_BIT
|
||||
| 1 << KVM_FEATURE_ASYNC_PF_VMEXIT_BIT
|
||||
| 1 << KVM_FEATURE_STEAL_TIME_BIT)
|
||||
entry.eax &= !((1 << KVM_FEATURE_CLOCKSOURCE_BIT)
|
||||
| (1 << KVM_FEATURE_CLOCKSOURCE2_BIT)
|
||||
| (1 << KVM_FEATURE_CLOCKSOURCE_STABLE_BIT)
|
||||
| (1 << KVM_FEATURE_ASYNC_PF_BIT)
|
||||
| (1 << KVM_FEATURE_ASYNC_PF_VMEXIT_BIT)
|
||||
| (1 << KVM_FEATURE_STEAL_TIME_BIT))
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
@@ -801,10 +801,10 @@ pub fn generate_common_cpuid(
|
||||
});
|
||||
cpuid.push(CpuIdEntry {
|
||||
function: 0x4000_0003,
|
||||
eax: 1 << 1 // AccessPartitionReferenceCounter
|
||||
| 1 << 2 // AccessSynicRegs
|
||||
| 1 << 3 // AccessSyntheticTimerRegs
|
||||
| 1 << 9, // AccessPartitionReferenceTsc
|
||||
eax: (1 << 1) // AccessPartitionReferenceCounter
|
||||
| (1 << 2) // AccessSynicRegs
|
||||
| (1 << 3) // AccessSyntheticTimerRegs
|
||||
| (1 << 9), // AccessPartitionReferenceTsc
|
||||
edx: 1 << 3, // CPU dynamic partitioning
|
||||
..Default::default()
|
||||
});
|
||||
@@ -887,6 +887,10 @@ pub fn configure_vcpu(
|
||||
}
|
||||
}
|
||||
|
||||
for c in &cpuid {
|
||||
info!("{}", c);
|
||||
}
|
||||
|
||||
vcpu.set_cpuid2(&cpuid)
|
||||
.map_err(|e| Error::SetSupportedCpusFailed(e.into()))?;
|
||||
|
||||
@@ -1380,7 +1384,7 @@ fn update_cpuid_topology(
|
||||
|
||||
let mut cpu_ebx = CpuidPatch::get_cpuid_reg(cpuid, 0x1, None, CpuidReg::EBX).unwrap_or(0);
|
||||
cpu_ebx |= ((dies_per_package as u32) * (cores_per_die as u32) * (threads_per_core as u32))
|
||||
& 0xff << 16;
|
||||
& (0xff << 16);
|
||||
CpuidPatch::set_cpuid_reg(cpuid, 0x1, None, CpuidReg::EBX, cpu_ebx);
|
||||
|
||||
let mut cpu_edx = CpuidPatch::get_cpuid_reg(cpuid, 0x1, None, CpuidReg::EDX).unwrap_or(0);
|
||||
|
||||
@@ -35,7 +35,7 @@ const TABLE_FOOTER_GUID: &str = "96b582de-1fb2-45f7-baea-a366c55a082d";
|
||||
const TDVF_METADATA_OFFSET_GUID: &str = "e47a6535-984a-4798-865e-4685a7bf8ec2";
|
||||
|
||||
// TDVF_DESCRIPTOR
|
||||
#[repr(packed)]
|
||||
#[repr(C, packed)]
|
||||
#[derive(Default)]
|
||||
pub struct TdvfDescriptor {
|
||||
signature: [u8; 4],
|
||||
@@ -45,7 +45,7 @@ pub struct TdvfDescriptor {
|
||||
}
|
||||
|
||||
// TDVF_SECTION
|
||||
#[repr(packed)]
|
||||
#[repr(C, packed)]
|
||||
#[derive(Clone, Copy, Default, Debug)]
|
||||
pub struct TdvfSection {
|
||||
pub data_offset: u32,
|
||||
@@ -209,7 +209,7 @@ enum HobType {
|
||||
EndOfHobList = 0xffff,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[repr(C, packed)]
|
||||
#[derive(Copy, Clone, Default, Debug)]
|
||||
struct HobHeader {
|
||||
r#type: HobType,
|
||||
@@ -217,7 +217,7 @@ struct HobHeader {
|
||||
reserved: u32,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[repr(C, packed)]
|
||||
#[derive(Copy, Clone, Default, Debug)]
|
||||
struct HobHandoffInfoTable {
|
||||
header: HobHeader,
|
||||
@@ -230,7 +230,7 @@ struct HobHandoffInfoTable {
|
||||
efi_end_of_hob_list: u64,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[repr(C, packed)]
|
||||
#[derive(Copy, Clone, Default, Debug)]
|
||||
struct EfiGuid {
|
||||
data1: u32,
|
||||
@@ -239,7 +239,7 @@ struct EfiGuid {
|
||||
data4: [u8; 8],
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[repr(C, packed)]
|
||||
#[derive(Copy, Clone, Default, Debug)]
|
||||
struct HobResourceDescriptor {
|
||||
header: HobHeader,
|
||||
@@ -250,7 +250,7 @@ struct HobResourceDescriptor {
|
||||
resource_length: u64,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[repr(C, packed)]
|
||||
#[derive(Copy, Clone, Default, Debug)]
|
||||
struct HobGuidType {
|
||||
header: HobHeader,
|
||||
@@ -266,14 +266,14 @@ pub enum PayloadImageType {
|
||||
RawVmLinux,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[repr(C, packed)]
|
||||
#[derive(Copy, Clone, Default, Debug)]
|
||||
pub struct PayloadInfo {
|
||||
pub image_type: PayloadImageType,
|
||||
pub entry_point: u64,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[repr(C, packed)]
|
||||
#[derive(Copy, Clone, Default, Debug)]
|
||||
struct TdPayload {
|
||||
guid_type: HobGuidType,
|
||||
@@ -299,7 +299,7 @@ pub struct TdHob {
|
||||
}
|
||||
|
||||
fn align_hob(v: u64) -> u64 {
|
||||
(v + 7) / 8 * 8
|
||||
v.div_ceil(8) * 8
|
||||
}
|
||||
|
||||
impl TdHob {
|
||||
|
||||
@@ -12,13 +12,13 @@ io_uring = ["dep:io-uring"]
|
||||
byteorder = "1.5.0"
|
||||
crc-any = "2.4.4"
|
||||
io-uring = { version = "0.6.4", optional = true }
|
||||
libc = "0.2.158"
|
||||
libc = "0.2.167"
|
||||
log = "0.4.22"
|
||||
remain = "0.2.14"
|
||||
serde = { version = "1.0.208", features = ["derive"] }
|
||||
smallvec = "1.13.2"
|
||||
thiserror = "1.0.62"
|
||||
uuid = { version = "1.8.0", features = ["v4"] }
|
||||
thiserror = "2.0.6"
|
||||
uuid = { version = "1.12.1", features = ["v4"] }
|
||||
virtio-bindings = { workspace = true, features = ["virtio-v5_0_0"] }
|
||||
virtio-queue = { workspace = true }
|
||||
vm-memory = { workspace = true, features = [
|
||||
|
||||
@@ -59,9 +59,7 @@ use vmm_sys_util::eventfd::EventFd;
|
||||
use vmm_sys_util::{aio, ioctl_io_nr, ioctl_ioc_nr};
|
||||
|
||||
use crate::async_io::{AsyncIo, AsyncIoError, AsyncIoResult};
|
||||
use crate::fixed_vhd::FixedVhd;
|
||||
use crate::qcow::{QcowFile, RawFile};
|
||||
use crate::vhdx::{Vhdx, VhdxError};
|
||||
use crate::vhdx::VhdxError;
|
||||
|
||||
const SECTOR_SHIFT: u8 = 9;
|
||||
pub const SECTOR_SIZE: u64 = 0x01 << SECTOR_SHIFT;
|
||||
@@ -220,6 +218,8 @@ fn sector<B: Bitmap + 'static>(
|
||||
mem.read_obj(addr).map_err(Error::GuestMemory)
|
||||
}
|
||||
|
||||
const DEFAULT_DESCRIPTOR_VEC_SIZE: usize = 32;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AlignedOperation {
|
||||
origin_ptr: u64,
|
||||
@@ -232,10 +232,10 @@ pub struct AlignedOperation {
|
||||
pub struct Request {
|
||||
pub request_type: RequestType,
|
||||
pub sector: u64,
|
||||
pub data_descriptors: SmallVec<[(GuestAddress, u32); 1]>,
|
||||
pub data_descriptors: SmallVec<[(GuestAddress, u32); DEFAULT_DESCRIPTOR_VEC_SIZE]>,
|
||||
pub status_addr: GuestAddress,
|
||||
pub writeback: bool,
|
||||
pub aligned_operations: SmallVec<[AlignedOperation; 1]>,
|
||||
pub aligned_operations: SmallVec<[AlignedOperation; DEFAULT_DESCRIPTOR_VEC_SIZE]>,
|
||||
pub start: Instant,
|
||||
}
|
||||
|
||||
@@ -263,10 +263,10 @@ impl Request {
|
||||
let mut req = Request {
|
||||
request_type: request_type(desc_chain.memory(), hdr_desc_addr)?,
|
||||
sector: sector(desc_chain.memory(), hdr_desc_addr)?,
|
||||
data_descriptors: SmallVec::with_capacity(1),
|
||||
data_descriptors: SmallVec::with_capacity(DEFAULT_DESCRIPTOR_VEC_SIZE),
|
||||
status_addr: GuestAddress(0),
|
||||
writeback: true,
|
||||
aligned_operations: SmallVec::with_capacity(1),
|
||||
aligned_operations: SmallVec::with_capacity(DEFAULT_DESCRIPTOR_VEC_SIZE),
|
||||
start: Instant::now(),
|
||||
};
|
||||
|
||||
@@ -398,7 +398,7 @@ impl Request {
|
||||
let request_type = self.request_type;
|
||||
let offset = (sector << SECTOR_SHIFT) as libc::off_t;
|
||||
|
||||
let mut iovecs: SmallVec<[libc::iovec; 1]> =
|
||||
let mut iovecs: SmallVec<[libc::iovec; DEFAULT_DESCRIPTOR_VEC_SIZE]> =
|
||||
SmallVec::with_capacity(self.data_descriptors.len());
|
||||
for (data_addr, data_len) in &self.data_descriptors {
|
||||
if *data_len == 0 {
|
||||
@@ -653,7 +653,8 @@ where
|
||||
completion_list: &mut VecDeque<(u64, i32)>,
|
||||
) -> AsyncIoResult<()> {
|
||||
// Convert libc::iovec into IoSliceMut
|
||||
let mut slices: SmallVec<[IoSliceMut; 1]> = SmallVec::with_capacity(iovecs.len());
|
||||
let mut slices: SmallVec<[IoSliceMut; DEFAULT_DESCRIPTOR_VEC_SIZE]> =
|
||||
SmallVec::with_capacity(iovecs.len());
|
||||
for iovec in iovecs.iter() {
|
||||
// SAFETY: on Linux IoSliceMut wraps around libc::iovec
|
||||
slices.push(IoSliceMut::new(unsafe {
|
||||
@@ -668,9 +669,11 @@ where
|
||||
file.seek(SeekFrom::Start(offset as u64))
|
||||
.map_err(AsyncIoError::ReadVectored)?;
|
||||
|
||||
// Read vectored
|
||||
file.read_vectored(slices.as_mut_slice())
|
||||
.map_err(AsyncIoError::ReadVectored)?
|
||||
let mut r = 0;
|
||||
for b in slices.iter_mut() {
|
||||
r += file.read(b).map_err(AsyncIoError::ReadVectored)?;
|
||||
}
|
||||
r
|
||||
};
|
||||
|
||||
completion_list.push_back((user_data, result as i32));
|
||||
@@ -688,7 +691,8 @@ where
|
||||
completion_list: &mut VecDeque<(u64, i32)>,
|
||||
) -> AsyncIoResult<()> {
|
||||
// Convert libc::iovec into IoSlice
|
||||
let mut slices: SmallVec<[IoSlice; 1]> = SmallVec::with_capacity(iovecs.len());
|
||||
let mut slices: SmallVec<[IoSlice; DEFAULT_DESCRIPTOR_VEC_SIZE]> =
|
||||
SmallVec::with_capacity(iovecs.len());
|
||||
for iovec in iovecs.iter() {
|
||||
// SAFETY: on Linux IoSlice wraps around libc::iovec
|
||||
slices.push(IoSlice::new(unsafe {
|
||||
@@ -703,9 +707,11 @@ where
|
||||
file.seek(SeekFrom::Start(offset as u64))
|
||||
.map_err(AsyncIoError::WriteVectored)?;
|
||||
|
||||
// Write vectored
|
||||
file.write_vectored(slices.as_slice())
|
||||
.map_err(AsyncIoError::WriteVectored)?
|
||||
let mut r = 0;
|
||||
for b in slices.iter() {
|
||||
r += file.write(b).map_err(AsyncIoError::WriteVectored)?;
|
||||
}
|
||||
r
|
||||
};
|
||||
|
||||
completion_list.push_back((user_data, result as i32));
|
||||
@@ -789,25 +795,6 @@ pub trait BlockBackend: Read + Write + Seek + Send + Debug {
|
||||
fn size(&self) -> Result<u64, Error>;
|
||||
}
|
||||
|
||||
/// Inspect the image file type and create an appropriate disk file to match it.
|
||||
pub fn create_disk_file(mut file: File, direct_io: bool) -> Result<Box<dyn BlockBackend>, Error> {
|
||||
let image_type = detect_image_type(&mut file).map_err(Error::DetectImageType)?;
|
||||
|
||||
Ok(match image_type {
|
||||
ImageType::Qcow2 => {
|
||||
Box::new(QcowFile::from(RawFile::new(file, direct_io)).map_err(Error::QcowError)?)
|
||||
as Box<dyn BlockBackend>
|
||||
}
|
||||
ImageType::FixedVhd => {
|
||||
Box::new(FixedVhd::new(file).map_err(Error::FixedVhdError)?) as Box<dyn BlockBackend>
|
||||
}
|
||||
ImageType::Vhdx => {
|
||||
Box::new(Vhdx::new(file).map_err(Error::VhdxError)?) as Box<dyn BlockBackend>
|
||||
}
|
||||
ImageType::Raw => Box::new(RawFile::new(file, direct_io)) as Box<dyn BlockBackend>,
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DiskTopology {
|
||||
pub logical_block_size: u64,
|
||||
|
||||
@@ -45,7 +45,7 @@ pub enum Error {
|
||||
CompressedBlocksNotSupported,
|
||||
#[error("Failed to evict cache: {0}")]
|
||||
EvictingCache(io::Error),
|
||||
#[error("File larger than max of {}: {0}", MAX_QCOW_FILE_SIZE)]
|
||||
#[error("File larger than max of {MAX_QCOW_FILE_SIZE}: {0}")]
|
||||
FileTooBig(u64),
|
||||
#[error("Failed to get file size: {0}")]
|
||||
GettingFileSize(io::Error),
|
||||
@@ -502,7 +502,7 @@ impl QcowFile {
|
||||
if refcount_bits != 16 {
|
||||
return Err(Error::UnsupportedRefcountOrder);
|
||||
}
|
||||
let refcount_bytes = (refcount_bits + 7) / 8;
|
||||
let refcount_bytes = refcount_bits.div_ceil(8);
|
||||
|
||||
// Need at least one refcount cluster
|
||||
if header.refcount_table_clusters == 0 {
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::{Seek, SeekFrom};
|
||||
use std::io::{Error, Seek, SeekFrom};
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
|
||||
use io_uring::{opcode, squeue, types, IoUring};
|
||||
use io_uring::{opcode, types, IoUring};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
use crate::async_io::{
|
||||
@@ -86,14 +86,14 @@ impl AsyncIo for RawFileAsync {
|
||||
|
||||
// SAFETY: we know the file descriptor is valid and we
|
||||
// relied on vm-memory to provide the buffer address.
|
||||
let _ = unsafe {
|
||||
unsafe {
|
||||
sq.push(
|
||||
&opcode::Readv::new(types::Fd(self.fd), iovecs.as_ptr(), iovecs.len() as u32)
|
||||
.offset(offset.try_into().unwrap())
|
||||
.build()
|
||||
.flags(squeue::Flags::ASYNC)
|
||||
.user_data(user_data),
|
||||
)
|
||||
.map_err(|_| AsyncIoError::ReadVectored(Error::other("Submission queue is full")))?
|
||||
};
|
||||
|
||||
// Update the submission queue and submit new operations to the
|
||||
@@ -114,14 +114,14 @@ impl AsyncIo for RawFileAsync {
|
||||
|
||||
// SAFETY: we know the file descriptor is valid and we
|
||||
// relied on vm-memory to provide the buffer address.
|
||||
let _ = unsafe {
|
||||
unsafe {
|
||||
sq.push(
|
||||
&opcode::Writev::new(types::Fd(self.fd), iovecs.as_ptr(), iovecs.len() as u32)
|
||||
.offset(offset.try_into().unwrap())
|
||||
.build()
|
||||
.flags(squeue::Flags::ASYNC)
|
||||
.user_data(user_data),
|
||||
)
|
||||
.map_err(|_| AsyncIoError::WriteVectored(Error::other("Submission queue is full")))?
|
||||
};
|
||||
|
||||
// Update the submission queue and submit new operations to the
|
||||
@@ -137,13 +137,13 @@ impl AsyncIo for RawFileAsync {
|
||||
let (submitter, mut sq, _) = self.io_uring.split();
|
||||
|
||||
// SAFETY: we know the file descriptor is valid.
|
||||
let _ = unsafe {
|
||||
unsafe {
|
||||
sq.push(
|
||||
&opcode::Fsync::new(types::Fd(self.fd))
|
||||
.build()
|
||||
.flags(squeue::Flags::ASYNC)
|
||||
.user_data(user_data),
|
||||
)
|
||||
.map_err(|_| AsyncIoError::Fsync(Error::other("Submission queue is full")))?
|
||||
};
|
||||
|
||||
// Update the submission queue and submit new operations to the
|
||||
|
||||
@@ -102,7 +102,7 @@ impl Read for Vhdx {
|
||||
let sector_count = (buf.len() as u64).div_ceil(self.disk_spec.logical_sector_size as u64);
|
||||
let sector_index = self.current_offset / self.disk_spec.logical_sector_size as u64;
|
||||
|
||||
vhdx_io::read(
|
||||
let result = vhdx_io::read(
|
||||
&mut self.file,
|
||||
buf,
|
||||
&self.disk_spec,
|
||||
@@ -117,7 +117,11 @@ impl Read for Vhdx {
|
||||
"Failed reading {sector_count} sectors from VHDx at index {sector_index}: {e}"
|
||||
),
|
||||
)
|
||||
})
|
||||
})?;
|
||||
|
||||
self.current_offset = self.current_offset.checked_add(result as u64).unwrap();
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,7 +146,7 @@ impl Write for Vhdx {
|
||||
})?;
|
||||
}
|
||||
|
||||
vhdx_io::write(
|
||||
let result = vhdx_io::write(
|
||||
&mut self.file,
|
||||
buf,
|
||||
&mut self.disk_spec,
|
||||
@@ -158,7 +162,11 @@ impl Write for Vhdx {
|
||||
"Failed writing {sector_count} sectors on VHDx at index {sector_index}: {e}"
|
||||
),
|
||||
)
|
||||
})
|
||||
})?;
|
||||
|
||||
self.current_offset = self.current_offset.checked_add(result as u64).unwrap();
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -110,7 +110,7 @@ impl FileTypeIdentifier {
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(packed)]
|
||||
#[repr(C, packed)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Header {
|
||||
pub signature: u32,
|
||||
@@ -142,7 +142,7 @@ impl Header {
|
||||
return Err(VhdxHeaderError::InvalidHeaderSign);
|
||||
}
|
||||
|
||||
let new_checksum = calculate_checksum(&mut buffer, size_of::<u32>())?;
|
||||
let new_checksum = calculate_checksum(&mut buffer, size_of::<u32>());
|
||||
if header.checksum != new_checksum {
|
||||
return Err(VhdxHeaderError::InvalidChecksum(String::from("Header")));
|
||||
}
|
||||
@@ -151,7 +151,7 @@ impl Header {
|
||||
}
|
||||
|
||||
/// Converts the header structure into a buffer
|
||||
fn get_header_as_buffer(&self, buffer: &mut [u8; HEADER_SIZE as usize]) {
|
||||
fn write_to_buffer(&self, buffer: &mut [u8; HEADER_SIZE as usize]) {
|
||||
// SAFETY: self is a valid header.
|
||||
let reference = unsafe {
|
||||
std::slice::from_raw_parts(self as *const Header as *const u8, HEADER_SIZE as usize)
|
||||
@@ -160,7 +160,7 @@ impl Header {
|
||||
}
|
||||
|
||||
/// Creates and returns new updated header from the provided current header
|
||||
pub fn update_header(
|
||||
fn update_header(
|
||||
f: &mut File,
|
||||
current_header: &Header,
|
||||
change_data_guid: bool,
|
||||
@@ -191,11 +191,9 @@ impl Header {
|
||||
log_offset: current_header.log_offset,
|
||||
};
|
||||
|
||||
new_header.get_header_as_buffer(&mut buffer);
|
||||
let mut crc = crc_any::CRC::crc32c();
|
||||
crc.digest(&buffer);
|
||||
new_header.checksum = crc.get_crc() as u32;
|
||||
new_header.get_header_as_buffer(&mut buffer);
|
||||
new_header.write_to_buffer(&mut buffer);
|
||||
new_header.checksum = calculate_checksum(&mut buffer, size_of::<u32>());
|
||||
new_header.write_to_buffer(&mut buffer);
|
||||
|
||||
f.seek(SeekFrom::Start(start))
|
||||
.map_err(VhdxHeaderError::SeekHeader)?;
|
||||
@@ -205,7 +203,7 @@ impl Header {
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(packed)]
|
||||
#[repr(C, packed)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
struct RegionTableHeader {
|
||||
pub signature: u32,
|
||||
@@ -231,7 +229,7 @@ impl RegionTableHeader {
|
||||
return Err(VhdxHeaderError::InvalidRegionSign);
|
||||
}
|
||||
|
||||
let new_checksum = calculate_checksum(&mut buffer, size_of::<u32>())?;
|
||||
let new_checksum = calculate_checksum(&mut buffer, size_of::<u32>());
|
||||
if region_table_header.checksum != new_checksum {
|
||||
return Err(VhdxHeaderError::InvalidChecksum(String::from("Region")));
|
||||
}
|
||||
@@ -330,7 +328,7 @@ impl RegionInfo {
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(packed)]
|
||||
#[repr(C, packed)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct RegionTableEntry {
|
||||
pub guid: Uuid,
|
||||
@@ -371,22 +369,10 @@ pub struct VhdxHeader {
|
||||
impl VhdxHeader {
|
||||
/// Creates a VhdxHeader from a reference to a file
|
||||
pub fn new(f: &mut File) -> Result<VhdxHeader> {
|
||||
let _file_type_identifier: FileTypeIdentifier = FileTypeIdentifier::new(f)?;
|
||||
let header_1 = Header::new(f, HEADER_1_START);
|
||||
let header_2 = Header::new(f, HEADER_2_START);
|
||||
|
||||
let mut file_write_guid: u128 = 0;
|
||||
let metadata = f.metadata().map_err(VhdxHeaderError::ReadMetadata)?;
|
||||
if !metadata.permissions().readonly() {
|
||||
file_write_guid = Uuid::new_v4().as_u128();
|
||||
}
|
||||
|
||||
let (header_1, header_2) =
|
||||
VhdxHeader::update_headers(f, header_1, header_2, file_write_guid)?;
|
||||
Ok(VhdxHeader {
|
||||
_file_type_identifier,
|
||||
header_1,
|
||||
header_2,
|
||||
_file_type_identifier: FileTypeIdentifier::new(f)?,
|
||||
header_1: Header::new(f, HEADER_1_START)?,
|
||||
header_2: Header::new(f, HEADER_2_START)?,
|
||||
region_table_1: RegionTableHeader::new(f, REGION_TABLE_1_START)?,
|
||||
_region_table_2: RegionTableHeader::new(f, REGION_TABLE_2_START)?,
|
||||
})
|
||||
@@ -468,13 +454,11 @@ impl VhdxHeader {
|
||||
/// Therefore, before calculating, the existing checksum is retrieved and the
|
||||
/// corresponding field is made zero. After the calculation, the existing checksum
|
||||
/// is put back to the buffer.
|
||||
pub fn calculate_checksum(buffer: &mut [u8], csum_offset: usize) -> Result<u32> {
|
||||
// Read the checksum into a mutable slice
|
||||
let csum_buf = &mut buffer[csum_offset..csum_offset + 4];
|
||||
// Convert the checksum chunk into a u32 integer
|
||||
let orig_csum = LittleEndian::read_u32(csum_buf);
|
||||
fn calculate_checksum(buffer: &mut [u8], csum_offset: usize) -> u32 {
|
||||
// Read the original checksum from the buffer
|
||||
let orig_csum = LittleEndian::read_u32(&buffer[csum_offset..csum_offset + 4]);
|
||||
// Zero the checksum in the buffer
|
||||
LittleEndian::write_u32(csum_buf, 0);
|
||||
LittleEndian::write_u32(&mut buffer[csum_offset..csum_offset + 4], 0);
|
||||
// Calculate the checksum on the resulting buffer
|
||||
let mut crc = crc_any::CRC::crc32c();
|
||||
crc.digest(&buffer);
|
||||
@@ -483,5 +467,5 @@ pub fn calculate_checksum(buffer: &mut [u8], csum_offset: usize) -> Result<u32>
|
||||
// Put back the original checksum in the buffer
|
||||
LittleEndian::write_u32(&mut buffer[csum_offset..csum_offset + 4], orig_csum);
|
||||
|
||||
Ok(new_csum)
|
||||
new_csum
|
||||
}
|
||||
|
||||
@@ -95,46 +95,45 @@ pub fn read(
|
||||
mut sector_index: u64,
|
||||
mut sector_count: u64,
|
||||
) -> Result<usize> {
|
||||
if disk_spec.has_parent {
|
||||
return Err(VhdxIoError::UnsupportedMode);
|
||||
}
|
||||
|
||||
let mut read_count: usize = 0;
|
||||
|
||||
while sector_count > 0 {
|
||||
if disk_spec.has_parent {
|
||||
return Err(VhdxIoError::UnsupportedMode);
|
||||
} else {
|
||||
let sector = Sector::new(disk_spec, bat, sector_index, sector_count)?;
|
||||
let sector = Sector::new(disk_spec, bat, sector_index, sector_count)?;
|
||||
|
||||
let bat_entry = match bat.get(sector.bat_index as usize) {
|
||||
Some(entry) => entry.0,
|
||||
None => {
|
||||
return Err(VhdxIoError::InvalidBatIndex);
|
||||
}
|
||||
};
|
||||
|
||||
match bat_entry & vhdx_bat::BAT_STATE_BIT_MASK {
|
||||
vhdx_bat::PAYLOAD_BLOCK_NOT_PRESENT
|
||||
| vhdx_bat::PAYLOAD_BLOCK_UNDEFINED
|
||||
| vhdx_bat::PAYLOAD_BLOCK_UNMAPPED
|
||||
| vhdx_bat::PAYLOAD_BLOCK_ZERO => {}
|
||||
vhdx_bat::PAYLOAD_BLOCK_FULLY_PRESENT => {
|
||||
f.seek(SeekFrom::Start(sector.file_offset))
|
||||
.map_err(VhdxIoError::ReadSectorBlock)?;
|
||||
f.read_exact(
|
||||
&mut buf[read_count
|
||||
..(read_count + (sector.free_sectors * SECTOR_SIZE) as usize)],
|
||||
)
|
||||
.map_err(VhdxIoError::ReadSectorBlock)?;
|
||||
}
|
||||
vhdx_bat::PAYLOAD_BLOCK_PARTIALLY_PRESENT => {
|
||||
return Err(VhdxIoError::UnsupportedMode);
|
||||
}
|
||||
_ => {
|
||||
return Err(VhdxIoError::InvalidBatEntryState);
|
||||
}
|
||||
};
|
||||
sector_count -= sector.free_sectors;
|
||||
sector_index += sector.free_sectors;
|
||||
read_count = sector.free_bytes as usize;
|
||||
let bat_entry = match bat.get(sector.bat_index as usize) {
|
||||
Some(entry) => entry.0,
|
||||
None => {
|
||||
return Err(VhdxIoError::InvalidBatIndex);
|
||||
}
|
||||
};
|
||||
|
||||
match bat_entry & vhdx_bat::BAT_STATE_BIT_MASK {
|
||||
vhdx_bat::PAYLOAD_BLOCK_NOT_PRESENT
|
||||
| vhdx_bat::PAYLOAD_BLOCK_UNDEFINED
|
||||
| vhdx_bat::PAYLOAD_BLOCK_UNMAPPED
|
||||
| vhdx_bat::PAYLOAD_BLOCK_ZERO => {}
|
||||
vhdx_bat::PAYLOAD_BLOCK_FULLY_PRESENT => {
|
||||
f.seek(SeekFrom::Start(sector.file_offset))
|
||||
.map_err(VhdxIoError::ReadSectorBlock)?;
|
||||
f.read_exact(
|
||||
&mut buf
|
||||
[read_count..(read_count + (sector.free_sectors * SECTOR_SIZE) as usize)],
|
||||
)
|
||||
.map_err(VhdxIoError::ReadSectorBlock)?;
|
||||
}
|
||||
vhdx_bat::PAYLOAD_BLOCK_PARTIALLY_PRESENT => {
|
||||
return Err(VhdxIoError::UnsupportedMode);
|
||||
}
|
||||
_ => {
|
||||
return Err(VhdxIoError::InvalidBatEntryState);
|
||||
}
|
||||
};
|
||||
sector_count -= sector.free_sectors;
|
||||
sector_index += sector.free_sectors;
|
||||
read_count += sector.free_bytes as usize;
|
||||
}
|
||||
Ok(read_count)
|
||||
}
|
||||
@@ -150,77 +149,73 @@ pub fn write(
|
||||
mut sector_index: u64,
|
||||
mut sector_count: u64,
|
||||
) -> Result<usize> {
|
||||
if disk_spec.has_parent {
|
||||
return Err(VhdxIoError::UnsupportedMode);
|
||||
}
|
||||
|
||||
let mut write_count: usize = 0;
|
||||
|
||||
while sector_count > 0 {
|
||||
if disk_spec.has_parent {
|
||||
return Err(VhdxIoError::UnsupportedMode);
|
||||
} else {
|
||||
let sector = Sector::new(disk_spec, bat, sector_index, sector_count)?;
|
||||
let sector = Sector::new(disk_spec, bat, sector_index, sector_count)?;
|
||||
|
||||
let bat_entry = match bat.get(sector.bat_index as usize) {
|
||||
Some(entry) => entry.0,
|
||||
None => {
|
||||
return Err(VhdxIoError::InvalidBatIndex);
|
||||
}
|
||||
};
|
||||
|
||||
match bat_entry & vhdx_bat::BAT_STATE_BIT_MASK {
|
||||
vhdx_bat::PAYLOAD_BLOCK_NOT_PRESENT
|
||||
| vhdx_bat::PAYLOAD_BLOCK_UNDEFINED
|
||||
| vhdx_bat::PAYLOAD_BLOCK_UNMAPPED
|
||||
| vhdx_bat::PAYLOAD_BLOCK_ZERO => {
|
||||
let file_offset =
|
||||
align!(disk_spec.image_size, vhdx_metadata::BLOCK_SIZE_MIN as u64);
|
||||
let new_size = file_offset
|
||||
.checked_add(disk_spec.block_size as u64)
|
||||
.ok_or(VhdxIoError::InvalidDiskSize)?;
|
||||
|
||||
f.set_len(new_size).map_err(VhdxIoError::ResizeFile)?;
|
||||
disk_spec.image_size = new_size;
|
||||
|
||||
let new_bat_entry = file_offset
|
||||
| (vhdx_bat::PAYLOAD_BLOCK_FULLY_PRESENT & vhdx_bat::BAT_STATE_BIT_MASK);
|
||||
bat[sector.bat_index as usize] = BatEntry(new_bat_entry);
|
||||
BatEntry::write_bat_entries(f, bat_offset, bat)
|
||||
.map_err(VhdxIoError::WriteBat)?;
|
||||
|
||||
if file_offset < vhdx_metadata::BLOCK_SIZE_MIN as u64 {
|
||||
break;
|
||||
}
|
||||
|
||||
f.seek(SeekFrom::Start(file_offset))
|
||||
.map_err(VhdxIoError::ReadSectorBlock)?;
|
||||
f.write_all(
|
||||
&buf[write_count
|
||||
..(write_count + (sector.free_sectors * SECTOR_SIZE) as usize)],
|
||||
)
|
||||
.map_err(VhdxIoError::ReadSectorBlock)?;
|
||||
}
|
||||
vhdx_bat::PAYLOAD_BLOCK_FULLY_PRESENT => {
|
||||
if sector.file_offset < vhdx_metadata::BLOCK_SIZE_MIN as u64 {
|
||||
break;
|
||||
}
|
||||
|
||||
f.seek(SeekFrom::Start(sector.file_offset))
|
||||
.map_err(VhdxIoError::ReadSectorBlock)?;
|
||||
f.write_all(
|
||||
&buf[write_count
|
||||
..(write_count + (sector.free_sectors * SECTOR_SIZE) as usize)],
|
||||
)
|
||||
.map_err(VhdxIoError::ReadSectorBlock)?;
|
||||
}
|
||||
vhdx_bat::PAYLOAD_BLOCK_PARTIALLY_PRESENT => {
|
||||
return Err(VhdxIoError::UnsupportedMode);
|
||||
}
|
||||
_ => {
|
||||
return Err(VhdxIoError::InvalidBatEntryState);
|
||||
}
|
||||
};
|
||||
sector_count -= sector.free_sectors;
|
||||
sector_index += sector.free_sectors;
|
||||
write_count = sector.free_bytes as usize;
|
||||
let bat_entry = match bat.get(sector.bat_index as usize) {
|
||||
Some(entry) => entry.0,
|
||||
None => {
|
||||
return Err(VhdxIoError::InvalidBatIndex);
|
||||
}
|
||||
};
|
||||
|
||||
match bat_entry & vhdx_bat::BAT_STATE_BIT_MASK {
|
||||
vhdx_bat::PAYLOAD_BLOCK_NOT_PRESENT
|
||||
| vhdx_bat::PAYLOAD_BLOCK_UNDEFINED
|
||||
| vhdx_bat::PAYLOAD_BLOCK_UNMAPPED
|
||||
| vhdx_bat::PAYLOAD_BLOCK_ZERO => {
|
||||
let file_offset =
|
||||
align!(disk_spec.image_size, vhdx_metadata::BLOCK_SIZE_MIN as u64);
|
||||
let new_size = file_offset
|
||||
.checked_add(disk_spec.block_size as u64)
|
||||
.ok_or(VhdxIoError::InvalidDiskSize)?;
|
||||
|
||||
f.set_len(new_size).map_err(VhdxIoError::ResizeFile)?;
|
||||
disk_spec.image_size = new_size;
|
||||
|
||||
let new_bat_entry = file_offset
|
||||
| (vhdx_bat::PAYLOAD_BLOCK_FULLY_PRESENT & vhdx_bat::BAT_STATE_BIT_MASK);
|
||||
bat[sector.bat_index as usize] = BatEntry(new_bat_entry);
|
||||
BatEntry::write_bat_entries(f, bat_offset, bat).map_err(VhdxIoError::WriteBat)?;
|
||||
|
||||
if file_offset < vhdx_metadata::BLOCK_SIZE_MIN as u64 {
|
||||
break;
|
||||
}
|
||||
|
||||
f.seek(SeekFrom::Start(file_offset))
|
||||
.map_err(VhdxIoError::ReadSectorBlock)?;
|
||||
f.write_all(
|
||||
&buf[write_count..(write_count + (sector.free_sectors * SECTOR_SIZE) as usize)],
|
||||
)
|
||||
.map_err(VhdxIoError::ReadSectorBlock)?;
|
||||
}
|
||||
vhdx_bat::PAYLOAD_BLOCK_FULLY_PRESENT => {
|
||||
if sector.file_offset < vhdx_metadata::BLOCK_SIZE_MIN as u64 {
|
||||
break;
|
||||
}
|
||||
|
||||
f.seek(SeekFrom::Start(sector.file_offset))
|
||||
.map_err(VhdxIoError::ReadSectorBlock)?;
|
||||
f.write_all(
|
||||
&buf[write_count..(write_count + (sector.free_sectors * SECTOR_SIZE) as usize)],
|
||||
)
|
||||
.map_err(VhdxIoError::ReadSectorBlock)?;
|
||||
}
|
||||
vhdx_bat::PAYLOAD_BLOCK_PARTIALLY_PRESENT => {
|
||||
return Err(VhdxIoError::UnsupportedMode);
|
||||
}
|
||||
_ => {
|
||||
return Err(VhdxIoError::InvalidBatEntryState);
|
||||
}
|
||||
};
|
||||
sector_count -= sector.free_sectors;
|
||||
sector_index += sector.free_sectors;
|
||||
write_count += sector.free_bytes as usize;
|
||||
}
|
||||
Ok(write_count)
|
||||
}
|
||||
|
||||
@@ -55,6 +55,8 @@ const METADATA_LENGTH_MAX: u32 = 1 << 20; // 1 MiB
|
||||
pub enum VhdxMetadataError {
|
||||
#[error("Invalid block size count")]
|
||||
InvalidBlockSize,
|
||||
#[error("Invalid disk size {0}")]
|
||||
InvalidDiskSize(u64),
|
||||
#[error("Invalid metadata entry count")]
|
||||
InvalidEntryCount,
|
||||
#[error("Invalid logical sector size")]
|
||||
@@ -214,6 +216,14 @@ impl DiskSpec {
|
||||
if metadata_presence != METADATA_ALL_PRESENT {
|
||||
return Err(VhdxMetadataError::MissingMetadata);
|
||||
}
|
||||
// Make sure virtual disk size is not zero
|
||||
if (metadata_presence & METADATA_VIRTUAL_DISK_SIZE_PRESENT != 0)
|
||||
&& disk_spec.virtual_disk_size == 0
|
||||
{
|
||||
return Err(VhdxMetadataError::InvalidDiskSize(
|
||||
disk_spec.virtual_disk_size,
|
||||
));
|
||||
}
|
||||
// Check if the virtual disk size is a multiple of the logical sector
|
||||
// size.
|
||||
if ((metadata_presence & METADATA_LOGICAL_SECTOR_SIZE_PRESENT) != 0)
|
||||
@@ -257,7 +267,7 @@ impl DiskSpec {
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(packed)]
|
||||
#[repr(C, packed)]
|
||||
#[derive(Default, Debug, Clone, Copy)]
|
||||
struct MetadataTableHeader {
|
||||
signature: u64,
|
||||
@@ -288,7 +298,7 @@ impl MetadataTableHeader {
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(packed)]
|
||||
#[repr(C, packed)]
|
||||
#[derive(Default, Debug, Clone, Copy)]
|
||||
pub struct MetadataTableEntry {
|
||||
item_id: Uuid,
|
||||
|
||||
9
build.rs
9
build.rs
@@ -3,6 +3,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
use std::env;
|
||||
use std::process::Command;
|
||||
|
||||
fn main() {
|
||||
@@ -12,10 +13,18 @@ fn main() {
|
||||
if git_out.status.success() {
|
||||
if let Ok(git_out_str) = String::from_utf8(git_out.stdout) {
|
||||
version = git_out_str;
|
||||
// Pop the trailing newline.
|
||||
version.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Append CH_EXTRA_VERSION to version if it is set.
|
||||
if let Ok(extra_version) = env::var("CH_EXTRA_VERSION") {
|
||||
println!("cargo:rerun-if-env-changed=CH_EXTRA_VERSION");
|
||||
version.push_str(&format!("-{}", extra_version));
|
||||
}
|
||||
|
||||
// This println!() has a special behavior, as it will set the environment
|
||||
// variable BUILD_VERSION, so that it can be reused from the binary.
|
||||
// Particularly, this is used from src/main.rs to display the exact
|
||||
|
||||
@@ -6,18 +6,18 @@ version = "0.1.0"
|
||||
|
||||
[dependencies]
|
||||
acpi_tables = { workspace = true }
|
||||
anyhow = "1.0.87"
|
||||
anyhow = "1.0.94"
|
||||
arch = { path = "../arch" }
|
||||
bitflags = "2.6.0"
|
||||
byteorder = "1.5.0"
|
||||
event_monitor = { path = "../event_monitor" }
|
||||
hypervisor = { path = "../hypervisor" }
|
||||
libc = "0.2.158"
|
||||
libc = "0.2.167"
|
||||
log = "0.4.22"
|
||||
num_enum = "0.7.2"
|
||||
pci = { path = "../pci" }
|
||||
serde = { version = "1.0.208", features = ["derive"] }
|
||||
thiserror = "1.0.62"
|
||||
thiserror = "2.0.6"
|
||||
tpm = { path = "../tpm" }
|
||||
vm-allocator = { path = "../vm-allocator" }
|
||||
vm-device = { path = "../vm-device" }
|
||||
@@ -29,9 +29,10 @@ vm-memory = { workspace = true, features = [
|
||||
vm-migration = { path = "../vm-migration" }
|
||||
vmm-sys-util = { workspace = true }
|
||||
|
||||
[target.'cfg(target_arch = "aarch64")'.dependencies]
|
||||
[target.'cfg(any(target_arch = "aarch64", target_arch = "riscv64"))'.dependencies]
|
||||
arch = { path = "../arch" }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
kvm = ["arch/kvm"]
|
||||
pvmemcontrol = []
|
||||
|
||||
144
devices/src/aia.rs
Normal file
144
devices/src/aia.rs
Normal file
@@ -0,0 +1,144 @@
|
||||
// Copyright © 2024 Institute of Software, CAS. All rights reserved.
|
||||
// Copyright 2020, ARM Limited.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||
|
||||
use super::interrupt_controller::{Error, InterruptController};
|
||||
extern crate arch;
|
||||
use std::result;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use arch::layout;
|
||||
use hypervisor::arch::riscv64::aia::{Vaia, VaiaConfig};
|
||||
use hypervisor::{AiaState, CpuState};
|
||||
use vm_device::interrupt::{
|
||||
InterruptIndex, InterruptManager, InterruptSourceConfig, InterruptSourceGroup,
|
||||
LegacyIrqSourceConfig, MsiIrqGroupConfig,
|
||||
};
|
||||
use vm_memory::address::Address;
|
||||
use vm_migration::{Migratable, Pausable, Snapshottable, Transportable};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
type Result<T> = result::Result<T, Error>;
|
||||
|
||||
// Reserve 32 IRQs for legacy devices.
|
||||
pub const IRQ_LEGACY_BASE: usize = layout::IRQ_BASE as usize;
|
||||
pub const IRQ_LEGACY_COUNT: usize = 32;
|
||||
// TODO: AIA snapshotting is not yet completed.
|
||||
pub const _AIA_SNAPSHOT_ID: &str = "";
|
||||
|
||||
// Aia (Advance Interrupt Architecture) struct provides all the functionality of a
|
||||
// AIA device. It wraps a hypervisor-emulated AIA device (Vaia) provided by the
|
||||
// `hypervisor` crate.
|
||||
// Aia struct also implements InterruptController to provide interrupt delivery
|
||||
// service.
|
||||
pub struct Aia {
|
||||
interrupt_source_group: Arc<dyn InterruptSourceGroup>,
|
||||
// The hypervisor agnostic virtual AIA
|
||||
vaia: Arc<Mutex<dyn Vaia>>,
|
||||
}
|
||||
|
||||
impl Aia {
|
||||
pub fn new(
|
||||
vcpu_count: u8,
|
||||
interrupt_manager: Arc<dyn InterruptManager<GroupConfig = MsiIrqGroupConfig>>,
|
||||
vm: Arc<dyn hypervisor::Vm>,
|
||||
) -> Result<Aia> {
|
||||
let interrupt_source_group = interrupt_manager
|
||||
.create_group(MsiIrqGroupConfig {
|
||||
base: IRQ_LEGACY_BASE as InterruptIndex,
|
||||
count: IRQ_LEGACY_COUNT as InterruptIndex,
|
||||
})
|
||||
.map_err(Error::CreateInterruptSourceGroup)?;
|
||||
|
||||
let vaia = vm
|
||||
.create_vaia(Aia::create_default_config(vcpu_count as u64))
|
||||
.map_err(Error::CreateAia)?;
|
||||
|
||||
let aia = Aia {
|
||||
interrupt_source_group,
|
||||
vaia,
|
||||
};
|
||||
aia.enable()?;
|
||||
|
||||
Ok(aia)
|
||||
}
|
||||
|
||||
pub fn restore_vaia(
|
||||
&mut self,
|
||||
state: Option<AiaState>,
|
||||
_saved_vcpu_states: &[CpuState],
|
||||
) -> Result<()> {
|
||||
self.vaia
|
||||
.clone()
|
||||
.lock()
|
||||
.unwrap()
|
||||
.set_state(&state.unwrap())
|
||||
.map_err(Error::RestoreAia)
|
||||
}
|
||||
|
||||
fn enable(&self) -> Result<()> {
|
||||
// Set irqfd for legacy interrupts
|
||||
self.interrupt_source_group
|
||||
.enable()
|
||||
.map_err(Error::EnableInterrupt)?;
|
||||
|
||||
// Set irq_routing for legacy interrupts.
|
||||
// irqchip: Hardcode to 0 as we support only 1 APLIC
|
||||
// pin: Use irq number as pin
|
||||
for i in IRQ_LEGACY_BASE..(IRQ_LEGACY_BASE + IRQ_LEGACY_COUNT) {
|
||||
let config = LegacyIrqSourceConfig {
|
||||
irqchip: 0,
|
||||
pin: (i - IRQ_LEGACY_BASE) as u32,
|
||||
};
|
||||
self.interrupt_source_group
|
||||
.update(
|
||||
i as InterruptIndex,
|
||||
InterruptSourceConfig::LegacyIrq(config),
|
||||
false,
|
||||
false,
|
||||
)
|
||||
.map_err(Error::EnableInterrupt)?;
|
||||
}
|
||||
|
||||
self.interrupt_source_group
|
||||
.set_gsi()
|
||||
.map_err(Error::EnableInterrupt)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Default config implied by arch::layout
|
||||
pub fn create_default_config(vcpu_count: u64) -> VaiaConfig {
|
||||
VaiaConfig {
|
||||
vcpu_count: vcpu_count as u32,
|
||||
aplic_addr: layout::APLIC_START.raw_value(),
|
||||
imsic_addr: layout::IMSIC_START.raw_value(),
|
||||
nr_irqs: layout::IRQ_NUM,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_vaia(&mut self) -> Result<Arc<Mutex<dyn Vaia>>> {
|
||||
Ok(self.vaia.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl InterruptController for Aia {
|
||||
// This should be called anytime an interrupt needs to be injected into the
|
||||
// running guest.
|
||||
fn service_irq(&mut self, irq: usize) -> Result<()> {
|
||||
self.interrupt_source_group
|
||||
.trigger(irq as InterruptIndex)
|
||||
.map_err(Error::TriggerInterrupt)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn notifier(&self, irq: usize) -> Option<EventFd> {
|
||||
self.interrupt_source_group.notifier(irq as InterruptIndex)
|
||||
}
|
||||
}
|
||||
|
||||
impl Snapshottable for Aia {}
|
||||
impl Pausable for Aia {}
|
||||
impl Transportable for Aia {}
|
||||
impl Migratable for Aia {}
|
||||
@@ -1,3 +1,4 @@
|
||||
// Copyright © 2024 Institute of Software, CAS. All rights reserved.
|
||||
// Copyright 2020, ARM Limited.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||
@@ -41,6 +42,14 @@ pub enum Error {
|
||||
/// Failed restoring GIC device.
|
||||
#[error("Failed restoring GIC device: {0}")]
|
||||
RestoreGic(hypervisor::arch::aarch64::gic::Error),
|
||||
#[cfg(target_arch = "riscv64")]
|
||||
/// Failed creating AIA device.
|
||||
#[error("Failed creating AIA device: {0}")]
|
||||
CreateAia(hypervisor::HypervisorVmError),
|
||||
#[cfg(target_arch = "riscv64")]
|
||||
/// Failed restoring AIA device.
|
||||
#[error("Failed restoring AIA device: {0}")]
|
||||
RestoreAia(hypervisor::arch::riscv64::aia::Error),
|
||||
}
|
||||
|
||||
type Result<T> = result::Result<T, Error>;
|
||||
@@ -67,7 +76,7 @@ pub struct MsiMessage {
|
||||
// Introduce trait InterruptController to uniform the interrupt
|
||||
// service provided for devices.
|
||||
// Device manager uses this trait without caring whether it is a
|
||||
// IOAPIC (X86) or GIC (Arm).
|
||||
// IOAPIC (X86), GIC (Arm) or AIA (RISC-V).
|
||||
pub trait InterruptController: Send {
|
||||
fn service_irq(&mut self, irq: usize) -> Result<()>;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
|
||||
@@ -346,9 +346,9 @@ impl Ioapic {
|
||||
|
||||
// Generate MSI message address
|
||||
let low_addr: u32 = self.apic_address.0 as u32
|
||||
| u32::from(destination_id) << 12
|
||||
| u32::from(redirection_hint) << 3
|
||||
| u32::from(destination_mode) << 2;
|
||||
| (u32::from(destination_id) << 12)
|
||||
| (u32::from(redirection_hint) << 3)
|
||||
| (u32::from(destination_mode) << 2);
|
||||
|
||||
// Validate Trigger Mode value
|
||||
let trigger_mode = trigger_mode(entry);
|
||||
@@ -372,9 +372,9 @@ impl Ioapic {
|
||||
}
|
||||
|
||||
// Generate MSI message data
|
||||
let data: u32 = u32::from(trigger_mode) << 15
|
||||
| u32::from(remote_irr(entry)) << 14
|
||||
| u32::from(delivery_mode) << 8
|
||||
let data: u32 = (u32::from(trigger_mode) << 15)
|
||||
| (u32::from(remote_irr(entry)) << 14)
|
||||
| (u32::from(delivery_mode) << 8)
|
||||
| u32::from(vector(entry));
|
||||
|
||||
let config = MsiIrqSourceConfig {
|
||||
|
||||
@@ -152,7 +152,7 @@ impl BusDevice for Cmos {
|
||||
0x08 => to_bcd(month as u8),
|
||||
0x09 => to_bcd((year % 100) as u8),
|
||||
// Bit 5 for 32kHz clock. Bit 7 for Update in Progress
|
||||
0x0a => 1 << 5 | (update_in_progress as u8) << 7,
|
||||
0x0a => (1 << 5) | ((update_in_progress as u8) << 7),
|
||||
// Bit 0-6 are reserved and must be 0.
|
||||
// Bit 7 must be 1 (CMOS has power)
|
||||
0x0d => 1 << 7,
|
||||
|
||||
@@ -15,6 +15,8 @@ extern crate event_monitor;
|
||||
extern crate log;
|
||||
|
||||
pub mod acpi;
|
||||
#[cfg(target_arch = "riscv64")]
|
||||
pub mod aia;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub mod debug_console;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
@@ -26,6 +28,8 @@ pub mod legacy;
|
||||
#[cfg(feature = "pvmemcontrol")]
|
||||
pub mod pvmemcontrol;
|
||||
pub mod pvpanic;
|
||||
// TODO: TPM is not yet supported
|
||||
#[cfg(not(target_arch = "riscv64"))]
|
||||
pub mod tpm;
|
||||
|
||||
pub use self::acpi::{AcpiGedDevice, AcpiPmTimerDevice, AcpiShutdownDevice};
|
||||
|
||||
@@ -708,7 +708,7 @@ impl PciDevice for PvmemcontrolPciDevice {
|
||||
self.configuration.read_config_register(reg_idx)
|
||||
}
|
||||
|
||||
fn as_any(&mut self) -> &mut dyn std::any::Any {
|
||||
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
|
||||
|
||||
@@ -237,7 +237,7 @@ impl PciDevice for PvPanicDevice {
|
||||
data[0] = self.events;
|
||||
}
|
||||
|
||||
fn as_any(&mut self) -> &mut dyn Any {
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# AMD SEV-SNP
|
||||
|
||||
### WARNING
|
||||
|
||||
This feature is only currently supported on MSHV.
|
||||
|
||||
AMD Secure Encrypted Virtualization & Secure Nested Paging (SEV-SNP) is an AMD
|
||||
@@ -9,8 +10,8 @@ malicious hypervisor-based attacks like data replay, memory-remapping and more
|
||||
in order to create an isolated execution environment. Here are some useful
|
||||
links:
|
||||
|
||||
* [SNP Homepage] (https://www.amd.com/en/processors/amd-secure-encrypted-virtualization)
|
||||
more information about SEV-SNP technical aspects, design and specification.
|
||||
- [SNP Homepage](https://www.amd.com/content/dam/amd/en/documents/epyc-business-docs/solution-briefs/amd-secure-encrypted-virtualization-solution-brief.pdf):
|
||||
more information about SEV-SNP technical aspects, design and specification.
|
||||
|
||||
## Cloud Hypervisor support
|
||||
|
||||
|
||||
13
docs/api.md
13
docs/api.md
@@ -50,17 +50,12 @@ for more details about the API payloads and responses.
|
||||
|
||||
#### REST API Location and availability
|
||||
|
||||
The REST API is available as soon as the Cloud Hypervisor binary is started,
|
||||
through a local UNIX socket.
|
||||
By default, it is located at `/run/user/{user ID}/cloud-hypervisor.{Cloud Hypervisor PID}`.
|
||||
For example, if you launched Cloud Hypervisor as user ID 1000 and its PID is
|
||||
123456, the Cloud Hypervisor REST API will be available at `/run/user/1000/cloud-hypervisor.123456`.
|
||||
|
||||
The REST API default URL can be overridden through the Cloud Hypervisor
|
||||
option `--api-socket`:
|
||||
The REST API, if enabled, is available as soon as the Cloud Hypervisor binary is started,
|
||||
through either a local UNIX socket as given in the Cloud Hypervisor option `--api-socket path=...`
|
||||
or a fd with `--api-socket fd=...`.
|
||||
|
||||
```
|
||||
$ ./target/debug/cloud-hypervisor --api-socket /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
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
# `cloud-hypervisor` debug IO ports
|
||||
|
||||
When running x86 guests, `cloud-hypervisor` provides different kinds of debug ports:
|
||||
- [`0x80` debug port](https://www.intel.com/content/www/us/en/support/articles/000005500/boards-and-kits.html)
|
||||
- Debug console (by default at `0xe9`).
|
||||
|
||||
- [`0x80` debug port](https://web.archive.org/web/20211028033025/https://www.intel.com/content/www/us/en/support/articles/000005500/boards-and-kits.html)
|
||||
- Debug console (by default at `0xe9`).
|
||||
- Firmware debug port at `0x402`.
|
||||
|
||||
All of them can be used to trace user-defined guest events and all of them can
|
||||
@@ -25,13 +26,13 @@ for debugging specific components of the guest software stack. When logging a
|
||||
write of one of those codes to the debug port, `cloud-hypervisor` adds a
|
||||
pre-defined string to the logs.
|
||||
|
||||
| Code Range | Component | Log string |
|
||||
| ---------------- | ----------- | ------------ |
|
||||
| `0x00` to `0x1f` | Firmware | `Firmware` |
|
||||
| `0x20` to `0x3f` | Bootloader | `Bootloader` |
|
||||
| `0x40` to `0x5f` | Kernel | `Kernel` |
|
||||
| `0x60` to `0x7f` | Userspace | `Userspace` |
|
||||
| `0x80` to `0xff` | Custom | `Custom` |
|
||||
| Code Range | Component | Log string |
|
||||
| ---------------- | ---------- | ------------ |
|
||||
| `0x00` to `0x1f` | Firmware | `Firmware` |
|
||||
| `0x20` to `0x3f` | Bootloader | `Bootloader` |
|
||||
| `0x40` to `0x5f` | Kernel | `Kernel` |
|
||||
| `0x60` to `0x7f` | Userspace | `Userspace` |
|
||||
| `0x80` to `0xff` | Custom | `Custom` |
|
||||
|
||||
One typical use case is guest boot time measurement and tracing. By writing
|
||||
different values to the debug I/O port at different boot process steps, the
|
||||
@@ -71,8 +72,8 @@ cloud-hypervisor: 403.499628ms: DEBUG:vmm/src/vm.rs:510 -- [Debug I/O port: Firm
|
||||
|
||||
### Debug console port
|
||||
|
||||
The debug console is inspired by QEMU and Bochs, which have a similar feature.
|
||||
By default, the I/O port `0xe9` is used. This port can be configured like a
|
||||
The debug console is inspired by QEMU and Bochs, which have a similar feature.
|
||||
By default, the I/O port `0xe9` is used. This port can be configured like a
|
||||
console. Thus, it can print to a tty, a file, or a pty, for example.
|
||||
|
||||
### Firmware debug port
|
||||
@@ -83,7 +84,7 @@ it. The firmware debug port only prints to stdout.
|
||||
## When do I need these ports?
|
||||
|
||||
The ports are on the one hand interesting for firmware or kernel developers, as
|
||||
they provide an easy way to print debug information from within a guest.
|
||||
they provide an easy way to print debug information from within a guest.
|
||||
Furthermore, you can patch "normal" software to measure certain events, such as
|
||||
the boot time of a guest.
|
||||
|
||||
@@ -91,6 +92,6 @@ the boot time of a guest.
|
||||
|
||||
The `0x80` debug port and the port of the firmware debug device are always
|
||||
available. The debug console must be activated via the command line, but
|
||||
provides more configuration options.
|
||||
provides more configuration options.
|
||||
|
||||
You can use different ports for different aspect of your logging messages.
|
||||
You can use different ports for different aspect of your logging messages.
|
||||
|
||||
@@ -8,11 +8,12 @@ the host kernel. The required Linux and KVM changes can be found in the
|
||||
[KVM SGX Tree](https://github.com/intel/kvm-sgx).
|
||||
|
||||
Utilizing SGX in the guest requires a kernel/OS with SGX support, e.g. a kernel
|
||||
built using the [SGX Linux Development Tree](https://git.kernel.org/pub/scm/linux/kernel/git/jarkko/linux-sgx.git)
|
||||
since release 5.11, see
|
||||
[here](https://www.intel.com/content/www/us/en/developer/tools/software-guard-extensions/linux-overview.html)
|
||||
or the [KVM SGX Tree](https://github.com/intel/kvm-sgx). Running KVM SGX as the
|
||||
guest kernel allows nested virtualization of SGX.
|
||||
|
||||
For more information about SGX, please refer to the [SGX Homepage](https://software.intel.com/sgx).
|
||||
For more information about SGX, please refer to the [SGX Homepage](https://www.intel.com/content/www/us/en/developer/tools/software-guard-extensions/linux-overview.html).
|
||||
|
||||
For more information about SGX SDK and how to test SGX, please refer to the
|
||||
following [instructions](https://github.com/intel/linux-sgx).
|
||||
|
||||
@@ -4,21 +4,21 @@ Intel® Trust Domain Extensions (Intel® TDX) is an Intel technology designed to
|
||||
isolate virtual machines from the VMM, hypervisor and any other software on the
|
||||
host platform. Here are some useful links:
|
||||
|
||||
* [TDX Homepage](https://www.intel.com/content/www/us/en/developer/articles/technical/intel-trust-domain-extensions.html):
|
||||
more information about TDX technical aspects, design and specification
|
||||
- [TDX Homepage](https://www.intel.com/content/www/us/en/developer/tools/trust-domain-extensions/overview.html):
|
||||
more information about TDX technical aspects, design and specification
|
||||
|
||||
* [KVM TDX tree](https://github.com/intel/tdx/tree/kvm): the required
|
||||
- [KVM TDX tree](https://github.com/intel/tdx/tree/kvm): the required
|
||||
Linux kernel changes for the host side
|
||||
|
||||
* [Guest TDX tree](https://github.com/intel/tdx/tree/guest): the Linux
|
||||
- [Guest TDX tree](https://github.com/intel/tdx/tree/guest): the Linux
|
||||
kernel changes for the guest side
|
||||
|
||||
* [EDK2 project](https://github.com/tianocore/edk2): the TDVF firmware
|
||||
- [EDK2 project](https://github.com/tianocore/edk2): the TDVF firmware
|
||||
|
||||
* [Confidential Containers project](https://github.com/confidential-containers/td-shim):
|
||||
- [Confidential Containers project](https://github.com/confidential-containers/td-shim):
|
||||
the TDShim firmware
|
||||
|
||||
* [TDX Tools](https://github.com/intel/tdx-tools): a collection of tools
|
||||
- [TDX Linux](https://github.com/intel/tdx-linux): a collection of tools
|
||||
and scripts to setup TDX environment for testing purpose (such as
|
||||
installing required packages on the host, creating guest images, and
|
||||
building the custom Linux kernel for TDX host and guest)
|
||||
@@ -27,17 +27,13 @@ more information about TDX technical aspects, design and specification
|
||||
|
||||
It is required to use a machine with TDX enabled in hardware and
|
||||
with the host OS compiled from the [KVM TDX tree](https://github.com/intel/tdx/tree/kvm).
|
||||
The host environment can also be setup with the [TDX Tools](https://github.com/intel/tdx-tools).
|
||||
The host environment can also be setup with the [TDX Linux](https://github.com/intel/tdx-linux).
|
||||
|
||||
Cloud Hypervisor can run TDX VM (Trust Domain) by loading a TD firmware ([TDVF](https://github.com/tianocore/edk2)),
|
||||
which will then load the guest kernel from the image. The image must be custom
|
||||
as it must include a kernel built from the [Guest TDX tree](https://github.com/intel/tdx/tree/guest).
|
||||
Cloud Hypervisor can also boot a TDX VM with direct kernel boot using [TDshim](https://github.com/confidential-containers/td-shim).
|
||||
The custom Linux kernel for the guest can be built with the [TDX Tools](https://github.com/intel/tdx-tools).
|
||||
|
||||
> **Note**
|
||||
> The latest version of custom host and guest kernel being tested is
|
||||
> from [TDX Tools - 2023ww01](https://github.com/intel/tdx-tools/commits/2023ww01).
|
||||
The custom Linux kernel for the guest can be built with the [TDX Linux](https://github.com/intel/tdx-linux).
|
||||
|
||||
### TDVF
|
||||
|
||||
@@ -110,6 +106,7 @@ direct kernel boot, which is useful for containers use cases.
|
||||
|
||||
To build TDShim from source, it is required to install `Rust`, `NASM`,
|
||||
and `LLVM` first. The TDshim can be build as follows:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/confidential-containers/td-shim
|
||||
cd td-shim
|
||||
@@ -126,13 +123,14 @@ cargo image --release
|
||||
|
||||
If debug logs from the TDShim is needed, here are the alternative
|
||||
commands:
|
||||
|
||||
```bash
|
||||
cargo image
|
||||
```
|
||||
|
||||
And run a TDX VM by providing the firmware previously built, along with a guest
|
||||
kernel built from the [Guest TDX tree](https://github.com/intel/tdx/tree/guest)
|
||||
or the [TDX Tools](https://github.com/intel/tdx-tools).
|
||||
or the [TDX Linux](https://github.com/intel/tdx-linux).
|
||||
The appropriate kernel boot options must be provided through the `--cmdline`
|
||||
option as well.
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# I/O Throttling
|
||||
|
||||
Cloud Hypervisor now supports I/O throttling on virtio-block and virtio-net
|
||||
devices. This support is based on the [`rate-limiter` module](https://github.com/firecracker-microvm/firecracker/tree/master/src/rate_limiter)
|
||||
devices. This support is based on the [`rate-limiter` module](https://github.com/firecracker-microvm/firecracker/tree/7a1231b141e958d15d5b2c079dd5e0880528b4b0/src/rate_limiter)
|
||||
from Firecracker. This document explains the user interface of this
|
||||
feature, and highlights some internal implementations that can help users
|
||||
better understand the expected behavior of I/O throttling in practice.
|
||||
@@ -44,10 +44,12 @@ generally advisable to keep `bw/ops_refill_time` larger than `100 ms`
|
||||
expectation ("refill-rate").
|
||||
|
||||
## Rate Limit Groups
|
||||
|
||||
It is possible to throttle the aggregate bandwidth or operations
|
||||
of multiple virtio-blk devices using a `rate_limit_group`. virtio-blk devices may be
|
||||
dynamically added and removed from a `rate_limit_group`. The following example
|
||||
demonstrates how to throttle the aggregate bandwidth of two disks to 10 MiB/s.
|
||||
|
||||
```
|
||||
--disk path=disk0.raw,rate_limit_group=group0 \
|
||||
path=disk1.raw,rate_limit_group=group0 \
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
# Live Migration
|
||||
|
||||
This document gives two examples of how to use the live migration
|
||||
support in Cloud Hypervisor:
|
||||
This document gives examples of how to use the live migration support
|
||||
in Cloud Hypervisor:
|
||||
|
||||
1. local migration - migrating between two VMs running on the same
|
||||
machine;
|
||||
1. nested-vm migration - migrating between two nested VMs whose host VMs
|
||||
are running on the same machine.
|
||||
1. local migration - migrating a VM from one Cloud Hypervisor instance to another on the same machine;
|
||||
1. remote migration - migrating a VM between two machines;
|
||||
|
||||
> :warning: These examples place sockets /tmp. This is done for
|
||||
> simplicity and should not be done in production.
|
||||
|
||||
## Local Migration (Suitable for Live Upgrade of VMM)
|
||||
|
||||
Launch the source VM (on the host machine):
|
||||
```bash
|
||||
|
||||
```console
|
||||
$ target/release/cloud-hypervisor
|
||||
--kernel ~/workloads/vmlinux \
|
||||
--disk path=~/workloads/focal.raw \
|
||||
@@ -20,17 +23,20 @@ $ target/release/cloud-hypervisor
|
||||
```
|
||||
|
||||
Launch the destination VM from the same directory (on the host machine):
|
||||
```bash
|
||||
|
||||
```console
|
||||
$ target/release/cloud-hypervisor --api-socket=/tmp/api2
|
||||
```
|
||||
|
||||
Get ready for receiving migration for the destination VM (on the host machine):
|
||||
```bash
|
||||
|
||||
```console
|
||||
$ target/release/ch-remote --api-socket=/tmp/api2 receive-migration unix:/tmp/sock
|
||||
```
|
||||
|
||||
Start to send migration for the source VM (on the host machine):
|
||||
```bash
|
||||
|
||||
```console
|
||||
$ target/release/ch-remote --api-socket=/tmp/api1 send-migration --local unix:/tmp/sock
|
||||
```
|
||||
|
||||
@@ -38,102 +44,139 @@ When the above commands completed, the source VM should be successfully
|
||||
migrated to the destination VM. Now the destination VM is running while
|
||||
the source VM is terminated gracefully.
|
||||
|
||||
## Nested-VM Migration
|
||||
## Remote Migration
|
||||
|
||||
Launch VM 1 (on the host machine) with an extra virtio-blk device for
|
||||
exposing a guest image for the nested source VM:
|
||||
> Note: the example below also attached an additional virtio-blk device
|
||||
> with a dummy image for testing purpose (which is optional).
|
||||
```bash
|
||||
$ head -c 1M < /dev/urandom > tmp.img # create a dummy image for testing
|
||||
$ sudo /target/release/cloud-hypervisor \
|
||||
In this example, we will migrate a VM from one machine (`src`) to
|
||||
another (`dst`) across the network. To keep it simple, we will use a
|
||||
minimal VM setup without storage.
|
||||
|
||||
### Preparation
|
||||
|
||||
Make sure that `src` and `dst` can reach each other via the
|
||||
network. You should be able to ping each machine. Also each machine
|
||||
should have an open TCP port.
|
||||
|
||||
You will need a kernel and initramfs for a minimal Linux system. For
|
||||
this example, we will use the Debian netboot image.
|
||||
|
||||
Place the kernel and initramfs into the _same directory_ on both
|
||||
machines. This is important for the migration to succeed. We will use
|
||||
`/var/images`:
|
||||
|
||||
```console
|
||||
src $ export DEBIAN=https://ftp.debian.org/debian/dists/stable/main/installer-amd64/current/images/netboot/debian-installer/amd64
|
||||
src $ mkdir -p /var/images
|
||||
src $ curl $DEBIAN/linux > /var/images/linux
|
||||
src $ curl $DEBIAN/initrd.gz > /var/images/initrd
|
||||
```
|
||||
|
||||
Repeat the above steps on the destination host.
|
||||
|
||||
### Unix Socket Migration
|
||||
|
||||
If Unix socket is selected for migration, we can tunnel traffic through "socat".
|
||||
|
||||
#### Starting the Receiver VM
|
||||
|
||||
On the receiver side, we prepare an empty VM:
|
||||
|
||||
```console
|
||||
dst $ cloud-hypervisor --api-socket /tmp/api
|
||||
```
|
||||
|
||||
In a different terminal, configure the VM as a migration target:
|
||||
|
||||
```console
|
||||
dst $ ch-remote --api-socket=/tmp/api receive-migration unix:/tmp/sock
|
||||
```
|
||||
|
||||
In yet another terminal, forward TCP connections to the Unix domain socket:
|
||||
|
||||
```console
|
||||
dst $ socat TCP-LISTEN:{port},reuseaddr UNIX-CLIENT:/tmp/sock
|
||||
```
|
||||
|
||||
#### Starting the Sender VM
|
||||
|
||||
Let's start the VM on the source machine:
|
||||
|
||||
```console
|
||||
src $ cloud-hypervisor \
|
||||
--serial tty --console off \
|
||||
--cpus boot=1 --memory size=512M \
|
||||
--kernel vmlinux \
|
||||
--cmdline "root=/dev/vda1 console=ttyS0" \
|
||||
--disk path=focal-1.raw path=focal-nested.raw path=tmp.img\
|
||||
--net ip=192.168.101.1
|
||||
--cpus boot=2 --memory size=4G \
|
||||
--kernel /var/images/linux \
|
||||
--initramfs /var/images/initrd \
|
||||
--cmdline "console=ttyS0" \
|
||||
--api-socket /tmp/api
|
||||
```
|
||||
|
||||
Launch VM 2 (on the host machine) with an extra virtio-blk device for
|
||||
exposing the same guest image for the nested destination VM:
|
||||
```bash
|
||||
$ sudo /target/release/cloud-hypervisor \
|
||||
After a few seconds the VM should be up and you can interact with it.
|
||||
|
||||
#### Performing the Migration
|
||||
|
||||
First, we start `socat`:
|
||||
|
||||
```console
|
||||
src $ socat UNIX-LISTEN:/tmp/sock,reuseaddr TCP:{dst}:{port}
|
||||
```
|
||||
|
||||
> Replace {dst}:{port} with the actual IP address and port of your destination host.
|
||||
|
||||
Then we kick-off the migration itself:
|
||||
|
||||
```console
|
||||
src $ ch-remote --api-socket=/tmp/api send-migration unix:/tmp/sock
|
||||
```
|
||||
|
||||
When the above commands completed, the VM should be successfully
|
||||
migrated to the destination machine without interrupting the workload.
|
||||
|
||||
### TCP Socket Migration
|
||||
|
||||
If TCP socket is selected for migration, we need to consider migrating
|
||||
in a trusted network.
|
||||
|
||||
#### Starting the Receiver VM
|
||||
|
||||
On the receiver side, we prepare an empty VM:
|
||||
|
||||
```console
|
||||
dst $ cloud-hypervisor --api-socket /tmp/api
|
||||
```
|
||||
|
||||
In a different terminal, prepare to receive the migration:
|
||||
|
||||
```console
|
||||
dst $ ch-remote --api-socket=/tmp/api receive-migration tcp:0.0.0.0:{port}
|
||||
```
|
||||
|
||||
#### Starting the Sender VM
|
||||
|
||||
Let's start the VM on the source machine:
|
||||
|
||||
```console
|
||||
src $ cloud-hypervisor \
|
||||
--serial tty --console off \
|
||||
--cpus boot=1 --memory size=512M \
|
||||
--kernel vmlinux \
|
||||
--cmdline "root=/dev/vda1 console=ttyS0" \
|
||||
--disk path=focal-2.raw path=focal-nested.raw path=tmp.img\
|
||||
--net ip=192.168.102.1
|
||||
--cpus boot=2 --memory size=4G \
|
||||
--kernel /var/images/linux \
|
||||
--initramfs /var/images/initrd \
|
||||
--cmdline "console=ttyS0" \
|
||||
--api-socket /tmp/api
|
||||
```
|
||||
|
||||
Launch the nested source VM (inside the guest OS of the VM 1) :
|
||||
```bash
|
||||
vm-1:~$ sudo ./cloud-hypervisor \
|
||||
--serial tty --console off \
|
||||
--memory size=128M \
|
||||
--kernel vmlinux \
|
||||
--cmdline "console=ttyS0 root=/dev/vda1" \
|
||||
--disk path=/dev/vdb path=/dev/vdc \
|
||||
--api-socket=/tmp/api1 \
|
||||
--net ip=192.168.100.1
|
||||
vm-1:~$ # setup the guest network if needed
|
||||
vm-1:~$ sudo ip addr add 192.168.101.2/24 dev ens4
|
||||
vm-1:~$ sudo ip link set up dev ens4
|
||||
vm-1:~$ sudo ip r add default via 192.168.101.1
|
||||
```
|
||||
Optional: Run the guest workload below (on the guest OS of the nested source VM),
|
||||
which performs intensive virtio-blk operations. Now the console of the nested
|
||||
source VM should repeatedly print `"equal"`, and our goal is migrating
|
||||
this VM and the running workload without interruption.
|
||||
```bash
|
||||
#/bin/bash
|
||||
After a few seconds the VM should be up and you can interact with it.
|
||||
|
||||
# On the guest OS of the nested source VM
|
||||
#### Performing the Migration
|
||||
|
||||
input="/dev/vdb"
|
||||
result=$(md5sum $input)
|
||||
tmp=$(md5sum $input)
|
||||
Initiate the Migration over TCP:
|
||||
|
||||
while [[ "$result" == "$tmp" ]]
|
||||
do
|
||||
echo "equal"
|
||||
tmp=$(md5sum $input)
|
||||
done
|
||||
|
||||
echo "not equal"
|
||||
echo "result = $result"
|
||||
echo "tmp = $tmp"
|
||||
```console
|
||||
src $ ch-remote --api-socket=/tmp/api send-migration tcp:{dst}:{port}
|
||||
```
|
||||
|
||||
Launch the nested destination VM (inside the guest OS of the VM 2):
|
||||
```bash
|
||||
vm-2:~$ sudo ./cloud-hypervisor --api-socket=/tmp/api2
|
||||
vm-2:~$ # setup the guest network with the following commands if needed
|
||||
vm-2:~$ sudo ip addr add 192.168.102.2/24 dev ens4
|
||||
vm-2:~$ sudo ip link set up dev ens4
|
||||
vm-2:~$ sudo ip r add default via 192.168.102.1
|
||||
vm-2:~$ ping 192.168.101.2 # This should succeed
|
||||
```
|
||||
> Note: If the above ping failed, please check the iptables rule on the
|
||||
> host machine, e.g. whether the policy for the `FORWARD` chain is set
|
||||
> to `DROP` (which is the default setting configured by Docker).
|
||||
> Replace {dst}:{port} with the actual IP address and port of your destination host.
|
||||
|
||||
Get ready for receiving migration for the nested destination VM (inside
|
||||
the guest OS of the VM 2):
|
||||
```bash
|
||||
vm-2:~$ sudo ./ch-remote --api-socket=/tmp/api2 receive-migration unix:/tmp/sock2
|
||||
vm-2:~$ sudo socat TCP-LISTEN:6000,reuseaddr UNIX-CLIENT:/tmp/sock2
|
||||
```
|
||||
|
||||
Start to send migration for the nested source VM (inside the guest OS of
|
||||
the VM 1):
|
||||
```bash
|
||||
vm-1:~$ sudo socat UNIX-LISTEN:/tmp/sock1,reuseaddr TCP:192.168.102.2:6000
|
||||
vm-1:~$ sudo ./ch-remote --api-socket=/tmp/api1 send-migration unix:/tmp/sock1
|
||||
```
|
||||
|
||||
When the above commands completed, the source VM should be successfully
|
||||
migrated to the destination VM without interrupting our testing guest
|
||||
workload. Now the destination VM is running the testing guest workload
|
||||
while the source VM is terminated gracefully.
|
||||
After completing the above commands, the source VM will be migrated to
|
||||
the destination host and continue running there. The source VM instance
|
||||
will terminate normally. All ongoing processes and connections within
|
||||
the VM should remain intact after the migration.
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
The target audience of this document is both:
|
||||
|
||||
* Developers who want to understand what log level to use and when,
|
||||
* Users who want to debug issues with running their workloads in Cloud Hypervisor
|
||||
- Developers who want to understand what log level to use and when,
|
||||
- Users who want to debug issues with running their workloads in Cloud Hypervisor
|
||||
|
||||
## Control
|
||||
|
||||
@@ -29,7 +29,6 @@ A typical example of where this level of message should be generated is during a
|
||||
|
||||
The user should investigate the meaning of this warning and take steps to ensure the correct functionality.
|
||||
|
||||
|
||||
### `info!()`
|
||||
|
||||
Use `-v` to enable.
|
||||
@@ -40,4 +39,4 @@ This level is for the benefit of developers. It should be used for sporadic and
|
||||
|
||||
Use `-vv` to enable.
|
||||
|
||||
For the most verbose of logging messages. It is acceptable to "spam" the log with repeated invocations of the same message. This level of logging would be combined with `--log-file`.
|
||||
For the most verbose of logging messages. It is acceptable to "spam" the log with repeated invocations of the same message. This level of logging would be combined with `--log-file`.
|
||||
|
||||
88
docs/riscv.md
Normal file
88
docs/riscv.md
Normal file
@@ -0,0 +1,88 @@
|
||||
# How to build and test Cloud Hypervisor on riscv64
|
||||
|
||||
This document introduces how to build and test Cloud Hypervisor on `riscv64`.
|
||||
All instructions here are tested with Ubuntu 24.04.2 as the host OS.
|
||||
|
||||
## Hardware requirements
|
||||
|
||||
- riscv64 servers (recommended) or development boards equipped with the AIA
|
||||
(Advance Interrupt Architecture) interrupt controller.
|
||||
|
||||
## Getting started
|
||||
|
||||
We create a folder to build and run Cloud Hypervisor at `$HOME/cloud-hypervisor`
|
||||
|
||||
```console
|
||||
export CLOUDH=$HOME/cloud-hypervisor
|
||||
mkdir $CLOUDH
|
||||
```
|
||||
|
||||
## Prerequisites
|
||||
|
||||
You need to install some prerequisite packages to build and test Cloud Hypervisor.
|
||||
|
||||
### Tools
|
||||
|
||||
```console
|
||||
# Install rust tool chain
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||
# Install the tools used for building guest kernel, EDK2 and converting guest disk
|
||||
sudo apt-get update
|
||||
sudo apt-get install git build-essential m4 bison flex uuid-dev qemu-utils
|
||||
```
|
||||
|
||||
### Building Cloud Hypervisor
|
||||
|
||||
```console
|
||||
pushd $CLOUDH
|
||||
git clone https://github.com/cloud-hypervisor/cloud-hypervisor.git
|
||||
cd cloud-hypervisor
|
||||
cargo build
|
||||
popd
|
||||
```
|
||||
|
||||
### Disk image
|
||||
|
||||
Download the Ubuntu cloud image and convert the image type.
|
||||
|
||||
```console
|
||||
pushd $CLOUDH
|
||||
wget https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-riscv64.img
|
||||
qemu-img convert -p -f qcow2 -O raw jammy-server-cloudimg-riscv64.img jammy-server-cloudimg-riscv64.raw
|
||||
popd
|
||||
```
|
||||
|
||||
## Direct-kernel booting
|
||||
|
||||
### Building kernel
|
||||
|
||||
```console
|
||||
pushd $CLOUDH
|
||||
git clone --depth 1 "https://github.com/cloud-hypervisor/linux.git" -b ch-6.12.8
|
||||
cd linux
|
||||
make ch_defconfig
|
||||
make -j `nproc`
|
||||
popd
|
||||
```
|
||||
|
||||
### Booting the guest VM
|
||||
|
||||
```console
|
||||
pushd $CLOUDH
|
||||
sudo $CLOUDH/cloud-hypervisor/target/debug/cloud-hypervisor \
|
||||
--kernel $CLOUDH/linux/arch/riscv64/boot/Image \
|
||||
--disk path=jammy-server-cloudimg-riscv64.raw \
|
||||
--cmdline "console=hvc0 root=/dev/vda rw" \
|
||||
--cpus boot=1 \
|
||||
--memory size=1024M \
|
||||
--seccomp false \
|
||||
--log-file boot.log -vv
|
||||
popd
|
||||
```
|
||||
|
||||
## Known limitations
|
||||
|
||||
- Direct kernel boot only
|
||||
- `64-bit Linux` guest OS only
|
||||
- For more details, see
|
||||
[here](https://github.com/cloud-hypervisor/cloud-hypervisor/issues/6978).
|
||||
@@ -5,8 +5,8 @@ name = "event_monitor"
|
||||
version = "0.1.0"
|
||||
|
||||
[dependencies]
|
||||
flume = "0.11.0"
|
||||
libc = "0.2.158"
|
||||
flume = "0.11.1"
|
||||
libc = "0.2.167"
|
||||
once_cell = "1.20.2"
|
||||
serde = { version = "1.0.208", features = ["derive", "rc"] }
|
||||
serde_json = "1.0.120"
|
||||
|
||||
256
fuzz/Cargo.lock
generated
256
fuzz/Cargo.lock
generated
@@ -1,13 +1,13 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "acpi_tables"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/rust-vmm/acpi_tables?branch=main#ca1a473fe73cdd8eb49c1449faad7aaac06f32c2"
|
||||
source = "git+https://github.com/rust-vmm/acpi_tables?branch=main#849d5950196f66dd10f2b2606d8fe8c7cb39ec24"
|
||||
dependencies = [
|
||||
"zerocopy",
|
||||
"zerocopy 0.7.35",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -61,15 +61,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.87"
|
||||
version = "1.0.95"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10f00e1f6e58a40e807377c75c6a7f97bf9044fab57816f2414e6f5f4499d7b8"
|
||||
checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04"
|
||||
|
||||
[[package]]
|
||||
name = "arbitrary"
|
||||
version = "1.3.2"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110"
|
||||
checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223"
|
||||
|
||||
[[package]]
|
||||
name = "arc-swap"
|
||||
@@ -89,7 +89,7 @@ dependencies = [
|
||||
"linux-loader",
|
||||
"log",
|
||||
"serde",
|
||||
"thiserror",
|
||||
"thiserror 2.0.9",
|
||||
"uuid",
|
||||
"vm-fdt",
|
||||
"vm-memory",
|
||||
@@ -126,7 +126,7 @@ dependencies = [
|
||||
"remain",
|
||||
"serde",
|
||||
"smallvec",
|
||||
"thiserror",
|
||||
"thiserror 2.0.9",
|
||||
"uuid",
|
||||
"virtio-bindings",
|
||||
"virtio-queue",
|
||||
@@ -195,13 +195,16 @@ checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97"
|
||||
name = "cloud-hypervisor-fuzz"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"arbitrary",
|
||||
"block",
|
||||
"devices",
|
||||
"epoll",
|
||||
"hypervisor",
|
||||
"libc",
|
||||
"libfuzzer-sys",
|
||||
"linux-loader",
|
||||
"micro_http",
|
||||
"mshv-bindings",
|
||||
"net_util",
|
||||
"once_cell",
|
||||
"seccompiler",
|
||||
@@ -297,7 +300,7 @@ dependencies = [
|
||||
"num_enum",
|
||||
"pci",
|
||||
"serde",
|
||||
"thiserror",
|
||||
"thiserror 2.0.9",
|
||||
"tpm",
|
||||
"vm-allocator",
|
||||
"vm-device",
|
||||
@@ -361,9 +364,9 @@ checksum = "784a4df722dc6267a04af36895398f59d21d07dce47232adf31ec0ff2fa45e67"
|
||||
|
||||
[[package]]
|
||||
name = "flume"
|
||||
version = "0.11.0"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181"
|
||||
checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
@@ -422,10 +425,22 @@ dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"wasi",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi 0.13.3+wasi-0.2.2",
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.5"
|
||||
@@ -437,21 +452,33 @@ name = "hypervisor"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"arc-swap",
|
||||
"byteorder",
|
||||
"cfg-if",
|
||||
"concat-idents",
|
||||
"iced-x86",
|
||||
"kvm-bindings",
|
||||
"kvm-ioctls",
|
||||
"libc",
|
||||
"log",
|
||||
"mshv-bindings",
|
||||
"serde",
|
||||
"serde_with",
|
||||
"thiserror",
|
||||
"thiserror 2.0.9",
|
||||
"vfio-ioctls",
|
||||
"vm-memory",
|
||||
"vmm-sys-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iced-x86"
|
||||
version = "1.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c447cff8c7f384a7d4f741cfcff32f75f3ad02b406432e8d6c878d56b1edf6b"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ident_case"
|
||||
version = "1.0.1"
|
||||
@@ -500,20 +527,20 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kvm-bindings"
|
||||
version = "0.9.1"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2efe3f1a4437bffe000e6297a593b98184213cd27486776c335f95ab53d48e3a"
|
||||
checksum = "fa4933174d0cc4b77b958578cd45784071cc5ae212c2d78fbd755aaaa6dfa71a"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"vmm-sys-util",
|
||||
"zerocopy",
|
||||
"zerocopy 0.7.35",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kvm-ioctls"
|
||||
version = "0.18.0"
|
||||
version = "0.19.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92c2176b91f68903b54ac8c6185bada7d607ca6110998976ff15c032f88a7d39"
|
||||
checksum = "e013ae7fcd2c6a8f384104d16afe7ea02969301ea2bb2a56e44b011ebc907cab"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"kvm-bindings",
|
||||
@@ -529,14 +556,20 @@ checksum = "18738c5d4c7fae6727a96adb94722ef7ce82f3eafea0a11777e258a93816537e"
|
||||
dependencies = [
|
||||
"enumflags2",
|
||||
"libc",
|
||||
"thiserror",
|
||||
"thiserror 1.0.64",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.158"
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.169"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
|
||||
|
||||
[[package]]
|
||||
name = "libfuzzer-sys"
|
||||
@@ -551,9 +584,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "linux-loader"
|
||||
version = "0.12.0"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d379d0089d0fbf4161c35a4fdfd76125923f1a93632c49195f5372b4c0b1472"
|
||||
checksum = "870c3814345f050991f99869417779f6062542bcf4ed81db7a1b926ad1306638"
|
||||
dependencies = [
|
||||
"vm-memory",
|
||||
]
|
||||
@@ -595,13 +628,27 @@ dependencies = [
|
||||
"vmm-sys-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mshv-bindings"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9c369385758f81ca937414dc2147737c92032e4fb399669f287abf94d89252d"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"num_enum",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"vmm-sys-util",
|
||||
"zerocopy 0.8.14",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nanorand"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"getrandom 0.2.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -616,13 +663,13 @@ name = "net_util"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"epoll",
|
||||
"getrandom",
|
||||
"getrandom 0.3.1",
|
||||
"libc",
|
||||
"log",
|
||||
"net_gen",
|
||||
"rate_limiter",
|
||||
"serde",
|
||||
"thiserror",
|
||||
"thiserror 2.0.9",
|
||||
"virtio-bindings",
|
||||
"virtio-queue",
|
||||
"vm-memory",
|
||||
@@ -662,9 +709,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.19.0"
|
||||
version = "1.20.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
||||
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
|
||||
|
||||
[[package]]
|
||||
name = "option_parser"
|
||||
@@ -686,7 +733,7 @@ dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"serde",
|
||||
"thiserror",
|
||||
"thiserror 2.0.9",
|
||||
"vfio-bindings",
|
||||
"vfio-ioctls",
|
||||
"vfio_user",
|
||||
@@ -703,7 +750,7 @@ version = "0.2.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
|
||||
dependencies = [
|
||||
"zerocopy",
|
||||
"zerocopy 0.7.35",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -717,9 +764,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.86"
|
||||
version = "1.0.92"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
|
||||
checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@@ -735,20 +782,20 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.5"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||
checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
"zerocopy 0.8.14",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.3.1"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
@@ -756,11 +803,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.4"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"getrandom 0.3.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -770,7 +817,7 @@ dependencies = [
|
||||
"epoll",
|
||||
"libc",
|
||||
"log",
|
||||
"thiserror",
|
||||
"thiserror 2.0.9",
|
||||
"vmm-sys-util",
|
||||
]
|
||||
|
||||
@@ -799,9 +846,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "seccompiler"
|
||||
version = "0.4.0"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "345a3e4dddf721a478089d4697b83c6c0a8f5bf16086f6c13397e4534eb6e2e5"
|
||||
checksum = "a4ae55de56877481d112a559bbc12667635fdaf5e005712fd4e2b2fa50ffc884"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
@@ -913,9 +960,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.77"
|
||||
version = "2.0.95"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed"
|
||||
checksum = "46f71c0377baf4ef1cc3e3402ded576dccc315800fbc62dfc7fe04b009773b4a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -928,7 +975,16 @@ version = "1.0.64"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
"thiserror-impl 1.0.64",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f072643fd0190df67a8bab670c20ef5d8737177d6ac6b2e9a236cb096206b2cc"
|
||||
dependencies = [
|
||||
"thiserror-impl 2.0.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -942,6 +998,17 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "2.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b50fa271071aae2e6ee85f842e2e28ba8cd2c5fb67f11fcb1fd70b276f9e7d4"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.8"
|
||||
@@ -968,7 +1035,7 @@ dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"net_gen",
|
||||
"thiserror",
|
||||
"thiserror 2.0.9",
|
||||
"vmm-sys-util",
|
||||
]
|
||||
|
||||
@@ -997,20 +1064,20 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.10.0"
|
||||
version = "1.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314"
|
||||
checksum = "e0f540e3240398cce6128b64ba83fdbdd86129c16a3aa1a3a252efd66eb3d587"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"getrandom 0.3.1",
|
||||
"rand",
|
||||
"uuid-macro-internal",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uuid-macro-internal"
|
||||
version = "1.10.0"
|
||||
version = "1.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee1cd046f83ea2c4e920d6ee9f7c3537ef928d75dce5d84a87c2c5d6b3999a3a"
|
||||
checksum = "9521621447c21497fac206ffe6e9f642f977c4f82eeba9201055f64884d9cb01"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1020,7 +1087,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "vfio-bindings"
|
||||
version = "0.4.0"
|
||||
source = "git+https://github.com/rust-vmm/vfio?branch=main#a51a4746b0d317bfc21fa49d40f9287f3b8137fd"
|
||||
source = "git+https://github.com/rust-vmm/vfio?branch=main#b135b8305c2cc8ec333e0cf77a780445cc98dcee"
|
||||
dependencies = [
|
||||
"vmm-sys-util",
|
||||
]
|
||||
@@ -1028,14 +1095,14 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "vfio-ioctls"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/rust-vmm/vfio?branch=main#a51a4746b0d317bfc21fa49d40f9287f3b8137fd"
|
||||
source = "git+https://github.com/rust-vmm/vfio?branch=main#b135b8305c2cc8ec333e0cf77a780445cc98dcee"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"kvm-bindings",
|
||||
"kvm-ioctls",
|
||||
"libc",
|
||||
"log",
|
||||
"thiserror",
|
||||
"thiserror 1.0.64",
|
||||
"vfio-bindings",
|
||||
"vm-memory",
|
||||
"vmm-sys-util",
|
||||
@@ -1044,7 +1111,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "vfio_user"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/rust-vmm/vfio-user?branch=main#bf7d7e851b604d8414a7960fb9137b59fc42421d"
|
||||
source = "git+https://github.com/rust-vmm/vfio-user?branch=main#3febcdd3fa2531623865663ca1721e1962ed9979"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"libc",
|
||||
@@ -1052,7 +1119,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"thiserror 1.0.64",
|
||||
"vfio-bindings",
|
||||
"vm-memory",
|
||||
"vmm-sys-util",
|
||||
@@ -1060,9 +1127,8 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "vhost"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c1c4c6c9f79fbe3150d9a403008ca416d34c489897effdda28b646f09900aad"
|
||||
version = "0.12.1"
|
||||
source = "git+https://github.com/rust-vmm/vhost?rev=d983ae0#d983ae07f78663b7d24059667376992460b571a2"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"libc",
|
||||
@@ -1073,9 +1139,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "virtio-bindings"
|
||||
version = "0.2.3"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68d0df4f5ad79b1dc81b5913ac737e24a84dcd5100f36ed953a1faec18aba241"
|
||||
checksum = "1711e61c00f8cb450bd15368152a1e37a12ef195008ddc7d0f4812f9e2b30a68"
|
||||
|
||||
[[package]]
|
||||
name = "virtio-devices"
|
||||
@@ -1098,7 +1164,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"serde_with",
|
||||
"serial_buffer",
|
||||
"thiserror",
|
||||
"thiserror 2.0.9",
|
||||
"vhost",
|
||||
"virtio-bindings",
|
||||
"virtio-queue",
|
||||
@@ -1112,9 +1178,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "virtio-queue"
|
||||
version = "0.13.0"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ffb1761348d3b5e82131379b9373435b48dc8333100bff3f1cdf9cc541a0ad83"
|
||||
checksum = "872e2f3fbd70a7e6f01689720cce3d5c2c5efe52b484dd07b674246ada0e9a8d"
|
||||
dependencies = [
|
||||
"log",
|
||||
"virtio-bindings",
|
||||
@@ -1138,7 +1204,7 @@ dependencies = [
|
||||
"anyhow",
|
||||
"hypervisor",
|
||||
"serde",
|
||||
"thiserror",
|
||||
"thiserror 2.0.9",
|
||||
"vfio-ioctls",
|
||||
"vm-memory",
|
||||
"vmm-sys-util",
|
||||
@@ -1147,17 +1213,17 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "vm-fdt"
|
||||
version = "0.3.0"
|
||||
source = "git+https://github.com/rust-vmm/vm-fdt?branch=main#982fb8d9c8cd7f53520d7e304b39ff307fa3a641"
|
||||
source = "git+https://github.com/rust-vmm/vm-fdt?branch=main#ef5bd734f5f66fb07722d766981adbc915f0d941"
|
||||
|
||||
[[package]]
|
||||
name = "vm-memory"
|
||||
version = "0.15.0"
|
||||
version = "0.16.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a320fc11792e063174402ff444aa3c80363cbf1e31c47b5ef74124406c334ce6"
|
||||
checksum = "f1720e7240cdc739f935456eb77f370d7e9b2a3909204da1e2b47bef1137a013"
|
||||
dependencies = [
|
||||
"arc-swap",
|
||||
"libc",
|
||||
"thiserror",
|
||||
"thiserror 1.0.64",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
@@ -1168,7 +1234,7 @@ dependencies = [
|
||||
"anyhow",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"thiserror 2.0.9",
|
||||
"vm-memory",
|
||||
]
|
||||
|
||||
@@ -1215,7 +1281,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"serial_buffer",
|
||||
"signal-hook",
|
||||
"thiserror",
|
||||
"thiserror 2.0.9",
|
||||
"tracer",
|
||||
"uuid",
|
||||
"vfio-ioctls",
|
||||
@@ -1228,7 +1294,7 @@ dependencies = [
|
||||
"vm-migration",
|
||||
"vm-virtio",
|
||||
"vmm-sys-util",
|
||||
"zerocopy",
|
||||
"zerocopy 0.7.35",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1249,6 +1315,15 @@ version = "0.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.13.3+wasi-0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2"
|
||||
dependencies = [
|
||||
"wit-bindgen-rt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.93"
|
||||
@@ -1408,6 +1483,15 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rt"
|
||||
version = "0.33.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.7.35"
|
||||
@@ -1415,7 +1499,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"zerocopy-derive",
|
||||
"zerocopy-derive 0.7.35",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a367f292d93d4eab890745e75a778da40909cab4d6ff8173693812f79c4a2468"
|
||||
dependencies = [
|
||||
"zerocopy-derive 0.8.14",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1428,3 +1521,14 @@ dependencies = [
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3931cb58c62c13adec22e38686b559c86a30565e16ad6e8510a337cedc611e1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
@@ -9,24 +9,29 @@ version = "0.0.0"
|
||||
cargo-fuzz = true
|
||||
|
||||
[features]
|
||||
default = ["mshv_emulator"]
|
||||
igvm = []
|
||||
mshv_emulator = ["hypervisor/mshv_emulator"]
|
||||
pvmemcontrol = []
|
||||
|
||||
[dependencies]
|
||||
arbitrary = "1.4.1"
|
||||
block = { path = "../block" }
|
||||
devices = { path = "../devices" }
|
||||
epoll = "4.3.3"
|
||||
hypervisor = { path = "../hypervisor", features = ["mshv_emulator"] }
|
||||
libc = "0.2.155"
|
||||
libfuzzer-sys = "0.4.7"
|
||||
linux-loader = { version = "0.12.0", features = ["bzimage", "elf", "pe"] }
|
||||
linux-loader = { version = "0.13.0", features = ["bzimage", "elf", "pe"] }
|
||||
micro_http = { git = "https://github.com/firecracker-microvm/micro-http", branch = "main" }
|
||||
mshv-bindings = "0.3.2"
|
||||
net_util = { path = "../net_util" }
|
||||
once_cell = "1.19.0"
|
||||
seccompiler = "0.4.0"
|
||||
seccompiler = "0.5.0"
|
||||
virtio-devices = { path = "../virtio-devices" }
|
||||
virtio-queue = "0.13.0"
|
||||
virtio-queue = "0.14.0"
|
||||
vm-device = { path = "../vm-device" }
|
||||
vm-memory = "0.15.0"
|
||||
vm-memory = "0.16.0"
|
||||
vm-migration = { path = "../vm-migration" }
|
||||
vm-virtio = { path = "../vm-virtio" }
|
||||
vmm = { path = "../vmm", features = ["guest_debug"] }
|
||||
@@ -126,8 +131,21 @@ name = "vhdx"
|
||||
path = "fuzz_targets/vhdx.rs"
|
||||
test = false
|
||||
|
||||
[[bin]]
|
||||
doc = false
|
||||
name = "vsock"
|
||||
path = "fuzz_targets/vsock.rs"
|
||||
test = false
|
||||
|
||||
[[bin]]
|
||||
doc = false
|
||||
name = "watchdog"
|
||||
path = "fuzz_targets/watchdog.rs"
|
||||
test = false
|
||||
|
||||
[[bin]]
|
||||
doc = false
|
||||
name = "x86emul"
|
||||
path = "fuzz_targets/x86emul.rs"
|
||||
required-features = ["mshv_emulator"]
|
||||
test = false
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
use std::os::unix::io::{AsRawFd, FromRawFd};
|
||||
use std::sync::Arc;
|
||||
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use libfuzzer_sys::{fuzz_target, Corpus};
|
||||
use seccompiler::SeccompAction;
|
||||
use virtio_devices::{VirtioDevice, VirtioInterrupt, VirtioInterruptType};
|
||||
use virtio_queue::{Queue, QueueT};
|
||||
@@ -37,11 +37,11 @@ const AVAIL_RING_SIZE: u64 = 6_u64 + 2 * QUEUE_SIZE as u64;
|
||||
// Used ring size
|
||||
const USED_RING_SIZE: u64 = 6_u64 + 8 * QUEUE_SIZE as u64;
|
||||
|
||||
fuzz_target!(|bytes| {
|
||||
fuzz_target!(|bytes: &[u8]| -> Corpus {
|
||||
if bytes.len() < QUEUE_DATA_SIZE * QUEUE_NUM
|
||||
|| bytes.len() > (QUEUE_DATA_SIZE * QUEUE_NUM + MEM_SIZE)
|
||||
{
|
||||
return;
|
||||
return Corpus::Reject;
|
||||
}
|
||||
|
||||
let mut balloon = virtio_devices::Balloon::new(
|
||||
@@ -61,7 +61,7 @@ fuzz_target!(|bytes| {
|
||||
// Setup the guest memory with the input bytes
|
||||
let mem = GuestMemoryMmap::from_ranges(&[(GuestAddress(0), MEM_SIZE)]).unwrap();
|
||||
if mem.write_slice(mem_bytes, GuestAddress(0 as u64)).is_err() {
|
||||
return;
|
||||
return Corpus::Reject;
|
||||
}
|
||||
let guest_memory = GuestMemoryAtomic::new(mem);
|
||||
|
||||
@@ -108,6 +108,8 @@ fuzz_target!(|bytes| {
|
||||
|
||||
// Wait for the events to finish and balloon device worker thread to return
|
||||
balloon.wait_for_epoll_threads();
|
||||
|
||||
Corpus::Keep
|
||||
});
|
||||
|
||||
pub struct NoopVirtioInterrupt {}
|
||||
|
||||
@@ -17,7 +17,7 @@ use std::{ffi, io};
|
||||
|
||||
use block::async_io::DiskFile;
|
||||
use block::raw_sync::RawFileDiskSync;
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use libfuzzer_sys::{fuzz_target, Corpus};
|
||||
use seccompiler::SeccompAction;
|
||||
use virtio_devices::{Block, VirtioDevice, VirtioInterrupt, VirtioInterruptType};
|
||||
use virtio_queue::{Queue, QueueT};
|
||||
@@ -40,9 +40,9 @@ const AVAIL_RING_SIZE: u64 = 6_u64 + 2 * QUEUE_SIZE as u64;
|
||||
// Guest physical address for used ring (requires to 4-bytes aligned)
|
||||
const USED_RING_ADDR: u64 = (AVAIL_RING_ADDR + AVAIL_RING_SIZE + 3) & !3_u64;
|
||||
|
||||
fuzz_target!(|bytes| {
|
||||
fuzz_target!(|bytes: &[u8]| -> Corpus {
|
||||
if bytes.len() < QUEUE_DATA_SIZE || bytes.len() > (QUEUE_DATA_SIZE + MEM_SIZE) {
|
||||
return;
|
||||
return Corpus::Reject;
|
||||
}
|
||||
|
||||
let queue_data = &bytes[..QUEUE_DATA_SIZE];
|
||||
@@ -76,7 +76,7 @@ fuzz_target!(|bytes| {
|
||||
// Setup the guest memory with the input bytes
|
||||
let mem = GuestMemoryMmap::from_ranges(&[(GuestAddress(0), MEM_SIZE)]).unwrap();
|
||||
if mem.write_slice(mem_bytes, GuestAddress(0 as u64)).is_err() {
|
||||
return;
|
||||
return Corpus::Reject;
|
||||
}
|
||||
let guest_memory = GuestMemoryAtomic::new(mem);
|
||||
|
||||
@@ -96,6 +96,8 @@ fuzz_target!(|bytes| {
|
||||
|
||||
// Wait for the events to finish and block device worker thread to return
|
||||
block.wait_for_epoll_threads();
|
||||
|
||||
Corpus::Keep
|
||||
});
|
||||
|
||||
fn memfd_create(name: &ffi::CStr, flags: u32) -> Result<RawFd, io::Error> {
|
||||
|
||||
@@ -5,14 +5,14 @@
|
||||
#![no_main]
|
||||
use devices::legacy::Cmos;
|
||||
use libc::EFD_NONBLOCK;
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use libfuzzer_sys::{fuzz_target, Corpus};
|
||||
use vm_device::BusDevice;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
fuzz_target!(|bytes| {
|
||||
fuzz_target!(|bytes: &[u8]| -> Corpus {
|
||||
// Need at least 16 bytes for the test
|
||||
if bytes.len() < 16 {
|
||||
return;
|
||||
return Corpus::Reject;
|
||||
}
|
||||
|
||||
let mut below_4g = [0u8; 8];
|
||||
@@ -46,4 +46,6 @@ fuzz_target!(|bytes| {
|
||||
cmos.write(0, offset, &data);
|
||||
}
|
||||
}
|
||||
|
||||
Corpus::Keep
|
||||
});
|
||||
|
||||
@@ -9,7 +9,7 @@ use std::io::Write;
|
||||
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
|
||||
use std::sync::Arc;
|
||||
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use libfuzzer_sys::{fuzz_target, Corpus};
|
||||
use seccompiler::SeccompAction;
|
||||
use virtio_devices::{VirtioDevice, VirtioInterrupt, VirtioInterruptType};
|
||||
use virtio_queue::{Queue, QueueT};
|
||||
@@ -54,12 +54,12 @@ const QUEUE_BYTES_SIZE: usize = align!(
|
||||
DESC_TABLE_ALIGN_SIZE
|
||||
) as usize;
|
||||
|
||||
fuzz_target!(|bytes| {
|
||||
fuzz_target!(|bytes: &[u8]| -> Corpus {
|
||||
if bytes.len() < (QUEUE_DATA_SIZE + QUEUE_BYTES_SIZE) * QUEUE_NUM + CONSOLE_INPUT_SIZE
|
||||
|| bytes.len()
|
||||
> (QUEUE_DATA_SIZE + QUEUE_BYTES_SIZE) * QUEUE_NUM + CONSOLE_INPUT_SIZE + MEM_SIZE
|
||||
{
|
||||
return;
|
||||
return Corpus::Reject;
|
||||
}
|
||||
|
||||
let (pipe_rx, mut pipe_tx) = create_pipe().unwrap();
|
||||
@@ -108,10 +108,10 @@ fuzz_target!(|bytes| {
|
||||
.write_slice(queue_bytes, GuestAddress(BASE_VIRT_QUEUE_ADDR))
|
||||
.is_err()
|
||||
{
|
||||
return;
|
||||
return Corpus::Reject;
|
||||
}
|
||||
if mem.write_slice(mem_bytes, GuestAddress(0 as u64)).is_err() {
|
||||
return;
|
||||
return Corpus::Reject;
|
||||
}
|
||||
let guest_memory = GuestMemoryAtomic::new(mem);
|
||||
|
||||
@@ -137,6 +137,8 @@ fuzz_target!(|bytes| {
|
||||
|
||||
// Wait for the events to finish and console device worker thread to return
|
||||
console.wait_for_epoll_threads();
|
||||
|
||||
Corpus::Keep
|
||||
});
|
||||
|
||||
pub struct NoopVirtioInterrupt {}
|
||||
|
||||
@@ -8,7 +8,7 @@ use std::path::PathBuf;
|
||||
use std::sync::mpsc::{channel, Receiver};
|
||||
use std::thread;
|
||||
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use libfuzzer_sys::{fuzz_target, Corpus};
|
||||
use micro_http::Request;
|
||||
use once_cell::sync::Lazy;
|
||||
use vm_migration::MigratableError;
|
||||
@@ -27,9 +27,9 @@ use vmm_sys_util::eventfd::EventFd;
|
||||
static ROUTES: Lazy<Vec<&Box<dyn EndpointHandler + Sync + Send>>> =
|
||||
Lazy::new(|| HTTP_ROUTES.routes.values().collect());
|
||||
|
||||
fuzz_target!(|bytes| {
|
||||
fuzz_target!(|bytes: &[u8]| -> Corpus {
|
||||
if bytes.len() < 2 {
|
||||
return;
|
||||
return Corpus::Reject;
|
||||
}
|
||||
|
||||
let route = ROUTES[bytes[0] as usize % ROUTES.len()];
|
||||
@@ -53,6 +53,8 @@ fuzz_target!(|bytes| {
|
||||
exit_evt.write(1).ok();
|
||||
http_receiver_thread.join().unwrap();
|
||||
};
|
||||
|
||||
Corpus::Keep
|
||||
});
|
||||
|
||||
fn generate_request(bytes: &[u8]) -> Option<Request> {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
use std::os::unix::io::{AsRawFd, FromRawFd};
|
||||
use std::sync::Arc;
|
||||
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use libfuzzer_sys::{fuzz_target, Corpus};
|
||||
use seccompiler::SeccompAction;
|
||||
use virtio_devices::{VirtioDevice, VirtioInterrupt, VirtioInterruptType};
|
||||
use virtio_queue::{Queue, QueueT};
|
||||
@@ -54,11 +54,11 @@ const USED_RING_ADDR: u64 = align!(AVAIL_RING_ADDR + AVAIL_RING_SIZE, USED_RING_
|
||||
// Virtio-queue size in bytes
|
||||
const QUEUE_BYTES_SIZE: usize = (USED_RING_ADDR + USED_RING_SIZE - DESC_TABLE_ADDR) as usize;
|
||||
|
||||
fuzz_target!(|bytes| {
|
||||
fuzz_target!(|bytes: &[u8]| -> Corpus {
|
||||
if bytes.len() < (QUEUE_DATA_SIZE + QUEUE_BYTES_SIZE)
|
||||
|| bytes.len() > (QUEUE_DATA_SIZE + QUEUE_BYTES_SIZE + MEM_SIZE)
|
||||
{
|
||||
return;
|
||||
return Corpus::Reject;
|
||||
}
|
||||
|
||||
let (mut iommu, _) = virtio_devices::Iommu::new(
|
||||
@@ -66,6 +66,7 @@ fuzz_target!(|bytes| {
|
||||
SeccompAction::Allow,
|
||||
EventFd::new(EFD_NONBLOCK).unwrap(),
|
||||
((MEM_SIZE - IOVA_SPACE_SIZE) as u64, (MEM_SIZE - 1) as u64),
|
||||
64,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
@@ -91,10 +92,10 @@ fuzz_target!(|bytes| {
|
||||
.write_slice(queue_bytes, GuestAddress(DESC_TABLE_ADDR))
|
||||
.is_err()
|
||||
{
|
||||
return;
|
||||
return Corpus::Reject;
|
||||
}
|
||||
if mem.write_slice(mem_bytes, GuestAddress(0 as u64)).is_err() {
|
||||
return;
|
||||
return Corpus::Reject;
|
||||
}
|
||||
let guest_memory = GuestMemoryAtomic::new(mem);
|
||||
|
||||
@@ -118,6 +119,8 @@ fuzz_target!(|bytes| {
|
||||
|
||||
// Wait for the events to finish and vIOMMU device worker thread to return
|
||||
iommu.wait_for_epoll_threads();
|
||||
|
||||
Corpus::Keep
|
||||
});
|
||||
|
||||
pub struct NoopVirtioInterrupt {}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
#![no_main]
|
||||
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use libfuzzer_sys::{fuzz_target, Corpus};
|
||||
use vm_memory::bitmap::AtomicBitmap;
|
||||
use vm_memory::GuestAddress;
|
||||
|
||||
@@ -18,7 +18,7 @@ const MEM_SIZE: usize = 256 * 1024 * 1024;
|
||||
// From 'arch::x86_64::layout::CMDLINE_START'
|
||||
const CMDLINE_START: GuestAddress = GuestAddress(0x20000);
|
||||
|
||||
fuzz_target!(|bytes| {
|
||||
fuzz_target!(|bytes: &[u8]| -> Corpus {
|
||||
let payload_config = vmm::vm_config::PayloadConfig {
|
||||
firmware: None,
|
||||
kernel: None,
|
||||
@@ -29,9 +29,11 @@ fuzz_target!(|bytes| {
|
||||
};
|
||||
let kernel_cmdline = match vmm::vm::Vm::generate_cmdline(&payload_config) {
|
||||
Ok(cmdline) => cmdline,
|
||||
_ => return,
|
||||
_ => return Corpus::Reject,
|
||||
};
|
||||
let guest_memory = GuestMemoryMmap::from_ranges(&[(GuestAddress(0), MEM_SIZE)]).unwrap();
|
||||
|
||||
linux_loader::loader::load_cmdline(&guest_memory, CMDLINE_START, &kernel_cmdline).ok();
|
||||
|
||||
Corpus::Keep
|
||||
});
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
use std::os::unix::io::{AsRawFd, FromRawFd};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use libfuzzer_sys::{fuzz_target, Corpus};
|
||||
use seccompiler::SeccompAction;
|
||||
use virtio_devices::{BlocksState, Mem, VirtioDevice, VirtioInterrupt, VirtioInterruptType};
|
||||
use virtio_queue::{Queue, QueueT};
|
||||
@@ -57,11 +57,11 @@ const USED_RING_ADDR: u64 = align!(AVAIL_RING_ADDR + AVAIL_RING_SIZE, USED_RING_
|
||||
// Virtio-queue size in bytes
|
||||
const QUEUE_BYTES_SIZE: usize = (USED_RING_ADDR + USED_RING_SIZE - DESC_TABLE_ADDR) as usize;
|
||||
|
||||
fuzz_target!(|bytes| {
|
||||
fuzz_target!(|bytes: &[u8]| -> Corpus {
|
||||
if bytes.len() < VIRTIO_MEM_DATA_SIZE + QUEUE_DATA_SIZE + QUEUE_BYTES_SIZE
|
||||
|| bytes.len() > (VIRTIO_MEM_DATA_SIZE + QUEUE_DATA_SIZE + QUEUE_BYTES_SIZE + MEM_SIZE)
|
||||
{
|
||||
return;
|
||||
return Corpus::Reject;
|
||||
}
|
||||
|
||||
let virtio_mem_data = &bytes[..VIRTIO_MEM_DATA_SIZE];
|
||||
@@ -86,7 +86,7 @@ fuzz_target!(|bytes| {
|
||||
.write_slice(queue_bytes, GuestAddress(DESC_TABLE_ADDR))
|
||||
.is_err()
|
||||
{
|
||||
return;
|
||||
return Corpus::Reject;
|
||||
}
|
||||
// Add the memory region for the virtio-mem device
|
||||
let mem = mem.insert_region(virtio_mem_region).unwrap();
|
||||
@@ -94,7 +94,7 @@ fuzz_target!(|bytes| {
|
||||
.write_slice(mem_bytes, GuestAddress(VIRTIO_MEM_REGION_ADDRESS))
|
||||
.is_err()
|
||||
{
|
||||
return;
|
||||
return Corpus::Reject;
|
||||
}
|
||||
let guest_memory = GuestMemoryAtomic::new(mem);
|
||||
|
||||
@@ -114,6 +114,8 @@ fuzz_target!(|bytes| {
|
||||
|
||||
// Wait for the events to finish and virtio-mem device worker thread to return
|
||||
virtio_mem.wait_for_epoll_threads();
|
||||
|
||||
return Corpus::Keep;
|
||||
});
|
||||
|
||||
pub struct NoopVirtioInterrupt {}
|
||||
|
||||
@@ -9,7 +9,7 @@ use std::io::{Read, Write};
|
||||
use std::os::unix::io::{AsRawFd, FromRawFd};
|
||||
use std::sync::Arc;
|
||||
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use libfuzzer_sys::{fuzz_target, Corpus};
|
||||
use seccompiler::SeccompAction;
|
||||
use virtio_devices::{VirtioDevice, VirtioInterrupt, VirtioInterruptType};
|
||||
use virtio_queue::{Queue, QueueT};
|
||||
@@ -55,12 +55,12 @@ const QUEUE_BYTES_SIZE: usize = align!(
|
||||
DESC_TABLE_ALIGN_SIZE
|
||||
) as usize;
|
||||
|
||||
fuzz_target!(|bytes| {
|
||||
fuzz_target!(|bytes: &[u8]| -> Corpus {
|
||||
if bytes.len() < TAP_INPUT_SIZE + (QUEUE_DATA_SIZE + QUEUE_BYTES_SIZE) * QUEUE_NUM
|
||||
|| bytes.len()
|
||||
> TAP_INPUT_SIZE + (QUEUE_DATA_SIZE + QUEUE_BYTES_SIZE) * QUEUE_NUM + MEM_SIZE
|
||||
{
|
||||
return;
|
||||
return Corpus::Reject;
|
||||
}
|
||||
|
||||
let (dummy_tap_frontend, dummy_tap_backend) = create_socketpair().unwrap();
|
||||
@@ -111,10 +111,10 @@ fuzz_target!(|bytes| {
|
||||
.write_slice(queue_bytes, GuestAddress(BASE_VIRT_QUEUE_ADDR))
|
||||
.is_err()
|
||||
{
|
||||
return;
|
||||
return Corpus::Reject;
|
||||
}
|
||||
if mem.write_slice(mem_bytes, GuestAddress(0 as u64)).is_err() {
|
||||
return;
|
||||
return Corpus::Reject;
|
||||
}
|
||||
let guest_memory = GuestMemoryAtomic::new(mem);
|
||||
|
||||
@@ -155,6 +155,8 @@ fuzz_target!(|bytes| {
|
||||
// Terminate the thread for the dummy tap backend
|
||||
exit_evt.write(1).ok();
|
||||
tap_backend_thread.join().unwrap();
|
||||
|
||||
return Corpus::Keep;
|
||||
});
|
||||
|
||||
pub struct NoopVirtioInterrupt {}
|
||||
|
||||
@@ -10,7 +10,7 @@ use std::sync::Arc;
|
||||
use std::{ffi, io};
|
||||
|
||||
use libc::{MAP_NORESERVE, MAP_PRIVATE, PROT_READ, PROT_WRITE};
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use libfuzzer_sys::{fuzz_target, Corpus};
|
||||
use seccompiler::SeccompAction;
|
||||
use virtio_devices::{Pmem, UserspaceMapping, VirtioDevice, VirtioInterrupt, VirtioInterruptType};
|
||||
use virtio_queue::{Queue, QueueT};
|
||||
@@ -35,9 +35,9 @@ const AVAIL_RING_SIZE: u64 = 6_u64 + 2 * QUEUE_SIZE as u64;
|
||||
// Guest physical address for used ring (requires to 4-bytes aligned)
|
||||
const USED_RING_ADDR: u64 = (AVAIL_RING_ADDR + AVAIL_RING_SIZE + 3) & !3_u64;
|
||||
|
||||
fuzz_target!(|bytes| {
|
||||
fuzz_target!(|bytes: &[u8]| -> Corpus {
|
||||
if bytes.len() < QUEUE_DATA_SIZE || bytes.len() > (QUEUE_DATA_SIZE + MEM_SIZE) {
|
||||
return;
|
||||
return Corpus::Reject;
|
||||
}
|
||||
|
||||
let mut pmem = create_dummy_pmem();
|
||||
@@ -50,7 +50,7 @@ fuzz_target!(|bytes| {
|
||||
// Setup the guest memory with the input bytes
|
||||
let mem = GuestMemoryMmap::from_ranges(&[(GuestAddress(0), MEM_SIZE)]).unwrap();
|
||||
if mem.write_slice(mem_bytes, GuestAddress(0 as u64)).is_err() {
|
||||
return;
|
||||
return Corpus::Reject;
|
||||
}
|
||||
let guest_memory = GuestMemoryAtomic::new(mem);
|
||||
|
||||
@@ -69,6 +69,8 @@ fuzz_target!(|bytes| {
|
||||
|
||||
// Wait for the events to finish and pmem device worker thread to return
|
||||
pmem.wait_for_epoll_threads();
|
||||
|
||||
Corpus::Keep
|
||||
});
|
||||
|
||||
fn memfd_create_with_size(name: &ffi::CStr, flags: u32, size: usize) -> Result<RawFd, io::Error> {
|
||||
|
||||
@@ -12,14 +12,14 @@ use std::mem::size_of;
|
||||
use std::os::unix::io::{FromRawFd, RawFd};
|
||||
|
||||
use block::qcow::{QcowFile, RawFile};
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use libfuzzer_sys::{fuzz_target, Corpus};
|
||||
|
||||
// Take the first 64 bits of data as an address and the next 64 bits as data to
|
||||
// store there. The rest of the data is used as a qcow image.
|
||||
fuzz_target!(|bytes| {
|
||||
fuzz_target!(|bytes: &[u8]| -> Corpus {
|
||||
if bytes.len() < 16 {
|
||||
// Need an address and data, each are 8 bytes.
|
||||
return;
|
||||
return Corpus::Reject;
|
||||
}
|
||||
let mut disk_image = Cursor::new(bytes);
|
||||
let addr = read_u64(&mut disk_image);
|
||||
@@ -33,6 +33,8 @@ fuzz_target!(|bytes| {
|
||||
let _ = qcow.write_all(&value.to_le_bytes());
|
||||
}
|
||||
}
|
||||
|
||||
Corpus::Keep
|
||||
});
|
||||
|
||||
fn read_u64<T: Read>(readable: &mut T) -> u64 {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
use std::os::unix::io::{AsRawFd, FromRawFd};
|
||||
use std::sync::Arc;
|
||||
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use libfuzzer_sys::{fuzz_target, Corpus};
|
||||
use seccompiler::SeccompAction;
|
||||
use virtio_devices::{VirtioDevice, VirtioInterrupt, VirtioInterruptType};
|
||||
use virtio_queue::{Queue, QueueT};
|
||||
@@ -52,11 +52,11 @@ const USED_RING_ADDR: u64 = align!(AVAIL_RING_ADDR + AVAIL_RING_SIZE, USED_RING_
|
||||
// Virtio-queue size in bytes
|
||||
const QUEUE_BYTES_SIZE: usize = (USED_RING_ADDR + USED_RING_SIZE - DESC_TABLE_ADDR) as usize;
|
||||
|
||||
fuzz_target!(|bytes| {
|
||||
fuzz_target!(|bytes: &[u8]| -> Corpus {
|
||||
if bytes.len() < (QUEUE_DATA_SIZE + QUEUE_BYTES_SIZE)
|
||||
|| bytes.len() > (QUEUE_DATA_SIZE + QUEUE_BYTES_SIZE + MEM_SIZE)
|
||||
{
|
||||
return;
|
||||
return Corpus::Reject;
|
||||
}
|
||||
|
||||
let mut rng = virtio_devices::Rng::new(
|
||||
@@ -86,10 +86,10 @@ fuzz_target!(|bytes| {
|
||||
.write_slice(queue_bytes, GuestAddress(DESC_TABLE_ADDR))
|
||||
.is_err()
|
||||
{
|
||||
return;
|
||||
return Corpus::Reject;
|
||||
}
|
||||
if mem.write_slice(mem_bytes, GuestAddress(0 as u64)).is_err() {
|
||||
return;
|
||||
return Corpus::Reject;
|
||||
}
|
||||
let guest_memory = GuestMemoryAtomic::new(mem);
|
||||
|
||||
@@ -108,6 +108,8 @@ fuzz_target!(|bytes| {
|
||||
|
||||
// Wait for the events to finish and rng device worker thread to return
|
||||
rng.wait_for_epoll_threads();
|
||||
|
||||
Corpus::Keep
|
||||
});
|
||||
|
||||
pub struct NoopVirtioInterrupt {}
|
||||
|
||||
@@ -9,38 +9,47 @@ use std::io::{self, Read, Seek, SeekFrom, Write};
|
||||
use std::os::unix::io::{FromRawFd, RawFd};
|
||||
|
||||
use block::vhdx::Vhdx;
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use libfuzzer_sys::{fuzz_target, Corpus};
|
||||
|
||||
// Populate the corpus directory with a test file:
|
||||
// truncate -s 16M /tmp/source
|
||||
// qemu-img convert -O vhdx /tmp/source fuzz/corpus/vhdx/test.vhdx
|
||||
// Run with:
|
||||
// cargo fuzz run vhdx -j 32 -- -max_len=16777216
|
||||
fuzz_target!(|bytes| {
|
||||
fuzz_target!(|bytes: &[u8]| -> Corpus {
|
||||
let shm = memfd_create(&ffi::CString::new("fuzz").unwrap(), 0).unwrap();
|
||||
let mut disk_file: File = unsafe { File::from_raw_fd(shm) };
|
||||
disk_file.write_all(&bytes[..]).unwrap();
|
||||
disk_file.seek(SeekFrom::Start(0)).unwrap();
|
||||
|
||||
if let Ok(mut vhdx) = Vhdx::new(disk_file) {
|
||||
if vhdx.seek(SeekFrom::Start(0)).is_ok() {
|
||||
let mut offset = 0;
|
||||
while offset < bytes.len() {
|
||||
let mut data = vec![0; 8192];
|
||||
vhdx.read_exact(&mut data).ok();
|
||||
offset += data.len();
|
||||
}
|
||||
}
|
||||
let mut vhdx = match Vhdx::new(disk_file) {
|
||||
Ok(vhdx) => vhdx,
|
||||
Err(_) => return Corpus::Reject,
|
||||
};
|
||||
|
||||
if vhdx.seek(SeekFrom::Start(0)).is_ok() {
|
||||
let mut offset = 0;
|
||||
while offset < bytes.len() {
|
||||
let data = vec![0; 8192];
|
||||
vhdx.write_all(&data).ok();
|
||||
offset += data.len();
|
||||
}
|
||||
}
|
||||
if matches!(vhdx.seek(SeekFrom::Start(0)).is_err(), true) {
|
||||
return Corpus::Reject;
|
||||
};
|
||||
|
||||
let mut offset = 0;
|
||||
while offset < bytes.len() {
|
||||
let mut data = vec![0; 8192];
|
||||
vhdx.read_exact(&mut data).ok();
|
||||
offset += data.len();
|
||||
}
|
||||
|
||||
if matches!(vhdx.seek(SeekFrom::Start(0)).is_err(), true) {
|
||||
return Corpus::Reject;
|
||||
};
|
||||
|
||||
offset = 0;
|
||||
while offset < bytes.len() {
|
||||
let data = vec![0; 8192];
|
||||
vhdx.write_all(&data).ok();
|
||||
offset += data.len();
|
||||
}
|
||||
|
||||
Corpus::Keep
|
||||
});
|
||||
|
||||
fn memfd_create(name: &ffi::CStr, flags: u32) -> Result<RawFd, io::Error> {
|
||||
|
||||
148
fuzz/fuzz_targets/vsock.rs
Normal file
148
fuzz/fuzz_targets/vsock.rs
Normal file
@@ -0,0 +1,148 @@
|
||||
// Copyright © 2025 Microsoft
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#![no_main]
|
||||
|
||||
use std::os::unix::io::{AsRawFd, FromRawFd};
|
||||
use std::sync::Arc;
|
||||
|
||||
use libfuzzer_sys::{fuzz_target, Corpus};
|
||||
use seccompiler::SeccompAction;
|
||||
use virtio_devices::vsock::tests::TestBackend;
|
||||
use virtio_devices::{VirtioDevice, VirtioInterrupt, VirtioInterruptType};
|
||||
use virtio_queue::{Queue, QueueT};
|
||||
use vm_memory::bitmap::AtomicBitmap;
|
||||
use vm_memory::{Bytes, GuestAddress, GuestMemoryAtomic};
|
||||
use vmm_sys_util::eventfd::{EventFd, EFD_NONBLOCK};
|
||||
use vmm_sys_util::tempdir::TempDir;
|
||||
|
||||
type GuestMemoryMmap = vm_memory::GuestMemoryMmap<AtomicBitmap>;
|
||||
|
||||
macro_rules! align {
|
||||
($n:expr, $align:expr) => {{
|
||||
$n.div_ceil($align) * $align
|
||||
}};
|
||||
}
|
||||
|
||||
const QUEUE_DATA_SIZE: usize = 4;
|
||||
const MEM_SIZE: usize = 1 * 1024 * 1024;
|
||||
|
||||
// Max entries in the queue.
|
||||
const QUEUE_SIZE: u16 = 256;
|
||||
// Descriptor table alignment
|
||||
const DESC_TABLE_ALIGN_SIZE: u64 = 16;
|
||||
// Available ring alignment
|
||||
const AVAIL_RING_ALIGN_SIZE: u64 = 2;
|
||||
// Used ring alignment
|
||||
const USED_RING_ALIGN_SIZE: u64 = 4;
|
||||
// Descriptor table size
|
||||
const DESC_TABLE_SIZE: u64 = 16_u64 * QUEUE_SIZE as u64;
|
||||
// Available ring size
|
||||
const AVAIL_RING_SIZE: u64 = 6_u64 + 2 * QUEUE_SIZE as u64;
|
||||
// Used ring size
|
||||
const USED_RING_SIZE: u64 = 6_u64 + 8 * QUEUE_SIZE as u64;
|
||||
|
||||
// Guest memory gap
|
||||
const GUEST_MEM_GAP: u64 = 1 * 1024 * 1024;
|
||||
// Guest physical address for descriptor table.
|
||||
const DESC_TABLE_ADDR: u64 = align!(MEM_SIZE as u64 + GUEST_MEM_GAP, DESC_TABLE_ALIGN_SIZE);
|
||||
// Guest physical address for available ring
|
||||
const AVAIL_RING_ADDR: u64 = align!(DESC_TABLE_ADDR + DESC_TABLE_SIZE, AVAIL_RING_ALIGN_SIZE);
|
||||
// Guest physical address for used ring
|
||||
const USED_RING_ADDR: u64 = align!(AVAIL_RING_ADDR + AVAIL_RING_SIZE, USED_RING_ALIGN_SIZE);
|
||||
// Virtio-queue size in bytes
|
||||
const QUEUE_BYTES_SIZE: usize = (USED_RING_ADDR + USED_RING_SIZE - DESC_TABLE_ADDR) as usize;
|
||||
|
||||
fuzz_target!(|bytes: &[u8]| -> Corpus {
|
||||
if bytes.len() < (QUEUE_DATA_SIZE + QUEUE_BYTES_SIZE)
|
||||
|| bytes.len() > (QUEUE_DATA_SIZE + QUEUE_BYTES_SIZE + MEM_SIZE)
|
||||
{
|
||||
return Corpus::Reject;
|
||||
}
|
||||
|
||||
let queue_data = &bytes[..QUEUE_DATA_SIZE];
|
||||
let queue_bytes = &bytes[QUEUE_DATA_SIZE..QUEUE_DATA_SIZE + QUEUE_BYTES_SIZE];
|
||||
let mem_bytes = &bytes[QUEUE_DATA_SIZE + QUEUE_BYTES_SIZE..];
|
||||
|
||||
let q = setup_virt_queue(queue_data.try_into().unwrap());
|
||||
|
||||
let tmp_dir = TempDir::new_with_prefix("/tmp/fuzz_virtio_vsock").unwrap();
|
||||
let vsock_path = tmp_dir.as_path().join("vsock.sock");
|
||||
|
||||
let backend = TestBackend::new();
|
||||
|
||||
// Setup the guest memory with the input bytes
|
||||
let mem = GuestMemoryMmap::from_ranges(&[
|
||||
(GuestAddress(0), MEM_SIZE),
|
||||
(GuestAddress(DESC_TABLE_ADDR), QUEUE_BYTES_SIZE),
|
||||
])
|
||||
.unwrap();
|
||||
if mem
|
||||
.write_slice(queue_bytes, GuestAddress(DESC_TABLE_ADDR))
|
||||
.is_err()
|
||||
{
|
||||
return Corpus::Reject;
|
||||
}
|
||||
if mem.write_slice(mem_bytes, GuestAddress(0 as u64)).is_err() {
|
||||
return Corpus::Reject;
|
||||
}
|
||||
let guest_memory = GuestMemoryAtomic::new(mem);
|
||||
|
||||
let evt = EventFd::new(0).unwrap();
|
||||
let queue_evt = unsafe { EventFd::from_raw_fd(libc::dup(evt.as_raw_fd())) };
|
||||
|
||||
// Kick the 'queue' event before activate the rng device
|
||||
queue_evt.write(1).unwrap();
|
||||
|
||||
let mut vsock = virtio_devices::Vsock::new(
|
||||
"fuzzer_vsock".to_owned(),
|
||||
0,
|
||||
vsock_path,
|
||||
backend,
|
||||
false,
|
||||
SeccompAction::Allow,
|
||||
EventFd::new(EFD_NONBLOCK).unwrap(),
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
vsock
|
||||
.activate(
|
||||
guest_memory,
|
||||
Arc::new(NoopVirtioInterrupt {}),
|
||||
vec![(0, q, evt)],
|
||||
)
|
||||
.ok();
|
||||
|
||||
// Wait for the events to finish and vsock device worker thread to return
|
||||
vsock.wait_for_epoll_threads();
|
||||
|
||||
Corpus::Keep
|
||||
});
|
||||
|
||||
pub struct NoopVirtioInterrupt {}
|
||||
|
||||
impl VirtioInterrupt for NoopVirtioInterrupt {
|
||||
fn trigger(&self, _int_type: VirtioInterruptType) -> std::result::Result<(), std::io::Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn setup_virt_queue(bytes: &[u8; QUEUE_DATA_SIZE]) -> Queue {
|
||||
let mut q = Queue::new(QUEUE_SIZE).unwrap();
|
||||
q.set_next_avail(bytes[0] as u16); // 'u8' is enough given the 'QUEUE_SIZE' is small
|
||||
q.set_next_used(bytes[1] as u16);
|
||||
q.set_event_idx(bytes[2] % 2 != 0);
|
||||
q.set_size(bytes[3] as u16 % QUEUE_SIZE);
|
||||
|
||||
q.try_set_desc_table_address(GuestAddress(DESC_TABLE_ADDR))
|
||||
.unwrap();
|
||||
q.try_set_avail_ring_address(GuestAddress(AVAIL_RING_ADDR))
|
||||
.unwrap();
|
||||
q.try_set_used_ring_address(GuestAddress(USED_RING_ADDR))
|
||||
.unwrap();
|
||||
q.set_ready(true);
|
||||
|
||||
q
|
||||
}
|
||||
@@ -7,7 +7,7 @@
|
||||
use std::os::unix::io::{AsRawFd, FromRawFd};
|
||||
use std::sync::Arc;
|
||||
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use libfuzzer_sys::{fuzz_target, Corpus};
|
||||
use seccompiler::SeccompAction;
|
||||
use virtio_devices::{VirtioDevice, VirtioInterrupt, VirtioInterruptType};
|
||||
use virtio_queue::{Queue, QueueT};
|
||||
@@ -30,9 +30,9 @@ const AVAIL_RING_SIZE: u64 = 6_u64 + 2 * QUEUE_SIZE as u64;
|
||||
// Guest physical address for used ring (requires to 4-bytes aligned)
|
||||
const USED_RING_ADDR: u64 = (AVAIL_RING_ADDR + AVAIL_RING_SIZE + 3) & !3_u64;
|
||||
|
||||
fuzz_target!(|bytes| {
|
||||
fuzz_target!(|bytes: &[u8]| -> Corpus {
|
||||
if bytes.len() < QUEUE_DATA_SIZE || bytes.len() > (QUEUE_DATA_SIZE + MEM_SIZE) {
|
||||
return;
|
||||
return Corpus::Reject;
|
||||
}
|
||||
|
||||
let mut watchdog = virtio_devices::Watchdog::new(
|
||||
@@ -53,7 +53,7 @@ fuzz_target!(|bytes| {
|
||||
// Setup the guest memory with the input bytes
|
||||
let mem = GuestMemoryMmap::from_ranges(&[(GuestAddress(0), MEM_SIZE)]).unwrap();
|
||||
if mem.write_slice(mem_bytes, GuestAddress(0 as u64)).is_err() {
|
||||
return;
|
||||
return Corpus::Reject;
|
||||
}
|
||||
let guest_memory = GuestMemoryAtomic::new(mem);
|
||||
|
||||
@@ -73,6 +73,8 @@ fuzz_target!(|bytes| {
|
||||
|
||||
// Wait for the events to finish and watchdog device worker thread to return
|
||||
watchdog.wait_for_epoll_threads();
|
||||
|
||||
Corpus::Keep
|
||||
});
|
||||
|
||||
pub struct NoopVirtioInterrupt {}
|
||||
|
||||
153
fuzz/fuzz_targets/x86emul.rs
Normal file
153
fuzz/fuzz_targets/x86emul.rs
Normal file
@@ -0,0 +1,153 @@
|
||||
// Copyright © 2025 Microsoft Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#![no_main]
|
||||
|
||||
use hypervisor::arch::emulator::{PlatformEmulator, PlatformError};
|
||||
use hypervisor::arch::x86::emulator::{Emulator, EmulatorCpuState};
|
||||
use hypervisor::arch::x86::{DescriptorTable, SegmentRegister, SpecialRegisters};
|
||||
use hypervisor::StandardRegisters;
|
||||
use libfuzzer_sys::{fuzz_target, Corpus};
|
||||
|
||||
#[derive(Debug)]
|
||||
struct EmulatorContext {
|
||||
state: EmulatorCpuState,
|
||||
memory: [u8; 8],
|
||||
}
|
||||
|
||||
impl PlatformEmulator for EmulatorContext {
|
||||
type CpuState = EmulatorCpuState;
|
||||
|
||||
fn read_memory(&self, _gva: u64, data: &mut [u8]) -> Result<(), PlatformError> {
|
||||
data.copy_from_slice(&self.memory[..data.len()]);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_memory(&mut self, _gva: u64, _data: &[u8]) -> Result<(), PlatformError> {
|
||||
// Discard writes
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn cpu_state(&self, _cpu_id: usize) -> Result<Self::CpuState, PlatformError> {
|
||||
Ok(self.state.clone())
|
||||
}
|
||||
|
||||
fn set_cpu_state(&self, _cpu_id: usize, _state: Self::CpuState) -> Result<(), PlatformError> {
|
||||
// Ignore
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn fetch(&self, _ip: u64, _data: &mut [u8]) -> Result<(), PlatformError> {
|
||||
// The fuzzer already provides 16 bytes of data, we don't need to fetch anything
|
||||
panic!("fetch should not be called");
|
||||
}
|
||||
}
|
||||
|
||||
fuzz_target!(|bytes: &[u8]| -> Corpus {
|
||||
let (mut ctx, insn) = match generate_context_and_instruction(bytes) {
|
||||
Ok((ctx, insn)) => (ctx, insn),
|
||||
Err(_) => return Corpus::Reject,
|
||||
};
|
||||
|
||||
let mut e = Emulator::new(&mut ctx);
|
||||
|
||||
if e.emulate_first_insn(0, &insn).is_err() {
|
||||
return Corpus::Reject;
|
||||
}
|
||||
|
||||
Corpus::Keep
|
||||
});
|
||||
|
||||
// Helper functions to generate structures from fuzzer input below
|
||||
|
||||
fn generate_segment_register(
|
||||
u: &mut arbitrary::Unstructured<'_>,
|
||||
) -> arbitrary::Result<SegmentRegister> {
|
||||
Ok(SegmentRegister {
|
||||
base: u.arbitrary()?,
|
||||
limit: u.arbitrary()?,
|
||||
selector: u.arbitrary()?,
|
||||
avl: u.arbitrary()?,
|
||||
dpl: u.arbitrary()?,
|
||||
db: u.arbitrary()?,
|
||||
g: u.arbitrary()?,
|
||||
l: u.arbitrary()?,
|
||||
present: u.arbitrary()?,
|
||||
s: u.arbitrary()?,
|
||||
type_: u.arbitrary()?,
|
||||
unusable: u.arbitrary()?,
|
||||
})
|
||||
}
|
||||
|
||||
fn generate_descriptor_table(
|
||||
u: &mut arbitrary::Unstructured<'_>,
|
||||
) -> arbitrary::Result<DescriptorTable> {
|
||||
Ok(DescriptorTable {
|
||||
base: u.arbitrary()?,
|
||||
limit: u.arbitrary()?,
|
||||
})
|
||||
}
|
||||
|
||||
fn generate_context_and_instruction(
|
||||
bytes: &[u8],
|
||||
) -> arbitrary::Result<(EmulatorContext, [u8; 16])> {
|
||||
let mut u = arbitrary::Unstructured::new(bytes);
|
||||
|
||||
let mut regs = mshv_bindings::StandardRegisters {
|
||||
rax: u.arbitrary()?,
|
||||
rbx: u.arbitrary()?,
|
||||
rcx: u.arbitrary()?,
|
||||
rdx: u.arbitrary()?,
|
||||
rsi: u.arbitrary()?,
|
||||
rdi: u.arbitrary()?,
|
||||
rsp: u.arbitrary()?,
|
||||
rbp: u.arbitrary()?,
|
||||
r8: u.arbitrary()?,
|
||||
r9: u.arbitrary()?,
|
||||
r10: u.arbitrary()?,
|
||||
r11: u.arbitrary()?,
|
||||
r12: u.arbitrary()?,
|
||||
r13: u.arbitrary()?,
|
||||
r14: u.arbitrary()?,
|
||||
r15: u.arbitrary()?,
|
||||
rip: u.arbitrary()?,
|
||||
rflags: u.arbitrary()?,
|
||||
};
|
||||
|
||||
// Cap RCX to avoid looping for too long for reps instructions.
|
||||
regs.rcx &= 0xFFFFu64;
|
||||
|
||||
let regs = StandardRegisters::Mshv(regs);
|
||||
|
||||
let sregs = SpecialRegisters {
|
||||
cs: generate_segment_register(&mut u)?,
|
||||
ds: generate_segment_register(&mut u)?,
|
||||
es: generate_segment_register(&mut u)?,
|
||||
fs: generate_segment_register(&mut u)?,
|
||||
gs: generate_segment_register(&mut u)?,
|
||||
ss: generate_segment_register(&mut u)?,
|
||||
tr: generate_segment_register(&mut u)?,
|
||||
ldt: generate_segment_register(&mut u)?,
|
||||
gdt: generate_descriptor_table(&mut u)?,
|
||||
idt: generate_descriptor_table(&mut u)?,
|
||||
cr0: u.arbitrary()?,
|
||||
cr2: u.arbitrary()?,
|
||||
cr3: u.arbitrary()?,
|
||||
cr4: u.arbitrary()?,
|
||||
cr8: u.arbitrary()?,
|
||||
efer: u.arbitrary()?,
|
||||
apic_base: u.arbitrary()?,
|
||||
interrupt_bitmap: u.arbitrary()?,
|
||||
};
|
||||
|
||||
let memory = u.arbitrary::<[u8; 8]>()?;
|
||||
let insn = u.arbitrary::<[u8; 16]>()?;
|
||||
|
||||
let ctx = EmulatorContext {
|
||||
state: EmulatorCpuState { regs, sregs },
|
||||
memory,
|
||||
};
|
||||
|
||||
Ok((ctx, insn))
|
||||
}
|
||||
@@ -7,20 +7,22 @@ version = "0.1.0"
|
||||
|
||||
[features]
|
||||
kvm = ["kvm-bindings", "kvm-ioctls", "vfio-ioctls/kvm"]
|
||||
mshv = ["iced-x86", "mshv-bindings", "mshv-ioctls", "vfio-ioctls/mshv"]
|
||||
mshv = ["mshv-bindings", "mshv-ioctls", "mshv_emulator", "vfio-ioctls/mshv"]
|
||||
mshv_emulator = ["iced-x86", "mshv-bindings"]
|
||||
sev_snp = ["igvm", "igvm_defs"]
|
||||
tdx = []
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.87"
|
||||
anyhow = "1.0.94"
|
||||
arc-swap = "1.7.1"
|
||||
byteorder = "1.5.0"
|
||||
cfg-if = "1.0.0"
|
||||
concat-idents = "1.1.5"
|
||||
igvm = { version = "0.3.3", optional = true }
|
||||
igvm_defs = { version = "0.3.1", optional = true }
|
||||
igvm = { workspace = true, optional = true }
|
||||
igvm_defs = { workspace = true, optional = true }
|
||||
kvm-bindings = { workspace = true, optional = true, features = ["serde"] }
|
||||
kvm-ioctls = { workspace = true, optional = true }
|
||||
libc = "0.2.158"
|
||||
libc = "0.2.167"
|
||||
log = "0.4.22"
|
||||
mshv-bindings = { workspace = true, features = [
|
||||
"fam-wrappers",
|
||||
@@ -31,14 +33,27 @@ serde = { version = "1.0.208", features = ["derive", "rc"] }
|
||||
serde_with = { version = "3.9.0", default-features = false, features = [
|
||||
"macros",
|
||||
] }
|
||||
thiserror = "1.0.62"
|
||||
thiserror = "2.0.6"
|
||||
vfio-ioctls = { workspace = true, default-features = false }
|
||||
vm-memory = { workspace = true, features = ["backend-atomic", "backend-mmap"] }
|
||||
vm-memory = { workspace = true, features = [
|
||||
"backend-atomic",
|
||||
"backend-bitmap",
|
||||
"backend-mmap",
|
||||
] }
|
||||
vmm-sys-util = { workspace = true, features = ["with-serde"] }
|
||||
|
||||
[target.'cfg(target_arch = "x86_64")'.dependencies.iced-x86]
|
||||
default-features = false
|
||||
features = ["decoder", "fast_fmt", "instr_info", "op_code_info", "std"]
|
||||
features = [
|
||||
"decoder",
|
||||
"instr_info",
|
||||
"no_d3now",
|
||||
"no_evex",
|
||||
"no_vex",
|
||||
"no_xop",
|
||||
"op_code_info",
|
||||
"std",
|
||||
]
|
||||
optional = true
|
||||
version = "1.21.0"
|
||||
|
||||
|
||||
@@ -21,3 +21,6 @@ pub mod x86;
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub mod aarch64;
|
||||
|
||||
#[cfg(target_arch = "riscv64")]
|
||||
pub mod riscv64;
|
||||
|
||||
63
hypervisor/src/arch/riscv64/aia.rs
Normal file
63
hypervisor/src/arch/riscv64/aia.rs
Normal file
@@ -0,0 +1,63 @@
|
||||
// Copyright © 2024 Institute of Software, CAS. All rights reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::any::Any;
|
||||
use std::result;
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::{AiaState, HypervisorDeviceError, HypervisorVmError};
|
||||
|
||||
/// Errors thrown while setting up the VAIA.
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
/// Error while calling KVM ioctl for setting up the global interrupt controller.
|
||||
#[error("Failed creating AIA device: {0}")]
|
||||
CreateAia(HypervisorVmError),
|
||||
/// Error while setting device attributes for the AIA.
|
||||
#[error("Failed setting device attributes for the AIA: {0}")]
|
||||
SetDeviceAttribute(HypervisorDeviceError),
|
||||
/// Error while getting device attributes for the AIA.
|
||||
#[error("Failed getting device attributes for the AIA: {0}")]
|
||||
GetDeviceAttribute(HypervisorDeviceError),
|
||||
}
|
||||
pub type Result<T> = result::Result<T, Error>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct VaiaConfig {
|
||||
pub vcpu_count: u32,
|
||||
pub aplic_addr: u64,
|
||||
pub imsic_addr: u64,
|
||||
pub nr_irqs: u32,
|
||||
}
|
||||
|
||||
/// Hypervisor agnostic interface for a virtualized AIA
|
||||
pub trait Vaia: Send + Sync {
|
||||
/// Returns the compatibility property of APLIC
|
||||
fn aplic_compatibility(&self) -> &str;
|
||||
|
||||
/// Returns an array with APLIC device properties
|
||||
fn aplic_properties(&self) -> [u32; 4];
|
||||
|
||||
/// Returns the compatibility property of IMSIC
|
||||
fn imsic_compatibility(&self) -> &str;
|
||||
|
||||
/// Returns an array with IMSIC device properties
|
||||
fn imsic_properties(&self) -> [u32; 4];
|
||||
|
||||
/// Returns the number of vCPUs this AIA handles
|
||||
fn vcpu_count(&self) -> u32;
|
||||
|
||||
/// Returns whether the AIA device is MSI compatible or not
|
||||
fn msi_compatible(&self) -> bool;
|
||||
|
||||
/// Downcast the trait object to its concrete type.
|
||||
fn as_any_concrete_mut(&mut self) -> &mut dyn Any;
|
||||
|
||||
/// Save the state of AiaImsics.
|
||||
fn state(&self) -> Result<AiaState>;
|
||||
|
||||
/// Restore the state of AiaImsics.
|
||||
fn set_state(&mut self, state: &AiaState) -> Result<()>;
|
||||
}
|
||||
5
hypervisor/src/arch/riscv64/mod.rs
Normal file
5
hypervisor/src/arch/riscv64/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
// Copyright © 2024 Institute of Software, CAS. All rights reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod aia;
|
||||
@@ -116,14 +116,14 @@ fn memory_operand_address<T: CpuStateManager>(
|
||||
|
||||
if insn.memory_base() != iced_x86::Register::None {
|
||||
let base: u64 = state.read_reg(insn.memory_base())?;
|
||||
address += base;
|
||||
address = address.wrapping_add(base);
|
||||
}
|
||||
|
||||
if insn.memory_index() != iced_x86::Register::None {
|
||||
let mut index: u64 = state.read_reg(insn.memory_index())?;
|
||||
index *= insn.memory_index_scale() as u64;
|
||||
index = index.wrapping_mul(insn.memory_index_scale() as u64);
|
||||
|
||||
address += index;
|
||||
address = address.wrapping_add(index);
|
||||
}
|
||||
|
||||
address = address.wrapping_add(insn.memory_displacement64());
|
||||
@@ -140,16 +140,3 @@ pub trait InstructionHandler<T: CpuStateManager> {
|
||||
platform: &mut dyn PlatformEmulator<CpuState = T>,
|
||||
) -> Result<(), EmulationError<Exception>>;
|
||||
}
|
||||
|
||||
macro_rules! insn_format {
|
||||
($insn:ident) => {{
|
||||
let mut output = String::new();
|
||||
let mut formatter = FastFormatter::new();
|
||||
formatter
|
||||
.options_mut()
|
||||
.set_space_after_operand_separator(true);
|
||||
formatter.format(&$insn, &mut output);
|
||||
|
||||
output
|
||||
}};
|
||||
}
|
||||
|
||||
@@ -185,7 +185,7 @@ pub trait CpuStateManager: Clone {
|
||||
)));
|
||||
}
|
||||
|
||||
Ok(logical_addr + segment_register.base)
|
||||
Ok(logical_addr.wrapping_add(segment_register.base))
|
||||
}
|
||||
|
||||
_ => Err(PlatformError::UnsupportedCpuMode(anyhow!("{:?}", mode))),
|
||||
@@ -599,8 +599,8 @@ impl<T: CpuStateManager> Emulator<'_, T> {
|
||||
decoder.decode_out(&mut insn);
|
||||
if decoder.last_error() != DecoderError::None {
|
||||
return Err(EmulationError::InstructionFetchingError(anyhow!(
|
||||
"{:#x?}",
|
||||
insn_format!(insn)
|
||||
"{:?}",
|
||||
insn.code()
|
||||
)));
|
||||
}
|
||||
}
|
||||
@@ -609,14 +609,17 @@ impl<T: CpuStateManager> Emulator<'_, T> {
|
||||
Emulator::get_handler(insn.code())
|
||||
.ok_or_else(|| {
|
||||
EmulationError::UnsupportedInstruction(anyhow!(
|
||||
"{:#x?} {:?} {:?}",
|
||||
insn_format!(insn),
|
||||
insn.mnemonic(),
|
||||
insn.code()
|
||||
"{:?} {:x?}",
|
||||
insn.code(),
|
||||
insn_stream
|
||||
))
|
||||
})?
|
||||
.emulate(&insn, &mut state, self.platform)
|
||||
.context(anyhow!("Failed to emulate {:#x?}", insn_format!(insn)))?;
|
||||
.context(anyhow!(
|
||||
"Failed to emulate {:?} {:x?}",
|
||||
insn.code(),
|
||||
insn_stream
|
||||
))?;
|
||||
|
||||
last_decoded_ip = decoder.ip();
|
||||
num_insn_emulated += 1;
|
||||
|
||||
@@ -11,7 +11,9 @@
|
||||
// Copyright © 2020, Microsoft Corporation
|
||||
//
|
||||
|
||||
#[cfg(all(feature = "mshv", target_arch = "x86_64"))]
|
||||
use core::fmt;
|
||||
|
||||
#[cfg(all(feature = "mshv_emulator", target_arch = "x86_64"))]
|
||||
pub mod emulator;
|
||||
pub mod gdt;
|
||||
#[allow(non_camel_case_types)]
|
||||
@@ -211,6 +213,22 @@ pub struct CpuIdEntry {
|
||||
pub edx: u32,
|
||||
}
|
||||
|
||||
impl fmt::Display for CpuIdEntry {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"function = 0x{:08x} \
|
||||
index = 0x{:08x} \
|
||||
eax = 0x{:08x} \
|
||||
ebx = 0x{:08x} \
|
||||
ecx = 0x{:08x} \
|
||||
edx = 0x{:08x} \
|
||||
flags = 0x{:08x}",
|
||||
self.function, self.index, self.eax, self.ebx, self.ecx, self.edx, self.flags
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub const CPUID_FLAG_VALID_INDEX: u32 = 1;
|
||||
|
||||
#[derive(Default, Debug, Clone, serde::Deserialize, serde::Serialize)]
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// Copyright © 2024 Institute of Software, CAS. All rights reserved.
|
||||
//
|
||||
// Copyright © 2019 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
|
||||
@@ -8,15 +10,21 @@
|
||||
//
|
||||
//
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
use std::sync::Arc;
|
||||
|
||||
use thiserror::Error;
|
||||
#[cfg(not(target_arch = "riscv64"))]
|
||||
use vm_memory::GuestAddress;
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
use crate::aarch64::{RegList, VcpuInit};
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use crate::arch::x86::{CpuIdEntry, FpuState, LapicState, MsrEntry, SpecialRegisters};
|
||||
#[cfg(feature = "tdx")]
|
||||
use crate::kvm::{TdxExitDetails, TdxExitStatus};
|
||||
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
|
||||
use crate::RegList;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
use crate::VcpuInit;
|
||||
use crate::{CpuState, MpState, StandardRegisters};
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
@@ -216,6 +224,16 @@ pub enum HypervisorCpuError {
|
||||
#[error("Failed to set aarch64 core register: {0}")]
|
||||
SetAarchCoreRegister(#[source] anyhow::Error),
|
||||
///
|
||||
/// Getting RISC-V 64-bit core register error
|
||||
///
|
||||
#[error("Failed to get riscv64 core register: {0}")]
|
||||
GetRiscvCoreRegister(#[source] anyhow::Error),
|
||||
///
|
||||
/// Setting RISC-V 64-bit core register error
|
||||
///
|
||||
#[error("Failed to set riscv64 core register: {0}")]
|
||||
SetRiscvCoreRegister(#[source] anyhow::Error),
|
||||
///
|
||||
/// Getting registers list error
|
||||
///
|
||||
#[error("Failed to retrieve list of registers: {0}")]
|
||||
@@ -231,6 +249,16 @@ pub enum HypervisorCpuError {
|
||||
#[error("Failed to set system register: {0}")]
|
||||
SetSysRegister(#[source] anyhow::Error),
|
||||
///
|
||||
/// Getting RISC-V 64-bit non-core register error
|
||||
///
|
||||
#[error("Failed to get non-core register: {0}")]
|
||||
GetNonCoreRegister(#[source] anyhow::Error),
|
||||
///
|
||||
/// Setting RISC-V 64-bit non-core register error
|
||||
///
|
||||
#[error("Failed to set non-core register: {0}")]
|
||||
SetNonCoreRegister(#[source] anyhow::Error),
|
||||
///
|
||||
/// GVA translation error
|
||||
///
|
||||
#[error("Failed to translate GVA: {0}")]
|
||||
@@ -412,6 +440,7 @@ pub trait Vcpu: Send + Sync {
|
||||
///
|
||||
/// Sets debug registers to set hardware breakpoints and/or enable single step.
|
||||
///
|
||||
#[cfg(not(target_arch = "riscv64"))]
|
||||
fn set_guest_debug(&self, _addrs: &[GuestAddress], _singlestep: bool) -> Result<()> {
|
||||
Err(HypervisorCpuError::SetDebugRegs(anyhow!("unimplemented")))
|
||||
}
|
||||
@@ -423,12 +452,31 @@ pub trait Vcpu: Send + Sync {
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
fn vcpu_finalize(&self, feature: i32) -> Result<()>;
|
||||
|
||||
///
|
||||
/// Gets the features that have been finalized for a given CPU.
|
||||
///
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
fn vcpu_get_finalized_features(&self) -> i32;
|
||||
///
|
||||
/// Sets processor features for a given CPU.
|
||||
///
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
fn vcpu_set_processor_features(
|
||||
&self,
|
||||
vm: &Arc<dyn crate::Vm>,
|
||||
kvi: &mut VcpuInit,
|
||||
id: u8,
|
||||
) -> Result<()>;
|
||||
///
|
||||
/// Returns VcpuInit with default value set
|
||||
///
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
fn create_vcpu_init(&self) -> VcpuInit;
|
||||
///
|
||||
/// Gets a list of the guest registers that are supported for the
|
||||
/// KVM_GET_ONE_REG/KVM_SET_ONE_REG calls.
|
||||
///
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
|
||||
fn get_reg_list(&self, reg_list: &mut RegList) -> Result<()>;
|
||||
///
|
||||
/// Gets the value of a system register
|
||||
@@ -436,9 +484,14 @@ pub trait Vcpu: Send + Sync {
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
fn get_sys_reg(&self, sys_reg: u32) -> Result<u64>;
|
||||
///
|
||||
/// Gets the value of a non-core register on RISC-V 64-bit
|
||||
///
|
||||
#[cfg(target_arch = "riscv64")]
|
||||
fn get_non_core_reg(&self, non_core_reg: u32) -> Result<u64>;
|
||||
///
|
||||
/// Configure core registers for a given CPU.
|
||||
///
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
|
||||
fn setup_regs(&self, cpu_id: u8, boot_ip: u64, fdt_start: u64) -> Result<()>;
|
||||
///
|
||||
/// Check if the CPU supports PMU
|
||||
|
||||
@@ -118,6 +118,17 @@ pub trait Hypervisor: Send + Sync {
|
||||
fn create_vm_with_type(&self, _vm_type: u64) -> Result<Arc<dyn Vm>> {
|
||||
unreachable!()
|
||||
}
|
||||
///
|
||||
/// Create a Vm of a specific type using the underlying hypervisor, passing memory size
|
||||
/// Return a hypervisor-agnostic Vm trait object
|
||||
///
|
||||
fn create_vm_with_type_and_memory(
|
||||
&self,
|
||||
_vm_type: u64,
|
||||
#[cfg(feature = "sev_snp")] _mem_size: u64,
|
||||
) -> Result<Arc<dyn Vm>> {
|
||||
unreachable!()
|
||||
}
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// Get the supported CpuID
|
||||
|
||||
@@ -103,9 +103,8 @@ fn dist_attr_get(gic: &DeviceFd, offset: u32) -> Result<u32> {
|
||||
flags: 0,
|
||||
};
|
||||
|
||||
// get_device_attr should be marked as unsafe, and will be in future.
|
||||
// SAFETY: gic_dist_attr.addr is safe to write to.
|
||||
gic.get_device_attr(&mut gic_dist_attr).map_err(|e| {
|
||||
unsafe { gic.get_device_attr(&mut gic_dist_attr) }.map_err(|e| {
|
||||
Error::GetDeviceAttribute(HypervisorDeviceError::GetDeviceAttribute(e.into()))
|
||||
})?;
|
||||
|
||||
@@ -131,9 +130,8 @@ fn get_interrupts_num(gic: &DeviceFd) -> Result<u32> {
|
||||
addr: &mut num_irq as *mut u32 as u64,
|
||||
flags: 0,
|
||||
};
|
||||
// get_device_attr should be marked as unsafe, and will be in future.
|
||||
// SAFETY: nr_irqs_attr.addr is safe to write to.
|
||||
gic.get_device_attr(&mut nr_irqs_attr).map_err(|e| {
|
||||
unsafe { gic.get_device_attr(&mut nr_irqs_attr) }.map_err(|e| {
|
||||
Error::GetDeviceAttribute(HypervisorDeviceError::GetDeviceAttribute(e.into()))
|
||||
})?;
|
||||
Ok(num_irq)
|
||||
|
||||
@@ -105,9 +105,8 @@ fn icc_attr_get(gic: &DeviceFd, offset: u64, typer: u64) -> Result<u32> {
|
||||
flags: 0,
|
||||
};
|
||||
|
||||
// get_device_attr should be marked as unsafe, and will be in future.
|
||||
// SAFETY: gic_icc_attr.addr is safe to write to.
|
||||
gic.get_device_attr(&mut gic_icc_attr).map_err(|e| {
|
||||
unsafe { gic.get_device_attr(&mut gic_icc_attr) }.map_err(|e| {
|
||||
Error::GetDeviceAttribute(HypervisorDeviceError::GetDeviceAttribute(e.into()))
|
||||
})?;
|
||||
|
||||
|
||||
@@ -49,13 +49,10 @@ fn gicv3_its_attr_get(its_device: &DeviceFd, group: u32, attr: u32) -> Result<u6
|
||||
flags: 0,
|
||||
};
|
||||
|
||||
// get_device_attr should be marked as unsafe, and will be in future.
|
||||
// SAFETY: gicv3_its_attr.addr is safe to write to.
|
||||
its_device
|
||||
.get_device_attr(&mut gicv3_its_attr)
|
||||
.map_err(|e| {
|
||||
Error::GetDeviceAttribute(HypervisorDeviceError::GetDeviceAttribute(e.into()))
|
||||
})?;
|
||||
unsafe { its_device.get_device_attr(&mut gicv3_its_attr) }.map_err(|e| {
|
||||
Error::GetDeviceAttribute(HypervisorDeviceError::GetDeviceAttribute(e.into()))
|
||||
})?;
|
||||
|
||||
Ok(val)
|
||||
}
|
||||
|
||||
@@ -7,11 +7,11 @@ use kvm_ioctls::DeviceFd;
|
||||
use crate::arch::aarch64::gic::{Error, Result};
|
||||
use crate::device::HypervisorDeviceError;
|
||||
use crate::kvm::kvm_bindings::{
|
||||
kvm_device_attr, KVM_DEV_ARM_VGIC_GRP_REDIST_REGS, KVM_REG_ARM64, KVM_REG_ARM64_SYSREG,
|
||||
KVM_REG_ARM64_SYSREG_OP0_MASK, KVM_REG_ARM64_SYSREG_OP0_SHIFT, KVM_REG_ARM64_SYSREG_OP2_MASK,
|
||||
KVM_REG_ARM64_SYSREG_OP2_SHIFT, KVM_REG_SIZE_U64,
|
||||
kvm_device_attr, kvm_one_reg, KVM_DEV_ARM_VGIC_GRP_REDIST_REGS, KVM_REG_ARM64,
|
||||
KVM_REG_ARM64_SYSREG, KVM_REG_ARM64_SYSREG_OP0_MASK, KVM_REG_ARM64_SYSREG_OP0_SHIFT,
|
||||
KVM_REG_ARM64_SYSREG_OP2_MASK, KVM_REG_ARM64_SYSREG_OP2_SHIFT, KVM_REG_SIZE_U64,
|
||||
};
|
||||
use crate::kvm::{Register, VcpuKvmState};
|
||||
use crate::kvm::VcpuKvmState;
|
||||
use crate::CpuState;
|
||||
|
||||
// Relevant redistributor registers that we want to save/restore.
|
||||
@@ -121,9 +121,8 @@ fn redist_attr_get(gic: &DeviceFd, offset: u32, typer: u64) -> Result<u32> {
|
||||
flags: 0,
|
||||
};
|
||||
|
||||
// get_device_attr should be marked as unsafe, and will be in future.
|
||||
// SAFETY: gic_redist_attr.addr is safe to write to.
|
||||
gic.get_device_attr(&mut gic_redist_attr).map_err(|e| {
|
||||
unsafe { gic.get_device_attr(&mut gic_redist_attr) }.map_err(|e| {
|
||||
Error::GetDeviceAttribute(HypervisorDeviceError::GetDeviceAttribute(e.into()))
|
||||
})?;
|
||||
|
||||
@@ -214,7 +213,7 @@ pub fn construct_gicr_typers(vcpu_states: &[CpuState]) -> Vec<u64> {
|
||||
let state: VcpuKvmState = state.clone().into();
|
||||
let last = (index == vcpu_states.len() - 1) as u64;
|
||||
// state.sys_regs is a big collection of system registers, including MIPDR_EL1
|
||||
let mpidr: Vec<Register> = state
|
||||
let mpidr: Vec<kvm_one_reg> = state
|
||||
.sys_regs
|
||||
.into_iter()
|
||||
.filter(|reg| reg.id == KVM_ARM64_SYSREG_MPIDR_EL1)
|
||||
@@ -222,7 +221,7 @@ pub fn construct_gicr_typers(vcpu_states: &[CpuState]) -> Vec<u64> {
|
||||
//calculate affinity
|
||||
let mut cpu_affid = mpidr[0].addr & 1095233437695;
|
||||
cpu_affid = ((cpu_affid & 0xFF00000000) >> 8) | (cpu_affid & 0xFFFFFF);
|
||||
gicr_typers.push((cpu_affid << 32) | (1 << 24) | (index as u64) << 8 | (last << 4));
|
||||
gicr_typers.push((cpu_affid << 32) | (1 << 24) | ((index as u64) << 8) | (last << 4));
|
||||
}
|
||||
|
||||
gicr_typers
|
||||
|
||||
@@ -14,7 +14,6 @@ use kvm_bindings::{
|
||||
kvm_mp_state, kvm_one_reg, kvm_regs, KVM_REG_ARM_COPROC_MASK, KVM_REG_ARM_CORE,
|
||||
KVM_REG_SIZE_MASK, KVM_REG_SIZE_U32, KVM_REG_SIZE_U64,
|
||||
};
|
||||
pub use kvm_bindings::{kvm_one_reg as Register, kvm_vcpu_init as VcpuInit, RegList};
|
||||
pub use kvm_ioctls::{Cap, Kvm};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// Copyright © 2024 Institute of Software, CAS. All rights reserved.
|
||||
//
|
||||
// Copyright © 2019 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
|
||||
@@ -28,20 +30,28 @@ use vmm_sys_util::eventfd::EventFd;
|
||||
use crate::aarch64::gic::KvmGicV3Its;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub use crate::aarch64::{
|
||||
check_required_kvm_extensions, gic::Gicv3ItsState as GicState, is_system_register, VcpuInit,
|
||||
VcpuKvmState,
|
||||
check_required_kvm_extensions, gic::Gicv3ItsState as GicState, is_system_register, VcpuKvmState,
|
||||
};
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
use crate::arch::aarch64::gic::{Vgic, VgicConfig};
|
||||
#[cfg(target_arch = "riscv64")]
|
||||
use crate::arch::riscv64::aia::{Vaia, VaiaConfig};
|
||||
#[cfg(target_arch = "riscv64")]
|
||||
use crate::riscv64::aia::KvmAiaImsics;
|
||||
#[cfg(target_arch = "riscv64")]
|
||||
pub use crate::riscv64::{
|
||||
aia::AiaImsicsState as AiaState, check_required_kvm_extensions, is_non_core_register,
|
||||
VcpuKvmState,
|
||||
};
|
||||
use crate::vm::{self, InterruptSourceConfig, VmOps};
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
use crate::{arm64_core_reg_id, offset_of};
|
||||
use crate::{cpu, hypervisor, vec_with_array_field, HypervisorType};
|
||||
#[cfg(target_arch = "riscv64")]
|
||||
use crate::{offset_of, riscv64_reg_id};
|
||||
// x86_64 dependencies
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub mod x86_64;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
use aarch64::{RegList, Register};
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use kvm_bindings::{
|
||||
kvm_enable_cap, kvm_msr_entry, MsrList, KVM_CAP_HYPERV_SYNIC, KVM_CAP_SPLIT_IRQCHIP,
|
||||
@@ -65,12 +75,21 @@ use crate::{
|
||||
// aarch64 dependencies
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub mod aarch64;
|
||||
// riscv64 dependencies
|
||||
#[cfg(target_arch = "riscv64")]
|
||||
pub mod riscv64;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
use std::mem;
|
||||
|
||||
///
|
||||
/// Export generically-named wrappers of kvm-bindings for Unix-based platforms
|
||||
///
|
||||
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
|
||||
pub use kvm_bindings::kvm_vcpu_events as VcpuEvents;
|
||||
pub use kvm_bindings::{
|
||||
kvm_clock_data, kvm_create_device, kvm_device_type_KVM_DEV_TYPE_VFIO, kvm_guest_debug,
|
||||
kvm_irq_routing, kvm_irq_routing_entry, kvm_mp_state, kvm_userspace_memory_region,
|
||||
kvm_clock_data, kvm_create_device, kvm_create_device as CreateDevice,
|
||||
kvm_device_attr as DeviceAttr, kvm_device_type_KVM_DEV_TYPE_VFIO, kvm_guest_debug,
|
||||
kvm_irq_routing, kvm_irq_routing_entry, kvm_mp_state, kvm_run, kvm_userspace_memory_region,
|
||||
KVM_GUESTDBG_ENABLE, KVM_GUESTDBG_SINGLESTEP, KVM_IRQ_ROUTING_IRQCHIP, KVM_IRQ_ROUTING_MSI,
|
||||
KVM_MEM_LOG_DIRTY_PAGES, KVM_MEM_READONLY, KVM_MSI_VALID_DEVID,
|
||||
};
|
||||
@@ -81,21 +100,19 @@ use kvm_bindings::{
|
||||
KVM_REG_ARM64_SYSREG_OP0_MASK, KVM_REG_ARM64_SYSREG_OP1_MASK, KVM_REG_ARM64_SYSREG_OP2_MASK,
|
||||
KVM_REG_ARM_CORE, KVM_REG_SIZE_U128, KVM_REG_SIZE_U32, KVM_REG_SIZE_U64,
|
||||
};
|
||||
#[cfg(target_arch = "riscv64")]
|
||||
use kvm_bindings::{kvm_riscv_core, user_regs_struct, KVM_REG_RISCV_CORE};
|
||||
#[cfg(feature = "tdx")]
|
||||
use kvm_bindings::{kvm_run__bindgen_ty_1, KVMIO};
|
||||
pub use kvm_ioctls::{Cap, Kvm};
|
||||
pub use kvm_ioctls::{Cap, Kvm, VcpuExit};
|
||||
use thiserror::Error;
|
||||
use vfio_ioctls::VfioDeviceFd;
|
||||
#[cfg(feature = "tdx")]
|
||||
use vmm_sys_util::{ioctl::ioctl_with_val, ioctl_ioc_nr, ioctl_iowr_nr};
|
||||
pub use {kvm_bindings, kvm_ioctls};
|
||||
///
|
||||
/// Export generically-named wrappers of kvm-bindings for Unix-based platforms
|
||||
///
|
||||
pub use {
|
||||
kvm_bindings::kvm_create_device as CreateDevice, kvm_bindings::kvm_device_attr as DeviceAttr,
|
||||
kvm_bindings::kvm_run, kvm_bindings::kvm_vcpu_events as VcpuEvents, kvm_ioctls::VcpuExit,
|
||||
};
|
||||
|
||||
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
|
||||
use crate::RegList;
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
const KVM_CAP_SGX_ATTRIBUTE: u32 = 196;
|
||||
@@ -334,12 +351,69 @@ impl From<ClockData> for kvm_clock_data {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<kvm_bindings::kvm_one_reg> for crate::Register {
|
||||
fn from(s: kvm_bindings::kvm_one_reg) -> Self {
|
||||
crate::Register::Kvm(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<crate::Register> for kvm_bindings::kvm_one_reg {
|
||||
fn from(e: crate::Register) -> Self {
|
||||
match e {
|
||||
crate::Register::Kvm(e) => e,
|
||||
/* Needed in case other hypervisors are enabled */
|
||||
#[allow(unreachable_patterns)]
|
||||
_ => panic!("Register is not valid"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
impl From<kvm_bindings::kvm_vcpu_init> for crate::VcpuInit {
|
||||
fn from(s: kvm_bindings::kvm_vcpu_init) -> Self {
|
||||
crate::VcpuInit::Kvm(s)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
impl From<crate::VcpuInit> for kvm_bindings::kvm_vcpu_init {
|
||||
fn from(e: crate::VcpuInit) -> Self {
|
||||
match e {
|
||||
crate::VcpuInit::Kvm(e) => e,
|
||||
/* Needed in case other hypervisors are enabled */
|
||||
#[allow(unreachable_patterns)]
|
||||
_ => panic!("VcpuInit is not valid"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
|
||||
impl From<kvm_bindings::RegList> for crate::RegList {
|
||||
fn from(s: kvm_bindings::RegList) -> Self {
|
||||
crate::RegList::Kvm(s)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
|
||||
impl From<crate::RegList> for kvm_bindings::RegList {
|
||||
fn from(e: crate::RegList) -> Self {
|
||||
match e {
|
||||
crate::RegList::Kvm(e) => e,
|
||||
/* Needed in case other hypervisors are enabled */
|
||||
#[allow(unreachable_patterns)]
|
||||
_ => panic!("RegList is not valid"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "riscv64"))]
|
||||
impl From<kvm_bindings::kvm_regs> for crate::StandardRegisters {
|
||||
fn from(s: kvm_bindings::kvm_regs) -> Self {
|
||||
crate::StandardRegisters::Kvm(s)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "riscv64"))]
|
||||
impl From<crate::StandardRegisters> for kvm_bindings::kvm_regs {
|
||||
fn from(e: crate::StandardRegisters) -> Self {
|
||||
match e {
|
||||
@@ -351,6 +425,25 @@ impl From<crate::StandardRegisters> for kvm_bindings::kvm_regs {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "riscv64")]
|
||||
impl From<kvm_bindings::kvm_riscv_core> for crate::StandardRegisters {
|
||||
fn from(s: kvm_bindings::kvm_riscv_core) -> Self {
|
||||
crate::StandardRegisters::Kvm(s)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "riscv64")]
|
||||
impl From<crate::StandardRegisters> for kvm_bindings::kvm_riscv_core {
|
||||
fn from(e: crate::StandardRegisters) -> Self {
|
||||
match e {
|
||||
crate::StandardRegisters::Kvm(e) => e,
|
||||
/* Needed in case other hypervisors are enabled */
|
||||
#[allow(unreachable_patterns)]
|
||||
_ => panic!("StandardRegisters are not valid"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<kvm_irq_routing_entry> for IrqRoutingEntry {
|
||||
fn from(s: kvm_irq_routing_entry) -> Self {
|
||||
IrqRoutingEntry::Kvm(s)
|
||||
@@ -433,6 +526,7 @@ impl vm::Vm for KvmVm {
|
||||
.map_err(|e| vm::HypervisorVmError::SetTssAddress(e.into()))
|
||||
}
|
||||
|
||||
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
|
||||
///
|
||||
/// Creates an in-kernel interrupt controller.
|
||||
///
|
||||
@@ -493,6 +587,16 @@ impl vm::Vm for KvmVm {
|
||||
Ok(Arc::new(Mutex::new(gic_device)))
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "riscv64")]
|
||||
///
|
||||
/// Creates a virtual AIA device.
|
||||
///
|
||||
fn create_vaia(&self, config: VaiaConfig) -> vm::Result<Arc<Mutex<dyn Vaia>>> {
|
||||
let aia_device = KvmAiaImsics::new(self, config)
|
||||
.map_err(|e| vm::HypervisorVmError::CreateVaia(anyhow!("Vaia error {:?}", e)))?;
|
||||
Ok(Arc::new(Mutex::new(aia_device)))
|
||||
}
|
||||
|
||||
///
|
||||
/// Registers an event to be signaled whenever a certain address is written to.
|
||||
///
|
||||
@@ -563,7 +667,7 @@ impl vm::Vm for KvmVm {
|
||||
// different `devid` to all the devices. Limitation is that at
|
||||
// most 256 segments can be supported.
|
||||
//
|
||||
let modified_devid = (cfg.devid & 0x00ff_0000) >> 8 | cfg.devid & 0xff;
|
||||
let modified_devid = ((cfg.devid & 0x00ff_0000) >> 8) | cfg.devid & 0xff;
|
||||
|
||||
kvm_route.flags = KVM_MSI_VALID_DEVID;
|
||||
kvm_route.u.msi.__bindgen_anon_1.devid = modified_devid;
|
||||
@@ -703,10 +807,13 @@ impl vm::Vm for KvmVm {
|
||||
/// Returns the preferred CPU target type which can be emulated by KVM on underlying host.
|
||||
///
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
fn get_preferred_target(&self, kvi: &mut VcpuInit) -> vm::Result<()> {
|
||||
fn get_preferred_target(&self, kvi: &mut crate::VcpuInit) -> vm::Result<()> {
|
||||
let mut kvm_kvi: kvm_bindings::kvm_vcpu_init = (*kvi).into();
|
||||
self.fd
|
||||
.get_preferred_target(kvi)
|
||||
.map_err(|e| vm::HypervisorVmError::GetPreferredTarget(e.into()))
|
||||
.get_preferred_target(&mut kvm_kvi)
|
||||
.map_err(|e| vm::HypervisorVmError::GetPreferredTarget(e.into()))?;
|
||||
*kvi = kvm_kvi.into();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
@@ -1025,6 +1132,26 @@ impl hypervisor::Hypervisor for KvmHypervisor {
|
||||
HypervisorType::Kvm
|
||||
}
|
||||
|
||||
///
|
||||
/// Create a Vm of a specific type using the underlying hypervisor, passing memory size
|
||||
/// Return a hypervisor-agnostic Vm trait object
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use hypervisor::kvm::KvmHypervisor;
|
||||
/// use hypervisor::kvm::KvmVm;
|
||||
/// let hypervisor = KvmHypervisor::new().unwrap();
|
||||
/// let vm = hypervisor.create_vm_with_type_and_memory(0).unwrap();
|
||||
/// ```
|
||||
fn create_vm_with_type_and_memory(
|
||||
&self,
|
||||
vm_type: u64,
|
||||
#[cfg(feature = "sev_snp")] _mem_size: u64,
|
||||
) -> hypervisor::Result<Arc<dyn vm::Vm>> {
|
||||
self.create_vm_with_type(vm_type)
|
||||
}
|
||||
|
||||
/// Create a KVM vm object of a specific VM type and return the object as Vm trait object
|
||||
///
|
||||
/// # Examples
|
||||
@@ -1078,7 +1205,7 @@ impl hypervisor::Hypervisor for KvmHypervisor {
|
||||
}))
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
|
||||
{
|
||||
Ok(Arc::new(KvmVm {
|
||||
fd: vm_fd,
|
||||
@@ -1161,6 +1288,7 @@ impl hypervisor::Hypervisor for KvmHypervisor {
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
|
||||
///
|
||||
/// Get the number of supported hardware breakpoints
|
||||
///
|
||||
@@ -1208,7 +1336,14 @@ impl cpu::Vcpu for KvmVcpu {
|
||||
/// Returns StandardRegisters with default value set
|
||||
///
|
||||
fn create_standard_regs(&self) -> StandardRegisters {
|
||||
kvm_bindings::kvm_regs::default().into()
|
||||
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
|
||||
{
|
||||
kvm_bindings::kvm_regs::default().into()
|
||||
}
|
||||
#[cfg(target_arch = "riscv64")]
|
||||
{
|
||||
kvm_bindings::kvm_riscv_core::default().into()
|
||||
}
|
||||
}
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
@@ -1349,6 +1484,77 @@ impl cpu::Vcpu for KvmVcpu {
|
||||
Ok(state.into())
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "riscv64")]
|
||||
///
|
||||
/// Returns the RISC-V vCPU core registers.
|
||||
/// The `KVM_GET_REGS` ioctl is not available on RISC-V 64-bit,
|
||||
/// `KVM_GET_ONE_REG` is used to get registers one by one.
|
||||
///
|
||||
fn get_regs(&self) -> cpu::Result<StandardRegisters> {
|
||||
let mut state = kvm_riscv_core::default();
|
||||
|
||||
/// Macro used to extract RISC-V register data from KVM Vcpu according
|
||||
/// to `$reg_name` provided to `state`.
|
||||
macro_rules! riscv64_get_one_reg_from_vcpu {
|
||||
(mode) => {
|
||||
let off = offset_of!(kvm_riscv_core, mode);
|
||||
let mut bytes = [0_u8; 8];
|
||||
self.fd
|
||||
.lock()
|
||||
.unwrap()
|
||||
.get_one_reg(riscv64_reg_id!(KVM_REG_RISCV_CORE, off), &mut bytes)
|
||||
.map_err(|e| cpu::HypervisorCpuError::GetRiscvCoreRegister(e.into()))?;
|
||||
state.mode = u64::from_le_bytes(bytes);
|
||||
};
|
||||
($reg_name:ident) => {
|
||||
let off = offset_of!(kvm_riscv_core, regs, user_regs_struct, $reg_name);
|
||||
let mut bytes = [0_u8; 8];
|
||||
self.fd
|
||||
.lock()
|
||||
.unwrap()
|
||||
.get_one_reg(riscv64_reg_id!(KVM_REG_RISCV_CORE, off), &mut bytes)
|
||||
.map_err(|e| cpu::HypervisorCpuError::GetRiscvCoreRegister(e.into()))?;
|
||||
state.regs.$reg_name = u64::from_le_bytes(bytes);
|
||||
};
|
||||
}
|
||||
|
||||
riscv64_get_one_reg_from_vcpu!(pc);
|
||||
riscv64_get_one_reg_from_vcpu!(ra);
|
||||
riscv64_get_one_reg_from_vcpu!(sp);
|
||||
riscv64_get_one_reg_from_vcpu!(gp);
|
||||
riscv64_get_one_reg_from_vcpu!(tp);
|
||||
riscv64_get_one_reg_from_vcpu!(t0);
|
||||
riscv64_get_one_reg_from_vcpu!(t1);
|
||||
riscv64_get_one_reg_from_vcpu!(t2);
|
||||
riscv64_get_one_reg_from_vcpu!(s0);
|
||||
riscv64_get_one_reg_from_vcpu!(s1);
|
||||
riscv64_get_one_reg_from_vcpu!(a0);
|
||||
riscv64_get_one_reg_from_vcpu!(a1);
|
||||
riscv64_get_one_reg_from_vcpu!(a2);
|
||||
riscv64_get_one_reg_from_vcpu!(a3);
|
||||
riscv64_get_one_reg_from_vcpu!(a4);
|
||||
riscv64_get_one_reg_from_vcpu!(a5);
|
||||
riscv64_get_one_reg_from_vcpu!(a6);
|
||||
riscv64_get_one_reg_from_vcpu!(a7);
|
||||
riscv64_get_one_reg_from_vcpu!(s2);
|
||||
riscv64_get_one_reg_from_vcpu!(s3);
|
||||
riscv64_get_one_reg_from_vcpu!(s4);
|
||||
riscv64_get_one_reg_from_vcpu!(s5);
|
||||
riscv64_get_one_reg_from_vcpu!(s6);
|
||||
riscv64_get_one_reg_from_vcpu!(s7);
|
||||
riscv64_get_one_reg_from_vcpu!(s8);
|
||||
riscv64_get_one_reg_from_vcpu!(s9);
|
||||
riscv64_get_one_reg_from_vcpu!(s10);
|
||||
riscv64_get_one_reg_from_vcpu!(s11);
|
||||
riscv64_get_one_reg_from_vcpu!(t3);
|
||||
riscv64_get_one_reg_from_vcpu!(t4);
|
||||
riscv64_get_one_reg_from_vcpu!(t5);
|
||||
riscv64_get_one_reg_from_vcpu!(t6);
|
||||
riscv64_get_one_reg_from_vcpu!(mode);
|
||||
|
||||
Ok(state.into())
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// Sets the vCPU general purpose registers using the `KVM_SET_REGS` ioctl.
|
||||
@@ -1483,6 +1689,81 @@ impl cpu::Vcpu for KvmVcpu {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "riscv64")]
|
||||
///
|
||||
/// Sets the RISC-V vCPU core registers.
|
||||
/// The `KVM_SET_REGS` ioctl is not available on RISC-V 64-bit,
|
||||
/// `KVM_SET_ONE_REG` is used to set registers one by one.
|
||||
///
|
||||
fn set_regs(&self, state: &StandardRegisters) -> cpu::Result<()> {
|
||||
// The function follows the exact identical order from `state`. Look there
|
||||
// for some additional info on registers.
|
||||
let kvm_regs_state: kvm_riscv_core = (*state).into();
|
||||
|
||||
/// Macro used to set value of specific RISC-V `$reg_name` stored in
|
||||
/// `state` to KVM Vcpu.
|
||||
macro_rules! riscv64_set_one_reg_to_vcpu {
|
||||
(mode) => {
|
||||
let off = offset_of!(kvm_riscv_core, mode);
|
||||
self.fd
|
||||
.lock()
|
||||
.unwrap()
|
||||
.set_one_reg(
|
||||
riscv64_reg_id!(KVM_REG_RISCV_CORE, off),
|
||||
&kvm_regs_state.mode.to_le_bytes(),
|
||||
)
|
||||
.map_err(|e| cpu::HypervisorCpuError::SetRiscvCoreRegister(e.into()))?;
|
||||
};
|
||||
($reg_name:ident) => {
|
||||
let off = offset_of!(kvm_riscv_core, regs, user_regs_struct, $reg_name);
|
||||
self.fd
|
||||
.lock()
|
||||
.unwrap()
|
||||
.set_one_reg(
|
||||
riscv64_reg_id!(KVM_REG_RISCV_CORE, off),
|
||||
&kvm_regs_state.regs.$reg_name.to_le_bytes(),
|
||||
)
|
||||
.map_err(|e| cpu::HypervisorCpuError::SetRiscvCoreRegister(e.into()))?;
|
||||
};
|
||||
}
|
||||
|
||||
riscv64_set_one_reg_to_vcpu!(pc);
|
||||
riscv64_set_one_reg_to_vcpu!(ra);
|
||||
riscv64_set_one_reg_to_vcpu!(sp);
|
||||
riscv64_set_one_reg_to_vcpu!(gp);
|
||||
riscv64_set_one_reg_to_vcpu!(tp);
|
||||
riscv64_set_one_reg_to_vcpu!(t0);
|
||||
riscv64_set_one_reg_to_vcpu!(t1);
|
||||
riscv64_set_one_reg_to_vcpu!(t2);
|
||||
riscv64_set_one_reg_to_vcpu!(s0);
|
||||
riscv64_set_one_reg_to_vcpu!(s1);
|
||||
riscv64_set_one_reg_to_vcpu!(a0);
|
||||
riscv64_set_one_reg_to_vcpu!(a1);
|
||||
riscv64_set_one_reg_to_vcpu!(a2);
|
||||
riscv64_set_one_reg_to_vcpu!(a3);
|
||||
riscv64_set_one_reg_to_vcpu!(a4);
|
||||
riscv64_set_one_reg_to_vcpu!(a5);
|
||||
riscv64_set_one_reg_to_vcpu!(a6);
|
||||
riscv64_set_one_reg_to_vcpu!(a7);
|
||||
riscv64_set_one_reg_to_vcpu!(s2);
|
||||
riscv64_set_one_reg_to_vcpu!(s3);
|
||||
riscv64_set_one_reg_to_vcpu!(s4);
|
||||
riscv64_set_one_reg_to_vcpu!(s5);
|
||||
riscv64_set_one_reg_to_vcpu!(s6);
|
||||
riscv64_set_one_reg_to_vcpu!(s7);
|
||||
riscv64_set_one_reg_to_vcpu!(s8);
|
||||
riscv64_set_one_reg_to_vcpu!(s9);
|
||||
riscv64_set_one_reg_to_vcpu!(s10);
|
||||
riscv64_set_one_reg_to_vcpu!(s11);
|
||||
riscv64_set_one_reg_to_vcpu!(t3);
|
||||
riscv64_set_one_reg_to_vcpu!(t4);
|
||||
riscv64_set_one_reg_to_vcpu!(t5);
|
||||
riscv64_set_one_reg_to_vcpu!(t6);
|
||||
riscv64_set_one_reg_to_vcpu!(mode);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// Returns the vCPU special registers.
|
||||
@@ -1812,6 +2093,7 @@ impl cpu::Vcpu for KvmVcpu {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "riscv64"))]
|
||||
///
|
||||
/// Sets debug registers to set hardware breakpoints and/or enable single step.
|
||||
///
|
||||
@@ -1869,11 +2151,70 @@ impl cpu::Vcpu for KvmVcpu {
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
fn vcpu_init(&self, kvi: &VcpuInit) -> cpu::Result<()> {
|
||||
fn vcpu_get_finalized_features(&self) -> i32 {
|
||||
kvm_bindings::KVM_ARM_VCPU_SVE as i32
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
fn vcpu_set_processor_features(
|
||||
&self,
|
||||
vm: &Arc<dyn crate::Vm>,
|
||||
kvi: &mut crate::VcpuInit,
|
||||
id: u8,
|
||||
) -> cpu::Result<()> {
|
||||
use std::arch::is_aarch64_feature_detected;
|
||||
#[allow(clippy::nonminimal_bool)]
|
||||
let sve_supported =
|
||||
is_aarch64_feature_detected!("sve") || is_aarch64_feature_detected!("sve2");
|
||||
|
||||
let mut kvm_kvi: kvm_bindings::kvm_vcpu_init = (*kvi).into();
|
||||
|
||||
// We already checked that the capability is supported.
|
||||
kvm_kvi.features[0] |= 1 << kvm_bindings::KVM_ARM_VCPU_PSCI_0_2;
|
||||
if vm
|
||||
.as_any()
|
||||
.downcast_ref::<crate::kvm::KvmVm>()
|
||||
.unwrap()
|
||||
.check_extension(Cap::ArmPmuV3)
|
||||
{
|
||||
kvm_kvi.features[0] |= 1 << kvm_bindings::KVM_ARM_VCPU_PMU_V3;
|
||||
}
|
||||
|
||||
if sve_supported
|
||||
&& vm
|
||||
.as_any()
|
||||
.downcast_ref::<crate::kvm::KvmVm>()
|
||||
.unwrap()
|
||||
.check_extension(Cap::ArmSve)
|
||||
{
|
||||
kvm_kvi.features[0] |= 1 << kvm_bindings::KVM_ARM_VCPU_SVE;
|
||||
}
|
||||
|
||||
// Non-boot cpus are powered off initially.
|
||||
if id > 0 {
|
||||
kvm_kvi.features[0] |= 1 << kvm_bindings::KVM_ARM_VCPU_POWER_OFF;
|
||||
}
|
||||
|
||||
*kvi = kvm_kvi.into();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
///
|
||||
/// Return VcpuInit with default value set
|
||||
///
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
fn create_vcpu_init(&self) -> crate::VcpuInit {
|
||||
kvm_bindings::kvm_vcpu_init::default().into()
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
fn vcpu_init(&self, kvi: &crate::VcpuInit) -> cpu::Result<()> {
|
||||
let kvm_kvi: kvm_bindings::kvm_vcpu_init = (*kvi).into();
|
||||
self.fd
|
||||
.lock()
|
||||
.unwrap()
|
||||
.vcpu_init(kvi)
|
||||
.vcpu_init(&kvm_kvi)
|
||||
.map_err(|e| cpu::HypervisorCpuError::VcpuInit(e.into()))
|
||||
}
|
||||
|
||||
@@ -1886,17 +2227,20 @@ impl cpu::Vcpu for KvmVcpu {
|
||||
.map_err(|e| cpu::HypervisorCpuError::VcpuFinalize(e.into()))
|
||||
}
|
||||
|
||||
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
|
||||
///
|
||||
/// Gets a list of the guest registers that are supported for the
|
||||
/// KVM_GET_ONE_REG/KVM_SET_ONE_REG calls.
|
||||
///
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
fn get_reg_list(&self, reg_list: &mut RegList) -> cpu::Result<()> {
|
||||
let mut kvm_reg_list: kvm_bindings::RegList = reg_list.clone().into();
|
||||
self.fd
|
||||
.lock()
|
||||
.unwrap()
|
||||
.get_reg_list(reg_list)
|
||||
.map_err(|e| cpu::HypervisorCpuError::GetRegList(e.into()))
|
||||
.get_reg_list(&mut kvm_reg_list)
|
||||
.map_err(|e: kvm_ioctls::Error| cpu::HypervisorCpuError::GetRegList(e.into()))?;
|
||||
*reg_list = kvm_reg_list.into();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
///
|
||||
@@ -1934,6 +2278,14 @@ impl cpu::Vcpu for KvmVcpu {
|
||||
Ok(u64::from_le_bytes(bytes))
|
||||
}
|
||||
|
||||
///
|
||||
/// Gets the value of a non-core register
|
||||
///
|
||||
#[cfg(target_arch = "riscv64")]
|
||||
fn get_non_core_reg(&self, _non_core_reg: u32) -> cpu::Result<u64> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
///
|
||||
/// Configure core registers for a given CPU.
|
||||
///
|
||||
@@ -1994,6 +2346,49 @@ impl cpu::Vcpu for KvmVcpu {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "riscv64")]
|
||||
///
|
||||
/// Configure registers for a given RISC-V CPU.
|
||||
///
|
||||
fn setup_regs(&self, cpu_id: u8, boot_ip: u64, fdt_start: u64) -> cpu::Result<()> {
|
||||
// Setting the A0 () to the hartid of this CPU.
|
||||
let a0 = offset_of!(kvm_riscv_core, regs, user_regs_struct, a0);
|
||||
self.fd
|
||||
.lock()
|
||||
.unwrap()
|
||||
.set_one_reg(
|
||||
riscv64_reg_id!(KVM_REG_RISCV_CORE, a0),
|
||||
&u64::from(cpu_id).to_le_bytes(),
|
||||
)
|
||||
.map_err(|e| cpu::HypervisorCpuError::SetRiscvCoreRegister(e.into()))?;
|
||||
|
||||
// Setting the PC (Processor Counter) to the current program address (kernel address).
|
||||
let pc = offset_of!(kvm_riscv_core, regs, user_regs_struct, pc);
|
||||
self.fd
|
||||
.lock()
|
||||
.unwrap()
|
||||
.set_one_reg(
|
||||
riscv64_reg_id!(KVM_REG_RISCV_CORE, pc),
|
||||
&boot_ip.to_le_bytes(),
|
||||
)
|
||||
.map_err(|e| cpu::HypervisorCpuError::SetRiscvCoreRegister(e.into()))?;
|
||||
|
||||
// Last mandatory thing to set -> the address pointing to the FDT (also called DTB).
|
||||
// "The device tree blob (dtb) must be placed on an 8-byte boundary and must
|
||||
// not exceed 64 kilobytes in size." -> https://www.kernel.org/doc/Documentation/arch/riscv/boot.txt.
|
||||
let a1 = offset_of!(kvm_riscv_core, regs, user_regs_struct, a1);
|
||||
self.fd
|
||||
.lock()
|
||||
.unwrap()
|
||||
.set_one_reg(
|
||||
riscv64_reg_id!(KVM_REG_RISCV_CORE, a1),
|
||||
&fdt_start.to_le_bytes(),
|
||||
)
|
||||
.map_err(|e| cpu::HypervisorCpuError::SetRiscvCoreRegister(e.into()))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// Get the current CPU state
|
||||
@@ -2133,8 +2528,8 @@ impl cpu::Vcpu for KvmVcpu {
|
||||
// Get systerm register
|
||||
// Call KVM_GET_REG_LIST to get all registers available to the guest.
|
||||
// For ArmV8 there are around 500 registers.
|
||||
let mut sys_regs: Vec<Register> = Vec::new();
|
||||
let mut reg_list = RegList::new(500).unwrap();
|
||||
let mut sys_regs: Vec<kvm_bindings::kvm_one_reg> = Vec::new();
|
||||
let mut reg_list = kvm_bindings::RegList::new(500).unwrap();
|
||||
self.fd
|
||||
.lock()
|
||||
.unwrap()
|
||||
@@ -2171,6 +2566,66 @@ impl cpu::Vcpu for KvmVcpu {
|
||||
Ok(state.into())
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "riscv64")]
|
||||
///
|
||||
/// Get the current RISC-V 64-bit CPU state
|
||||
///
|
||||
fn state(&self) -> cpu::Result<CpuState> {
|
||||
let mut state = VcpuKvmState {
|
||||
mp_state: self.get_mp_state()?.into(),
|
||||
..Default::default()
|
||||
};
|
||||
// Get core registers
|
||||
state.core_regs = self.get_regs()?.into();
|
||||
|
||||
// Get non-core register
|
||||
// Call KVM_GET_REG_LIST to get all registers available to the guest.
|
||||
// For RISC-V 64-bit there are around 200 registers.
|
||||
let mut sys_regs: Vec<kvm_bindings::kvm_one_reg> = Vec::new();
|
||||
let mut reg_list = kvm_bindings::RegList::new(200).unwrap();
|
||||
self.fd
|
||||
.lock()
|
||||
.unwrap()
|
||||
.get_reg_list(&mut reg_list)
|
||||
.map_err(|e| cpu::HypervisorCpuError::GetRegList(e.into()))?;
|
||||
|
||||
// At this point reg_list should contain:
|
||||
// - core registers
|
||||
// - config registers
|
||||
// - timer registers
|
||||
// - control and status registers
|
||||
// - AIA control and status registers
|
||||
// - smstateen control and status registers
|
||||
// - sbi_sta control and status registers.
|
||||
//
|
||||
// The register list contains the number of registers and their ids. We
|
||||
// will be needing to call KVM_GET_ONE_REG on each id in order to save
|
||||
// all of them. We carve out from the list the core registers which are
|
||||
// represented in the kernel by `kvm_riscv_core` structure and for which
|
||||
// we can calculate the id based on the offset in the structure.
|
||||
reg_list.retain(|regid| is_non_core_register(*regid));
|
||||
|
||||
// Now, for the rest of the registers left in the previously fetched
|
||||
// register list, we are simply calling KVM_GET_ONE_REG.
|
||||
let indices = reg_list.as_slice();
|
||||
for index in indices.iter() {
|
||||
let mut bytes = [0_u8; 8];
|
||||
self.fd
|
||||
.lock()
|
||||
.unwrap()
|
||||
.get_one_reg(*index, &mut bytes)
|
||||
.map_err(|e| cpu::HypervisorCpuError::GetSysRegister(e.into()))?;
|
||||
sys_regs.push(kvm_bindings::kvm_one_reg {
|
||||
id: *index,
|
||||
addr: u64::from_le_bytes(bytes),
|
||||
});
|
||||
}
|
||||
|
||||
state.non_core_regs = sys_regs;
|
||||
|
||||
Ok(state.into())
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// Restore the previously saved CPU state
|
||||
@@ -2283,6 +2738,28 @@ impl cpu::Vcpu for KvmVcpu {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "riscv64")]
|
||||
///
|
||||
/// Restore the previously saved RISC-V 64-bit CPU state
|
||||
///
|
||||
fn set_state(&self, state: &CpuState) -> cpu::Result<()> {
|
||||
let state: VcpuKvmState = state.clone().into();
|
||||
// Set core registers
|
||||
self.set_regs(&state.core_regs.into())?;
|
||||
// Set system registers
|
||||
for reg in &state.non_core_regs {
|
||||
self.fd
|
||||
.lock()
|
||||
.unwrap()
|
||||
.set_one_reg(reg.id, ®.addr.to_le_bytes())
|
||||
.map_err(|e| cpu::HypervisorCpuError::SetSysRegister(e.into()))?;
|
||||
}
|
||||
|
||||
self.set_mp_state(state.mp_state.into())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
///
|
||||
/// Initialize TDX for this CPU
|
||||
///
|
||||
@@ -2548,3 +3025,58 @@ impl KvmVcpu {
|
||||
.map_err(|e| cpu::HypervisorCpuError::SetVcpuEvents(e.into()))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
#[cfg(target_arch = "riscv64")]
|
||||
fn test_get_and_set_regs() {
|
||||
use super::*;
|
||||
|
||||
let kvm = KvmHypervisor::new().unwrap();
|
||||
let hypervisor = Arc::new(kvm);
|
||||
let vm = hypervisor.create_vm().expect("new VM fd creation failed");
|
||||
let vcpu0 = vm.create_vcpu(0, None).unwrap();
|
||||
|
||||
let core_regs = StandardRegisters::from(kvm_riscv_core {
|
||||
regs: user_regs_struct {
|
||||
pc: 0x00,
|
||||
ra: 0x01,
|
||||
sp: 0x02,
|
||||
gp: 0x03,
|
||||
tp: 0x04,
|
||||
t0: 0x05,
|
||||
t1: 0x06,
|
||||
t2: 0x07,
|
||||
s0: 0x08,
|
||||
s1: 0x09,
|
||||
a0: 0x0a,
|
||||
a1: 0x0b,
|
||||
a2: 0x0c,
|
||||
a3: 0x0d,
|
||||
a4: 0x0e,
|
||||
a5: 0x0f,
|
||||
a6: 0x10,
|
||||
a7: 0x11,
|
||||
s2: 0x12,
|
||||
s3: 0x13,
|
||||
s4: 0x14,
|
||||
s5: 0x15,
|
||||
s6: 0x16,
|
||||
s7: 0x17,
|
||||
s8: 0x18,
|
||||
s9: 0x19,
|
||||
s10: 0x1a,
|
||||
s11: 0x1b,
|
||||
t3: 0x1c,
|
||||
t4: 0x1d,
|
||||
t5: 0x1e,
|
||||
t6: 0x1f,
|
||||
},
|
||||
mode: 0x00,
|
||||
});
|
||||
|
||||
vcpu0.set_regs(&core_regs).unwrap();
|
||||
assert_eq!(vcpu0.get_regs().unwrap(), core_regs);
|
||||
}
|
||||
}
|
||||
|
||||
275
hypervisor/src/kvm/riscv64/aia.rs
Normal file
275
hypervisor/src/kvm/riscv64/aia.rs
Normal file
@@ -0,0 +1,275 @@
|
||||
// Copyright © 2024 Institute of Software, CAS. All rights reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::any::Any;
|
||||
|
||||
use kvm_ioctls::DeviceFd;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::arch::riscv64::aia::{Error, Result, Vaia, VaiaConfig};
|
||||
use crate::device::HypervisorDeviceError;
|
||||
use crate::kvm::KvmVm;
|
||||
use crate::Vm;
|
||||
|
||||
pub struct KvmAiaImsics {
|
||||
/// The KVM device for the Aia
|
||||
device: DeviceFd,
|
||||
|
||||
/// AIA APLIC address
|
||||
aplic_addr: u64,
|
||||
|
||||
/// AIA IMSIC address
|
||||
imsic_addr: u64,
|
||||
|
||||
/// Number of CPUs handled by the device
|
||||
vcpu_count: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, Serialize, Deserialize)]
|
||||
pub struct AiaImsicsState {}
|
||||
|
||||
impl KvmAiaImsics {
|
||||
/// Device trees specific constants
|
||||
|
||||
fn version() -> u32 {
|
||||
kvm_bindings::kvm_device_type_KVM_DEV_TYPE_RISCV_AIA
|
||||
}
|
||||
|
||||
/// Setup the device-specific attributes
|
||||
fn init_device_attributes(&mut self, nr_irqs: u32) -> Result<()> {
|
||||
// AIA part attributes
|
||||
// Getting the working mode of RISC-V AIA, defaults to EMUL, passible
|
||||
// variants are EMUL, HW_ACCL, AUTO
|
||||
let mut aia_mode = kvm_bindings::KVM_DEV_RISCV_AIA_MODE_EMUL;
|
||||
Self::get_device_attribute(
|
||||
&self.device,
|
||||
kvm_bindings::KVM_DEV_RISCV_AIA_GRP_CONFIG,
|
||||
u64::from(kvm_bindings::KVM_DEV_RISCV_AIA_CONFIG_MODE),
|
||||
&mut aia_mode as *mut u32 as u64,
|
||||
0,
|
||||
)?;
|
||||
|
||||
// Report AIA MODE
|
||||
|
||||
// Setting up the number of wired interrupt sources
|
||||
Self::set_device_attribute(
|
||||
&self.device,
|
||||
kvm_bindings::KVM_DEV_RISCV_AIA_GRP_CONFIG,
|
||||
u64::from(kvm_bindings::KVM_DEV_RISCV_AIA_CONFIG_SRCS),
|
||||
&nr_irqs as *const u32 as u64,
|
||||
0,
|
||||
)?;
|
||||
|
||||
// Getting the number of ids
|
||||
let mut aia_nr_ids: u32 = 0;
|
||||
Self::get_device_attribute(
|
||||
&self.device,
|
||||
kvm_bindings::KVM_DEV_RISCV_AIA_GRP_CONFIG,
|
||||
u64::from(kvm_bindings::KVM_DEV_RISCV_AIA_CONFIG_IDS),
|
||||
&mut aia_nr_ids as *mut u32 as u64,
|
||||
0,
|
||||
)?;
|
||||
|
||||
// Report NR_IDS
|
||||
|
||||
// Setting up hart_bits
|
||||
let max_hart_index = self.vcpu_count as u64 - 1;
|
||||
let hart_bits = std::cmp::max(64 - max_hart_index.leading_zeros(), 1);
|
||||
Self::set_device_attribute(
|
||||
&self.device,
|
||||
kvm_bindings::KVM_DEV_RISCV_AIA_GRP_CONFIG,
|
||||
u64::from(kvm_bindings::KVM_DEV_RISCV_AIA_CONFIG_HART_BITS),
|
||||
&hart_bits as *const u32 as u64,
|
||||
0,
|
||||
)?;
|
||||
|
||||
// Designate addresses of APLIC and IMSICS
|
||||
|
||||
// Setting up RISC-V APLIC
|
||||
Self::set_device_attribute(
|
||||
&self.device,
|
||||
kvm_bindings::KVM_DEV_RISCV_AIA_GRP_ADDR,
|
||||
u64::from(kvm_bindings::KVM_DEV_RISCV_AIA_ADDR_APLIC),
|
||||
&self.aplic_addr as *const u64 as u64,
|
||||
0,
|
||||
)?;
|
||||
|
||||
// Helpers to calculate address and attribute of IMSIC of each vCPU
|
||||
let riscv_imsic_addr_of = |cpu_index: u32| -> u64 {
|
||||
self.imsic_addr + (cpu_index * kvm_bindings::KVM_DEV_RISCV_IMSIC_SIZE) as u64
|
||||
};
|
||||
let riscv_imsic_attr_of = |cpu_index: u32| -> u64 { cpu_index as u64 + 1 };
|
||||
|
||||
// Setting up RISC-V IMSICs
|
||||
for cpu_index in 0..self.vcpu_count {
|
||||
let cpu_imsic_addr = riscv_imsic_addr_of(cpu_index);
|
||||
Self::set_device_attribute(
|
||||
&self.device,
|
||||
kvm_bindings::KVM_DEV_RISCV_AIA_GRP_ADDR,
|
||||
riscv_imsic_attr_of(cpu_index),
|
||||
&cpu_imsic_addr as *const u64 as u64,
|
||||
0,
|
||||
)?;
|
||||
}
|
||||
|
||||
// Finalizing the AIA device
|
||||
Self::set_device_attribute(
|
||||
&self.device,
|
||||
kvm_bindings::KVM_DEV_RISCV_AIA_GRP_CTRL,
|
||||
u64::from(kvm_bindings::KVM_DEV_RISCV_AIA_CTRL_INIT),
|
||||
0,
|
||||
0,
|
||||
)
|
||||
}
|
||||
|
||||
/// Create a KVM Vaia device
|
||||
fn create_device(vm: &KvmVm) -> Result<DeviceFd> {
|
||||
let mut aia_device = kvm_bindings::kvm_create_device {
|
||||
type_: Self::version(),
|
||||
fd: 0,
|
||||
flags: 0,
|
||||
};
|
||||
|
||||
let device_fd = vm
|
||||
.create_device(&mut aia_device)
|
||||
.map_err(Error::CreateAia)?;
|
||||
|
||||
// We know for sure this is a KVM fd
|
||||
Ok(device_fd.to_kvm().unwrap())
|
||||
}
|
||||
|
||||
/// Get an AIA device attribute
|
||||
fn get_device_attribute(
|
||||
device: &DeviceFd,
|
||||
group: u32,
|
||||
attr: u64,
|
||||
addr: u64,
|
||||
flags: u32,
|
||||
) -> Result<()> {
|
||||
let mut attr = kvm_bindings::kvm_device_attr {
|
||||
flags,
|
||||
group,
|
||||
attr,
|
||||
addr,
|
||||
};
|
||||
// SAFETY: attr.addr is safe to write to.
|
||||
unsafe {
|
||||
device.get_device_attr(&mut attr).map_err(|e| {
|
||||
Error::GetDeviceAttribute(HypervisorDeviceError::GetDeviceAttribute(e.into()))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Set an AIA device attribute
|
||||
fn set_device_attribute(
|
||||
device: &DeviceFd,
|
||||
group: u32,
|
||||
attr: u64,
|
||||
addr: u64,
|
||||
flags: u32,
|
||||
) -> Result<()> {
|
||||
let attr = kvm_bindings::kvm_device_attr {
|
||||
flags,
|
||||
group,
|
||||
attr,
|
||||
addr,
|
||||
};
|
||||
device.set_device_attr(&attr).map_err(|e| {
|
||||
Error::SetDeviceAttribute(HypervisorDeviceError::SetDeviceAttribute(e.into()))
|
||||
})
|
||||
}
|
||||
|
||||
/// Method to initialize the AIA device
|
||||
pub fn new(vm: &dyn Vm, config: VaiaConfig) -> Result<KvmAiaImsics> {
|
||||
// This is inside KVM module
|
||||
let vm = vm.as_any().downcast_ref::<KvmVm>().expect("Wrong VM type?");
|
||||
|
||||
let vaia = Self::create_device(vm)?;
|
||||
|
||||
let mut aia_device = KvmAiaImsics {
|
||||
device: vaia,
|
||||
vcpu_count: config.vcpu_count,
|
||||
aplic_addr: config.aplic_addr,
|
||||
imsic_addr: config.imsic_addr,
|
||||
};
|
||||
|
||||
aia_device.init_device_attributes(config.nr_irqs)?;
|
||||
|
||||
Ok(aia_device)
|
||||
}
|
||||
}
|
||||
|
||||
impl Vaia for KvmAiaImsics {
|
||||
fn aplic_compatibility(&self) -> &str {
|
||||
"riscv,aplic"
|
||||
}
|
||||
|
||||
fn aplic_properties(&self) -> [u32; 4] {
|
||||
[
|
||||
0,
|
||||
self.aplic_addr as u32,
|
||||
0,
|
||||
kvm_bindings::KVM_DEV_RISCV_APLIC_SIZE,
|
||||
]
|
||||
}
|
||||
|
||||
fn imsic_compatibility(&self) -> &str {
|
||||
"riscv,imsics"
|
||||
}
|
||||
|
||||
fn imsic_properties(&self) -> [u32; 4] {
|
||||
[
|
||||
0,
|
||||
self.imsic_addr as u32,
|
||||
0,
|
||||
kvm_bindings::KVM_DEV_RISCV_IMSIC_SIZE * self.vcpu_count,
|
||||
]
|
||||
}
|
||||
|
||||
fn vcpu_count(&self) -> u32 {
|
||||
self.vcpu_count
|
||||
}
|
||||
|
||||
fn msi_compatible(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn as_any_concrete_mut(&mut self) -> &mut dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
/// Save the state of AIA.
|
||||
fn state(&self) -> Result<AiaImsicsState> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
/// Restore the state of AIA_IMSICs.
|
||||
fn set_state(&mut self, _state: &AiaImsicsState) -> Result<()> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::arch::riscv64::aia::VaiaConfig;
|
||||
use crate::kvm::KvmAiaImsics;
|
||||
|
||||
fn create_test_vaia_config() -> VaiaConfig {
|
||||
VaiaConfig {
|
||||
vcpu_count: 1,
|
||||
aplic_addr: 0xd000000,
|
||||
imsic_addr: 0x2800000,
|
||||
nr_irqs: 256,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_aia() {
|
||||
let hv = crate::new().unwrap();
|
||||
let vm = hv.create_vm().unwrap();
|
||||
let _vcpu = vm.create_vcpu(0, None).unwrap();
|
||||
|
||||
assert!(KvmAiaImsics::new(&*vm, create_test_vaia_config()).is_ok());
|
||||
}
|
||||
}
|
||||
162
hypervisor/src/kvm/riscv64/mod.rs
Normal file
162
hypervisor/src/kvm/riscv64/mod.rs
Normal file
@@ -0,0 +1,162 @@
|
||||
// Copyright © 2024 Institute of Software, CAS. All rights reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod aia;
|
||||
|
||||
use kvm_bindings::{
|
||||
kvm_mp_state, kvm_one_reg, kvm_riscv_core, KVM_REG_RISCV_CORE, KVM_REG_RISCV_TYPE_MASK,
|
||||
KVM_REG_SIZE_MASK, KVM_REG_SIZE_U64,
|
||||
};
|
||||
pub use kvm_ioctls::{Cap, Kvm};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::kvm::{KvmError, KvmResult};
|
||||
|
||||
// This macro gets the offset of a structure (i.e `str`) member (i.e `field`) without having
|
||||
// an instance of that structure.
|
||||
#[macro_export]
|
||||
macro_rules! _offset_of {
|
||||
($str:ty, $field:ident) => {{
|
||||
let tmp: std::mem::MaybeUninit<$str> = std::mem::MaybeUninit::uninit();
|
||||
let base = tmp.as_ptr();
|
||||
|
||||
// Avoid warnings when nesting `unsafe` blocks.
|
||||
#[allow(unused_unsafe)]
|
||||
// SAFETY: The pointer is valid and aligned, just not initialised. Using `addr_of` ensures
|
||||
// that we don't actually read from `base` (which would be UB) nor create an intermediate
|
||||
// reference.
|
||||
let member = unsafe { core::ptr::addr_of!((*base).$field) } as *const u8;
|
||||
|
||||
// Avoid warnings when nesting `unsafe` blocks.
|
||||
#[allow(unused_unsafe)]
|
||||
// SAFETY: The two pointers are within the same allocated object `tmp`. All requirements
|
||||
// from offset_from are upheld.
|
||||
unsafe {
|
||||
member.offset_from(base as *const u8) as usize
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! offset_of {
|
||||
($reg_struct:ty, $field:ident) => {
|
||||
$crate::_offset_of!($reg_struct, $field)
|
||||
};
|
||||
($outer_reg_struct:ty, $outer_field:ident, $($inner_reg_struct:ty, $inner_field:ident), +) => {
|
||||
$crate::_offset_of!($outer_reg_struct, $outer_field) + offset_of!($($inner_reg_struct, $inner_field), +)
|
||||
};
|
||||
}
|
||||
|
||||
// Following are macros that help with getting the ID of a riscv64 register, including config registers, core registers and timer registers.
|
||||
// The register of core registers are wrapped in the `user_regs_struct` structure. See:
|
||||
// https://elixir.bootlin.com/linux/v6.10/source/arch/riscv/include/uapi/asm/kvm.h#L62
|
||||
|
||||
// Get the ID of a register
|
||||
#[macro_export]
|
||||
macro_rules! riscv64_reg_id {
|
||||
($reg_type: tt, $offset: tt) => {
|
||||
// The core registers of an riscv64 machine are represented
|
||||
// in kernel by the `kvm_riscv_core` structure:
|
||||
//
|
||||
// struct kvm_riscv_core {
|
||||
// struct user_regs_struct regs;
|
||||
// unsigned long mode;
|
||||
// };
|
||||
//
|
||||
// struct user_regs_struct {
|
||||
// unsigned long pc;
|
||||
// unsigned long ra;
|
||||
// unsigned long sp;
|
||||
// unsigned long gp;
|
||||
// unsigned long tp;
|
||||
// unsigned long t0;
|
||||
// unsigned long t1;
|
||||
// unsigned long t2;
|
||||
// unsigned long s0;
|
||||
// unsigned long s1;
|
||||
// unsigned long a0;
|
||||
// unsigned long a1;
|
||||
// unsigned long a2;
|
||||
// unsigned long a3;
|
||||
// unsigned long a4;
|
||||
// unsigned long a5;
|
||||
// unsigned long a6;
|
||||
// unsigned long a7;
|
||||
// unsigned long s2;
|
||||
// unsigned long s3;
|
||||
// unsigned long s4;
|
||||
// unsigned long s5;
|
||||
// unsigned long s6;
|
||||
// unsigned long s7;
|
||||
// unsigned long s8;
|
||||
// unsigned long s9;
|
||||
// unsigned long s10;
|
||||
// unsigned long s11;
|
||||
// unsigned long t3;
|
||||
// unsigned long t4;
|
||||
// unsigned long t5;
|
||||
// unsigned long t6;
|
||||
// };
|
||||
// The id of a core register can be obtained like this: offset = id &
|
||||
// ~(KVM_REG_ARCH_MASK | KVM_REG_SIZE_MASK | KVM_REG_RISCV_CORE). Thus,
|
||||
// id = KVM_REG_RISCV | KVM_REG_SIZE_U64 | KVM_REG_RISCV_CORE | offset
|
||||
//
|
||||
// To generalize, the id of a register can be obtained by:
|
||||
// id = KVM_REG_RISCV | KVM_REG_SIZE_U64 |
|
||||
// KVM_REG_RISCV_CORE/KVM_REG_RISCV_CONFIG/KVM_REG_RISCV_TIMER |
|
||||
// offset
|
||||
kvm_bindings::KVM_REG_RISCV as u64
|
||||
| u64::from($reg_type)
|
||||
| u64::from(kvm_bindings::KVM_REG_SIZE_U64)
|
||||
| (($offset / std::mem::size_of::<u64>()) as u64)
|
||||
};
|
||||
}
|
||||
|
||||
/// Specifies whether a particular register is a core register or not.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `regid` - The index of the register we are checking.
|
||||
pub fn is_non_core_register(regid: u64) -> bool {
|
||||
if (regid & KVM_REG_RISCV_TYPE_MASK as u64) == KVM_REG_RISCV_CORE as u64 {
|
||||
return false;
|
||||
}
|
||||
|
||||
let size = regid & KVM_REG_SIZE_MASK;
|
||||
|
||||
assert!(
|
||||
size == KVM_REG_SIZE_U64,
|
||||
"Unexpected register size for system register {size}"
|
||||
);
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
pub fn check_required_kvm_extensions(kvm: &Kvm) -> KvmResult<()> {
|
||||
macro_rules! check_extension {
|
||||
($cap:expr) => {
|
||||
if !kvm.check_extension($cap) {
|
||||
return Err(KvmError::CapabilityMissing($cap));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// SetGuestDebug is required but some kernels have it implemented without the capability flag.
|
||||
check_extension!(Cap::ImmediateExit);
|
||||
check_extension!(Cap::Ioeventfd);
|
||||
check_extension!(Cap::Irqchip);
|
||||
check_extension!(Cap::Irqfd);
|
||||
check_extension!(Cap::IrqRouting);
|
||||
check_extension!(Cap::MpState);
|
||||
check_extension!(Cap::OneReg);
|
||||
check_extension!(Cap::UserMemory);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, Serialize, Deserialize)]
|
||||
pub struct VcpuKvmState {
|
||||
pub mp_state: kvm_mp_state,
|
||||
pub core_regs: kvm_riscv_core,
|
||||
pub non_core_regs: Vec<kvm_one_reg>,
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
// Copyright © 2024 Institute of Software, CAS. All rights reserved.
|
||||
//
|
||||
// Copyright © 2019 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
|
||||
@@ -16,6 +18,7 @@
|
||||
//!
|
||||
//! - x86_64
|
||||
//! - arm64
|
||||
//! - riscv64 (experimental)
|
||||
//!
|
||||
|
||||
#[macro_use]
|
||||
@@ -57,6 +60,8 @@ pub use cpu::{HypervisorCpuError, Vcpu, VmExit};
|
||||
pub use device::HypervisorDeviceError;
|
||||
#[cfg(all(feature = "kvm", target_arch = "aarch64"))]
|
||||
pub use kvm::{aarch64, GicState};
|
||||
#[cfg(all(feature = "kvm", target_arch = "riscv64"))]
|
||||
pub use kvm::{riscv64, AiaState};
|
||||
pub use vm::{
|
||||
DataMatch, HypervisorVmError, InterruptSourceConfig, LegacyIrqSourceConfig, MsiIrqSourceConfig,
|
||||
Vm, VmOps,
|
||||
@@ -158,7 +163,7 @@ pub enum IoEventAddress {
|
||||
pub enum CpuState {
|
||||
#[cfg(feature = "kvm")]
|
||||
Kvm(kvm::VcpuKvmState),
|
||||
#[cfg(all(feature = "mshv", target_arch = "x86_64"))]
|
||||
#[cfg(feature = "mshv")]
|
||||
Mshv(mshv::VcpuMshvState),
|
||||
}
|
||||
|
||||
@@ -192,10 +197,34 @@ pub enum IrqRoutingEntry {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub enum StandardRegisters {
|
||||
pub enum VcpuInit {
|
||||
#[cfg(all(feature = "kvm", target_arch = "aarch64"))]
|
||||
Kvm(kvm_bindings::kvm_vcpu_init),
|
||||
#[cfg(all(feature = "mshv", target_arch = "aarch64"))]
|
||||
Mshv(mshv_bindings::MshvVcpuInit),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum RegList {
|
||||
#[cfg(all(feature = "kvm", any(target_arch = "aarch64", target_arch = "riscv64")))]
|
||||
Kvm(kvm_bindings::RegList),
|
||||
#[cfg(all(feature = "mshv", target_arch = "aarch64"))]
|
||||
Mshv(mshv_bindings::MshvRegList),
|
||||
}
|
||||
|
||||
pub enum Register {
|
||||
#[cfg(feature = "kvm")]
|
||||
Kvm(kvm_bindings::kvm_one_reg),
|
||||
}
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub enum StandardRegisters {
|
||||
#[cfg(all(feature = "kvm", not(target_arch = "riscv64")))]
|
||||
Kvm(kvm_bindings::kvm_regs),
|
||||
#[cfg(all(feature = "mshv", target_arch = "x86_64"))]
|
||||
#[cfg(all(feature = "kvm", target_arch = "riscv64"))]
|
||||
Kvm(kvm_bindings::kvm_riscv_core),
|
||||
#[cfg(any(feature = "mshv", feature = "mshv_emulator"))]
|
||||
Mshv(mshv_bindings::StandardRegisters),
|
||||
}
|
||||
|
||||
@@ -208,7 +237,7 @@ macro_rules! set_x86_64_reg {
|
||||
match self {
|
||||
#[cfg(feature = "kvm")]
|
||||
StandardRegisters::Kvm(s) => s.$reg_name = val,
|
||||
#[cfg(feature = "mshv")]
|
||||
#[cfg(any(feature = "mshv", feature = "mshv_emulator"))]
|
||||
StandardRegisters::Mshv(s) => s.$reg_name = val,
|
||||
}
|
||||
}
|
||||
@@ -226,7 +255,7 @@ macro_rules! get_x86_64_reg {
|
||||
match self {
|
||||
#[cfg(feature = "kvm")]
|
||||
StandardRegisters::Kvm(s) => s.$reg_name,
|
||||
#[cfg(feature = "mshv")]
|
||||
#[cfg(any(feature = "mshv", feature = "mshv_emulator"))]
|
||||
StandardRegisters::Mshv(s) => s.$reg_name,
|
||||
}
|
||||
}
|
||||
@@ -282,6 +311,8 @@ macro_rules! set_aarch64_reg {
|
||||
match self {
|
||||
#[cfg(feature = "kvm")]
|
||||
StandardRegisters::Kvm(s) => s.regs.$reg_name = val,
|
||||
#[cfg(feature = "mshv")]
|
||||
StandardRegisters::Mshv(s) => s.$reg_name = val,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -298,6 +329,8 @@ macro_rules! get_aarch64_reg {
|
||||
match self {
|
||||
#[cfg(feature = "kvm")]
|
||||
StandardRegisters::Kvm(s) => s.regs.$reg_name,
|
||||
#[cfg(feature = "mshv")]
|
||||
StandardRegisters::Mshv(s) => s.$reg_name,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -314,3 +347,125 @@ get_aarch64_reg!(regs, [u64; 31usize]);
|
||||
get_aarch64_reg!(sp, u64);
|
||||
get_aarch64_reg!(pc, u64);
|
||||
get_aarch64_reg!(pstate, u64);
|
||||
|
||||
macro_rules! set_riscv64_reg {
|
||||
(mode) => {
|
||||
#[cfg(target_arch = "riscv64")]
|
||||
impl StandardRegisters {
|
||||
pub fn set_mode(&mut self, val: u64) {
|
||||
match self {
|
||||
#[cfg(feature = "kvm")]
|
||||
StandardRegisters::Kvm(s) => s.mode = val,
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
($reg_name:ident) => {
|
||||
concat_idents!(method_name = "set_", $reg_name {
|
||||
#[cfg(target_arch = "riscv64")]
|
||||
impl StandardRegisters {
|
||||
pub fn method_name(&mut self, val: u64) {
|
||||
match self {
|
||||
#[cfg(feature = "kvm")]
|
||||
StandardRegisters::Kvm(s) => s.regs.$reg_name = val,
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! get_riscv64_reg {
|
||||
(mode) => {
|
||||
#[cfg(target_arch = "riscv64")]
|
||||
impl StandardRegisters {
|
||||
pub fn get_mode(&self) -> u64 {
|
||||
match self {
|
||||
#[cfg(feature = "kvm")]
|
||||
StandardRegisters::Kvm(s) => s.mode,
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
($reg_name:ident) => {
|
||||
concat_idents!(method_name = "get_", $reg_name {
|
||||
#[cfg(target_arch = "riscv64")]
|
||||
impl StandardRegisters {
|
||||
pub fn method_name(&self) -> u64 {
|
||||
match self {
|
||||
#[cfg(feature = "kvm")]
|
||||
StandardRegisters::Kvm(s) => s.regs.$reg_name,
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
set_riscv64_reg!(pc);
|
||||
set_riscv64_reg!(ra);
|
||||
set_riscv64_reg!(sp);
|
||||
set_riscv64_reg!(gp);
|
||||
set_riscv64_reg!(tp);
|
||||
set_riscv64_reg!(t0);
|
||||
set_riscv64_reg!(t1);
|
||||
set_riscv64_reg!(t2);
|
||||
set_riscv64_reg!(s0);
|
||||
set_riscv64_reg!(s1);
|
||||
set_riscv64_reg!(a0);
|
||||
set_riscv64_reg!(a1);
|
||||
set_riscv64_reg!(a2);
|
||||
set_riscv64_reg!(a3);
|
||||
set_riscv64_reg!(a4);
|
||||
set_riscv64_reg!(a5);
|
||||
set_riscv64_reg!(a6);
|
||||
set_riscv64_reg!(a7);
|
||||
set_riscv64_reg!(s2);
|
||||
set_riscv64_reg!(s3);
|
||||
set_riscv64_reg!(s4);
|
||||
set_riscv64_reg!(s5);
|
||||
set_riscv64_reg!(s6);
|
||||
set_riscv64_reg!(s7);
|
||||
set_riscv64_reg!(s8);
|
||||
set_riscv64_reg!(s9);
|
||||
set_riscv64_reg!(s10);
|
||||
set_riscv64_reg!(s11);
|
||||
set_riscv64_reg!(t3);
|
||||
set_riscv64_reg!(t4);
|
||||
set_riscv64_reg!(t5);
|
||||
set_riscv64_reg!(t6);
|
||||
set_riscv64_reg!(mode);
|
||||
|
||||
get_riscv64_reg!(pc);
|
||||
get_riscv64_reg!(ra);
|
||||
get_riscv64_reg!(sp);
|
||||
get_riscv64_reg!(gp);
|
||||
get_riscv64_reg!(tp);
|
||||
get_riscv64_reg!(t0);
|
||||
get_riscv64_reg!(t1);
|
||||
get_riscv64_reg!(t2);
|
||||
get_riscv64_reg!(s0);
|
||||
get_riscv64_reg!(s1);
|
||||
get_riscv64_reg!(a0);
|
||||
get_riscv64_reg!(a1);
|
||||
get_riscv64_reg!(a2);
|
||||
get_riscv64_reg!(a3);
|
||||
get_riscv64_reg!(a4);
|
||||
get_riscv64_reg!(a5);
|
||||
get_riscv64_reg!(a6);
|
||||
get_riscv64_reg!(a7);
|
||||
get_riscv64_reg!(s2);
|
||||
get_riscv64_reg!(s3);
|
||||
get_riscv64_reg!(s4);
|
||||
get_riscv64_reg!(s5);
|
||||
get_riscv64_reg!(s6);
|
||||
get_riscv64_reg!(s7);
|
||||
get_riscv64_reg!(s8);
|
||||
get_riscv64_reg!(s9);
|
||||
get_riscv64_reg!(s10);
|
||||
get_riscv64_reg!(s11);
|
||||
get_riscv64_reg!(t3);
|
||||
get_riscv64_reg!(t4);
|
||||
get_riscv64_reg!(t5);
|
||||
get_riscv64_reg!(t6);
|
||||
get_riscv64_reg!(mode);
|
||||
|
||||
22
hypervisor/src/mshv/aarch64/mod.rs
Normal file
22
hypervisor/src/mshv/aarch64/mod.rs
Normal file
@@ -0,0 +1,22 @@
|
||||
// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
|
||||
//
|
||||
// Copyright © 2025, Microsoft Corporation
|
||||
//
|
||||
use std::fmt;
|
||||
|
||||
///
|
||||
/// Export generically-named wrappers of mshv_bindings for Unix-based platforms
|
||||
///
|
||||
pub use mshv_bindings::StandardRegisters as MshvStandardRegisters;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct VcpuMshvState {
|
||||
pub regs: MshvStandardRegisters,
|
||||
}
|
||||
|
||||
impl fmt::Display for VcpuMshvState {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "Standard registers: {:?}", self.regs)
|
||||
}
|
||||
}
|
||||
@@ -5,16 +5,26 @@
|
||||
|
||||
use std::any::Any;
|
||||
use std::collections::HashMap;
|
||||
#[cfg(feature = "sev_snp")]
|
||||
use std::num::NonZeroUsize;
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
#[cfg(feature = "sev_snp")]
|
||||
use arc_swap::ArcSwap;
|
||||
use mshv_bindings::*;
|
||||
use mshv_ioctls::{set_registers_64, InterruptRequest, Mshv, NoDatamatch, VcpuFd, VmFd, VmType};
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use mshv_ioctls::{set_registers_64, InterruptRequest};
|
||||
use mshv_ioctls::{Mshv, NoDatamatch, VcpuFd, VmFd, VmType};
|
||||
use vfio_ioctls::VfioDeviceFd;
|
||||
use vm::DataMatch;
|
||||
#[cfg(feature = "sev_snp")]
|
||||
use vm_memory::bitmap::AtomicBitmap;
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use crate::arch::emulator::PlatformEmulator;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use crate::arch::x86::emulator::Emulator;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use crate::mshv::emulator::MshvEmulatorContext;
|
||||
use crate::vm::{self, InterruptSourceConfig, VmOps};
|
||||
use crate::{cpu, hypervisor, vec_with_array_field, HypervisorType};
|
||||
@@ -23,10 +33,17 @@ mod snp_constants;
|
||||
// x86_64 dependencies
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub mod x86_64;
|
||||
// aarch64 dependencies
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub mod aarch64;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use std::fs::File;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
use std::sync::Mutex;
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub use aarch64::VcpuMshvState;
|
||||
#[cfg(feature = "sev_snp")]
|
||||
use igvm_defs::IGVM_VHS_SNP_ID_BLOCK;
|
||||
#[cfg(feature = "sev_snp")]
|
||||
@@ -44,6 +61,8 @@ pub use {
|
||||
mshv_bindings::mshv_device_attr as DeviceAttr, mshv_ioctls, mshv_ioctls::DeviceFd,
|
||||
};
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
use crate::arch::aarch64::gic::{Vgic, VgicConfig};
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use crate::arch::x86::{CpuIdEntry, FpuState, MsrEntry};
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
@@ -185,6 +204,44 @@ impl From<IrqRoutingEntry> for mshv_user_irq_entry {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
impl From<mshv_bindings::MshvRegList> for crate::RegList {
|
||||
fn from(s: mshv_bindings::MshvRegList) -> Self {
|
||||
crate::RegList::Mshv(s)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
impl From<crate::RegList> for mshv_bindings::MshvRegList {
|
||||
fn from(e: crate::RegList) -> Self {
|
||||
match e {
|
||||
crate::RegList::Mshv(e) => e,
|
||||
/* Needed in case other hypervisors are enabled */
|
||||
#[allow(unreachable_patterns)]
|
||||
_ => panic!("RegList is not valid"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
impl From<mshv_bindings::MshvVcpuInit> for crate::VcpuInit {
|
||||
fn from(s: mshv_bindings::MshvVcpuInit) -> Self {
|
||||
crate::VcpuInit::Mshv(s)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
impl From<crate::VcpuInit> for mshv_bindings::MshvVcpuInit {
|
||||
fn from(e: crate::VcpuInit) -> Self {
|
||||
match e {
|
||||
crate::VcpuInit::Mshv(e) => e,
|
||||
/* Needed in case other hypervisors are enabled */
|
||||
#[allow(unreachable_patterns)]
|
||||
_ => panic!("VcpuInit is not valid"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct MshvDirtyLogSlot {
|
||||
guest_pfn: u64,
|
||||
memory_size: u64,
|
||||
@@ -205,48 +262,12 @@ impl MshvHypervisor {
|
||||
.get_msr_index_list()
|
||||
.map_err(|e| hypervisor::HypervisorError::GetMsrList(e.into()))
|
||||
}
|
||||
}
|
||||
|
||||
impl MshvHypervisor {
|
||||
/// Create a hypervisor based on Mshv
|
||||
#[allow(clippy::new_ret_no_self)]
|
||||
pub fn new() -> hypervisor::Result<Arc<dyn hypervisor::Hypervisor>> {
|
||||
let mshv_obj =
|
||||
Mshv::new().map_err(|e| hypervisor::HypervisorError::HypervisorCreate(e.into()))?;
|
||||
Ok(Arc::new(MshvHypervisor { mshv: mshv_obj }))
|
||||
}
|
||||
/// Check if the hypervisor is available
|
||||
pub fn is_available() -> hypervisor::Result<bool> {
|
||||
match std::fs::metadata("/dev/mshv") {
|
||||
Ok(_) => Ok(true),
|
||||
Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(false),
|
||||
Err(err) => Err(hypervisor::HypervisorError::HypervisorAvailableCheck(
|
||||
err.into(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Implementation of Hypervisor trait for Mshv
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use hypervisor::mshv::MshvHypervisor;
|
||||
/// # use std::sync::Arc;
|
||||
/// let mshv = MshvHypervisor::new().unwrap();
|
||||
/// let hypervisor = Arc::new(mshv);
|
||||
/// let vm = hypervisor.create_vm().expect("new VM fd creation failed");
|
||||
/// ```
|
||||
impl hypervisor::Hypervisor for MshvHypervisor {
|
||||
///
|
||||
/// Returns the type of the hypervisor
|
||||
///
|
||||
fn hypervisor_type(&self) -> HypervisorType {
|
||||
HypervisorType::Mshv
|
||||
}
|
||||
|
||||
fn create_vm_with_type(&self, vm_type: u64) -> hypervisor::Result<Arc<dyn crate::Vm>> {
|
||||
fn create_vm_with_type_and_memory_int(
|
||||
&self,
|
||||
vm_type: u64,
|
||||
#[cfg(feature = "sev_snp")] _mem_size: Option<u64>,
|
||||
) -> hypervisor::Result<Arc<dyn crate::Vm>> {
|
||||
let mshv_vm_type: VmType = match VmType::try_from(vm_type) {
|
||||
Ok(vm_type) => vm_type,
|
||||
Err(_) => return Err(hypervisor::HypervisorError::UnsupportedVmType()),
|
||||
@@ -339,6 +360,14 @@ impl hypervisor::Hypervisor for MshvHypervisor {
|
||||
dirty_log_slots: Arc::new(RwLock::new(HashMap::new())),
|
||||
#[cfg(feature = "sev_snp")]
|
||||
sev_snp_enabled: mshv_vm_type == VmType::Snp,
|
||||
#[cfg(feature = "sev_snp")]
|
||||
host_access_pages: ArcSwap::new(
|
||||
AtomicBitmap::new(
|
||||
_mem_size.unwrap_or_default() as usize,
|
||||
NonZeroUsize::new(HV_PAGE_SIZE).unwrap(),
|
||||
)
|
||||
.into(),
|
||||
),
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -350,6 +379,78 @@ impl hypervisor::Hypervisor for MshvHypervisor {
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MshvHypervisor {
|
||||
/// Create a hypervisor based on Mshv
|
||||
#[allow(clippy::new_ret_no_self)]
|
||||
pub fn new() -> hypervisor::Result<Arc<dyn hypervisor::Hypervisor>> {
|
||||
let mshv_obj =
|
||||
Mshv::new().map_err(|e| hypervisor::HypervisorError::HypervisorCreate(e.into()))?;
|
||||
Ok(Arc::new(MshvHypervisor { mshv: mshv_obj }))
|
||||
}
|
||||
/// Check if the hypervisor is available
|
||||
pub fn is_available() -> hypervisor::Result<bool> {
|
||||
match std::fs::metadata("/dev/mshv") {
|
||||
Ok(_) => Ok(true),
|
||||
Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(false),
|
||||
Err(err) => Err(hypervisor::HypervisorError::HypervisorAvailableCheck(
|
||||
err.into(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Implementation of Hypervisor trait for Mshv
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use hypervisor::mshv::MshvHypervisor;
|
||||
/// use std::sync::Arc;
|
||||
/// let mshv = MshvHypervisor::new().unwrap();
|
||||
/// let hypervisor = Arc::new(mshv);
|
||||
/// let vm = hypervisor.create_vm().expect("new VM fd creation failed");
|
||||
/// ```
|
||||
impl hypervisor::Hypervisor for MshvHypervisor {
|
||||
///
|
||||
/// Returns the type of the hypervisor
|
||||
///
|
||||
fn hypervisor_type(&self) -> HypervisorType {
|
||||
HypervisorType::Mshv
|
||||
}
|
||||
|
||||
///
|
||||
/// Create a Vm of a specific type using the underlying hypervisor, passing memory size
|
||||
/// Return a hypervisor-agnostic Vm trait object
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use hypervisor::kvm::KvmHypervisor;
|
||||
/// use hypervisor::kvm::KvmVm;
|
||||
/// let hypervisor = KvmHypervisor::new().unwrap();
|
||||
/// let vm = hypervisor.create_vm_with_type(0, 512*1024*1024).unwrap();
|
||||
/// ```
|
||||
fn create_vm_with_type_and_memory(
|
||||
&self,
|
||||
vm_type: u64,
|
||||
#[cfg(feature = "sev_snp")] _mem_size: u64,
|
||||
) -> hypervisor::Result<Arc<dyn vm::Vm>> {
|
||||
self.create_vm_with_type_and_memory_int(
|
||||
vm_type,
|
||||
#[cfg(feature = "sev_snp")]
|
||||
Some(_mem_size),
|
||||
)
|
||||
}
|
||||
|
||||
fn create_vm_with_type(&self, vm_type: u64) -> hypervisor::Result<Arc<dyn crate::Vm>> {
|
||||
self.create_vm_with_type_and_memory_int(
|
||||
vm_type,
|
||||
#[cfg(feature = "sev_snp")]
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
/// Create a mshv vm object and return the object as Vm trait object
|
||||
///
|
||||
@@ -357,7 +458,7 @@ impl hypervisor::Hypervisor for MshvHypervisor {
|
||||
///
|
||||
/// ```
|
||||
/// # extern crate hypervisor;
|
||||
/// # use hypervisor::mshv::MshvHypervisor;
|
||||
/// use hypervisor::mshv::MshvHypervisor;
|
||||
/// use hypervisor::mshv::MshvVm;
|
||||
/// let hypervisor = MshvHypervisor::new().unwrap();
|
||||
/// let vm = hypervisor.create_vm().unwrap();
|
||||
@@ -393,8 +494,39 @@ impl hypervisor::Hypervisor for MshvHypervisor {
|
||||
fn get_guest_debug_hw_bps(&self) -> usize {
|
||||
0
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
///
|
||||
/// Retrieve AArch64 host maximum IPA size supported by MSHV.
|
||||
///
|
||||
fn get_host_ipa_limit(&self) -> i32 {
|
||||
let host_ipa = self.mshv.get_host_partition_property(
|
||||
hv_partition_property_code_HV_PARTITION_PROPERTY_PHYSICAL_ADDRESS_WIDTH as u64,
|
||||
);
|
||||
|
||||
match host_ipa {
|
||||
Ok(ipa) => ipa,
|
||||
Err(e) => {
|
||||
panic!("Failed to get host IPA limit: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "sev_snp")]
|
||||
struct Ghcb(*mut svm_ghcb_base);
|
||||
|
||||
#[cfg(feature = "sev_snp")]
|
||||
// SAFETY: struct is based on GHCB page in the hypervisor,
|
||||
// safe to Send across threads
|
||||
unsafe impl Send for Ghcb {}
|
||||
|
||||
#[cfg(feature = "sev_snp")]
|
||||
// SAFETY: struct is based on GHCB page in the hypervisor,
|
||||
// safe to Sync across threads as this is only required for Vcpu trait
|
||||
// functionally not used anyway
|
||||
unsafe impl Sync for Ghcb {}
|
||||
|
||||
/// Vcpu struct for Microsoft Hypervisor
|
||||
pub struct MshvVcpu {
|
||||
fd: VcpuFd,
|
||||
@@ -405,6 +537,10 @@ pub struct MshvVcpu {
|
||||
msrs: Vec<MsrEntry>,
|
||||
vm_ops: Option<Arc<dyn vm::VmOps>>,
|
||||
vm_fd: Arc<VmFd>,
|
||||
#[cfg(feature = "sev_snp")]
|
||||
ghcb: Option<Ghcb>,
|
||||
#[cfg(feature = "sev_snp")]
|
||||
host_access_pages: ArcSwap<AtomicBitmap>,
|
||||
}
|
||||
|
||||
/// Implementation of Vcpu trait for Microsoft Hypervisor
|
||||
@@ -412,8 +548,8 @@ pub struct MshvVcpu {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use hypervisor::mshv::MshvHypervisor;
|
||||
/// # use std::sync::Arc;
|
||||
/// use hypervisor::mshv::MshvHypervisor;
|
||||
/// use std::sync::Arc;
|
||||
/// let mshv = MshvHypervisor::new().unwrap();
|
||||
/// let hypervisor = Arc::new(mshv);
|
||||
/// let vm = hypervisor.create_vm().expect("new VM fd creation failed");
|
||||
@@ -732,6 +868,12 @@ impl cpu::Vcpu for MshvVcpu {
|
||||
.map_err(|e| cpu::HypervisorCpuError::RunVcpu(anyhow!(
|
||||
"Unhandled VCPU exit: attribute intercept - couldn't modify host access {}", e
|
||||
)))?;
|
||||
// Guest is revoking the shared access, so we need to update the bitmap
|
||||
self.host_access_pages.rcu(|_bitmap| {
|
||||
let bm = self.host_access_pages.load().as_ref().clone();
|
||||
bm.reset_addr_range(gpa_start as usize, gfn_count as usize);
|
||||
bm
|
||||
});
|
||||
Ok(cpu::VmExit::Ignore)
|
||||
}
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
@@ -789,6 +931,10 @@ impl cpu::Vcpu for MshvVcpu {
|
||||
let ghcb_msr = svm_ghcb_msr {
|
||||
as_uint64: info.ghcb_msr,
|
||||
};
|
||||
// Safe to use unwrap, for sev_snp guest we already have the
|
||||
// GHCB pointer wrapped in the option, otherwise this place is not reached.
|
||||
let ghcb = self.ghcb.as_ref().unwrap().0;
|
||||
|
||||
// SAFETY: Accessing a union element from bindgen generated bindings.
|
||||
let ghcb_op = unsafe { ghcb_msr.__bindgen_anon_2.ghcb_info() as u32 };
|
||||
// Sanity check on the header fields before handling other operations.
|
||||
@@ -887,9 +1033,7 @@ impl cpu::Vcpu for MshvVcpu {
|
||||
GHCB_INFO_NORMAL => {
|
||||
let exit_code =
|
||||
info.__bindgen_anon_2.__bindgen_anon_1.sw_exit_code as u32;
|
||||
// SAFETY: Accessing a union element from bindgen generated bindings.
|
||||
let pfn = unsafe { ghcb_msr.__bindgen_anon_2.gpa_page_number() };
|
||||
let ghcb_gpa = pfn << GHCB_INFO_BIT_WIDTH;
|
||||
|
||||
match exit_code {
|
||||
SVM_EXITCODE_HV_DOORBELL_PAGE => {
|
||||
let exit_info1 =
|
||||
@@ -898,10 +1042,11 @@ impl cpu::Vcpu for MshvVcpu {
|
||||
SVM_NAE_HV_DOORBELL_PAGE_GET_PREFERRED => {
|
||||
// Hypervisor does not have any preference for doorbell GPA.
|
||||
let preferred_doorbell_gpa: u64 = 0xFFFFFFFFFFFFFFFF;
|
||||
self.gpa_write(
|
||||
ghcb_gpa + GHCB_SW_EXITINFO2_OFFSET,
|
||||
&preferred_doorbell_gpa.to_le_bytes(),
|
||||
)?;
|
||||
set_svm_field_u64_ptr!(
|
||||
ghcb,
|
||||
exit_info2,
|
||||
preferred_doorbell_gpa
|
||||
);
|
||||
}
|
||||
SVM_NAE_HV_DOORBELL_PAGE_SET => {
|
||||
let exit_info2 = info
|
||||
@@ -928,13 +1073,10 @@ impl cpu::Vcpu for MshvVcpu {
|
||||
cpu::HypervisorCpuError::SetRegister(e.into())
|
||||
})?;
|
||||
|
||||
self.gpa_write(
|
||||
ghcb_gpa + GHCB_SW_EXITINFO2_OFFSET,
|
||||
&exit_info2.to_le_bytes(),
|
||||
)?;
|
||||
set_svm_field_u64_ptr!(ghcb, exit_info2, exit_info2);
|
||||
|
||||
// Clear the SW_EXIT_INFO1 register to indicate no error
|
||||
self.clear_swexit_info1(ghcb_gpa)?;
|
||||
self.clear_swexit_info1()?;
|
||||
}
|
||||
SVM_NAE_HV_DOORBELL_PAGE_QUERY => {
|
||||
let mut reg_assocs = [ hv_register_assoc {
|
||||
@@ -945,19 +1087,13 @@ impl cpu::Vcpu for MshvVcpu {
|
||||
// SAFETY: Accessing a union element from bindgen generated bindings.
|
||||
let doorbell_gpa = unsafe { reg_assocs[0].value.reg64 };
|
||||
|
||||
self.gpa_write(
|
||||
ghcb_gpa + GHCB_SW_EXITINFO2_OFFSET,
|
||||
&doorbell_gpa.to_le_bytes(),
|
||||
)?;
|
||||
set_svm_field_u64_ptr!(ghcb, exit_info2, doorbell_gpa);
|
||||
|
||||
// Clear the SW_EXIT_INFO1 register to indicate no error
|
||||
self.clear_swexit_info1(ghcb_gpa)?;
|
||||
self.clear_swexit_info1()?;
|
||||
}
|
||||
SVM_NAE_HV_DOORBELL_PAGE_CLEAR => {
|
||||
self.gpa_write(
|
||||
ghcb_gpa + GHCB_SW_EXITINFO2_OFFSET,
|
||||
&[0; 8],
|
||||
)?;
|
||||
set_svm_field_u64_ptr!(ghcb, exit_info2, 0);
|
||||
}
|
||||
_ => {
|
||||
panic!(
|
||||
@@ -991,9 +1127,8 @@ impl cpu::Vcpu for MshvVcpu {
|
||||
let is_write =
|
||||
// SAFETY: Accessing a union element from bindgen generated bindings.
|
||||
unsafe { port_info.__bindgen_anon_1.access_type() == 0 };
|
||||
|
||||
let mut data = [0; 8];
|
||||
self.gpa_read(ghcb_gpa + GHCB_RAX_OFFSET, &mut data)?;
|
||||
// SAFETY: Accessing the field from a mapped address
|
||||
let mut data = unsafe { (*ghcb).rax.to_le_bytes() };
|
||||
|
||||
if is_write {
|
||||
if let Some(vm_ops) = &self.vm_ops {
|
||||
@@ -1009,17 +1144,15 @@ impl cpu::Vcpu for MshvVcpu {
|
||||
cpu::HypervisorCpuError::RunVcpu(e.into())
|
||||
})?;
|
||||
}
|
||||
|
||||
self.gpa_write(ghcb_gpa + GHCB_RAX_OFFSET, &data)?;
|
||||
set_svm_field_u64_ptr!(ghcb, rax, u64::from_le_bytes(data));
|
||||
}
|
||||
|
||||
// Clear the SW_EXIT_INFO1 register to indicate no error
|
||||
self.clear_swexit_info1(ghcb_gpa)?;
|
||||
self.clear_swexit_info1()?;
|
||||
}
|
||||
SVM_EXITCODE_MMIO_READ => {
|
||||
let src_gpa =
|
||||
info.__bindgen_anon_2.__bindgen_anon_1.sw_exit_info1;
|
||||
let dst_gpa = info.__bindgen_anon_2.__bindgen_anon_1.sw_scratch;
|
||||
let data_len =
|
||||
info.__bindgen_anon_2.__bindgen_anon_1.sw_exit_info2
|
||||
as usize;
|
||||
@@ -1032,16 +1165,18 @@ impl cpu::Vcpu for MshvVcpu {
|
||||
cpu::HypervisorCpuError::RunVcpu(e.into())
|
||||
})?;
|
||||
}
|
||||
|
||||
self.gpa_write(dst_gpa, &data)?;
|
||||
// Copy the data to the shared buffer of the GHCB page
|
||||
let mut buffer_data = [0; 8];
|
||||
buffer_data[..data_len].copy_from_slice(&data[..data_len]);
|
||||
// SAFETY: Updating the value of mapped area
|
||||
unsafe { (*ghcb).shared[0] = u64::from_le_bytes(buffer_data) };
|
||||
|
||||
// Clear the SW_EXIT_INFO1 register to indicate no error
|
||||
self.clear_swexit_info1(ghcb_gpa)?;
|
||||
self.clear_swexit_info1()?;
|
||||
}
|
||||
SVM_EXITCODE_MMIO_WRITE => {
|
||||
let dst_gpa =
|
||||
info.__bindgen_anon_2.__bindgen_anon_1.sw_exit_info1;
|
||||
let src_gpa = info.__bindgen_anon_2.__bindgen_anon_1.sw_scratch;
|
||||
let data_len =
|
||||
info.__bindgen_anon_2.__bindgen_anon_1.sw_exit_info2
|
||||
as usize;
|
||||
@@ -1049,7 +1184,10 @@ impl cpu::Vcpu for MshvVcpu {
|
||||
assert!(data_len <= 0x8);
|
||||
|
||||
let mut data = vec![0; data_len];
|
||||
self.gpa_read(src_gpa, &mut data)?;
|
||||
// SAFETY: Accessing data from a mapped address
|
||||
let bytes_shared_ghcb =
|
||||
unsafe { (*ghcb).shared[0].to_le_bytes() };
|
||||
data.copy_from_slice(&bytes_shared_ghcb[..data_len]);
|
||||
|
||||
if let Some(vm_ops) = &self.vm_ops {
|
||||
vm_ops.mmio_write(dst_gpa, &data).map_err(|e| {
|
||||
@@ -1058,7 +1196,7 @@ impl cpu::Vcpu for MshvVcpu {
|
||||
}
|
||||
|
||||
// Clear the SW_EXIT_INFO1 register to indicate no error
|
||||
self.clear_swexit_info1(ghcb_gpa)?;
|
||||
self.clear_swexit_info1()?;
|
||||
}
|
||||
SVM_EXITCODE_SNP_GUEST_REQUEST
|
||||
| SVM_EXITCODE_SNP_EXTENDED_GUEST_REQUEST => {
|
||||
@@ -1067,15 +1205,16 @@ impl cpu::Vcpu for MshvVcpu {
|
||||
// We don't support extended guest request, so we just write empty data.
|
||||
// This matches the behavior of KVM in Linux 6.11.
|
||||
|
||||
// Read RAX & RBX from the GHCB.
|
||||
let mut data = [0; 8];
|
||||
self.gpa_read(ghcb_gpa + GHCB_RAX_OFFSET, &mut data)?;
|
||||
let data_gpa = u64::from_le_bytes(data);
|
||||
self.gpa_read(ghcb_gpa + GHCB_RBX_OFFSET, &mut data)?;
|
||||
let data_npages = u64::from_le_bytes(data);
|
||||
// Read RBX from the GHCB.
|
||||
// SAFETY: Accessing data from a mapped address
|
||||
let data_gpa = unsafe { (*ghcb).rax };
|
||||
// SAFETY: Accessing data from a mapped address
|
||||
let data_npages = unsafe { (*ghcb).rbx };
|
||||
|
||||
if data_npages > 0 {
|
||||
// The certificates are terminated by 24 zero bytes.
|
||||
// TODO: Need to check if data_gpa is the address of the shared buffer in the GHCB page
|
||||
// in that case we should clear the shared buffer(24 bytes)
|
||||
self.gpa_write(data_gpa, &[0; 24])?;
|
||||
}
|
||||
}
|
||||
@@ -1096,7 +1235,7 @@ impl cpu::Vcpu for MshvVcpu {
|
||||
req_gpa, rsp_gpa
|
||||
);
|
||||
|
||||
self.gpa_write(ghcb_gpa + GHCB_SW_EXITINFO2_OFFSET, &[0; 8])?;
|
||||
set_svm_field_u64_ptr!(ghcb, exit_info2, 0);
|
||||
}
|
||||
SVM_EXITCODE_SNP_AP_CREATION => {
|
||||
let vmsa_gpa =
|
||||
@@ -1117,7 +1256,7 @@ impl cpu::Vcpu for MshvVcpu {
|
||||
.map_err(|e| cpu::HypervisorCpuError::RunVcpu(e.into()))?;
|
||||
|
||||
// Clear the SW_EXIT_INFO1 register to indicate no error
|
||||
self.clear_swexit_info1(ghcb_gpa)?;
|
||||
self.clear_swexit_info1()?;
|
||||
}
|
||||
_ => panic!(
|
||||
"GHCB_INFO_NORMAL: Unhandled exit code: {:0x}",
|
||||
@@ -1167,25 +1306,50 @@ impl cpu::Vcpu for MshvVcpu {
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
fn get_reg_list(&self, reg_list: &mut RegList) -> cpu::Result<()> {
|
||||
fn get_reg_list(&self, _reg_list: &mut crate::RegList) -> cpu::Result<()> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
fn vcpu_init(&self, kvi: &VcpuInit) -> cpu::Result<()> {
|
||||
fn vcpu_init(&self, _kvi: &crate::VcpuInit) -> cpu::Result<()> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
fn set_regs(&self, regs: &StandardRegisters) -> cpu::Result<()> {
|
||||
fn set_regs(&self, _regs: &crate::StandardRegisters) -> cpu::Result<()> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
fn get_regs(&self) -> cpu::Result<StandardRegisters> {
|
||||
fn get_regs(&self) -> cpu::Result<crate::StandardRegisters> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
fn vcpu_finalize(&self, _feature: i32) -> cpu::Result<()> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
fn vcpu_get_finalized_features(&self) -> i32 {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
fn vcpu_set_processor_features(
|
||||
&self,
|
||||
_vm: &Arc<dyn crate::Vm>,
|
||||
_kvi: &mut crate::VcpuInit,
|
||||
_id: u8,
|
||||
) -> cpu::Result<()> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
fn create_vcpu_init(&self) -> crate::VcpuInit {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// X86 specific call to setup the CPUID registers.
|
||||
@@ -1495,37 +1659,16 @@ impl MshvVcpu {
|
||||
/// Clear SW_EXIT_INFO1 register for SEV-SNP guests.
|
||||
///
|
||||
#[cfg(feature = "sev_snp")]
|
||||
fn clear_swexit_info1(
|
||||
&self,
|
||||
ghcb_gpa: u64,
|
||||
) -> std::result::Result<cpu::VmExit, cpu::HypervisorCpuError> {
|
||||
fn clear_swexit_info1(&self) -> std::result::Result<cpu::VmExit, cpu::HypervisorCpuError> {
|
||||
// Clear the SW_EXIT_INFO1 register to indicate no error
|
||||
self.gpa_write(ghcb_gpa + GHCB_SW_EXITINFO1_OFFSET, &[0; 4])?;
|
||||
// Safe to use unwrap, for sev_snp guest we already have the
|
||||
// GHCB pointer wrapped in the option, otherwise this place is not reached.
|
||||
let ghcb = self.ghcb.as_ref().unwrap().0;
|
||||
set_svm_field_u64_ptr!(ghcb, exit_info1, 0);
|
||||
|
||||
Ok(cpu::VmExit::Ignore)
|
||||
}
|
||||
|
||||
#[cfg(feature = "sev_snp")]
|
||||
fn gpa_read(&self, gpa: u64, data: &mut [u8]) -> cpu::Result<()> {
|
||||
for (gpa, chunk) in (gpa..)
|
||||
.step_by(HV_READ_WRITE_GPA_MAX_SIZE as usize)
|
||||
.zip(data.chunks_mut(HV_READ_WRITE_GPA_MAX_SIZE as usize))
|
||||
{
|
||||
let mut rw_gpa_arg = mshv_bindings::mshv_read_write_gpa {
|
||||
base_gpa: gpa,
|
||||
byte_count: chunk.len() as u32,
|
||||
..Default::default()
|
||||
};
|
||||
self.fd
|
||||
.gpa_read(&mut rw_gpa_arg)
|
||||
.map_err(|e| cpu::HypervisorCpuError::GpaRead(e.into()))?;
|
||||
|
||||
chunk.copy_from_slice(&rw_gpa_arg.data[..chunk.len()]);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "sev_snp")]
|
||||
fn gpa_write(&self, gpa: u64, data: &[u8]) -> cpu::Result<()> {
|
||||
for (gpa, chunk) in (gpa..)
|
||||
@@ -1558,6 +1701,8 @@ pub struct MshvVm {
|
||||
dirty_log_slots: Arc<RwLock<HashMap<u64, MshvDirtyLogSlot>>>,
|
||||
#[cfg(feature = "sev_snp")]
|
||||
sev_snp_enabled: bool,
|
||||
#[cfg(feature = "sev_snp")]
|
||||
host_access_pages: ArcSwap<AtomicBitmap>,
|
||||
}
|
||||
|
||||
impl MshvVm {
|
||||
@@ -1580,9 +1725,9 @@ impl MshvVm {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # extern crate hypervisor;
|
||||
/// # use hypervisor::mshv::MshvHypervisor;
|
||||
/// # use std::sync::Arc;
|
||||
/// extern crate hypervisor;
|
||||
/// use hypervisor::mshv::MshvHypervisor;
|
||||
/// use std::sync::Arc;
|
||||
/// let mshv = MshvHypervisor::new().unwrap();
|
||||
/// let hypervisor = Arc::new(mshv);
|
||||
/// let vm = hypervisor.create_vm().expect("new VM fd creation failed");
|
||||
@@ -1649,6 +1794,34 @@ impl vm::Vm for MshvVm {
|
||||
.fd
|
||||
.create_vcpu(id)
|
||||
.map_err(|e| vm::HypervisorVmError::CreateVcpu(e.into()))?;
|
||||
|
||||
/* Map the GHCB page to the VMM(root) address space
|
||||
* The map is available after the vcpu creation. This address is mapped
|
||||
* to the overlay ghcb page of the Microsoft Hypervisor, don't have
|
||||
* to worry about the scenario when a guest changes the GHCB mapping.
|
||||
*/
|
||||
#[cfg(feature = "sev_snp")]
|
||||
let ghcb = if self.sev_snp_enabled {
|
||||
// SAFETY: Safe to call as VCPU has this map already available upon creation
|
||||
let addr = unsafe {
|
||||
libc::mmap(
|
||||
std::ptr::null_mut(),
|
||||
HV_PAGE_SIZE,
|
||||
libc::PROT_READ | libc::PROT_WRITE,
|
||||
libc::MAP_SHARED,
|
||||
vcpu_fd.as_raw_fd(),
|
||||
MSHV_VP_MMAP_OFFSET_GHCB as i64 * libc::sysconf(libc::_SC_PAGE_SIZE),
|
||||
)
|
||||
};
|
||||
if addr == libc::MAP_FAILED {
|
||||
// No point of continuing, without this mmap VMGEXIT will fail anyway
|
||||
// Return error
|
||||
return Err(vm::HypervisorVmError::MmapToRoot);
|
||||
}
|
||||
Some(Ghcb(addr as *mut svm_ghcb_base))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let vcpu = MshvVcpu {
|
||||
fd: vcpu_fd,
|
||||
vp_index: id,
|
||||
@@ -1658,6 +1831,10 @@ impl vm::Vm for MshvVm {
|
||||
msrs: self.msrs.clone(),
|
||||
vm_ops,
|
||||
vm_fd: self.fd.clone(),
|
||||
#[cfg(feature = "sev_snp")]
|
||||
ghcb,
|
||||
#[cfg(feature = "sev_snp")]
|
||||
host_access_pages: ArcSwap::new(self.host_access_pages.load().clone()),
|
||||
};
|
||||
Ok(Arc::new(vcpu))
|
||||
}
|
||||
@@ -1784,7 +1961,7 @@ impl vm::Vm for MshvVm {
|
||||
|
||||
fn create_passthrough_device(&self) -> vm::Result<VfioDeviceFd> {
|
||||
let mut vfio_dev = mshv_create_device {
|
||||
type_: mshv_device_type_MSHV_DEV_TYPE_VFIO,
|
||||
type_: MSHV_DEV_TYPE_VFIO,
|
||||
fd: 0,
|
||||
flags: 0,
|
||||
};
|
||||
@@ -2011,7 +2188,7 @@ impl vm::Vm for MshvVm {
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
fn get_preferred_target(&self, kvi: &mut VcpuInit) -> vm::Result<()> {
|
||||
fn get_preferred_target(&self, _kvi: &mut crate::VcpuInit) -> vm::Result<()> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
@@ -2050,6 +2227,7 @@ impl vm::Vm for MshvVm {
|
||||
#[cfg(feature = "sev_snp")]
|
||||
fn gain_page_access(&self, gpa: u64, size: u32) -> vm::Result<()> {
|
||||
use mshv_ioctls::set_bits;
|
||||
const ONE_GB: usize = 1024 * 1024 * 1024;
|
||||
|
||||
if !self.sev_snp_enabled {
|
||||
return Ok(());
|
||||
@@ -2058,7 +2236,25 @@ impl vm::Vm for MshvVm {
|
||||
let start_gpfn: u64 = gpa >> PAGE_SHIFT;
|
||||
let end_gpfn: u64 = (gpa + size as u64 - 1) >> PAGE_SHIFT;
|
||||
|
||||
let gpas: Vec<u64> = (start_gpfn..=end_gpfn).map(|x| x << PAGE_SHIFT).collect();
|
||||
// Enlarge the bitmap if the PFN is greater than the bitmap length
|
||||
if end_gpfn >= self.host_access_pages.load().as_ref().len() as u64 {
|
||||
self.host_access_pages.rcu(|bitmap| {
|
||||
let mut bm = bitmap.as_ref().clone();
|
||||
bm.enlarge(ONE_GB);
|
||||
bm
|
||||
});
|
||||
}
|
||||
|
||||
let gpas: Vec<u64> = (start_gpfn..=end_gpfn)
|
||||
.filter(|x| {
|
||||
!self
|
||||
.host_access_pages
|
||||
.load()
|
||||
.as_ref()
|
||||
.is_bit_set(*x as usize)
|
||||
})
|
||||
.map(|x| x << PAGE_SHIFT)
|
||||
.collect();
|
||||
|
||||
if !gpas.is_empty() {
|
||||
let mut gpa_list = vec_with_array_field::<mshv_modify_gpa_host_access, u64>(gpas.len());
|
||||
@@ -2081,6 +2277,14 @@ impl vm::Vm for MshvVm {
|
||||
self.fd
|
||||
.modify_gpa_host_access(&gpa_list[0])
|
||||
.map_err(|e| vm::HypervisorVmError::ModifyGpaHostAccess(e.into()))?;
|
||||
|
||||
for acquired_gpa in gpas {
|
||||
self.host_access_pages.rcu(|bitmap| {
|
||||
let bm = bitmap.clone();
|
||||
bm.set_bit((acquired_gpa >> PAGE_SHIFT) as usize);
|
||||
bm
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -16,10 +16,3 @@ pub const ECDSA_SIG_X_COMPONENT_END: usize =
|
||||
pub const ECDSA_SIG_Y_COMPONENT_START: usize = ECDSA_SIG_X_COMPONENT_END;
|
||||
pub const ECDSA_SIG_Y_COMPONENT_END: usize =
|
||||
ECDSA_SIG_X_COMPONENT_END + ECDSA_SIG_Y_COMPONENT_SIZE_IN_BYTES;
|
||||
|
||||
// These constants are derived from GHCB spec Sect. 2.6 Table 3 GHCB Layout
|
||||
// Link: https://www.amd.com/content/dam/amd/en/documents/epyc-technical-docs/specifications/56421.pdf
|
||||
pub const GHCB_RAX_OFFSET: u64 = 0x01F8;
|
||||
pub const GHCB_RBX_OFFSET: u64 = 0x0318;
|
||||
pub const GHCB_SW_EXITINFO1_OFFSET: u64 = 0x398;
|
||||
pub const GHCB_SW_EXITINFO2_OFFSET: u64 = 0x3A0;
|
||||
|
||||
@@ -35,12 +35,7 @@ impl MshvEmulatorContext<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
fn read_memory_flags(
|
||||
&self,
|
||||
gva: u64,
|
||||
data: &mut [u8],
|
||||
flags: u32,
|
||||
) -> Result<(), PlatformError> {
|
||||
fn r(&self, gva: u64, data: &mut [u8], flags: u32) -> Result<(), PlatformError> {
|
||||
let gpa = self.translate(gva, flags)?;
|
||||
debug!(
|
||||
"mshv emulator: memory read {} bytes from [{:#x} -> {:#x}]",
|
||||
@@ -59,17 +54,38 @@ impl MshvEmulatorContext<'_> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Platform emulation for Hyper-V
|
||||
impl PlatformEmulator for MshvEmulatorContext<'_> {
|
||||
type CpuState = EmulatorCpuState;
|
||||
fn read_memory_flags(
|
||||
&self,
|
||||
gva: u64,
|
||||
data: &mut [u8],
|
||||
flags: u32,
|
||||
) -> Result<(), PlatformError> {
|
||||
let mut len = data.len() as u64;
|
||||
|
||||
fn read_memory(&self, gva: u64, data: &mut [u8]) -> Result<(), PlatformError> {
|
||||
self.read_memory_flags(gva, data, HV_TRANSLATE_GVA_VALIDATE_READ)
|
||||
// Compare the page number of the first and last byte. If they are different, this is a
|
||||
// cross-page access.
|
||||
let pg1 = gva >> HV_HYP_PAGE_SHIFT;
|
||||
let pg2 = (gva + len - 1) >> HV_HYP_PAGE_SHIFT;
|
||||
let cross_page = pg1 != pg2;
|
||||
|
||||
if cross_page {
|
||||
// We only handle one page cross-page access
|
||||
assert!(pg1 + 1 == pg2);
|
||||
let n = (gva + len) & HV_HYP_PAGE_MASK as u64;
|
||||
len -= n;
|
||||
}
|
||||
|
||||
self.r(gva, &mut data[..len as usize], flags)?;
|
||||
|
||||
if cross_page {
|
||||
self.r(gva + len, &mut data[len as usize..], flags)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_memory(&mut self, gva: u64, data: &[u8]) -> Result<(), PlatformError> {
|
||||
fn w(&mut self, gva: u64, data: &[u8]) -> Result<(), PlatformError> {
|
||||
let gpa = self.translate(gva, HV_TRANSLATE_GVA_VALIDATE_WRITE)?;
|
||||
debug!(
|
||||
"mshv emulator: memory write {} bytes at [{:#x} -> {:#x}]",
|
||||
@@ -88,6 +104,40 @@ impl PlatformEmulator for MshvEmulatorContext<'_> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Platform emulation for Hyper-V
|
||||
impl PlatformEmulator for MshvEmulatorContext<'_> {
|
||||
type CpuState = EmulatorCpuState;
|
||||
|
||||
fn read_memory(&self, gva: u64, data: &mut [u8]) -> Result<(), PlatformError> {
|
||||
self.read_memory_flags(gva, data, HV_TRANSLATE_GVA_VALIDATE_READ)
|
||||
}
|
||||
|
||||
fn write_memory(&mut self, gva: u64, data: &[u8]) -> Result<(), PlatformError> {
|
||||
let mut len = data.len() as u64;
|
||||
|
||||
// Compare the page number of the first and last byte. If they are different, this is a
|
||||
// cross-page access.
|
||||
let pg1 = gva >> HV_HYP_PAGE_SHIFT;
|
||||
let pg2 = (gva + len - 1) >> HV_HYP_PAGE_SHIFT;
|
||||
let cross_page = pg1 != pg2;
|
||||
|
||||
if cross_page {
|
||||
// We only handle one page cross-page access
|
||||
assert!(pg1 + 1 == pg2);
|
||||
let n = (gva + len) & HV_HYP_PAGE_MASK as u64;
|
||||
len -= n;
|
||||
}
|
||||
|
||||
self.w(gva, &data[..len as usize])?;
|
||||
|
||||
if cross_page {
|
||||
self.w(gva + len, &data[len as usize..])?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn cpu_state(&self, cpu_id: usize) -> Result<Self::CpuState, PlatformError> {
|
||||
if cpu_id != self.vcpu.vp_index as usize {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// Copyright © 2024 Institute of Software, CAS. All rights reserved.
|
||||
//
|
||||
// Copyright © 2019 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
|
||||
@@ -12,7 +14,7 @@ use std::any::Any;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use std::fs::File;
|
||||
use std::sync::Arc;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
|
||||
use std::sync::Mutex;
|
||||
|
||||
#[cfg(feature = "sev_snp")]
|
||||
@@ -20,10 +22,10 @@ use igvm_defs::IGVM_VHS_SNP_ID_BLOCK;
|
||||
use thiserror::Error;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
use crate::aarch64::VcpuInit;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
use crate::arch::aarch64::gic::{Vgic, VgicConfig};
|
||||
#[cfg(target_arch = "riscv64")]
|
||||
use crate::arch::riscv64::aia::{Vaia, VaiaConfig};
|
||||
#[cfg(feature = "tdx")]
|
||||
use crate::arch::x86::CpuIdEntry;
|
||||
use crate::cpu::Vcpu;
|
||||
@@ -224,6 +226,11 @@ pub enum HypervisorVmError {
|
||||
#[error("Failed to create Vgic: {0}")]
|
||||
CreateVgic(#[source] anyhow::Error),
|
||||
///
|
||||
/// Create Vaia error
|
||||
///
|
||||
#[error("Failed to create Vaia: {0}")]
|
||||
CreateVaia(#[source] anyhow::Error),
|
||||
///
|
||||
/// Import isolated pages error
|
||||
///
|
||||
#[error("Failed to import isolated pages: {0}")]
|
||||
@@ -242,6 +249,12 @@ pub enum HypervisorVmError {
|
||||
#[cfg(feature = "sev_snp")]
|
||||
#[error("Failed to modify GPA host access: {0}")]
|
||||
ModifyGpaHostAccess(#[source] anyhow::Error),
|
||||
///
|
||||
/// Failed to mmap
|
||||
///
|
||||
#[cfg(feature = "sev_snp")]
|
||||
#[error("Failed to mmap:")]
|
||||
MmapToRoot,
|
||||
}
|
||||
///
|
||||
/// Result type for returning from a function
|
||||
@@ -293,6 +306,7 @@ pub trait Vm: Send + Sync + Any {
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
/// Sets the address of the three-page region in the VM's address space.
|
||||
fn set_tss_address(&self, offset: usize) -> Result<()>;
|
||||
#[cfg(not(target_arch = "riscv64"))]
|
||||
/// Creates an in-kernel interrupt controller.
|
||||
fn create_irq_chip(&self) -> Result<()>;
|
||||
/// Registers an event that will, when signaled, trigger the `gsi` IRQ.
|
||||
@@ -303,6 +317,8 @@ pub trait Vm: Send + Sync + Any {
|
||||
fn create_vcpu(&self, id: u8, vm_ops: Option<Arc<dyn VmOps>>) -> Result<Arc<dyn Vcpu>>;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
fn create_vgic(&self, config: VgicConfig) -> Result<Arc<Mutex<dyn Vgic>>>;
|
||||
#[cfg(target_arch = "riscv64")]
|
||||
fn create_vaia(&self, config: VaiaConfig) -> Result<Arc<Mutex<dyn Vaia>>>;
|
||||
|
||||
/// Registers an event to be signaled whenever a certain address is written to.
|
||||
fn register_ioevent(
|
||||
@@ -333,7 +349,7 @@ pub trait Vm: Send + Sync + Any {
|
||||
fn remove_user_memory_region(&self, user_memory_region: UserMemoryRegion) -> Result<()>;
|
||||
/// Returns the preferred CPU target type which can be emulated by KVM on underlying host.
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
fn get_preferred_target(&self, kvi: &mut VcpuInit) -> Result<()>;
|
||||
fn get_preferred_target(&self, kvi: &mut crate::VcpuInit) -> Result<()>;
|
||||
/// Enable split Irq capability
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
fn enable_split_irq(&self) -> Result<()>;
|
||||
|
||||
@@ -6,13 +6,13 @@ version = "0.1.0"
|
||||
|
||||
[dependencies]
|
||||
epoll = "4.3.3"
|
||||
getrandom = "0.2.14"
|
||||
libc = "0.2.158"
|
||||
getrandom = "0.3.1"
|
||||
libc = "0.2.167"
|
||||
log = "0.4.22"
|
||||
net_gen = { path = "../net_gen" }
|
||||
rate_limiter = { path = "../rate_limiter" }
|
||||
serde = { version = "1.0.208", features = ["derive"] }
|
||||
thiserror = "1.0.62"
|
||||
thiserror = "2.0.6"
|
||||
virtio-bindings = { workspace = true }
|
||||
virtio-queue = { workspace = true }
|
||||
vm-memory = { workspace = true, features = [
|
||||
|
||||
@@ -80,7 +80,7 @@ impl MacAddr {
|
||||
pub fn local_random() -> MacAddr {
|
||||
// Generate a fully random MAC
|
||||
let mut random_bytes = [0u8; MAC_ADDR_LEN];
|
||||
if let Err(e) = getrandom::getrandom(&mut random_bytes) {
|
||||
if let Err(e) = getrandom::fill(&mut random_bytes) {
|
||||
error!(
|
||||
"Error populating MAC address with random data: {}",
|
||||
e.to_string()
|
||||
|
||||
@@ -75,9 +75,7 @@ pub fn open_tap(
|
||||
let mut ifname: String = String::new();
|
||||
let vnet_hdr_size = vnet_hdr_len() as i32;
|
||||
// Check if the given interface exists before we create it.
|
||||
let tap_existed = if_name.map_or(false, |n| {
|
||||
Path::new(&format!("/sys/class/net/{n}")).exists()
|
||||
});
|
||||
let tap_existed = if_name.is_some_and(|n| Path::new(&format!("/sys/class/net/{n}")).exists());
|
||||
|
||||
// In case the tap interface already exists, check if the number of
|
||||
// queues is appropriate. The tap might not support multiqueue while
|
||||
|
||||
@@ -468,7 +468,7 @@ impl NetQueuePair {
|
||||
let rate_limit_reached = self
|
||||
.rx_rate_limiter
|
||||
.as_ref()
|
||||
.map_or(false, |r| r.is_blocked());
|
||||
.is_some_and(|r| r.is_blocked());
|
||||
|
||||
// Stop listening on the `RX_TAP_EVENT` when:
|
||||
// 1) there is no available describes, or
|
||||
|
||||
@@ -10,13 +10,13 @@ kvm = ["vfio-ioctls/kvm"]
|
||||
mshv = ["vfio-ioctls/mshv"]
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.87"
|
||||
anyhow = "1.0.94"
|
||||
byteorder = "1.5.0"
|
||||
hypervisor = { path = "../hypervisor" }
|
||||
libc = "0.2.158"
|
||||
libc = "0.2.167"
|
||||
log = "0.4.22"
|
||||
serde = { version = "1.0.208", features = ["derive"] }
|
||||
thiserror = "1.0.62"
|
||||
thiserror = "2.0.6"
|
||||
vfio-bindings = { workspace = true, features = ["fam-wrappers"] }
|
||||
vfio-ioctls = { workspace = true, default-features = false }
|
||||
vfio_user = { workspace = true }
|
||||
|
||||
@@ -90,7 +90,7 @@ impl PciDevice for PciRoot {
|
||||
self.config.read_reg(reg_idx)
|
||||
}
|
||||
|
||||
fn as_any(&mut self) -> &mut dyn Any {
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
@@ -125,19 +125,16 @@ impl PciBus {
|
||||
pub fn register_mapping(
|
||||
&self,
|
||||
dev: Arc<dyn BusDeviceSync>,
|
||||
#[cfg(target_arch = "x86_64")] io_bus: &Bus,
|
||||
io_bus: &Bus,
|
||||
mmio_bus: &Bus,
|
||||
bars: Vec<PciBarConfiguration>,
|
||||
) -> Result<()> {
|
||||
for bar in bars {
|
||||
match bar.region_type() {
|
||||
PciBarRegionType::IoRegion => {
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
io_bus
|
||||
.insert(dev.clone(), bar.addr(), bar.size())
|
||||
.map_err(PciRootError::PioInsert)?;
|
||||
#[cfg(not(target_arch = "x86_64"))]
|
||||
error!("I/O region is not supported");
|
||||
}
|
||||
PciBarRegionType::Memory32BitRegion | PciBarRegionType::Memory64BitRegion => {
|
||||
mmio_bus
|
||||
@@ -294,7 +291,7 @@ impl PciConfigIo {
|
||||
),
|
||||
2 => (
|
||||
0x0000_ffff << (offset * 16),
|
||||
(u32::from(data[1]) << 8 | u32::from(data[0])) << (offset * 16),
|
||||
((u32::from(data[1]) << 8) | u32::from(data[0])) << (offset * 16),
|
||||
),
|
||||
4 => (0xffff_ffff, LittleEndian::read_u32(data)),
|
||||
_ => return,
|
||||
|
||||
@@ -582,7 +582,7 @@ impl PciConfiguration {
|
||||
} else {
|
||||
let mut registers = [0u32; NUM_CONFIGURATION_REGISTERS];
|
||||
let mut writable_bits = [0u32; NUM_CONFIGURATION_REGISTERS];
|
||||
registers[0] = u32::from(device_id) << 16 | u32::from(vendor_id);
|
||||
registers[0] = (u32::from(device_id) << 16) | u32::from(vendor_id);
|
||||
// TODO(dverkamp): Status should be write-1-to-clear
|
||||
writable_bits[1] = 0x0000_ffff; // Status (r/o), command (r/w)
|
||||
let pi = if let Some(pi) = programming_interface {
|
||||
@@ -590,9 +590,9 @@ impl PciConfiguration {
|
||||
} else {
|
||||
0
|
||||
};
|
||||
registers[2] = u32::from(class_code.get_register_value()) << 24
|
||||
| u32::from(subclass.get_register_value()) << 16
|
||||
| u32::from(pi) << 8
|
||||
registers[2] = (u32::from(class_code.get_register_value()) << 24)
|
||||
| (u32::from(subclass.get_register_value()) << 16)
|
||||
| (u32::from(pi) << 8)
|
||||
| u32::from(revision_id);
|
||||
writable_bits[3] = 0x0000_00ff; // Cacheline size (r/w)
|
||||
match header_type {
|
||||
@@ -606,7 +606,7 @@ impl PciConfiguration {
|
||||
writable_bits[15] = 0xffff_00ff; // Bridge control (r/w), interrupt line (r/w)
|
||||
}
|
||||
};
|
||||
registers[11] = u32::from(subsystem_id) << 16 | u32::from(subsystem_vendor_id);
|
||||
registers[11] = (u32::from(subsystem_id) << 16) | u32::from(subsystem_vendor_id);
|
||||
|
||||
(
|
||||
registers,
|
||||
@@ -933,7 +933,7 @@ impl PciConfiguration {
|
||||
1 => self.write_byte(reg_idx * 4 + offset as usize, data[0]),
|
||||
2 => self.write_word(
|
||||
reg_idx * 4 + offset as usize,
|
||||
u16::from(data[0]) | u16::from(data[1]) << 8,
|
||||
u16::from(data[0]) | (u16::from(data[1]) << 8),
|
||||
),
|
||||
4 => self.write_reg(reg_idx, LittleEndian::read_u32(data)),
|
||||
_ => (),
|
||||
@@ -1009,9 +1009,9 @@ impl PciConfiguration {
|
||||
"Detected BAR reprogramming: (BAR {}) 0x{:x}->0x{:x}",
|
||||
reg_idx, self.registers[reg_idx], value
|
||||
);
|
||||
let old_base = u64::from(self.bars[bar_idx].addr & mask) << 32
|
||||
let old_base = (u64::from(self.bars[bar_idx].addr & mask) << 32)
|
||||
| u64::from(self.bars[bar_idx - 1].addr & self.writable_bits[reg_idx - 1]);
|
||||
let new_base = u64::from(value & mask) << 32
|
||||
let new_base = (u64::from(value & mask) << 32)
|
||||
| u64::from(self.registers[reg_idx - 1] & self.writable_bits[reg_idx - 1]);
|
||||
let len =
|
||||
decode_64_bits_bar_size(self.bars[bar_idx].size, self.bars[bar_idx - 1].size)
|
||||
@@ -1159,7 +1159,7 @@ mod tests {
|
||||
|
||||
use super::*;
|
||||
|
||||
#[repr(packed)]
|
||||
#[repr(C, packed)]
|
||||
#[derive(Clone, Copy, Default)]
|
||||
#[allow(dead_code)]
|
||||
struct TestCap {
|
||||
|
||||
@@ -115,7 +115,7 @@ pub trait PciDevice: Send {
|
||||
}
|
||||
/// Provides a mutable reference to the Any trait. This is useful to let
|
||||
/// the caller have access to the underlying type behind the trait.
|
||||
fn as_any(&mut self) -> &mut dyn Any;
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any;
|
||||
|
||||
/// Optionally returns a unique identifier.
|
||||
fn id(&self) -> Option<String>;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user