Compare commits

..

63 Commits
v48.0 ... v37.1

Author SHA1 Message Date
Bo Chen
115c455eaf build: Release v37.1 (bug fix release)
Signed-off-by: Bo Chen <chen.bo@intel.com>
2024-03-14 20:39:27 -07:00
Bo Chen
259b8aa1c8 tests: Run "test_live_upgrade_numa" on aarch64 only
Our Azure VM for x86_64 workers are now much smaller, and does not have
enough RAM to run the "test_live_upgrade_numa" test. Instead, this test
will still be tested on the aarch64 worker, and the "local" upgrade
variation of the same test will also be tested on all workers. So we
should be good from test coverage point of view.

Signed-off-by: Bo Chen <chen.bo@intel.com>
2024-03-14 19:52:57 -07:00
Bo Chen
2a9978f3c0 tests: Enable live upgrade tests
Signed-off-by: Bo Chen <chen.bo@intel.com>
2024-03-14 19:52:57 -07:00
Bo Chen
3c4ff7de01 hypervisor: Use legacy definitions of kvm structs for live-upgrade
Use 'kvm_vcpu_events_old' and 'kvm_clock_data_old' to support
deserialization from legacy definitions of kvm structs, so that we can
support live-upgrade from previous point releases.

Signed-off-by: Bo Chen <chen.bo@intel.com>
2024-03-14 19:52:57 -07:00
Bo Chen
de6d6f2558 hypervisor: Make (de)serialize for XsaveState backward compatible
Signed-off-by: Bo Chen <chen.bo@intel.com>
2024-03-14 19:52:57 -07:00
Rob Bradford
baf719c6ff tests: Remove unnecessary use of vec![] macro
Beta clippy fix

warning: useless use of `vec!`
    --> tests/integration.rs:5845:23
     |
5845 |         let kernels = vec![direct_kernel_boot_path()];
     |                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: you can use an array directly: `[direct_kernel_boot_path()]`
     |
     = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#useless_vec
     = note: `#[warn(clippy::useless_vec)]` on by default

Signed-off-by: Rob Bradford <rbradford@rivosinc.com>
(cherry picked from commit 61afd93a50)
2024-03-14 19:52:57 -07:00
Rob Bradford
c59c61a983 vmm: Make thread local initialiser constant
Beta clippy fix:

warning: initializer for `thread_local` value can be made `const`
  --> vmm/src/sigwinch_listener.rs:27:40
   |
27 |     static TX: RefCell<Option<File>> = RefCell::new(None);
   |                                        ^^^^^^^^^^^^^^^^^^ help: replace with: `const { RefCell::new(None) }`
   |
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#thread_local_initializer_can_be_made_const
   = note: `#[warn(clippy::thread_local_initializer_can_be_made_const)]` on by default

Signed-off-by: Rob Bradford <rbradford@rivosinc.com>
(cherry picked from commit 9dfc39d336)
2024-03-14 19:52:57 -07:00
Rob Bradford
448fafd23a vmm: Directly clone console resize pipe
Beta clippy fix:

warning: this call to `as_ref.map(...)` does nothing
    --> vmm/src/device_manager.rs:1234:9
     |
1234 |         self.console_resize_pipe.as_ref().map(Arc::clone)
     |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `self.console_resize_pipe.clone()`
     |
     = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#useless_asref
     = note: `#[warn(clippy::useless_asref)]` on by default

Signed-off-by: Rob Bradford <rbradford@rivosinc.com>
(cherry picked from commit e70bf59809)
2024-03-14 19:52:57 -07:00
Ravi kumar Veeramally
196a59f209 tests: Migrate docker container from ubuntu 20.04 to 22.04
The following tests have been temporarily disabled:

1. Live upgrade/migration test with ovs-dpdk (#5532);
2. Disk hotplug tests on windows guests (#6037);

This patch has been tested with PR #6048.

Signed-off-by: Ravi kumar Veeramally <ravikumar.veeramally@intel.com>
Signed-off-by: Michael Zhao <michael.zhao@arm.com>
Tested-by: Bo Chen <chen.bo@intel.com>
(cherry picked from commit 24f384d239)
2024-03-14 19:52:57 -07:00
Ravi kumar Veeramally
35d1998965 vmm: Replace Debug with Display rendering in HTTP error message
Bumping anyhow crate from 1.0.75 to 1.0.79 will cause seccomp
failures through integration tests. Newly added backtrace support
relies on readlink and many other syscalls.

Issue noticed with test_api_http_pause_resume test, where second time
of VM PAUSE or VM RESUME prints error and causes panic.
Noticed that panic message in a thread which is not allowed to write
output triggered the issue.

So implementing Display trait for HttpError and ApiError enums to avoid
adding many syscalls to seccomp filter section.

Signed-off-by: Ravi kumar Veeramally <ravikumar.veeramally@intel.com>
(cherry picked from commit 895dc12a74)
2024-03-14 19:52:57 -07:00
Bo Chen
c5904a413e arch: Remove unused wrapper data structure for linux_loader
The `ByteValued` trait implementations for the data structures from the
'linux_loader' crate are no longer needed, and hence their wrappers can
be removed.

Signed-off-by: Bo Chen <chen.bo@intel.com>
(cherry picked from commit 9b0b881351)
2024-03-14 19:52:57 -07:00
Bo Chen
7729024451 main: Clarify truncate behavior for event monitor file
Fix beta clippy issue:

error: file opened with `create`, but `truncate` behavior not defined
   --> src/main.rs:624:26
    |
624 |                         .create(true)
    |                          ^^^^^^^^^^^^- help: add: `.truncate(true)`
    |
    = help: if you intend to overwrite an existing file entirely, call `.truncate(true)`
    = help: if you instead know that you may want to keep some parts of the old file, call `.truncate(false)`
    = help: alternatively, use `.append(true)` to append to the file instead of overwriting it
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_open_options
    = note: `-D clippy::suspicious-open-options` implied by `-D warnings`
    = help: to override `-D warnings` add `#[allow(clippy::suspicious_open_options)]`

Signed-off-by: Bo Chen <chen.bo@intel.com>
(cherry picked from commit c1f4a7b295)
2024-03-14 19:52:57 -07:00
Bo Chen
5580dd6e6a tests: Avoid clippy warning of unhandled I/O bytes
Fixes beta clippy issue:

error: read amount is not handled
    --> tests/integration.rs:2121:15
     |
2121 |         match pty.read(&mut buf) {
     |               ^^^^^^^^^^^^^^^^^^
     |
     = help: use `Read::read_exact` instead, or handle partial reads
note: the result is consumed here, but the amount of I/O bytes remains unhandled
    --> tests/integration.rs:2122:13
     |
2122 | /             Ok(_) => {
2123 | |                 let output = std::str::from_utf8(&buf).unwrap().to_string();
2124 | |                 match tx.send(output) {
2125 | |                     Ok(_) => (),
2126 | |                     Err(_) => break,
2127 | |                 }
2128 | |             }
     | |_____________^
     = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#unused_io_amount
     = note: `#[deny(clippy::unused_io_amount)]` on by default

Signed-off-by: Bo Chen <chen.bo@intel.com>
(cherry picked from commit 36890373cd)
2024-03-14 19:52:57 -07:00
Rob Bradford
21ea5afa0a vhost_user_block: Allow dead_code for embedded error
The embedded error in the enum will be read on debug output of the
error.

Fixes beta clippy issue:

warning: field `0` is never read
  --> vhost_user_block/src/lib.rs:64:23
   |
64 |     CreateKillEventFd(io::Error),
   |     ----------------- ^^^^^^^^^
   |     |
   |     field in this variant
   |
   = note: `#[warn(dead_code)]` on by default
help: consider changing the field to be of unit type to suppress this warning while preserving the field numbering, or remove the field
   |
64 |     CreateKillEventFd(()),
   |                       ~~

Signed-off-by: Rob Bradford <rbradford@rivosinc.com>
(cherry picked from commit 107f4bdc12)
2024-03-14 19:52:57 -07:00
Rob Bradford
a09d536dc1 performance-metrics: Allow dead_code for embedded error
The embedded error in the enum will be read on debug output of the
error.

Fixes beta clippy issue:

warning: field `0` is never read
  --> performance-metrics/src/performance_tests.rs:25:11
   |
25 |     Infra(InfraError),
   |     ----- ^^^^^^^^^^
   |     |
   |     field in this variant
   |
   = note: `#[warn(dead_code)]` on by default
help: consider changing the field to be of unit type to suppress this warning while preserving the field numbering, or remove the field
   |
25 |     Infra(()),
   |           ~~

Signed-off-by: Rob Bradford <rbradford@rivosinc.com>
(cherry picked from commit 8899ebd63c)
2024-03-14 19:52:57 -07:00
Rob Bradford
f8a5c149eb block: qcow: Fix beta clippy issue
warning: field `0` is never read
   --> block/src/qcow/vec_cache.rs:139:21
    |
139 |     struct NumCache(pub u64);
    |            -------- ^^^^^^^
    |            |
    |            field in this struct
    |
    = note: `#[warn(dead_code)]` on by default
help: consider changing the field to be of unit type to suppress this warning while preserving the field numbering, or remove the field
    |
139 |     struct NumCache(());
    |                     ~~

Signed-off-by: Rob Bradford <rbradford@rivosinc.com>
(cherry picked from commit c19c73cb99)
2024-03-14 19:52:57 -07:00
Yi Wang
8e6bdcbf11 build: fix clippy ptr arg issue
CI reports errors:

error: writing `&Vec` instead of `&[_]` involves a new object where a slice will do
    --> arch/src/x86_64/mod.rs:1351:19
     |
1351 |     epc_sections: &Vec<SgxEpcSection>,
     |                   ^^^^^^^^^^^^^^^^^^^ help: change this to: `&[SgxEpcSection]`
     |
     = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#ptr_arg
     = note: `-D clippy::ptr-arg` implied by `-D warnings`
     = help: to override `-D warnings` add `#[allow(clippy::ptr_arg)]`

Signed-off-by: Yi Wang <foxywang@tencent.com>
(cherry picked from commit 3d6594a594)
2024-03-14 19:52:57 -07:00
Yi Wang
93631b5e23 build: fix clippy Path::join issue
CI reports clippy errors:

error: argument to `Path::join` starts with a path separator
    --> tests/integration.rs:4076:58
     |
4076 |         let serial_socket = guest.tmp_dir.as_path().join("/tmp/serial.socket");
     |                                                          ^^^^^^^^^^^^^^^^^^^^
     |
     = note: joining a path starting with separator will replace the path instead
     = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#join_absolute_paths

Signed-off-by: Yi Wang <foxywang@tencent.com>
(cherry picked from commit ee2f0c3cb4)
2024-03-14 19:52:57 -07:00
Yi Wang
ade953e582 build: fix clippy complex closures issue
CI reports clippy errors:

error: in a `match` scrutinee, avoid complex blocks or closures with blocks; instead, move the block or closure higher and bind it with a `let`
   --> test_infra/src/lib.rs:93:51
    |
93  |           match (|| -> Result<(), WaitForBootError> {
    |  ___________________________________________________^
94  | |             let listener =
95  | |                 TcpListener::bind(listen_addr.as_str()).map_err(WaitForBootError::Listen)?;
96  | |             listener
...   |
145 | |             }
146 | |         })() {
    | |_________^
    |
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#blocks_in_conditions
    = note: `-D clippy::blocks-in-conditions` implied by `-D warnings`
    = help: to override `-D warnings` add `#[allow(clippy::blocks_in_conditions)]`

Signed-off-by: Yi Wang <foxywang@tencent.com>
(cherry picked from commit 9c2d650cb8)
2024-03-14 19:52:57 -07:00
Rob Bradford
a3bd7eb9a0 hypervisor: kvm: Import TDX vmcall structure
Consistent with the other data structures and constants used in TDX
support code import the necessary structures from the kernel for
accessing the vmcall structure.

Signed-off-by: Rob Bradford <rbradford@rivosinc.com>
(cherry picked from commit 3993663e5c)
2024-03-14 19:52:57 -07:00
Thomas Barrett
b0dd4e72c5 pci: vfio: naturally align bar
According to PCIe specification, a 64-bit MMIO BAR should be
naturally aligned. In addition to being more compliant with
the specification, natural aligned BARs are mapped with
the largest possible page size by the host iommu driver, which
should speed up boot time and reduce IOTLB thrashing for virtual
machines with VFIO devices.

Signed-off-by: Thomas Barrett <tbarrett@crusoeenergy.com>
(cherry picked from commit c9f94be7ab)
2024-03-14 19:52:57 -07:00
Alyssa Ross
ed1b415bad virtio-devices: fix reading vsock connect command
The socket is nonblocking, so it's not guaranteed that it will be
possible to read the whole connect command in a single iteration of
the event loop.  To reproduce:

	(echo -n 'CONNECT '; sleep 1; echo 1234; cat) | socat STDIO UNIX-CONNECT:vsock.sock

This would produce the error:

	cloud-hypervisor: 5.509209s: <_vsock4> INFO:virtio-devices/src/vsock/unix/muxer.rs:446 -- vsock: error adding local-init connection: UnixRead(Os { code: 11, kind: WouldBlock, message: "Resource temporarily unavailable" })

To fix this, if we only get a partial command, we need to save it for
future iterations of the event loop, and only proceed once we've read
a complete command.

Signed-off-by: Alyssa Ross <hi@alyssa.is>
(cherry picked from commit 48de800756)
2024-03-14 19:52:57 -07:00
Alyssa Ross
71708c9794 vmm: limit VSOCK CIDs to 32 bits
The VIRTIO specification[1] says:

> The upper 32 bits of the CID are reserved and zeroed.

We should therefore not allow the user to supply a VSOCK CID with
those bits set.  To accomplish this, limit the public API of the
virtio-vsock device to only accept 32-bit CIDs, while still using
64-bit CIDs internally since that's how virtio-vsock works.

[1]: https://docs.oasis-open.org/virtio/virtio/v1.2/csd01/virtio-v1.2-csd01.html#x1-4400004

Signed-off-by: Alyssa Ross <hi@alyssa.is>
(cherry picked from commit 451d3fb2f0)
2024-03-14 19:52:57 -07:00
Alyssa Ross
f58f9cf16a vmm: forbid using special VSOCK CIDs for guests
I accidentally ran a VM with CID 2 (VMADDR_CID_HOST), and very strange
and difficult to debug behavior ensued.  I don't think a virtio-vsock
device should be allowed to have any of the special CIDs
(VMADDR_CID_ANY, VMADDR_CID_HYPERVISOR, VMADDR_CID_LOCAL, VMADDR_CID_HOST).

Signed-off-by: Alyssa Ross <hi@alyssa.is>
(cherry picked from commit 7d0b85d727)
2024-03-14 19:52:57 -07:00
Thomas Barrett
a7d967215f arch: x86_64: handle npot CPU topology
This PR addresses a bug in which the cpu topology of a guest
with non power-of-two number of cores is incorrect. For example,
in some contexts, a virtual machine with 2-sockets and 12-cores
will incorrectly believe that 16 cores are on socket 1 and 8
cores are on socket 2. In other cases, common topology enumeration
software such as hwloc will crash.

The root of the problem was the way that cloud-hypervisor generates
apic_id. On x86_64, the (x2) apic_id embeds information about cpu
topology. The cpuid instruction is primarily used to discover the
number of sockets, dies, cores, threads, etc. Using this information,
the (x2) apic_id is masked to determine which {core, die, socket} the
cpu is on. When the cpu topology is not a power of two
(e.g. a 12-core machine), this requires non-contiguous (x2) apic_id.

Signed-off-by: Thomas Barrett <tbarrett@crusoeenergy.com>
(cherry picked from commit 5c0b66529a)
2024-03-14 19:52:57 -07:00
Bo Chen
0e29fe1517 build: Bump rustix from 0.38.8 to 0.38.25
Signed-off-by: Bo Chen <chen.bo@intel.com>
(cherry picked from commit 026d8908fd)
2024-03-14 19:52:57 -07:00
Bo Chen
fbb648166a fuzz: Fix cargo fuzz build issue with crc32c
Signed-off-by: Bo Chen <chen.bo@intel.com>
(cherry picked from commit 08120b79fc)
2024-03-14 19:52:57 -07:00
Rob Bradford
ea87988f93 block: Replace use of crc32c crate with crc-any
According to crates.io the crc-any crate is actively maintained which
avoids issues with the crc32c crate and the nightly compiler.

Fixes: #6168

Signed-off-by: Rob Bradford <rbradford@rivosinc.com>
(cherry picked from commit d516374c39)
2024-03-14 19:52:57 -07:00
Bo Chen
f35d573431 build: Bump vmm-sys-util crate and its consumers
This patch bumps the following crates, including `kvm-bindings@0.7.0`*,
`kvm-ioctls@0.16.0`**, `linux-loader@0.11.0`, `versionize@0.2.0`,
`versionize_derive@0.1.6`***, `vhost@0.10.0`,
`vhost-user-backend@0.13.1`, `virtio-queue@0.11.0`, `vm-memory@0.14.0`,
`vmm-sys-util@0.12.1`, and the latest of `vfio-bindings`, `vfio-ioctls`,
`mshv-bindings`,`mshv-ioctls`, and `vfio-user`.

* A fork of the `kvm-bindings` crate is being used to support
serialization of various structs for migration [1]. Also, code changes
are made to accommodate the updated `struct xsave` from the Linux
kernel. Note: these changes related to `struct xsave` break
live-upgrade.

** The new `kvm-ioctls` crate introduced breaking changes for
the `get/set_one_reg` API on `aarch64` [2], so code changes are made to
the new APIs.

*** A fork of the `versionize_derive` crate is being used to support
versionize on packed structs [3].

[1] https://github.com/cloud-hypervisor/kvm-bindings/tree/ch-v0.7.0
[2] https://github.com/rust-vmm/kvm-ioctls/pull/223
[3] https://github.com/cloud-hypervisor/versionize_derive/tree/ch-0.1.6

Fixes: #6072

Signed-off-by: Bo Chen <chen.bo@intel.com>
(cherry picked from commit 3ce0fef7fd)
2024-03-14 19:52:57 -07:00
Thomas Barrett
a489a11ccf arch: x86_64: enable HTT flag
When the HTT flag CPUID.1.EDX[HTT] is 0, it indicates that there is
only a single logical processor in the package. When HTT is 1, it
indicates that CPUID.1.EBX[23:16] contains the number of logical
processors in the package.

When this information is not included in CPUID leaf 0x1, some cpu
topology enumeration software such as hwloc are known to crash.

Signed-off-by: Thomas Barrett <tbarrett@crusoeenergy.com>
(cherry picked from commit 5ec47d4883)
2024-03-14 19:52:57 -07:00
Thomas Barrett
4f1fb3632b arch: x86_64: enable nested virtualization on amd if supported
When using amd topology, the svm feature flag on cpuid leaf
0x8000_0001.ecx is overwritten. We update the amd cpu topology
logic to use the flag values that originated in
KVM_GET_SUPPORTED_CPUID ioctl and override as necessary.

Signed-off-by: Thomas Barrett <tbarrett@crusoeenergy.com>
(cherry picked from commit 7bc764d4e0)
2024-03-14 19:52:57 -07:00
Ravi kumar Veeramally
693e456793 build: Update ARM64 GitHub action for windows integration tests
Signed-off-by: Ravi kumar Veeramally <ravikumar.veeramally@intel.com>
(cherry picked from commit fbcf5fb37d)
2024-03-14 19:52:57 -07:00
Ravi kumar Veeramally
8c1b112a60 build: Add GitHub action for metrics tests
Signed-off-by: Ravi kumar Veeramally <ravikumar.veeramally@intel.com>
Signed-off-by: Rob Bradford <rbradford@rivosinc.com>
(cherry picked from commit d245e62427)
2024-03-14 19:52:57 -07:00
Rob Bradford
26cab16830 build: Avoid cancellation of release build workflow on MQ
When running on the merge group this workflow is run twice - once for
the create event (merge queue creates a new branch) and once for the
merge_group event. Unfortunately the second event would cause the first
to be cancelled - unfortunately sometimes that second event is the
create event where the job in the workflow only runs if it is also a
tag.

By creating distinct concurrency groups for each event type then the
cross cancellation can be avoided.

Signed-off-by: Rob Bradford <rbradford@rivosinc.com>
(cherry picked from commit 6f49d7f192)
2024-03-14 19:52:57 -07:00
Rob Bradford
c588138187 build: Use authentication token to avoid GitHub rate limit
The workers share a common public IP address and often GitHub will
reject attempts to access the API due to exceeding the anonymous rate
limit threshold.

Signed-off-by: Rob Bradford <rbradford@rivosinc.com>
(cherry picked from commit 0f71956d6d)
2024-03-14 19:52:57 -07:00
Bo Chen
4b72e5a886 build: Allow 'cancel-in-progress' for bare-metal workers
Signed-off-by: Bo Chen <chen.bo@intel.com>
(cherry picked from commit 46c9b9693c)
2024-03-14 19:52:57 -07:00
Rob Bradford
51febbb7fe build: Add SGX, VFIO and rate limit testing to MQ
Run these workflows as part of the merge queue to help improve testing
coverage.

Signed-off-by: Rob Bradford <rbradford@rivosinc.com>
(cherry picked from commit cdafe5344d)
2024-03-14 19:52:57 -07:00
Bo Chen
551d36e502 build: Add a step to fix workspace permissions on bare-metal workers
When a bare-metal worker is canceled, its workspace can be left with
files owned by the root user as a result of running tests from our
container. This patch add a step to fix workspace permissions for such
case before checking out code.

Signed-off-by: Bo Chen <chen.bo@intel.com>
(cherry picked from commit f48942ce3f)
2024-03-14 19:52:57 -07:00
Bo Chen
6a5a2ac83d tests: Fix test_snapshot_restore_hotplug_virtiomem on 16 cores VM
It takes longer time to restore a VM on a VM with 16 cores comparing
with ones with 64 cores.

Signed-off-by: Bo Chen <chen.bo@intel.com>
(cherry picked from commit 0718067851)
2024-03-14 19:52:57 -07:00
Bo Chen
37666f842d build: Run integration tests on smaller VMs
Signed-off-by: Bo Chen <chen.bo@intel.com>
Signed-off-by: Rob Bradford <rbradford@rivosinc.com>
(cherry picked from commit 7d60ab70e6)
2024-03-14 19:52:57 -07:00
Ravi kumar Veeramally
a09d828713 scripts: Update Azure storage location to access images
Signed-off-by: Ravi kumar Veeramally <ravikumar.veeramally@intel.com>
(cherry picked from commit 05ec6190da)
2024-03-14 19:52:57 -07:00
Rob Bradford
67904a90fc build: Cancel in progress actions on update
If the PR updated cancel outstanding jobs to conserve resources.

Signed-off-by: Rob Bradford <rbradford@rivosinc.com>
(cherry picked from commit 1db30405e1)
2024-03-14 19:52:57 -07:00
Rob Bradford
196e653a50 build: Only run bisectability check on PRs
Signed-off-by: Rob Bradford <rbradford@rivosinc.com>
(cherry picked from commit 3e35529842)
2024-03-14 19:52:57 -07:00
Rob Bradford
355148c3d6 build: Only check DCO on PRs
The DCO tool doesn't understand merge_groups but we still need to have a
valid status check to allow the merge group to proceed.

Signed-off-by: Rob Bradford <rbradford@rivosinc.com>
(cherry picked from commit 96cc1ba76c)
2024-03-14 19:52:57 -07:00
Rob Bradford
1dff2503a6 build: Skip release check on pull requests
This takes a long time and duplicates existing checks on the pull
requests.

Signed-off-by: Rob Bradford <rbradford@rivosinc.com>
(cherry picked from commit 022f375ef8)
2024-03-14 19:52:57 -07:00
Rob Bradford
f4c85aef89 build: Only run Intel + glibc on PR builds for x86-64 tests
Run all the tests on the merge queue.

Signed-off-by: Rob Bradford <rbradford@rivosinc.com>
(cherry picked from commit 81b95023c4)
2024-03-14 19:52:57 -07:00
Rob Bradford
0131a408bf build: Make the Windows Guest Test always pass on PR builds
When running with the merge queue the tests will be fully executed.

Signed-off-by: Rob Bradford <rbradford@rivosinc.com>
(cherry picked from commit f15ca1aec3)
2024-03-14 19:52:57 -07:00
Rob Bradford
bd506500d7 build: Remove unnecessary if event checks from vfio/sgx workflows
Signed-off-by: Rob Bradford <rbradford@rivosinc.com>
(cherry picked from commit cb8a728dfb)
2024-03-14 19:52:57 -07:00
Rob Bradford
80724b1662 build: Use a nicer name for DCO check step
Signed-off-by: Rob Bradford <rbradford@rivosinc.com>
(cherry picked from commit 80aa91f24c)
2024-03-14 19:52:57 -07:00
Rob Bradford
1f6b43db49 build: Ensure all required checks run on merge_group
And clean up some of the whitespace formatting so that the "name" and
"on" are grouped away from the "jobs".

Signed-off-by: Rob Bradford <rbradford@rivosinc.com>
(cherry picked from commit d9f48505fe)
2024-03-14 19:52:57 -07:00
Bo Chen
878c2275a2 ci: Remove Jenkinsfile
Most of our CI workers are now running form GitHub actions, so we are
ready to disable Jenkins CI workers.

See: #6231

Signed-off-by: Bo Chen <chen.bo@intel.com>
(cherry picked from commit 1d098949b9)
2024-03-14 19:52:57 -07:00
Ravi kumar Veeramally
5fc018abdd build: Add GitHub action for Windows guest integration tests
Signed-off-by: Ravi kumar Veeramally <ravikumar.veeramally@intel.com>
Signed-off-by: Rob Bradford <rbradford@rivosinc.com>
(cherry picked from commit ba6bfee4ff)
2024-03-14 19:52:57 -07:00
Ravi kumar Veeramally
690e10eef4 build: Add GitHub action for Rate Limiter integration tests
Signed-off-by: Ravi kumar Veeramally <ravikumar.veeramally@intel.com>
(cherry picked from commit 57fb97e41f)
2024-03-14 19:52:57 -07:00
Ravi kumar Veeramally
ea12024793 build: Add GitHub action for VFIO integration tests
Signed-off-by: Ravi kumar Veeramally <ravikumar.veeramally@intel.com>
(cherry picked from commit b765acd608)
2024-03-14 19:52:57 -07:00
Ravi kumar Veeramally
e082ed23ed build: Add GitHub action for SGX integration tests
Signed-off-by: Ravi kumar Veeramally <ravikumar.veeramally@intel.com>
(cherry picked from commit 4fb86e9915)
2024-03-14 19:52:57 -07:00
Rob Bradford
d1953633e2 build: Add some timeouts to integration test workflow
Add top-level timeout for the jobs and also more agressive per step
timeouts.

Signed-off-by: Rob Bradford <rbradford@rivosinc.com>
(cherry picked from commit 1fe2771a0d)
2024-03-14 19:52:57 -07:00
Rob Bradford
23f1490667 build: Add libc to matrix for x86-64 tests
To reduce issues caused by flaky tests split the musl and glibc jobs
into separate jobs. This means fewer jobs will need to be restarted for
flaky tests. This will also increase CI throughput since the musl builds
account for ~40% of the total CI time when run together with glibc.

Signed-off-by: Rob Bradford <rbradford@rivosinc.com>
(cherry picked from commit 2e4079becb)
2024-03-14 19:52:57 -07:00
Rob Bradford
b312a970ef build: Disable "fail fast" on x86-64 GitHub action
This will help handle flakiness in the builds by requiring the minimum
number of restarts.

Signed-off-by: Rob Bradford <rbradford@rivosinc.com>
(cherry picked from commit d32de07be7)
2024-03-14 19:52:57 -07:00
Rob Bradford
6e544d0a30 build: Switch GitHub action ARM64 builds to musl
Signed-off-by: Rob Bradford <rbradford@rivosinc.com>
(cherry picked from commit 6ec83c7d8e)
2024-03-14 19:52:57 -07:00
Rob Bradford
bc84ac4699 build: Extend x86-64 GitHub action to AMD runner
Use the matrix to add a build runnind on the AMD variant of the garm
runner.

Signed-off-by: Rob Bradford <rbradford@rivosinc.com>
(cherry picked from commit 84a6da5e93)
2024-03-14 19:52:57 -07:00
Rob Bradford
78f0f30751 tests: Remove download of unused bionic image for aarch64
The bionic image was being downloaded and converted but no test uses
this image any longer.

Signed-off-by: Rob Bradford <rbradford@rivosinc.com>
(cherry picked from commit 6930370a03)
2024-03-14 19:52:57 -07:00
Rob Bradford
8b0d43e2fe build: Add GitHub action for ARM64 integration tests
Signed-off-by: Rob Bradford <rbradford@rivosinc.com>
(cherry picked from commit 89f2a4882e)
2024-03-14 19:52:57 -07:00
Rob Bradford
61430fb345 build: Add GitHub action for unit/integration testing
Signed-off-by: Rob Bradford <rbradford@rivosinc.com>
(cherry picked from commit 307a0166c5)
2024-03-14 19:52:57 -07:00
353 changed files with 22196 additions and 33543 deletions

View File

@@ -16,8 +16,3 @@ updates:
allow:
- dependency-type: direct
- dependency-type: indirect
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: daily
open-pull-requests-limit: 1

View File

@@ -10,7 +10,7 @@ jobs:
name: Audit
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions-rust-lang/audit@v1
- uses: actions/checkout@v4
- uses: actions-rs/audit-check@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -15,13 +15,13 @@ jobs:
- stable
- beta
- nightly
- "1.88.0"
- "1.70"
target:
- x86_64-unknown-linux-gnu
- x86_64-unknown-linux-musl
steps:
- name: Code checkout
uses: actions/checkout@v5
uses: actions/checkout@v4
with:
fetch-depth: 0
@@ -29,46 +29,38 @@ jobs:
run: sudo apt install -y musl-tools
- name: Install Rust toolchain (${{ matrix.rust }})
uses: dtolnay/rust-toolchain@stable
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
override: true
- name: Build (default features)
run: cargo rustc --locked --bin cloud-hypervisor -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
run: cargo rustc --locked --bin cloud-hypervisor -- -D warnings -D clippy::undocumented_unsafe_blocks
- name: Build (kvm)
run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks
- name: Build (default features + tdx)
run: cargo rustc --locked --bin cloud-hypervisor --features "tdx" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
run: cargo rustc --locked --bin cloud-hypervisor --features "tdx" -- -D warnings -D clippy::undocumented_unsafe_blocks
- name: Build (default features + dbus_api)
run: cargo rustc --locked --bin cloud-hypervisor --features "dbus_api" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
run: cargo rustc --locked --bin cloud-hypervisor --features "dbus_api" -- -D warnings -D clippy::undocumented_unsafe_blocks
- name: Build (default features + guest_debug)
run: cargo rustc --locked --bin cloud-hypervisor --features "guest_debug" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
- name: Build (default features + pvmemcontrol)
run: cargo rustc --locked --bin cloud-hypervisor --features "pvmemcontrol" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
- name: Build (default features + fw_cfg)
run: cargo rustc --locked --bin cloud-hypervisor --features "fw_cfg" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
- name: Build (default features + ivshmem)
run: cargo rustc --locked --bin cloud-hypervisor --features "ivshmem" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
run: cargo rustc --locked --bin cloud-hypervisor --features "guest_debug" -- -D warnings -D clippy::undocumented_unsafe_blocks
- name: Build (mshv)
run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "mshv" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "mshv" -- -D warnings -D clippy::undocumented_unsafe_blocks
- name: Build (sev_snp)
run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "sev_snp" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "sev_snp" -- -D warnings -D clippy::undocumented_unsafe_blocks
- name: Build (igvm)
run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "igvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "igvm" -- -D warnings -D clippy::undocumented_unsafe_blocks
- name: Build (mshv + kvm)
run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "mshv,kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
run: cargo rustc --locked --bin cloud-hypervisor --no-default-features --features "mshv,kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks
- name: Release Build (default features)
run: cargo build --locked --all --release --target=${{ matrix.target }}

View File

@@ -6,9 +6,9 @@ jobs:
name: DCO Check ("Signed-Off-By")
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v4
- name: Set up Python 3.x
uses: actions/setup-python@v6
uses: actions/setup-python@v1
with:
python-version: '3.x'
- name: Check DCO

View File

@@ -18,16 +18,16 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Code checkout
uses: actions/checkout@v5
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@v1
- name: Login to ghcr
uses: docker/login-action@v3
uses: docker/login-action@v2
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
@@ -36,17 +36,17 @@ jobs:
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
uses: docker/metadata-action@v4
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
# generate Docker tags based on the following events/attributes
tags: |
type=raw,value=20250815-0
type=raw,value={{date 'YYYYMMDD'}}-0
type=sha
- name: Build and push
if: ${{ github.event_name == 'push' }}
uses: docker/build-push-action@v6
uses: docker/build-push-action@v2
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@v6
uses: docker/build-push-action@v2
with:
file: ./resources/Dockerfile
platforms: linux/amd64,linux/arm64

View File

@@ -1,32 +0,0 @@
name: Cloud Hypervisor Code Formatting
on: [pull_request, merge_group]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
name: Code Formatting
runs-on: ubuntu-latest
strategy:
matrix:
rust:
- nightly
target:
- x86_64-unknown-linux-gnu
- aarch64-unknown-linux-musl
env:
RUSTFLAGS: -D warnings
steps:
- name: Code checkout
uses: actions/checkout@v5
- name: Install Rust toolchain (${{ matrix.rust }})
uses: dtolnay/rust-toolchain@stable
with:
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
components: rustfmt
- name: Formatting (rustfmt)
run: cargo fmt --all -- --check
- name: Formatting (fuzz) (rustfmt)
run: cargo fmt --all --manifest-path fuzz/Cargo.toml -- --check

View File

@@ -14,19 +14,18 @@ jobs:
- nightly
target:
- x86_64-unknown-linux-gnu
env:
RUSTFLAGS: -D warnings
steps:
- name: Code checkout
uses: actions/checkout@v5
uses: actions/checkout@v4
- name: Install Rust toolchain (${{ matrix.rust }})
uses: dtolnay/rust-toolchain@stable
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
override: true
- name: Install Cargo fuzz
run: cargo install cargo-fuzz
- name: Fuzz Build
# Temporary fix for cargo-fuzz on latest nightly: https://github.com/rust-fuzz/cargo-fuzz/issues/276
#run: cargo install cargo-fuzz
run: cargo install --git https://github.com/rust-fuzz/cargo-fuzz --rev b4df3e58f767b5cad8d1aa6753961003f56f3609
- name: Cargo Fuzz Build
run: cargo fuzz build
- name: Fuzz Check
run: cargo fuzz check

View File

@@ -8,12 +8,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v5
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 0
- name: Set up Python 3.10
uses: actions/setup-python@v6
uses: actions/setup-python@v3
with:
python-version: "3.10"
- name: Install dependencies

View File

@@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v5
uses: actions/checkout@v4
- name: Lint Dockerfile
uses: hadolint/hadolint-action@master

View File

@@ -6,14 +6,14 @@ concurrency:
jobs:
build:
timeout-minutes: 120
timeout-minutes: 60
name: Tests (ARM64)
runs-on: bookworm-arm64
runs-on: focal-arm64
steps:
- name: Fix workspace permissions
run: sudo chown -R runner:runner ${GITHUB_WORKSPACE}
run: sudo chown -R github-runner:github-runner ${GITHUB_WORKSPACE}
- name: Code checkout
uses: actions/checkout@v5
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Run unit tests (musl)
@@ -21,14 +21,14 @@ jobs:
- name: Load openvswitch module
run: sudo modprobe openvswitch
- name: Run integration tests (musl)
timeout-minutes: 60
timeout-minutes: 30
run: scripts/dev_cli.sh tests --integration --libc musl
- name: Install Azure CLI
if: ${{ github.event_name != 'pull_request' }}
run: |
sudo apt install -y ca-certificates curl apt-transport-https lsb-release gnupg
curl -sL https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/microsoft.gpg > /dev/null
echo "deb [arch=arm64] https://packages.microsoft.com/repos/azure-cli/ bookworm main" | sudo tee /etc/apt/sources.list.d/azure-cli.list
echo "deb [arch=arm64] https://packages.microsoft.com/repos/azure-cli/ focal main" | sudo tee /etc/apt/sources.list.d/azure-cli.list
sudo apt update
sudo apt install -y azure-cli
- name: Download Windows image

View File

@@ -7,12 +7,12 @@ on:
jobs:
build:
name: Tests (Metrics)
runs-on: bare-metal-9950x
runs-on: jammy-metrics
env:
METRICS_PUBLISH_KEY: ${{ secrets.METRICS_PUBLISH_KEY }}
steps:
- name: Code checkout
uses: actions/checkout@v5
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Run metrics tests

View File

@@ -7,18 +7,21 @@ concurrency:
jobs:
build:
name: Tests (Rate-Limiter)
runs-on: ${{ github.event_name == 'pull_request' && 'ubuntu-latest' || 'bare-metal-9950x' }}
runs-on: ${{ github.event_name == 'pull_request' && 'ubuntu-latest' || 'jammy-rate-limiter' }}
env:
AUTH_DOWNLOAD_TOKEN: ${{ secrets.AUTH_DOWNLOAD_TOKEN }}
steps:
- name: Fix workspace permissions
if: ${{ github.event_name != 'pull_request' }}
run: sudo chown -R github-runner:github-runner ${GITHUB_WORKSPACE}
- name: Code checkout
if: ${{ github.event_name != 'pull_request' }}
uses: actions/checkout@v5
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Run rate-limiter integration tests
if: ${{ github.event_name != 'pull_request' }}
timeout-minutes: 20
timeout-minutes: 10
run: scripts/dev_cli.sh tests --integration-rate-limiter
- name: Skipping build for PR
if: ${{ github.event_name == 'pull_request' }}

32
.github/workflows/integration-sgx.yaml vendored Normal file
View File

@@ -0,0 +1,32 @@
name: Cloud Hypervisor Tests (SGX)
on: [merge_group, pull_request]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
name: Tests (SGX)
runs-on: ${{ github.event_name == 'pull_request' && 'ubuntu-latest' || 'jammy-sgx' }}
env:
AUTH_DOWNLOAD_TOKEN: ${{ secrets.AUTH_DOWNLOAD_TOKEN }}
steps:
- name: Fix workspace permissions
if: ${{ github.event_name != 'pull_request' }}
run: sudo chown -R github-runner:github-runner ${GITHUB_WORKSPACE}
- name: Code checkout
if: ${{ github.event_name != 'pull_request' }}
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Run SGX integration tests
if: ${{ github.event_name != 'pull_request' }}
timeout-minutes: 10
run: scripts/dev_cli.sh tests --integration-sgx
- name: Run SGX integration tests for musl
if: ${{ github.event_name != 'pull_request' }}
timeout-minutes: 10
run: scripts/dev_cli.sh tests --integration-sgx --libc musl
- name: Skipping build for PR
if: ${{ github.event_name == 'pull_request' }}
run: echo "Skipping build for PR"

View File

@@ -7,27 +7,26 @@ concurrency:
jobs:
build:
name: Tests (VFIO)
runs-on: ${{ github.event_name == 'pull_request' && 'ubuntu-latest' || 'vfio-nvidia' }}
runs-on: ${{ github.event_name == 'pull_request' && 'ubuntu-latest' || 'jammy-vfio' }}
env:
AUTH_DOWNLOAD_TOKEN: ${{ secrets.AUTH_DOWNLOAD_TOKEN }}
steps:
- name: Fix workspace permissions
if: ${{ github.event_name != 'pull_request' }}
run: sudo chown -R runner:runner ${GITHUB_WORKSPACE}
run: sudo chown -R github-runner:github-runner ${GITHUB_WORKSPACE}
- name: Code checkout
if: ${{ github.event_name != 'pull_request' }}
uses: actions/checkout@v5
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Run VFIO integration tests
if: ${{ github.event_name != 'pull_request' }}
timeout-minutes: 15
run: scripts/dev_cli.sh tests --integration-vfio
# Most tests are failing with musl see #6790
# - name: Run VFIO integration tests for musl
# if: ${{ github.event_name != 'pull_request' }}
# timeout-minutes: 15
# run: scripts/dev_cli.sh tests --integration-vfio --libc musl
- name: Run VFIO integration tests for musl
if: ${{ github.event_name != 'pull_request' }}
timeout-minutes: 15
run: scripts/dev_cli.sh tests --integration-vfio --libc musl
- name: Skipping build for PR
if: ${{ github.event_name == 'pull_request' }}
run: echo "Skipping build for PR"

View File

@@ -11,7 +11,7 @@ jobs:
steps:
- name: Code checkout
if: ${{ github.event_name != 'pull_request' }}
uses: actions/checkout@v5
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install Docker

View File

@@ -17,7 +17,7 @@ jobs:
steps:
- name: Code checkout
if: ${{ github.event_name != 'pull_request' || (matrix.runner == 'garm-jammy' && matrix.libc == 'gnu') }}
uses: actions/checkout@v5
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install Docker

View File

@@ -1,45 +0,0 @@
name: Link Check (lychee)
on: pull_request
jobs:
link_check:
name: Link Check
runs-on: ubuntu-latest
steps:
- name: Code checkout
uses: actions/checkout@v5
with:
# Fetch the entire history so git diff can compare against the base branch
fetch-depth: 0
- name: Get changed files in PR
id: changed-files
uses: tj-actions/changed-files@v46 # Using a dedicated action for robustness
with:
# Compare the HEAD of the PR with the merge-base (where the PR branches off)
base_sha: ${{ github.event.pull_request.base.sha }}
# NEW STEP: Print all changed-files outputs for verification
- name: Verify Changed Files
run: |
echo "--- tj-actions/changed-files Outputs ---"
echo "any_changed: ${{ steps.changed-files.outputs.any_changed }}"
echo "all_changed_files: ${{ steps.changed-files.outputs.all_changed_files }}"
echo "added_files: ${{ steps.changed-files.outputs.added_files }}"
echo "modified_files: ${{ steps.changed-files.outputs.modified_files }}"
echo "deleted_files: ${{ steps.changed-files.outputs.deleted_files }}"
echo "renamed_files: ${{ steps.changed-files.outputs.renamed_files }}"
echo "----------------------------------------"
# This will also show if the all_changed_files string is empty or not
if [ -n "${{ steps.changed-files.outputs.all_changed_files }}" ]; then
echo "Detected changes: all_changed_files output is NOT empty."
else
echo "No changes detected: all_changed_files output IS empty."
fi
- name: Link Availability Check (Diff Only)
# MODIFIED: Only run lychee if the 'all_changed_files' output is not an empty string
if: ${{ steps.changed-files.outputs.all_changed_files != '' }}
uses: lycheeverse/lychee-action@master
with:
# Pass the space-separated list of changed files to lychee
args: --verbose --config .lychee.toml ${{ steps.changed-files.outputs.all_changed_files }}
failIfEmpty: false
fail: true

View File

@@ -6,7 +6,7 @@ jobs:
runs-on: ubuntu-latest
container: openapitools/openapi-generator-cli
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v4
- name: Validate OpenAPI
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -1,32 +0,0 @@
name: Cloud Hypervisor Consistency
on: [pull_request, merge_group]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
name: Rust VMM Consistency Check
runs-on: ubuntu-latest
steps:
- name: Code checkout
uses: actions/checkout@v5
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

View File

@@ -1,30 +0,0 @@
name: Cloud Hypervisor RISC-V 64-bit kvm build Preview
on: [pull_request, merge_group]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
name: Cargo
runs-on: riscv64-qemu-host
strategy:
fail-fast: false
steps:
- name: Code checkout
uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Install Rust toolchain
run: /opt/scripts/exec-in-qemu.sh rustup default 1.88.0
- name: Build test (kvm)
run: /opt/scripts/exec-in-qemu.sh cargo rustc --locked --no-default-features --features "kvm"
- name: Clippy test (kvm)
run: /opt/scripts/exec-in-qemu.sh cargo clippy --locked --no-default-features --features "kvm"
- name: Check no files were modified
run: test -z "$(git status --porcelain)"

View File

@@ -1,39 +0,0 @@
name: Cloud Hypervisor RISC-V 64-bit Preview
on: [pull_request, merge_group]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
name: Cargo
runs-on: riscv64-qemu-host
strategy:
fail-fast: false
matrix:
module:
- hypervisor
- arch
- vm-allocator
- devices
steps:
- name: Code checkout
uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Install Rust toolchain
run: /opt/scripts/exec-in-qemu.sh rustup default 1.88.0
- name: Build ${{ matrix.module }} Module (kvm)
run: /opt/scripts/exec-in-qemu.sh cargo rustc --locked -p ${{ matrix.module }} --no-default-features --features "kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
- 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)"

View File

@@ -6,14 +6,13 @@ concurrency:
jobs:
build:
name: Quality (clippy)
name: Quality (clippy, rustfmt)
runs-on: ubuntu-latest
continue-on-error: ${{ matrix.experimental }}
strategy:
fail-fast: false
matrix:
rust:
- beta
- stable
target:
- aarch64-unknown-linux-gnu
@@ -21,15 +20,23 @@ jobs:
- x86_64-unknown-linux-gnu
- x86_64-unknown-linux-musl
experimental: [false]
include:
- rust: beta
target: aarch64-unknown-linux-gnu
experimental: true
- rust: beta
target: aarch64-unknown-linux-musl
experimental: true
- rust: beta
target: x86_64-unknown-linux-gnu
experimental: true
- rust: beta
target: x86_64-unknown-linux-musl
experimental: true
- rust: stable
experimental: false
steps:
- name: Code checkout
uses: actions/checkout@v5
uses: actions/checkout@v4
with:
fetch-depth: 0
@@ -39,7 +46,7 @@ jobs:
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
override: true
components: clippy
components: rustfmt, clippy
- name: Bisectability Check (default features)
if: ${{ github.event_name == 'pull_request' && matrix.target == 'x86_64-unknown-linux-gnu' }}
@@ -49,113 +56,76 @@ jobs:
for commit in $commits; do git checkout $commit; cargo check --tests --examples --all --target=${{ matrix.target }}; done
git checkout ${{ github.sha }}
- name: Formatting (rustfmt)
run: cargo fmt -- --check
- name: Clippy (kvm)
uses: houseabsolute/actions-rust-cross@v1
with:
command: clippy
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
args: --locked --all --all-targets --no-default-features --tests --examples --features "kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
- name: Clippy (mshv)
uses: houseabsolute/actions-rust-cross@v1
with:
command: clippy
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
args: --locked --all --all-targets --no-default-features --tests --examples --features "mshv" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
- name: Clippy (mshv + kvm)
uses: houseabsolute/actions-rust-cross@v1
with:
command: clippy
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
args: --locked --all --all-targets --no-default-features --tests --examples --features "mshv,kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
- name: Clippy (default features)
uses: houseabsolute/actions-rust-cross@v1
with:
command: clippy
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
args: --locked --all --all-targets --tests --examples -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
- name: Clippy (default features + guest_debug)
uses: houseabsolute/actions-rust-cross@v1
with:
command: clippy
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
args: --locked --all --all-targets --tests --examples --features "guest_debug" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
- name: Clippy (default features + pvmemcontrol)
uses: houseabsolute/actions-rust-cross@v1
with:
command: clippy
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
args: --locked --all --all-targets --tests --examples --features "pvmemcontrol" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
- name: Clippy (default features + tracing)
uses: houseabsolute/actions-rust-cross@v1
with:
command: clippy
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
args: --locked --all --all-targets --tests --examples --features "tracing" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
- name: Clippy (default features + fw_cfg)
uses: actions-rs/cargo@v1
with:
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
command: clippy
args: --target=${{ matrix.target }} --locked --all --all-targets --tests --examples --features "fw_cfg" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
args: --target=${{ matrix.target }} --locked --all --all-targets --no-default-features --tests --examples --features "kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks
- name: Clippy (default features + ivshmem)
uses: houseabsolute/actions-rust-cross@v1
- name: Clippy (default features)
uses: actions-rs/cargo@v1
with:
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
command: clippy
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
args: --locked --all --all-targets --tests --examples --features "ivshmem" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
args: --target=${{ matrix.target }} --locked --all --all-targets --tests --examples -- -D warnings -D clippy::undocumented_unsafe_blocks
- name: Clippy (default features + guest_debug)
uses: actions-rs/cargo@v1
with:
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
command: clippy
args: --target=${{ matrix.target }} --locked --all --all-targets --tests --examples --features "guest_debug" -- -D warnings -D clippy::undocumented_unsafe_blocks
- name: Clippy (default features + tracing)
uses: actions-rs/cargo@v1
with:
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
command: clippy
args: --target=${{ matrix.target }} --locked --all --all-targets --tests --examples --features "tracing" -- -D warnings -D clippy::undocumented_unsafe_blocks
- name: Clippy (mshv)
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
uses: actions-rs/cargo@v1
with:
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
command: clippy
args: --target=${{ matrix.target }} --locked --all --all-targets --no-default-features --tests --examples --features "mshv" -- -D warnings -D clippy::undocumented_unsafe_blocks
- name: Clippy (mshv + kvm)
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
uses: actions-rs/cargo@v1
with:
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
command: clippy
args: --target=${{ matrix.target }} --locked --all --all-targets --no-default-features --tests --examples --features "mshv,kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks
- name: Clippy (sev_snp)
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
uses: houseabsolute/actions-rust-cross@v1
uses: actions-rs/cargo@v1
with:
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
command: clippy
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
args: --locked --all --all-targets --no-default-features --tests --examples --features "sev_snp" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
args: --target=${{ matrix.target }} --locked --all --all-targets --no-default-features --tests --examples --features "sev_snp" -- -D warnings -D clippy::undocumented_unsafe_blocks
- name: Clippy (igvm)
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
uses: houseabsolute/actions-rust-cross@v1
uses: actions-rs/cargo@v1
with:
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
command: clippy
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
args: --locked --all --all-targets --no-default-features --tests --examples --features "igvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
args: --target=${{ matrix.target }} --locked --all --all-targets --no-default-features --tests --examples --features "igvm" -- -D warnings -D clippy::undocumented_unsafe_blocks
- name: Clippy (kvm + tdx)
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
uses: houseabsolute/actions-rust-cross@v1
uses: actions-rs/cargo@v1
with:
use-cross: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }}
command: clippy
cross-version: 3e0957637b49b1bbced23ad909170650c5b70635
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
args: --locked --all --all-targets --no-default-features --tests --examples --features "tdx,kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks -W clippy::assertions_on_result_states
args: --target=${{ matrix.target }} --locked --all --all-targets --no-default-features --tests --examples --features "tdx,kvm" -- -D warnings -D clippy::undocumented_unsafe_blocks
- name: Check build did not modify any files
run: test -z "$(git status --porcelain)"
@@ -165,6 +135,6 @@ jobs:
name: Typos / Spellcheck
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v4
# Executes "typos ."
- uses: crate-ci/typos@v1.36.2
- uses: crate-ci/typos@v1.16.11

View File

@@ -3,67 +3,135 @@ on: [create, merge_group]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}
cancel-in-progress: true
env:
GITHUB_TOKEN: ${{ github.token }}
jobs:
release:
if: (github.event_name == 'create' && github.event.ref_type == 'tag') || github.event_name == 'merge_group'
name: Release ${{ matrix.platform.target }}
strategy:
fail-fast: false
matrix:
platform:
- target: x86_64-unknown-linux-gnu
args: --all --release --features mshv
name_ch: cloud-hypervisor
name_ch_remote: ch-remote
- target: x86_64-unknown-linux-musl
args: --all --release --features mshv
name_ch: cloud-hypervisor-static
name_ch_remote: ch-remote-static
- target: aarch64-unknown-linux-musl
args: --all --release
name_ch: cloud-hypervisor-static-aarch64
name_ch_remote: ch-remote-static-aarch64
name: Release
runs-on: ubuntu-latest
steps:
- name: Code checkout
uses: actions/checkout@v5
uses: actions/checkout@v4
- name: Install musl-gcc
if: contains(matrix.platform.target, 'musl')
run: sudo apt install -y musl-tools
- name: Create release directory
if: |
github.event_name == 'create' && github.event.ref_type == 'tag' &&
matrix.platform.target == 'x86_64-unknown-linux-gnu'
run: rsync -rv --exclude=.git . ../cloud-hypervisor-${{ github.event.ref }}
- name: Build ${{ matrix.platform.target }}
uses: houseabsolute/actions-rust-cross@v1
- name: Install Rust toolchain (x86_64-unknown-linux-gnu)
uses: actions-rs/toolchain@v1
with:
toolchain: "1.70"
target: x86_64-unknown-linux-gnu
- name: Install Rust toolchain (x86_64-unknown-linux-musl)
uses: actions-rs/toolchain@v1
with:
toolchain: "1.70"
target: x86_64-unknown-linux-musl
- name: Build
uses: actions-rs/cargo@v1
with:
toolchain: "1.70"
command: build
target: ${{ matrix.platform.target }}
args: ${{ matrix.platform.args }}
strip: true
toolchain: "1.88.0"
- name: Copy Release Binaries
if: github.event_name == 'create' && github.event.ref_type == 'tag'
shell: bash
run: |
cp target/${{ matrix.platform.target }}/release/cloud-hypervisor ./${{ matrix.platform.name_ch }}
cp target/${{ matrix.platform.target }}/release/ch-remote ./${{ matrix.platform.name_ch_remote }}
- name: Upload Release Artifacts
if: github.event_name == 'create' && github.event.ref_type == 'tag'
uses: actions/upload-artifact@v4
args: --all --release --features mshv --target=x86_64-unknown-linux-gnu
- name: Static Build
uses: actions-rs/cargo@v1
with:
name: Artifacts for ${{ matrix.platform.target }}
path: |
./${{ matrix.platform.name_ch }}
./${{ matrix.platform.name_ch_remote }}
toolchain: "1.70"
command: build
args: --all --release --features mshv --target=x86_64-unknown-linux-musl
- name: Install Rust toolchain (aarch64-unknown-linux-musl)
uses: actions-rs/toolchain@v1
with:
toolchain: "1.70"
target: aarch64-unknown-linux-musl
override: true
- name: Create Release
if: github.event_name == 'create' && github.event.ref_type == 'tag'
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: ${{ github.ref }}
draft: true
prerelease: true
- name: Upload cloud-hypervisor
if: github.event_name == 'create' && github.event.ref_type == 'tag'
id: upload-release-cloud-hypervisor
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: target/x86_64-unknown-linux-gnu/release/cloud-hypervisor
asset_name: cloud-hypervisor
asset_content_type: application/octet-stream
- name: Upload static cloud-hypervisor
if: github.event_name == 'create' && github.event.ref_type == 'tag'
id: upload-release-static-cloud-hypervisor
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: target/x86_64-unknown-linux-musl/release/cloud-hypervisor
asset_name: cloud-hypervisor-static
asset_content_type: application/octet-stream
- name: Upload ch-remote
if: github.event_name == 'create' && github.event.ref_type == 'tag'
id: upload-release-ch-remote
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: target/x86_64-unknown-linux-gnu/release/ch-remote
asset_name: ch-remote
asset_content_type: application/octet-stream
- name: Upload static-ch-remote
if: github.event_name == 'create' && github.event.ref_type == 'tag'
id: upload-release-static-ch-remote
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: target/x86_64-unknown-linux-musl/release/ch-remote
asset_name: ch-remote-static
asset_content_type: application/octet-stream
- name: Clean build tree ahead of cross build
uses: actions-rs/cargo@v1
with:
command: clean
- name: Static Build (AArch64)
uses: actions-rs/cargo@v1
with:
use-cross: true
command: build
args: --all --release --target=aarch64-unknown-linux-musl
- name: Upload static AArch64 cloud-hypervisor
if: github.event_name == 'create' && github.event.ref_type == 'tag'
id: upload-release-static-aarch64-cloud-hypervisor
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: target/aarch64-unknown-linux-musl/release/cloud-hypervisor
asset_name: cloud-hypervisor-static-aarch64
asset_content_type: application/octet-stream
- name: Upload static AArch64 ch-remote
if: github.event_name == 'create' && github.event.ref_type == 'tag'
id: upload-release-static-aarch64-ch-remote
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: target/aarch64-unknown-linux-musl/release/ch-remote
asset_name: ch-remote-static-aarch64
asset_content_type: application/octet-stream
- name: Vendor
if: |
github.event_name == 'create' && github.event.ref_type == 'tag' &&
matrix.platform.target == 'x86_64-unknown-linux-gnu'
working-directory: ../cloud-hypervisor-${{ github.event.ref }}
run: |
mkdir ../vendor-cargo-home
@@ -71,25 +139,16 @@ jobs:
mkdir .cargo
cargo vendor > .cargo/config.toml
- name: Create vendored source archive
if: |
github.event_name == 'create' && github.event.ref_type == 'tag' &&
matrix.platform.target == 'x86_64-unknown-linux-gnu'
run: tar cJf cloud-hypervisor-${{ github.event.ref }}.tar.xz ../cloud-hypervisor-${{ github.event.ref }}
working-directory: ../
run: tar cJf cloud-hypervisor-${{ github.event.ref }}.tar.xz cloud-hypervisor-${{ github.event.ref }}
- name: Upload cloud-hypervisor vendored source archive
if: |
github.event_name == 'create' && github.event.ref_type == 'tag' &&
matrix.platform.target == 'x86_64-unknown-linux-gnu'
id: upload-release-cloud-hypervisor-vendored-sources
uses: actions/upload-artifact@v4
with:
path: cloud-hypervisor-${{ github.event.ref }}.tar.xz
name: cloud-hypervisor-${{ github.event.ref }}.tar.xz
- name: Create GitHub Release
if: github.event_name == 'create' && github.event.ref_type == 'tag'
uses: softprops/action-gh-release@v2
id: upload-release-cloud-hypervisor-vendored-sources
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
draft: true
files: |
./${{ matrix.platform.name_ch }}
./${{ matrix.platform.name_ch_remote }}
./cloud-hypervisor-${{ github.event.ref }}.tar.xz
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ../cloud-hypervisor-${{ github.event.ref }}.tar.xz
asset_name: cloud-hypervisor-${{ github.event.ref }}.tar.xz
asset_content_type: application/x-xz

View File

@@ -1,12 +0,0 @@
name: REUSE Compliance Check
on: [push, pull_request]
jobs:
reuse:
name: REUSE Compliance Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: REUSE Compliance Check
uses: fsfe/reuse-action@v5

View File

@@ -1,20 +0,0 @@
name: Shell scripts check
on:
pull_request:
merge_group:
push:
branches:
- main
jobs:
sh-checker:
name: Check shell scripts
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v5
- name: Run the shell script checkers
uses: luizm/action-sh-checker@master
env:
SHFMT_OPTS: -i 4 -d
SHELLCHECK_OPTS: -x --source-path scripts

View File

@@ -1,21 +0,0 @@
name: Cargo.toml Formatting (taplo)
on:
pull_request:
paths:
- '**/Cargo.toml'
jobs:
cargo_toml_format:
name: Cargo.toml Formatting
runs-on: ubuntu-latest
steps:
- name: Code checkout
uses: actions/checkout@v5
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Install build dependencies
run: sudo apt-get update && sudo apt-get -yqq install build-essential libssl-dev
- name: Install taplo
run: cargo install taplo-cli --locked
- name: Check formatting
run: taplo fmt --check

2
.gitignore vendored
View File

@@ -6,5 +6,3 @@
**/rusty-tags.vi
/rpm/SOURCES
/.vscode
/vendor
__pycache__

View File

@@ -1,7 +1,6 @@
[general]
extra-path=scripts/gitlint/rules
extra-path=scripts/gitlint/rules.py
regex-style-search=true
ignore=body-max-line-length
[ignore-by-author-name]
regex=dependabot
@@ -11,3 +10,6 @@ ignore=all
[title-max-length]
line-length=72
# default 80
[body-max-line-length]
line-length=72

View File

@@ -1,27 +0,0 @@
verbose = "info"
exclude = [
# Availability of links below should be manually verified.
# Page for intel TDX support, returns 403 while querying.
'^https://www.intel.com/content/www/us/en/developer/tools/trust-domain-extensions/overview.html',
# Page for TPM, returns 403 while querying.
'^https://trustedcomputinggroup.org/wp-content/uploads/PC-Client-Specific-Platform-TPM-Profile-for-TPM-2p0-v1p05p_r14_pub.pdf',
# GitHub user smibarber referenced in `CREDITS.md` no longer exist
'^https://github.com/smibarber',
# OSDev has added bot protection and accesses my result in 403 Forbidden.
'^https://wiki.osdev.org',
# Exclude all pages with $ in the URL since $XXX is a variable
"\\$.*",
# Exclude local files
"file://.*",
]
# Exclude loopback addresses
exclude_loopback = true
max_retries = 3
retry_wait_time = 5

View File

@@ -1,12 +0,0 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: cloud-hypervisor
Upstream-Contact: <>
Source: https://www.cloudhypervisor.org
Files: docs/*.md *.md
Copyright: 2024
License: CC-BY-4.0
Files: scripts/* test_data/* *.toml .git* fuzz/Cargo.lock fuzz/.gitignore resources/linux-config-* vmm/src/api/openapi/cloud-hypervisor.yaml CODEOWNERS Cargo.lock
Copyright: 2024
License: Apache-2.0

View File

@@ -1,4 +1 @@
edition = "2024"
group_imports="StdExternalCrate"
imports_granularity="Module"
edition = "2021"

View File

@@ -1,5 +0,0 @@
include = ["**/Cargo.toml"]
[formatting]
reorder_arrays = true
reorder_keys = true

View File

@@ -7,15 +7,12 @@ extend-exclude = [
]
[default.extend-words]
ba = "ba"
CLASSE = "CLASSE"
conectix = "conectix"
Dake = "Dake"
EXTINT = "EXTINT"
INOUT = "INOUT"
SME = "SME" # Secure Memory Encryption
THR = "THR" # Transmitter Holding Register
TRANSLATER = "TRANSLATER"
ba = "ba"
conectix = "conectix"
liness = "liness"
outout = "outout"

View File

@@ -13,7 +13,7 @@ License](https://opensource.org/licenses/Apache-2.0).
## Coding Style
We follow the [Rust Style](https://github.com/rust-lang/rust/tree/HEAD/src/doc/style-guide/src)
We follow the [Rust Style](https://github.com/rust-dev-tools/fmt-rfcs/blob/master/guide/guide.md)
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://web.archive.org/web/20230406041855/https://01.org/community/signed-process)
In order to get a clear contribution chain of trust we use the [signed-off-by language](https://01.org/community/signed-process)
used by the Linux kernel project.
## 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.
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/)
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/)
against the main branch of the Cloud Hypervisor repository.
1. To update your pull request amend existing commits whenever applicable and
4. To update your pull request amend existing commits whenever applicable and
then push the new changes to your pull request branch.
1. Once the pull request is approved it can be integrated.
5. Once the pull request is approved it can be integrated.
## Issue tracking
@@ -112,15 +112,5 @@ Fixes #88
Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
```
Then, after the corresponding PR is merged, GitHub will automatically close that issue when parsing the
Then, after the corresponding PR is merged, Github will automatically close that issue when parsing the
[commit message](https://help.github.com/articles/closing-issues-via-commit-messages/).
## AI Generated Code
Our policy is to decline any contributions known to contain contents
generated or derived from using Large Language Models (LLMs). This
includes ChatGPT, Gemini, Claude, Copilot and similar tools.
The goal is to avoid ambiguity in license compliance and optimize the
use of limited project resources, especially for code review and
maintenance. This policy can be revisited as LLMs evolve and mature.

1855
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,155 +1,106 @@
[package]
authors = ["The Cloud Hypervisor Authors"]
build = "build.rs"
default-run = "cloud-hypervisor"
description = "Open source Virtual Machine Monitor (VMM) that runs on top of KVM & MSHV"
edition = "2024"
homepage = "https://github.com/cloud-hypervisor/cloud-hypervisor"
license = "Apache-2.0 AND BSD-3-Clause"
name = "cloud-hypervisor"
version = "48.0.0"
version = "37.1.0"
authors = ["The Cloud Hypervisor Authors"]
edition = "2021"
default-run = "cloud-hypervisor"
build = "build.rs"
license = "LICENSE-APACHE & LICENSE-BSD-3-Clause"
description = "Open source Virtual Machine Monitor (VMM) that runs on top of KVM"
homepage = "https://github.com/cloud-hypervisor/cloud-hypervisor"
# Minimum buildable version:
# Keep in sync with version in .github/workflows/build.yaml
# Policy on MSRV (see #4318):
# Can only be bumped if satisfying any of the following:
# Can only be bumped by:
# a.) A dependency requires it,
# b.) If we want to use a new feature and that MSRV is at least 6 months old,
# c.) There is a security issue that is addressed by the toolchain update.
rust-version = "1.88.0"
rust-version = "1.70"
[profile.release]
codegen-units = 1
lto = true
codegen-units = 1
opt-level = "s"
strip = true
[profile.profiling]
debug = true
inherits = "release"
strip = false
debug = true
[dependencies]
anyhow = { workspace = true }
anyhow = "1.0.75"
api_client = { path = "api_client" }
clap = { workspace = true, features = ["string"] }
dhat = { workspace = true, optional = true }
env_logger = { workspace = true }
epoll = { workspace = true }
clap = { version = "4.4.7", features = ["string"] }
dhat = { version = "0.3.2", optional = true }
epoll = "4.3.3"
event_monitor = { path = "event_monitor" }
hypervisor = { path = "hypervisor" }
libc = { workspace = true }
log = { workspace = true, features = ["std"] }
libc = "0.2.147"
log = { version = "0.4.20", features = ["std"] }
option_parser = { path = "option_parser" }
seccompiler = { workspace = true }
serde_json = { workspace = true }
signal-hook = { workspace = true }
thiserror = { workspace = true }
tpm = { path = "tpm" }
seccompiler = "0.4.0"
serde_json = "1.0.107"
signal-hook = "0.3.17"
thiserror = "1.0.40"
tpm = { path = "tpm"}
tracer = { path = "tracer" }
vm-memory = { workspace = true }
vmm = { path = "vmm" }
vmm-sys-util = { workspace = true }
zbus = { version = "5.7.1", optional = true }
vmm-sys-util = "0.12.1"
vm-memory = "0.14.0"
zbus = { version = "3.11.1", optional = true }
# List of patched crates
[patch.crates-io]
kvm-bindings = { git = "https://github.com/cloud-hypervisor/kvm-bindings", branch = "ch-live-upgrade-stable-37.x" }
versionize_derive = { git = "https://github.com/cloud-hypervisor/versionize_derive", branch = "ch-0.1.6" }
[dev-dependencies]
dirs = { workspace = true }
dirs = "5.0.0"
net_util = { path = "net_util" }
serde_json = { workspace = true }
once_cell = "1.18.0"
serde_json = "1.0.107"
test_infra = { path = "test_infra" }
wait-timeout = { workspace = true }
wait-timeout = "0.2.0"
# Please adjust `vmm::feature_list()` accordingly when changing the
# feature list below
[features]
dbus_api = ["vmm/dbus_api", "zbus"]
default = ["io_uring", "kvm"]
dhat-heap = ["dhat", "vmm/dhat-heap"] # For heap profiling
fw_cfg = ["vmm/fw_cfg"]
default = ["kvm", "io_uring"]
dbus_api = ["zbus", "vmm/dbus_api"]
dhat-heap = ["dhat"] # For heap profiling
guest_debug = ["vmm/guest_debug"]
igvm = ["mshv", "vmm/igvm"]
igvm = ["vmm/igvm", "mshv"]
io_uring = ["vmm/io_uring"]
ivshmem = ["vmm/ivshmem"]
kvm = ["vmm/kvm"]
mshv = ["vmm/mshv"]
pvmemcontrol = ["vmm/pvmemcontrol"]
sev_snp = ["igvm", "mshv", "vmm/sev_snp"]
sev_snp = ["igvm", "vmm/sev_snp", "mshv"]
tdx = ["vmm/tdx"]
tracing = ["tracer/tracing", "vmm/tracing"]
tracing = ["vmm/tracing", "tracer/tracing"]
[workspace]
members = [
"api_client",
"arch",
"block",
"devices",
"event_monitor",
"hypervisor",
"net_gen",
"net_util",
"option_parser",
"pci",
"performance-metrics",
"rate_limiter",
"serial_buffer",
"test_infra",
"tracer",
"vhost_user_block",
"vhost_user_net",
"virtio-devices",
"vm-allocator",
"vm-device",
"vm-migration",
"vm-virtio",
"vmm",
"api_client",
"arch",
"block",
"devices",
"event_monitor",
"hypervisor",
"net_gen",
"net_util",
"option_parser",
"pci",
"performance-metrics",
"rate_limiter",
"serial_buffer",
"test_infra",
"tracer",
"vhost_user_block",
"vhost_user_net",
"virtio-devices",
"vmm",
"vm-allocator",
"vm-device",
"vm-migration",
"vm-virtio"
]
package.edition = "2024"
[workspace.dependencies]
# rust-vmm crates
acpi_tables = { git = "https://github.com/rust-vmm/acpi_tables", branch = "main" }
kvm-bindings = "0.12.0"
kvm-ioctls = "0.22.0"
# TODO: update to 0.13.1+
linux-loader = { git = "https://github.com/rust-vmm/linux-loader", branch = "main" }
mshv-bindings = "0.6.0"
mshv-ioctls = "0.6.0"
seccompiler = "0.5.0"
vfio-bindings = { version = "0.6.0", default-features = false }
vfio-ioctls = { version = "0.5.1", default-features = false }
vfio_user = { version = "0.1.1", default-features = false }
vhost = { version = "0.14.0", default-features = false }
vhost-user-backend = { version = "0.20.0", default-features = false }
virtio-bindings = "0.2.6"
virtio-queue = "0.16.0"
vm-fdt = { git = "https://github.com/rust-vmm/vm-fdt", branch = "main" }
vm-memory = "0.16.1"
vmm-sys-util = "0.14.0"
# igvm crates
# TODO: bump to 0.3.5 release
igvm = { git = "https://github.com/microsoft/igvm", branch = "main" }
igvm_defs = { git = "https://github.com/microsoft/igvm", branch = "main" }
# serde crates
serde = "1.0.208"
serde_json = "1.0.143"
serde_with = { version = "3.14.0", default-features = false }
# other crates
anyhow = "1.0.98"
bitflags = "2.9.4"
byteorder = "1.5.0"
cfg-if = "1.0.0"
clap = "4.5.47"
dhat = "0.3.3"
dirs = "6.0.0"
env_logger = "0.11.8"
epoll = "4.3.3"
flume = "0.11.1"
libc = "0.2.167"
log = "0.4.22"
signal-hook = "0.3.18"
thiserror = "2.0.12"
uuid = { version = "1.18.1" }
wait-timeout = "0.2.1"
zerocopy = { version = "0.8.26", default-features = false }

View File

@@ -59,13 +59,9 @@ based on the [Rust VMM](https://github.com/rust-vmm) crates.
### Architectures
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).
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)).
### Guest OS
@@ -112,7 +108,7 @@ do not wish to use the pre-built binaries.
## Booting Linux
Cloud Hypervisor supports direct kernel boot (the x86-64 kernel requires the kernel
built with PVH support or a bzImage) or booting via a firmware (either [Rust Hypervisor
built with PVH support) or booting via a firmware (either [Rust Hypervisor
Firmware](https://github.com/cloud-hypervisor/rust-hypervisor-firmware) or an
edk2 UEFI firmware called `CLOUDHV` / `CLOUDHV_EFI`.)
@@ -153,7 +149,7 @@ interface will be enabled as per `network-config` details.
$ sudo setcap cap_net_admin+ep ./cloud-hypervisor
$ ./create-cloud-init.sh
$ ./cloud-hypervisor \
--firmware ./hypervisor-fw \
--kernel ./hypervisor-fw \
--disk path=focal-server-cloudimg-amd64.raw path=/tmp/ubuntu-cloudinit.img \
--cpus boot=4 \
--memory size=1024M \
@@ -175,31 +171,24 @@ $ ./cloud-hypervisor \
--console off
```
## Booting: `--firmware` vs `--kernel`
The following scenarios are supported by Cloud Hypervisor to bootstrap a VM, i.e.,
to load a payload/bootitem(s):
- Provide firmware
- Provide kernel \[+ cmdline\]\ [+ initrd\]
Please note that our Cloud Hypervisor firmware (`hypervisor-fw`) has a Xen PVH
boot entry, therefore it can also be booted via the `--kernel` parameter, as
seen in some examples.
### Custom Kernel and Disk Image
#### Building your Kernel
Cloud Hypervisor also supports direct kernel boot. For x86-64, a `vmlinux` ELF kernel (compiled with PVH support) or a regular bzImage are supported. In order to support development there is a custom branch; however provided the required options are enabled any recent kernel will suffice.
Cloud Hypervisor also supports direct kernel boot. For x86-64, a `vmlinux` ELF kernel (compiled with PVH support) is needed. In order to support development there is a custom branch; however provided the required options are enabled any recent kernel will suffice.
To build the kernel:
```shell
# Clone the Cloud Hypervisor Linux branch
$ git clone --depth 1 https://github.com/cloud-hypervisor/linux.git -b ch-6.12.8 linux-cloud-hypervisor
$ git clone --depth 1 https://github.com/cloud-hypervisor/linux.git -b ch-6.2 linux-cloud-hypervisor
$ pushd linux-cloud-hypervisor
$ make ch_defconfig
# Use the x86-64 cloud-hypervisor kernel config to build your kernel for x86-64
$ wget https://raw.githubusercontent.com/cloud-hypervisor/cloud-hypervisor/main/resources/linux-config-x86_64
# Use the AArch64 cloud-hypervisor kernel config to build your kernel for AArch64
$ wget https://raw.githubusercontent.com/cloud-hypervisor/cloud-hypervisor/main/resources/linux-config-aarch64
$ cp linux-config-x86_64 .config # x86-64
$ cp linux-config-aarch64 .config # AArch64
# Do native build of the x86-64 kernel
$ KCFLAGS="-Wa,-mx86-used-note=no" make bzImage -j `nproc`
# Do native build of the AArch64 kernel
@@ -313,9 +302,8 @@ Further details can be found in the [release documentation](docs/releases.md).
As of 2023-01-03, the following cloud images are supported:
- [Ubuntu Focal](https://cloud-images.ubuntu.com/focal/current/) (focal-server-cloudimg-{amd64,arm64}.img)
- [Ubuntu Jammy](https://cloud-images.ubuntu.com/jammy/current/) (jammy-server-cloudimg-{amd64,arm64}.img)
- [Ubuntu Noble](https://cloud-images.ubuntu.com/noble/current/) (noble-server-cloudimg-{amd64,arm64}.img)
- [Fedora 36](https://archives.fedoraproject.org/pub/archive/fedora/linux/releases/36/Cloud/) ([Fedora-Cloud-Base-36-1.5.x86_64.raw.xz](https://archives.fedoraproject.org/pub/archive/fedora/linux/releases/36/Cloud/x86_64/images/) / [Fedora-Cloud-Base-36-1.5.aarch64.raw.xz](https://archives.fedoraproject.org/pub/archive/fedora/linux/releases/36/Cloud/aarch64/images/))
- [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/))
Direct kernel boot to userspace should work with a rootfs from most
distributions although you may need to enable exotic filesystem types in the

View File

@@ -1,9 +1,8 @@
[package]
authors = ["The Cloud Hypervisor Authors"]
edition.workspace = true
name = "api_client"
version = "0.1.0"
authors = ["The Cloud Hypervisor Authors"]
edition = "2021"
[dependencies]
thiserror = { workspace = true }
vmm-sys-util = { workspace = true }
vmm-sys-util = "0.12.1"

View File

@@ -3,32 +3,39 @@
// SPDX-License-Identifier: Apache-2.0
//
use std::fmt;
use std::io::{Read, Write};
use std::os::unix::io::RawFd;
use thiserror::Error;
use vmm_sys_util::sock_ctrl_msg::ScmSocket;
#[derive(Debug, Error)]
#[derive(Debug)]
pub enum Error {
#[error("Error writing to or reading from HTTP socket")]
Socket(#[source] std::io::Error),
#[error("Error sending file descriptors")]
SocketSendFds(#[source] vmm_sys_util::errno::Error),
#[error("Error parsing HTTP status code")]
StatusCodeParsing(#[source] std::num::ParseIntError),
#[error("HTTP output is missing protocol statement")]
Socket(std::io::Error),
SocketSendFds(vmm_sys_util::errno::Error),
StatusCodeParsing(std::num::ParseIntError),
MissingProtocol,
#[error("Error parsing HTTP Content-Length field")]
ContentLengthParsing(#[source] std::num::ParseIntError),
#[error("Server responded with error {0:?}: {1:?}")]
ServerResponse(
StatusCode,
// TODO: Move `api` module from `vmm` to dedicated crate and use a common type definition
Option<
String, /* Untyped: Currently Vec<String> of error messages from top to root cause */
>,
),
ContentLengthParsing(std::num::ParseIntError),
ServerResponse(StatusCode, Option<String>),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use Error::*;
match self {
Socket(e) => write!(f, "Error writing to or reading from HTTP socket: {e}"),
SocketSendFds(e) => write!(f, "Error writing to or reading from HTTP socket: {e}"),
StatusCodeParsing(e) => write!(f, "Error parsing HTTP status code: {e}"),
MissingProtocol => write!(f, "HTTP output is missing protocol statement"),
ContentLengthParsing(e) => write!(f, "Error parsing HTTP Content-Length field: {e}"),
ServerResponse(s, o) => {
if let Some(o) = o {
write!(f, "Server responded with an error: {s:?}: {o}")
} else {
write!(f, "Server responded with an error: {s:?}")
}
}
}
}
}
#[derive(Clone, Copy, Debug)]
@@ -38,7 +45,6 @@ pub enum StatusCode {
NoContent,
BadRequest,
NotFound,
TooManyRequests,
InternalServerError,
NotImplemented,
Unknown,
@@ -52,7 +58,6 @@ impl StatusCode {
204 => StatusCode::NoContent,
400 => StatusCode::BadRequest,
404 => StatusCode::NotFound,
429 => StatusCode::TooManyRequests,
500 => StatusCode::InternalServerError,
501 => StatusCode::NotImplemented,
_ => StatusCode::Unknown,
@@ -118,11 +123,12 @@ fn parse_http_response(socket: &mut dyn Read) -> Result<Option<String>, Error> {
}
}
if let Some(body_offset) = body_offset
&& let Some(content_length) = content_length
&& res.len() >= content_length + body_offset
{
break;
if let Some(body_offset) = body_offset {
if let Some(content_length) = content_length {
if res.len() >= content_length + body_offset {
break;
}
}
}
}
let body_string = content_length.and(body_offset.map(|o| String::from(&res[o..])));

View File

@@ -1,29 +1,30 @@
[package]
authors = ["The Chromium OS Authors"]
edition.workspace = true
name = "arch"
version = "0.1.0"
authors = ["The Chromium OS Authors"]
edition = "2021"
[features]
default = []
fw_cfg = []
kvm = ["hypervisor/kvm"]
sev_snp = []
tdx = []
[dependencies]
anyhow = { workspace = true }
byteorder = { workspace = true }
anyhow = "1.0.75"
byteorder = "1.4.3"
hypervisor = { path = "../hypervisor" }
libc = { workspace = true }
linux-loader = { workspace = true, features = ["bzimage", "elf", "pe"] }
log = { workspace = true }
serde = { workspace = true, features = ["derive", "rc"] }
thiserror = { workspace = true }
uuid = { workspace = true }
vm-memory = { workspace = true, features = ["backend-bitmap", "backend-mmap"] }
vmm-sys-util = { workspace = true, features = ["with-serde"] }
libc = "0.2.147"
linux-loader = { version = "0.11.0", features = ["elf", "bzimage", "pe"] }
log = "0.4.20"
serde = { version = "1.0.168", features = ["rc", "derive"] }
thiserror = "1.0.40"
uuid = "1.3.4"
versionize = "0.2.0"
versionize_derive = "0.1.6"
vm-memory = { version = "0.14.0", features = ["backend-mmap", "backend-bitmap"] }
vm-migration = { path = "../vm-migration" }
vmm-sys-util = { version = "0.12.1", features = ["with-serde"] }
[target.'cfg(any(target_arch = "aarch64", target_arch = "riscv64"))'.dependencies]
fdt_parser = { version = "0.1.5", package = "fdt" }
vm-fdt = { workspace = true }
[target.'cfg(target_arch = "aarch64")'.dependencies]
fdt_parser = { version = "0.1.4", package = "fdt" }
vm-fdt = { git = "https://github.com/rust-vmm/vm-fdt", branch = "main" }

View File

@@ -6,30 +6,29 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the THIRD-PARTY file.
use crate::{NumaNodes, PciSpaceInfo};
use byteorder::{BigEndian, ByteOrder};
use hypervisor::arch::aarch64::gic::Vgic;
use std::cmp;
use std::collections::HashMap;
use std::ffi::CStr;
use std::fmt::Debug;
use std::path::Path;
use std::result;
use std::str;
use std::sync::{Arc, Mutex};
use std::{cmp, fs, result, str};
use byteorder::{BigEndian, ByteOrder};
use hypervisor::arch::aarch64::gic::Vgic;
use hypervisor::arch::aarch64::regs::{
AARCH64_ARCH_TIMER_HYP_IRQ, AARCH64_ARCH_TIMER_PHYS_NONSECURE_IRQ,
AARCH64_ARCH_TIMER_PHYS_SECURE_IRQ, AARCH64_ARCH_TIMER_VIRT_IRQ, AARCH64_PMU_IRQ,
use super::super::DeviceType;
use super::super::GuestMemoryMmap;
use super::super::InitramfsConfig;
use super::layout::{
IRQ_BASE, MEM_32BIT_DEVICES_SIZE, MEM_32BIT_DEVICES_START, MEM_PCI_IO_SIZE, MEM_PCI_IO_START,
PCI_HIGH_BASE, PCI_MMIO_CONFIG_SIZE_PER_SEGMENT,
};
use thiserror::Error;
use std::fs;
use std::path::Path;
use vm_fdt::{FdtWriter, FdtWriterResult};
use vm_memory::{Address, Bytes, GuestMemory, GuestMemoryError, GuestMemoryRegion};
use super::super::{DeviceType, GuestMemoryMmap, InitramfsConfig};
use super::layout::{
GIC_V2M_COMPATIBLE, IRQ_BASE, MEM_32BIT_DEVICES_SIZE, MEM_32BIT_DEVICES_START, MEM_PCI_IO_SIZE,
MEM_PCI_IO_START, PCI_HIGH_BASE, PCI_MMIO_CONFIG_SIZE_PER_SEGMENT, SPI_BASE, SPI_NUM,
};
use crate::{NumaNodes, PciSpaceInfo};
// This is a value for uniquely identifying the FDT node declaring the interrupt controller.
const GIC_PHANDLE: u32 = 1;
// This is a value for uniquely identifying the FDT node declaring the MSI controller.
@@ -63,6 +62,9 @@ const GIC_FDT_IRQ_TYPE_PPI: u32 = 1;
const IRQ_TYPE_EDGE_RISING: u32 = 1;
const IRQ_TYPE_LEVEL_HI: u32 = 4;
// PMU PPI interrupt number
pub const AARCH64_PMU_IRQ: u32 = 7;
// Keys and Buttons
// System Power Down
const KEY_POWER: u32 = 116;
@@ -78,11 +80,10 @@ pub trait DeviceInfoForFdt {
}
/// Errors thrown while configuring the Flattened Device Tree for aarch64.
#[derive(Debug, Error)]
#[derive(Debug)]
pub enum Error {
/// Failure in writing FDT in memory.
#[error("Failure in writing FDT in memory")]
WriteFdtToMemory(#[source] GuestMemoryError),
WriteFdtToMemory(GuestMemoryError),
}
type Result<T> = result::Result<T, Error>;
@@ -110,8 +111,11 @@ pub fn get_cache_size(cache_level: CacheLevel) -> u32 {
let file_path = Path::new(&file_directory);
if !file_path.exists() {
warn!("File: {} does not exist.", file_directory);
0
} else {
info!("File: {} exist.", file_directory);
let src = fs::read_to_string(file_directory).expect("File not exists or file corrupted.");
// The content of the file is as simple as a size, like: "32K"
let src = src.trim();
@@ -141,8 +145,11 @@ pub fn get_cache_coherency_line_size(cache_level: CacheLevel) -> u32 {
let file_path = Path::new(&file_directory);
if !file_path.exists() {
warn!("File: {} does not exist.", file_directory);
0
} else {
info!("File: {} exist.", file_directory);
let src = fs::read_to_string(file_directory).expect("File not exists or file corrupted.");
src.trim().parse::<u32>().unwrap()
}
@@ -161,8 +168,11 @@ pub fn get_cache_number_of_sets(cache_level: CacheLevel) -> u32 {
let file_path = Path::new(&file_directory);
if !file_path.exists() {
warn!("File: {} does not exist.", file_directory);
0
} else {
info!("File: {} exist.", file_directory);
let src = fs::read_to_string(file_directory).expect("File not exists or file corrupted.");
src.trim().parse::<u32>().unwrap()
}
@@ -186,8 +196,11 @@ pub fn get_cache_shared(cache_level: CacheLevel) -> bool {
let file_path = Path::new(&file_directory);
if !file_path.exists() {
warn!("File: {} does not exist.", file_directory);
result = false;
} else {
info!("File: {} exist.", file_directory);
let src = fs::read_to_string(file_directory).expect("File not exists or file corrupted.");
let src = src.trim();
if src.is_empty() {
@@ -206,7 +219,7 @@ pub fn create_fdt<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHash
guest_mem: &GuestMemoryMmap,
cmdline: &str,
vcpu_mpidr: Vec<u64>,
vcpu_topology: Option<(u16, u16, u16, u16)>,
vcpu_topology: Option<(u8, u8, u8)>,
device_info: &HashMap<(DeviceType, String), T, S>,
gic_device: &Arc<Mutex<dyn Vgic>>,
initrd: &Option<InitramfsConfig>,
@@ -219,8 +232,8 @@ pub fn create_fdt<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHash
let mut fdt = FdtWriter::new().unwrap();
// For an explanation why these nodes were introduced in the blob take a look at
// the "Device Node Requirements" chapter of the Devicetree Specification.
// https://www.devicetree.org/specifications/
// https://github.com/torvalds/linux/blob/master/Documentation/devicetree/booting-without-of.txt#L845
// Look for "Required nodes and properties".
// Header or the root node as per above mentioned documentation.
let root_node = fdt.begin_node("")?;
@@ -268,7 +281,7 @@ pub fn write_fdt_to_memory(fdt_final: Vec<u8>, guest_mem: &GuestMemoryMmap) -> R
fn create_cpu_nodes(
fdt: &mut FdtWriter,
vcpu_mpidr: &[u64],
vcpu_topology: Option<(u16, u16, u16, u16)>,
vcpu_topology: Option<(u8, u8, u8)>,
numa_nodes: &NumaNodes,
) -> FdtWriterResult<()> {
// See https://github.com/torvalds/linux/blob/master/Documentation/devicetree/bindings/arm/cpus.yaml.
@@ -277,11 +290,8 @@ fn create_cpu_nodes(
fdt.property_u32("#size-cells", 0x0)?;
let num_cpus = vcpu_mpidr.len();
let (threads_per_core, cores_per_die, dies_per_package, packages) =
vcpu_topology.unwrap_or((1, 1, 1, 1));
let cores_per_package = cores_per_die * dies_per_package;
let max_cpus: u32 =
threads_per_core as u32 * cores_per_die as u32 * dies_per_package as u32 * packages as u32;
let (threads_per_core, cores_per_package, packages) = vcpu_topology.unwrap_or((1, 1, 1));
let max_cpus: u32 = (threads_per_core * cores_per_package * packages).into();
// Add cache info.
// L1 Data Cache Info.
@@ -313,6 +323,7 @@ fn create_cpu_nodes(
if !cache_exist {
warn!("cache sysfs system does not exist.");
} else {
info!("cache sysfs system exists.");
// L1 Data Cache Info.
l1_d_cache_size = get_cache_size(CacheLevel::L1D);
l1_d_cache_line_size = get_cache_coherency_line_size(CacheLevel::L1D);
@@ -360,7 +371,7 @@ fn create_cpu_nodes(
if numa_nodes.len() > 1 {
for numa_node_idx in 0..numa_nodes.len() {
let numa_node = numa_nodes.get(&(numa_node_idx as u32));
if numa_node.unwrap().cpus.contains(&(cpu_id as u32)) {
if numa_node.unwrap().cpus.contains(&(cpu_id as u8)) {
fdt.property_u32("numa-node-id", numa_node_idx as u32)?;
}
}
@@ -413,6 +424,9 @@ fn create_cpu_nodes(
fdt.end_node(l2_cache_node)?;
}
if l2_cache_size != 0 && l2_cache_shared {
warn!("L2 cache shared with other cpus");
}
}
fdt.end_node(cpu_node)?;
@@ -449,8 +463,7 @@ fn create_cpu_nodes(
}
if let Some(topology) = vcpu_topology {
let (threads_per_core, cores_per_die, dies_per_package, packages) = topology;
let cores_per_package = cores_per_die * dies_per_package;
let (threads_per_core, cores_per_package, packages) = topology;
let cpu_map_node = fdt.begin_node("cpu-map")?;
// Create device tree nodes with regard of above mapping.
@@ -510,7 +523,7 @@ fn create_memory_node(
let memory_region_size: u64 = memory_region.size() as u64;
mem_reg_prop.push(memory_region_start_addr);
mem_reg_prop.push(memory_region_size);
// Set the node address the first non-zero region address
// Set the node address the first non-zero regison address
if node_memory_addr == 0 {
node_memory_addr = memory_region_start_addr;
}
@@ -556,7 +569,7 @@ fn create_memory_node(
if ram_regions.len() > 2 {
panic!(
"There should be up to two non-continuous regions, divided by the
"There should be up to two non-continuous regions, devidided by the
gap at the end of 32bit address space."
);
}
@@ -574,14 +587,15 @@ fn create_memory_node(
&& (first_region_end <= &mem_32bit_reserved_start))
{
panic!(
"Unexpected first memory region layout: (start: 0x{first_region_start:08x}, end: 0x{first_region_end:08x}).
ram_start: 0x{ram_start:08x}, mem_32bit_reserved_start: 0x{mem_32bit_reserved_start:08x}"
"Unexpected first memory region layout: (start: 0x{:08x}, end: 0x{:08x}).
ram_start: 0x{:08x}, mem_32bit_reserved_start: 0x{:08x}",
first_region_start, first_region_end, ram_start, mem_32bit_reserved_start
);
}
let mem_size = first_region_end - ram_start;
let mem_reg_prop = [ram_start, mem_size];
let memory_node_name = format!("memory@{ram_start:x}");
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_prop)?;
@@ -594,13 +608,14 @@ fn create_memory_node(
if second_region_start != &ram_64bit_start {
panic!(
"Unexpected second memory region layout: start: 0x{second_region_start:08x}, ram_64bit_start: 0x{ram_64bit_start:08x}"
"Unexpected second memory region layout: start: 0x{:08x}, ram_64bit_start: 0x{:08x}",
second_region_start, ram_64bit_start
);
}
let mem_size = second_region_end - ram_64bit_start;
let mem_reg_prop = [ram_64bit_start, mem_size];
let memory_node_name = format!("memory@{ram_64bit_start:x}");
let memory_node_name = format!("memory@{:x}", ram_64bit_start);
let memory_node = fdt.begin_node(&memory_node_name)?;
fdt.property_string("device_type", "memory")?;
fdt.property_array_u64("reg", &mem_reg_prop)?;
@@ -657,19 +672,11 @@ fn create_gic_node(fdt: &mut FdtWriter, gic_device: &Arc<Mutex<dyn Vgic>>) -> Fd
if gic_device.lock().unwrap().msi_compatible() {
let msic_node = fdt.begin_node("msic")?;
let msi_compatibility = gic_device.lock().unwrap().msi_compatibility().to_string();
fdt.property_string("compatible", msi_compatibility.as_str())?;
fdt.property_string("compatible", gic_device.lock().unwrap().msi_compatibility())?;
fdt.property_null("msi-controller")?;
fdt.property_u32("phandle", MSI_PHANDLE)?;
let msi_reg_prop = gic_device.lock().unwrap().msi_properties();
fdt.property_array_u64("reg", &msi_reg_prop)?;
if msi_compatibility == GIC_V2M_COMPATIBLE {
fdt.property_u32("arm,msi-base-spi", SPI_BASE)?;
fdt.property_u32("arm,msi-num-spis", SPI_NUM)?;
}
fdt.end_node(msic_node)?;
}
@@ -696,14 +703,9 @@ fn create_clock_node(fdt: &mut FdtWriter) -> FdtWriterResult<()> {
fn create_timer_node(fdt: &mut FdtWriter) -> FdtWriterResult<()> {
// See
// https://github.com/torvalds/linux/blob/master/Documentation/devicetree/bindings/timer/arm%2Carch_timer.yaml
// https://github.com/torvalds/linux/blob/master/Documentation/devicetree/bindings/interrupt-controller/arch_timer.txt
// These are fixed interrupt numbers for the timer device.
let irqs = [
AARCH64_ARCH_TIMER_PHYS_SECURE_IRQ,
AARCH64_ARCH_TIMER_PHYS_NONSECURE_IRQ,
AARCH64_ARCH_TIMER_VIRT_IRQ,
AARCH64_ARCH_TIMER_HYP_IRQ,
];
let irqs = [13, 14, 11, 10];
let compatible = "arm,armv8-timer";
let mut timer_reg_cells: Vec<u32> = Vec::new();
@@ -838,21 +840,6 @@ fn create_gpio_node<T: DeviceInfoForFdt + Clone + Debug>(
Ok(())
}
// https://www.kernel.org/doc/Documentation/devicetree/bindings/arm/fw-cfg.txt
#[cfg(feature = "fw_cfg")]
fn create_fw_cfg_node<T: DeviceInfoForFdt + Clone + Debug>(
fdt: &mut FdtWriter,
dev_info: &T,
) -> FdtWriterResult<()> {
// FwCfg node
let fw_cfg_node = fdt.begin_node(&format!("fw-cfg@{:x}", dev_info.addr()))?;
fdt.property("compatible", b"qemu,fw-cfg-mmio\0")?;
fdt.property_array_u64("reg", &[dev_info.addr(), dev_info.length()])?;
fdt.end_node(fw_cfg_node)?;
Ok(())
}
fn create_devices_node<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHasher>(
fdt: &mut FdtWriter,
dev_info: &HashMap<(DeviceType, String), T, S>,
@@ -868,8 +855,6 @@ fn create_devices_node<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::Buil
DeviceType::Virtio(_) => {
ordered_virtio_device.push(info);
}
#[cfg(feature = "fw_cfg")]
DeviceType::FwCfg => create_fw_cfg_node(fdt, info)?,
}
}
@@ -909,7 +894,7 @@ fn create_pci_nodes(
for pci_device_info_elem in pci_device_info.iter() {
// EDK2 requires the PCIe high space above 4G address.
// The actual space in CLH follows the RAM. If the RAM space is small, the PCIe high space
// could fall below 4G.
// could fall bellow 4G.
// Here we cut off PCI device space below 8G in FDT to workaround the EDK2 check.
// But the address written in ACPI is not impacted.
let (pci_device_base_64bit, pci_device_size_64bit) =
@@ -999,39 +984,39 @@ fn create_pci_nodes(
fdt.property_array_u32("msi-map", &msi_map)?;
fdt.property_u32("msi-parent", MSI_PHANDLE)?;
if pci_device_info_elem.pci_segment_id == 0
&& let Some(virtio_iommu_bdf) = virtio_iommu_bdf
{
// See kernel document Documentation/devicetree/bindings/pci/pci-iommu.txt
// for 'iommu-map' attribute setting.
let iommu_map = [
0_u32,
VIRTIO_IOMMU_PHANDLE,
0_u32,
virtio_iommu_bdf,
virtio_iommu_bdf + 1,
VIRTIO_IOMMU_PHANDLE,
virtio_iommu_bdf + 1,
0xffff - virtio_iommu_bdf,
];
fdt.property_array_u32("iommu-map", &iommu_map)?;
if pci_device_info_elem.pci_segment_id == 0 {
if let Some(virtio_iommu_bdf) = virtio_iommu_bdf {
// See kernel document Documentation/devicetree/bindings/pci/pci-iommu.txt
// for 'iommu-map' attribute setting.
let iommu_map = [
0_u32,
VIRTIO_IOMMU_PHANDLE,
0_u32,
virtio_iommu_bdf,
virtio_iommu_bdf + 1,
VIRTIO_IOMMU_PHANDLE,
virtio_iommu_bdf + 1,
0xffff - virtio_iommu_bdf,
];
fdt.property_array_u32("iommu-map", &iommu_map)?;
// See kernel document Documentation/devicetree/bindings/virtio/iommu.txt
// for virtio-iommu node settings.
let virtio_iommu_node_name = format!("virtio_iommu@{virtio_iommu_bdf:x}");
let virtio_iommu_node = fdt.begin_node(&virtio_iommu_node_name)?;
fdt.property_u32("#iommu-cells", 1)?;
fdt.property_string("compatible", "virtio,pci-iommu")?;
// See kernel document Documentation/devicetree/bindings/virtio/iommu.txt
// for virtio-iommu node settings.
let virtio_iommu_node_name = format!("virtio_iommu@{virtio_iommu_bdf:x}");
let virtio_iommu_node = fdt.begin_node(&virtio_iommu_node_name)?;
fdt.property_u32("#iommu-cells", 1)?;
fdt.property_string("compatible", "virtio,pci-iommu")?;
// 'reg' is a five-cell address encoded as
// (phys.hi phys.mid phys.lo size.hi size.lo). phys.hi should contain the
// device's BDF as 0b00000000 bbbbbbbb dddddfff 00000000. The other cells
// should be zero.
let reg = [virtio_iommu_bdf << 8, 0_u32, 0_u32, 0_u32, 0_u32];
fdt.property_array_u32("reg", &reg)?;
fdt.property_u32("phandle", VIRTIO_IOMMU_PHANDLE)?;
// 'reg' is a five-cell address encoded as
// (phys.hi phys.mid phys.lo size.hi size.lo). phys.hi should contain the
// device's BDF as 0b00000000 bbbbbbbb dddddfff 00000000. The other cells
// should be zero.
let reg = [virtio_iommu_bdf << 8, 0_u32, 0_u32, 0_u32, 0_u32];
fdt.property_array_u32("reg", &reg)?;
fdt.property_u32("phandle", VIRTIO_IOMMU_PHANDLE)?;
fdt.end_node(virtio_iommu_node)?;
fdt.end_node(virtio_iommu_node)?;
}
}
fdt.end_node(pci_node)?;
@@ -1126,7 +1111,10 @@ 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) => value_cstr.to_str().ok(),
Ok(value_cstr) => match value_cstr.to_str() {
Ok(value_str) => Some(value_str),
Err(_e) => None,
},
Err(_e) => None,
};

View File

@@ -111,9 +111,8 @@ pub const RAM_64BIT_START: GuestAddress = GuestAddress(0x1_0000_0000);
pub const CMDLINE_MAX_SIZE: usize = 2048;
/// FDT is at the beginning of RAM.
/// Maximum size of the device tree blob as specified in https://www.kernel.org/doc/Documentation/arm64/booting.txt.
pub const FDT_START: GuestAddress = RAM_START;
/// Maximum size of the device tree blob as specified in [the kernel
/// documentation](https://www.kernel.org/doc/Documentation/arm64/booting.txt).
pub const FDT_MAX_SIZE: u64 = 0x20_0000;
/// Put ACPI table above dtb
@@ -138,12 +137,3 @@ pub const IRQ_BASE: u32 = 32;
/// Number of supported interrupts
pub const IRQ_NUM: u32 = 256;
/// Base SPI interrupt number
pub const SPI_BASE: u32 = 32;
/// Total number of SPIs
pub const SPI_NUM: u32 = 64;
/// GICv2M compatible string
pub const GIC_V2M_COMPATIBLE: &str = "arm,gic-v2m-frame";

View File

@@ -6,56 +6,54 @@
pub mod fdt;
/// Layout for this aarch64 system.
pub mod layout;
/// Module for system registers definition
pub mod regs;
/// Module for loading UEFI binary.
pub mod uefi;
use std::collections::HashMap;
use std::fmt::Debug;
use std::sync::{Arc, Mutex};
use hypervisor::arch::aarch64::gic::Vgic;
use hypervisor::arch::aarch64::regs::MPIDR_EL1;
use log::{Level, log_enabled};
use thiserror::Error;
use vm_memory::{Address, GuestAddress, GuestMemory, GuestMemoryAtomic};
pub use self::fdt::DeviceInfoForFdt;
use crate::{DeviceType, GuestMemoryMmap, NumaNodes, PciSpaceInfo, RegionType};
use hypervisor::arch::aarch64::gic::Vgic;
use log::{log_enabled, Level};
use std::collections::HashMap;
use std::convert::TryInto;
use std::fmt::Debug;
use std::sync::{Arc, Mutex};
use vm_memory::{Address, GuestAddress, GuestMemory, GuestMemoryAtomic};
pub const _NSIG: i32 = 65;
/// Errors thrown while configuring aarch64 system.
#[derive(Debug, Error)]
#[derive(Debug)]
pub enum Error {
/// Failed to create a FDT.
#[error("Failed to create a FDT")]
SetupFdt,
/// Failed to write FDT to memory.
#[error("Failed to write FDT to memory")]
WriteFdtToMemory(#[source] fdt::Error),
WriteFdtToMemory(fdt::Error),
/// Failed to create a GIC.
#[error("Failed to create a GIC")]
SetupGic,
/// Failed to compute the initramfs address.
#[error("Failed to compute the initramfs address")]
InitramfsAddress,
/// Error configuring the general purpose registers
#[error("Error configuring the general purpose registers")]
RegsConfiguration(#[source] hypervisor::HypervisorCpuError),
RegsConfiguration(hypervisor::HypervisorCpuError),
/// Error configuring the MPIDR register
#[error("Error configuring the MPIDR register")]
VcpuRegMpidr(#[source] hypervisor::HypervisorCpuError),
VcpuRegMpidr(hypervisor::HypervisorCpuError),
/// Error initializing PMU for vcpu
#[error("Error initializing PMU for vcpu")]
VcpuInitPmu,
}
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.
@@ -67,7 +65,7 @@ pub struct EntryPoint {
/// Configure the specified VCPU, and return its MPIDR.
pub fn configure_vcpu(
vcpu: &Arc<dyn hypervisor::Vcpu>,
id: u32,
id: u8,
boot_setup: Option<(EntryPoint, &GuestMemoryAtomic<GuestMemoryMmap>)>,
) -> super::Result<u64> {
if let Some((kernel_entry_point, _guest_memory)) = boot_setup {
@@ -79,7 +77,9 @@ pub fn configure_vcpu(
.map_err(Error::RegsConfiguration)?;
}
let mpidr = vcpu.get_sys_reg(MPIDR_EL1).map_err(Error::VcpuRegMpidr)?;
let mpidr = vcpu
.get_sys_reg(regs::MPIDR_EL1)
.map_err(Error::VcpuRegMpidr)?;
Ok(mpidr)
}
@@ -126,7 +126,7 @@ pub fn configure_system<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::Bui
guest_mem: &GuestMemoryMmap,
cmdline: &str,
vcpu_mpidr: Vec<u64>,
vcpu_topology: Option<(u16, u16, u16, u16)>,
vcpu_topology: Option<(u8, u8, u8)>,
device_info: &HashMap<(DeviceType, String), T, S>,
initrd: &Option<super::InitramfsConfig>,
pci_space_info: &[PciSpaceInfo],

43
arch/src/aarch64/regs.rs Normal file
View File

@@ -0,0 +1,43 @@
// Copyright 2022 Arm Limited (or its affiliates). All rights reserved.
// SPDX-License-Identifier: Apache-2.0
// AArch64 system register encoding:
// See https://developer.arm.com/documentation/ddi0487 (chapter D12)
//
// 31 22 21 20 19 18 16 15 12 11 8 7 5 4 0
// +----------+---+-----+-----+-----+-----+-----+----+
// |1101010100| L | op0 | op1 | CRn | CRm | op2 | Rt |
// +----------+---+-----+-----+-----+-----+-----+----+
//
// Notes:
// - L and Rt are reserved as implementation defined fields, ignored.
const SYSREG_HEAD: u32 = 0b1101010100u32 << 22;
const SYSREG_OP0_SHIFT: u32 = 19;
const SYSREG_OP0_MASK: u32 = 0b11u32 << 19;
const SYSREG_OP1_SHIFT: u32 = 16;
const SYSREG_OP1_MASK: u32 = 0b111u32 << 16;
const SYSREG_CRN_SHIFT: u32 = 12;
const SYSREG_CRN_MASK: u32 = 0b1111u32 << 12;
const SYSREG_CRM_SHIFT: u32 = 8;
const SYSREG_CRM_MASK: u32 = 0b1111u32 << 8;
const SYSREG_OP2_SHIFT: u32 = 5;
const SYSREG_OP2_MASK: u32 = 0b111u32 << 5;
/// Define the ID of system registers
#[macro_export]
macro_rules! arm64_sys_reg {
($name: tt, $op0: tt, $op1: tt, $crn: tt, $crm: tt, $op2: tt) => {
pub const $name: u32 = SYSREG_HEAD
| ((($op0 as u32) << SYSREG_OP0_SHIFT) & SYSREG_OP0_MASK as u32)
| ((($op1 as u32) << SYSREG_OP1_SHIFT) & SYSREG_OP1_MASK as u32)
| ((($crn as u32) << SYSREG_CRN_SHIFT) & SYSREG_CRN_MASK as u32)
| ((($crm as u32) << SYSREG_CRM_SHIFT) & SYSREG_CRM_MASK as u32)
| ((($op2 as u32) << SYSREG_OP2_SHIFT) & SYSREG_OP2_MASK as u32);
};
}
arm64_sys_reg!(MPIDR_EL1, 3, 0, 0, 0, 5);
arm64_sys_reg!(ID_AA64MMFR0_EL1, 3, 0, 0, 7, 0);
arm64_sys_reg!(TTBR1_EL1, 3, 0, 2, 0, 1);
arm64_sys_reg!(TCR_EL1, 3, 0, 2, 0, 2);

View File

@@ -1,28 +1,20 @@
// Copyright 2020 Arm Limited (or its affiliates). All rights reserved.
//
// SPDX-License-Identifier: Apache-2.0
use std::io::{Read, Seek, SeekFrom};
use std::os::fd::AsFd;
use std::result;
use thiserror::Error;
use vm_memory::{GuestAddress, GuestMemory};
/// Errors thrown while loading UEFI binary
#[derive(Debug, Error)]
#[derive(Debug)]
pub enum Error {
/// Unable to seek to UEFI image start.
#[error("Unable to seek to UEFI image start")]
SeekUefiStart,
/// Unable to seek to UEFI image end.
#[error("Unable to seek to UEFI image end")]
SeekUefiEnd,
/// UEFI image too big.
#[error("UEFI image too big")]
UefiTooBig,
/// Unable to read UEFI image
#[error("Unable to read UEFI image")]
ReadUefiImage,
}
type Result<T> = result::Result<T, Error>;

View File

@@ -1,4 +1,3 @@
// Copyright © 2024 Institute of Software, CAS. All rights reserved.
// Copyright 2020 Arm Limited (or its affiliates). All rights reserved.
// Copyright © 2020, Oracle and/or its affiliates.
//
@@ -6,17 +5,22 @@
// SPDX-License-Identifier: Apache-2.0
//! Implements platform specific functionality.
//! Supported platforms: x86_64, aarch64, riscv64.
//! Supported platforms: x86_64, aarch64.
#[macro_use]
extern crate log;
use std::collections::BTreeMap;
use std::sync::Arc;
use std::{fmt, result};
#[cfg(target_arch = "x86_64")]
use crate::x86_64::SgxEpcSection;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::fmt;
use std::result;
use std::sync::Arc;
use thiserror::Error;
use versionize::{VersionMap, Versionize, VersionizeError, VersionizeResult};
use versionize_derive::Versionize;
use vm_migration::VersionMapped;
type GuestMemoryMmap = vm_memory::GuestMemoryMmap<vm_memory::bitmap::AtomicBitmap>;
type GuestRegionMmap = vm_memory::GuestRegionMmap<vm_memory::bitmap::AtomicBitmap>;
@@ -25,14 +29,11 @@ type GuestRegionMmap = vm_memory::GuestRegionMmap<vm_memory::bitmap::AtomicBitma
#[derive(Debug, Error)]
pub enum Error {
#[cfg(target_arch = "x86_64")]
#[error("Platform specific error (x86_64)")]
PlatformSpecific(#[from] x86_64::Error),
#[error("Platform specific error (x86_64): {0:?}")]
PlatformSpecific(x86_64::Error),
#[cfg(target_arch = "aarch64")]
#[error("Platform specific error (aarch64)")]
PlatformSpecific(#[from] aarch64::Error),
#[cfg(target_arch = "riscv64")]
#[error("Platform specific error (riscv64)")]
PlatformSpecific(#[from] riscv64::Error),
#[error("Platform specific error (aarch64): {0:?}")]
PlatformSpecific(aarch64::Error),
#[error("The memory map table extends past the end of guest memory")]
MemmapTablePastRamEnd,
#[error("Error writing memory map table to guest memory")]
@@ -43,21 +44,17 @@ pub enum Error {
StartInfoSetup,
#[error("Failed to compute initramfs address")]
InitramfsAddress,
#[error("Error writing module entry to guest memory")]
#[error("Error writing module entry to guest memory: {0}")]
ModlistSetup(#[source] vm_memory::GuestMemoryError),
#[error("RSDP extends past the end of guest memory")]
RsdpPastRamEnd,
#[error("Failed to setup Zero Page for bzImage")]
ZeroPageSetup(#[source] vm_memory::GuestMemoryError),
#[error("Zero Page for bzImage past RAM end")]
ZeroPagePastRamEnd,
}
/// Type for returning public functions outcome.
pub type Result<T> = result::Result<T, Error>;
/// Type for memory region types.
#[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize)]
#[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize, Versionize)]
pub enum RegionType {
/// RAM type
Ram,
@@ -75,26 +72,17 @@ pub enum RegionType {
Reserved,
}
impl VersionMapped for RegionType {}
/// Module for aarch64 related functionality.
#[cfg(target_arch = "aarch64")]
pub mod aarch64;
#[cfg(target_arch = "aarch64")]
pub use aarch64::{
_NSIG, EntryPoint, arch_memory_regions, configure_system, configure_vcpu,
fdt::DeviceInfoForFdt, get_host_cpu_phys_bits, initramfs_load_addr, layout,
layout::CMDLINE_MAX_SIZE, layout::IRQ_BASE, uefi,
};
/// Module for riscv64 related functionality.
#[cfg(target_arch = "riscv64")]
pub mod riscv64;
#[cfg(target_arch = "riscv64")]
pub use riscv64::{
_NSIG, EntryPoint, arch_memory_regions, configure_system, configure_vcpu,
fdt::DeviceInfoForFdt, get_host_cpu_phys_bits, initramfs_load_addr, layout,
layout::CMDLINE_MAX_SIZE, layout::IRQ_BASE, uefi,
arch_memory_regions, configure_system, configure_vcpu, fdt::DeviceInfoForFdt,
get_host_cpu_phys_bits, initramfs_load_addr, layout, layout::CMDLINE_MAX_SIZE,
layout::IRQ_BASE, uefi, EntryPoint, _NSIG,
};
#[cfg(target_arch = "x86_64")]
@@ -102,9 +90,10 @@ pub mod x86_64;
#[cfg(target_arch = "x86_64")]
pub use x86_64::{
_NSIG, CpuidConfig, CpuidFeatureEntry, EntryPoint, arch_memory_regions, configure_system,
configure_vcpu, generate_common_cpuid, generate_ram_ranges, get_host_cpu_phys_bits,
initramfs_load_addr, layout, layout::CMDLINE_MAX_SIZE, layout::CMDLINE_START, regs,
arch_memory_regions, configure_system, configure_vcpu, generate_common_cpuid,
generate_ram_ranges, get_host_cpu_phys_bits, initramfs_load_addr, layout,
layout::CMDLINE_MAX_SIZE, layout::CMDLINE_START, regs, CpuidConfig, CpuidFeatureEntry,
EntryPoint, _NSIG,
};
/// Safe wrapper for `sysconf(_SC_PAGESIZE)`.
@@ -119,10 +108,12 @@ fn pagesize() -> usize {
pub struct NumaNode {
pub memory_regions: Vec<Arc<GuestRegionMmap>>,
pub hotplug_regions: Vec<Arc<GuestRegionMmap>>,
pub cpus: Vec<u32>,
pub cpus: Vec<u8>,
pub pci_segments: Vec<u16>,
pub distances: BTreeMap<u32, u8>,
pub memory_zones: Vec<String>,
#[cfg(target_arch = "x86_64")]
pub sgx_epc_sections: Vec<SgxEpcSection>,
}
pub type NumaNodes = BTreeMap<u32, NumaNode>;
@@ -141,7 +132,7 @@ pub enum DeviceType {
/// Device Type: Virtio.
Virtio(u32),
/// Device Type: Serial.
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
#[cfg(target_arch = "aarch64")]
Serial,
/// Device Type: RTC.
#[cfg(target_arch = "aarch64")]
@@ -149,9 +140,6 @@ pub enum DeviceType {
/// Device Type: GPIO.
#[cfg(target_arch = "aarch64")]
Gpio,
/// Device Type: fw_cfg.
#[cfg(feature = "fw_cfg")]
FwCfg,
}
/// Default (smallest) memory page size for the supported architectures.
@@ -165,7 +153,7 @@ impl fmt::Display for DeviceType {
/// Structure to describe MMIO device information
#[derive(Clone, Debug)]
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
#[cfg(target_arch = "aarch64")]
pub struct MmioDeviceInfo {
pub addr: u64,
pub len: u64,
@@ -174,7 +162,7 @@ pub struct MmioDeviceInfo {
/// Structure to describe PCI space information
#[derive(Clone, Debug)]
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
#[cfg(target_arch = "aarch64")]
pub struct PciSpaceInfo {
pub pci_segment_id: u16,
pub mmio_config_address: u64,
@@ -182,7 +170,7 @@ pub struct PciSpaceInfo {
pub pci_device_space_size: u64,
}
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
#[cfg(target_arch = "aarch64")]
impl DeviceInfoForFdt for MmioDeviceInfo {
fn addr(&self) -> u64 {
self.addr

View File

@@ -1,481 +0,0 @@
// Copyright © 2024 Institute of Software, CAS. All rights reserved.
// Copyright 2020 Arm Limited (or its affiliates). All rights reserved.
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
//
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the THIRD-PARTY file.
use std::collections::HashMap;
use std::ffi::CStr;
use std::fmt::Debug;
use std::sync::{Arc, Mutex};
use std::{cmp, result, str};
use byteorder::{BigEndian, ByteOrder};
use hypervisor::arch::riscv64::aia::Vaia;
use thiserror::Error;
use vm_fdt::{FdtWriter, FdtWriterResult};
use vm_memory::{Address, Bytes, GuestMemory, GuestMemoryError, GuestMemoryRegion};
use super::super::{DeviceType, GuestMemoryMmap, InitramfsConfig};
use super::layout::{
IRQ_BASE, MEM_32BIT_DEVICES_SIZE, MEM_32BIT_DEVICES_START, MEM_PCI_IO_SIZE, MEM_PCI_IO_START,
PCI_HIGH_BASE, PCI_MMIO_CONFIG_SIZE_PER_SEGMENT,
};
use crate::PciSpaceInfo;
const AIA_APLIC_PHANDLE: u32 = 1;
const AIA_IMSIC_PHANDLE: u32 = 2;
const CPU_INTC_BASE_PHANDLE: u32 = 3;
const CPU_BASE_PHANDLE: u32 = 256 + CPU_INTC_BASE_PHANDLE;
// Read the documentation specified when appending the root node to the FDT.
const ADDRESS_CELLS: u32 = 0x2;
const SIZE_CELLS: u32 = 0x2;
// From https://elixir.bootlin.com/linux/v6.10/source/include/dt-bindings/interrupt-controller/irq.h#L14
const _IRQ_TYPE_EDGE_RISING: u32 = 1;
const IRQ_TYPE_LEVEL_HI: u32 = 4;
const S_MODE_EXT_IRQ: u32 = 9;
/// Trait for devices to be added to the Flattened Device Tree.
pub trait DeviceInfoForFdt {
/// Returns the address where this device will be loaded.
fn addr(&self) -> u64;
/// Returns the associated interrupt for this device.
fn irq(&self) -> u32;
/// Returns the amount of memory that needs to be reserved for this device.
fn length(&self) -> u64;
}
/// Errors thrown while configuring the Flattened Device Tree for riscv64.
#[derive(Debug, Error)]
pub enum Error {
/// Failure in writing FDT in memory.
#[error("Failure in writing FDT in memory")]
WriteFdtToMemory(#[source] 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@{cpu_index:x}"))?;
fdt.property_string("device_type", "cpu")?;
fdt.property_string("compatible", "riscv")?;
fdt.property_string("mmu-type", "sv48")?;
fdt.property_string("riscv,isa", "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@{ram_start:x}");
let memory_node = fdt.begin_node(&memory_node_name)?;
fdt.property_string("device_type", "memory")?;
fdt.property_array_u64("reg", &mem_reg_property)?;
fdt.end_node(memory_node)?;
Ok(())
}
fn create_chosen_node(
fdt: &mut FdtWriter,
cmdline: &str,
initrd: &Option<InitramfsConfig>,
) -> FdtWriterResult<()> {
let chosen_node = fdt.begin_node("chosen")?;
fdt.property_string("bootargs", cmdline)?;
if let Some(initrd_config) = initrd {
let initrd_start = initrd_config.address.raw_value();
let initrd_end = initrd_config.address.raw_value() + initrd_config.size as u64;
fdt.property_u64("linux,initrd-start", initrd_start)?;
fdt.property_u64("linux,initrd-end", initrd_end)?;
}
fdt.end_node(chosen_node)?;
Ok(())
}
fn create_aia_node(fdt: &mut FdtWriter, aia_device: &Arc<Mutex<dyn Vaia>>) -> FdtWriterResult<()> {
// IMSIC
if aia_device.lock().unwrap().msi_compatible() {
use super::layout::IMSIC_START;
let imsic_name = format!("imsics@{:x}", IMSIC_START.0);
let imsic_node = fdt.begin_node(&imsic_name)?;
fdt.property_string(
"compatible",
aia_device.lock().unwrap().imsic_compatibility(),
)?;
let imsic_reg_prop = aia_device.lock().unwrap().imsic_properties();
fdt.property_array_u32("reg", &imsic_reg_prop)?;
fdt.property_u32("#interrupt-cells", 0u32)?;
fdt.property_null("interrupt-controller")?;
fdt.property_null("msi-controller")?;
// 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", &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", &reg)?;
fdt.property_u32("#interrupt-cells", 1)?;
fdt.property_null("interrupt-map")?;
fdt.property_null("interrupt-map-mask")?;
fdt.property_null("dma-coherent")?;
fdt.property_array_u32("msi-map", &msi_map)?;
fdt.property_u32("msi-parent", AIA_IMSIC_PHANDLE)?;
fdt.end_node(pci_node)?;
}
Ok(())
}
// Parse the DTB binary and print for debugging
pub fn print_fdt(dtb: &[u8]) {
match fdt_parser::Fdt::new(dtb) {
Ok(fdt) => {
if let Some(root) = fdt.find_node("/") {
debug!("Printing the FDT:");
print_node(root, 0);
} else {
debug!("Failed to find root node in FDT for debugging.");
}
}
Err(_) => debug!("Failed to parse FDT for debugging."),
}
}
fn print_node(node: fdt_parser::node::FdtNode<'_, '_>, n_spaces: usize) {
debug!("{:indent$}{}/", "", node.name, indent = n_spaces);
for property in node.properties() {
let name = property.name;
// If the property is 'compatible', its value requires special handling.
// The u8 array could contain multiple null-terminated strings.
// We copy the original array and simply replace all 'null' characters with spaces.
let value = if name == "compatible" {
let mut compatible = vec![0u8; 256];
let handled_value = property
.value
.iter()
.map(|&c| if c == 0 { b' ' } else { c })
.collect::<Vec<_>>();
let len = cmp::min(255, handled_value.len());
compatible[..len].copy_from_slice(&handled_value[..len]);
compatible[..(len + 1)].to_vec()
} else {
property.value.to_vec()
};
let value = &value;
// Now the value can be either:
// - A null-terminated C string, or
// - Binary data
// We follow a very simple logic to present the value:
// - At first, try to convert it to CStr and print,
// - If failed, print it as u32 array.
let value_result = match CStr::from_bytes_with_nul(value) {
Ok(value_cstr) => value_cstr.to_str().ok(),
Err(_e) => None,
};
if let Some(value_str) = value_result {
debug!(
"{:indent$}{} : {:#?}",
"",
name,
value_str,
indent = (n_spaces + 2)
);
} else {
let mut array = Vec::with_capacity(256);
array.resize(value.len() / 4, 0u32);
BigEndian::read_u32_into(value, &mut array);
debug!(
"{:indent$}{} : {:X?}",
"",
name,
array,
indent = (n_spaces + 2)
);
};
}
// Print children nodes if there is any
for child in node.children() {
print_node(child, n_spaces + 2);
}
}

View File

@@ -1,112 +0,0 @@
// Copyright © 2024 Institute of Software, CAS. All rights reserved.
// Copyright 2020 Arm Limited (or its affiliates). All rights reserved.
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
//
// Memory layout of RISC-V 64-bit guest:
//
// Physical +---------------------------------------------------------------+
// address | |
// end | |
// ~ ~ ~ ~
// | |
// | Highmem PCI MMIO space |
// | |
// RAM end +---------------------------------------------------------------+
// (dynamic, | |
// including | |
// hotplug ~ ~ ~ ~
// memory) | |
// | DRAM |
// | |
// | |
// | |
// | |
// 1 GB +---------------------------------------------------------------+
// | |
// | PCI MMCONFIG space |
// | |
// 768 MB +---------------------------------------------------------------+
// | |
// | |
// | PCI MMIO space |
// | |
// 256 MB +---------------------------------------------------------------|
// | |
// | Legacy devices space |
// | |
// 128 MB +---------------------------------------------------------------|
// | |
// | IMSICs |
// | |
// 64 MB +---------------------------------------------------------------+
// | |
// | APLICs |
// | |
// 4 MB +---------------------------------------------------------------+
// | UEFI flash |
// 0 GB +---------------------------------------------------------------+
//
//
use vm_memory::GuestAddress;
/// 0x0 ~ 0x40_0000 (4 MiB) is reserved to UEFI
/// UEFI binary size is required less than 3 MiB, reserving 4 MiB is enough.
pub const UEFI_START: GuestAddress = GuestAddress(0);
pub const UEFI_SIZE: u64 = 0x040_0000;
/// AIA related devices
/// See https://elixir.bootlin.com/linux/v6.10/source/arch/riscv/include/uapi/asm/kvm.h
/// 0x40_0000 ~ 0x0400_0000 (64 MiB) resides APLICs
pub const APLIC_START: GuestAddress = GuestAddress(0x40_0000);
pub const APLIC_SIZE: u64 = 0x4000;
/// 0x0400_0000 ~ 0x0800_0000 (64 MiB) resides IMSICs
pub const IMSIC_START: GuestAddress = GuestAddress(0x0400_0000);
pub const IMSIC_SIZE: u64 = 0x1000;
/// Below this address will reside the AIA, above this address will reside the MMIO devices.
const MAPPED_IO_START: GuestAddress = GuestAddress(0x0800_0000);
/// Space 0x0800_0000 ~ 0x1000_0000 is reserved for legacy devices.
pub const LEGACY_SERIAL_MAPPED_IO_START: GuestAddress = MAPPED_IO_START;
/// Space 0x0905_0000 ~ 0x0906_0000 is reserved for pcie io address
pub const MEM_PCI_IO_START: GuestAddress = GuestAddress(0x0905_0000);
pub const MEM_PCI_IO_SIZE: u64 = 0x1_0000;
/// Starting from 0x1000_0000 (256MiB) to 0x3000_0000 (768MiB) is used for PCIE MMIO
pub const MEM_32BIT_DEVICES_START: GuestAddress = GuestAddress(0x1000_0000);
pub const MEM_32BIT_DEVICES_SIZE: u64 = 0x2000_0000;
/// PCI MMCONFIG space (start: after the device space at 768MiB, length: 256MiB)
pub const PCI_MMCONFIG_START: GuestAddress = GuestAddress(0x3000_0000);
pub const PCI_MMCONFIG_SIZE: u64 = 256 << 20;
// One bus with potentially 256 devices (32 slots x 8 functions).
pub const PCI_MMIO_CONFIG_SIZE_PER_SEGMENT: u64 = 4096 * 256;
/// Start of RAM.
pub const RAM_START: GuestAddress = GuestAddress(0x4000_0000);
/// Kernel command line maximum size on RISC-V.
/// See https://elixir.bootlin.com/linux/v6.10/source/arch/riscv/include/uapi/asm/setup.h
pub const CMDLINE_MAX_SIZE: usize = 1024;
/// FDT is at the beginning of RAM.
pub const FDT_START: GuestAddress = RAM_START;
pub const FDT_MAX_SIZE: u64 = 0x1_0000;
/// 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;

View File

@@ -1,169 +0,0 @@
// Copyright © 2024 Institute of Software, CAS. All rights reserved.
// Copyright 2020 Arm Limited (or its affiliates). All rights reserved.
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
/// Module for the flattened device tree.
pub mod fdt;
/// Layout for this riscv64 system.
pub mod layout;
/// Module for loading UEFI binary.
pub mod uefi;
use std::collections::HashMap;
use std::fmt::Debug;
use std::sync::{Arc, Mutex};
use hypervisor::arch::riscv64::aia::Vaia;
use log::{Level, log_enabled};
use thiserror::Error;
use vm_memory::{Address, GuestAddress, GuestMemory, GuestMemoryAtomic};
pub use self::fdt::DeviceInfoForFdt;
use crate::{DeviceType, GuestMemoryMmap, PciSpaceInfo, RegionType};
pub const _NSIG: i32 = 65;
/// Errors thrown while configuring riscv64 system.
#[derive(Debug, Error)]
pub enum Error {
/// Failed to create a FDT.
#[error("Failed to create a FDT")]
SetupFdt,
/// Failed to write FDT to memory.
#[error("Failed to write FDT to memory")]
WriteFdtToMemory(#[source] fdt::Error),
/// Failed to create a AIA.
#[error("Failed to create a AIA")]
SetupAia,
/// Failed to compute the initramfs address.
#[error("Failed to compute the initramfs address")]
InitramfsAddress,
/// Error configuring the general purpose registers
#[error("Error configuring the general purpose registers")]
RegsConfiguration(#[source] hypervisor::HypervisorCpuError),
}
#[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: u32,
boot_setup: Option<(EntryPoint, &GuestMemoryAtomic<GuestMemoryMmap>)>,
) -> super::Result<()> {
if let Some((kernel_entry_point, _guest_memory)) = boot_setup {
vcpu.setup_regs(
id,
kernel_entry_point.entry_addr.raw_value(),
layout::FDT_START.raw_value(),
)
.map_err(Error::RegsConfiguration)?;
}
Ok(())
}
pub fn arch_memory_regions() -> Vec<(GuestAddress, usize, RegionType)> {
vec![
// 0 MiB ~ 256 MiB: AIA and legacy devices
(
GuestAddress(0),
layout::MEM_32BIT_DEVICES_START.0 as usize,
RegionType::Reserved,
),
// 256 MiB ~ 768 MiB: MMIO space
(
layout::MEM_32BIT_DEVICES_START,
layout::MEM_32BIT_DEVICES_SIZE as usize,
RegionType::SubRegion,
),
// 768 MiB ~ 1 GiB: reserved. The leading 256M for PCIe MMCONFIG space
(
layout::PCI_MMCONFIG_START,
layout::PCI_MMCONFIG_SIZE as usize,
RegionType::Reserved,
),
// 1GiB ~ inf: RAM
(layout::RAM_START, usize::MAX, RegionType::Ram),
]
}
/// 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);
}
}

View File

@@ -1,50 +0,0 @@
// Copyright 2020 Arm Limited (or its affiliates). All rights reserved.
//
// SPDX-License-Identifier: Apache-2.0
use std::io::{Read, Seek, SeekFrom};
use std::os::fd::AsFd;
use std::result;
use thiserror::Error;
use vm_memory::{GuestAddress, GuestMemory};
/// Errors thrown while loading UEFI binary
#[derive(Debug, Error)]
pub enum Error {
/// Unable to seek to UEFI image start.
#[error("Unable to seek to UEFI image start")]
SeekUefiStart,
/// Unable to seek to UEFI image end.
#[error("Unable to seek to UEFI image end")]
SeekUefiEnd,
/// UEFI image too big.
#[error("UEFI image too big")]
UefiTooBig,
/// Unable to read UEFI image
#[error("Unable to read UEFI image")]
ReadUefiImage,
}
type Result<T> = result::Result<T, Error>;
pub fn load_uefi<F, M: GuestMemory>(
guest_mem: &M,
guest_addr: GuestAddress,
uefi_image: &mut F,
) -> Result<()>
where
F: Read + Seek + AsFd,
{
let uefi_size = uefi_image
.seek(SeekFrom::End(0))
.map_err(|_| Error::SeekUefiEnd)? as usize;
// edk2 image on virtual platform is smaller than 3M
if uefi_size > 0x300000 {
return Err(Error::UefiTooBig);
}
uefi_image.rewind().map_err(|_| Error::SeekUefiStart)?;
guest_mem
.read_exact_volatile_from(guest_addr, &mut uefi_image.as_fd(), uefi_size)
.map_err(|_| Error::ReadUefiImage)
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,10 +1,6 @@
// Copyright 2017 The Chromium OS Authors. All rights reserved.
//
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE-BSD-3-Clause file.
#![allow(non_camel_case_types)]
use vm_memory::ByteValued;
pub const MP_PROCESSOR: ::std::os::raw::c_uint = 0;
pub const MP_BUS: ::std::os::raw::c_uint = 1;
@@ -19,7 +15,7 @@ pub const MP_IRQDIR_DEFAULT: ::std::os::raw::c_uint = 0;
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct mpf_intel {
pub signature: [::std::os::raw::c_uchar; 4usize],
pub signature: [::std::os::raw::c_char; 4usize],
pub physptr: ::std::os::raw::c_uint,
pub length: ::std::os::raw::c_uchar,
pub specification: ::std::os::raw::c_uchar,
@@ -31,22 +27,15 @@ pub struct mpf_intel {
pub feature5: ::std::os::raw::c_uchar,
}
const _: () = assert!(::core::mem::size_of::<mpf_intel>() == 16);
// SAFETY: all members of this struct are plain integers
// and the sum of their sizes is the size of the struct, so
// padding and reserved values are not possible as there
// would be nowhere for them to exist.
unsafe impl ByteValued for mpf_intel {}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct mpc_table {
pub signature: [::std::os::raw::c_uchar; 4usize],
pub signature: [::std::os::raw::c_char; 4usize],
pub length: ::std::os::raw::c_ushort,
pub spec: ::std::os::raw::c_uchar,
pub checksum: ::std::os::raw::c_uchar,
pub oem: [::std::os::raw::c_uchar; 8usize],
pub productid: [::std::os::raw::c_uchar; 12usize],
pub spec: ::std::os::raw::c_char,
pub checksum: ::std::os::raw::c_char,
pub oem: [::std::os::raw::c_char; 8usize],
pub productid: [::std::os::raw::c_char; 12usize],
pub oemptr: ::std::os::raw::c_uint,
pub oemsize: ::std::os::raw::c_ushort,
pub oemcount: ::std::os::raw::c_ushort,
@@ -54,19 +43,6 @@ pub struct mpc_table {
pub reserved: ::std::os::raw::c_uint,
}
const _: () = {
assert!(::core::mem::size_of::<mpc_table>() == 4 + 2 + 1 + 1 + 8 + 12 + 4 + 2 + 2 + 4 + 4);
assert!(::core::mem::size_of::<::std::os::raw::c_uint>() == 4);
assert!(::core::mem::size_of::<::std::os::raw::c_ushort>() == 2);
assert!(::core::mem::size_of::<::std::os::raw::c_uchar>() == 1);
};
// SAFETY: all members of this struct are plain integers
// and the sum of their sizes is the size of the struct, so
// padding and reserved values are not possible as there
// would be nowhere for them to exist.
unsafe impl ByteValued for mpc_table {}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct mpc_cpu {
@@ -79,13 +55,6 @@ pub struct mpc_cpu {
pub reserved: [::std::os::raw::c_uint; 2usize],
}
const _: () = assert!(::core::mem::size_of::<mpc_cpu>() == 20);
// SAFETY: all members of this struct are plain integers
// and the sum of their sizes is the size of the struct, so
// padding and reserved values are not possible as there
// would be nowhere for them to exist.
unsafe impl ByteValued for mpc_cpu {}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct mpc_bus {
@@ -94,13 +63,6 @@ pub struct mpc_bus {
pub bustype: [::std::os::raw::c_uchar; 6usize],
}
const _: () = assert!(::core::mem::size_of::<mpc_bus>() == 8);
// SAFETY: all members of this struct are plain integers
// and the sum of their sizes is the size of the struct, so
// padding and reserved values are not possible as there
// would be nowhere for them to exist.
unsafe impl ByteValued for mpc_bus {}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct mpc_ioapic {
@@ -111,13 +73,6 @@ pub struct mpc_ioapic {
pub apicaddr: ::std::os::raw::c_uint,
}
const _: () = assert!(::core::mem::size_of::<mpc_ioapic>() == 8);
// SAFETY: all members of this struct are plain integers
// and the sum of their sizes is the size of the struct, so
// padding and reserved values are not possible as there
// would be nowhere for them to exist.
unsafe impl ByteValued for mpc_ioapic {}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct mpc_intsrc {
@@ -130,13 +85,6 @@ pub struct mpc_intsrc {
pub dstirq: ::std::os::raw::c_uchar,
}
const _: () = assert!(::core::mem::size_of::<mpc_intsrc>() == 8);
// SAFETY: all members of this struct are plain integers
// and the sum of their sizes is the size of the struct, so
// padding and reserved values are not possible as there
// would be nowhere for them to exist.
unsafe impl ByteValued for mpc_intsrc {}
pub const MP_IRQ_SOURCE_TYPES_MP_INT: ::std::os::raw::c_uint = 0;
pub const MP_IRQ_SOURCE_TYPES_MP_NMI: ::std::os::raw::c_uint = 1;
pub const MP_IRQ_SOURCE_TYPES_MP_EXT_INT: ::std::os::raw::c_uint = 3;
@@ -153,26 +101,12 @@ pub struct mpc_lintsrc {
pub destapiclint: ::std::os::raw::c_uchar,
}
const _: () = assert!(::core::mem::size_of::<mpc_lintsrc>() == 8);
// SAFETY: all members of this struct are plain integers
// and the sum of their sizes is the size of the struct, so
// padding and reserved values are not possible as there
// would be nowhere for them to exist.
unsafe impl ByteValued for mpc_lintsrc {}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct mpc_oemtable {
pub signature: [::std::os::raw::c_uchar; 4usize],
pub signature: [::std::os::raw::c_char; 4usize],
pub length: ::std::os::raw::c_ushort,
pub rev: ::std::os::raw::c_uchar,
pub checksum: ::std::os::raw::c_uchar,
pub mpc: [::std::os::raw::c_uchar; 8usize],
pub rev: ::std::os::raw::c_char,
pub checksum: ::std::os::raw::c_char,
pub mpc: [::std::os::raw::c_char; 8usize],
}
const _: () = assert!(::core::mem::size_of::<mpc_oemtable>() == 16);
// SAFETY: all members of this struct are plain integers
// and the sum of their sizes is the size of the struct, so
// padding and reserved values are not possible as there
// would be nowhere for them to exist.
unsafe impl ByteValued for mpc_oemtable {}

View File

@@ -5,16 +5,14 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE-BSD-3-Clause file.
use std::{mem, result, slice};
use libc::c_uchar;
use thiserror::Error;
use vm_memory::{Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryError};
use super::MAX_SUPPORTED_CPUS_LEGACY;
use crate::GuestMemoryMmap;
use crate::layout::{APIC_START, HIGH_RAM_START, IOAPIC_START};
use crate::x86_64::{get_x2apic_id, mpspec};
use crate::GuestMemoryMmap;
use libc::c_char;
use std::mem;
use std::result;
use std::slice;
use vm_memory::{Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryError};
// This is a workaround to the Rust enforcement specifying that any implementation of a foreign
// trait (in this case `ByteValued`) where:
@@ -51,55 +49,57 @@ unsafe impl ByteValued for MpcLintsrcWrapper {}
// SAFETY: see above
unsafe impl ByteValued for MpfIntelWrapper {}
#[derive(Debug, Error)]
#[derive(Debug)]
pub enum Error {
/// There was too little guest memory to store the entire MP table.
#[error("There was too little guest memory to store the entire MP table")]
NotEnoughMemory,
/// The MP table has too little address space to be stored.
#[error("The MP table has too little address space to be stored")]
AddressOverflow,
/// Failure while zeroing out the memory for the MP table.
#[error("Failure while zeroing out the memory for the MP table")]
Clear(#[source] GuestMemoryError),
Clear(GuestMemoryError),
/// Number of CPUs exceeds the maximum supported CPUs
TooManyCpus,
/// Failure to write the MP floating pointer.
#[error("Failure to write the MP floating pointer")]
WriteMpfIntel(#[source] GuestMemoryError),
WriteMpfIntel(GuestMemoryError),
/// Failure to write MP CPU entry.
#[error("Failure to write MP CPU entry")]
WriteMpcCpu(#[source] GuestMemoryError),
WriteMpcCpu(GuestMemoryError),
/// Failure to write MP ioapic entry.
#[error("Failure to write MP ioapic entry")]
WriteMpcIoapic(#[source] GuestMemoryError),
WriteMpcIoapic(GuestMemoryError),
/// Failure to write MP bus entry.
#[error("Failure to write MP bus entry")]
WriteMpcBus(#[source] GuestMemoryError),
WriteMpcBus(GuestMemoryError),
/// Failure to write MP interrupt source entry.
#[error("Failure to write MP interrupt source entry")]
WriteMpcIntsrc(#[source] GuestMemoryError),
WriteMpcIntsrc(GuestMemoryError),
/// Failure to write MP local interrupt source entry.
#[error("Failure to write MP local interrupt source entry")]
WriteMpcLintsrc(#[source] GuestMemoryError),
WriteMpcLintsrc(GuestMemoryError),
/// Failure to write MP table header.
#[error("Failure to write MP table header")]
WriteMpcTable(#[source] GuestMemoryError),
WriteMpcTable(GuestMemoryError),
}
pub type Result<T> = result::Result<T, Error>;
// With APIC/xAPIC, there are only 255 APIC IDs available. And IOAPIC occupies
// one APIC ID, so only 254 CPUs at maximum may be supported. Actually it's
// a large number for FC usecases.
pub const MAX_SUPPORTED_CPUS: u32 = 254;
// Convenience macro for making arrays of diverse character types.
macro_rules! char_array {
($t:ty; $( $c:expr ),*) => ( [ $( $c as $t ),* ] )
}
// Most of these variables are sourced from the Intel MP Spec 1.4.
const SMP_MAGIC_IDENT: &[c_uchar; 4] = b"_MP_";
const MPC_SIGNATURE: &[c_uchar; 4] = b"PCMP";
const MPC_SPEC: u8 = 4;
const MPC_OEM: &[c_uchar; 8] = b"FC ";
const MPC_PRODUCT_ID: &[c_uchar; 12] = &[b'0'; 12];
const BUS_TYPE_ISA: &[c_uchar; 6] = b"ISA ";
const SMP_MAGIC_IDENT: [c_char; 4] = char_array!(c_char; '_', 'M', 'P', '_');
const MPC_SIGNATURE: [c_char; 4] = char_array!(c_char; 'P', 'C', 'M', 'P');
const MPC_SPEC: i8 = 4;
const MPC_OEM: [c_char; 8] = char_array!(c_char; 'F', 'C', ' ', ' ', ' ', ' ', ' ', ' ');
const MPC_PRODUCT_ID: [c_char; 12] = ['0' as c_char; 12];
const BUS_TYPE_ISA: [u8; 6] = char_array!(u8; 'I', 'S', 'A', ' ', ' ', ' ');
const APIC_VERSION: u8 = 0x14;
const CPU_STEPPING: u32 = 0x600;
const CPU_FEATURE_APIC: u32 = 0x200;
const CPU_FEATURE_FPU: u32 = 0x001;
fn compute_checksum<T: Copy + ByteValued>(v: &T) -> u8 {
fn compute_checksum<T: Copy>(v: &T) -> u8 {
// SAFETY: we are only reading the bytes within the size of the `T` reference `v`.
let v_slice = unsafe { slice::from_raw_parts(v as *const T as *const u8, mem::size_of::<T>()) };
let mut checksum: u8 = 0;
@@ -114,7 +114,7 @@ fn mpf_intel_compute_checksum(v: &mpspec::mpf_intel) -> u8 {
(!checksum).wrapping_add(1)
}
fn compute_mp_size(num_cpus: u32) -> usize {
fn compute_mp_size(num_cpus: u8) -> usize {
mem::size_of::<MpfIntelWrapper>()
+ mem::size_of::<MpcTableWrapper>()
+ mem::size_of::<MpcCpuWrapper>() * (num_cpus as usize)
@@ -128,15 +128,14 @@ fn compute_mp_size(num_cpus: u32) -> usize {
pub fn setup_mptable(
offset: GuestAddress,
mem: &GuestMemoryMmap,
num_cpus: u32,
topology: Option<(u16, u16, u16, u16)>,
num_cpus: u8,
topology: Option<(u8, u8, u8)>,
) -> Result<()> {
if num_cpus > 0 {
let cpu_id_max = num_cpus - 1;
let x2apic_id_max = get_x2apic_id(cpu_id_max, topology);
if x2apic_id_max >= MAX_SUPPORTED_CPUS_LEGACY {
info!("Skipping mptable creation due to too many CPUs");
return Ok(());
let x2apic_id_max = get_x2apic_id(cpu_id_max.into(), topology);
if x2apic_id_max >= MAX_SUPPORTED_CPUS {
return Err(Error::TooManyCpus);
}
}
@@ -151,7 +150,7 @@ pub fn setup_mptable(
}
let mut checksum: u8 = 0;
let ioapicid: u8 = MAX_SUPPORTED_CPUS_LEGACY as u8 + 1;
let ioapicid: u8 = MAX_SUPPORTED_CPUS as u8 + 1;
// The checked_add here ensures the all of the following base_mp.unchecked_add's will be without
// overflow.
@@ -169,7 +168,7 @@ pub fn setup_mptable(
{
let mut mpf_intel = MpfIntelWrapper(mpspec::mpf_intel::default());
let size = mem::size_of::<MpfIntelWrapper>() as u64;
mpf_intel.0.signature = *SMP_MAGIC_IDENT;
mpf_intel.0.signature = SMP_MAGIC_IDENT;
mpf_intel.0.length = 1;
mpf_intel.0.specification = 4;
mpf_intel.0.physptr = (base_mp.raw_value() + size) as u32;
@@ -189,7 +188,7 @@ pub fn setup_mptable(
for cpu_id in 0..num_cpus {
let mut mpc_cpu = MpcCpuWrapper(mpspec::mpc_cpu::default());
mpc_cpu.0.type_ = mpspec::MP_PROCESSOR as u8;
mpc_cpu.0.apicid = get_x2apic_id(cpu_id, topology) as u8;
mpc_cpu.0.apicid = get_x2apic_id(cpu_id as u32, topology) as u8;
mpc_cpu.0.apicver = APIC_VERSION;
mpc_cpu.0.cpuflag = mpspec::CPU_ENABLED as u8
| if cpu_id == 0 {
@@ -210,7 +209,7 @@ pub fn setup_mptable(
let mut mpc_bus = MpcBusWrapper(mpspec::mpc_bus::default());
mpc_bus.0.type_ = mpspec::MP_BUS as u8;
mpc_bus.0.busid = 0;
mpc_bus.0.bustype = *BUS_TYPE_ISA;
mpc_bus.0.bustype = BUS_TYPE_ISA;
mem.write_obj(mpc_bus, base_mp)
.map_err(Error::WriteMpcBus)?;
base_mp = base_mp.unchecked_add(size as u64);
@@ -281,14 +280,14 @@ pub fn setup_mptable(
{
let mut mpc_table = MpcTableWrapper(mpspec::mpc_table::default());
mpc_table.0.signature = *MPC_SIGNATURE;
mpc_table.0.signature = MPC_SIGNATURE;
mpc_table.0.length = table_end.unchecked_offset_from(table_base) as u16;
mpc_table.0.spec = MPC_SPEC;
mpc_table.0.oem = *MPC_OEM;
mpc_table.0.productid = *MPC_PRODUCT_ID;
mpc_table.0.oem = MPC_OEM;
mpc_table.0.productid = MPC_PRODUCT_ID;
mpc_table.0.lapic = APIC_START.0 as u32;
checksum = checksum.wrapping_add(compute_checksum(&mpc_table.0));
mpc_table.0.checksum = (!checksum).wrapping_add(1);
mpc_table.0.checksum = (!checksum).wrapping_add(1) as i8;
mem.write_obj(mpc_table, table_base)
.map_err(Error::WriteMpcTable)?;
}
@@ -298,11 +297,12 @@ pub fn setup_mptable(
#[cfg(test)]
mod tests {
use vm_memory::bitmap::BitmapSlice;
use vm_memory::{GuestUsize, VolatileMemoryError, VolatileSlice, WriteVolatile};
use super::*;
use crate::layout::MPTABLE_START;
use vm_memory::{
bitmap::BitmapSlice, GuestAddress, GuestUsize, VolatileMemoryError, VolatileSlice,
WriteVolatile,
};
fn table_entry_size(type_: u8) -> usize {
match type_ as u32 {
@@ -330,7 +330,7 @@ mod tests {
let mem = GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(num_cpus) - 1)])
.unwrap();
setup_mptable(MPTABLE_START, &mem, num_cpus, None).unwrap_err();
assert!(setup_mptable(MPTABLE_START, &mem, num_cpus, None).is_err());
}
#[test]
@@ -388,11 +388,11 @@ mod tests {
fn cpu_entry_count() {
let mem = GuestMemoryMmap::from_ranges(&[(
MPTABLE_START,
compute_mp_size(MAX_SUPPORTED_CPUS_LEGACY),
compute_mp_size(MAX_SUPPORTED_CPUS as u8),
)])
.unwrap();
for i in 0..MAX_SUPPORTED_CPUS_LEGACY {
for i in 0..MAX_SUPPORTED_CPUS as u8 {
setup_mptable(MPTABLE_START, &mem, i, None).unwrap();
let mpf_intel: MpfIntelWrapper = mem.read_obj(MPTABLE_START).unwrap();
@@ -422,9 +422,11 @@ mod tests {
#[test]
fn cpu_entry_count_max() {
let cpus = MAX_SUPPORTED_CPUS_LEGACY + 1;
let mem = GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(cpus))]).unwrap();
let cpus = MAX_SUPPORTED_CPUS + 1;
let mem =
GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(cpus as u8))]).unwrap();
setup_mptable(MPTABLE_START, &mem, cpus, None).unwrap();
let result = setup_mptable(MPTABLE_START, &mem, cpus as u8, None);
assert!(result.is_err());
}
}

View File

@@ -6,58 +6,41 @@
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE-BSD-3-Clause file.
use std::sync::Arc;
use std::{mem, result};
use crate::layout::{BOOT_GDT_START, BOOT_IDT_START, PVH_INFO_START};
use crate::GuestMemoryMmap;
use hypervisor::arch::x86::gdt::{gdt_entry, segment_from_gdt};
use hypervisor::arch::x86::regs::CR0_PE;
use hypervisor::arch::x86::{FpuState, SpecialRegisters};
use thiserror::Error;
use hypervisor::arch::x86::{FpuState, SpecialRegisters, StandardRegisters};
use std::sync::Arc;
use std::{mem, result};
use vm_memory::{Address, Bytes, GuestMemory, GuestMemoryError};
use crate::layout::{
BOOT_GDT_START, BOOT_IDT_START, BOOT_STACK_POINTER, PVH_INFO_START, ZERO_PAGE_START,
};
use crate::{EntryPoint, GuestMemoryMmap};
#[derive(Debug, Error)]
#[derive(Debug)]
pub enum Error {
/// Failed to get SREGs for this CPU.
#[error("Failed to get SREGs for this CPU")]
GetStatusRegisters(#[source] hypervisor::HypervisorCpuError),
GetStatusRegisters(hypervisor::HypervisorCpuError),
/// Failed to set base registers for this CPU.
#[error("Failed to set base registers for this CPU")]
SetBaseRegisters(#[source] hypervisor::HypervisorCpuError),
SetBaseRegisters(hypervisor::HypervisorCpuError),
/// Failed to configure the FPU.
#[error("Failed to configure the FPU")]
SetFpuRegisters(#[source] hypervisor::HypervisorCpuError),
SetFpuRegisters(hypervisor::HypervisorCpuError),
/// Setting up MSRs failed.
#[error("Setting up MSRs failed")]
SetModelSpecificRegisters(#[source] hypervisor::HypervisorCpuError),
SetModelSpecificRegisters(hypervisor::HypervisorCpuError),
/// Failed to set SREGs for this CPU.
#[error("Failed to set SREGs for this CPU")]
SetStatusRegisters(#[source] hypervisor::HypervisorCpuError),
SetStatusRegisters(hypervisor::HypervisorCpuError),
/// Checking the GDT address failed.
#[error("Checking the GDT address failed")]
CheckGdtAddr,
/// Writing the GDT to RAM failed.
#[error("Writing the GDT to RAM failed")]
WriteGdt(#[source] GuestMemoryError),
WriteGdt(GuestMemoryError),
/// Writing the IDT to RAM failed.
#[error("Writing the IDT to RAM failed")]
WriteIdt(#[source] GuestMemoryError),
WriteIdt(GuestMemoryError),
/// Writing PDPTE to RAM failed.
#[error("Writing PDPTE to RAM failed")]
WritePdpteAddress(#[source] GuestMemoryError),
WritePdpteAddress(GuestMemoryError),
/// Writing PDE to RAM failed.
#[error("Writing PDE to RAM failed")]
WritePdeAddress(#[source] GuestMemoryError),
WritePdeAddress(GuestMemoryError),
/// Writing PML4 to RAM failed.
#[error("Writing PML4 to RAM failed")]
WritePml4Address(#[source] GuestMemoryError),
WritePml4Address(GuestMemoryError),
/// Writing PML5 to RAM failed.
#[error("Writing PML5 to RAM failed")]
WritePml5Address(#[source] GuestMemoryError),
WritePml5Address(GuestMemoryError),
}
pub type Result<T> = result::Result<T, Error>;
@@ -94,21 +77,13 @@ pub fn setup_msrs(vcpu: &Arc<dyn hypervisor::Vcpu>) -> Result<()> {
/// # Arguments
///
/// * `vcpu` - Structure for the VCPU that holds the VCPU's fd.
/// * `entry_point` - Description of the boot entry to set up.
pub fn setup_regs(vcpu: &Arc<dyn hypervisor::Vcpu>, entry_point: EntryPoint) -> Result<()> {
let mut regs = vcpu.create_standard_regs();
match entry_point.setup_header {
None => {
regs.set_rflags(0x0000000000000002u64);
regs.set_rip(entry_point.entry_addr.raw_value());
regs.set_rbx(PVH_INFO_START.raw_value());
}
Some(_) => {
regs.set_rflags(0x0000000000000002u64);
regs.set_rip(entry_point.entry_addr.raw_value());
regs.set_rsp(BOOT_STACK_POINTER.raw_value());
regs.set_rsi(ZERO_PAGE_START.raw_value());
}
/// * `boot_ip` - Starting instruction pointer.
pub fn setup_regs(vcpu: &Arc<dyn hypervisor::Vcpu>, boot_ip: u64) -> Result<()> {
let regs = StandardRegisters {
rflags: 0x0000000000000002u64,
rbx: PVH_INFO_START.raw_value(),
rip: boot_ip,
..Default::default()
};
vcpu.set_regs(&regs).map_err(Error::SetBaseRegisters)
}
@@ -119,13 +94,9 @@ pub fn setup_regs(vcpu: &Arc<dyn hypervisor::Vcpu>, entry_point: EntryPoint) ->
///
/// * `mem` - The memory that will be passed to the guest.
/// * `vcpu` - Structure for the VCPU that holds the VCPU's fd.
pub fn setup_sregs(
mem: &GuestMemoryMmap,
vcpu: &Arc<dyn hypervisor::Vcpu>,
enable_x2_apic_mode: bool,
) -> Result<()> {
pub fn setup_sregs(mem: &GuestMemoryMmap, vcpu: &Arc<dyn hypervisor::Vcpu>) -> Result<()> {
let mut sregs: SpecialRegisters = vcpu.get_sregs().map_err(Error::GetStatusRegisters)?;
configure_segments_and_sregs(mem, &mut sregs, enable_x2_apic_mode)?;
configure_segments_and_sregs(mem, &mut sregs)?;
vcpu.set_sregs(&sregs).map_err(Error::SetStatusRegisters)
}
@@ -152,7 +123,6 @@ fn write_idt_value(val: u64, guest_mem: &GuestMemoryMmap) -> Result<()> {
pub fn configure_segments_and_sregs(
mem: &GuestMemoryMmap,
sregs: &mut SpecialRegisters,
enable_x2_apic_mode: bool,
) -> Result<()> {
let gdt_table: [u64; BOOT_GDT_MAX] = {
// Configure GDT entries as specified by PVH boot protocol
@@ -188,19 +158,14 @@ pub fn configure_segments_and_sregs(
sregs.cr0 = CR0_PE;
sregs.cr4 = 0;
if enable_x2_apic_mode {
const X2APIC_ENABLE_BIT: u64 = 1 << 10;
sregs.apic_base |= X2APIC_ENABLE_BIT;
}
Ok(())
}
#[cfg(test)]
mod tests {
use vm_memory::GuestAddress;
use super::*;
use crate::GuestMemoryMmap;
use vm_memory::GuestAddress;
fn create_guest_mem() -> GuestMemoryMmap {
GuestMemoryMmap::from_ranges(&[(GuestAddress(0), 0x10000)]).unwrap()
@@ -214,7 +179,7 @@ mod tests {
fn segments_and_sregs() {
let mut sregs: SpecialRegisters = Default::default();
let gm = create_guest_mem();
configure_segments_and_sregs(&gm, &mut sregs, false).unwrap();
configure_segments_and_sregs(&gm, &mut sregs).unwrap();
assert_eq!(0x0, read_u64(&gm, BOOT_GDT_START));
assert_eq!(
0xcf9b000000ffff,

View File

@@ -6,35 +6,53 @@
//
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
use std::{mem, result, slice};
use thiserror::Error;
use uuid::Uuid;
use vm_memory::{Address, ByteValued, Bytes, GuestAddress};
use crate::GuestMemoryMmap;
use crate::layout::SMBIOS_START;
use crate::GuestMemoryMmap;
use std::fmt::{self, Display};
use std::mem;
use std::result;
use std::slice;
use uuid::Uuid;
use vm_memory::ByteValued;
use vm_memory::{Address, Bytes, GuestAddress};
#[derive(Debug, Error)]
#[derive(Debug)]
pub enum Error {
/// There was too little guest memory to store the entire SMBIOS table.
#[error("There was too little guest memory to store the SMBIOS table")]
NotEnoughMemory,
/// The SMBIOS table has too little address space to be stored.
#[error("The SMBIOS table has too little address space to be stored")]
AddressOverflow,
/// Failure while zeroing out the memory for the SMBIOS table.
#[error("Failure while zeroing out the memory for the SMBIOS table")]
Clear,
/// Failure to write SMBIOS entrypoint structure
#[error("Failure to write SMBIOS entrypoint structure")]
WriteSmbiosEp,
/// Failure to write additional data to memory
#[error("Failure to write additional data to memory")]
WriteData,
/// Failure to parse uuid, uuid format may be error
#[error("Failure to parse uuid")]
ParseUuid(#[source] uuid::Error),
ParseUuid(uuid::Error),
}
impl std::error::Error for Error {}
impl Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::Error::*;
let description = match self {
NotEnoughMemory => {
"There was too little guest memory to store the SMBIOS table".to_string()
}
AddressOverflow => {
"The SMBIOS table has too little address space to be stored".to_string()
}
Clear => "Failure while zeroing out the memory for the SMBIOS table".to_string(),
WriteSmbiosEp => "Failure to write SMBIOS entrypoint structure".to_string(),
WriteData => "Failure to write additional data to memory".to_string(),
ParseUuid(e) => format!("Failure to parse uuid: {e}"),
};
write!(f, "SMBIOS error: {description}")
}
}
pub type Result<T> = result::Result<T, Error>;

View File

@@ -1,23 +1,21 @@
// Copyright © 2021 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
use crate::GuestMemoryMmap;
use std::fs::File;
use std::io::{Read, Seek, SeekFrom};
use std::str::FromStr;
use thiserror::Error;
use uuid::Uuid;
use vm_memory::{ByteValued, Bytes, GuestAddress, GuestMemoryError};
use crate::GuestMemoryMmap;
#[derive(Error, Debug)]
pub enum TdvfError {
#[error("Failed read TDVF descriptor")]
#[error("Failed read TDVF descriptor: {0}")]
ReadDescriptor(#[source] std::io::Error),
#[error("Failed read TDVF descriptor offset")]
#[error("Failed read TDVF descriptor offset: {0}")]
ReadDescriptorOffset(#[source] std::io::Error),
#[error("Failed read GUID table")]
#[error("Failed read GUID table: {0}")]
ReadGuidTable(#[source] std::io::Error),
#[error("Invalid descriptor signature")]
InvalidDescriptorSignature,
@@ -25,9 +23,9 @@ pub enum TdvfError {
InvalidDescriptorSize,
#[error("Invalid descriptor version")]
InvalidDescriptorVersion,
#[error("Failed to write HOB details to guest memory")]
#[error("Failed to write HOB details to guest memory: {0}")]
GuestMemoryWriteHob(#[source] GuestMemoryError),
#[error("Failed to create Uuid")]
#[error("Failed to create Uuid: {0}")]
UuidCreation(#[source] uuid::Error),
}
@@ -35,7 +33,7 @@ const TABLE_FOOTER_GUID: &str = "96b582de-1fb2-45f7-baea-a366c55a082d";
const TDVF_METADATA_OFFSET_GUID: &str = "e47a6535-984a-4798-865e-4685a7bf8ec2";
// TDVF_DESCRIPTOR
#[repr(C, packed)]
#[repr(packed)]
#[derive(Default)]
pub struct TdvfDescriptor {
signature: [u8; 4],
@@ -45,7 +43,7 @@ pub struct TdvfDescriptor {
}
// TDVF_SECTION
#[repr(C, packed)]
#[repr(packed)]
#[derive(Clone, Copy, Default, Debug)]
pub struct TdvfSection {
pub data_offset: u32,
@@ -209,7 +207,7 @@ enum HobType {
EndOfHobList = 0xffff,
}
#[repr(C, packed)]
#[repr(C)]
#[derive(Copy, Clone, Default, Debug)]
struct HobHeader {
r#type: HobType,
@@ -217,7 +215,7 @@ struct HobHeader {
reserved: u32,
}
#[repr(C, packed)]
#[repr(C)]
#[derive(Copy, Clone, Default, Debug)]
struct HobHandoffInfoTable {
header: HobHeader,
@@ -230,7 +228,7 @@ struct HobHandoffInfoTable {
efi_end_of_hob_list: u64,
}
#[repr(C, packed)]
#[repr(C)]
#[derive(Copy, Clone, Default, Debug)]
struct EfiGuid {
data1: u32,
@@ -239,7 +237,7 @@ struct EfiGuid {
data4: [u8; 8],
}
#[repr(C, packed)]
#[repr(C)]
#[derive(Copy, Clone, Default, Debug)]
struct HobResourceDescriptor {
header: HobHeader,
@@ -250,7 +248,7 @@ struct HobResourceDescriptor {
resource_length: u64,
}
#[repr(C, packed)]
#[repr(C)]
#[derive(Copy, Clone, Default, Debug)]
struct HobGuidType {
header: HobHeader,
@@ -266,14 +264,14 @@ pub enum PayloadImageType {
RawVmLinux,
}
#[repr(C, packed)]
#[repr(C)]
#[derive(Copy, Clone, Default, Debug)]
pub struct PayloadInfo {
pub image_type: PayloadImageType,
pub entry_point: u64,
}
#[repr(C, packed)]
#[repr(C)]
#[derive(Copy, Clone, Default, Debug)]
struct TdPayload {
guid_type: HobGuidType,
@@ -299,7 +297,7 @@ pub struct TdHob {
}
fn align_hob(v: u64) -> u64 {
v.div_ceil(8) * 8
(v + 7) / 8 * 8
}
impl TdHob {

View File

@@ -1,30 +1,27 @@
[package]
authors = ["The Chromium OS Authors", "The Cloud Hypervisor Authors"]
edition.workspace = true
name = "block"
version = "0.1.0"
edition = "2021"
authors = ["The Cloud Hypervisor Authors", "The Chromium OS Authors"]
[features]
default = []
io_uring = ["dep:io-uring"]
[dependencies]
byteorder = { workspace = true }
crc-any = "2.5.0"
io-uring = { version = "0.7.10", optional = true }
libc = { workspace = true }
log = { workspace = true }
remain = "0.2.15"
serde = { workspace = true, features = ["derive"] }
smallvec = "1.15.1"
thiserror = { workspace = true }
uuid = { workspace = true, features = ["v4"] }
virtio-bindings = { workspace = true }
virtio-queue = { workspace = true }
vm-memory = { workspace = true, features = [
"backend-atomic",
"backend-bitmap",
"backend-mmap",
] }
byteorder = "1.4.3"
crc-any = "2.4.4"
io-uring = { version = "0.6.2", optional = true }
libc = "0.2.147"
log = "0.4.20"
remain = "0.2.11"
smallvec = "1.11.0"
thiserror = "1.0.40"
uuid = { version = "1.3.4", features = ["v4"] }
versionize = "0.2.0"
versionize_derive = "0.1.6"
virtio-bindings = { version = "0.2.0", features = ["virtio-v5_0_0"] }
virtio-queue = "0.11.0"
vm-memory = { version = "0.14.0", features = ["backend-mmap", "backend-atomic", "backend-bitmap"] }
vm-virtio = { path = "../vm-virtio" }
vmm-sys-util = { workspace = true }
vmm-sys-util = "0.12.1"

View File

@@ -2,86 +2,41 @@
//
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
use std::marker::PhantomData;
use std::os::fd::{AsRawFd, OwnedFd, RawFd};
use crate::DiskTopology;
use thiserror::Error;
use vmm_sys_util::eventfd::EventFd;
use crate::{BatchRequest, DiskTopology};
#[derive(Error, Debug)]
pub enum DiskFileError {
/// Failed getting disk file size.
#[error("Failed getting disk file size")]
#[error("Failed getting disk file size: {0}")]
Size(#[source] std::io::Error),
/// Failed creating a new AsyncIo.
#[error("Failed creating a new AsyncIo")]
#[error("Failed creating a new AsyncIo: {0}")]
NewAsyncIo(#[source] std::io::Error),
}
pub type DiskFileResult<T> = std::result::Result<T, DiskFileError>;
/// A wrapper for [`RawFd`] capturing the lifetime of a corresponding [`DiskFile`].
///
/// This fulfills the same role as [`BorrowedFd`] but is tailored to the limitations
/// by some implementations of [`DiskFile`], which wrap the effective [`File`]
/// in an `Arc<Mutex<T>>`, making the use of [`BorrowedFd`] impossible.
///
/// [`BorrowedFd`]: std::os::fd::BorrowedFd
#[derive(Copy, Clone, Debug)]
pub struct BorrowedDiskFd<'fd> {
raw_fd: RawFd,
_lifetime: PhantomData<&'fd OwnedFd>,
}
impl BorrowedDiskFd<'_> {
pub(super) fn new(raw_fd: RawFd) -> Self {
Self {
raw_fd,
_lifetime: PhantomData,
}
}
}
impl AsRawFd for BorrowedDiskFd<'_> {
fn as_raw_fd(&self) -> RawFd {
self.raw_fd
}
}
/// Abstraction over the effective [`File`] backing up a block device,
/// with support for synchronous and asynchronous I/O.
///
/// This allows abstracting over raw image formats as well as structured
/// image formats.
pub trait DiskFile: Send {
fn size(&mut self) -> DiskFileResult<u64>;
fn new_async_io(&self, ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>>;
fn topology(&mut self) -> DiskTopology {
DiskTopology::default()
}
/// Returns the file descriptor of the underlying disk image file.
///
/// The file descriptor is supposed to be used for `fcntl()` calls but no
/// other operation.
fn fd(&mut self) -> BorrowedDiskFd<'_>;
}
#[derive(Error, Debug)]
pub enum AsyncIoError {
/// Failed vectored reading from file.
#[error("Failed vectored reading from file")]
#[error("Failed vectored reading from file: {0}")]
ReadVectored(#[source] std::io::Error),
/// Failed vectored writing to file.
#[error("Failed vectored writing to file")]
#[error("Failed vectored writing to file: {0}")]
WriteVectored(#[source] std::io::Error),
/// Failed synchronizing file.
#[error("Failed synchronizing file")]
#[error("Failed synchronizing file: {0}")]
Fsync(#[source] std::io::Error),
/// Failed submitting batch requests.
#[error("Failed submitting batch requests: {0}")]
SubmitBatchRequests(#[source] std::io::Error),
}
pub type AsyncIoResult<T> = std::result::Result<T, AsyncIoError>;
@@ -102,10 +57,4 @@ pub trait AsyncIo: Send {
) -> AsyncIoResult<()>;
fn fsync(&mut self, user_data: Option<u64>) -> AsyncIoResult<()>;
fn next_completed_request(&mut self) -> Option<(u64, i32)>;
fn batch_requests_enabled(&self) -> bool {
false
}
fn submit_batch_requests(&mut self, _batch_request: &[BatchRequest]) -> AsyncIoResult<()> {
Ok(())
}
}

View File

@@ -1,173 +0,0 @@
// Copyright © 2025 Cyberus Technology GmbH
//
// SPDX-License-Identifier: Apache-2.0
//
//! Helpers for advisory file locking.
//!
//! Under the hood, the implementation uses OFD locks for the entire file,
//! as described in [[0]]. The advantage over `F_SETLKW` (currently used by
//! Rust std: `File::try_lock()`) is that only the very last `close()` on a
//! file descriptor releases the lock. This prevents mistakes and unexpected
//! behavior.
//!
//! [0]: <https://apenwarr.ca/log/20101213>.
use std::fmt::Debug;
use std::io;
use std::os::fd::{AsRawFd, RawFd};
use thiserror::Error;
/// Errors that can happen when working with file locks.
#[derive(Error, Debug)]
pub enum LockError {
/// The file is already locked.
///
/// A call to [`get_lock_state`] can help to identify the reason.
#[error("The file is already locked")]
AlreadyLocked,
/// IO error.
#[error("The lock state could not be checked or set")]
Io(#[source] io::Error),
}
/// Commands for use with [`fcntl`].
#[allow(non_camel_case_types)]
enum FcntlArg<'a> {
/// Set an OFD lock from the given lock description.
F_OFD_SETLK(&'a libc::flock),
/// Get the first OFD lock for the given lock description.
F_OFD_GETLK(&'a mut libc::flock),
}
/// Wrapper for [`libc::fcntl`] that properly sets the function arguments.
fn fcntl(fd: RawFd, arg: FcntlArg) -> libc::c_int {
// SAFETY: We use a valid FD.
unsafe {
match arg {
FcntlArg::F_OFD_SETLK(flock) => libc::fcntl(fd, libc::F_OFD_SETLK, flock),
FcntlArg::F_OFD_GETLK(flock) => libc::fcntl(fd, libc::F_OFD_GETLK, flock),
}
}
}
/// Describes the type of lock you want to set.
#[derive(Clone, Copy, Debug)]
pub enum LockType {
/// Clear a lock.
Unlock,
/// Set a write lock (exclusive).
Write,
/// Set a read lock (shared).
Read,
}
impl LockType {
pub const fn to_libc_val(self) -> libc::c_int {
match self {
Self::Unlock => libc::F_UNLCK as libc::c_int,
Self::Write => libc::F_WRLCK as libc::c_int,
Self::Read => libc::F_RDLCK as libc::c_int,
}
}
}
/// Describes the current state of a lock.
#[derive(Debug)]
pub enum LockState {
/// No lock set.
Unlocked,
/// Locked for reading (non-exclusive).
SharedRead,
/// Locked for writing (exclusive mode).
ExclusiveWrite,
}
impl LockState {
fn new(value: libc::c_int) -> Self {
const F_UNLCK: libc::c_int = libc::F_UNLCK as libc::c_int;
const F_WRLCK: libc::c_int = libc::F_WRLCK as libc::c_int;
const F_RDLCK: libc::c_int = libc::F_RDLCK as libc::c_int;
match value {
F_UNLCK => Self::Unlocked,
F_WRLCK => Self::ExclusiveWrite,
F_RDLCK => Self::SharedRead,
// This is so unlikely that we want to avoid the complexity of
// coping with this error case. Can only fail if either Linux
// is broken or memory is messed up.
other => panic!("Unexpected lock state: {other}"),
}
}
}
/// Returns a [`struct@libc::flock`] structure for the whole file.
const fn get_flock(lock_type: LockType) -> libc::flock {
libc::flock {
l_type: lock_type.to_libc_val() as libc::c_short,
l_whence: libc::SEEK_SET as libc::c_short,
l_start: 0,
l_len: 0, /* EOF */
l_pid: 0, /* filled by callee */
}
}
/// Tries to acquire a lock using [`fcntl`] with respect to the given
/// parameters.
///
/// Please note that `fcntl()` OFD locks are **advisory locks**, which do not
/// prevent to `open()` a file if a lock is already placed.
///
/// # Parameters
/// - `file`: The file to acquire a lock for [`LockType`]. The file's state will
/// be logically mutated, but not technically.
/// - `lock_type`: The [`LockType`]
pub fn try_acquire_lock<Fd: AsRawFd>(file: Fd, lock_type: LockType) -> Result<(), LockError> {
let flock = get_flock(lock_type);
let res = fcntl(file.as_raw_fd(), FcntlArg::F_OFD_SETLK(&flock));
match res {
0 => Ok(()),
-1 => {
let io_error = io::Error::last_os_error();
let errno = io_error.raw_os_error().unwrap();
match errno {
// See man page for error code:
// <https://man7.org/linux/man-pages/man2/fcntl.2.html>
libc::EAGAIN | libc::EACCES => Err(LockError::AlreadyLocked),
_ => Err(LockError::Io(io_error)),
}
}
val => panic!("Unexpected return value from fcntl(): {val}"),
}
}
/// Clears a lock.
///
/// # Parameters
/// - `file`: The file to clear all locks for [`LockType`].
pub fn clear_lock<Fd: AsRawFd>(file: Fd) -> Result<(), LockError> {
try_acquire_lock(file, LockType::Unlock)
}
/// Returns the current lock state using [`fcntl`] with respect to the given
/// parameters.
///
/// # Parameters
/// - `file`: The file for which to get the lock state.
pub fn get_lock_state<Fd: AsRawFd>(file: Fd) -> Result<LockState, LockError> {
let mut flock = get_flock(LockType::Write);
let res = fcntl(file.as_raw_fd(), FcntlArg::F_OFD_GETLK(&mut flock));
match res {
0 => {
let state = flock.l_type as libc::c_int;
let state = LockState::new(state);
Ok(state)
}
-1 => {
let io_error = io::Error::last_os_error();
Err(LockError::Io(io_error))
}
val => panic!("Unexpected return value from fcntl(): {val}"),
}
}

View File

@@ -2,13 +2,12 @@
//
// SPDX-License-Identifier: Apache-2.0
use crate::vhd::VhdFooter;
use crate::BlockBackend;
use std::fs::File;
use std::io::{Read, Seek, SeekFrom, Write};
use std::os::unix::io::{AsRawFd, RawFd};
use crate::BlockBackend;
use crate::vhd::VhdFooter;
#[derive(Debug)]
pub struct FixedVhd {
file: File,

View File

@@ -2,17 +2,15 @@
//
// SPDX-License-Identifier: Apache-2.0
use std::fs::File;
use std::os::unix::io::{AsRawFd, RawFd};
use vmm_sys_util::eventfd::EventFd;
use crate::async_io::{
AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult,
AsyncIo, AsyncIoError, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult,
};
use crate::fixed_vhd::FixedVhd;
use crate::raw_async::RawFileAsync;
use crate::{BatchRequest, BlockBackend};
use crate::BlockBackend;
use std::fs::File;
use std::os::unix::io::{AsRawFd, RawFd};
use vmm_sys_util::eventfd::EventFd;
pub struct FixedVhdDiskAsync(FixedVhd);
@@ -33,10 +31,6 @@ impl DiskFile for FixedVhdDiskAsync {
.map_err(DiskFileError::NewAsyncIo)?,
) as Box<dyn AsyncIo>)
}
fn fd(&mut self) -> BorrowedDiskFd<'_> {
BorrowedDiskFd::new(self.0.as_raw_fd())
}
}
pub struct FixedVhdAsync {
@@ -106,12 +100,4 @@ impl AsyncIo for FixedVhdAsync {
fn next_completed_request(&mut self) -> Option<(u64, i32)> {
self.raw_file_async.next_completed_request()
}
fn batch_requests_enabled(&self) -> bool {
true
}
fn submit_batch_requests(&mut self, batch_request: &[BatchRequest]) -> AsyncIoResult<()> {
self.raw_file_async.submit_batch_requests(batch_request)
}
}

View File

@@ -2,17 +2,15 @@
//
// SPDX-License-Identifier: Apache-2.0
use std::fs::File;
use std::os::unix::io::{AsRawFd, RawFd};
use vmm_sys_util::eventfd::EventFd;
use crate::BlockBackend;
use crate::async_io::{
AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult,
AsyncIo, AsyncIoError, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult,
};
use crate::fixed_vhd::FixedVhd;
use crate::raw_sync::RawFileSync;
use crate::BlockBackend;
use std::fs::File;
use std::os::unix::io::{AsRawFd, RawFd};
use vmm_sys_util::eventfd::EventFd;
pub struct FixedVhdDiskSync(FixedVhd);
@@ -33,10 +31,6 @@ impl DiskFile for FixedVhdDiskSync {
.map_err(DiskFileError::NewAsyncIo)?,
) as Box<dyn AsyncIo>)
}
fn fd(&mut self) -> BorrowedDiskFd<'_> {
BorrowedDiskFd::new(self.0.as_raw_fd())
}
}
pub struct FixedVhdSync {

View File

@@ -12,7 +12,6 @@
extern crate log;
pub mod async_io;
pub mod fcntl;
pub mod fixed_vhd;
#[cfg(feature = "io_uring")]
/// Enabled with the `"io_uring"` feature
@@ -31,36 +30,43 @@ pub mod vhd;
pub mod vhdx;
pub mod vhdx_sync;
use std::alloc::{Layout, alloc_zeroed, dealloc};
use crate::async_io::{AsyncIo, AsyncIoError, AsyncIoResult};
use crate::fixed_vhd::FixedVhd;
use crate::qcow::{QcowFile, RawFile};
use crate::vhdx::{Vhdx, VhdxError};
#[cfg(feature = "io_uring")]
use io_uring::{opcode, IoUring, Probe};
use libc::{ioctl, S_IFBLK, S_IFMT};
use smallvec::SmallVec;
use std::alloc::{alloc_zeroed, dealloc, Layout};
use std::cmp;
use std::collections::VecDeque;
use std::convert::TryInto;
use std::fmt::Debug;
use std::fs::File;
use std::io::{self, IoSlice, IoSliceMut, Read, Seek, SeekFrom, Write};
use std::os::linux::fs::MetadataExt;
use std::os::unix::io::AsRawFd;
use std::path::Path;
use std::result;
use std::sync::Arc;
use std::sync::MutexGuard;
use std::time::Instant;
use std::{cmp, result};
#[cfg(feature = "io_uring")]
use io_uring::{IoUring, Probe, opcode};
use libc::{S_IFBLK, S_IFMT, ioctl};
use serde::{Deserialize, Serialize};
use smallvec::SmallVec;
use thiserror::Error;
use versionize::{VersionMap, Versionize, VersionizeResult};
use versionize_derive::Versionize;
use virtio_bindings::virtio_blk::*;
use virtio_queue::DescriptorChain;
use vm_memory::bitmap::Bitmap;
use vm_memory::{
ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryError, GuestMemoryLoadGuard,
bitmap::AtomicBitmap, bitmap::Bitmap, ByteValued, Bytes, GuestAddress, GuestMemory,
GuestMemoryError, GuestMemoryLoadGuard,
};
use vm_virtio::{AccessPlatform, Translatable};
use vmm_sys_util::aio;
use vmm_sys_util::eventfd::EventFd;
use vmm_sys_util::{aio, ioctl_io_nr};
use vmm_sys_util::{ioctl_io_nr, ioctl_ioc_nr};
use crate::async_io::{AsyncIo, AsyncIoError, AsyncIoResult};
use crate::vhdx::VhdxError;
type GuestMemoryMmap = vm_memory::GuestMemoryMmap<AtomicBitmap>;
const SECTOR_SHIFT: u8 = 9;
pub const SECTOR_SIZE: u64 = 0x01 << SECTOR_SHIFT;
@@ -68,9 +74,9 @@ pub const SECTOR_SIZE: u64 = 0x01 << SECTOR_SHIFT;
#[derive(Error, Debug)]
pub enum Error {
#[error("Guest gave us bad memory addresses")]
GuestMemory(#[source] GuestMemoryError),
GuestMemory(GuestMemoryError),
#[error("Guest gave us offsets that would have overflowed a usize")]
CheckedOffset(GuestAddress, usize /* sector offset */),
CheckedOffset(GuestAddress, usize),
#[error("Guest gave us a write only descriptor that protocol says to read from")]
UnexpectedWriteOnlyDescriptor,
#[error("Guest gave us a read only descriptor that protocol says to write to")]
@@ -79,22 +85,22 @@ pub enum Error {
DescriptorChainTooShort,
#[error("Guest gave us a descriptor that was too short to use")]
DescriptorLengthTooSmall,
#[error("Failed to detect image type")]
DetectImageType(#[source] std::io::Error),
#[error("Failure in fixed vhd")]
FixedVhdError(#[source] std::io::Error),
#[error("Failed to detect image type: {0}")]
DetectImageType(std::io::Error),
#[error("Failure in fixed vhd: {0}")]
FixedVhdError(std::io::Error),
#[error("Getting a block's metadata fails for any reason")]
GetFileMetadata,
#[error("The requested operation would cause a seek beyond disk end")]
InvalidOffset,
#[error("Failure in qcow")]
QcowError(#[source] qcow::Error),
#[error("Failure in raw file")]
RawFileError(#[source] std::io::Error),
#[error("Failure in qcow: {0}")]
QcowError(qcow::Error),
#[error("Failure in raw file: {0}")]
RawFileError(std::io::Error),
#[error("The requested operation does not support multiple descriptors")]
TooManyDescriptors,
#[error("Failure in vhdx")]
VhdxError(#[source] VhdxError),
#[error("Failure in vhdx: {0}")]
VhdxError(VhdxError),
}
fn build_device_id(disk_path: &Path) -> result::Result<String, Error> {
@@ -131,46 +137,43 @@ pub fn build_serial(disk_path: &Path) -> Vec<u8> {
#[derive(Error, Debug)]
pub enum ExecuteError {
#[error("Bad request")]
BadRequest(#[source] Error),
#[error("Failed to flush")]
Flush(#[source] io::Error),
#[error("Failed to read")]
Read(#[source] GuestMemoryError),
#[error("Failed to read_exact")]
ReadExact(#[source] io::Error),
#[error("Can't execute an operation other than `read` on a read-only device")]
ReadOnly,
#[error("Failed to seek")]
Seek(#[source] io::Error),
#[error("Failed to write")]
Write(#[source] GuestMemoryError),
#[error("Failed to write_all")]
WriteAll(#[source] io::Error),
#[error("Bad request: {0}")]
BadRequest(Error),
#[error("Failed to flush: {0}")]
Flush(io::Error),
#[error("Failed to read: {0}")]
Read(GuestMemoryError),
#[error("Failed to read_exact: {0}")]
ReadExact(io::Error),
#[error("Failed to seek: {0}")]
Seek(io::Error),
#[error("Failed to write: {0}")]
Write(GuestMemoryError),
#[error("Failed to write_all: {0}")]
WriteAll(io::Error),
#[error("Unsupported request: {0}")]
Unsupported(u32),
#[error("Failed to submit io uring")]
SubmitIoUring(#[source] io::Error),
#[error("Failed to get guest address")]
GetHostAddress(#[source] GuestMemoryError),
#[error("Failed to async read")]
AsyncRead(#[source] AsyncIoError),
#[error("Failed to async write")]
AsyncWrite(#[source] AsyncIoError),
#[error("failed to async flush")]
AsyncFlush(#[source] AsyncIoError),
#[error("Failed allocating a temporary buffer")]
TemporaryBufferAllocation(#[source] io::Error),
#[error("Failed to submit io uring: {0}")]
SubmitIoUring(io::Error),
#[error("Failed to get guest address: {0}")]
GetHostAddress(GuestMemoryError),
#[error("Failed to async read: {0}")]
AsyncRead(AsyncIoError),
#[error("Failed to async write: {0}")]
AsyncWrite(AsyncIoError),
#[error("failed to async flush: {0}")]
AsyncFlush(AsyncIoError),
#[error("Failed allocating a temporary buffer: {0}")]
TemporaryBufferAllocation(io::Error),
}
impl ExecuteError {
pub fn status(&self) -> u8 {
let status = match *self {
pub fn status(&self) -> u32 {
match *self {
ExecuteError::BadRequest(_) => VIRTIO_BLK_S_IOERR,
ExecuteError::Flush(_) => VIRTIO_BLK_S_IOERR,
ExecuteError::Read(_) => VIRTIO_BLK_S_IOERR,
ExecuteError::ReadExact(_) => VIRTIO_BLK_S_IOERR,
ExecuteError::ReadOnly => VIRTIO_BLK_S_IOERR,
ExecuteError::Seek(_) => VIRTIO_BLK_S_IOERR,
ExecuteError::Write(_) => VIRTIO_BLK_S_IOERR,
ExecuteError::WriteAll(_) => VIRTIO_BLK_S_IOERR,
@@ -181,8 +184,7 @@ impl ExecuteError {
ExecuteError::AsyncWrite(_) => VIRTIO_BLK_S_IOERR,
ExecuteError::AsyncFlush(_) => VIRTIO_BLK_S_IOERR,
ExecuteError::TemporaryBufferAllocation(_) => VIRTIO_BLK_S_IOERR,
};
status as u8
}
}
}
@@ -195,8 +197,8 @@ pub enum RequestType {
Unsupported(u32),
}
pub fn request_type<B: Bitmap + 'static>(
mem: &vm_memory::GuestMemoryMmap<B>,
pub fn request_type(
mem: &GuestMemoryMmap,
desc_addr: GuestAddress,
) -> result::Result<RequestType, Error> {
let type_ = mem.read_obj(desc_addr).map_err(Error::GuestMemory)?;
@@ -209,10 +211,7 @@ pub fn request_type<B: Bitmap + 'static>(
}
}
fn sector<B: Bitmap + 'static>(
mem: &vm_memory::GuestMemoryMmap<B>,
desc_addr: GuestAddress,
) -> result::Result<u64, Error> {
fn sector(mem: &GuestMemoryMmap, desc_addr: GuestAddress) -> result::Result<u64, Error> {
const SECTOR_OFFSET: usize = 8;
let addr = match mem.checked_offset(desc_addr, SECTOR_OFFSET) {
Some(v) => v,
@@ -222,8 +221,6 @@ fn sector<B: Bitmap + 'static>(
mem.read_obj(addr).map_err(Error::GuestMemory)
}
const DEFAULT_DESCRIPTOR_VEC_SIZE: usize = 32;
#[derive(Debug)]
pub struct AlignedOperation {
origin_ptr: u64,
@@ -232,41 +229,28 @@ pub struct AlignedOperation {
layout: Layout,
}
pub struct BatchRequest {
pub offset: libc::off_t,
pub iovecs: SmallVec<[libc::iovec; DEFAULT_DESCRIPTOR_VEC_SIZE]>,
pub user_data: u64,
pub request_type: RequestType,
}
pub struct ExecuteAsync {
// `true` if the execution will complete asynchronously
pub async_complete: bool,
// request need to be batched for submission if any
pub batch_request: Option<BatchRequest>,
}
#[derive(Debug)]
pub struct Request {
pub request_type: RequestType,
pub sector: u64,
pub data_descriptors: SmallVec<[(GuestAddress, u32); DEFAULT_DESCRIPTOR_VEC_SIZE]>,
pub data_descriptors: SmallVec<[(GuestAddress, u32); 1]>,
pub status_addr: GuestAddress,
pub writeback: bool,
pub aligned_operations: SmallVec<[AlignedOperation; DEFAULT_DESCRIPTOR_VEC_SIZE]>,
pub aligned_operations: SmallVec<[AlignedOperation; 1]>,
pub start: Instant,
}
impl Request {
pub fn parse<B: Bitmap + 'static>(
desc_chain: &mut DescriptorChain<GuestMemoryLoadGuard<vm_memory::GuestMemoryMmap<B>>>,
pub fn parse(
desc_chain: &mut DescriptorChain<GuestMemoryLoadGuard<GuestMemoryMmap>>,
access_platform: Option<&Arc<dyn AccessPlatform>>,
) -> result::Result<Request, Error> {
let hdr_desc = desc_chain
.next()
.ok_or(Error::DescriptorChainTooShort)
.inspect_err(|_| {
.map_err(|e| {
error!("Missing head descriptor");
e
})?;
// The head contains the request type which MUST be readable.
@@ -281,10 +265,10 @@ impl Request {
let mut req = Request {
request_type: request_type(desc_chain.memory(), hdr_desc_addr)?,
sector: sector(desc_chain.memory(), hdr_desc_addr)?,
data_descriptors: SmallVec::with_capacity(DEFAULT_DESCRIPTOR_VEC_SIZE),
data_descriptors: SmallVec::with_capacity(1),
status_addr: GuestAddress(0),
writeback: true,
aligned_operations: SmallVec::with_capacity(DEFAULT_DESCRIPTOR_VEC_SIZE),
aligned_operations: SmallVec::with_capacity(1),
start: Instant::now(),
};
@@ -292,8 +276,9 @@ impl Request {
let mut desc = desc_chain
.next()
.ok_or(Error::DescriptorChainTooShort)
.inspect_err(|_| {
.map_err(|e| {
error!("Only head descriptor present: request = {:?}", req);
e
})?;
if !desc.has_next() {
@@ -324,8 +309,9 @@ impl Request {
desc = desc_chain
.next()
.ok_or(Error::DescriptorChainTooShort)
.inspect_err(|_| {
.map_err(|e| {
error!("DescriptorChain corrupted: request = {:?}", req);
e
})?;
}
status_desc = desc;
@@ -347,11 +333,11 @@ impl Request {
Ok(req)
}
pub fn execute<T: Seek + Read + Write, B: Bitmap + 'static>(
pub fn execute<T: Seek + Read + Write>(
&self,
disk: &mut T,
disk_nsectors: u64,
mem: &vm_memory::GuestMemoryMmap<B>,
mem: &GuestMemoryMmap,
serial: &[u8],
) -> result::Result<u32, ExecuteError> {
disk.seek(SeekFrom::Start(self.sector << SECTOR_SHIFT))
@@ -404,34 +390,28 @@ impl Request {
Ok(len)
}
pub fn execute_async<B: Bitmap + 'static>(
pub fn execute_async(
&mut self,
mem: &vm_memory::GuestMemoryMmap<B>,
mem: &GuestMemoryMmap,
disk_nsectors: u64,
disk_image: &mut dyn AsyncIo,
serial: &[u8],
user_data: u64,
) -> result::Result<ExecuteAsync, ExecuteError> {
) -> result::Result<bool, ExecuteError> {
let sector = self.sector;
let request_type = self.request_type;
let offset = (sector << SECTOR_SHIFT) as libc::off_t;
let mut iovecs: SmallVec<[libc::iovec; DEFAULT_DESCRIPTOR_VEC_SIZE]> =
let mut iovecs: SmallVec<[libc::iovec; 1]> =
SmallVec::with_capacity(self.data_descriptors.len());
for &(data_addr, data_len) in &self.data_descriptors {
let _: u32 = data_len; // compiler-checked documentation
const _: () = assert!(
core::mem::size_of::<u32>() <= core::mem::size_of::<usize>(),
"unsupported platform"
);
if data_len == 0 {
for (data_addr, data_len) in &self.data_descriptors {
if *data_len == 0 {
continue;
}
let mut top: u64 = u64::from(data_len) / SECTOR_SIZE;
if u64::from(data_len) % SECTOR_SIZE != 0 {
let mut top: u64 = u64::from(*data_len) / SECTOR_SIZE;
if u64::from(*data_len) % SECTOR_SIZE != 0 {
top += 1;
}
let data_len = data_len as usize;
top = top
.checked_add(sector)
.ok_or(ExecuteError::BadRequest(Error::InvalidOffset))?;
@@ -440,7 +420,7 @@ impl Request {
}
let origin_ptr = mem
.get_slice(data_addr, data_len)
.get_slice(*data_addr, *data_len as usize)
.map_err(ExecuteError::GetHostAddress)?
.ptr_guard();
@@ -448,8 +428,9 @@ impl Request {
// In case it's not properly aligned, an intermediate buffer is
// created with the correct alignment, and a copy from/to the
// origin buffer is performed, depending on the type of operation.
let iov_base = if !(origin_ptr.as_ptr() as u64).is_multiple_of(SECTOR_SIZE) {
let layout = Layout::from_size_align(data_len, SECTOR_SIZE as usize).unwrap();
let iov_base = if (origin_ptr.as_ptr() as u64) % SECTOR_SIZE != 0 {
let layout =
Layout::from_size_align(*data_len as usize, SECTOR_SIZE as usize).unwrap();
// SAFETY: layout has non-zero size
let aligned_ptr = unsafe { alloc_zeroed(layout) };
if aligned_ptr.is_null() {
@@ -463,7 +444,7 @@ impl Request {
if request_type == RequestType::Out {
// SAFETY: destination buffer has been allocated with
// the proper size.
unsafe { std::ptr::copy(origin_ptr.as_ptr(), aligned_ptr, data_len) };
unsafe { std::ptr::copy(origin_ptr.as_ptr(), aligned_ptr, *data_len as usize) };
}
// Store both origin and aligned pointers for complete_async()
@@ -471,7 +452,7 @@ impl Request {
self.aligned_operations.push(AlignedOperation {
origin_ptr: origin_ptr.as_ptr() as u64,
aligned_ptr: aligned_ptr as u64,
size: data_len,
size: *data_len as usize,
layout,
});
@@ -482,15 +463,11 @@ impl Request {
let iovec = libc::iovec {
iov_base,
iov_len: data_len as libc::size_t,
iov_len: *data_len as libc::size_t,
};
iovecs.push(iovec);
}
let mut ret = ExecuteAsync {
async_complete: true,
batch_request: None,
};
// Queue operations expected to be submitted.
match request_type {
RequestType::In => {
@@ -500,32 +477,14 @@ impl Request {
.bitmap()
.mark_dirty(0, *data_len as usize);
}
if disk_image.batch_requests_enabled() {
ret.batch_request = Some(BatchRequest {
offset,
iovecs,
user_data,
request_type,
});
} else {
disk_image
.read_vectored(offset, &iovecs, user_data)
.map_err(ExecuteError::AsyncRead)?;
}
disk_image
.read_vectored(offset, &iovecs, user_data)
.map_err(ExecuteError::AsyncRead)?;
}
RequestType::Out => {
if disk_image.batch_requests_enabled() {
ret.batch_request = Some(BatchRequest {
offset,
iovecs,
user_data,
request_type,
});
} else {
disk_image
.write_vectored(offset, &iovecs, user_data)
.map_err(ExecuteError::AsyncWrite)?;
}
disk_image
.write_vectored(offset, &iovecs, user_data)
.map_err(ExecuteError::AsyncWrite)?;
}
RequestType::Flush => {
disk_image
@@ -543,13 +502,12 @@ impl Request {
}
mem.write_slice(serial, data_addr)
.map_err(ExecuteError::Write)?;
ret.async_complete = false;
return Ok(ret);
return Ok(false);
}
RequestType::Unsupported(t) => return Err(ExecuteError::Unsupported(t)),
}
Ok(ret)
Ok(true)
}
pub fn complete_async(&mut self) -> result::Result<(), Error> {
@@ -587,7 +545,7 @@ impl Request {
}
}
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize)]
#[derive(Copy, Clone, Debug, Default, Versionize)]
#[repr(C, packed)]
pub struct VirtioBlockConfig {
pub capacity: u64,
@@ -610,7 +568,7 @@ pub struct VirtioBlockConfig {
pub write_zeroes_may_unmap: u8,
pub unused1: [u8; 3],
}
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize)]
#[derive(Copy, Clone, Debug, Default, Versionize)]
#[repr(C, packed)]
pub struct VirtioBlockGeometry {
pub cylinders: u16,
@@ -686,7 +644,10 @@ pub fn block_io_uring_is_supported() -> bool {
}
}
pub trait AsyncAdaptor {
pub trait AsyncAdaptor<F>
where
F: Read + Write + Seek,
{
fn read_vectored_sync(
&mut self,
offset: libc::off_t,
@@ -694,30 +655,24 @@ pub trait AsyncAdaptor {
user_data: u64,
eventfd: &EventFd,
completion_list: &mut VecDeque<(u64, i32)>,
) -> AsyncIoResult<()>
where
Self: Read + Seek,
{
) -> AsyncIoResult<()> {
// Convert libc::iovec into IoSliceMut
let mut slices: SmallVec<[IoSliceMut; DEFAULT_DESCRIPTOR_VEC_SIZE]> =
SmallVec::with_capacity(iovecs.len());
let mut slices: SmallVec<[IoSliceMut; 1]> = SmallVec::with_capacity(iovecs.len());
for iovec in iovecs.iter() {
// SAFETY: on Linux IoSliceMut wraps around libc::iovec
slices.push(IoSliceMut::new(unsafe {
std::mem::transmute::<libc::iovec, &mut [u8]>(*iovec)
}));
slices.push(IoSliceMut::new(unsafe { std::mem::transmute(*iovec) }));
}
let result = {
let mut file = self.file();
// Move the cursor to the right offset
self.seek(SeekFrom::Start(offset as u64))
file.seek(SeekFrom::Start(offset as u64))
.map_err(AsyncIoError::ReadVectored)?;
let mut r = 0;
for b in slices.iter_mut() {
r += self.read(b).map_err(AsyncIoError::ReadVectored)?;
}
r
// Read vectored
file.read_vectored(slices.as_mut_slice())
.map_err(AsyncIoError::ReadVectored)?
};
completion_list.push_back((user_data, result as i32));
@@ -733,30 +688,24 @@ pub trait AsyncAdaptor {
user_data: u64,
eventfd: &EventFd,
completion_list: &mut VecDeque<(u64, i32)>,
) -> AsyncIoResult<()>
where
Self: Write + Seek,
{
) -> AsyncIoResult<()> {
// Convert libc::iovec into IoSlice
let mut slices: SmallVec<[IoSlice; DEFAULT_DESCRIPTOR_VEC_SIZE]> =
SmallVec::with_capacity(iovecs.len());
let mut slices: SmallVec<[IoSlice; 1]> = SmallVec::with_capacity(iovecs.len());
for iovec in iovecs.iter() {
// SAFETY: on Linux IoSlice wraps around libc::iovec
slices.push(IoSlice::new(unsafe {
std::mem::transmute::<libc::iovec, &mut [u8]>(*iovec)
}));
slices.push(IoSlice::new(unsafe { std::mem::transmute(*iovec) }));
}
let result = {
let mut file = self.file();
// Move the cursor to the right offset
self.seek(SeekFrom::Start(offset as u64))
file.seek(SeekFrom::Start(offset as u64))
.map_err(AsyncIoError::WriteVectored)?;
let mut r = 0;
for b in slices.iter() {
r += self.write(b).map_err(AsyncIoError::WriteVectored)?;
}
r
// Write vectored
file.write_vectored(slices.as_slice())
.map_err(AsyncIoError::WriteVectored)?
};
completion_list.push_back((user_data, result as i32));
@@ -770,13 +719,12 @@ pub trait AsyncAdaptor {
user_data: Option<u64>,
eventfd: &EventFd,
completion_list: &mut VecDeque<(u64, i32)>,
) -> AsyncIoResult<()>
where
Self: Write,
{
) -> AsyncIoResult<()> {
let result: i32 = {
let mut file = self.file();
// Flush
self.flush().map_err(AsyncIoError::Fsync)?;
file.flush().map_err(AsyncIoError::Fsync)?;
0
};
@@ -788,6 +736,8 @@ pub trait AsyncAdaptor {
Ok(())
}
fn file(&mut self) -> MutexGuard<F>;
}
pub enum ImageType {
@@ -839,6 +789,25 @@ pub trait BlockBackend: Read + Write + Seek + Send + Debug {
fn size(&self) -> Result<u64, Error>;
}
/// Inspect the image file type and create an appropriate disk file to match it.
pub fn create_disk_file(mut file: File, direct_io: bool) -> Result<Box<dyn BlockBackend>, Error> {
let image_type = detect_image_type(&mut file).map_err(Error::DetectImageType)?;
Ok(match image_type {
ImageType::Qcow2 => {
Box::new(QcowFile::from(RawFile::new(file, direct_io)).map_err(Error::QcowError)?)
as Box<dyn BlockBackend>
}
ImageType::FixedVhd => {
Box::new(FixedVhd::new(file).map_err(Error::FixedVhdError)?) as Box<dyn BlockBackend>
}
ImageType::Vhdx => {
Box::new(Vhdx::new(file).map_err(Error::VhdxError)?) as Box<dyn BlockBackend>
}
ImageType::Raw => Box::new(RawFile::new(file, direct_io)) as Box<dyn BlockBackend>,
})
}
#[derive(Debug)]
pub struct DiskTopology {
pub logical_block_size: u64,

View File

@@ -1,127 +1,134 @@
// Copyright 2018 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE-BSD-3-Clause file.
//
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
mod qcow_raw_file;
mod raw_file;
mod refcount;
mod vec_cache;
use std::cmp::{max, min};
use std::fs::OpenOptions;
use std::io::{self, Read, Seek, SeekFrom, Write};
use std::mem::size_of;
use std::os::fd::{AsRawFd, RawFd};
use std::str;
use crate::qcow::{
qcow_raw_file::QcowRawFile,
refcount::RefCount,
vec_cache::{CacheMap, Cacheable, VecCache},
};
use crate::BlockBackend;
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use libc::{EINVAL, ENOSPC, ENOTSUP};
use remain::sorted;
use thiserror::Error;
use vmm_sys_util::file_traits::{FileSetLen, FileSync};
use vmm_sys_util::seek_hole::SeekHole;
use vmm_sys_util::write_zeroes::{PunchHole, WriteZeroesAt};
use std::cmp::{max, min};
use std::fmt::{self, Display};
use std::fs::OpenOptions;
use std::io::{self, Read, Seek, SeekFrom, Write};
use std::mem::size_of;
use std::str;
use vmm_sys_util::{
file_traits::FileSetLen, file_traits::FileSync, seek_hole::SeekHole, write_zeroes::PunchHole,
write_zeroes::WriteZeroesAt,
};
use crate::BlockBackend;
use crate::qcow::qcow_raw_file::QcowRawFile;
pub use crate::qcow::raw_file::RawFile;
use crate::qcow::refcount::RefCount;
use crate::qcow::vec_cache::{CacheMap, Cacheable, VecCache};
/// Nesting depth limit for disk formats that can open other disk files.
const MAX_NESTING_DEPTH: u32 = 10;
#[sorted]
#[derive(Debug, Error)]
#[derive(Debug)]
pub enum Error {
#[error("Backing file io error")]
BackingFileIo(#[source] io::Error),
#[error("Backing file open error")]
BackingFileOpen(#[source] Box<Error>),
#[error("Backing file name is too long: {0} bytes over")]
BackingFileIo(io::Error),
BackingFileOpen(Box<crate::Error>),
BackingFileTooLong(usize),
#[error("Compressed blocks not supported")]
CompressedBlocksNotSupported,
#[error("Failed to evict cache")]
EvictingCache(#[source] io::Error),
#[error("File larger than max of {MAX_QCOW_FILE_SIZE}: {0}")]
EvictingCache(io::Error),
FileTooBig(u64),
#[error("Failed to get file size")]
GettingFileSize(#[source] io::Error),
#[error("Failed to get refcount")]
GettingRefcount(#[source] refcount::Error),
#[error("Failed to parse filename")]
InvalidBackingFileName(#[source] str::Utf8Error),
#[error("Invalid cluster index")]
GettingFileSize(io::Error),
GettingRefcount(refcount::Error),
InvalidBackingFileName(str::Utf8Error),
InvalidClusterIndex,
#[error("Invalid cluster size")]
InvalidClusterSize,
#[error("Invalid index")]
InvalidIndex,
#[error("Invalid L1 table offset")]
InvalidL1TableOffset,
#[error("Invalid L1 table size: {0}")]
InvalidL1TableSize(u32),
#[error("Invalid magic")]
InvalidMagic,
#[error("Invalid offset: {0}")]
InvalidOffset(u64),
#[error("Invalid refcount table offset")]
InvalidRefcountTableOffset,
#[error("Invalid refcount table size: {0}")]
InvalidRefcountTableSize(u64),
#[error("Maximum disk nesting depth exceeded")]
MaxNestingDepthExceeded,
#[error("No free clusters")]
NoFreeClusters,
#[error("No refcount clusters")]
NoRefcountClusters,
#[error("Not enough space for refcounts")]
NotEnoughSpaceForRefcounts,
#[error("Failed to open file {0}")]
OpeningFile(#[source] io::Error),
#[error("Failed to read data")]
ReadingData(#[source] io::Error),
#[error("Failed to read header")]
ReadingHeader(#[source] io::Error),
#[error("Failed to read pointers")]
ReadingPointers(#[source] io::Error),
#[error("Failed to read ref count block")]
ReadingRefCountBlock(#[source] refcount::Error),
#[error("Failed to read ref counts")]
ReadingRefCounts(#[source] io::Error),
#[error("Failed to rebuild ref counts")]
RebuildingRefCounts(#[source] io::Error),
#[error("Refcount table offset past file end")]
OpeningFile(io::Error),
ReadingData(io::Error),
ReadingHeader(io::Error),
ReadingPointers(io::Error),
ReadingRefCountBlock(refcount::Error),
ReadingRefCounts(io::Error),
RebuildingRefCounts(io::Error),
RefcountTableOffEnd,
#[error("Too many clusters specified for refcount")]
RefcountTableTooLarge,
#[error("Failed to seek file")]
SeekingFile(#[source] io::Error),
#[error("Failed to set file size")]
SettingFileSize(#[source] io::Error),
#[error("Failed to set refcount refcount")]
SettingRefcountRefcount(#[source] io::Error),
#[error("Size too small for number of clusters")]
SeekingFile(io::Error),
SettingFileSize(io::Error),
SettingRefcountRefcount(io::Error),
SizeTooSmallForNumberOfClusters,
#[error("L1 entry table too large: {0}")]
TooManyL1Entries(u64),
#[error("Ref count table too large: {0}")]
TooManyRefcounts(u64),
#[error("Unsupported refcount order")]
UnsupportedRefcountOrder,
#[error("Unsupported version: {0}")]
UnsupportedVersion(u32),
#[error("Failed to write data")]
WritingData(#[source] io::Error),
#[error("Failed to write header")]
WritingHeader(#[source] io::Error),
WritingData(io::Error),
WritingHeader(io::Error),
}
pub type Result<T> = std::result::Result<T, Error>;
impl Display for Error {
#[remain::check]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::Error::*;
#[sorted]
match self {
BackingFileIo(e) => write!(f, "backing file io error: {}", e),
BackingFileOpen(e) => write!(f, "backing file open error: {}", *e),
BackingFileTooLong(len) => {
write!(f, "backing file name is too long: {} bytes over", len)
}
CompressedBlocksNotSupported => write!(f, "compressed blocks not supported"),
EvictingCache(e) => write!(f, "failed to evict cache: {e}"),
FileTooBig(size) => write!(f, "file larger than max of {MAX_QCOW_FILE_SIZE}: {size}"),
GettingFileSize(e) => write!(f, "failed to get file size: {e}"),
GettingRefcount(e) => write!(f, "failed to get refcount: {e}"),
InvalidBackingFileName(e) => write!(f, "failed to parse filename: {}", e),
InvalidClusterIndex => write!(f, "invalid cluster index"),
InvalidClusterSize => write!(f, "invalid cluster size"),
InvalidIndex => write!(f, "invalid index"),
InvalidL1TableOffset => write!(f, "invalid L1 table offset"),
InvalidL1TableSize(size) => write!(f, "invalid L1 table size {size}"),
InvalidMagic => write!(f, "invalid magic"),
InvalidOffset(_) => write!(f, "invalid offset"),
InvalidRefcountTableOffset => write!(f, "invalid refcount table offset"),
InvalidRefcountTableSize(size) => write!(f, "invalid refcount table size: {size}"),
NoFreeClusters => write!(f, "no free clusters"),
NoRefcountClusters => write!(f, "no refcount clusters"),
NotEnoughSpaceForRefcounts => write!(f, "not enough space for refcounts"),
OpeningFile(e) => write!(f, "failed to open file: {e}"),
ReadingData(e) => write!(f, "failed to read data: {e}"),
ReadingHeader(e) => write!(f, "failed to read header: {e}"),
ReadingPointers(e) => write!(f, "failed to read pointers: {e}"),
ReadingRefCountBlock(e) => write!(f, "failed to read ref count block: {e}"),
ReadingRefCounts(e) => write!(f, "failed to read ref counts: {e}"),
RebuildingRefCounts(e) => write!(f, "failed to rebuild ref counts: {e}"),
RefcountTableOffEnd => write!(f, "refcount table offset past file end"),
RefcountTableTooLarge => write!(f, "too many clusters specified for refcount table"),
SeekingFile(e) => write!(f, "failed to seek file: {e}"),
SettingFileSize(e) => write!(f, "failed to set file size: {e}"),
SettingRefcountRefcount(e) => write!(f, "failed to set refcount refcount: {e}"),
SizeTooSmallForNumberOfClusters => write!(f, "size too small for number of clusters"),
TooManyL1Entries(count) => write!(f, "l1 entry table too large: {count}"),
TooManyRefcounts(count) => write!(f, "ref count table too large: {count}"),
UnsupportedRefcountOrder => write!(f, "unsupported refcount order"),
UnsupportedVersion(v) => write!(f, "unsupported version: {v}"),
WritingData(e) => write!(f, "failed to write data: {e}"),
WritingHeader(e) => write!(f, "failed to write header: {e}"),
}
}
}
pub enum ImageType {
Raw,
Qcow2,
@@ -287,12 +294,11 @@ impl QcowHeader {
let cluster_bits: u32 = DEFAULT_CLUSTER_BITS;
let cluster_size: u32 = 0x01 << cluster_bits;
let max_length: usize = (cluster_size - header_size) as usize;
if let Some(path) = backing_file
&& path.len() > max_length
{
return Err(Error::BackingFileTooLong(path.len() - max_length));
if let Some(path) = backing_file {
if path.len() > max_length {
return Err(Error::BackingFileTooLong(path.len() - max_length));
}
}
// L2 blocks are always one cluster long. They contain cluster_size/sizeof(u64) addresses.
let entries_per_cluster: u32 = cluster_size / size_of::<u64>() as u32;
let num_clusters: u32 = div_round_up_u64(size, u64::from(cluster_size)) as u32;
@@ -380,7 +386,7 @@ impl QcowHeader {
}
if let Some(backing_file_path) = self.backing_file_path.as_ref() {
write!(file, "{backing_file_path}").map_err(Error::WritingHeader)?;
write!(file, "{}", backing_file_path).map_err(Error::WritingHeader)?;
}
// Set the file length by seeking and writing a zero to the last byte. This avoids needing
@@ -426,7 +432,7 @@ fn max_refcount_clusters(refcount_order: u32, cluster_size: u32, num_clusters: u
/// # Ok(())
/// # }
/// ```
#[derive(Clone, Debug)]
#[derive(Debug)]
pub struct QcowFile {
raw_file: QcowRawFile,
header: QcowHeader,
@@ -439,20 +445,12 @@ pub struct QcowFile {
// List of unreferenced clusters available to be used. unref clusters become available once the
// removal of references to them have been synced to disk.
avail_clusters: Vec<u64>,
backing_file: Option<Box<Self>>,
backing_file: Option<Box<dyn BlockBackend>>,
}
impl QcowFile {
/// Creates a QcowFile from `file`. File must be a valid qcow2 image.
///
/// Additionally, max nesting depth of this qcow2 image will be set to default value 10.
pub fn from(file: RawFile) -> Result<QcowFile> {
Self::from_with_nesting_depth(file, MAX_NESTING_DEPTH)
}
/// Creates a QcowFile from `file` and with a max nesting depth. File must be a valid qcow2
/// image.
pub fn from_with_nesting_depth(mut file: RawFile, max_nesting_depth: u32) -> Result<QcowFile> {
pub fn from(mut file: RawFile) -> Result<QcowFile> {
let header = QcowHeader::new(&mut file)?;
// Only v2 and v3 files are supported.
@@ -479,20 +477,14 @@ impl QcowFile {
let direct_io = file.is_direct();
let backing_file = if let Some(backing_file_path) = header.backing_file_path.as_ref() {
if max_nesting_depth == 0 {
return Err(Error::MaxNestingDepthExceeded);
}
let path = backing_file_path.clone();
let backing_raw_file = OpenOptions::new()
.read(true)
.open(path)
.map_err(Error::BackingFileIo)?;
let backing_file = Self::from_with_nesting_depth(
RawFile::new(backing_raw_file, direct_io),
max_nesting_depth - 1,
)
.map_err(|e| Error::BackingFileOpen(Box::new(e)))?;
Some(Box::new(backing_file))
let backing_file = crate::create_disk_file(backing_raw_file, direct_io)
.map_err(|e| Error::BackingFileOpen(Box::new(e)))?;
Some(backing_file)
} else {
None
};
@@ -504,7 +496,7 @@ impl QcowFile {
if refcount_bits != 16 {
return Err(Error::UnsupportedRefcountOrder);
}
let refcount_bytes = refcount_bits.div_ceil(8);
let refcount_bytes = (refcount_bits + 7) / 8;
// Need at least one refcount cluster
if header.refcount_table_clusters == 0 {
@@ -590,12 +582,14 @@ impl QcowFile {
// Check for compressed blocks
for l2_addr_disk in l1_table.get_values() {
if *l2_addr_disk != 0
&& let Err(e) = Self::read_l2_cluster(&mut raw_file, *l2_addr_disk)
&& let Some(os_error) = e.raw_os_error()
&& os_error == ENOTSUP
{
return Err(Error::CompressedBlocksNotSupported);
if *l2_addr_disk != 0 {
if let Err(e) = Self::read_l2_cluster(&mut raw_file, *l2_addr_disk) {
if let Some(os_error) = e.raw_os_error() {
if os_error == ENOTSUP {
return Err(Error::CompressedBlocksNotSupported);
}
}
}
}
}
@@ -638,22 +632,20 @@ impl QcowFile {
file: RawFile,
version: u32,
backing_file_name: &str,
backing_file_max_nesting_depth: u32,
) -> Result<QcowFile> {
let direct_io = file.is_direct();
let backing_raw_file = OpenOptions::new()
.read(true)
.open(backing_file_name)
.map_err(Error::BackingFileIo)?;
let backing_file = Self::from_with_nesting_depth(
RawFile::new(backing_raw_file, direct_io),
backing_file_max_nesting_depth,
)
.map_err(|e| Error::BackingFileOpen(Box::new(e)))?;
let size = backing_file.virtual_size();
let backing_file = crate::create_disk_file(backing_raw_file, direct_io)
.map_err(|e| Error::BackingFileOpen(Box::new(e)))?;
let size = backing_file
.size()
.map_err(|e| Error::BackingFileOpen(Box::new(e)))?;
let header = QcowHeader::create_for_size_and_path(version, size, Some(backing_file_name))?;
let mut result = QcowFile::new_from_header(file, header)?;
result.backing_file = Some(Box::new(backing_file));
result.backing_file = Some(backing_file);
Ok(result)
}
@@ -681,7 +673,7 @@ impl QcowFile {
Ok(qcow)
}
pub fn set_backing_file(&mut self, backing: Option<Box<Self>>) {
pub fn set_backing_file(&mut self, backing: Option<Box<dyn BlockBackend>>) {
self.backing_file = backing;
}
@@ -1219,7 +1211,7 @@ impl QcowFile {
}
// Allocate and initialize a new data cluster. Returns the offset of the
// cluster into the file on success.
// cluster in to the file on success.
fn append_data_cluster(&mut self, initial_data: Option<Vec<u8>>) -> std::io::Result<u64> {
let new_addr: u64 = self.get_new_cluster(initial_data)?;
// The cluster refcount starts at one indicating it is used but doesn't need COW.
@@ -1516,12 +1508,6 @@ impl QcowFile {
}
}
impl AsRawFd for QcowFile {
fn as_raw_fd(&self) -> RawFd {
self.raw_file.as_raw_fd()
}
}
impl Drop for QcowFile {
fn drop(&mut self) {
let _ = self.sync_caches();
@@ -1583,11 +1569,11 @@ impl Seek for QcowFile {
}
};
if let Some(o) = new_offset
&& o <= self.virtual_size()
{
self.current_offset = o;
return Ok(o);
if let Some(o) = new_offset {
if o <= self.virtual_size() {
self.current_offset = o;
return Ok(o);
}
}
Err(std::io::Error::from_raw_os_error(EINVAL))
}
@@ -1631,7 +1617,8 @@ impl FileSync for QcowFile {
impl FileSetLen for QcowFile {
fn set_len(&self, _len: u64) -> std::io::Result<()> {
Err(std::io::Error::other(
Err(std::io::Error::new(
std::io::ErrorKind::Other,
"set_len() not supported for QcowFile",
))
}
@@ -1642,7 +1629,7 @@ impl PunchHole for QcowFile {
let mut remaining = length;
let mut offset = offset;
while remaining > 0 {
let chunk_length = min(remaining, usize::MAX as u64) as usize;
let chunk_length = min(remaining, std::usize::MAX as u64) as usize;
self.deallocate_bytes(offset, chunk_length)?;
remaining -= chunk_length as u64;
offset += chunk_length as u64;
@@ -1704,12 +1691,12 @@ fn offset_is_cluster_boundary(offset: u64, cluster_bits: u32) -> Result<()> {
// Ceiling of the division of `dividend`/`divisor`.
fn div_round_up_u64(dividend: u64, divisor: u64) -> u64 {
dividend / divisor + u64::from(!dividend.is_multiple_of(divisor))
dividend / divisor + u64::from(dividend % divisor != 0)
}
// Ceiling of the division of `dividend`/`divisor`.
fn div_round_up_u32(dividend: u32, divisor: u32) -> u32 {
dividend / divisor + u32::from(!dividend.is_multiple_of(divisor))
dividend / divisor + u32::from(dividend % divisor != 0)
}
fn convert_copy<R, W>(reader: &mut R, writer: &mut W, offset: u64, size: u64) -> Result<()>
@@ -1802,17 +1789,11 @@ where
/// Copy the contents of a disk image in `src_file` into `dst_file`.
/// The type of `src_file` is automatically detected, and the output file type is
/// determined by `dst_type`.
pub fn convert(
mut src_file: RawFile,
dst_file: RawFile,
dst_type: ImageType,
src_max_nesting_depth: u32,
) -> Result<()> {
pub fn convert(mut src_file: RawFile, dst_file: RawFile, dst_type: ImageType) -> Result<()> {
let src_type = detect_image_type(&mut src_file)?;
match src_type {
ImageType::Qcow2 => {
let mut src_reader =
QcowFile::from_with_nesting_depth(src_file, src_max_nesting_depth)?;
let mut src_reader = QcowFile::from(src_file)?;
convert_reader(&mut src_reader, dst_file, dst_type)
}
ImageType::Raw => {
@@ -1840,15 +1821,11 @@ pub fn detect_image_type(file: &mut RawFile) -> Result<ImageType> {
#[cfg(test)]
mod tests {
use std::fs::File;
use std::path::Path;
use vmm_sys_util::tempdir::TempDir;
use super::*;
use std::io::{Read, Seek, SeekFrom, Write};
use vmm_sys_util::tempfile::TempFile;
use vmm_sys_util::write_zeroes::WriteZeroes;
use super::*;
fn valid_header_v3() -> Vec<u8> {
vec![
0x51u8, 0x46, 0x49, 0xfb, // magic
@@ -2045,80 +2022,6 @@ mod tests {
assert_eq!(read_header.backing_file_path, header.backing_file_path);
}
#[test]
fn no_backing_file() {
// `backing_file` is `None`
let header = QcowHeader::create_for_size_and_path(3, 0x10_0000, None)
.expect("Failed to create header.");
let mut disk_file: RawFile = RawFile::new(TempFile::new().unwrap().into_file(), false);
header
.write_to(&mut disk_file)
.expect("Failed to write header to shm.");
disk_file.rewind().unwrap();
// The maximum nesting depth is 0, which means backing file is not allowed.
QcowFile::from_with_nesting_depth(disk_file, 0).unwrap();
}
#[test]
fn disable_backing_file() {
// `backing_file` is `Some`
let header =
QcowHeader::create_for_size_and_path(3, 0x10_0000, Some("/path/to/backing/file"))
.expect("Failed to create header.");
let mut disk_file: RawFile = RawFile::new(TempFile::new().unwrap().into_file(), false);
header
.write_to(&mut disk_file)
.expect("Failed to write header to shm.");
disk_file.rewind().unwrap();
// The maximum nesting depth is 0, which means backing file is not allowed.
let res = QcowFile::from_with_nesting_depth(disk_file, 0);
assert!(matches!(res.unwrap_err(), Error::MaxNestingDepthExceeded));
}
/// Create a qcow2 file with itself as its backing file.
///
/// Without configuration `max_nesting_depth`, this will cause infinite recursion when loading
/// the file until stack overflow.
fn new_self_referential_qcow(path: &Path) -> Result<()> {
let header = QcowHeader::create_for_size_and_path(3, 0x10_0000, path.to_str())?;
let mut disk_file = RawFile::new(
File::create(path).expect("Failed to create image file."),
false,
);
header.write_to(&mut disk_file)?;
Ok(())
}
#[test]
fn max_nesting_backing() {
let test_dir = TempDir::new_with_prefix("/tmp/ch").unwrap();
let img_path = test_dir.as_path().join("test.img");
new_self_referential_qcow(img_path.as_path()).unwrap();
let err = QcowFile::from_with_nesting_depth(
RawFile::new(
File::open(img_path.as_path()).expect("Failed to open qcow image file"),
false,
),
MAX_NESTING_DEPTH,
)
.expect_err("Opening qcow file with itself as backing file should fail.");
// This type of error is complex. For comparing easily, we can check if it contains the
// type name after formatting.
assert!(format!("{err:?}").contains(&format!("{:?}", Error::MaxNestingDepthExceeded)));
// This should recursively call the function ten times before throwing an error, and the
// error `BackingFileOpen` should also be repeated ten times.
assert_eq!(
format!("{err:?}")
.matches("BackingFileOpen")
.collect::<Vec<_>>()
.len() as u32,
MAX_NESTING_DEPTH,
);
}
#[test]
fn invalid_magic() {
let invalid_header = vec![0x51u8, 0x46, 0x4a, 0xfb];

View File

@@ -1,17 +1,12 @@
// Copyright 2018 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE-BSD-3-Clause file.
//
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
use std::io::{self, BufWriter, Seek, SeekFrom, Write};
use std::mem::size_of;
use std::os::fd::{AsRawFd, RawFd};
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use vmm_sys_util::write_zeroes::WriteZeroes;
use super::RawFile;
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use std::io::{self, BufWriter, Seek, SeekFrom, Write};
use std::mem::size_of;
use vmm_sys_util::write_zeroes::WriteZeroes;
/// A qcow file. Allows reading/writing clusters and appending clusters.
#[derive(Debug)]
@@ -25,7 +20,7 @@ impl QcowRawFile {
/// Creates a `QcowRawFile` from the given `File`, `None` is returned if `cluster_size` is not
/// a power of two.
pub fn from(file: RawFile, cluster_size: u64) -> Option<Self> {
if !cluster_size.is_power_of_two() {
if cluster_size.count_ones() != 1 {
return None;
}
Some(QcowRawFile {
@@ -36,7 +31,7 @@ impl QcowRawFile {
}
/// Reads `count` 64 bit offsets and returns them as a vector.
/// `mask` optionally `&`s out some of the bits on the file.
/// `mask` optionally ands out some of the bits on the file.
pub fn read_pointer_table(
&mut self,
offset: u64,
@@ -55,7 +50,7 @@ impl QcowRawFile {
}
/// Reads a cluster's worth of 64 bit offsets and returns them as a vector.
/// `mask` optionally `&`s out some of the bits on the file.
/// `mask` optionally ands out some of the bits on the file.
pub fn read_pointer_cluster(&mut self, offset: u64, mask: Option<u64>) -> io::Result<Vec<u64>> {
let count = self.cluster_size / size_of::<u64>() as u64;
self.read_pointer_table(offset, count, mask)
@@ -160,9 +155,3 @@ impl Clone for QcowRawFile {
}
}
}
impl AsRawFd for QcowRawFile {
fn as_raw_fd(&self) -> RawFd {
self.file.as_raw_fd()
}
}

View File

@@ -8,17 +8,15 @@
//
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
use std::alloc::{Layout, alloc_zeroed, dealloc};
use crate::BlockBackend;
use libc::c_void;
use std::alloc::{alloc_zeroed, dealloc, Layout};
use std::convert::TryInto;
use std::fs::{File, Metadata};
use std::io::{self, Read, Seek, SeekFrom, Write};
use std::os::unix::io::{AsRawFd, RawFd};
use std::slice;
use libc::c_void;
use vmm_sys_util::seek_hole::SeekHole;
use vmm_sys_util::write_zeroes::{PunchHole, WriteZeroesAt};
use crate::BlockBackend;
use vmm_sys_util::{seek_hole::SeekHole, write_zeroes::PunchHole, write_zeroes::WriteZeroesAt};
#[derive(Debug)]
pub struct RawFile {
@@ -74,7 +72,7 @@ impl RawFile {
fn round_up(&self, offset: u64) -> u64 {
let align: u64 = self.alignment.try_into().unwrap();
offset.div_ceil(align) * align
((offset + align - 1) / align) * align
}
fn round_down(&self, offset: u64) -> u64 {
@@ -89,9 +87,9 @@ impl RawFile {
let align64: u64 = self.alignment.try_into().unwrap();
self.position.is_multiple_of(align64)
&& (buf.as_ptr() as usize).is_multiple_of(self.alignment)
&& buf.len().is_multiple_of(self.alignment)
(self.position % align64 == 0)
&& ((buf.as_ptr() as usize) % self.alignment == 0)
&& (buf.len() % self.alignment == 0)
}
pub fn set_len(&self, size: u64) -> std::io::Result<()> {
@@ -369,9 +367,3 @@ impl Clone for RawFile {
}
}
}
impl AsRawFd for RawFile {
fn as_raw_fd(&self) -> RawFd {
self.file.as_raw_fd()
}
}

View File

@@ -1,38 +1,51 @@
// Copyright 2018 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE-BSD-3-Clause file.
//
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
use std::fmt::{self, Display};
use std::io;
use libc::EINVAL;
use thiserror::Error;
use crate::qcow::qcow_raw_file::QcowRawFile;
use crate::qcow::vec_cache::{CacheMap, Cacheable, VecCache};
use crate::qcow::{
qcow_raw_file::QcowRawFile,
vec_cache::{CacheMap, Cacheable, VecCache},
};
#[derive(Debug, Error)]
#[derive(Debug)]
pub enum Error {
/// `EvictingCache` - Error writing a refblock from the cache to disk.
#[error("Failed to write a refblock from the cache to disk")]
EvictingRefCounts(#[source] io::Error),
EvictingRefCounts(io::Error),
/// `InvalidIndex` - Address requested isn't within the range of the disk.
#[error("Address requested is not within the range of the disk")]
InvalidIndex,
/// `NeedCluster` - Handle this error by reading the cluster and calling the function again.
#[error("Cluster with addr={0} needs to be read")]
NeedCluster(u64),
/// `NeedNewCluster` - Handle this error by allocating a cluster and calling the function again.
#[error("New cluster needs to be allocated for refcounts")]
NeedNewCluster,
/// `ReadingRefCounts` - Error reading the file into the refcount cache.
#[error("Failed to read the file into the refcount cache")]
ReadingRefCounts(#[source] io::Error),
/// `ReadingRefCounts` - Error reading the file in to the refcount cache.
ReadingRefCounts(io::Error),
}
pub type Result<T> = std::result::Result<T, Error>;
impl Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::Error::*;
match self {
EvictingRefCounts(e) => {
write!(f, "failed to write a refblock from the cache to disk: {e}")
}
InvalidIndex => write!(f, "address requested is not within the range of the disk"),
NeedCluster(addr) => write!(f, "cluster with addr={addr} needs to be read"),
NeedNewCluster => write!(f, "new cluster needs to be allocated for refcounts"),
ReadingRefCounts(e) => {
write!(f, "failed to read the file into the refcount cache: {e}")
}
}
}
}
/// Represents the refcount entries for an open qcow file.
#[derive(Clone, Debug)]
pub struct RefCount {

View File

@@ -1,11 +1,9 @@
// Copyright 2018 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE-BSD-3-Clause file.
//
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
use std::collections::HashMap;
use std::collections::hash_map::IterMut;
use std::collections::HashMap;
use std::io;
use std::ops::{Index, IndexMut};
use std::slice::SliceIndex;
@@ -111,7 +109,7 @@ impl<T: Cacheable> CacheMap<T> {
self.map.get_mut(&index)
}
pub fn iter_mut(&mut self) -> IterMut<'_, usize, T> {
pub fn iter_mut(&mut self) -> IterMut<usize, T> {
self.map.iter_mut()
}
@@ -123,10 +121,10 @@ impl<T: Cacheable> CacheMap<T> {
if self.map.len() == self.capacity {
// TODO(dgreid) - smarter eviction strategy.
let to_evict = *self.map.iter().next().unwrap().0;
if let Some(evicted) = self.map.remove(&to_evict)
&& evicted.dirty()
{
write_callback(to_evict, evicted)?;
if let Some(evicted) = self.map.remove(&to_evict) {
if evicted.dirty() {
write_callback(to_evict, evicted)?;
}
}
}
self.map.insert(index, block);

View File

@@ -2,55 +2,47 @@
//
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
use crate::async_io::{AsyncIo, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult};
use crate::qcow::{QcowFile, RawFile, Result as QcowResult};
use crate::AsyncAdaptor;
use std::collections::VecDeque;
use std::fs::File;
use std::io::{Seek, SeekFrom};
use std::os::fd::AsRawFd;
use std::sync::{Arc, Mutex, MutexGuard};
use vmm_sys_util::eventfd::EventFd;
use crate::AsyncAdaptor;
use crate::async_io::{
AsyncIo, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult,
};
use crate::qcow::{QcowFile, RawFile, Result as QcowResult};
pub struct QcowDiskSync {
qcow_file: QcowFile,
qcow_file: Arc<Mutex<QcowFile>>,
}
impl QcowDiskSync {
pub fn new(file: File, direct_io: bool) -> QcowResult<Self> {
Ok(QcowDiskSync {
qcow_file: QcowFile::from(RawFile::new(file, direct_io))?,
qcow_file: Arc::new(Mutex::new(QcowFile::from(RawFile::new(file, direct_io))?)),
})
}
}
impl DiskFile for QcowDiskSync {
fn size(&mut self) -> DiskFileResult<u64> {
self.qcow_file
.seek(SeekFrom::End(0))
.map_err(DiskFileError::Size)
let mut file = self.qcow_file.lock().unwrap();
file.seek(SeekFrom::End(0)).map_err(DiskFileError::Size)
}
fn new_async_io(&self, _ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
Ok(Box::new(QcowSync::new(self.qcow_file.clone())) as Box<dyn AsyncIo>)
}
fn fd(&mut self) -> BorrowedDiskFd<'_> {
BorrowedDiskFd::new(self.qcow_file.as_raw_fd())
}
}
pub struct QcowSync {
qcow_file: QcowFile,
qcow_file: Arc<Mutex<QcowFile>>,
eventfd: EventFd,
completion_list: VecDeque<(u64, i32)>,
}
impl QcowSync {
pub fn new(qcow_file: QcowFile) -> Self {
pub fn new(qcow_file: Arc<Mutex<QcowFile>>) -> Self {
QcowSync {
qcow_file,
eventfd: EventFd::new(libc::EFD_NONBLOCK)
@@ -60,7 +52,11 @@ impl QcowSync {
}
}
impl AsyncAdaptor for QcowFile {}
impl AsyncAdaptor<QcowFile> for Arc<Mutex<QcowFile>> {
fn file(&mut self) -> MutexGuard<QcowFile> {
self.lock().unwrap()
}
}
impl AsyncIo for QcowSync {
fn notifier(&self) -> &EventFd {

View File

@@ -2,17 +2,15 @@
//
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
use std::fs::File;
use std::io::{Error, Seek, SeekFrom};
use std::os::unix::io::{AsRawFd, RawFd};
use io_uring::{IoUring, opcode, types};
use vmm_sys_util::eventfd::EventFd;
use crate::async_io::{
AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult,
AsyncIo, AsyncIoError, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult,
};
use crate::{BatchRequest, DiskTopology, RequestType};
use crate::DiskTopology;
use io_uring::{opcode, squeue, types, IoUring};
use std::fs::File;
use std::io::{Seek, SeekFrom};
use std::os::unix::io::{AsRawFd, RawFd};
use vmm_sys_util::eventfd::EventFd;
pub struct RawFileDisk {
file: File,
@@ -46,10 +44,6 @@ impl DiskFile for RawFileDisk {
DiskTopology::default()
}
}
fn fd(&mut self) -> BorrowedDiskFd<'_> {
BorrowedDiskFd::new(self.file.as_raw_fd())
}
}
pub struct RawFileAsync {
@@ -90,14 +84,14 @@ impl AsyncIo for RawFileAsync {
// SAFETY: we know the file descriptor is valid and we
// relied on vm-memory to provide the buffer address.
unsafe {
let _ = unsafe {
sq.push(
&opcode::Readv::new(types::Fd(self.fd), iovecs.as_ptr(), iovecs.len() as u32)
.offset(offset.try_into().unwrap())
.build()
.flags(squeue::Flags::ASYNC)
.user_data(user_data),
)
.map_err(|_| AsyncIoError::ReadVectored(Error::other("Submission queue is full")))?
};
// Update the submission queue and submit new operations to the
@@ -118,14 +112,14 @@ impl AsyncIo for RawFileAsync {
// SAFETY: we know the file descriptor is valid and we
// relied on vm-memory to provide the buffer address.
unsafe {
let _ = unsafe {
sq.push(
&opcode::Writev::new(types::Fd(self.fd), iovecs.as_ptr(), iovecs.len() as u32)
.offset(offset.try_into().unwrap())
.build()
.flags(squeue::Flags::ASYNC)
.user_data(user_data),
)
.map_err(|_| AsyncIoError::WriteVectored(Error::other("Submission queue is full")))?
};
// Update the submission queue and submit new operations to the
@@ -141,13 +135,13 @@ impl AsyncIo for RawFileAsync {
let (submitter, mut sq, _) = self.io_uring.split();
// SAFETY: we know the file descriptor is valid.
unsafe {
let _ = unsafe {
sq.push(
&opcode::Fsync::new(types::Fd(self.fd))
.build()
.flags(squeue::Flags::ASYNC)
.user_data(user_data),
)
.map_err(|_| AsyncIoError::Fsync(Error::other("Submission queue is full")))?
};
// Update the submission queue and submit new operations to the
@@ -168,77 +162,4 @@ impl AsyncIo for RawFileAsync {
.next()
.map(|entry| (entry.user_data(), entry.result()))
}
fn batch_requests_enabled(&self) -> bool {
true
}
fn submit_batch_requests(&mut self, batch_request: &[BatchRequest]) -> AsyncIoResult<()> {
if !self.batch_requests_enabled() {
return Ok(());
}
let (submitter, mut sq, _) = self.io_uring.split();
let mut submitted = false;
for req in batch_request {
match req.request_type {
RequestType::In => {
// SAFETY: we know the file descriptor is valid and we
// relied on vm-memory to provide the buffer address.
unsafe {
sq.push(
&opcode::Readv::new(
types::Fd(self.fd),
req.iovecs.as_ptr(),
req.iovecs.len() as u32,
)
.offset(req.offset as u64)
.build()
.user_data(req.user_data),
)
.map_err(|_| {
AsyncIoError::ReadVectored(Error::other("Submission queue is full"))
})?
};
submitted = true;
}
RequestType::Out => {
// SAFETY: we know the file descriptor is valid and we
// relied on vm-memory to provide the buffer address.
unsafe {
sq.push(
&opcode::Writev::new(
types::Fd(self.fd),
req.iovecs.as_ptr(),
req.iovecs.len() as u32,
)
.offset(req.offset as u64)
.build()
.user_data(req.user_data),
)
.map_err(|_| {
AsyncIoError::WriteVectored(Error::other("Submission queue is full"))
})?
};
submitted = true;
}
_ => {
unreachable!("Unexpected batch request type: {:?}", req.request_type)
}
}
}
// Only submit if we actually queued something
if submitted {
// Update the submission queue and submit new operations to the
// io_uring instance.
sq.sync();
submitter
.submit()
.map_err(AsyncIoError::SubmitBatchRequests)?;
}
Ok(())
}
}

View File

@@ -5,18 +5,16 @@
// Copyright © 2023 Crusoe Energy Systems LLC
//
use crate::async_io::{
AsyncIo, AsyncIoError, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult,
};
use crate::DiskTopology;
use std::fs::File;
use std::io::{Seek, SeekFrom};
use std::os::unix::io::{AsRawFd, RawFd};
use vmm_sys_util::aio;
use vmm_sys_util::eventfd::EventFd;
use crate::DiskTopology;
use crate::async_io::{
AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult,
};
pub struct RawFileDiskAio {
file: File,
}
@@ -49,10 +47,6 @@ impl DiskFile for RawFileDiskAio {
DiskTopology::default()
}
}
fn fd(&mut self) -> BorrowedDiskFd<'_> {
BorrowedDiskFd::new(self.file.as_raw_fd())
}
}
pub struct RawFileAsyncAio {

View File

@@ -2,18 +2,16 @@
//
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
use crate::async_io::{
AsyncIo, AsyncIoError, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult,
};
use crate::DiskTopology;
use std::collections::VecDeque;
use std::fs::File;
use std::io::{Seek, SeekFrom};
use std::os::unix::io::{AsRawFd, RawFd};
use vmm_sys_util::eventfd::EventFd;
use crate::DiskTopology;
use crate::async_io::{
AsyncIo, AsyncIoError, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult,
};
pub struct RawFileDiskSync {
file: File,
}
@@ -43,10 +41,6 @@ impl DiskFile for RawFileDiskSync {
DiskTopology::default()
}
}
fn fd(&mut self) -> BorrowedDiskFd<'_> {
BorrowedDiskFd::new(self.file.as_raw_fd())
}
}
pub struct RawFileSync {

View File

@@ -2,11 +2,11 @@
//
// SPDX-License-Identifier: Apache-2.0
use crate::{read_aligned_block_size, DiskTopology};
use std::convert::TryInto;
use std::fs::File;
use std::io::{Seek, SeekFrom};
use crate::{DiskTopology, read_aligned_block_size};
#[derive(Clone, Copy)]
pub struct VhdFooter {
cookie: u64,
@@ -118,13 +118,11 @@ pub fn is_fixed_vhd(f: &mut File) -> std::io::Result<bool> {
#[cfg(test)]
mod tests {
use super::{is_fixed_vhd, VhdFooter};
use std::fs::File;
use std::io::{Seek, SeekFrom, Write};
use vmm_sys_util::tempfile::TempFile;
use super::{VhdFooter, is_fixed_vhd};
fn valid_fixed_vhd_footer() -> Vec<u8> {
vec![
0x63, 0x6f, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x78, // cookie

View File

@@ -2,21 +2,26 @@
//
// SPDX-License-Identifier: Apache-2.0
use crate::vhdx::{
vhdx_bat::{BatEntry, VhdxBatError},
vhdx_header::{RegionInfo, RegionTableEntry, VhdxHeader, VhdxHeaderError},
vhdx_io::VhdxIoError,
vhdx_metadata::{DiskSpec, VhdxMetadataError},
};
use crate::BlockBackend;
use byteorder::{BigEndian, ByteOrder};
use remain::sorted;
use std::collections::btree_map::BTreeMap;
use std::fs::File;
use std::io::{Read, Seek, SeekFrom, Write};
use std::os::fd::{AsRawFd, RawFd};
use byteorder::{BigEndian, ByteOrder};
use remain::sorted;
use thiserror::Error;
use uuid::Uuid;
use crate::BlockBackend;
use crate::vhdx::vhdx_bat::{BatEntry, VhdxBatError};
use crate::vhdx::vhdx_header::{RegionInfo, RegionTableEntry, VhdxHeader, VhdxHeaderError};
use crate::vhdx::vhdx_io::VhdxIoError;
use crate::vhdx::vhdx_metadata::{DiskSpec, VhdxMetadataError};
macro_rules! div_round_up {
($n:expr,$d:expr) => {
($n + $d - 1) / $d
};
}
mod vhdx_bat;
mod vhdx_header;
@@ -26,19 +31,19 @@ mod vhdx_metadata;
#[sorted]
#[derive(Error, Debug)]
pub enum VhdxError {
#[error("Not a VHDx file")]
#[error("Not a VHDx file {0}")]
NotVhdx(#[source] VhdxHeaderError),
#[error("Failed to parse VHDx header")]
#[error("Failed to parse VHDx header {0}")]
ParseVhdxHeader(#[source] VhdxHeaderError),
#[error("Failed to parse VHDx metadata")]
#[error("Failed to parse VHDx metadata {0}")]
ParseVhdxMetadata(#[source] VhdxMetadataError),
#[error("Failed to parse VHDx region entries")]
#[error("Failed to parse VHDx region entries {0}")]
ParseVhdxRegionEntry(#[source] VhdxHeaderError),
#[error("Failed reading metadata")]
#[error("Failed reading metadata {0}")]
ReadBatEntry(#[source] VhdxBatError),
#[error("Failed reading sector from disk")]
#[error("Failed reading sector from disk {0}")]
ReadFailed(#[source] VhdxIoError),
#[error("Failed writing to sector on disk")]
#[error("Failed writing to sector on disk {0}")]
WriteFailed(#[source] VhdxIoError),
}
@@ -100,10 +105,11 @@ impl Read for Vhdx {
/// Wrapper function to satisfy Read trait implementation for VHDx disk.
/// Convert the offset to sector index and buffer length to sector count.
fn read(&mut self, buf: &mut [u8]) -> std::result::Result<usize, std::io::Error> {
let sector_count = (buf.len() as u64).div_ceil(self.disk_spec.logical_sector_size as u64);
let sector_count =
div_round_up!(buf.len() as u64, self.disk_spec.logical_sector_size as u64);
let sector_index = self.current_offset / self.disk_spec.logical_sector_size as u64;
let result = vhdx_io::read(
vhdx_io::read(
&mut self.file,
buf,
&self.disk_spec,
@@ -112,14 +118,13 @@ impl Read for Vhdx {
sector_count,
)
.map_err(|e| {
std::io::Error::other(format!(
"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)
std::io::Error::new(
std::io::ErrorKind::Other,
format!(
"Failed reading {sector_count} sectors from VHDx at index {sector_index}: {e}"
),
)
})
}
}
@@ -131,17 +136,21 @@ impl Write for Vhdx {
/// Wrapper function to satisfy Write trait implementation for VHDx disk.
/// Convert the offset to sector index and buffer length to sector count.
fn write(&mut self, buf: &[u8]) -> std::result::Result<usize, std::io::Error> {
let sector_count = (buf.len() as u64).div_ceil(self.disk_spec.logical_sector_size as u64);
let sector_count =
div_round_up!(buf.len() as u64, self.disk_spec.logical_sector_size as u64);
let sector_index = self.current_offset / self.disk_spec.logical_sector_size as u64;
if self.first_write {
self.first_write = false;
self.vhdx_header
.update(&mut self.file)
.map_err(|e| std::io::Error::other(format!("Failed to update VHDx header: {e}")))?;
self.vhdx_header.update(&mut self.file).map_err(|e| {
std::io::Error::new(
std::io::ErrorKind::Other,
format!("Failed to update VHDx header: {e}"),
)
})?;
}
let result = vhdx_io::write(
vhdx_io::write(
&mut self.file,
buf,
&mut self.disk_spec,
@@ -151,14 +160,13 @@ impl Write for Vhdx {
sector_count,
)
.map_err(|e| {
std::io::Error::other(format!(
"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)
std::io::Error::new(
std::io::ErrorKind::Other,
format!(
"Failed writing {sector_count} sectors on VHDx at index {sector_index}: {e}"
),
)
})
}
}
@@ -187,11 +195,11 @@ impl Seek for Vhdx {
}
};
if let Some(o) = new_offset
&& o <= self.virtual_disk_size()
{
self.current_offset = o;
return Ok(o);
if let Some(o) = new_offset {
if o <= self.virtual_disk_size() {
self.current_offset = o;
return Ok(o);
}
}
Err(std::io::Error::new(
@@ -223,12 +231,6 @@ impl Clone for Vhdx {
}
}
impl AsRawFd for Vhdx {
fn as_raw_fd(&self) -> RawFd {
self.file.as_raw_fd()
}
}
pub(crate) fn uuid_from_guid(buf: &[u8]) -> Uuid {
// The first 3 fields of UUID are stored in Big Endian format, and
// the last 8 bytes are stored as byte array. Therefore, we read the

View File

@@ -2,17 +2,14 @@
//
// SPDX-License-Identifier: Apache-2.0
use crate::vhdx::{vhdx_header::RegionTableEntry, vhdx_metadata::DiskSpec};
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use remain::sorted;
use std::fs::File;
use std::io::{self, Seek, SeekFrom};
use std::mem::size_of;
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use remain::sorted;
use thiserror::Error;
use crate::vhdx::vhdx_header::RegionTableEntry;
use crate::vhdx::vhdx_metadata::DiskSpec;
// Payload BAT Entry States
pub const PAYLOAD_BLOCK_NOT_PRESENT: u64 = 0;
pub const PAYLOAD_BLOCK_UNDEFINED: u64 = 1;
@@ -33,9 +30,9 @@ pub enum VhdxBatError {
InvalidBatEntry,
#[error("Invalid BAT entry count")]
InvalidEntryCount,
#[error("Failed to read BAT entry")]
#[error("Failed to read BAT entry {0}")]
ReadBat(#[source] io::Error),
#[error("Failed to write BAT entry")]
#[error("Failed to write BAT entry {0}")]
WriteBat(#[source] io::Error),
}
@@ -78,7 +75,7 @@ impl BatEntry {
// Calculate the number of entries in the BAT
fn calculate_entries(block_size: u32, virtual_disk_size: u64, chunk_ratio: u64) -> u64 {
let data_blocks_count = virtual_disk_size.div_ceil(block_size as u64);
let data_blocks_count = div_round_up!(virtual_disk_size, block_size as u64);
data_blocks_count + (data_blocks_count - 1) / chunk_ratio
}

View File

@@ -4,13 +4,13 @@
extern crate log;
use byteorder::{ByteOrder, LittleEndian, ReadBytesExt};
use remain::sorted;
use std::collections::btree_map::BTreeMap;
use std::convert::TryInto;
use std::fs::File;
use std::io::{self, Read, Seek, SeekFrom, Write};
use std::mem::size_of;
use byteorder::{ByteOrder, LittleEndian, ReadBytesExt};
use remain::sorted;
use thiserror::Error;
use uuid::Uuid;
@@ -91,7 +91,7 @@ pub type Result<T> = std::result::Result<T, VhdxHeaderError>;
#[derive(Clone, Debug)]
pub struct FileTypeIdentifier {
pub _signature: u64,
pub signature: u64,
}
impl FileTypeIdentifier {
@@ -99,18 +99,18 @@ impl FileTypeIdentifier {
pub fn new(f: &mut File) -> Result<FileTypeIdentifier> {
f.seek(SeekFrom::Start(FILE_START))
.map_err(VhdxHeaderError::SeekFileTypeIdentifier)?;
let _signature = f
let signature = f
.read_u64::<LittleEndian>()
.map_err(VhdxHeaderError::ReadFileTypeIdentifier)?;
if _signature != VHDX_SIGN {
if signature != VHDX_SIGN {
return Err(VhdxHeaderError::InvalidVHDXSign);
}
Ok(FileTypeIdentifier { _signature })
Ok(FileTypeIdentifier { signature })
}
}
#[repr(C, packed)]
#[repr(packed)]
#[derive(Clone, Copy, Debug)]
pub struct Header {
pub signature: u32,
@@ -128,7 +128,7 @@ pub struct Header {
impl Header {
/// Reads the Header structure from a reference VHDx file
pub fn new(f: &mut File, start: u64) -> Result<Header> {
// Read the whole header into a buffer. We will need it for
// Read the whole header in to a buffer. We will need it for
// calculating checksum.
let mut buffer = [0; HEADER_SIZE as usize];
f.seek(SeekFrom::Start(start))
@@ -142,7 +142,7 @@ impl Header {
return Err(VhdxHeaderError::InvalidHeaderSign);
}
let new_checksum = calculate_checksum(&mut buffer, size_of::<u32>());
let new_checksum = calculate_checksum(&mut buffer, size_of::<u32>())?;
if header.checksum != new_checksum {
return Err(VhdxHeaderError::InvalidChecksum(String::from("Header")));
}
@@ -151,7 +151,7 @@ impl Header {
}
/// Converts the header structure into a buffer
fn write_to_buffer(&self, buffer: &mut [u8; HEADER_SIZE as usize]) {
fn get_header_as_buffer(&self, buffer: &mut [u8; HEADER_SIZE as usize]) {
// SAFETY: self is a valid header.
let reference = unsafe {
std::slice::from_raw_parts(self as *const Header as *const u8, HEADER_SIZE as usize)
@@ -160,7 +160,7 @@ impl Header {
}
/// Creates and returns new updated header from the provided current header
fn update_header(
pub fn update_header(
f: &mut File,
current_header: &Header,
change_data_guid: bool,
@@ -191,9 +191,11 @@ impl Header {
log_offset: current_header.log_offset,
};
new_header.write_to_buffer(&mut buffer);
new_header.checksum = calculate_checksum(&mut buffer, size_of::<u32>());
new_header.write_to_buffer(&mut buffer);
new_header.get_header_as_buffer(&mut buffer);
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);
f.seek(SeekFrom::Start(start))
.map_err(VhdxHeaderError::SeekHeader)?;
@@ -203,7 +205,7 @@ impl Header {
}
}
#[repr(C, packed)]
#[repr(packed)]
#[derive(Clone, Copy, Debug)]
struct RegionTableHeader {
pub signature: u32,
@@ -229,7 +231,7 @@ impl RegionTableHeader {
return Err(VhdxHeaderError::InvalidRegionSign);
}
let new_checksum = calculate_checksum(&mut buffer, size_of::<u32>());
let new_checksum = calculate_checksum(&mut buffer, size_of::<u32>())?;
if region_table_header.checksum != new_checksum {
return Err(VhdxHeaderError::InvalidChecksum(String::from("Region")));
}
@@ -328,7 +330,7 @@ impl RegionInfo {
}
}
#[repr(C, packed)]
#[repr(packed)]
#[derive(Clone, Copy, Debug)]
pub struct RegionTableEntry {
pub guid: Uuid,
@@ -351,6 +353,12 @@ impl RegionTableEntry {
}
}
#[derive(Clone, Debug)]
struct RegionEntry {
_start: u64,
_end: u64,
}
enum HeaderNo {
First,
Second,
@@ -369,10 +377,22 @@ pub struct VhdxHeader {
impl VhdxHeader {
/// Creates a VhdxHeader from a reference to a file
pub fn new(f: &mut File) -> Result<VhdxHeader> {
let _file_type_identifier: FileTypeIdentifier = FileTypeIdentifier::new(f)?;
let header_1 = Header::new(f, HEADER_1_START);
let header_2 = Header::new(f, HEADER_2_START);
let mut file_write_guid: u128 = 0;
let metadata = f.metadata().map_err(VhdxHeaderError::ReadMetadata)?;
if !metadata.permissions().readonly() {
file_write_guid = Uuid::new_v4().as_u128();
}
let (header_1, header_2) =
VhdxHeader::update_headers(f, header_1, header_2, file_write_guid)?;
Ok(VhdxHeader {
_file_type_identifier: FileTypeIdentifier::new(f)?,
header_1: Header::new(f, HEADER_1_START)?,
header_2: Header::new(f, HEADER_2_START)?,
_file_type_identifier,
header_1,
header_2,
region_table_1: RegionTableHeader::new(f, REGION_TABLE_1_START)?,
_region_table_2: RegionTableHeader::new(f, REGION_TABLE_2_START)?,
})
@@ -454,11 +474,13 @@ impl VhdxHeader {
/// Therefore, before calculating, the existing checksum is retrieved and the
/// corresponding field is made zero. After the calculation, the existing checksum
/// is put back to the buffer.
fn calculate_checksum(buffer: &mut [u8], csum_offset: usize) -> u32 {
// Read the original checksum from the buffer
let orig_csum = LittleEndian::read_u32(&buffer[csum_offset..csum_offset + 4]);
pub fn calculate_checksum(buffer: &mut [u8], csum_offset: usize) -> Result<u32> {
// Read the checksum into a mutable slice
let csum_buf = &mut buffer[csum_offset..csum_offset + 4];
// Convert the checksum chunk in to a u32 integer
let orig_csum = LittleEndian::read_u32(csum_buf);
// Zero the checksum in the buffer
LittleEndian::write_u32(&mut buffer[csum_offset..csum_offset + 4], 0);
LittleEndian::write_u32(csum_buf, 0);
// Calculate the checksum on the resulting buffer
let mut crc = crc_any::CRC::crc32c();
crc.digest(&buffer);
@@ -467,5 +489,5 @@ fn calculate_checksum(buffer: &mut [u8], csum_offset: usize) -> u32 {
// Put back the original checksum in the buffer
LittleEndian::write_u32(&mut buffer[csum_offset..csum_offset + 4], orig_csum);
new_csum
Ok(new_csum)
}

View File

@@ -2,15 +2,15 @@
//
// SPDX-License-Identifier: Apache-2.0
use crate::vhdx::{
vhdx_bat::{self, BatEntry, VhdxBatError},
vhdx_metadata::{self, DiskSpec},
};
use remain::sorted;
use std::fs::File;
use std::io::{self, Read, Seek, SeekFrom, Write};
use remain::sorted;
use thiserror::Error;
use crate::vhdx::vhdx_bat::{self, BatEntry, VhdxBatError};
use crate::vhdx::vhdx_metadata::{self, DiskSpec};
const SECTOR_SIZE: u64 = 512;
#[sorted]
@@ -35,7 +35,9 @@ pub enum VhdxIoError {
pub type Result<T> = std::result::Result<T, VhdxIoError>;
macro_rules! align {
($n:expr, $align:expr) => {{ $n.div_ceil($align) * $align }};
($n:expr, $align:expr) => {{
(($n + $align - 1) / $align) * $align
}};
}
#[derive(Default)]
@@ -93,45 +95,46 @@ pub fn read(
mut sector_index: u64,
mut sector_count: u64,
) -> Result<usize> {
if disk_spec.has_parent {
return Err(VhdxIoError::UnsupportedMode);
}
let mut read_count: usize = 0;
while sector_count > 0 {
let sector = Sector::new(disk_spec, bat, sector_index, sector_count)?;
if disk_spec.has_parent {
return Err(VhdxIoError::UnsupportedMode);
} else {
let sector = Sector::new(disk_spec, bat, sector_index, sector_count)?;
let bat_entry = match bat.get(sector.bat_index as usize) {
Some(entry) => entry.0,
None => {
return Err(VhdxIoError::InvalidBatIndex);
}
};
let bat_entry = match bat.get(sector.bat_index as usize) {
Some(entry) => entry.0,
None => {
return Err(VhdxIoError::InvalidBatIndex);
}
};
match bat_entry & vhdx_bat::BAT_STATE_BIT_MASK {
vhdx_bat::PAYLOAD_BLOCK_NOT_PRESENT
| vhdx_bat::PAYLOAD_BLOCK_UNDEFINED
| vhdx_bat::PAYLOAD_BLOCK_UNMAPPED
| vhdx_bat::PAYLOAD_BLOCK_ZERO => {}
vhdx_bat::PAYLOAD_BLOCK_FULLY_PRESENT => {
f.seek(SeekFrom::Start(sector.file_offset))
match bat_entry & vhdx_bat::BAT_STATE_BIT_MASK {
vhdx_bat::PAYLOAD_BLOCK_NOT_PRESENT
| vhdx_bat::PAYLOAD_BLOCK_UNDEFINED
| vhdx_bat::PAYLOAD_BLOCK_UNMAPPED
| vhdx_bat::PAYLOAD_BLOCK_ZERO => {}
vhdx_bat::PAYLOAD_BLOCK_FULLY_PRESENT => {
f.seek(SeekFrom::Start(sector.file_offset))
.map_err(VhdxIoError::ReadSectorBlock)?;
f.read_exact(
&mut buf[read_count
..(read_count + (sector.free_sectors * SECTOR_SIZE) as usize)],
)
.map_err(VhdxIoError::ReadSectorBlock)?;
f.read_exact(
&mut buf
[read_count..(read_count + (sector.free_sectors * SECTOR_SIZE) as usize)],
)
.map_err(VhdxIoError::ReadSectorBlock)?;
}
vhdx_bat::PAYLOAD_BLOCK_PARTIALLY_PRESENT => {
return Err(VhdxIoError::UnsupportedMode);
}
_ => {
return Err(VhdxIoError::InvalidBatEntryState);
}
}
vhdx_bat::PAYLOAD_BLOCK_PARTIALLY_PRESENT => {
return Err(VhdxIoError::UnsupportedMode);
}
_ => {
return Err(VhdxIoError::InvalidBatEntryState);
}
};
sector_count -= sector.free_sectors;
sector_index += sector.free_sectors;
read_count = sector.free_bytes as usize;
};
sector_count -= sector.free_sectors;
sector_index += sector.free_sectors;
read_count += sector.free_bytes as usize;
}
Ok(read_count)
}
@@ -147,73 +150,77 @@ pub fn write(
mut sector_index: u64,
mut sector_count: u64,
) -> Result<usize> {
if disk_spec.has_parent {
return Err(VhdxIoError::UnsupportedMode);
}
let mut write_count: usize = 0;
while sector_count > 0 {
let sector = Sector::new(disk_spec, bat, sector_index, sector_count)?;
if disk_spec.has_parent {
return Err(VhdxIoError::UnsupportedMode);
} else {
let sector = Sector::new(disk_spec, bat, sector_index, sector_count)?;
let bat_entry = match bat.get(sector.bat_index as usize) {
Some(entry) => entry.0,
None => {
return Err(VhdxIoError::InvalidBatIndex);
}
};
match bat_entry & vhdx_bat::BAT_STATE_BIT_MASK {
vhdx_bat::PAYLOAD_BLOCK_NOT_PRESENT
| vhdx_bat::PAYLOAD_BLOCK_UNDEFINED
| vhdx_bat::PAYLOAD_BLOCK_UNMAPPED
| vhdx_bat::PAYLOAD_BLOCK_ZERO => {
let file_offset =
align!(disk_spec.image_size, vhdx_metadata::BLOCK_SIZE_MIN as u64);
let new_size = file_offset
.checked_add(disk_spec.block_size as u64)
.ok_or(VhdxIoError::InvalidDiskSize)?;
f.set_len(new_size).map_err(VhdxIoError::ResizeFile)?;
disk_spec.image_size = new_size;
let new_bat_entry = file_offset
| (vhdx_bat::PAYLOAD_BLOCK_FULLY_PRESENT & vhdx_bat::BAT_STATE_BIT_MASK);
bat[sector.bat_index as usize] = BatEntry(new_bat_entry);
BatEntry::write_bat_entries(f, bat_offset, bat).map_err(VhdxIoError::WriteBat)?;
if file_offset < vhdx_metadata::BLOCK_SIZE_MIN as u64 {
break;
let bat_entry = match bat.get(sector.bat_index as usize) {
Some(entry) => entry.0,
None => {
return Err(VhdxIoError::InvalidBatIndex);
}
};
f.seek(SeekFrom::Start(file_offset))
match bat_entry & vhdx_bat::BAT_STATE_BIT_MASK {
vhdx_bat::PAYLOAD_BLOCK_NOT_PRESENT
| vhdx_bat::PAYLOAD_BLOCK_UNDEFINED
| vhdx_bat::PAYLOAD_BLOCK_UNMAPPED
| vhdx_bat::PAYLOAD_BLOCK_ZERO => {
let file_offset =
align!(disk_spec.image_size, vhdx_metadata::BLOCK_SIZE_MIN as u64);
let new_size = file_offset
.checked_add(disk_spec.block_size as u64)
.ok_or(VhdxIoError::InvalidDiskSize)?;
f.set_len(new_size).map_err(VhdxIoError::ResizeFile)?;
disk_spec.image_size = new_size;
let new_bat_entry = file_offset
| (vhdx_bat::PAYLOAD_BLOCK_FULLY_PRESENT & vhdx_bat::BAT_STATE_BIT_MASK);
bat[sector.bat_index as usize] = BatEntry(new_bat_entry);
BatEntry::write_bat_entries(f, bat_offset, bat)
.map_err(VhdxIoError::WriteBat)?;
if file_offset < vhdx_metadata::BLOCK_SIZE_MIN as u64 {
break;
}
f.seek(SeekFrom::Start(file_offset))
.map_err(VhdxIoError::ReadSectorBlock)?;
f.write_all(
&buf[write_count
..(write_count + (sector.free_sectors * SECTOR_SIZE) as usize)],
)
.map_err(VhdxIoError::ReadSectorBlock)?;
f.write_all(
&buf[write_count..(write_count + (sector.free_sectors * SECTOR_SIZE) as usize)],
)
.map_err(VhdxIoError::ReadSectorBlock)?;
}
vhdx_bat::PAYLOAD_BLOCK_FULLY_PRESENT => {
if sector.file_offset < vhdx_metadata::BLOCK_SIZE_MIN as u64 {
break;
}
vhdx_bat::PAYLOAD_BLOCK_FULLY_PRESENT => {
if sector.file_offset < vhdx_metadata::BLOCK_SIZE_MIN as u64 {
break;
}
f.seek(SeekFrom::Start(sector.file_offset))
f.seek(SeekFrom::Start(sector.file_offset))
.map_err(VhdxIoError::ReadSectorBlock)?;
f.write_all(
&buf[write_count
..(write_count + (sector.free_sectors * SECTOR_SIZE) as usize)],
)
.map_err(VhdxIoError::ReadSectorBlock)?;
f.write_all(
&buf[write_count..(write_count + (sector.free_sectors * SECTOR_SIZE) as usize)],
)
.map_err(VhdxIoError::ReadSectorBlock)?;
}
vhdx_bat::PAYLOAD_BLOCK_PARTIALLY_PRESENT => {
return Err(VhdxIoError::UnsupportedMode);
}
_ => {
return Err(VhdxIoError::InvalidBatEntryState);
}
}
vhdx_bat::PAYLOAD_BLOCK_PARTIALLY_PRESENT => {
return Err(VhdxIoError::UnsupportedMode);
}
_ => {
return Err(VhdxIoError::InvalidBatEntryState);
}
};
sector_count -= sector.free_sectors;
sector_index += sector.free_sectors;
write_count = sector.free_bytes as usize;
};
sector_count -= sector.free_sectors;
sector_index += sector.free_sectors;
write_count += sector.free_bytes as usize;
}
Ok(write_count)
}

View File

@@ -2,17 +2,15 @@
//
// SPDX-License-Identifier: Apache-2.0
use crate::vhdx::vhdx_header::RegionTableEntry;
use byteorder::{LittleEndian, ReadBytesExt};
use remain::sorted;
use std::fs::File;
use std::io::{self, Read, Seek, SeekFrom};
use std::mem::size_of;
use byteorder::{LittleEndian, ReadBytesExt};
use remain::sorted;
use thiserror::Error;
use uuid::Uuid;
use crate::vhdx::vhdx_header::RegionTableEntry;
const METADATA_SIGN: u64 = 0x6174_6164_6174_656D;
const METADATA_ENTRY_SIZE: usize = 32;
const METADATA_MAX_ENTRIES: u16 = 2047;
@@ -55,8 +53,6 @@ const METADATA_LENGTH_MAX: u32 = 1 << 20; // 1 MiB
pub enum VhdxMetadataError {
#[error("Invalid block size count")]
InvalidBlockSize,
#[error("Invalid disk size {0}")]
InvalidDiskSize(u64),
#[error("Invalid metadata entry count")]
InvalidEntryCount,
#[error("Invalid logical sector size")]
@@ -216,14 +212,6 @@ impl DiskSpec {
if metadata_presence != METADATA_ALL_PRESENT {
return Err(VhdxMetadataError::MissingMetadata);
}
// Make sure virtual disk size is not zero
if (metadata_presence & METADATA_VIRTUAL_DISK_SIZE_PRESENT != 0)
&& disk_spec.virtual_disk_size == 0
{
return Err(VhdxMetadataError::InvalidDiskSize(
disk_spec.virtual_disk_size,
));
}
// Check if the virtual disk size is a multiple of the logical sector
// size.
if ((metadata_presence & METADATA_LOGICAL_SECTOR_SIZE_PRESENT) != 0)
@@ -267,7 +255,7 @@ impl DiskSpec {
}
}
#[repr(C, packed)]
#[repr(packed)]
#[derive(Default, Debug, Clone, Copy)]
struct MetadataTableHeader {
signature: u64,
@@ -298,7 +286,7 @@ impl MetadataTableHeader {
}
}
#[repr(C, packed)]
#[repr(packed)]
#[derive(Default, Debug, Clone, Copy)]
pub struct MetadataTableEntry {
item_id: Uuid,

View File

@@ -2,33 +2,29 @@
//
// SPDX-License-Identifier: Apache-2.0
use crate::async_io::{AsyncIo, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult};
use crate::vhdx::{Result as VhdxResult, Vhdx};
use crate::AsyncAdaptor;
use std::collections::VecDeque;
use std::fs::File;
use std::os::fd::AsRawFd;
use std::sync::{Arc, Mutex, MutexGuard};
use vmm_sys_util::eventfd::EventFd;
use crate::AsyncAdaptor;
use crate::async_io::{
AsyncIo, AsyncIoResult, BorrowedDiskFd, DiskFile, DiskFileError, DiskFileResult,
};
use crate::vhdx::{Result as VhdxResult, Vhdx};
pub struct VhdxDiskSync {
vhdx_file: Vhdx,
vhdx_file: Arc<Mutex<Vhdx>>,
}
impl VhdxDiskSync {
pub fn new(f: File) -> VhdxResult<Self> {
Ok(VhdxDiskSync {
vhdx_file: Vhdx::new(f)?,
vhdx_file: Arc::new(Mutex::new(Vhdx::new(f)?)),
})
}
}
impl DiskFile for VhdxDiskSync {
fn size(&mut self) -> DiskFileResult<u64> {
Ok(self.vhdx_file.virtual_disk_size())
Ok(self.vhdx_file.lock().unwrap().virtual_disk_size())
}
fn new_async_io(&self, _ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
@@ -37,20 +33,16 @@ impl DiskFile for VhdxDiskSync {
as Box<dyn AsyncIo>,
)
}
fn fd(&mut self) -> BorrowedDiskFd<'_> {
BorrowedDiskFd::new(self.vhdx_file.as_raw_fd())
}
}
pub struct VhdxSync {
vhdx_file: Vhdx,
vhdx_file: Arc<Mutex<Vhdx>>,
eventfd: EventFd,
completion_list: VecDeque<(u64, i32)>,
}
impl VhdxSync {
pub fn new(vhdx_file: Vhdx) -> std::io::Result<Self> {
pub fn new(vhdx_file: Arc<Mutex<Vhdx>>) -> std::io::Result<Self> {
Ok(VhdxSync {
vhdx_file,
eventfd: EventFd::new(libc::EFD_NONBLOCK)?,
@@ -59,7 +51,11 @@ impl VhdxSync {
}
}
impl AsyncAdaptor for Vhdx {}
impl AsyncAdaptor<Vhdx> for Arc<Mutex<Vhdx>> {
fn file(&mut self) -> MutexGuard<Vhdx> {
self.lock().unwrap()
}
}
impl AsyncIo for VhdxSync {
fn notifier(&self) -> &EventFd {

View File

@@ -3,25 +3,17 @@
// SPDX-License-Identifier: Apache-2.0
//
use std::env;
use std::process::Command;
fn main() {
let mut version = "v".to_owned() + env!("CARGO_PKG_VERSION");
if let Ok(git_out) = Command::new("git").args(["describe", "--dirty"]).output()
&& git_out.status.success()
&& let Ok(git_out_str) = String::from_utf8(git_out.stdout)
{
version = git_out_str;
// Pop the trailing newline.
version.pop();
}
// Append CH_EXTRA_VERSION to version if it is set.
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}"));
if let Ok(git_out) = Command::new("git").args(["describe", "--dirty"]).output() {
if git_out.status.success() {
if let Ok(git_out_str) = String::from_utf8(git_out.stdout) {
version = git_out_str;
}
}
}
// This println!() has a special behavior, as it will set the environment

View File

@@ -1,50 +1,32 @@
[package]
authors = ["The Chromium OS Authors"]
edition.workspace = true
name = "devices"
version = "0.1.0"
authors = ["The Chromium OS Authors"]
edition = "2021"
[dependencies]
acpi_tables = { workspace = true }
anyhow = { workspace = true }
acpi_tables = { git = "https://github.com/rust-vmm/acpi_tables", branch = "main" }
anyhow = "1.0.75"
arch = { path = "../arch" }
bitfield-struct = { version = "0.10.1", optional = true }
bitflags = { workspace = true }
byteorder = { workspace = true }
bitflags = "2.4.1"
byteorder = "1.4.3"
event_monitor = { path = "../event_monitor" }
hypervisor = { path = "../hypervisor" }
libc = { workspace = true }
linux-loader = { workspace = true, features = [
"bzimage",
"elf",
"pe",
], optional = true }
log = { workspace = true }
num_enum = "0.7.2"
libc = "0.2.147"
log = "0.4.20"
pci = { path = "../pci" }
serde = { workspace = true, features = ["derive"] }
thiserror = { workspace = true }
thiserror = "1.0.40"
tpm = { path = "../tpm" }
versionize = "0.2.0"
versionize_derive = "0.1.6"
vm-allocator = { path = "../vm-allocator" }
vm-device = { path = "../vm-device" }
vm-memory = { workspace = true, features = [
"backend-atomic",
"backend-bitmap",
"backend-mmap",
] }
vm-memory = "0.14.0"
vm-migration = { path = "../vm-migration" }
vmm-sys-util = { workspace = true }
zerocopy = { version = "0.8.26", features = [
"alloc",
"derive",
], optional = true }
vmm-sys-util = "0.12.1"
[target.'cfg(any(target_arch = "aarch64", target_arch = "riscv64"))'.dependencies]
[target.'cfg(target_arch = "aarch64")'.dependencies]
arch = { path = "../arch" }
[features]
default = []
fw_cfg = ["arch/fw_cfg", "bitfield-struct", "linux-loader", "zerocopy"]
ivshmem = []
kvm = ["arch/kvm"]
pvmemcontrol = []

View File

@@ -3,19 +3,17 @@
// SPDX-License-Identifier: Apache-2.0
//
use super::AcpiNotificationFlags;
use acpi_tables::{aml, Aml, AmlSink};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Barrier};
use std::thread;
use std::time::Instant;
use acpi_tables::{Aml, AmlSink, aml};
use vm_device::BusDevice;
use vm_device::interrupt::InterruptSourceGroup;
use vm_device::BusDevice;
use vm_memory::GuestAddress;
use vmm_sys_util::eventfd::EventFd;
use super::AcpiNotificationFlags;
pub const GED_DEVICE_ACPI_SIZE: usize = 0x1;
/// A device for handling ACPI shutdown and reboot

View File

@@ -1,144 +0,0 @@
// Copyright © 2024 Institute of Software, CAS. All rights reserved.
// Copyright 2020, ARM Limited.
//
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
use super::interrupt_controller::{Error, InterruptController};
extern crate arch;
use std::result;
use std::sync::{Arc, Mutex};
use arch::layout;
use hypervisor::arch::riscv64::aia::{Vaia, VaiaConfig};
use hypervisor::{AiaState, CpuState};
use vm_device::interrupt::{
InterruptIndex, InterruptManager, InterruptSourceConfig, InterruptSourceGroup,
LegacyIrqSourceConfig, MsiIrqGroupConfig,
};
use vm_memory::address::Address;
use vm_migration::{Migratable, Pausable, Snapshottable, Transportable};
use vmm_sys_util::eventfd::EventFd;
type Result<T> = result::Result<T, Error>;
// Reserve 32 IRQs for legacy devices.
pub const IRQ_LEGACY_BASE: usize = layout::IRQ_BASE as usize;
pub const IRQ_LEGACY_COUNT: usize = 32;
// TODO: AIA snapshotting is not yet completed.
pub const _AIA_SNAPSHOT_ID: &str = "";
// Aia (Advance Interrupt Architecture) struct provides all the functionality of a
// AIA device. It wraps a hypervisor-emulated AIA device (Vaia) provided by the
// `hypervisor` crate.
// Aia struct also implements InterruptController to provide interrupt delivery
// service.
pub struct Aia {
interrupt_source_group: Arc<dyn InterruptSourceGroup>,
// The hypervisor agnostic virtual AIA
vaia: Arc<Mutex<dyn Vaia>>,
}
impl Aia {
pub fn new(
vcpu_count: u32,
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 {}

View File

@@ -1,65 +0,0 @@
// Copyright © 2023 Cyberus Technology
//
// SPDX-License-Identifier: Apache-2.0
//
//! Module for [`DebugconState`].
use std::io;
use std::io::Write;
use std::sync::{Arc, Barrier};
use vm_device::BusDevice;
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
/// I/O-port.
pub const DEFAULT_PORT: u64 = 0xe9;
#[derive(Default)]
pub struct DebugconState {}
/// Emulates a debug console similar to the QEMU debugcon device. This device
/// is stateless and only prints the bytes (usually text) that are written to
/// it.
///
/// This device is only available on x86.
///
/// Reference:
/// - https://github.com/qemu/qemu/blob/master/hw/char/debugcon.c
/// - https://phip1611.de/blog/how-to-use-qemus-debugcon-feature-and-write-to-a-file/
pub struct DebugConsole {
id: String,
out: Box<dyn io::Write + Send>,
}
impl DebugConsole {
pub fn new(id: String, out: Box<dyn io::Write + Send>) -> Self {
Self { id, out }
}
}
impl BusDevice for DebugConsole {
fn read(&mut self, _base: u64, _offset: u64, _data: &mut [u8]) {}
fn write(&mut self, _base: u64, _offset: u64, data: &[u8]) -> Option<Arc<Barrier>> {
if let Err(e) = self.out.write_all(data) {
// unlikely
error!("debug-console: failed writing data: {e:?}");
}
None
}
}
impl Snapshottable for DebugConsole {
fn id(&self) -> String {
self.id.clone()
}
fn snapshot(&mut self) -> Result<Snapshot, MigratableError> {
Snapshot::new_from_state(&())
}
}
impl Pausable for DebugConsole {}
impl Transportable for DebugConsole {}
impl Migratable for DebugConsole {}

View File

@@ -4,13 +4,14 @@
use super::interrupt_controller::{Error, InterruptController};
extern crate arch;
use std::result;
use std::sync::{Arc, Mutex};
use anyhow::anyhow;
use arch::layout;
use hypervisor::CpuState;
use hypervisor::arch::aarch64::gic::{GicState, Vgic, VgicConfig};
use hypervisor::{
arch::aarch64::gic::{Vgic, VgicConfig},
CpuState, GicState,
};
use std::result;
use std::sync::{Arc, Mutex};
use vm_device::interrupt::{
InterruptIndex, InterruptManager, InterruptSourceConfig, InterruptSourceGroup,
LegacyIrqSourceConfig, MsiIrqGroupConfig,
@@ -39,7 +40,7 @@ pub struct Gic {
impl Gic {
pub fn new(
vcpu_count: u32,
vcpu_count: u8,
interrupt_manager: Arc<dyn InterruptManager<GroupConfig = MsiIrqGroupConfig>>,
vm: Arc<dyn hypervisor::Vm>,
) -> Result<Gic> {

View File

@@ -1,55 +1,35 @@
// Copyright © 2024 Institute of Software, CAS. All rights reserved.
// Copyright 2020, ARM Limited.
//
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
use std::{io, result};
use thiserror::Error;
use std::io;
use std::result;
use vmm_sys_util::eventfd::EventFd;
#[derive(Debug, Error)]
#[derive(Debug)]
pub enum Error {
/// Invalid trigger mode.
#[error("Invalid trigger mode")]
InvalidTriggerMode,
/// Invalid delivery mode.
#[error("Invalid delivery mode")]
InvalidDeliveryMode,
/// Failed creating the interrupt source group.
#[error("Failed creating the interrupt source group")]
CreateInterruptSourceGroup(#[source] io::Error),
CreateInterruptSourceGroup(io::Error),
/// Failed triggering the interrupt.
#[error("Failed triggering the interrupt")]
TriggerInterrupt(#[source] io::Error),
TriggerInterrupt(io::Error),
/// Failed masking the interrupt.
#[error("Failed masking the interrupt")]
MaskInterrupt(#[source] io::Error),
MaskInterrupt(io::Error),
/// Failed unmasking the interrupt.
#[error("Failed unmasking the interrupt")]
UnmaskInterrupt(#[source] io::Error),
UnmaskInterrupt(io::Error),
/// Failed updating the interrupt.
#[error("Failed updating the interrupt")]
UpdateInterrupt(#[source] io::Error),
UpdateInterrupt(io::Error),
/// Failed enabling the interrupt.
#[error("Failed enabling the interrupt")]
EnableInterrupt(#[source] io::Error),
EnableInterrupt(io::Error),
#[cfg(target_arch = "aarch64")]
/// Failed creating GIC device.
#[error("Failed creating GIC device")]
CreateGic(#[source] hypervisor::HypervisorVmError),
CreateGic(hypervisor::HypervisorVmError),
#[cfg(target_arch = "aarch64")]
/// Failed restoring GIC device.
#[error("Failed restoring GIC device")]
RestoreGic(#[source] hypervisor::arch::aarch64::gic::Error),
#[cfg(target_arch = "riscv64")]
/// Failed creating AIA device.
#[error("Failed creating AIA device")]
CreateAia(#[source] hypervisor::HypervisorVmError),
#[cfg(target_arch = "riscv64")]
/// Failed restoring AIA device.
#[error("Failed restoring AIA device")]
RestoreAia(#[source] hypervisor::arch::riscv64::aia::Error),
RestoreGic(hypervisor::arch::aarch64::gic::Error),
}
type Result<T> = result::Result<T, Error>;
@@ -76,7 +56,7 @@ pub struct MsiMessage {
// Introduce trait InterruptController to uniform the interrupt
// service provided for devices.
// Device manager uses this trait without caring whether it is a
// IOAPIC (X86), GIC (Arm) or AIA (RISC-V).
// IOAPIC (X86) or GIC (Arm).
pub trait InterruptController: Send {
fn service_irq(&mut self, irq: usize) -> Result<()>;
#[cfg(target_arch = "x86_64")]

View File

@@ -9,22 +9,23 @@
// Implementation of an intel 82093AA Input/Output Advanced Programmable Interrupt Controller
// See https://pdos.csail.mit.edu/6.828/2016/readings/ia32/ioapic.pdf for a specification.
use super::interrupt_controller::{Error, InterruptController};
use byteorder::{ByteOrder, LittleEndian};
use std::result;
use std::sync::{Arc, Barrier};
use byteorder::{ByteOrder, LittleEndian};
use serde::{Deserialize, Serialize};
use vm_device::BusDevice;
use versionize::{VersionMap, Versionize, VersionizeResult};
use versionize_derive::Versionize;
use vm_device::interrupt::{
InterruptIndex, InterruptManager, InterruptSourceConfig, InterruptSourceGroup,
MsiIrqGroupConfig, MsiIrqSourceConfig,
};
use vm_device::BusDevice;
use vm_memory::GuestAddress;
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
use vm_migration::{
Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable, VersionMapped,
};
use vmm_sys_util::eventfd::EventFd;
use super::interrupt_controller::{Error, InterruptController};
type Result<T> = result::Result<T, Error>;
// I/O REDIRECTION TABLE REGISTER
@@ -135,7 +136,7 @@ pub struct Ioapic {
interrupt_source_group: Arc<dyn InterruptSourceGroup>,
}
#[derive(Serialize, Deserialize)]
#[derive(Versionize)]
pub struct IoapicState {
id_reg: u32,
reg_sel: u32,
@@ -143,6 +144,7 @@ pub struct IoapicState {
used_entries: [bool; NUM_IOAPIC_PINS],
apic_address: u64,
}
impl VersionMapped for IoapicState {}
impl BusDevice for Ioapic {
fn read(&mut self, _base: u64, offset: u64, data: &mut [u8]) {
@@ -346,9 +348,9 @@ impl Ioapic {
// Generate MSI message address
let low_addr: u32 = self.apic_address.0 as u32
| (u32::from(destination_id) << 12)
| (u32::from(redirection_hint) << 3)
| (u32::from(destination_mode) << 2);
| u32::from(destination_id) << 12
| u32::from(redirection_hint) << 3
| u32::from(destination_mode) << 2;
// Validate Trigger Mode value
let trigger_mode = trigger_mode(entry);
@@ -372,9 +374,9 @@ impl Ioapic {
}
// Generate MSI message data
let data: u32 = (u32::from(trigger_mode) << 15)
| (u32::from(remote_irr(entry)) << 14)
| (u32::from(delivery_mode) << 8)
let data: u32 = u32::from(trigger_mode) << 15
| u32::from(remote_irr(entry)) << 14
| u32::from(delivery_mode) << 8
| u32::from(vector(entry));
let config = MsiIrqSourceConfig {
@@ -418,7 +420,7 @@ impl InterruptController for Ioapic {
self.interrupt_source_group
.trigger(irq as InterruptIndex)
.map_err(Error::TriggerInterrupt)?;
trace!("Interrupt {irq} successfully delivered");
debug!("Interrupt successfully delivered");
// If trigger mode is level sensitive, set the Remote IRR bit.
// It will be cleared when the EOI is received.
@@ -442,7 +444,7 @@ impl Snapshottable for Ioapic {
}
fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
Snapshot::new_from_state(&self.state())
Snapshot::new_from_versioned_state(&self.state())
}
}

View File

@@ -1,420 +0,0 @@
// Copyright © 2024 Tencent Corporation. All rights reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
use std::any::Any;
use std::path::PathBuf;
use std::result;
use std::sync::atomic::{AtomicU32, Ordering};
use std::sync::{Arc, Barrier, Mutex};
use anyhow::anyhow;
use byteorder::{ByteOrder, LittleEndian};
use pci::{
BarReprogrammingParams, PCI_CONFIGURATION_ID, PciBarConfiguration, PciBarPrefetchable,
PciBarRegionType, PciClassCode, PciConfiguration, PciDevice, PciDeviceError, PciHeaderType,
PciSubclass,
};
use serde::{Deserialize, Serialize};
use thiserror::Error;
use vm_allocator::{AddressAllocator, SystemAllocator};
use vm_device::{BusDevice, Resource, UserspaceMapping};
use vm_memory::bitmap::AtomicBitmap;
use vm_memory::{Address, GuestAddress};
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
const IVSHMEM_BAR0_IDX: usize = 0;
const IVSHMEM_BAR1_IDX: usize = 1;
const IVSHMEM_BAR2_IDX: usize = 2;
const IVSHMEM_VENDOR_ID: u16 = 0x1af4;
const IVSHMEM_DEVICE_ID: u16 = 0x1110;
const IVSHMEM_REG_BAR_SIZE: u64 = 0x100;
type GuestRegionMmap = vm_memory::GuestRegionMmap<AtomicBitmap>;
#[derive(Debug, Error)]
pub enum IvshmemError {
#[error("Failed to retrieve PciConfigurationState: {0}")]
RetrievePciConfigurationState(#[source] anyhow::Error),
#[error("Failed to retrieve IvshmemDeviceState: {0}")]
RetrieveIvshmemDeviceStateState(#[source] anyhow::Error),
#[error("Failed to remove user memory region")]
RemoveUserMemoryRegion,
#[error("Failed to create user memory region.")]
CreateUserMemoryRegion,
#[error("Failed to create userspace mapping.")]
CreateUserspaceMapping,
#[error("Failed to remove old userspace mapping.")]
RemoveUserspaceMapping,
}
#[derive(Copy, Clone)]
pub enum IvshmemSubclass {
Other = 0x00,
}
impl PciSubclass for IvshmemSubclass {
fn get_register_value(&self) -> u8 {
*self as u8
}
}
pub trait IvshmemOps: Send + Sync {
fn map_ram_region(
&mut self,
start_addr: u64,
size: usize,
backing_file: Option<PathBuf>,
) -> Result<(Arc<GuestRegionMmap>, UserspaceMapping), IvshmemError>;
fn unmap_ram_region(&mut self, mapping: UserspaceMapping) -> Result<(), IvshmemError>;
}
/// Inner-Vm Shared Memory Device (Ivshmem device)
///
/// This device can share memory between host and guest(ivshmem-plain)
/// and share memory between guests(ivshmem-doorbell).
/// But only ivshmem-plain support now, ivshmem-doorbell doesn't support yet.
pub struct IvshmemDevice {
id: String,
// ivshmem device registers
// (only used for ivshmem-doorbell, ivshmem-doorbell don't support yet)
_interrupt_mask: u32,
_interrupt_status: Arc<AtomicU32>,
_iv_position: u32,
_doorbell: u32,
// PCI configuration registers.
configuration: PciConfiguration,
bar_regions: Vec<PciBarConfiguration>,
region_size: u64,
ivshmem_ops: Arc<Mutex<dyn IvshmemOps>>,
backend_file: Option<PathBuf>,
region: Option<Arc<GuestRegionMmap>>,
userspace_mapping: Option<UserspaceMapping>,
}
#[derive(Serialize, Deserialize, Default, Clone)]
pub struct IvshmemDeviceState {
interrupt_mask: u32,
interrupt_status: u32,
iv_position: u32,
doorbell: u32,
}
impl IvshmemDevice {
pub fn new(
id: String,
region_size: u64,
backend_file: Option<PathBuf>,
ivshmem_ops: Arc<Mutex<dyn IvshmemOps>>,
snapshot: Option<Snapshot>,
) -> Result<Self, IvshmemError> {
let pci_configuration_state =
vm_migration::state_from_id(snapshot.as_ref(), PCI_CONFIGURATION_ID).map_err(|e| {
IvshmemError::RetrievePciConfigurationState(anyhow!(
"Failed to get PciConfigurationState from Snapshot: {e}",
))
})?;
let state: Option<IvshmemDeviceState> = snapshot
.as_ref()
.map(|s| s.to_state())
.transpose()
.map_err(|e| {
IvshmemError::RetrieveIvshmemDeviceStateState(anyhow!(
"Failed to get IvshmemDeviceState from Snapshot: {e}",
))
})?;
let configuration = PciConfiguration::new(
IVSHMEM_VENDOR_ID,
IVSHMEM_DEVICE_ID,
0x1,
PciClassCode::MemoryController,
&IvshmemSubclass::Other,
None,
PciHeaderType::Device,
0,
0,
None,
pci_configuration_state,
);
let device = if let Some(s) = state {
IvshmemDevice {
id,
configuration,
bar_regions: vec![],
_interrupt_mask: s.interrupt_mask,
_interrupt_status: Arc::new(AtomicU32::new(s.interrupt_status)),
_iv_position: s.iv_position,
_doorbell: s.doorbell,
region_size,
ivshmem_ops,
region: None,
userspace_mapping: None,
backend_file,
}
} else {
IvshmemDevice {
id,
configuration,
bar_regions: vec![],
_interrupt_mask: 0,
_interrupt_status: Arc::new(AtomicU32::new(0)),
_iv_position: 0,
_doorbell: 0,
region_size,
ivshmem_ops,
region: None,
userspace_mapping: None,
backend_file,
}
};
Ok(device)
}
pub fn set_region(
&mut self,
region: Arc<GuestRegionMmap>,
userspace_mapping: UserspaceMapping,
) {
self.region = Some(region);
self.userspace_mapping = Some(userspace_mapping);
}
pub fn config_bar_addr(&self) -> u64 {
self.configuration.get_bar_addr(IVSHMEM_BAR0_IDX)
}
pub fn data_bar_addr(&self) -> u64 {
self.configuration.get_bar_addr(IVSHMEM_BAR2_IDX)
}
fn state(&self) -> IvshmemDeviceState {
IvshmemDeviceState {
interrupt_mask: self._interrupt_mask,
interrupt_status: self._interrupt_status.load(Ordering::SeqCst),
iv_position: self._iv_position,
doorbell: self._doorbell,
}
}
}
impl BusDevice for IvshmemDevice {
fn read(&mut self, base: u64, offset: u64, data: &mut [u8]) {
self.read_bar(base, offset, data)
}
fn write(&mut self, base: u64, offset: u64, data: &[u8]) -> Option<Arc<Barrier>> {
self.write_bar(base, offset, data)
}
}
impl PciDevice for IvshmemDevice {
fn allocate_bars(
&mut self,
_allocator: &Arc<Mutex<SystemAllocator>>,
mmio32_allocator: &mut AddressAllocator,
mmio64_allocator: &mut AddressAllocator,
resources: Option<Vec<Resource>>,
) -> std::result::Result<Vec<PciBarConfiguration>, PciDeviceError> {
let mut bars = Vec::new();
let mut bar0_addr = None;
let mut bar2_addr = None;
let restoring = resources.is_some();
if let Some(resources) = resources {
for resource in resources {
match resource {
Resource::PciBar { index, base, .. } => {
match index {
IVSHMEM_BAR0_IDX => {
bar0_addr = Some(GuestAddress(base));
}
IVSHMEM_BAR1_IDX => {}
IVSHMEM_BAR2_IDX => {
bar2_addr = Some(GuestAddress(base));
}
_ => {
error!("Unexpected pci bar index {index}");
}
};
}
_ => {
error!("Unexpected resource {resource:?}");
}
}
}
if bar0_addr.is_none() || bar2_addr.is_none() {
return Err(PciDeviceError::MissingResource);
}
}
// BAR0 holds device registers (256 Byte MMIO)
let bar0_addr = mmio32_allocator
.allocate(bar0_addr, IVSHMEM_REG_BAR_SIZE, None)
.ok_or(PciDeviceError::IoAllocationFailed(IVSHMEM_REG_BAR_SIZE))?;
debug!("ivshmem bar0 address 0x{:x}", bar0_addr.0);
let bar0 = PciBarConfiguration::default()
.set_index(IVSHMEM_BAR0_IDX)
.set_address(bar0_addr.raw_value())
.set_size(IVSHMEM_REG_BAR_SIZE)
.set_region_type(PciBarRegionType::Memory32BitRegion)
.set_prefetchable(PciBarPrefetchable::NotPrefetchable);
// BAR1 holds MSI-X table and PBA (only ivshmem-doorbell).
// BAR2 maps the shared memory object
let bar2_size = self.region_size;
let bar2_addr = mmio64_allocator
.allocate(bar2_addr, bar2_size, None)
.ok_or(PciDeviceError::IoAllocationFailed(bar2_size))?;
debug!("ivshmem bar2 address 0x{:x}", bar2_addr.0);
let bar2 = PciBarConfiguration::default()
.set_index(IVSHMEM_BAR2_IDX)
.set_address(bar2_addr.raw_value())
.set_size(bar2_size)
.set_region_type(PciBarRegionType::Memory64BitRegion)
.set_prefetchable(PciBarPrefetchable::Prefetchable);
if !restoring {
self.configuration
.add_pci_bar(&bar0)
.map_err(|e| PciDeviceError::IoRegistrationFailed(bar0_addr.raw_value(), e))?;
self.configuration
.add_pci_bar(&bar2)
.map_err(|e| PciDeviceError::IoRegistrationFailed(bar2_addr.raw_value(), e))?;
}
bars.push(bar0);
bars.push(bar2);
self.bar_regions = bars.clone();
Ok(bars)
}
fn free_bars(
&mut self,
_allocator: &mut SystemAllocator,
_mmio32_allocator: &mut AddressAllocator,
_mmio64_allocator: &mut AddressAllocator,
) -> std::result::Result<(), PciDeviceError> {
unimplemented!("Device hotplug and remove are not supported for ivshmem");
}
fn write_config_register(
&mut self,
reg_idx: usize,
offset: u64,
data: &[u8],
) -> (Vec<BarReprogrammingParams>, Option<Arc<Barrier>>) {
(
self.configuration
.write_config_register(reg_idx, offset, data),
None,
)
}
fn read_config_register(&mut self, reg_idx: usize) -> u32 {
self.configuration.read_reg(reg_idx)
}
fn read_bar(&mut self, base: u64, offset: u64, data: &mut [u8]) {
debug!("read base {base:x} offset {offset}");
let mut bar_idx = 0;
for (idx, bar) in self.bar_regions.iter().enumerate() {
if bar.addr() == base {
bar_idx = idx;
}
}
match bar_idx {
// bar 0
0 => {
// ivshmem don't use interrupt, we return zero now.
LittleEndian::write_u32(data, 0);
}
// bar 2
1 => warn!("Unexpected read ivshmem memory idx: {offset}"),
_ => {
warn!("Invalid bar_idx: {bar_idx}");
}
};
}
fn write_bar(&mut self, base: u64, offset: u64, _data: &[u8]) -> Option<Arc<Barrier>> {
debug!("write base {base:x} offset {offset}");
warn!("Unexpected write ivshmem memory idx: {offset}");
None
}
fn move_bar(&mut self, old_base: u64, new_base: u64) -> result::Result<(), std::io::Error> {
if new_base == self.data_bar_addr() {
if let Some(old_mapping) = self.userspace_mapping.take() {
self.ivshmem_ops
.lock()
.unwrap()
.unmap_ram_region(old_mapping)
.map_err(std::io::Error::other)?;
}
let (region, new_mapping) = self
.ivshmem_ops
.lock()
.unwrap()
.map_ram_region(
new_base,
self.region_size as usize,
self.backend_file.clone(),
)
.map_err(std::io::Error::other)?;
self.set_region(region, new_mapping);
}
for bar in self.bar_regions.iter_mut() {
if bar.addr() == old_base {
*bar = bar.set_address(new_base);
}
}
Ok(())
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
fn id(&self) -> Option<String> {
Some(self.id.clone())
}
}
impl Pausable for IvshmemDevice {}
impl Snapshottable for IvshmemDevice {
fn id(&self) -> String {
self.id.clone()
}
// The snapshot/restore (also live migration) support only work for ivshmem-plain mode.
// Additional work is needed for supporting ivshmem-doorbell.
fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
let mut snapshot = Snapshot::new_from_state(&self.state())?;
// Snapshot PciConfiguration
snapshot.add_snapshot(self.configuration.id(), self.configuration.snapshot()?);
Ok(snapshot)
}
}
impl Transportable for IvshmemDevice {}
impl Migratable for IvshmemDevice {}

View File

@@ -1,20 +1,19 @@
// Copyright 2017 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
use libc::{clock_gettime, gmtime_r, timespec, tm, CLOCK_REALTIME};
use std::cmp::min;
use std::mem;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Barrier};
use std::{mem, thread};
use std::thread;
use vm_device::BusDevice;
use vmm_sys_util::eventfd::EventFd;
// https://github.com/rust-lang/libc/issues/1848
#[cfg_attr(target_env = "musl", allow(deprecated))]
use libc::time_t;
use libc::{CLOCK_REALTIME, clock_gettime, gmtime_r, timespec, tm};
use vm_device::BusDevice;
use vmm_sys_util::eventfd::EventFd;
const INDEX_MASK: u8 = 0x7f;
const INDEX_OFFSET: u64 = 0x0;
@@ -152,7 +151,7 @@ impl BusDevice for Cmos {
0x08 => to_bcd(month as u8),
0x09 => to_bcd((year % 100) as u8),
// Bit 5 for 32kHz clock. Bit 7 for Update in Progress
0x0a => (1 << 5) | ((update_in_progress as u8) << 7),
0x0a => 1 << 5 | (update_in_progress as u8) << 7,
// Bit 0-6 are reserved and must be 0.
// Bit 7 must be 1 (CMOS has power)
0x0d => 1 << 7,

View File

@@ -5,7 +5,6 @@
use std::fmt;
use std::time::Instant;
use vm_device::BusDevice;
/// Debug I/O port, see:

View File

@@ -1,954 +0,0 @@
// Copyright 2025 Google LLC.
//
// SPDX-License-Identifier: Apache-2.0
//
/// Cloud Hypervisor implementation of Qemu's fw_cfg spec
/// https://www.qemu.org/docs/master/specs/fw_cfg.html
/// Linux kernel fw_cfg driver header
/// https://github.com/torvalds/linux/blob/master/include/uapi/linux/qemu_fw_cfg.h
/// Uploading files to the guest via fw_cfg is supported for all kernels 4.6+ w/ CONFIG_FW_CFG_SYSFS enabled
/// https://cateee.net/lkddb/web-lkddb/FW_CFG_SYSFS.html
/// No kernel requirement if above functionality is not required,
/// only firmware must implement mechanism to interact with this fw_cfg device
use std::{
fs::File,
io::{ErrorKind, Read, Result, Seek, SeekFrom},
mem::offset_of,
os::unix::fs::FileExt,
sync::{Arc, Barrier},
};
use acpi_tables::rsdp::Rsdp;
use arch::RegionType;
#[cfg(target_arch = "aarch64")]
use arch::aarch64::layout::{
MEM_32BIT_DEVICES_START, MEM_32BIT_RESERVED_START, RAM_64BIT_START, RAM_START as HIGH_RAM_START,
};
#[cfg(target_arch = "x86_64")]
use arch::layout::{
EBDA_START, HIGH_RAM_START, MEM_32BIT_DEVICES_SIZE, MEM_32BIT_DEVICES_START,
MEM_32BIT_RESERVED_START, PCI_MMCONFIG_SIZE, PCI_MMCONFIG_START, RAM_64BIT_START,
};
use bitfield_struct::bitfield;
#[cfg(target_arch = "x86_64")]
use linux_loader::bootparam::boot_params;
#[cfg(target_arch = "aarch64")]
use linux_loader::loader::pe::arm64_image_header as boot_params;
use vm_device::BusDevice;
use vm_memory::bitmap::AtomicBitmap;
use vm_memory::{
ByteValued, Bytes, GuestAddress, GuestAddressSpace, GuestMemoryAtomic, GuestMemoryMmap,
};
use vmm_sys_util::sock_ctrl_msg::IntoIovec;
use zerocopy::{FromBytes, FromZeros, Immutable, IntoBytes};
#[cfg(target_arch = "x86_64")]
// https://github.com/project-oak/oak/tree/main/stage0_bin#memory-layout
const STAGE0_START_ADDRESS: GuestAddress = GuestAddress(0xfffe_0000);
#[cfg(target_arch = "x86_64")]
const STAGE0_SIZE: usize = 0x2_0000;
const E820_RAM: u32 = 1;
const E820_RESERVED: u32 = 2;
#[cfg(target_arch = "x86_64")]
const PORT_FW_CFG_SELECTOR: u64 = 0x510;
#[cfg(target_arch = "x86_64")]
const PORT_FW_CFG_DATA: u64 = 0x511;
#[cfg(target_arch = "x86_64")]
const PORT_FW_CFG_DMA_HI: u64 = 0x514;
#[cfg(target_arch = "x86_64")]
const PORT_FW_CFG_DMA_LO: u64 = 0x518;
#[cfg(target_arch = "x86_64")]
pub const PORT_FW_CFG_BASE: u64 = 0x510;
#[cfg(target_arch = "x86_64")]
pub const PORT_FW_CFG_WIDTH: u64 = 0xc;
#[cfg(target_arch = "aarch64")]
const PORT_FW_CFG_SELECTOR: u64 = 0x9030008;
#[cfg(target_arch = "aarch64")]
const PORT_FW_CFG_DATA: u64 = 0x9030000;
#[cfg(target_arch = "aarch64")]
const PORT_FW_CFG_DMA_HI: u64 = 0x9030010;
#[cfg(target_arch = "aarch64")]
const PORT_FW_CFG_DMA_LO: u64 = 0x9030014;
#[cfg(target_arch = "aarch64")]
pub const PORT_FW_CFG_BASE: u64 = 0x9030000;
#[cfg(target_arch = "aarch64")]
pub const PORT_FW_CFG_WIDTH: u64 = 0x10;
const FW_CFG_SIGNATURE: u16 = 0x00;
const FW_CFG_ID: u16 = 0x01;
const FW_CFG_KERNEL_SIZE: u16 = 0x08;
const FW_CFG_INITRD_SIZE: u16 = 0x0b;
const FW_CFG_KERNEL_DATA: u16 = 0x11;
const FW_CFG_INITRD_DATA: u16 = 0x12;
const FW_CFG_CMDLINE_SIZE: u16 = 0x14;
const FW_CFG_CMDLINE_DATA: u16 = 0x15;
const FW_CFG_SETUP_SIZE: u16 = 0x17;
const FW_CFG_SETUP_DATA: u16 = 0x18;
const FW_CFG_FILE_DIR: u16 = 0x19;
const FW_CFG_KNOWN_ITEMS: usize = 0x20;
pub const FW_CFG_FILE_FIRST: u16 = 0x20;
pub const FW_CFG_DMA_SIGNATURE: [u8; 8] = *b"QEMU CFG";
// https://github.com/torvalds/linux/blob/master/include/uapi/linux/qemu_fw_cfg.h
pub const FW_CFG_ACPI_ID: &str = "QEMU0002";
// Reserved (must be enabled)
const FW_CFG_F_RESERVED: u8 = 1 << 0;
// DMA Toggle Bit (enabled by default)
const FW_CFG_F_DMA: u8 = 1 << 1;
pub const FW_CFG_FEATURE: [u8; 4] = [FW_CFG_F_RESERVED | FW_CFG_F_DMA, 0, 0, 0];
const COMMAND_ALLOCATE: u32 = 0x1;
const COMMAND_ADD_POINTER: u32 = 0x2;
const COMMAND_ADD_CHECKSUM: u32 = 0x3;
const ALLOC_ZONE_HIGH: u8 = 0x1;
const ALLOC_ZONE_FSEG: u8 = 0x2;
const FW_CFG_FILENAME_TABLE_LOADER: &str = "etc/table-loader";
const FW_CFG_FILENAME_RSDP: &str = "acpi/rsdp";
const FW_CFG_FILENAME_ACPI_TABLES: &str = "acpi/tables";
#[derive(Debug)]
pub enum FwCfgContent {
Bytes(Vec<u8>),
Slice(&'static [u8]),
File(u64, File),
U32(u32),
}
struct FwCfgContentAccess<'a> {
content: &'a FwCfgContent,
offset: u32,
}
impl Read for FwCfgContentAccess<'_> {
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
match self.content {
FwCfgContent::File(offset, f) => {
Seek::seek(&mut (&*f), SeekFrom::Start(offset + self.offset as u64))?;
Read::read(&mut (&*f), buf)
}
FwCfgContent::Bytes(b) => match b.get(self.offset as usize..) {
Some(mut s) => s.read(buf),
None => Err(ErrorKind::UnexpectedEof)?,
},
FwCfgContent::Slice(b) => match b.get(self.offset as usize..) {
Some(mut s) => s.read(buf),
None => Err(ErrorKind::UnexpectedEof)?,
},
FwCfgContent::U32(n) => match n.to_le_bytes().get(self.offset as usize..) {
Some(mut s) => s.read(buf),
None => Err(ErrorKind::UnexpectedEof)?,
},
}
}
}
impl Default for FwCfgContent {
fn default() -> Self {
FwCfgContent::Slice(&[])
}
}
impl FwCfgContent {
fn size(&self) -> Result<u32> {
let ret = match self {
FwCfgContent::Bytes(v) => v.len(),
FwCfgContent::File(offset, f) => (f.metadata()?.len() - offset) as usize,
FwCfgContent::Slice(s) => s.len(),
FwCfgContent::U32(n) => size_of_val(n),
};
u32::try_from(ret).map_err(|_| std::io::ErrorKind::InvalidInput.into())
}
fn access(&self, offset: u32) -> FwCfgContentAccess<'_> {
FwCfgContentAccess {
content: self,
offset,
}
}
}
#[derive(Debug, Default)]
pub struct FwCfgItem {
pub name: String,
pub content: FwCfgContent,
}
/// https://www.qemu.org/docs/master/specs/fw_cfg.html
#[derive(Debug)]
pub struct FwCfg {
selector: u16,
data_offset: u32,
dma_address: u64,
items: Vec<FwCfgItem>, // 0x20 and above
known_items: [FwCfgContent; FW_CFG_KNOWN_ITEMS], // 0x0 to 0x19
memory: GuestMemoryAtomic<GuestMemoryMmap<AtomicBitmap>>,
}
#[repr(C)]
#[derive(Debug, IntoBytes, FromBytes)]
struct FwCfgDmaAccess {
control_be: u32,
length_be: u32,
address_be: u64,
}
// https://github.com/torvalds/linux/blob/master/include/uapi/linux/qemu_fw_cfg.h#L67
#[bitfield(u32)]
struct AccessControl {
// FW_CFG_DMA_CTL_ERROR = 0x01
error: bool,
// FW_CFG_DMA_CTL_READ = 0x02
read: bool,
#[bits(1)]
_unused2: u8,
// FW_CFG_DMA_CTL_SKIP = 0x04
skip: bool,
#[bits(3)]
_unused3: u8,
// FW_CFG_DMA_CTL_ERROR = 0x08
select: bool,
#[bits(7)]
_unused4: u8,
// FW_CFG_DMA_CTL_WRITE = 0x10
write: bool,
#[bits(16)]
_unused: u32,
}
#[repr(C)]
#[derive(Debug, IntoBytes, FromBytes)]
struct FwCfgFilesHeader {
count_be: u32,
}
pub const FILE_NAME_SIZE: usize = 56;
pub fn create_file_name(name: &str) -> [u8; FILE_NAME_SIZE] {
let mut c_name = [0u8; FILE_NAME_SIZE];
let c_len = std::cmp::min(FILE_NAME_SIZE - 1, name.len());
c_name[0..c_len].copy_from_slice(&name.as_bytes()[0..c_len]);
c_name
}
#[allow(dead_code)]
#[repr(C, packed)]
#[derive(Debug, IntoBytes, FromBytes, Clone, Copy)]
struct BootE820Entry {
addr: u64,
size: u64,
type_: u32,
}
#[repr(C)]
#[derive(Debug, IntoBytes, FromBytes)]
struct FwCfgFile {
size_be: u32,
select_be: u16,
_reserved: u16,
name: [u8; FILE_NAME_SIZE],
}
#[repr(C, align(4))]
#[derive(Debug, IntoBytes, Immutable)]
struct Allocate {
command: u32,
file: [u8; FILE_NAME_SIZE],
align: u32,
zone: u8,
_pad: [u8; 63],
}
#[repr(C, align(4))]
#[derive(Debug, IntoBytes, Immutable)]
struct AddPointer {
command: u32,
dst: [u8; FILE_NAME_SIZE],
src: [u8; FILE_NAME_SIZE],
offset: u32,
size: u8,
_pad: [u8; 7],
}
#[repr(C, align(4))]
#[derive(Debug, IntoBytes, Immutable)]
struct AddChecksum {
command: u32,
file: [u8; FILE_NAME_SIZE],
offset: u32,
start: u32,
len: u32,
_pad: [u8; 56],
}
fn create_intra_pointer(name: &str, offset: usize, size: u8) -> AddPointer {
AddPointer {
command: COMMAND_ADD_POINTER,
dst: create_file_name(name),
src: create_file_name(name),
offset: offset as u32,
size,
_pad: [0; 7],
}
}
fn create_acpi_table_checksum(offset: usize, len: usize) -> AddChecksum {
AddChecksum {
command: COMMAND_ADD_CHECKSUM,
file: create_file_name(FW_CFG_FILENAME_ACPI_TABLES),
offset: (offset + offset_of!(AcpiTableHeader, checksum)) as u32,
start: offset as u32,
len: len as u32,
_pad: [0; 56],
}
}
#[repr(C, align(4))]
#[derive(Debug, Clone, Default, FromBytes, IntoBytes)]
struct AcpiTableHeader {
signature: [u8; 4],
length: u32,
revision: u8,
checksum: u8,
oem_id: [u8; 6],
oem_table_id: [u8; 8],
oem_revision: u32,
asl_compiler_id: [u8; 4],
asl_compiler_revision: u32,
}
struct AcpiTable {
rsdp: Rsdp,
tables: Vec<u8>,
table_pointers: Vec<usize>,
table_checksums: Vec<(usize, usize)>,
}
impl AcpiTable {
fn pointers(&self) -> &[usize] {
&self.table_pointers
}
fn checksums(&self) -> &[(usize, usize)] {
&self.table_checksums
}
fn take(self) -> (Rsdp, Vec<u8>) {
(self.rsdp, self.tables)
}
}
// Creates fw_cfg items used by firmware to load and verify Acpi tables
// https://github.com/qemu/qemu/blob/master/hw/acpi/bios-linker-loader.c
fn create_acpi_loader(acpi_table: AcpiTable) -> [FwCfgItem; 3] {
let mut table_loader_bytes: Vec<u8> = Vec::new();
let allocate_rsdp = Allocate {
command: COMMAND_ALLOCATE,
file: create_file_name(FW_CFG_FILENAME_RSDP),
align: 4,
zone: ALLOC_ZONE_FSEG,
_pad: [0; 63],
};
table_loader_bytes.extend(allocate_rsdp.as_bytes());
let allocate_tables = Allocate {
command: COMMAND_ALLOCATE,
file: create_file_name(FW_CFG_FILENAME_ACPI_TABLES),
align: 4,
zone: ALLOC_ZONE_HIGH,
_pad: [0; 63],
};
table_loader_bytes.extend(allocate_tables.as_bytes());
for pointer_offset in acpi_table.pointers().iter() {
let pointer = create_intra_pointer(FW_CFG_FILENAME_ACPI_TABLES, *pointer_offset, 8);
table_loader_bytes.extend(pointer.as_bytes());
}
for (offset, len) in acpi_table.checksums().iter() {
let checksum = create_acpi_table_checksum(*offset, *len);
table_loader_bytes.extend(checksum.as_bytes());
}
let pointer_rsdp_to_xsdt = AddPointer {
command: COMMAND_ADD_POINTER,
dst: create_file_name(FW_CFG_FILENAME_RSDP),
src: create_file_name(FW_CFG_FILENAME_ACPI_TABLES),
offset: offset_of!(Rsdp, xsdt_addr) as u32,
size: 8,
_pad: [0; 7],
};
table_loader_bytes.extend(pointer_rsdp_to_xsdt.as_bytes());
let checksum_rsdp = AddChecksum {
command: COMMAND_ADD_CHECKSUM,
file: create_file_name(FW_CFG_FILENAME_RSDP),
offset: offset_of!(Rsdp, checksum) as u32,
start: 0,
len: offset_of!(Rsdp, length) as u32,
_pad: [0; 56],
};
let checksum_rsdp_ext = AddChecksum {
command: COMMAND_ADD_CHECKSUM,
file: create_file_name(FW_CFG_FILENAME_RSDP),
offset: offset_of!(Rsdp, extended_checksum) as u32,
start: 0,
len: size_of::<Rsdp>() as u32,
_pad: [0; 56],
};
table_loader_bytes.extend(checksum_rsdp.as_bytes());
table_loader_bytes.extend(checksum_rsdp_ext.as_bytes());
let table_loader = FwCfgItem {
name: FW_CFG_FILENAME_TABLE_LOADER.to_owned(),
content: FwCfgContent::Bytes(table_loader_bytes),
};
let (rsdp, tables) = acpi_table.take();
let acpi_rsdp = FwCfgItem {
name: FW_CFG_FILENAME_RSDP.to_owned(),
content: FwCfgContent::Bytes(rsdp.as_bytes().to_owned()),
};
let apci_tables = FwCfgItem {
name: FW_CFG_FILENAME_ACPI_TABLES.to_owned(),
content: FwCfgContent::Bytes(tables),
};
[table_loader, acpi_rsdp, apci_tables]
}
impl FwCfg {
pub fn new(memory: GuestMemoryAtomic<GuestMemoryMmap<AtomicBitmap>>) -> FwCfg {
const DEFAULT_ITEM: FwCfgContent = FwCfgContent::Slice(&[]);
let mut known_items = [DEFAULT_ITEM; FW_CFG_KNOWN_ITEMS];
known_items[FW_CFG_SIGNATURE as usize] = FwCfgContent::Slice(&FW_CFG_DMA_SIGNATURE);
known_items[FW_CFG_ID as usize] = FwCfgContent::Slice(&FW_CFG_FEATURE);
let file_buf = Vec::from(FwCfgFilesHeader { count_be: 0 }.as_mut_bytes());
known_items[FW_CFG_FILE_DIR as usize] = FwCfgContent::Bytes(file_buf);
FwCfg {
selector: 0,
data_offset: 0,
dma_address: 0,
items: vec![],
known_items,
memory,
}
}
pub fn populate_fw_cfg(
&mut self,
mem_size: Option<usize>,
kernel: Option<File>,
initramfs: Option<File>,
cmdline: Option<std::ffi::CString>,
fw_cfg_item_list: Option<Vec<FwCfgItem>>,
) -> Result<()> {
if let Some(mem_size) = mem_size {
self.add_e820(mem_size)?
}
if let Some(kernel) = kernel {
self.add_kernel_data(&kernel)?;
}
if let Some(cmdline) = cmdline {
self.add_kernel_cmdline(cmdline);
}
if let Some(initramfs) = initramfs {
self.add_initramfs_data(&initramfs)?
}
if let Some(fw_cfg_item_list) = fw_cfg_item_list {
for item in fw_cfg_item_list {
self.add_item(item)?;
}
}
Ok(())
}
pub fn add_e820(&mut self, mem_size: usize) -> Result<()> {
#[cfg(target_arch = "x86_64")]
let mut mem_regions = vec![
(GuestAddress(0), EBDA_START.0 as usize, RegionType::Ram),
(
MEM_32BIT_DEVICES_START,
MEM_32BIT_DEVICES_SIZE as usize,
RegionType::Reserved,
),
(
PCI_MMCONFIG_START,
PCI_MMCONFIG_SIZE as usize,
RegionType::Reserved,
),
(STAGE0_START_ADDRESS, STAGE0_SIZE, RegionType::Reserved),
];
#[cfg(target_arch = "aarch64")]
let mut mem_regions = arch::aarch64::arch_memory_regions();
if mem_size < MEM_32BIT_DEVICES_START.0 as usize {
mem_regions.push((
HIGH_RAM_START,
mem_size - HIGH_RAM_START.0 as usize,
RegionType::Ram,
));
} else {
mem_regions.push((
HIGH_RAM_START,
MEM_32BIT_RESERVED_START.0 as usize - HIGH_RAM_START.0 as usize,
RegionType::Ram,
));
mem_regions.push((
RAM_64BIT_START,
mem_size - (MEM_32BIT_DEVICES_START.0 as usize),
RegionType::Ram,
));
}
let mut bytes = vec![];
for (addr, size, region) in mem_regions.iter() {
let type_ = match region {
RegionType::Ram => E820_RAM,
RegionType::Reserved => E820_RESERVED,
RegionType::SubRegion => continue,
};
let mut entry = BootE820Entry {
addr: addr.0,
size: *size as u64,
type_,
};
bytes.extend_from_slice(entry.as_mut_bytes());
}
let item = FwCfgItem {
name: "etc/e820".to_owned(),
content: FwCfgContent::Bytes(bytes),
};
self.add_item(item)
}
fn file_dir_mut(&mut self) -> &mut Vec<u8> {
let FwCfgContent::Bytes(file_buf) = &mut self.known_items[FW_CFG_FILE_DIR as usize] else {
unreachable!("fw_cfg: selector {FW_CFG_FILE_DIR:#x} should be FwCfgContent::Byte!")
};
file_buf
}
fn update_count(&mut self) {
let mut header = FwCfgFilesHeader {
count_be: (self.items.len() as u32).to_be(),
};
self.file_dir_mut()[0..4].copy_from_slice(header.as_mut_bytes());
}
pub fn add_item(&mut self, item: FwCfgItem) -> Result<()> {
let index = self.items.len();
let c_name = create_file_name(&item.name);
let size = item.content.size()?;
let mut cfg_file = FwCfgFile {
size_be: size.to_be(),
select_be: (FW_CFG_FILE_FIRST + index as u16).to_be(),
_reserved: 0,
name: c_name,
};
self.file_dir_mut()
.extend_from_slice(cfg_file.as_mut_bytes());
self.items.push(item);
self.update_count();
Ok(())
}
fn dma_read_content(
&self,
content: &FwCfgContent,
offset: u32,
len: u32,
address: u64,
) -> Result<u32> {
let content_size = content.size()?.saturating_sub(offset);
let op_size = std::cmp::min(content_size, len);
let mut access = content.access(offset);
let mut buf = vec![0u8; op_size as usize];
access.read_exact(buf.as_mut_bytes())?;
let r = self
.memory
.memory()
.write(buf.as_bytes(), GuestAddress(address));
match r {
Err(e) => {
error!("fw_cfg: dma read error: {e:x?}");
Err(ErrorKind::InvalidInput.into())
}
Ok(size) => Ok(size as u32),
}
}
fn dma_read(&mut self, selector: u16, len: u32, address: u64) -> Result<()> {
let op_size = if let Some(content) = self.known_items.get(selector as usize) {
self.dma_read_content(content, self.data_offset, len, address)
} else if let Some(item) = self.items.get((selector - FW_CFG_FILE_FIRST) as usize) {
self.dma_read_content(&item.content, self.data_offset, len, address)
} else {
error!("fw_cfg: selector {selector:#x} does not exist.");
Err(ErrorKind::NotFound.into())
}?;
self.data_offset += op_size;
Ok(())
}
fn do_dma(&mut self) {
let dma_address = self.dma_address;
let mut access = FwCfgDmaAccess::new_zeroed();
let dma_access = match self
.memory
.memory()
.read(access.as_mut_bytes(), GuestAddress(dma_address))
{
Ok(_) => access,
Err(e) => {
error!("fw_cfg: invalid address of dma access {dma_address:#x}: {e:?}");
return;
}
};
let control = AccessControl(u32::from_be(dma_access.control_be));
if control.select() {
self.selector = control.select() as u16;
}
let len = u32::from_be(dma_access.length_be);
let addr = u64::from_be(dma_access.address_be);
let ret = if control.read() {
self.dma_read(self.selector, len, addr)
} else if control.write() {
Err(ErrorKind::InvalidInput.into())
} else if control.skip() {
self.data_offset += len;
Ok(())
} else {
Err(ErrorKind::InvalidData.into())
};
let mut access_resp = AccessControl(0);
if let Err(e) = ret {
error!("fw_cfg: dma operation {dma_access:x?}: {e:x?}");
access_resp.set_error(true);
}
if let Err(e) = self.memory.memory().write(
&access_resp.0.to_be_bytes(),
GuestAddress(dma_address + core::mem::offset_of!(FwCfgDmaAccess, control_be) as u64),
) {
error!("fw_cfg: finishing dma: {e:?}")
}
}
pub fn add_kernel_data(&mut self, file: &File) -> Result<()> {
let mut buffer = vec![0u8; size_of::<boot_params>()];
file.read_exact_at(&mut buffer, 0)?;
let bp = boot_params::from_mut_slice(&mut buffer).unwrap();
#[cfg(target_arch = "x86_64")]
{
// must set to 4 for backwards compatibility
// https://docs.kernel.org/arch/x86/boot.html#the-real-mode-kernel-header
if bp.hdr.setup_sects == 0 {
bp.hdr.setup_sects = 4;
}
// wildcard boot loader type
bp.hdr.type_of_loader = 0xff;
}
#[cfg(target_arch = "aarch64")]
let kernel_start = bp.text_offset;
#[cfg(target_arch = "x86_64")]
let kernel_start = (bp.hdr.setup_sects as usize + 1) * 512;
self.known_items[FW_CFG_SETUP_SIZE as usize] = FwCfgContent::U32(buffer.len() as u32);
self.known_items[FW_CFG_SETUP_DATA as usize] = FwCfgContent::Bytes(buffer);
self.known_items[FW_CFG_KERNEL_SIZE as usize] =
FwCfgContent::U32(file.metadata()?.len() as u32 - kernel_start as u32);
self.known_items[FW_CFG_KERNEL_DATA as usize] =
FwCfgContent::File(kernel_start as u64, file.try_clone()?);
Ok(())
}
pub fn add_kernel_cmdline(&mut self, s: std::ffi::CString) {
let bytes = s.into_bytes_with_nul();
self.known_items[FW_CFG_CMDLINE_SIZE as usize] = FwCfgContent::U32(bytes.len() as u32);
self.known_items[FW_CFG_CMDLINE_DATA as usize] = FwCfgContent::Bytes(bytes);
}
pub fn add_acpi(
&mut self,
rsdp: Rsdp,
tables: Vec<u8>,
table_checksums: Vec<(usize, usize)>,
table_pointers: Vec<usize>,
) -> Result<()> {
let acpi_table = AcpiTable {
rsdp,
tables,
table_checksums,
table_pointers,
};
let [table_loader, acpi_rsdp, apci_tables] = create_acpi_loader(acpi_table);
self.add_item(table_loader)?;
self.add_item(acpi_rsdp)?;
self.add_item(apci_tables)
}
pub fn add_initramfs_data(&mut self, file: &File) -> Result<()> {
let initramfs_size = file.metadata()?.len();
self.known_items[FW_CFG_INITRD_SIZE as usize] = FwCfgContent::U32(initramfs_size as _);
self.known_items[FW_CFG_INITRD_DATA as usize] = FwCfgContent::File(0, file.try_clone()?);
Ok(())
}
fn read_content(content: &FwCfgContent, offset: u32, data: &mut [u8], size: u32) -> Option<u8> {
let start = offset as usize;
let end = start + size as usize;
match content {
FwCfgContent::Bytes(b) => {
if b.len() >= size as usize {
data.copy_from_slice(&b[start..end]);
}
}
FwCfgContent::Slice(s) => {
if s.len() >= size as usize {
data.copy_from_slice(&s[start..end]);
}
}
FwCfgContent::File(o, f) => {
f.read_exact_at(data, o + offset as u64).ok()?;
}
FwCfgContent::U32(n) => {
let bytes = n.to_le_bytes();
data.copy_from_slice(&bytes[start..end]);
}
};
Some(size as u8)
}
fn read_data(&mut self, data: &mut [u8], size: u32) -> u8 {
let ret = if let Some(content) = self.known_items.get(self.selector as usize) {
Self::read_content(content, self.data_offset, data, size)
} else if let Some(item) = self.items.get((self.selector - FW_CFG_FILE_FIRST) as usize) {
Self::read_content(&item.content, self.data_offset, data, size)
} else {
error!("fw_cfg: selector {:#x} does not exist.", self.selector);
None
};
if let Some(val) = ret {
self.data_offset += size;
val
} else {
0
}
}
}
impl BusDevice for FwCfg {
fn read(&mut self, _base: u64, offset: u64, data: &mut [u8]) {
let port = offset + PORT_FW_CFG_BASE;
let size = data.len();
match (port, size) {
(PORT_FW_CFG_SELECTOR, _) => {
error!("fw_cfg: selector register is write-only.");
}
(PORT_FW_CFG_DATA, _) => _ = self.read_data(data, size as u32),
(PORT_FW_CFG_DMA_HI, 4) => {
let addr = self.dma_address;
let addr_hi = (addr >> 32) as u32;
data.copy_from_slice(&addr_hi.to_be_bytes());
}
(PORT_FW_CFG_DMA_LO, 4) => {
let addr = self.dma_address;
let addr_lo = (addr & 0xffff_ffff) as u32;
data.copy_from_slice(&addr_lo.to_be_bytes());
}
_ => {
debug!(
"fw_cfg: read from unknown port {port:#x}: {size:#x} bytes and offset {offset:#x}."
);
}
};
}
fn write(&mut self, _base: u64, offset: u64, data: &[u8]) -> Option<Arc<Barrier>> {
let port = offset + PORT_FW_CFG_BASE;
let size = data.size();
match (port, size) {
(PORT_FW_CFG_SELECTOR, 2) => {
let mut buf = [0u8; 2];
buf[..size].copy_from_slice(&data[..size]);
#[cfg(target_arch = "x86_64")]
let val = u16::from_le_bytes(buf);
#[cfg(target_arch = "aarch64")]
let val = u16::from_be_bytes(buf);
self.selector = val;
self.data_offset = 0;
}
(PORT_FW_CFG_DATA, 1) => error!("fw_cfg: data register is read-only."),
(PORT_FW_CFG_DMA_HI, 4) => {
let mut buf = [0u8; 4];
buf[..size].copy_from_slice(&data[..size]);
let val = u32::from_be_bytes(buf);
self.dma_address &= 0xffff_ffff;
self.dma_address |= (val as u64) << 32;
}
(PORT_FW_CFG_DMA_LO, 4) => {
let mut buf = [0u8; 4];
buf[..size].copy_from_slice(&data[..size]);
let val = u32::from_be_bytes(buf);
self.dma_address &= !0xffff_ffff;
self.dma_address |= val as u64;
self.do_dma();
}
_ => debug!(
"fw_cfg: write to unknown port {port:#x}: {size:#x} bytes and offset {offset:#x} ."
),
};
None
}
}
#[cfg(test)]
mod tests {
use std::ffi::CString;
use std::io::Write;
use vmm_sys_util::tempfile::TempFile;
use super::*;
#[cfg(target_arch = "x86_64")]
const SELECTOR_OFFSET: u64 = 0;
#[cfg(target_arch = "aarch64")]
const SELECTOR_OFFSET: u64 = 8;
#[cfg(target_arch = "x86_64")]
const DATA_OFFSET: u64 = 1;
#[cfg(target_arch = "aarch64")]
const DATA_OFFSET: u64 = 0;
#[cfg(target_arch = "x86_64")]
const DMA_OFFSET: u64 = 4;
#[cfg(target_arch = "aarch64")]
const DMA_OFFSET: u64 = 16;
#[test]
fn test_signature() {
let gm = GuestMemoryAtomic::new(
GuestMemoryMmap::from_ranges(&[(GuestAddress(0), RAM_64BIT_START.0 as usize)]).unwrap(),
);
let mut fw_cfg = FwCfg::new(gm);
let mut data = vec![0u8];
let mut sig_iter = FW_CFG_DMA_SIGNATURE.into_iter();
fw_cfg.write(0, SELECTOR_OFFSET, &[FW_CFG_SIGNATURE as u8, 0]);
loop {
if let Some(char) = sig_iter.next() {
fw_cfg.read(0, DATA_OFFSET, &mut data);
assert_eq!(data[0], char);
} else {
return;
}
}
}
#[test]
fn test_kernel_cmdline() {
let gm = GuestMemoryAtomic::new(
GuestMemoryMmap::from_ranges(&[(GuestAddress(0), RAM_64BIT_START.0 as usize)]).unwrap(),
);
let mut fw_cfg = FwCfg::new(gm);
let cmdline = *b"cmdline\0";
fw_cfg.add_kernel_cmdline(CString::from_vec_with_nul(cmdline.to_vec()).unwrap());
let mut data = vec![0u8];
let mut cmdline_iter = cmdline.into_iter();
fw_cfg.write(0, SELECTOR_OFFSET, &[FW_CFG_CMDLINE_DATA as u8, 0]);
loop {
if let Some(char) = cmdline_iter.next() {
fw_cfg.read(0, DATA_OFFSET, &mut data);
assert_eq!(data[0], char);
} else {
return;
}
}
}
#[test]
fn test_initram_fs() {
let gm = GuestMemoryAtomic::new(
GuestMemoryMmap::from_ranges(&[(GuestAddress(0), RAM_64BIT_START.0 as usize)]).unwrap(),
);
let mut fw_cfg = FwCfg::new(gm);
let temp = TempFile::new().unwrap();
let mut temp_file = temp.as_file();
let initram_content = b"this is the initramfs";
let written = temp_file.write(initram_content);
assert_eq!(written.unwrap(), 21);
let _ = fw_cfg.add_initramfs_data(temp_file);
let mut data = vec![0u8];
let mut initram_iter = (*initram_content).into_iter();
fw_cfg.write(0, SELECTOR_OFFSET, &[FW_CFG_INITRD_DATA as u8, 0]);
loop {
if let Some(char) = initram_iter.next() {
fw_cfg.read(0, DATA_OFFSET, &mut data);
assert_eq!(data[0], char);
} else {
return;
}
}
}
#[test]
fn test_dma() {
let code = [
0xba, 0xf8, 0x03, 0x00, 0xd8, 0x04, b'0', 0xee, 0xb0, b'\n', 0xee, 0xf4,
];
let content = FwCfgContent::Bytes(code.to_vec());
let mem_size = 0x1000;
let load_addr = GuestAddress(0x1000);
let mem: GuestMemoryMmap<AtomicBitmap> =
GuestMemoryMmap::from_ranges(&[(load_addr, mem_size)]).unwrap();
// Note: In firmware we would just allocate FwCfgDmaAccess struct
// and use address of struct (&) as dma address
let mut access_control = AccessControl(0);
// bit 1 = read access
access_control.set_read(true);
// length of data to access
let length_be = (code.len() as u32).to_be();
// guest address for data
let code_address = 0x1900_u64;
let address_be = code_address.to_be();
let mut access = FwCfgDmaAccess {
control_be: access_control.0.to_be(), // bit(1) = read bit
length_be,
address_be,
};
// access address is where to put the code
let access_address = GuestAddress(load_addr.0);
let address_bytes = access_address.0.to_be_bytes();
let dma_lo: [u8; 4] = address_bytes[0..4].try_into().unwrap();
let dma_hi: [u8; 4] = address_bytes[4..8].try_into().unwrap();
// writing the FwCfgDmaAccess to mem (this would just be self.dma_access.as_ref() in guest)
let _ = mem.write(access.as_mut_bytes(), access_address);
let mem_m = GuestMemoryAtomic::new(mem.clone());
let mut fw_cfg = FwCfg::new(mem_m);
let cfg_item = FwCfgItem {
name: "code".to_string(),
content,
};
let _ = fw_cfg.add_item(cfg_item);
let mut data = [0u8; 12];
let _ = mem.read(&mut data, GuestAddress(code_address));
assert_ne!(data, code);
fw_cfg.write(0, SELECTOR_OFFSET, &[FW_CFG_FILE_FIRST as u8, 0]);
fw_cfg.write(0, DMA_OFFSET, &dma_lo);
fw_cfg.write(0, DMA_OFFSET + 4, &dma_hi);
let _ = mem.read(&mut data, GuestAddress(code_address));
assert_eq!(data, code);
}
}

View File

@@ -8,7 +8,6 @@
//
use std::sync::{Arc, Barrier};
use vm_device::BusDevice;
/// Provides firmware debug output via I/O port controls

View File

@@ -7,16 +7,17 @@
//! This module implements an ARM PrimeCell General Purpose Input/Output(PL061) to support gracefully poweroff microvm from external.
//!
use std::sync::{Arc, Barrier};
use std::{io, result};
use serde::{Deserialize, Serialize};
use thiserror::Error;
use vm_device::BusDevice;
use vm_device::interrupt::InterruptSourceGroup;
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
use crate::{read_le_u32, write_le_u32};
use std::result;
use std::sync::{Arc, Barrier};
use std::{fmt, io};
use versionize::{VersionMap, Versionize, VersionizeResult};
use versionize_derive::Versionize;
use vm_device::interrupt::InterruptSourceGroup;
use vm_device::BusDevice;
use vm_migration::{
Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable, VersionMapped,
};
const OFS_DATA: u64 = 0x400; // Data Register
const GPIODIR: u64 = 0x400; // Direction Register
@@ -28,10 +29,10 @@ const GPIORIE: u64 = 0x414; // Raw Interrupt Status Register
const GPIOMIS: u64 = 0x418; // Masked Interrupt Status Register
const GPIOIC: u64 = 0x41c; // Interrupt Clear Register
const GPIOAFSEL: u64 = 0x420; // Mode Control Select Register
// From 0x424 to 0xFDC => reserved space.
// From 0xFE0 to 0xFFC => Peripheral and PrimeCell Identification Registers which are Read Only registers.
// These registers can conceptually be treated as a 32-bit register, and PartNumber[11:0] is used to identify the peripheral.
// We are putting the expected values (look at 'Reset value' column from above mentioned document) in an array.
// From 0x424 to 0xFDC => reserved space.
// From 0xFE0 to 0xFFC => Peripheral and PrimeCell Identification Registers which are Read Only registers.
// These registers can conceptually be treated as a 32-bit register, and PartNumber[11:0] is used to identify the peripheral.
// We are putting the expected values (look at 'Reset value' column from above mentioned document) in an array.
const GPIO_ID: [u8; 8] = [0x61, 0x10, 0x14, 0x00, 0x0d, 0xf0, 0x05, 0xb1];
// ID Margins
const GPIO_ID_LOW: u64 = 0xfe0;
@@ -39,18 +40,29 @@ const GPIO_ID_HIGH: u64 = 0x1000;
const N_GPIOS: u32 = 8;
#[derive(Debug, Error)]
#[derive(Debug)]
pub enum Error {
#[error("Bad Write Offset: {0}")]
BadWriteOffset(u64),
#[error("GPIO interrupt disabled by guest driver")]
GpioInterruptDisabled,
#[error("Could not trigger GPIO interrupt")]
GpioInterruptFailure(#[source] io::Error),
#[error("Invalid GPIO Input key triggered: {0}")]
GpioInterruptFailure(io::Error),
GpioTriggerKeyFailure(u32),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Error::BadWriteOffset(offset) => write!(f, "Bad Write Offset: {offset}"),
Error::GpioInterruptDisabled => write!(f, "GPIO interrupt disabled by guest driver.",),
Error::GpioInterruptFailure(ref e) => {
write!(f, "Could not trigger GPIO interrupt: {e}.")
}
Error::GpioTriggerKeyFailure(key) => {
write!(f, "Invalid GPIO Input key triggered: {key}.")
}
}
}
}
type Result<T> = result::Result<T, Error>;
/// A GPIO device following the PL061 specification.
@@ -77,7 +89,7 @@ pub struct Gpio {
interrupt: Arc<dyn InterruptSourceGroup>,
}
#[derive(Serialize, Deserialize)]
#[derive(Versionize)]
pub struct GpioState {
data: u32,
old_in_data: u32,
@@ -90,6 +102,8 @@ pub struct GpioState {
afsel: u32,
}
impl VersionMapped for GpioState {}
impl Gpio {
/// Constructs an PL061 GPIO device.
pub fn new(
@@ -314,7 +328,7 @@ impl Snapshottable for Gpio {
}
fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
Snapshot::new_from_state(&self.state())
Snapshot::new_from_versioned_state(&self.state())
}
}
@@ -324,11 +338,12 @@ impl Migratable for Gpio {}
#[cfg(test)]
mod tests {
use super::*;
use crate::{read_le_u32, write_le_u32};
use std::sync::Arc;
use vm_device::interrupt::{InterruptIndex, InterruptSourceConfig};
use vmm_sys_util::eventfd::EventFd;
use super::*;
const GPIO_NAME: &str = "gpio";
const LEGACY_GPIO_MAPPED_IO_START: u64 = 0x0902_0000;

View File

@@ -1,13 +1,12 @@
// Copyright 2017 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE-BSD-3-Clause file.
//
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Barrier};
use std::sync::{
atomic::{AtomicBool, Ordering},
Arc, Barrier,
};
use std::thread;
use vm_device::BusDevice;
use vmm_sys_util::eventfd::EventFd;

View File

@@ -8,8 +8,6 @@
mod cmos;
#[cfg(target_arch = "x86_64")]
mod debug_port;
#[cfg(feature = "fw_cfg")]
pub mod fw_cfg;
#[cfg(target_arch = "x86_64")]
mod fwdebug;
#[cfg(target_arch = "aarch64")]
@@ -24,17 +22,16 @@ mod uart_pl011;
pub use self::cmos::Cmos;
#[cfg(target_arch = "x86_64")]
pub use self::debug_port::DebugPort;
#[cfg(feature = "fw_cfg")]
pub use self::fw_cfg::FwCfg;
#[cfg(target_arch = "x86_64")]
pub use self::fwdebug::FwDebugDevice;
pub use self::i8042::I8042Device;
pub use self::serial::Serial;
#[cfg(target_arch = "aarch64")]
pub use self::gpio_pl061::Error as GpioDeviceError;
#[cfg(target_arch = "aarch64")]
pub use self::gpio_pl061::Gpio;
pub use self::i8042::I8042Device;
#[cfg(target_arch = "aarch64")]
pub use self::rtc_pl031::Rtc;
pub use self::serial::Serial;
#[cfg(target_arch = "aarch64")]
pub use self::uart_pl011::Pl011;

View File

@@ -4,22 +4,18 @@
//! ARM PL031 Real Time Clock
//!
//! This module implements part of a PL031 Real Time Clock (RTC):
//! * provide a clock value via RTCDR
//! * no alarm is implemented through the match register
//! * no interrupt is generated
//! * RTC cannot be disabled via RTCCR
//! * no test registers
//! This module implements a PL031 Real Time Clock (RTC) that provides to provides long time base counter.
//! This is achieved by generating an interrupt signal after counting for a programmed number of cycles of
//! a real-time clock input.
//!
use std::result;
use crate::{read_le_u32, write_le_u32};
use std::fmt;
use std::sync::{Arc, Barrier};
use std::time::Instant;
use thiserror::Error;
use std::{io, result};
use vm_device::interrupt::InterruptSourceGroup;
use vm_device::BusDevice;
use crate::{read_le_u32, write_le_u32};
// As you can see in https://static.docs.arm.com/ddi0224/c/real_time_clock_pl031_r1p3_technical_reference_manual_DDI0224C.pdf
// at section 3.2 Summary of RTC registers, the total size occupied by this device is 0x000 -> 0xFFC + 4 = 0x1000.
// From 0x0 to 0x1C we have following registers:
@@ -31,11 +27,11 @@ const RTCIMSC: u64 = 0x10; // Interrupt Mask Set or Clear Register.
const RTCRIS: u64 = 0x14; // Raw Interrupt Status.
const RTCMIS: u64 = 0x18; // Masked Interrupt Status.
const RTCICR: u64 = 0x1c; // Interrupt Clear Register.
// From 0x020 to 0xFDC => reserved space.
// From 0xFE0 to 0x1000 => Peripheral and PrimeCell Identification Registers which are Read Only registers.
// AMBA standard devices have CIDs (Cell IDs) and PIDs (Peripheral IDs). The linux kernel will look for these in order to assert the identity
// of these devices (i.e look at the `amba_device_try_add` function).
// We are putting the expected values (look at 'Reset value' column from above mentioned document) in an array.
// From 0x020 to 0xFDC => reserved space.
// From 0xFE0 to 0x1000 => Peripheral and PrimeCell Identification Registers which are Read Only registers.
// AMBA standard devices have CIDs (Cell IDs) and PIDs (Peripheral IDs). The linux kernel will look for these in order to assert the identity
// of these devices (i.e look at the `amba_device_try_add` function).
// We are putting the expected values (look at 'Reset value' column from above mentioned document) in an array.
const PL031_ID: [u8; 8] = [0x31, 0x10, 0x14, 0x00, 0x0d, 0xf0, 0x05, 0xb1];
// We are only interested in the margins.
const AMBA_ID_LOW: u64 = 0xFE0;
@@ -43,10 +39,19 @@ const AMBA_ID_HIGH: u64 = 0x1000;
/// Constant to convert seconds to nanoseconds.
pub const NANOS_PER_SECOND: u64 = 1_000_000_000;
#[derive(Debug, Error)]
#[derive(Debug)]
pub enum Error {
#[error("Bad Write Offset: {0}")]
BadWriteOffset(u64),
InterruptFailure(io::Error),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Error::BadWriteOffset(offset) => write!(f, "Bad Write Offset: {offset}"),
Error::InterruptFailure(e) => write!(f, "Failed to trigger interrupt: {e}"),
}
}
}
type Result<T> = result::Result<T, Error>;
@@ -74,6 +79,98 @@ impl From<ClockType> for libc::clockid_t {
}
}
/// Structure representing the date in local time with nanosecond precision.
pub struct LocalTime {
/// Seconds in current minute.
sec: i32,
/// Minutes in current hour.
min: i32,
/// Hours in current day, 24H format.
hour: i32,
/// Days in current month.
mday: i32,
/// Months in current year.
mon: i32,
/// Years passed since 1900 BC.
year: i32,
/// Nanoseconds in current second.
nsec: i64,
}
impl LocalTime {
/// Returns the [LocalTime](struct.LocalTime.html) structure for the calling moment.
#[cfg(test)]
pub fn now() -> LocalTime {
let mut timespec = libc::timespec {
tv_sec: 0,
tv_nsec: 0,
};
let mut tm: libc::tm = libc::tm {
tm_sec: 0,
tm_min: 0,
tm_hour: 0,
tm_mday: 0,
tm_mon: 0,
tm_year: 0,
tm_wday: 0,
tm_yday: 0,
tm_isdst: 0,
tm_gmtoff: 0,
tm_zone: std::ptr::null(),
};
// SAFETY: the parameters are valid.
unsafe {
libc::clock_gettime(libc::CLOCK_REALTIME, &mut timespec);
libc::localtime_r(&timespec.tv_sec, &mut tm);
}
LocalTime {
sec: tm.tm_sec,
min: tm.tm_min,
hour: tm.tm_hour,
mday: tm.tm_mday,
mon: tm.tm_mon,
year: tm.tm_year,
nsec: timespec.tv_nsec,
}
}
}
impl fmt::Display for LocalTime {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}-{:02}-{:02}T{:02}:{:02}:{:02}.{:09}",
self.year + 1900,
self.mon + 1,
self.mday,
self.hour,
self.min,
self.sec,
self.nsec
)
}
}
/// Holds a micro-second resolution timestamp with both the real time and cpu time.
#[derive(Clone)]
pub struct TimestampUs {
/// Real time in microseconds.
pub time_us: u64,
/// Cpu time in microseconds.
pub cputime_us: u64,
}
impl Default for TimestampUs {
fn default() -> TimestampUs {
TimestampUs {
time_us: get_time(ClockType::Monotonic) / 1000,
cputime_us: get_time(ClockType::ProcessCpu) / 1000,
}
}
}
/// Returns a timestamp in nanoseconds based on the provided clock type.
///
/// # Arguments
@@ -107,20 +204,31 @@ pub struct Rtc {
match_value: u32,
// Writes to this register load an update value into the RTC.
load: u32,
imsc: u32,
ris: u32,
interrupt: Arc<dyn InterruptSourceGroup>,
}
impl Rtc {
/// Constructs an AMBA PL031 RTC device.
pub fn new() -> Self {
pub fn new(interrupt: Arc<dyn InterruptSourceGroup>) -> Self {
Self {
// This is used only for duration measuring purposes.
previous_now: Instant::now(),
tick_offset: get_time(ClockType::Real) as i64,
match_value: 0,
load: 0,
imsc: 0,
ris: 0,
interrupt,
}
}
fn trigger_interrupt(&mut self) -> Result<()> {
self.interrupt.trigger(0).map_err(Error::InterruptFailure)?;
Ok(())
}
fn get_time(&self) -> u32 {
let ts = (self.tick_offset as i128)
+ (Instant::now().duration_since(self.previous_now).as_nanos() as i128);
@@ -144,8 +252,16 @@ impl Rtc {
// we want to terminate the execution of the process.
self.tick_offset = seconds_to_nanoseconds(i64::from(val)).unwrap();
}
RTCIMSC => (),
RTCICR => (),
RTCIMSC => {
self.imsc = val & 1;
self.trigger_interrupt()?;
}
RTCICR => {
// As per above mentioned doc, the interrupt is cleared by writing any data value to
// the Interrupt Clear Register.
self.ris = 0;
self.trigger_interrupt()?;
}
RTCCR => (), // ignore attempts to turn off the timer.
o => {
return Err(Error::BadWriteOffset(o));
@@ -155,12 +271,6 @@ impl Rtc {
}
}
impl Default for Rtc {
fn default() -> Self {
Self::new()
}
}
impl BusDevice for Rtc {
fn read(&mut self, _base: u64, offset: u64, data: &mut [u8]) {
let mut read_ok = true;
@@ -176,10 +286,10 @@ impl BusDevice for Rtc {
self.match_value
}
RTCLR => self.load,
RTCCR => 1, // RTC is always enabled.
RTCIMSC => 0, // Interrupt is always disabled.
RTCRIS => 0,
RTCMIS => 0,
RTCCR => 1, // RTC is always enabled.
RTCIMSC => self.imsc,
RTCRIS => self.ris,
RTCMIS => self.ris & self.imsc,
_ => {
read_ok = false;
0
@@ -219,76 +329,15 @@ impl BusDevice for Rtc {
mod tests {
use super::*;
use crate::{
read_be_u16, read_be_u32, read_le_i32, read_le_u16, read_le_u64, write_be_u16,
write_be_u32, write_le_i32, write_le_u16, write_le_u64,
read_be_u16, read_be_u32, read_le_i32, read_le_u16, read_le_u32, read_le_u64, write_be_u16,
write_be_u32, write_le_i32, write_le_u16, write_le_u32, write_le_u64,
};
use std::sync::Arc;
use vm_device::interrupt::{InterruptIndex, InterruptSourceConfig};
use vmm_sys_util::eventfd::EventFd;
const LEGACY_RTC_MAPPED_IO_START: u64 = 0x0901_0000;
struct LocalTime {
sec: i32,
min: i32,
hour: i32,
mday: i32,
mon: i32,
year: i32,
nsec: i64,
}
impl LocalTime {
fn now() -> LocalTime {
let mut timespec = libc::timespec {
tv_sec: 0,
tv_nsec: 0,
};
let mut tm: libc::tm = libc::tm {
tm_sec: 0,
tm_min: 0,
tm_hour: 0,
tm_mday: 0,
tm_mon: 0,
tm_year: 0,
tm_wday: 0,
tm_yday: 0,
tm_isdst: 0,
tm_gmtoff: 0,
tm_zone: std::ptr::null(),
};
// SAFETY: the parameters are valid.
unsafe {
libc::clock_gettime(libc::CLOCK_REALTIME, &mut timespec);
libc::localtime_r(&timespec.tv_sec, &mut tm);
}
LocalTime {
sec: tm.tm_sec,
min: tm.tm_min,
hour: tm.tm_hour,
mday: tm.tm_mday,
mon: tm.tm_mon,
year: tm.tm_year,
nsec: timespec.tv_nsec,
}
}
}
impl std::fmt::Display for LocalTime {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}-{:02}-{:02}T{:02}:{:02}:{:02}.{:09}",
self.year + 1900,
self.mon + 1,
self.mday,
self.hour,
self.min,
self.sec,
self.nsec
)
}
}
#[test]
fn test_get_time() {
for _ in 0..1000 {
@@ -350,9 +399,45 @@ mod tests {
assert!(seconds_to_nanoseconds(9_223_372_037).is_none());
}
struct TestInterrupt {
event_fd: EventFd,
}
impl InterruptSourceGroup for TestInterrupt {
fn trigger(&self, _index: InterruptIndex) -> result::Result<(), std::io::Error> {
self.event_fd.write(1)
}
fn update(
&self,
_index: InterruptIndex,
_config: InterruptSourceConfig,
_masked: bool,
_set_gsi: bool,
) -> result::Result<(), std::io::Error> {
Ok(())
}
fn set_gsi(&self) -> result::Result<(), std::io::Error> {
Ok(())
}
fn notifier(&self, _index: InterruptIndex) -> Option<EventFd> {
Some(self.event_fd.try_clone().unwrap())
}
}
impl TestInterrupt {
fn new(event_fd: EventFd) -> Self {
TestInterrupt { event_fd }
}
}
#[test]
fn test_rtc_read_write_and_event() {
let mut rtc = Rtc::new();
let intr_evt = EventFd::new(libc::EFD_NONBLOCK).unwrap();
let mut rtc = Rtc::new(Arc::new(TestInterrupt::new(intr_evt.try_clone().unwrap())));
let mut data = [0; 4];
// Read and write to the MR register.
@@ -375,13 +460,15 @@ mod tests {
assert_eq!((v / NANOS_PER_SECOND) as u32, v_read);
// Read and write to IMSC register.
// Test with non zero value. Our device ignores the write.
// Test with non zero value.
let non_zero = 1;
write_le_u32(&mut data, non_zero);
rtc.write(LEGACY_RTC_MAPPED_IO_START, RTCIMSC, &data);
// The interrupt line should be on.
assert!(rtc.interrupt.notifier(0).unwrap().read().unwrap() == 1);
rtc.read(LEGACY_RTC_MAPPED_IO_START, RTCIMSC, &mut data);
let v = read_le_u32(&data);
assert_eq!(0, v);
assert_eq!(non_zero & 1, v);
// Now test with 0.
write_le_u32(&mut data, 0);
@@ -393,6 +480,8 @@ mod tests {
// Read and write to the ICR register.
write_le_u32(&mut data, 1);
rtc.write(LEGACY_RTC_MAPPED_IO_START, RTCICR, &data);
// The interrupt line should be on.
assert!(rtc.interrupt.notifier(0).unwrap().read().unwrap() > 1);
let v_before = read_le_u32(&data);
rtc.read(LEGACY_RTC_MAPPED_IO_START, RTCICR, &mut data);

View File

@@ -8,11 +8,13 @@
use std::collections::VecDeque;
use std::sync::{Arc, Barrier};
use std::{io, result};
use serde::{Deserialize, Serialize};
use vm_device::BusDevice;
use versionize::{VersionMap, Versionize, VersionizeResult};
use versionize_derive::Versionize;
use vm_device::interrupt::InterruptSourceGroup;
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
use vm_device::BusDevice;
use vm_migration::{
Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable, VersionMapped,
};
use vmm_sys_util::errno::Result;
const LOOP_SIZE: usize = 0x40;
@@ -72,7 +74,7 @@ pub struct Serial {
out: Option<Box<dyn io::Write + Send>>,
}
#[derive(Serialize, Deserialize)]
#[derive(Versionize)]
pub struct SerialState {
interrupt_enable: u8,
interrupt_identification: u8,
@@ -84,6 +86,7 @@ pub struct SerialState {
baud_divisor: u16,
in_buffer: Vec<u8>,
}
impl VersionMapped for SerialState {}
impl Serial {
pub fn new(
@@ -331,7 +334,7 @@ impl Snapshottable for Serial {
}
fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
Snapshot::new_from_state(&self.state())
Snapshot::new_from_versioned_state(&self.state())
}
}
@@ -341,13 +344,12 @@ impl Migratable for Serial {}
#[cfg(test)]
mod tests {
use std::sync::Mutex;
use super::*;
use std::io;
use std::sync::{Arc, Mutex};
use vm_device::interrupt::{InterruptIndex, InterruptSourceConfig};
use vmm_sys_util::eventfd::EventFd;
use super::*;
const SERIAL_NAME: &str = "serial";
struct TestInterrupt {
@@ -414,11 +416,14 @@ mod tests {
None,
);
serial.write(0, DATA as u64, b"xy");
serial.write(0, DATA as u64, b"a");
serial.write(0, DATA as u64, b"b");
serial.write(0, DATA as u64, b"c");
assert_eq!(serial_out.buf.lock().unwrap().as_slice(), b"abc");
serial.write(0, DATA as u64, &[b'x', b'y']);
serial.write(0, DATA as u64, &[b'a']);
serial.write(0, DATA as u64, &[b'b']);
serial.write(0, DATA as u64, &[b'c']);
assert_eq!(
serial_out.buf.lock().unwrap().as_slice(),
&[b'a', b'b', b'c']
);
}
#[test]
@@ -434,9 +439,9 @@ mod tests {
// write 1 to the interrupt event fd, so that read doesn't block in case the event fd
// counter doesn't change (for 0 it blocks)
intr_evt.write(1).unwrap();
assert!(intr_evt.write(1).is_ok());
serial.write(0, IER as u64, &[IER_RECV_BIT]);
serial.queue_input_bytes(b"abc").unwrap();
serial.queue_input_bytes(&[b'a', b'b', b'c']).unwrap();
assert_eq!(intr_evt.read().unwrap(), 2);
@@ -471,9 +476,9 @@ mod tests {
// write 1 to the interrupt event fd, so that read doesn't block in case the event fd
// counter doesn't change (for 0 it blocks)
intr_evt.write(1).unwrap();
assert!(intr_evt.write(1).is_ok());
serial.write(0, IER as u64, &[IER_THR_BIT]);
serial.write(0, DATA as u64, b"a");
serial.write(0, DATA as u64, &[b'a']);
assert_eq!(intr_evt.read().unwrap(), 2);
let mut data = [0u8];
@@ -515,9 +520,9 @@ mod tests {
);
serial.write(0, MCR as u64, &[MCR_LOOP_BIT]);
serial.write(0, DATA as u64, b"a");
serial.write(0, DATA as u64, b"b");
serial.write(0, DATA as u64, b"c");
serial.write(0, DATA as u64, &[b'a']);
serial.write(0, DATA as u64, &[b'b']);
serial.write(0, DATA as u64, &[b'c']);
let mut data = [0u8];
serial.read(0, MSR as u64, &mut data[..]);

View File

@@ -6,18 +6,19 @@
//! This module implements an ARM PrimeCell UART(PL011).
//!
use crate::{read_le_u32, write_le_u32};
use std::collections::VecDeque;
use std::fmt;
use std::sync::{Arc, Barrier};
use std::time::Instant;
use std::{io, result};
use serde::{Deserialize, Serialize};
use thiserror::Error;
use vm_device::BusDevice;
use versionize::{VersionMap, Versionize, VersionizeResult};
use versionize_derive::Versionize;
use vm_device::interrupt::InterruptSourceGroup;
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
use crate::{read_le_u32, write_le_u32};
use vm_device::BusDevice;
use vm_migration::{
Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable, VersionMapped,
};
/* Registers */
const UARTDR: u64 = 0;
@@ -47,18 +48,25 @@ const PL011_ID: [u8; 8] = [0x11, 0x10, 0x14, 0x00, 0x0d, 0xf0, 0x05, 0xb1];
const AMBA_ID_LOW: u64 = 0x3f8;
const AMBA_ID_HIGH: u64 = 0x401;
#[derive(Debug, Error)]
#[derive(Debug)]
pub enum Error {
#[error("pl011_write: Bad Write Offset: {0}")]
BadWriteOffset(u64),
#[error("pl011: DMA not implemented")]
DmaNotImplemented,
#[error("Failed to trigger interrupt")]
InterruptFailure(#[source] io::Error),
#[error("Failed to write")]
WriteAllFailure(#[source] io::Error),
#[error("Failed to flush")]
FlushFailure(#[source] io::Error),
InterruptFailure(io::Error),
WriteAllFailure(io::Error),
FlushFailure(io::Error),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Error::BadWriteOffset(offset) => write!(f, "pl011_write: Bad Write Offset: {offset}"),
Error::DmaNotImplemented => write!(f, "pl011: DMA not implemented."),
Error::InterruptFailure(e) => write!(f, "Failed to trigger interrupt: {e}"),
Error::WriteAllFailure(e) => write!(f, "Failed to write: {e}"),
Error::FlushFailure(e) => write!(f, "Failed to flush: {e}"),
}
}
}
type Result<T> = result::Result<T, Error>;
@@ -86,7 +94,7 @@ pub struct Pl011 {
timestamp: std::time::Instant,
}
#[derive(Serialize, Deserialize)]
#[derive(Versionize)]
pub struct Pl011State {
flags: u32,
lcr: u32,
@@ -105,6 +113,8 @@ pub struct Pl011State {
read_trigger: u32,
}
impl VersionMapped for Pl011State {}
impl Pl011 {
/// Constructs an AMBA PL011 UART device.
pub fn new(
@@ -444,7 +454,7 @@ impl Snapshottable for Pl011 {
}
fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
Snapshot::new_from_state(&self.state())
Snapshot::new_from_versioned_state(&self.state())
}
}
@@ -454,13 +464,12 @@ impl Migratable for Pl011 {}
#[cfg(test)]
mod tests {
use std::sync::Mutex;
use super::*;
use std::io;
use std::sync::{Arc, Mutex};
use vm_device::interrupt::{InterruptIndex, InterruptSourceConfig};
use vmm_sys_util::eventfd::EventFd;
use super::*;
const SERIAL_NAME: &str = "serial";
struct TestInterrupt {
@@ -528,11 +537,14 @@ mod tests {
None,
);
pl011.write(0, UARTDR, b"xy");
pl011.write(0, UARTDR, b"a");
pl011.write(0, UARTDR, b"b");
pl011.write(0, UARTDR, b"c");
assert_eq!(pl011_out.buf.lock().unwrap().as_slice(), b"xabc");
pl011.write(0, UARTDR, &[b'x', b'y']);
pl011.write(0, UARTDR, &[b'a']);
pl011.write(0, UARTDR, &[b'b']);
pl011.write(0, UARTDR, &[b'c']);
assert_eq!(
pl011_out.buf.lock().unwrap().as_slice(),
&[b'x', b'a', b'b', b'c']
);
}
#[test]
@@ -549,8 +561,8 @@ mod tests {
// write 1 to the interrupt event fd, so that read doesn't block in case the event fd
// counter doesn't change (for 0 it blocks)
intr_evt.write(1).unwrap();
pl011.queue_input_bytes(b"abc").unwrap();
assert!(intr_evt.write(1).is_ok());
pl011.queue_input_bytes(&[b'a', b'b', b'c']).unwrap();
assert_eq!(intr_evt.read().unwrap(), 2);

Some files were not shown because too many files have changed in this diff Show More