Compare commits

...

574 Commits

Author SHA1 Message Date
Rob Bradford
226d226fef build: Bump version to 0.5.1
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-02-24 13:50:54 +00:00
Rob Bradford
293b8fad41 release-notes: Update for v0.5.1 bug fix release
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
(cherry picked from commit 94f2fc3308)
2020-02-21 15:35:22 +00:00
Sergio Lopez
54ade15f9e vmm: openapi: Fix "readonly" and "wce" defaults in DiskConfig
Fix "readonly" and "wce" defaults in cloud-hypervisor.yaml to match
their respective defaults in config.rs:DiskConfig.

Signed-off-by: Sergio Lopez <slp@redhat.com>
2020-02-20 20:38:58 +01:00
Rob Bradford
03f64d12c3 build: Update dependencies
Updated by:

cargo upgrade --all
cargo update

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-02-20 15:41:00 +00:00
Sebastien Boeuf
9c3166237d vmm: Prevent memory overcommitment through virtio-fs shared regions
When a virtio-fs device is created with a dedicated shared region, by
default the region should be mapped as PROT_NONE so that no pages can be
faulted in.

It's only when the guest performs the mount of the virtiofs filesystem
that we can expect the VMM, on behalf of the backend, to perform some
new mappings in the reserved shared window, using PROT_READ and/or
PROT_WRITE.

Fixes #763

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
(cherry picked from commit 3edc2bd6ab)
2020-02-20 15:41:00 +00:00
Samuel Ortiz
3534540e6b vmm: api: Return a resize error when resize fails
And not a VmCreate one.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
(cherry picked from commit c49e31a6d9)
2020-02-20 15:41:00 +00:00
Samuel Ortiz
f444451a04 vmm: openapi: Update DiskConfig
It's missing a few knobs (readonly, vhost, wce) that should be exposed
through the rest API.

Fixes: #790

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
(cherry picked from commit 9de755334d)
2020-02-20 15:41:00 +00:00
Rob Bradford
89c25ea00c vmm: Workaround double reboot triggered by the kernel
The kernel does not adhere to the ACPI specification (probably to work
around broken hardware) and rather than busy looping after requesting an
ACPI reset it will attempt to reset by other mechanisms (such as i8042
reset.)

In order to trigger a reset the devices write to an EventFd (called
reset_evt.) This is used by the VMM to identify if a reset is requested
and make the VM reboot. As the reset_evt is part of the VMM and reused
for both the old and new VM it is possible for the newly booted VM to
immediately get reset as there is an old event sitting in the EventFd.

The simplest solution is to "drain" the reset_evt EventFd on reboot to
make sure that there is no spurious events in the EventFd.

Fixes: #783

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
(cherry picked from commit ed1e7817cc)
2020-02-20 15:41:00 +00:00
Sebastien Boeuf
d89ab632e9 ci: Improve test_memory_mergeable_on stability
The integration test test_memory_mergeable_on has been fairly unstable
for quite some time now. Because it can take some time for the VM to be
spawned and to be able to perform a correct measure of the PSS, this
commit simply increases the time before such measure is done.
This should return more accurate PSS results, which should help
stabilize the test.

Fixes #781

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
(cherry picked from commit ddf6caf955)
2020-02-20 15:41:00 +00:00
Sebastien Boeuf
e32be99d6c vhost_rs: Fix unit test race condition
The unit tests are run from cargo test through multiple threads of the
same process. For this reason, all these threads share their file
descriptors (because that's how this works on Linux), which means that
any of them can close a file descriptor opened from another thread.

In the context of create_listener() and accept_connection() tests, they
can run concurrently and this generates some failure when the file
descriptor create_listener() is binding to is being closed from the
accept_connection() test.

In order to avoid such race condition, this patch simply removes the
part of the unit test performing an explicit and unsafe file descriptor
closure.

Fixes #759

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
(cherry picked from commit be78c6da49)
2020-02-20 15:41:00 +00:00
Sebastien Boeuf
4a62821e07 ci: Don't run unit tests in a privileged container
The unit tests require some specific Linux capabilities and also to have
access to /dev/kvm device. This commit makes sure we enable only what's
necessary instead of blindly enable full priviliges with --privileged
option.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
(cherry picked from commit 7fabca3548)
2020-02-20 15:41:00 +00:00
Rob Bradford
548ef43ca2 tests: Use hugepages for test_vfio
Using hugepages with VFIO and virtiofs can improve the performance of
the test_vfio test.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
(cherry picked from commit 5c9828a34c)
2020-02-20 15:41:00 +00:00
Rob Bradford
27c1b40c83 tests: Always create shared VFIO directory from scratch
This ensures that any changes to the contents will not affect subsequent
tests runs.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
(cherry picked from commit e5f5b1d973)
2020-02-20 15:41:00 +00:00
Samuel Ortiz
2f395e60a0 release: v0.5.0
Expand the release notes and bump Cargo.toml.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-02-07 16:17:06 +01:00
dependabot-preview[bot]
d46c61c5d4 build(deps): bump byteorder from 1.3.2 to 1.3.4
Bumps [byteorder](https://github.com/BurntSushi/byteorder) from 1.3.2 to 1.3.4.
- [Release notes](https://github.com/BurntSushi/byteorder/releases)
- [Changelog](https://github.com/BurntSushi/byteorder/blob/master/CHANGELOG.md)
- [Commits](https://github.com/BurntSushi/byteorder/compare/1.3.2...1.3.4)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-07 14:18:07 +00:00
Samuel Ortiz
0cd3325a06 docs: Cloud Hypervisor API documentation
We document both the internal and external APIs, and how they relate to
each others.

Fixes: #318

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-02-07 11:26:41 +01:00
Cathy Zhang
14eddf72b4 vm-virtio: Simplify virtio feature handling
Remove duplicated code across the different devices by handling
the virtio feature pages in VirtioDevice itself rather than
in the backends. This works as no virtio devices use feature
bits beyond 64-bits.

Signed-off-by: Cathy Zhang <cathy.zhang@intel.com>
2020-02-07 08:32:21 +00:00
dependabot-preview[bot]
411e2b43ba build(deps): bump micro_http from 10d1f4a to ae15e75
Bumps [micro_http](https://github.com/firecracker-microvm/firecracker) from `10d1f4a` to `ae15e75`.
- [Release notes](https://github.com/firecracker-microvm/firecracker/releases)
- [Commits](10d1f4aa59...ae15e75eb7)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-07 08:24:12 +00:00
Bo Chen
3bf46d4c80 README: Fix a typo for using cli_dev
Fix the typo for the instructions of using cli_dev to run unit tests.

Signed-off-by: Bo Chen <chen.bo@intel.com>
2020-02-06 18:02:21 +01:00
Sebastien Boeuf
3447e226d9 dependencies: bump vm-memory from 4237db3 to f3d1c27
This commit updates Cloud-Hypervisor to rely on the latest version of
the vm-memory crate.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-02-06 11:40:45 +01:00
dependabot-preview[bot]
ae87455242 build(deps): bump micro_http from ab44474 to 10d1f4a
Bumps [micro_http](https://github.com/firecracker-microvm/firecracker) from `ab44474` to `10d1f4a`.
- [Release notes](https://github.com/firecracker-microvm/firecracker/releases)
- [Commits](ab44474a08...10d1f4aa59)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-06 06:35:35 +00:00
Sebastien Boeuf
62ccccc303 vmm: Make sure to retry creating the VM on EINTR
If the ioctl syscall KVM_CREATE_VM gets interrupted while creating the
VM, it is expected that we should retry since EINTR should not be
considered a standard error.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-02-05 12:06:21 +01:00
dependabot-preview[bot]
671b49ba23 build(deps): bump vm-memory from d6229fc to 4237db3
Bumps [vm-memory](https://github.com/rust-vmm/vm-memory) from `d6229fc` to `4237db3`.
- [Release notes](https://github.com/rust-vmm/vm-memory/releases)
- [Commits](d6229fc5bd...4237db35e8)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-05 07:28:33 +00:00
dependabot-preview[bot]
e5b7fff990 build(deps): bump micro_http from 1eb80f8 to ab44474
Bumps [micro_http](https://github.com/firecracker-microvm/firecracker) from `1eb80f8` to `ab44474`.
- [Release notes](https://github.com/firecracker-microvm/firecracker/releases)
- [Commits](1eb80f8d1b...ab44474a08)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-05 07:28:22 +00:00
Samuel Ortiz
da2b3c92d3 vm-device: interrupt: Remove InterruptType dependencies and definitions
Having the InterruptManager trait depend on an InterruptType forces
implementations into supporting potentially very different kind of
interrupts from the same code base. What we're defining through the
current, interrupt type based create_group() method is a need for having
different interrupt managers for different kind of interrupts.

By associating the InterruptManager trait to an interrupt group
configuration type, we create a cleaner design to support that need as
we're basically saying that one interrupt manager should have the single
responsibility of supporting one kind of interrupt (defined through its
configuration).

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-02-04 19:32:45 +01:00
Samuel Ortiz
84fc807bc6 interrupt: Interrupt manager split
We create 2 different interrupt managers for separately handling
creation of legacy and MSI interrupt groups.
Doing so allows us to have a cleaner interrupt manager and IOAPIC
initialization path. It also prepares for an InterruptManager trait
design improvement where we remove the interrupt source type dependency
by associating an interrupt configuration type to the trait.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-02-04 19:32:45 +01:00
Sebastien Boeuf
56d7c04226 vm-virtio: vsock: Don't return error when epoll_wait is interrupted
The existing code taking care of the epoll loop was too restrictive as
it was considering all errors the same. But in case the error is EINTR,
this means the syscall has been interrupted while waiting, and it should
be resumed to wait again.

This patch enforces the parsing of the returned error and prevent the
code from assuming EINTR should be handled as all other errors.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-02-04 18:16:37 +01:00
Rob Bradford
6e6b2b84fe scripts: Check the Rust formatting is valid
Check the rust formatting rather than just reformatting code on the CI
agent.

Also fix a formatting error that slipped in whilst the cargo fmt check
was not working correctly.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-02-04 16:47:05 +01:00
Samuel Ortiz
705f27151d scripts: dev_cli: Pass unit and integration test args to cargo test
In order, among other things, to use the development CLI to run specific
integration tests. For example, to run only the memory_overhead
integration test:

./scripts/dev_cli.sh tests --integration -- memory_overhead

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-02-04 15:31:58 +01:00
Rob Bradford
bc053f1b13 main: Ignore error on log writing
A previous version of this change attempted to avoid panicking by not
using .expect() when handling an error when attempting to write to the
log file. Unfortunately the macro eprintln!() that was used to replace
the .expect() also has the behaviour of panicking if stderr cannot be
used. Instead swallow the error completely as if writing to the log has
failed at logging time it is almost certainly the case that any message
about the log would also not be seen.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-02-04 12:22:23 +00:00
Yang Zhong
91739be120 main: Add help info for block-backend
Since the vhost-user-blk binary will be removed and the newer
release will integrate this block backend into cloud-hypervisor
binary. The block backend code has been added num_queues cmdline
support, we need update multiple queues help info for this
block-backend in the cloud-hypervisor.

Signed-off-by: Yang Zhong <yang.zhong@intel.com>
2020-02-04 11:06:17 +00:00
Samuel Ortiz
ae6cf4c922 tests: integration: Add memory overhead test
We measure the memory overhead that the VMM process adds to the guest VM
and compare it with a maximum acceptable limit. The test is run against
a simple VM, running 1 vCPU and 512MB of RAM. Although this is not by
any mean a comprehensive VMM overhead measurement, it will allow us to
detect when and if any PR makes our code cross an arbitrary memory
overhead threshold.

Fixes: #64

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-02-04 09:23:56 +00:00
Rob Bradford
7cb61d3960 main: Don't panic (by calling .expect()) if writing to the log fails
As this can happen during the running of the VMM we should be very
careful not to panic() as that can lead to a thread being used by the VM
disappearing.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-02-03 19:08:08 +01:00
Rob Bradford
880a57c920 vmm: Remove VmInfo struct
After refactoring the VmInfo struct is no longer needed.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-02-03 12:28:30 +00:00
Rob Bradford
07bc292fa5 vmm: device_manager: Get VmFd from AddressManager
A reference to the VmFd is stored on the AddressManager so it is not
necessary to pass in the VmInfo into all methods that need it as it can
be obtained from the AddressManager.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-02-03 12:28:30 +00:00
Rob Bradford
6411c3ae42 vmm: device_manager: Use MemoryManager to get guest memory
The DeviceManager has a reference to the MemoryManager so use that to
get the GuestMemoryMmap rather than the version stored in the VmInfo
struct.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-02-03 12:28:30 +00:00
Rob Bradford
066fc6c0d1 vmm: device_manager: Get VM config from the struct member
Remove the use of vm_info in methods to get the config and instead use
the config stored on the DeviceManager itself.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-02-03 12:28:30 +00:00
Rob Bradford
77ae3de4f3 vmm: device_manager: Make legacy device addition a method
Remove some in/out parameters and instead rely on them as members of the
&mut self parameter.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-02-03 12:28:30 +00:00
Rob Bradford
599275b610 vmm: device_manager: Make ACPI device creation a method
Remove some in/out parameters and instead rely on them as members of the
&mut self parameter.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-02-03 12:28:30 +00:00
Rob Bradford
b8c1b2e174 vmm: device_manager: Make console creation a method
Remove some in/out parameters and instead rely on them as members of the
&mut self parameter.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-02-03 12:28:30 +00:00
Rob Bradford
b5440e2d0a vmm: device_manager: Make virtio device creation functions methods
Remove some in/out parameters and instead rely on them as members of the
&mut self parameter. This prepares the way to more easily store state on
the DeviceManager.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-02-03 12:28:30 +00:00
Rob Bradford
e90c6f3c44 vmm: device_manager: Make make_virtio_devices a method
Remove some in/out parameters and instead rely on them as members of the
&mut self parameter. A follow-up commit will change the callee functions
that create the devices themselves.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-02-03 12:28:30 +00:00
Rob Bradford
dbc09ad0ef vmm: device_manager: Make add_vfio_devices a method
Remove some in/out parameters and instead rely on them as members of the
&mut self parameter.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-02-03 12:28:30 +00:00
Rob Bradford
d9e1c2cd22 vmm: device_manager: Make add_virtio_pci_device a method
Remove some in/out parameters and instead rely on them as members of the
&mut self parameter.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-02-03 12:28:30 +00:00
Rob Bradford
aaa5e2e9ea vmm: device_manager: Make add_virtio_mmio_device a method
Remove some in/out parameters and instead rely on them as members of the
&mut self parameter.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-02-03 12:28:30 +00:00
Rob Bradford
2987476e0a vmm: device_manager: Make add_pci_devices and add_mmio_devices methods
Modify these functions to take an &mut self and become methods on
DeviceManager. This allows the removal of some in/out parameters and
leads the way to further refactoring and simplification.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-02-03 12:28:30 +00:00
Rob Bradford
3dbae423bb vmm: device_manager: Only add MemoryManager to I/O bus on ACPI builds
The MemoryManager should only be included on the I/O bus when doing ACPI
builds as that is the only time it will be interrogated.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-02-03 12:28:30 +00:00
Rob Bradford
68fa97eb0e vmm: device_manager: Always embed MemoryManager in the struct
Currently the MemoryManager is only used on the ACPI code paths after
the DeviceManager has been created. This will change in a future commit
as part of the refactoring so for now always include it but name it with
underscore prefix to indicate it might not always be used.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-02-03 12:28:30 +00:00
Yang Zhong
789a39a2d5 ci: Add MQ support in the test cases
The CI need to add MQ support, and the vcpu number should
be bigger than or equal to queue number.

Signed-off-by: Yang Zhong <yang.zhong@intel.com>
2020-02-03 09:49:27 +01:00
Yang Zhong
99da1dff90 vhost-user-blk: Add MQ support in backend
Adding the num_queues parameter for vhost-user-blk backend, which
can enable MQ support in the backend.

This patch has enabled the MQ support from handle_event, and the
vhost-user-backend crate will enable multiple threads to call this
handle_event to handle read/write operations.

Signed-off-by: Yang Zhong <yang.zhong@intel.com>
2020-02-03 09:49:27 +01:00
Yang Zhong
1038a07dd6 vhost-user-blk: Device support multiple queues
The previous code only support one queue, and we need
to support MQ in vhost user block device. This patch
can work with SPDK with MQ setting.

Signed-off-by: Yang Zhong <yang.zhong@intel.com>
2020-02-03 09:49:27 +01:00
dependabot-preview[bot]
839f3a74ff build(deps): bump serde_json from 1.0.45 to 1.0.46
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.45 to 1.0.46.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.45...v1.0.46)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-03 07:07:34 +00:00
Samuel Ortiz
652b60320d vfio: Use the vfio-binding FAM wrappers
It's less unsafe and makes the code more readable.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-01-31 15:09:11 +01:00
Samuel Ortiz
275cb5c92f scripts: dev_cli: Add clean command support
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-01-31 11:42:20 +00:00
Samuel Ortiz
8676759c38 README: Basic dev_cli documentation
Document it as an alternative way of building the Cloud Hypervisor
binaries.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-01-31 11:42:20 +00:00
Samuel Ortiz
76087f1235 script: cargo: Improve the cargo tests
We don't need to force the cargo-audit install, we can check if it's
already available instead and install if it's not.

Also, since we now have workspaces properly setup, we can call directly
into cargo fmt and avoid calling into find magic incantation.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-01-31 11:42:20 +00:00
Samuel Ortiz
db6f894e5e scripts: Add container based development script
The script is a development tool that runs all commands in a dedicated
container. This allows for containerized, isolated and reproducible
builds and CI runs.

The script supports the following command:

* build: Build Cloud Hypervisor binaries (debug and release)
* build-container: Build the container used by the script
* tests: Run unit, cargo and integration tests

$ ./scripts/dev_cli.sh help

Cloud Hypervisor dev_cli.sh
Usage: dev_cli.sh <command> [<command args>]

Available commands:

    build [--debug|--release] [-- [<cargo args>]]
        Build the Cloud Hypervisor binaries.
        --debug               Build the debug binaries. This is the default.
        --release             Build the release binaries.

    tests [--unit|--cargo|--all]
        Run the Cloud Hypervisor tests.
        --unit               Run the unit tests.
        --cargo              Run the cargo tests.
	--integration        Run the integration tests.
        --all                Run all tests.

    build-container [--type]
        Build the Cloud Hypervisor container.
        --dev                Build dev container. This is the default.

    help
        Display this help message.

Fixes: #682
Fixes: #684

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-01-31 11:42:20 +00:00
dependabot-preview[bot]
31c3685117 build(deps): bump micro_http from e5ef374 to 1eb80f8
Bumps [micro_http](https://github.com/firecracker-microvm/firecracker) from `e5ef374` to `1eb80f8`.
- [Release notes](https://github.com/firecracker-microvm/firecracker/releases)
- [Commits](e5ef374c8e...1eb80f8d1b)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-31 07:55:08 +00:00
dependabot-preview[bot]
8dfb79e077 build(deps): bump vm-memory from df6207f to d6229fc
Bumps [vm-memory](https://github.com/rust-vmm/vm-memory) from `df6207f` to `d6229fc`.
- [Release notes](https://github.com/rust-vmm/vm-memory/releases)
- [Commits](df6207f1c0...d6229fc5bd)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-31 07:54:31 +00:00
dependabot-preview[bot]
e99b64097a build(deps): bump regex-syntax from 0.6.13 to 0.6.14
Bumps [regex-syntax](https://github.com/rust-lang/regex) from 0.6.13 to 0.6.14.
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-31 00:03:28 +00:00
dependabot-preview[bot]
92b3764553 build(deps): bump openssl-sys from 0.9.53 to 0.9.54
Bumps [openssl-sys](https://github.com/sfackler/rust-openssl) from 0.9.53 to 0.9.54.
- [Release notes](https://github.com/sfackler/rust-openssl/releases)
- [Commits](https://github.com/sfackler/rust-openssl/compare/openssl-sys-v0.9.53...openssl-sys-v0.9.54)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-30 17:20:30 +00:00
dependabot-preview[bot]
de91c30cd3 build(deps): bump micro_http from 9e68aa4 to e5ef374
Bumps [micro_http](https://github.com/firecracker-microvm/firecracker) from `9e68aa4` to `e5ef374`.
- [Release notes](https://github.com/firecracker-microvm/firecracker/releases)
- [Commits](9e68aa4d9d...e5ef374c8e)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-30 17:01:33 +00:00
Sebastien Boeuf
aa4efdd7ec ci: Update kernel related to virtio-iommu
Because of the new set of patches related to virtio-iommu allowing only
for the topology to be described through virtio configuration, this
patch updates the kernel branch and the kernel configuration our CI
relies on.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-30 10:37:40 +01:00
Sebastien Boeuf
ac01ceddbb vmm: Cleanup list of PCI IDs related to virtual IOMMU
Now that devices attached to the virtual IOMMU are described through
virtio configuration, there is no need for the DeviceManager to store
the list of IDs for all these devices. Instead, things are handled
locally when PCI devices are being added.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-30 10:37:40 +01:00
Sebastien Boeuf
097cff2d85 vmm: Use virtio topology for virtio-iommu
Instead of relying on the ACPI tables to describe the devices attached
to the virtual IOMMU, let's use the virtio topology, as the ACPI support
is getting deprecated.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-30 10:37:40 +01:00
Sebastien Boeuf
bac0d1e689 iommu: Implement virtio topology configuration
Based on the new structures previously introduced, the new topology
feature is being fully implemented through this commit. This allows
the description of the devices attached to the virtual IOMMU, which
is why a new function attach_devices() has been introduced. It gives
the virtual IOMMU device the full list of devices which must be attached
to it, letting the device share this information through its virtio
configuration.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-30 10:37:40 +01:00
Sebastien Boeuf
0c73ff8129 iommu: Add topology structures
The virtio-iommu device defines a new virtio feature allowing the
topology to be discovered fully through virtio configuration.

By topology, it means describing the devices attached to the virtual
IOMMU. This is currently managed through ACPI with IORT and VIOT table,
but this is another way of describing it.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-30 10:37:40 +01:00
Sebastien Boeuf
db42caef42 vm-virtio: Handle special virtio-pci capability CAP_PCI_CFG
The virtio capability VIRTIO_PCI_CAP_PCI_CFG is exposed through the
device's PCI config space the same way other virtio-pci capabilities
are exposed.

The main and important difference is that this specific capability is
designed as a way for the guest to access virtio capabilities without
mapping the PCI BAR. This is very rarely used, but it can be useful when
it is too early for the guest to be able to map the BARs.

One thing to note, this special feature MUST be implemented, based on
the virtio specification.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-30 09:25:52 +01:00
Sebastien Boeuf
db9f9b7820 pci: Make self mutable when reading from PCI config space
In order to anticipate the need to support more features related to the
access of a device's PCI config space, this commits changes the self
reference in the function read_config_register() to be mutable.

This also brings some more flexibility for any implementation of the
PciDevice trait.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-30 09:25:52 +01:00
dependabot-preview[bot]
655d9cdb0d build(deps): bump smallvec from 1.1.0 to 1.2.0
Bumps [smallvec](https://github.com/servo/rust-smallvec) from 1.1.0 to 1.2.0.
- [Release notes](https://github.com/servo/rust-smallvec/releases)
- [Commits](https://github.com/servo/rust-smallvec/compare/v1.1.0...v1.2.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-30 01:54:42 +00:00
dependabot-preview[bot]
74ab468bb4 build(deps): bump backtrace from 0.3.42 to 0.3.43
Bumps [backtrace](https://github.com/rust-lang/backtrace-rs) from 0.3.42 to 0.3.43.
- [Release notes](https://github.com/rust-lang/backtrace-rs/releases)
- [Commits](https://github.com/rust-lang/backtrace-rs/compare/0.3.42...0.3.43)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-29 22:59:26 +00:00
dependabot-preview[bot]
1651cc3953 build(deps): bump kvm-ioctls from 0.4.0 to 0.5.0
Bumps [kvm-ioctls](https://github.com/rust-vmm/kvm-ioctls) from 0.4.0 to 0.5.0.
- [Release notes](https://github.com/rust-vmm/kvm-ioctls/releases)
- [Changelog](https://github.com/rust-vmm/kvm-ioctls/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-vmm/kvm-ioctls/compare/v0.4.0...v0.5.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-29 10:22:51 +00:00
Rob Bradford
b4d04bdff6 tests: Add CLI <-> API validation test for --disk changes
Check that the CLI generates the JSON data as expected.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-01-29 08:06:37 +00:00
Rob Bradford
3c8cf0988d docs: Update device model to clarify how to enable vhost-user-block
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-01-29 08:06:37 +00:00
Rob Bradford
75e6762897 vmm: Give deprecation warning for "--vhost-user-blk" syntax
This will be removed in a future release.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-01-29 08:06:37 +00:00
Rob Bradford
969b5ee4e8 vmm: config: Add warning about specifying "wce" without "vhost-user"
Currently configuring WCE is only supported when using vhost-user.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-01-29 08:06:37 +00:00
Rob Bradford
12f4cd951a tests: Use "--disks" for vhost-user-block testing
Use newly unified command line options for testing vhost-user based
block support.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-01-29 08:06:37 +00:00
Rob Bradford
aeeae661fc vmm: Support vhost-user-block via "--disks"
Add a socket and vhost_user parameter to this option so that the same
configuration option can be used for both virtio-block and
vhost-user-block.  For now it is necessary to specify both vhost_user
and socket parameters as auto activation is not yet implemented. The wce
parameter for supporting "Write Cache Enabling" is also added to the
disk configuration.

The original command line parameter is still supported for now and will
be removed in a future release.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-01-29 08:06:37 +00:00
dependabot-preview[bot]
362942fa6e build(deps): bump micro_http from 53cf1ba to 9e68aa4
Bumps [micro_http](https://github.com/firecracker-microvm/firecracker) from `53cf1ba` to `9e68aa4`.
- [Release notes](https://github.com/firecracker-microvm/firecracker/releases)
- [Commits](53cf1bacad...9e68aa4d9d)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-29 07:06:52 +00:00
dependabot-preview[bot]
b5992fe90b build(deps): bump thiserror from 1.0.9 to 1.0.10
Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.9 to 1.0.10.
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/1.0.9...1.0.10)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-28 20:42:12 +00:00
Rob Bradford
2c6f528c23 vmm: Give deprecation warning for "--vhost-user-net" syntax
This will be removed in a future release.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-01-28 12:39:26 +00:00
Rob Bradford
f322e924ad docs: Update device model to clarify how to enable vhost-user-net
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-01-28 12:39:26 +00:00
Rob Bradford
4888dd2e01 tests: Add CLI <-> API validation test for --net changes
Check that the CLI generates the JSON data as expected.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-01-28 12:39:26 +00:00
Rob Bradford
8f096b8be5 tests: Use "--net" parameter for vhost-user-net setup
Use the newly unified parameter for setting up vhost-user-net based
networking.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-01-28 12:39:26 +00:00
Rob Bradford
a831aa214c vmm: Support vhost-user-net via "--net"
Add a socket and vhost_user parameter to this option so that the same
configuration option can be used for both virtio-net and vhost-user-net.
For now it is necessary to specify both vhost_user and socket parameters
as auto activation is not yet implemented. The original command line
parameter is still supported for now.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-01-28 12:39:26 +00:00
Sebastien Boeuf
e155e3690c vm-virtio: Simplify virtio-fs configuration
This commit introduces a clear definition of the virtio-fs
configuration structure, allowing vhost-user-fs device to
rely on it.

This makes the code more readable for developers.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-28 10:28:14 +00:00
Sebastien Boeuf
8e48fc445f vm-virtio: Simplify virtio-blk configuration
This commit reuses the clear definition of the virtio-blk
configuration structure, allowing both vhost-user-blk and
virtio-blk devices to rely on it.

This makes the code more readable for developers.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-28 10:28:14 +00:00
Sebastien Boeuf
8946a09afd vm-virtio: Simplify virtio-net configuration
This commit introduces a clear definition of the virtio-net
configuration structure, allowing both vhost-user-net and
virtio-net devices to rely on it.

This makes the code more readable for developers.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-28 10:28:14 +00:00
Sebastien Boeuf
9efaff73fa ci: Consolidate virtio-blk integration tests
In order to reduce the amount of times VMs are being started through
integration tests, this commit consolidates very similar tests related
to virtio-blk into a single one.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-28 09:26:53 +01:00
Sebastien Boeuf
a33e8342ee ci: Add integration test for virtio-blk multiqueue support
Just add a new integration test to verify that multiqueue support is
correctly supported and that we can find the right amount of queues in
the guest.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-28 09:26:53 +01:00
Sebastien Boeuf
f5b53ae4be vm-virtio: Implement multiqueue/multithread support for virtio-blk
This commit improves the existing virtio-blk implementation, allowing
for better I/O performance. The cost for the end user is to accept
allocating more vCPUs to the virtual machine, so that multiple I/O
threads can run in parallel.

One thing to notice, the amount of vCPUs must be egal or superior to the
amount of queues dedicated to the virtio-blk device.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-28 09:26:53 +01:00
Sebastien Boeuf
08e47ebd4b vmm: Add num_queues and queue_size parameters to virtio-blk
The number of queues and the size of each queue were not configurable.
In anticipation for adding multiqueue support, this commit introduces
some new parameters to let the user decide about the number of queues
and the queue size.

Note that the default values for each of these parameters are identical
to the default values used for vhost-user-blk, that is 1 for the number
of queues and 128 for the queue size.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-28 09:26:53 +01:00
Samuel Ortiz
c4b3ed7223 vm-virtio: Further factorization
The trait bound and non trait bound virtio devices can use the same
inner implementation.
Also, the virtio pausable trait definiton can also be factorized.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-01-28 07:51:13 +01:00
Samuel Ortiz
bce76271c5 vm-virtio: Define a separate macro alias for ctrl queue devices
Now that we have factorized the common virtio pausable implementation,
it's cleaner to have a dedicated macro for control queue devices rather
than overload the macro prototype.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-01-28 07:51:13 +01:00
Samuel Ortiz
2e2b1e4230 vm-virtio: Remove the multiqueue argument from the pausable macro
We only need the ctrl queue one.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-01-28 07:51:13 +01:00
Samuel Ortiz
2cb7ec04a4 vm-virtio: Pausable macro factorization improvements
By adding an internal layer of abstraction (the hidden VirtioPausable
trait), we can factorize the virtio common code.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-01-28 07:51:13 +01:00
Samuel Ortiz
c06a827cbb vm-virtio: Rename epoll_thread to epoll_threads
Now that we unified epoll_thread to potentially be a vector of threads,
it makes sense to make it a plural field.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-01-28 07:51:13 +01:00
Samuel Ortiz
f648f2856d vm-virtio: Make all virtio devices potentially multi-threaded
Although only the block and net virtio devices can actually be multi
threaded (for now), handling them as special cases makes the code more
complex.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-01-28 07:51:13 +01:00
dependabot-preview[bot]
ac8e4b9cb6 build(deps): bump vm-memory from 8a606cd to df6207f
Bumps [vm-memory](https://github.com/rust-vmm/vm-memory) from `8a606cd` to `df6207f`.
- [Release notes](https://github.com/rust-vmm/vm-memory/releases)
- [Commits](8a606cd838...df6207f1c0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-28 06:49:55 +00:00
dependabot-preview[bot]
45d43f9441 build(deps): bump micro_http from af25399 to 53cf1ba
Bumps [micro_http](https://github.com/firecracker-microvm/firecracker) from `af25399` to `53cf1ba`.
- [Release notes](https://github.com/firecracker-microvm/firecracker/releases)
- [Commits](af25399667...53cf1bacad)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-28 06:49:37 +00:00
dependabot-preview[bot]
91747094ae build(deps): bump arrayref from 0.3.5 to 0.3.6
Bumps [arrayref](https://github.com/droundy/arrayref) from 0.3.5 to 0.3.6.
- [Release notes](https://github.com/droundy/arrayref/releases)
- [Commits](https://github.com/droundy/arrayref/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-27 08:56:17 +00:00
dependabot-preview[bot]
90810a9691 build(deps): bump micro_http from 1f931c6 to af25399
Bumps [micro_http](https://github.com/firecracker-microvm/firecracker) from `1f931c6` to `af25399`.
- [Release notes](https://github.com/firecracker-microvm/firecracker/releases)
- [Commits](1f931c6649...af25399667)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-27 08:07:15 +00:00
dependabot-preview[bot]
16af54e583 build(deps): bump signal-hook from 0.1.12 to 0.1.13
Bumps [signal-hook](https://github.com/vorner/signal-hook) from 0.1.12 to 0.1.13.
- [Release notes](https://github.com/vorner/signal-hook/releases)
- [Changelog](https://github.com/vorner/signal-hook/blob/master/CHANGELOG.md)
- [Commits](https://github.com/vorner/signal-hook/compare/v0.1.12...v0.1.13)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-26 13:29:47 +00:00
dependabot-preview[bot]
4ff972c329 build(deps): bump ssh2 from 0.6.0 to 0.7.1
Bumps [ssh2](https://github.com/alexcrichton/ssh2-rs) from 0.6.0 to 0.7.1.
- [Release notes](https://github.com/alexcrichton/ssh2-rs/releases)
- [Commits](https://github.com/alexcrichton/ssh2-rs/compare/0.6.0...0.7.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-26 01:55:04 +00:00
dependabot-preview[bot]
3508440684 build(deps): bump itoa from 0.4.4 to 0.4.5
Bumps [itoa](https://github.com/dtolnay/itoa) from 0.4.4 to 0.4.5.
- [Release notes](https://github.com/dtolnay/itoa/releases)
- [Commits](https://github.com/dtolnay/itoa/compare/0.4.4...0.4.5)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-25 00:14:23 +00:00
Sebastien Boeuf
0fa1e2c241 vmm: Handle mapping from devices regions through vm-memory
Devices like virtio-pmem and virtio-fs require some dedicated memory
region to be mapped. The memory mapping from the DeviceManager is being
replaced by the usage of MmapRegion from the vm-memory crate.

The unmap will happen automatically when the MmapRegion will be dropped,
which should happen when the DeviceManager gets dropped.

Fixes #240

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-24 17:56:49 +01:00
Sebastien Boeuf
694da82691 ci: Make the integration tests fail if images are not valid
When the CI runs in a brand new VM, there's no problem with the validity
of the images as they just got downloaded from the Azure bucket.

In case of a user who runs the CI locally, while doing some debug, he
might provision the images with cloudinit at some point, and later try
to run the CI based on these same images. What happens is that the CI
might randomly fail because the provisioning will not happen again as
it already happened.

This patch ensures the CI to fail early and show an error message to
notify the user about the validity of the images, based on their
sha1sum.

Fixes #112

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-24 14:02:35 +01:00
dependabot-preview[bot]
0ea6406d5b build(deps): bump vm-memory from beaf215 to 8a606cd
Bumps [vm-memory](https://github.com/rust-vmm/vm-memory) from `beaf215` to `8a606cd`.
- [Release notes](https://github.com/rust-vmm/vm-memory/releases)
- [Commits](beaf215905...8a606cd838)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-24 12:32:16 +00:00
Sebastien Boeuf
148a9ed5ce vmm: Fix map_err losing the inner error
Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-24 12:42:09 +01:00
Sebastien Boeuf
0a7bcc9a7d vm-virtio: Fix map_err losing the inner error
Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-24 12:42:09 +01:00
Sebastien Boeuf
4587cc7ffa vhost_user_net: Fix map_err losing the inner error
Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-24 12:42:09 +01:00
Sebastien Boeuf
f444ef6ccf vfio: Fix map_err losing the inner error
Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-24 12:42:09 +01:00
Sebastien Boeuf
aa5ae7616e src: Fix map_err losing the inner error
Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-24 12:42:09 +01:00
Sebastien Boeuf
cf25bd74b0 qcow: Fix map_err losing the inner error
Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-24 12:42:09 +01:00
Sebastien Boeuf
06396593c9 net_util: Fix map_err losing the inner error
Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-24 12:42:09 +01:00
Sebastien Boeuf
f5a52eda2b arch: Fix map_err losing the inner error
Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-24 12:42:09 +01:00
Rob Bradford
a34893a402 Revert "vmm: Move MemoryManager from I/O ports to MMIO region"
This reverts commit 03108fb88b.
2020-01-24 12:08:31 +01:00
Rob Bradford
57ed006992 Revert "devices, vmm: Move GED device to MMIO region"
This reverts commit 5e3c62dc6a.
2020-01-24 12:08:31 +01:00
Rob Bradford
6120d0fb1b Revert "vmm: Move CpuManager device to MMIO region"
This reverts commit 980e03fa0a.
2020-01-24 12:08:31 +01:00
dependabot-preview[bot]
6475052692 build(deps): bump micro_http from 1db1bbd to 1f931c6
Bumps [micro_http](https://github.com/firecracker-microvm/firecracker) from `1db1bbd` to `1f931c6`.
- [Release notes](https://github.com/firecracker-microvm/firecracker/releases)
- [Commits](1db1bbd40d...1f931c6649)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-24 06:47:58 +00:00
Rob Bradford
980e03fa0a vmm: Move CpuManager device to MMIO region
Move the CpuManager device from the I/O bus to living in an MMIO region.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-01-23 16:04:58 +00:00
Rob Bradford
5e3c62dc6a devices, vmm: Move GED device to MMIO region
Move GED device reporting of required device type to scan into an MMIO
region rather than an I/O port.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-01-23 16:04:58 +00:00
Rob Bradford
03108fb88b vmm: Move MemoryManager from I/O ports to MMIO region
Rather than have the MemoryManager device sit on the I/O bus allocate
space for MMIO and add it to the MMIO bus.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-01-23 16:04:58 +00:00
Sebastien Boeuf
3202e8ac7a vfio: Fix 64bits BAR size reading
The way to get the BAR size is by writing all 1's to the BAR register in
the PCI config space. The mechanism was in place but the parameters were
swapped. The data buffer was provided with the actual offset, while the
offset was provided with the actual all 1's dword. We were effectively
trying to write the real offset at the offset 0xffffffff, which was
failing and resulting in the size being wrong.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-23 13:07:43 +01:00
Rob Bradford
d5d4053795 README.md: Update kernel instructions to use branch
Update the kernel build instructions to use the configuration and branch
that we test and develop against.

Fixes: #521

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-01-23 12:56:52 +01:00
Sebastien Boeuf
0042f1de75 ioapic: Rely fully on the InterruptSourceGroup to manage interrupts
This commit relies on the interrupt manager and the resulting interrupt
source group to abstract the knowledge about KVM and how interrupts are
updated and delivered.

This allows the entire "devices" crate to be freed from kvm_ioctls and
kvm_bindings dependencies.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-23 11:20:08 +00:00
Sebastien Boeuf
2dca959084 ioapic: Create the InterruptSourceGroup from InterruptManager
The interrupt manager is passed to the IOAPIC creation, and the IOAPIC
now creates an InterruptSourceGroup for MSI interrupts based on it.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-23 11:20:08 +00:00
Sebastien Boeuf
52800a871a vmm: Create an InterruptManager dedicated to IOAPIC
By introducing a new InterruptManager dedicated to the IOAPIC, we don't
have to solve the chicken and eggs problem about which of the
InterruptManager or the Ioapic should be created first. It's also
totally fine to have two interrupt manager instances as they both share
the same list of GSI routes and the same allocator.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-23 11:20:08 +00:00
Sebastien Boeuf
29e668c302 cargo: Update Cargo.lock
Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-23 11:20:08 +00:00
Rob Bradford
cafab9e981 main: Add some command line argument conflicts
Add explicit exclusions with --net-backend from --block-backend (and
vice-versa.) And also with "--kernel" as this is the option for "VM boot" that is never optional.

Ideally we would conflcit the backend arguments against the "vm-group"
however this does not work as it includes some arguments that have a
default value set and thus clap thinks those arguments are always
provided. Conflicting with "--kernel" is thus a reasonable compromise.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-01-23 10:30:06 +00:00
Rob Bradford
611d13ed1b main: Move logging arguments to their own group
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-01-23 10:30:06 +00:00
Rob Bradford
8aabb9012d main: Add "--vhost-user-blk" to the "vm-config" group
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-01-23 10:30:06 +00:00
Rob Bradford
bdb0ad98c4 tests: Use cloud-hypervisor binary for "--{net,block}-backend"
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-01-23 10:30:06 +00:00
Rob Bradford
45fb9187c5 main: Allow main binary to be used as net or block backend
Use newly factorised out function to start the block or net
vhost-user-backend.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-01-23 10:30:06 +00:00
Rob Bradford
7f73eebbdb vhost_user_block: Split launching backend into its own function
Split the basic launching functionality into its own function in the
newly added vhost_user_block crate.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-01-23 10:30:06 +00:00
Rob Bradford
9fd187c9fe vhost_user_net: Split launching backend into its own function
Split the basic launching functionality into its own function in the
newly added vhost_user_net crate.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-01-23 10:30:06 +00:00
Rob Bradford
b851887738 main: Split current VMM running code into it's own function
This prepaees the main binary to start running vhost-user backends.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-01-23 10:30:06 +00:00
Rob Bradford
1dd2451895 vhost_user_block: Refactor vhost_user_block backend code into a new crate
Extract the majority of the code that provides the vhost-user-block
backend into its own crate and port the binary to use it.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-01-23 10:30:06 +00:00
Rob Bradford
fb763c4043 vhost_user_net: Refactor vhost_user_net backend code into a new crate
Extract the majority of the code that provides the vhost-user-net
backend into its own crate and port the binary to use it.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-01-23 10:30:06 +00:00
Rob Bradford
dd82064aba main: Delay converting command line to VM params
Generate the VM params until after the logging has been enabled. This
will make it possible to reuse the logging for vhost backends.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-01-23 10:30:06 +00:00
Rob Bradford
e12179a3e6 bin: vhost_user_net: Rename "--backend" to "--net-backend"
This will prevent it from conflicting when it is aggregated into the
cloud-hypervisor binary.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-01-23 10:30:06 +00:00
Rob Bradford
3ede2dc53a bin: vhost_user_blk: Rename "--backend" to "--block-backend"
This will prevent it from conflicting when it is aggregated into the
cloud-hypervisor binary.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-01-23 10:30:06 +00:00
dependabot-preview[bot]
032b8e4c1d build(deps): bump micro_http from ebc15f2 to 1db1bbd
Bumps [micro_http](https://github.com/firecracker-microvm/firecracker) from `ebc15f2` to `1db1bbd`.
- [Release notes](https://github.com/firecracker-microvm/firecracker/releases)
- [Commits](ebc15f2eac...1db1bbd40d)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-23 06:44:46 +00:00
dependabot-preview[bot]
204f05fdf1 build(deps): bump serde_json from 1.0.44 to 1.0.45
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.44 to 1.0.45.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.44...v1.0.45)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-23 06:14:06 +00:00
dependabot-preview[bot]
7ee9ecaaf4 build(deps): bump vmm-sys-util from 0.3.1 to 0.4.0
Bumps [vmm-sys-util](https://github.com/rust-vmm/vmm-sys-util) from 0.3.1 to 0.4.0.
- [Release notes](https://github.com/rust-vmm/vmm-sys-util/releases)
- [Changelog](https://github.com/rust-vmm/vmm-sys-util/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-vmm/vmm-sys-util/compare/v0.3.1...v0.4.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-22 14:10:19 +00:00
Qiu Wenbo
2034fc2d84 vmm: Fix LENGTH_OFFSET_HIGH of MemoryManager
Signed-off-by: Qiu Wenbo <qiuwenbo@phytium.com.cn>
2020-01-22 12:33:38 +00:00
dependabot-preview[bot]
503e047c3a build(deps): bump redox_users from 0.3.3 to 0.3.4
Bumps redox_users from 0.3.3 to 0.3.4.

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-22 07:39:39 +00:00
Sergio Lopez
605fa8307b ci: Add integration test for virtio-blk with 'direct=on'
Previous commit added 'direct' support for virtio-blk to be able to
open files backing disk images with O_DIRECT, so add an integration
test for this new feature.

Signed-off-by: Sergio Lopez <slp@redhat.com>
2020-01-21 13:39:45 +00:00
Sergio Lopez
925c862f98 vmm: device_manager: Add 'direct' support for virtio-blk
vhost_user_blk already has it, so it's only fair to give it to
virtio-blk too. Extend DiskConfig with a 'direct' property, honor
it while opening the file backing the disk image, and pass it to
vm_virtio::RawFile.

Fixes #631

Signed-off-by: Sergio Lopez <slp@redhat.com>
2020-01-21 13:39:45 +00:00
Sergio Lopez
2bd90d9263 ci: Add integration test for virtio-blk with 'readonly=on'
Previous commit added 'readonly' support for virtio-blk to be able to
configure read-only devices, so add an integration test for this new
feature.

Signed-off-by: Sergio Lopez <slp@redhat.com>
2020-01-21 13:39:45 +00:00
Sergio Lopez
fb79e75afc vmm: device_manager: Add read-only support for virtio-blk
vhost_user_blk already has it, so it's only fair to give it to
virtio-blk too. Extend DiskConfig with a 'readonly' properly, and pass
it to vm_virtio::Block.

Signed-off-by: Sergio Lopez <slp@redhat.com>
2020-01-21 13:39:45 +00:00
Sebastien Boeuf
9ac06bf613 ci: Run clippy for each specific feature
The build is run against "--all-features", "pci,acpi", "pci" and "mmio"
separately. The clippy validation must be run against the same set of
features in order to validate the code is correct.

Because of these new checks, this commit includes multiple fixes
related to the errors generated when manually running the checks.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-21 11:44:40 +01:00
Sebastien Boeuf
e91638e6c5 pci: Cleanup the crate from unneeded types
Both InterruptDelivery and InterruptParameters can be removed from the
pci crate as they are not used anymore.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-21 10:44:48 +01:00
Sebastien Boeuf
99f39291fd pci: Simplify PciDevice trait
There's no need for assign_irq() or assign_msix() functions from the
PciDevice trait, as we can see it's never used anywhere in the codebase.
That's why it's better to remove these methods from the trait, and
slightly adapt the existing code.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-21 10:44:48 +01:00
Sebastien Boeuf
a20b383be8 vmm: Always use a reference for InterruptManager
Since the InterruptManager is never stored into any structure, it should
be passed as a reference instead of being cloned.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-21 10:44:48 +01:00
Sebastien Boeuf
bb8cd9eb24 vmm: Use LegacyUserspaceInterruptGroup for acpi device
This commit replaces the way legacy interrupts were handled with the
brand new implementation of the legacy InterruptSourceGroup for KVM.

Additionally, since it removes the last bit relying on the Interrupt
trait, the trait and its implementation can be removed from the
codebase.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-21 10:44:48 +01:00
Sebastien Boeuf
75e22ff34e vmm: Use LegacyUserspaceInterruptGroup for serial device
This commit replaces the way legacy interrupts were handled with the
brand new implementation of the legacy InterruptSourceGroup for KVM.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-21 10:44:48 +01:00
Sebastien Boeuf
8d7c4ea334 vmm: Use LegacyUserspaceInterruptGroup for mmio devices
This commit replaces the way legacy interrupts were handled with the
brand new implementation of the legacy InterruptSourceGroup for KVM.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-21 10:44:48 +01:00
Sebastien Boeuf
12657ef59f vmm: Fully implement LegacyUserspaceInterruptGroup
Relying on the previous commits, the legacy interrupt implementation can
be completed. The IOAPIC handler is used to deliver the interrupt that
will be triggered through the trigger() method.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-21 10:44:48 +01:00
Sebastien Boeuf
f70c9937fb vmm: Add ioapic to KvmInterruptManager
By having a reference to the IOAPIC, the KvmInterruptManager is going
to be able to initialize properly the legacy interrupt source group.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-21 10:44:48 +01:00
Sebastien Boeuf
c9ea235a0e vmm: Add LegacyUserspaceInterruptGroup skeleton for legacy interrupts
In order to be able to use the InterruptManager abstraction with
virtio-mmio devices, this commit introduces InterruptSourceGroup's
skeleton for legacy interrupts.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-21 10:44:48 +01:00
Sebastien Boeuf
2aabf58bf5 vmm: Move irq_routes creation to specific MSI use case
When KvmInterruptManager initializes a new InterruptSourceGroup, it's
only for PCI_MSI_IRQ case that it needs to allocate the GSI and create a
new InterruptRoute. That's why this commit moves the general code into
the specific use case.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-21 10:44:48 +01:00
Sebastien Boeuf
d34f31fe7b vmm: Fix KvmInterruptManager when base is different from 0
When the base InterruptIndex is different from 0, the loop allocating
GSI and HashMap entries won't work as expected. The for loop needs to
start from base, but the limit must be base+count so that we allocate
a number of "count" entries.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-21 10:44:48 +01:00
Sebastien Boeuf
e73cb1ff80 vmm: Initialize InterruptManager sooner
In order to let the InterruptManager be shared across both PCI and MMIO
devices, this commit moves the initialization earlier in the code.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-21 10:44:48 +01:00
dependabot-preview[bot]
9fab9bc645 build(deps): bump micro_http from f77960b to ebc15f2
Bumps [micro_http](https://github.com/firecracker-microvm/firecracker) from `f77960b` to `ebc15f2`.
- [Release notes](https://github.com/firecracker-microvm/firecracker/releases)
- [Commits](f77960b81e...ebc15f2eac)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-21 06:54:50 +00:00
dependabot-preview[bot]
9fe6ece262 build(deps): bump redox_users from 0.3.1 to 0.3.3
Bumps redox_users from 0.3.1 to 0.3.3.

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-20 21:47:15 +00:00
dependabot-preview[bot]
c27be842e4 build(deps): bump syn from 1.0.13 to 1.0.14
Bumps [syn](https://github.com/dtolnay/syn) from 1.0.13 to 1.0.14.
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/1.0.13...1.0.14)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-20 10:21:53 +00:00
dependabot-preview[bot]
9731be1126 build(deps): bump remain from 0.2.0 to 0.2.1
Bumps [remain](https://github.com/dtolnay/remain) from 0.2.0 to 0.2.1.
- [Release notes](https://github.com/dtolnay/remain/releases)
- [Commits](https://github.com/dtolnay/remain/compare/0.2.0...0.2.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-20 08:52:00 +00:00
dependabot-preview[bot]
eedfb36c66 build(deps): bump micro_http from 6327290 to f77960b
Bumps [micro_http](https://github.com/firecracker-microvm/firecracker) from `6327290` to `f77960b`.
- [Release notes](https://github.com/firecracker-microvm/firecracker/releases)
- [Commits](6327290424...f77960b81e)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-20 07:10:42 +00:00
Rob Bradford
3901a1dd7d vmm: Log an error if VM resize fails
As well as returing an error to the API caller.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-01-17 23:44:21 +01:00
Rob Bradford
76d9bf2792 vmm: Start memory slots at zero
After refactoring a common function is used to setup these slots and
that function takes care of allocating a new slot so it is not necessary
to reserve the initial region slots.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-01-17 23:44:21 +01:00
Rob Bradford
0ab22fea2c vmm: Only generate GED event when new DIMM added
Avoid the ACPI scan in the guest OS when no new DIMM is hotplugged.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-01-17 23:44:21 +01:00
Rob Bradford
211786ab42 vmm: Only generate GED interrupt when the number of vCPUs has changed
Avoid activity in the the guest OS if the number of vCPUs has not
changed.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-01-17 23:44:21 +01:00
Sebastien Boeuf
8049666eff vm-virtio: Cleanup from kvm_iotcls and kvm_bindings dependencies
Now that KVM specific interrupts are handled through InterruptManager
trait implementation, the vm-virtio crate does not need to rely on
kvm_ioctls and kvm_bindings crates.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-17 23:43:45 +01:00
Sebastien Boeuf
50a4c16d34 pci: Cleanup the crate from kvm_iotcls and kvm_bindings dependencies
Now that KVM specific interrupts are handled through InterruptManager
trait implementation, the pci crate does not need to rely on kvm_ioctls
and kvm_bindings crates.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-17 23:43:45 +01:00
Sebastien Boeuf
4bb12a2d8d interrupt: Reorganize all interrupt management with InterruptManager
Based on all the previous changes, we can at this point replace the
entire interrupt management with the implementation of InterruptManager
and InterruptSourceGroup traits.

By using KvmInterruptManager from the DeviceManager, we can provide both
VirtioPciDevice and VfioPciDevice a way to pick the kind of
InterruptSourceGroup they want to create. Because they choose the type
of interrupt to be MSI/MSI-X, they will be given a MsiInterruptGroup.

Both MsixConfig and MsiConfig are responsible for the update of the GSI
routes, which is why, by passing the MsiInterruptGroup to them, they can
still perform the GSI route management without knowing implementation
details. That's where the InterruptSourceGroup is powerful, as it
provides a generic way to manage interrupt, no matter the type of
interrupt and no matter which hypervisor might be in use.

Once the full replacement has been achieved, both SystemAllocator and
KVM specific dependencies can be removed.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-17 23:43:45 +01:00
Sebastien Boeuf
92082ad439 vmm: Fully implement interrupt traits
After the skeleton of InterruptManager and InterruptSourceGroup traits
have been implemented, this new commit takes care of fully implementing
the content of KvmInterruptManager (InterruptManager trait) and
MsiInterruptGroup (InterruptSourceGroup).

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-17 23:43:45 +01:00
Sebastien Boeuf
0f727127d5 vmm: Implement InterruptSourceGroup and InterruptManager skeleton
This commit introduces an empty implementation of both InterruptManager
and InterruptSourceGroup traits, as a proper basis for further
implementation.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-17 23:43:45 +01:00
Sebastien Boeuf
11d4d57c06 vm-device: Introduce InterruptManager and InterruptSourceGroup traits
These new traits are meant to abstract the knowledge about the
hypervisor and the type of interrupt being used from the perspective
of the devices.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-17 23:43:45 +01:00
Sebastien Boeuf
be421dccea vm-virtio: Optimize vhost-user interrupt notification
Thanks to the recently introduced function notifier() in the
VirtioInterrupt trait, all vhost-user devices can now bypass
listening onto an intermediate event fd as they can provide the
actual fd responsible for triggering the interrupt directly to
the vhost-user backend.

In case the notifier does not provide the event fd, the code falls
back onto the creation of an intermediate event fd it needs to listen
to, so that it can trigger the interrupt on behalf of the backend.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-17 23:43:45 +01:00
Sebastien Boeuf
1f029dd2dc vm-virtio: Add notifier to VirtioInterrupt trait
The point is to be able to retrieve directly the event fd related to
the interrupt, as this might optimize the way VirtioDevice devices are
implemented.

For instance, this can be used by vhost-user devices to provide
vhost-user backends directly with the event fd triggering the
interrupt related to a virtqueue.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-17 23:43:45 +01:00
Sebastien Boeuf
c396baca46 vm-virtio: Modify VirtioInterrupt callback into a trait
Callbacks are not the most Rust idiomatic way of programming. The right
way is to use a Trait to provide multiple implementation of the same
interface.

Additionally, a Trait will allow for multiple functions to be defined
while using callbacks means that a new callback must be introduced for
each new function we want to add.

For these two reasons, the current commit modifies the existing
VirtioInterrupt callback into a Trait of the same name.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-17 23:43:45 +01:00
Sebastien Boeuf
ef7d889a79 vfio: Remove unused GSI routing functions
At this point, both MSI and MSI-X handle the KVM GSI routing update,
which means the vfio crate does not have to deal with it anymore.
Therefore, several functions can be removed from the vfio-pci code, as
they are not needed anymore.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-17 23:43:45 +01:00
Sebastien Boeuf
1a4b5ecc75 msi: Set KVM routes from MsiConfig instead of VFIO
Now that MsiConfig has access to both KVM VmFd and the list of GSI
routes, the update of the KVM GSI routes can be directly done from
MsiConfig instead of specifically from the vfio-pci implementation.

By moving the KVM GSI routes update at the MsiConfig level, any PCI
device such as vfio-pci, virtio-pci, or any other emulated PCI device
can benefit from it, without having to implement it on their own.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-17 23:43:45 +01:00
Sebastien Boeuf
f3c3870159 msi: Create MsiConfig to embed MsiCap
The same way we have MsixConfig in charge of managing whatever relates
to MSI-X vectors, we need a MsiConfig structure to manage MSI vectors.
The MsiCap structure is still needed as a low level API, but it is now
part of the MsiConfig which oversees anything related to MSI.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-17 23:43:45 +01:00
Sebastien Boeuf
1e5e02801f msix: Perform interrupt enabling/disabling
In order to factorize one step further, we let MsixConfig perform the
interrupt enabling/disabling. This is done by registering/unregistering
the KVM irq_fds of all GSI routes related to this device.

And now that MsixConfig is in charge of the irq_fds, vfio-pci must rely
on it to retrieve them and provide them to the vfio driver.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-17 23:43:45 +01:00
Sebastien Boeuf
19aeac40c9 msix: Remove the need for interrupt callback
Now that MsixConfig has access to the irq_fd descriptors associated with
each vector, it can directly write to it anytime it needs to trigger an
interrupt.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-17 23:43:45 +01:00
Sebastien Boeuf
3fe362e3bd msix: Set KVM routes from MsixConfig instead of VFIO
Now that MsixConfig has access to both KVM VmFd and the list of GSI
routes, the update of the KVM GSI routes can be directly done from
MsixConfig instead of specifically from the vfio-pci implementation.

By moving the KVM GSI routes update at the MsixConfig level, both
vfio-pci and virtio-pci (or any other emulated PCI device) can benefit
from it, without having to implement it on their own.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-17 23:43:45 +01:00
Sebastien Boeuf
2381f32ae0 msix: Add gsi_msi_routes to MsixConfig
Because MsixConfig will be responsible for updating KVM GSI routes at
some point, it is necessary that it can access the list of routes
contained by gsi_msi_routes.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-17 23:43:45 +01:00
Sebastien Boeuf
9b60fcdc39 msix: Add VmFd to MsixConfig
Because MsixConfig will be responsible for updating the KVM GSI routes
at some point, it must have access to the VmFd to invoke the KVM ioctl
KVM_SET_GSI_ROUTING.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-17 23:43:45 +01:00
Sebastien Boeuf
86c760a0d9 msix: Add SystemAllocator to MsixConfig
The point here is to let MsixConfig take care of the GSI allocation,
which means the SystemAllocator must be passed from the vmm crate all
the way down to the pci crate.

Once this is done, the GSI allocation and irq_fd creation is performed
by MsixConfig directly.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-17 23:43:45 +01:00
Sebastien Boeuf
f77d2c2d16 pci: Add some KVM and interrupt utilities to the crate
In order to anticipate the need for both msi.rs and msix.rs to rely on
some KVM utils and InterruptRoute structure to handle the update of the
KVM GSI routes, this commit adds these utilities directly to the pci
crate. So far, these were exclusively used by the vfio crate, which is
why there were located there.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-17 23:43:45 +01:00
Sebastien Boeuf
f5704d32b3 vmm: Move gsi_msi_routes creation to be shared across all PCI devices
Because we will need to share the same list of GSI routes across
multiple PCI devices (virtio-pci, VFIO), this commit moves the creation
of such list to a higher level location in the code.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-17 23:43:45 +01:00
Sergio Lopez
ed5459f268 ci: Add integration test for vhost_user_blk with 'direct'
This test is a variant of test_boot_vhost_user_blk(), named
test_boot_vhost_user_blk_direct(), that instances the vhost-user-blk
daemon with 'direct=true', to test this recently introduced feature
for opening files with O_DIRECT.

Signed-off-by: Sergio Lopez <slp@redhat.com>
2020-01-17 17:28:44 +00:00
Sergio Lopez
e0a8da2f46 vhost_user_blk: Add missing WCE property support
Add missing WCE (write-cache enable) property support. This not only
an enhancement, but also a fix for a bug.

Right now, when vhost_user_blk uses a qcow2 image, it doesn't write
the QCOW2 metadata until the guest explicitly requests a flush. In
practice, this is equivalent to the write back semantic.

Without WCE, the guest assumes write through for the virtio_blk
device, and doesn't send those flush requests. Adding support for WCE,
and enabling it by default, we ensure the guest does send said
requests.

Supporting "WCE = false" would require updating our qcow2
implementation to ensure that, when required, it honors the write
through semantics by not deferring the updates to QCOW2 metadata.

Signed-off-by: Sergio Lopez <slp@redhat.com>
2020-01-17 17:28:44 +00:00
Sergio Lopez
c7e9056c1e vhost_user_blk: implement support for direct (O_DIRECT) mode
Add support for opening the disk images with O_DIRECT. This allows
bypassing the host's file system cache, which is useful to avoid
polluting its cache and for better data integrity.

This mode of operation can be enabled by adding the "direct=<bool>"
parameter to the "backend" argument:

./target/debug/vhost_user_blk --backend image=test.raw,sock=/tmp/vhostblk,direct=true

The "direct" parameter defaults to "false", to preserve the original
behavior.

Signed-off-by: Sergio Lopez <slp@redhat.com>
2020-01-17 17:28:44 +00:00
Sergio Lopez
a14aee9213 qcow: Use RawFile as backend instead of File
Use RawFile as backend instead of File. This allows us to abstract
the access to the actual image with a specialized layer, so we have a
place where we can deal with the low-level peculiarities.

Signed-off-by: Sergio Lopez <slp@redhat.com>
2020-01-17 17:28:44 +00:00
Sergio Lopez
c5a656c9dc vm-virtio: block: Add support for alignment restrictions
Doing I/O on an image opened with O_DIRECT requires to adhere to
certain restrictions, requiring the following elements to be aligned:

 - Address of the source/destination memory buffer.
 - File offset.
 - Length of the data to be read/written.

The actual alignment value depends on various elements, and according
to open(2) "(...) there is currently no filesystem-independent
interface for an application to discover these restrictions (...)".

To discover such value, we iterate through a list of alignments
(currently, 512 and 4096) calling pread() with each one and checking
if the operation succeeded.

We also extend RawFile so it can be used as a backend for QcowFile,
so the later can be easily adapted to support O_DIRECT too.

Signed-off-by: Sergio Lopez <slp@redhat.com>
2020-01-17 17:28:44 +00:00
Cathy Zhang
e483cde1bb docs: Update networking.md with multiple queue support
Signed-off-by: Cathy Zhang <cathy.zhang@intel.com>
2020-01-17 12:06:19 +01:00
Cathy Zhang
973eb16ae9 src: Add multiple queue checking in vhost-user-net integration test
Update queue number with 4 to verify if vhost-user-net device
and backend could work well with multiple queue.

Signed-off-by: Cathy Zhang <cathy.zhang@intel.com>
2020-01-17 12:06:19 +01:00
Cathy Zhang
4885dc0ed4 src: Update test_valid_vm_config_net with new option for virtio-net
There are two new options num_queues and queue_size defined for
virtio-net, add them in test_valid_vm_config_net which is used
to validate that both the CLI and the OpenAPI will generate the
same configuration.

Signed-off-by: Cathy Zhang <cathy.zhang@intel.com>
2020-01-17 12:06:19 +01:00
Cathy Zhang
652e7b9b8a vm-virtio: Implement multiple queue support for net devices
Update the common part in net_util.rs under vm-virtio to add mq
support, meanwhile enable mq for virtio-net device, vhost-user-net
device and vhost-user-net backend. Multiple threads will be created,
one thread will be responsible to handle one queue pair separately.
To gain the better performance, it requires to have the same amount
of vcpus as queue pair numbers defined for the net device, due to
the cpu affinity.

Multiple thread support is not added for vhost-user-net backend
currently, it will be added in future.

Signed-off-by: Cathy Zhang <cathy.zhang@intel.com>
2020-01-17 12:06:19 +01:00
Cathy Zhang
404316eea1 vmm: Add multiple queue option and update config for virtio-net device
Add num_queues and queue_size for virtio-net device to make them configurable,
while add the associated options in command line.

Update cloud-hypervisor.yaml with the new options for NetConfig.

Signed-off-by: Cathy Zhang <cathy.zhang@intel.com>
2020-01-17 12:06:19 +01:00
Cathy Zhang
4ab88a8173 net_util: Add multiple queue support for tap
Add support to allow VMMs to open the same tap device many times, it will
create multiple file descriptors meanwhile.

Signed-off-by: Cathy Zhang <cathy.zhang@intel.com>
2020-01-17 12:06:19 +01:00
Cathy Zhang
cf7e0cdf95 vm-virtio: Add multiple queue handling with control queue
Current guest kernel will check the oneline cpu count, in principle,
if the online cpu count is not smaller than the number of queue pairs
VMM reported, the net packets could be put/get to all the virtqueues,
otherwise, only the number of queue pairs that match the oneline cpu
count will have packets work with. guest kernel will send command
through control queue to tell VMMs the actual queue pair numbers which
it could currently play with. Add mq process in control queue handling
to get the queue pair number, VMM will verify if it is in a valid range,
nothing else but this.

Signed-off-by: Cathy Zhang <cathy.zhang@intel.com>
2020-01-17 12:06:19 +01:00
Cathy Zhang
709f7fe607 vm-virtio: Implement control queue support for net devices
While feature VIRTIO_NET_F_CTRL_VQ is negotiated, control queue
will exits besides the Tx/Rx virtqueues, an epoll handler should
be started to monitor and handle the control queue event.

Signed-off-by: Cathy Zhang <cathy.zhang@intel.com>
2020-01-17 12:06:19 +01:00
Cathy Zhang
d38787c578 vm-virtio: Add control queue support in net_util.rs
As virtio spec 1.1 said, the driver uses the control queue
to send commands to manipulate various features of the devices,
such as VIRTIO_NET_F_MQ which is required by multiple queue
support. Here add the control queue handling process.

Signed-off-by: Cathy Zhang <cathy.zhang@intel.com>
2020-01-17 12:06:19 +01:00
Cathy Zhang
1ae7deb393 vm-virtio: Implement refactor for net devices and backend
Since the common parts are put into net_util.rs under vm-virtio,
refactoring code for virtio-net device, vhost-user-net device
and backend to shrink the code size and improve readability
meanwhile.

Signed-off-by: Cathy Zhang <cathy.zhang@intel.com>
2020-01-17 12:06:19 +01:00
Cathy Zhang
6ae2597d19 vm-virtio: Create new module to abstract common parts for net devices
There are some common logic shared among virtio-net device, vhost-user-net
device and vhost-user-net backend, abstract those parts into net_util.rs
to improve code maintainability and readability.

Signed-off-by: Cathy Zhang <cathy.zhang@intel.com>
2020-01-17 12:06:19 +01:00
Cathy Zhang
3485e89080 vm-virtio: Stop delivering interrupt while NO_VECTOR
According to virtio spec, for used buffer notifications, if
MSI-X capability is enabled, and queue msix vector is
VIRTIO_MSI_NO_VECTOR 0xffff, the device must not deliver an
interrupt for that virtqueue.

Signed-off-by: Cathy Zhang <cathy.zhang@intel.com>
2020-01-17 12:06:19 +01:00
dependabot-preview[bot]
1324aa451f build(deps): bump proc-macro2 from 1.0.7 to 1.0.8
Bumps [proc-macro2](https://github.com/alexcrichton/proc-macro2) from 1.0.7 to 1.0.8.
- [Release notes](https://github.com/alexcrichton/proc-macro2/releases)
- [Commits](https://github.com/alexcrichton/proc-macro2/compare/1.0.7...1.0.8)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-17 06:58:27 +00:00
dependabot-preview[bot]
dcb6d02b98 build(deps): bump micro_http from db75e88 to 6327290
Bumps [micro_http](https://github.com/firecracker-microvm/firecracker) from `db75e88` to `6327290`.
- [Release notes](https://github.com/firecracker-microvm/firecracker/releases)
- [Commits](db75e88e9e...6327290424)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-17 06:58:10 +00:00
dependabot-preview[bot]
cde2c4449b build(deps): bump backtrace from 0.3.41 to 0.3.42
Bumps [backtrace](https://github.com/rust-lang/backtrace-rs) from 0.3.41 to 0.3.42.
- [Release notes](https://github.com/rust-lang/backtrace-rs/releases)
- [Commits](https://github.com/rust-lang/backtrace-rs/compare/0.3.41...0.3.42)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-16 10:59:19 +00:00
dependabot-preview[bot]
d8adf6a6d7 build(deps): bump micro_http from 52e21d0 to db75e88
Bumps [micro_http](https://github.com/firecracker-microvm/firecracker) from `52e21d0` to `db75e88`.
- [Release notes](https://github.com/firecracker-microvm/firecracker/releases)
- [Commits](52e21d0f9e...db75e88e9e)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-16 06:38:18 +00:00
Rob Bradford
14041e97e7 docs: Add memory resizing documentation
Add documentation for memory resizing.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-01-15 20:21:22 +01:00
Rob Bradford
32506dadfc docs: Document CPU unplug
This newly added feature wasn't yet documented.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-01-15 20:21:22 +01:00
Rob Bradford
8b500d7873 deps: Bump vm-memory and linux-loader version
The function GuestMemory::end_addr() has been renamed to last_addr()

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-01-15 20:21:22 +01:00
Rob Bradford
7310ab6fa7 devices, vmm: Use a bit field for ACPI GED interrupt type
Use independent bits for storing whether there is a CPU or memory device
changed when reporting changes via ACPI GED interrupt. This prevents a
later notification squashing an earlier one and ensure that hotplugging
both CPU and memory at the same time succeeds.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-01-15 20:21:22 +01:00
Rob Bradford
d2d1248342 tests: Add test combining memory and vCPU hotplug
Test resizing with both optional parameters set in the VmResize API
works.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-01-15 20:21:22 +01:00
Rob Bradford
2073007214 tests: Add integration test for RAM hotplug
Reuse the existing infrastructure for CPU hotplug and add a new test for
memory hotplug.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-01-15 20:21:22 +01:00
Rob Bradford
351058ab0f resources: Add memory hotplug support to the kernel configuration
Add the config option for enabling memory hotplug to our recommended
kenrel configuration.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-01-15 20:21:22 +01:00
Rob Bradford
28c6652e57 vmm: Upon VmResize attempt to hotplug the memory
If a new amount of RAM is requested in the VmResize command try and
hotplug if it an increase (MemoryManager::Resize() silently ignores
decreases.)

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-01-15 20:21:22 +01:00
Rob Bradford
4e414f0d84 vmm: device_manager: Scan memory devices upon GED interrupt
If there is a GED interrupt and the field indicates that the memory
device has changed triggers a scan of the memory devices.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-01-15 20:21:22 +01:00
Rob Bradford
284d5e011a vmm: Add memory hotplug ACPI entries to DSDT
Generate and expose the DSDT table entries required to support memory
hotplug. The AML methods call into the MemoryManager via I/O ports
exposed as fields.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-01-15 20:21:22 +01:00
Rob Bradford
8ecf736982 vmm: device_manager: Add the MemoryManager to the I/O bus
Now that the MemoryManager has I/O port functionality it needs to be
exposed on the I/O bus.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-01-15 20:21:22 +01:00
Rob Bradford
1218765df2 vmm: memory_manager: Expose the slots details via an I/O port
Expose the details of hotplug RAM slots via an I/O port. This will be
consumed by the ACPI DSDT tables to report the hotplug memory details to
the guest.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-01-15 20:21:22 +01:00
Rob Bradford
9880a2aba9 vmm: memory_manger: Add support for adding new memory to the VM
Add a "resize()" method on MemoryManager which will create a new memory
allocation based on the difference between the desired RAM amount and
the amount already in use. After allocating the added RAM using the same
backing method as the boot RAM store the details in a vector and update
the KVM map and create a new GuestMemoryMmap and replace all the users.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-01-15 20:21:22 +01:00
Rob Bradford
82fce5a4e2 vmm: Add support for resizing the memory used by the VM
For now the new memory size is only used after a reboot but support for
hotplugging memory will be added in a later commit.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-01-15 20:21:22 +01:00
Rob Bradford
78dcb1862c vmm: device_manager: Store the type of notification in a local value
When the value is read from the I/O port via the ACPI AML functions to
determine what has been triggered the notifiction value is reset
preventing a second read from exposing the value. If we need support
multiple types of GED notification (such as memory hotplug) then we
should avoid reading the value multiple times.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-01-15 20:21:22 +01:00
Rob Bradford
5da7c63127 acpi_tables: aml: Add support for creating fields
These fields allow you to expose the component part of an existing
buffer, such as a resource template as a new field which is required to
expose the memory CRS details.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-01-15 20:21:22 +01:00
Rob Bradford
f5137e84bb vmm, main: Add optional "hotplug_size" to --mem
This specifies how much address space should be reserved for hotplugging
of RAM. This space is reserved by adding move the start of the device
area by the desired amount.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-01-15 20:21:22 +01:00
Rob Bradford
f1b6657833 vmm: Make desired vCPUs optional in resize command
In order to be able to support resizing either vCPUs or memory or both
make the fields in the resize command optional.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-01-15 20:21:22 +01:00
Rob Bradford
72b9e920a3 vmm: memory_manager: Further refactor memory region allocation
This allows the memory regions to be allocated later which is necessary
for hotplug memory.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-01-15 20:21:22 +01:00
Rob Bradford
1af11a7c92 vmm: memory_manager: Refactor GuestMemoryMmap construction
Make the GuestMemoryMmap from a Vec<Arc<GuestRegionMmap>> by using this
method we can persist a set of regions in the MemoryManager and then
extend this set with a newly created region. Ultimately that will allow
the hotplug of memory.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-01-15 20:21:22 +01:00
dependabot-preview[bot]
281b99af0a build(deps): bump micro_http from 803c8fa to 52e21d0
Bumps [micro_http](https://github.com/firecracker-microvm/firecracker) from `803c8fa` to `52e21d0`.
- [Release notes](https://github.com/firecracker-microvm/firecracker/releases)
- [Commits](803c8fae14...52e21d0f9e)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-15 07:26:59 +00:00
dependabot-preview[bot]
0acabcdd27 build(deps): bump libssh2-sys from 0.2.13 to 0.2.14
Bumps [libssh2-sys](https://github.com/alexcrichton/ssh2-rs) from 0.2.13 to 0.2.14.
- [Release notes](https://github.com/alexcrichton/ssh2-rs/releases)
- [Commits](https://github.com/alexcrichton/ssh2-rs/compare/libssh2-sys-0.2.13...libssh2-sys-0.2.14)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-15 07:26:40 +00:00
dependabot-preview[bot]
85f43c7026 build(deps): bump micro_http from edbff4c to 803c8fa
Bumps [micro_http](https://github.com/firecracker-microvm/firecracker) from `edbff4c` to `803c8fa`.
- [Release notes](https://github.com/firecracker-microvm/firecracker/releases)
- [Commits](edbff4ce62...803c8fae14)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-14 06:42:28 +00:00
Samuel Ortiz
5788d36583 vmm: Do not create virtio devices when missing a transport
If neither PCI or MMIO are built in, we should not bother creating any
virtio devices at all.
When building a minimal VMM made of a kernel with an initramfs and a
serial console, the RNG virtio device is still created even though there
is no way it can ever get probed.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-01-14 07:42:09 +01:00
dependabot-preview[bot]
41895d7df1 build(deps): bump rand from 0.7.2 to 0.7.3
Bumps [rand](https://github.com/rust-random/rand) from 0.7.2 to 0.7.3.
- [Release notes](https://github.com/rust-random/rand/releases)
- [Changelog](https://github.com/rust-random/rand/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-random/rand/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-13 16:24:25 +00:00
dependabot-preview[bot]
85d155543e build(deps): bump micro_http from 0987cdc to edbff4c
Bumps [micro_http](https://github.com/firecracker-microvm/firecracker) from `0987cdc` to `edbff4c`.
- [Release notes](https://github.com/firecracker-microvm/firecracker/releases)
- [Commits](0987cdc52b...edbff4ce62)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-13 16:24:25 +00:00
dependabot-preview[bot]
3430e20f64 build(deps): bump memchr from 2.2.1 to 2.3.0
Bumps [memchr](https://github.com/BurntSushi/rust-memchr) from 2.2.1 to 2.3.0.
- [Release notes](https://github.com/BurntSushi/rust-memchr/releases)
- [Commits](https://github.com/BurntSushi/rust-memchr/compare/2.2.1...2.3.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-13 16:24:25 +00:00
dependabot-preview[bot]
7d853241ad build(deps): bump constant_time_eq from 0.1.4 to 0.1.5
Bumps [constant_time_eq](https://github.com/cesarb/constant_time_eq) from 0.1.4 to 0.1.5.
- [Release notes](https://github.com/cesarb/constant_time_eq/releases)
- [Commits](https://github.com/cesarb/constant_time_eq/compare/0.1.4...0.1.5)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-13 16:24:25 +00:00
dependabot-preview[bot]
fc8bfe07f3 build(deps): bump backtrace from 0.3.40 to 0.3.41
Bumps [backtrace](https://github.com/rust-lang/backtrace-rs) from 0.3.40 to 0.3.41.
- [Release notes](https://github.com/rust-lang/backtrace-rs/releases)
- [Commits](https://github.com/rust-lang/backtrace-rs/compare/0.3.40...0.3.41)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-13 16:24:25 +00:00
Sebastien Boeuf
fa34fae55f ci: Move to personal branch for virtiofsd
The current virtio-fs-dev branch from the official repo is breaking the
build on our Azure VMs because the glibc wrapper for renameat2() syscall
does not exist. This branch fixes the problem while the maintainers are
fixing the issue upstream.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-13 15:55:34 +00:00
Sebastien Boeuf
f427d94422 virtio-fs: Update virtiofs daemon parameters
With the latest version of virtiofsd, some changes have been made
to the socket path parameter. This needs to be updated in the cloud
hypervisor codebase, so that our CI keeps running correctly.

Fixes #611

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-13 15:55:34 +00:00
dependabot-preview[bot]
345c922cb9 build(deps): bump micro_http from 0bfaf39 to 0987cdc
Bumps [micro_http](https://github.com/firecracker-microvm/firecracker) from `0bfaf39` to `0987cdc`.
- [Release notes](https://github.com/firecracker-microvm/firecracker/releases)
- [Commits](0bfaf39af1...0987cdc52b)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-10 07:18:48 +00:00
dependabot-preview[bot]
b38beb606f build(deps): bump regex-syntax from 0.6.12 to 0.6.13
Bumps [regex-syntax](https://github.com/rust-lang/regex) from 0.6.12 to 0.6.13.
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/regex-syntax-0.6.12...regex-syntax-0.6.13)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-09 20:55:02 +00:00
Samuel Ortiz
c2f6dfce88 vm-virtio: Fix VirtioDeviceType traits
The From and Display traits were not handling some of the enum
definitions. We no longer have a default case for Display so any future
misses will fail at build time.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-01-09 15:29:09 +01:00
dependabot-preview[bot]
9d775a140f build(deps): bump pnet from 0.23.0 to 0.25.0
Bumps [pnet](https://github.com/libpnet/libpnet) from 0.23.0 to 0.25.0.
- [Release notes](https://github.com/libpnet/libpnet/releases)
- [Commits](https://github.com/libpnet/libpnet/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-09 08:07:19 +00:00
dependabot-preview[bot]
70d80cc064 build(deps): bump pnet_sys from 0.23.0 to 0.23.1
Bumps [pnet_sys](https://github.com/libpnet/libpnet) from 0.23.0 to 0.23.1.
- [Release notes](https://github.com/libpnet/libpnet/releases)
- [Commits](https://github.com/libpnet/libpnet/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-09 07:33:18 +00:00
dependabot-preview[bot]
f493d434ba build(deps): bump vm-memory from 467eda6 to 218afdd
Bumps [vm-memory](https://github.com/rust-vmm/vm-memory) from `467eda6` to `218afdd`.
- [Release notes](https://github.com/rust-vmm/vm-memory/releases)
- [Commits](467eda62ae...218afddd4f)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-09 07:08:08 +00:00
dependabot-preview[bot]
2500d0aaac build(deps): bump micro_http from 2953365 to 0bfaf39
Bumps [micro_http](https://github.com/firecracker-microvm/firecracker) from `2953365` to `0bfaf39`.
- [Release notes](https://github.com/firecracker-microvm/firecracker/releases)
- [Commits](29533658e5...0bfaf39af1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-09 07:07:55 +00:00
dependabot-preview[bot]
279c4f91a3 build(deps): bump pnet_macros from 0.23.0 to 0.25.0
Bumps [pnet_macros](https://github.com/libpnet/libpnet) from 0.23.0 to 0.25.0.
- [Release notes](https://github.com/libpnet/libpnet/releases)
- [Commits](https://github.com/libpnet/libpnet/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-09 07:05:53 +00:00
Sebastien Boeuf
24cf15d2b2 vfio: Don't throw an error if a region cannot be found
Everytime we use VFIO with cloud-hypervisor, we get the following error:

ERROR:vfio/src/vfio_device.rs:440 -- Could not get region #8 info

But this is not an error per se, and should be considered as a simple
warning.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-09 08:05:35 +01:00
Sebastien Boeuf
c2ae380503 pci: Refine detection of BAR reprogramming
The current code was always considering 0xffffffff being written to the
register as a sign it was expecting to get the size, hence the BAR
reprogramming detection was stating this case was not a reprogramming
case.

Problem is, if the value 0xffffffff is directed at a 64bits BAR, this
might be the high or low part of a 64bits address which is meant to be
the new address of the BAR, which means we would miss the detection of
the BAR being reprogrammed here.

This commit improves the code using finer granularity checks in order to
detect this corner case correctly.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-09 08:05:35 +01:00
Sebastien Boeuf
07bad79fd7 pci: Fix detection of expansion ROM BAR reprogramming
The expansion ROM BAR reprogramming was being triggered for the wrong
reason and was causing the following error to be reported:

ERROR:pci/src/bus.rs:207 -- Failed moving device BAR: failed allocating
new 32 bits MMIO range

Following the PCI specification, here is what is defined:

Device independent configuration software can determine how much address
space the device requires by writing a value of all 1's to the address
portion of the register and then reading the value back.

This means we cannot expect 0xffffffff to be written, as the address
portion corresponds to the bits 31-11. That's why whenever the size of
this special BAR is being asked for, the value being written is
0xfffff800.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-09 08:05:35 +01:00
dependabot-preview[bot]
0d2e38db25 build(deps): bump cc from 1.0.49 to 1.0.50
Bumps [cc](https://github.com/alexcrichton/cc-rs) from 1.0.49 to 1.0.50.
- [Release notes](https://github.com/alexcrichton/cc-rs/releases)
- [Commits](https://github.com/alexcrichton/cc-rs/compare/1.0.49...1.0.50)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-08 17:00:07 +00:00
Sebastien Boeuf
38468d3d9b vm-virtio: Improve virtio-console input processing
The way the code is currently implemented, only by writing to STDIN a
user can trigger some input to reach the VM through virtio-console. But
in case, there were not enough virtio descriptors to process what was
retrieved from STDIN, the remaining bits would be transferred only if
STDIN was triggered again. The missing part is that when some
descriptors are made available from the guest, the virtio-console device
should try to send any possible remaining bits.

By triggering the function process_input_queue() whenever the guest
notifies the host that some new descriptors are ready for the receive
queue, this patch allows to fill the implementation void that was left.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-08 15:37:02 +01:00
Sebastien Boeuf
e4c3401a33 vm-virtio: Don't waste a descriptor if not filled
In case the virtio descriptor is pulled out of the Queue iterator, it
is important to fill it and tag it as used. This is already done from
the successful code path, but in case there's an error during the
filling, we should make sure to put the descriptor back in the list of
available descriptors. This way, when the error occurs, we don't loose
a descriptor, and it could be used later.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-08 15:37:02 +01:00
Sebastien Boeuf
7a3e6caca4 vm-virtio: Simplify virtio-console input processing
The existing code was a bit too complex and it was introducing a bug
when trying to paste long lines directly to the console. By simplifying
the code, and by doing proper usage of the drain() function, the bug is
fixed by this commit.

Here is the similar output one could have gotten from time to time, when
pasting important amounts of bytes:

ERROR:vm-virtio/src/console.rs:104 -- Failed to write slice:
InvalidGuestAddress(GuestAddress(1040617472))

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-08 15:37:02 +01:00
Sebastien Boeuf
ad223f2b50 fs: Use cache=none by default
The cache= option from the virtiofsd backend is meant to be used
depending on how the user wants to use the guest page cache. For most
use cases, we want to bypass the guest page cache to reduce the guest
memory footprint, which is why "cache=none" should be the default.

"cache=always" is the other option when the user wants to do something
specific by using the guest page cache, but should not be the default
anymore.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-08 09:27:07 +01:00
Sebastien Boeuf
84445aae93 vm-virtio: Implement multi-mapping for virtio-fs
The virtio-fs messages coming from the slave can contain multiple
mappings (up to 8) through one single request. By implementing such
feature, the virtio-fs implementation of cloud-hypervisor is optimal and
fully functional as it resolves a bug that was seen when running fio
testing without this patch.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-08 09:27:07 +01:00
Sebastien Boeuf
143471b739 fs: Update mount command to mount virtiofs filesystem
With latest kernel, virtio-fs mount command has been simplified, and
this needs to be applied everywhere in our tests and documentation.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-08 09:27:07 +01:00
Sebastien Boeuf
e1822cfdad vm-virtio: Implement VIRTIO_IOMMU_F_PROBE feature
By implementing this virtio feature, we let the virtio-iommu driver call
the device backend so that it can probe each device that gets attached.

Through this probing, the device provides a range of reserved memory
related to MSI. This is mandatory for x86 architecture as we want to
avoid the default MSI range assigned by the virtio-iommu driver if no
range is provided at all. The default range is 0x8000000-0x80FFFFF but
it only makes sense for ARM architectures.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-08 09:27:07 +01:00
Sebastien Boeuf
ae6f27277b acpi: Introduce VIOT to support latest virtio-iommu implementation
Because virtio-iommu is still evolving (as it's only partly upstream),
some pieces like the ACPI declaration of the different nodes and devices
attached to the virtual IOMMU are changing.

This patch introduces a new ACPI table called VIOT, standing as the high
level table overseeing the IORT table and associated subtables.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-08 09:27:07 +01:00
Sebastien Boeuf
4dbb33a1fa resources: Update kernel config
Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-08 09:27:07 +01:00
Sebastien Boeuf
c56af8daeb ci: Use a temporary kernel branch before we override the current one
In order to validate the newly created virtio-fs-virtio-iommu-5.5-rc1
branch is not breaking anything in our CI, we must try it as a temporary
branch before we can override virtio-fs-virtio-iommu.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2020-01-08 09:27:07 +01:00
dependabot-preview[bot]
d756844096 build(deps): bump getrandom from 0.1.13 to 0.1.14
Bumps [getrandom](https://github.com/rust-random/getrandom) from 0.1.13 to 0.1.14.
- [Release notes](https://github.com/rust-random/getrandom/releases)
- [Changelog](https://github.com/rust-random/getrandom/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-random/getrandom/compare/v0.1.13...v0.1.14)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-07 13:54:27 +00:00
dependabot-preview[bot]
9d3f656572 build(deps): bump cc from 1.0.48 to 1.0.49
Bumps [cc](https://github.com/alexcrichton/cc-rs) from 1.0.48 to 1.0.49.
- [Release notes](https://github.com/alexcrichton/cc-rs/releases)
- [Commits](https://github.com/alexcrichton/cc-rs/compare/1.0.48...1.0.49)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-07 08:14:13 +00:00
dependabot-preview[bot]
f59665e8aa build(deps): bump epoll from 4.0.1 to 4.1.0
Bumps [epoll](https://github.com/nathansizemore/epoll) from 4.0.1 to 4.1.0.
- [Release notes](https://github.com/nathansizemore/epoll/releases)
- [Commits](https://github.com/nathansizemore/epoll/compare/4.0.1...4.1.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-07 07:39:59 +00:00
dependabot-preview[bot]
9482ded365 build(deps): bump micro_http from aec25f9 to 2953365
Bumps [micro_http](https://github.com/firecracker-microvm/firecracker) from `aec25f9` to `2953365`.
- [Release notes](https://github.com/firecracker-microvm/firecracker/releases)
- [Commits](aec25f9e5a...29533658e5)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-07 07:08:03 +00:00
dependabot-preview[bot]
e4ea60dd96 build(deps): bump rand from 0.7.0 to 0.7.2
Bumps [rand](https://github.com/rust-random/rand) from 0.7.0 to 0.7.2.
- [Release notes](https://github.com/rust-random/rand/releases)
- [Changelog](https://github.com/rust-random/rand/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-random/rand/compare/0.7.0...0.7.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-06 08:32:44 +00:00
dependabot-preview[bot]
44dd056647 build(deps): bump remain from 0.1.5 to 0.2.0
Bumps [remain](https://github.com/dtolnay/remain) from 0.1.5 to 0.2.0.
- [Release notes](https://github.com/dtolnay/remain/releases)
- [Commits](https://github.com/dtolnay/remain/compare/0.1.5...0.2.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-06 08:26:02 +00:00
dependabot-preview[bot]
091c9d9f93 build(deps): bump pnet from 0.22.0 to 0.23.0
Bumps [pnet](https://github.com/libpnet/libpnet) from 0.22.0 to 0.23.0.
- [Release notes](https://github.com/libpnet/libpnet/releases)
- [Commits](https://github.com/libpnet/libpnet/commits/v0.23.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-06 07:53:56 +00:00
dependabot-preview[bot]
d8c3912a18 build(deps): bump micro_http from be97831 to aec25f9
Bumps [micro_http](https://github.com/firecracker-microvm/firecracker) from `be97831` to `aec25f9`.
- [Release notes](https://github.com/firecracker-microvm/firecracker/releases)
- [Commits](be978312ea...aec25f9e5a)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-06 07:26:24 +00:00
dependabot-preview[bot]
1a28f64d5d build(deps): bump atty from 0.2.13 to 0.2.14
Bumps [atty](https://github.com/softprops/atty) from 0.2.13 to 0.2.14.
- [Release notes](https://github.com/softprops/atty/releases)
- [Changelog](https://github.com/softprops/atty/blob/master/CHANGELOG.md)
- [Commits](https://github.com/softprops/atty/compare/0.2.13...0.2.14)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-06 07:26:09 +00:00
dependabot-preview[bot]
8f6cbf849a build(deps): bump vm-memory from 291dc2e to 467eda6
Bumps [vm-memory](https://github.com/rust-vmm/vm-memory) from `291dc2e` to `467eda6`.
- [Release notes](https://github.com/rust-vmm/vm-memory/releases)
- [Commits](291dc2e236...467eda62ae)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-06 07:25:53 +00:00
dependabot-preview[bot]
5a0d01ca13 build(deps): bump blake2b_simd from 0.5.9 to 0.5.10
Bumps [blake2b_simd](https://github.com/oconnor663/blake2_simd) from 0.5.9 to 0.5.10.
- [Release notes](https://github.com/oconnor663/blake2_simd/releases)
- [Commits](https://github.com/oconnor663/blake2_simd/compare/0.5.9...0.5.10)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-05 09:03:50 +00:00
dependabot-preview[bot]
38c0d328c2 build(deps): bump syn from 1.0.12 to 1.0.13
Bumps [syn](https://github.com/dtolnay/syn) from 1.0.12 to 1.0.13.
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/1.0.12...1.0.13)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-03 17:50:44 +00:00
Rob Bradford
134bcd84e6 tests: Use the workspace to unit test all the crates
Make all the crates members of the workspace so that "cargo test
--workspace" will find them all and test them with the features enabled
that we use.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-01-03 10:20:53 +01:00
Rob Bradford
b6f6772df4 vhost_rs: Fix compile failure in tests
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-01-03 10:20:53 +01:00
Rob Bradford
cf1983c70e vhost_rs: Mark some broken tests ignored
If we use the workspace based testing methodology then we start testing
some code inside vhost_rs that is broken.

See #576

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-01-03 10:20:53 +01:00
Rob Bradford
32a39f9b95 vm-virtio: Fix broken write_base_regs() unit test
The following commit broke this unit test:

"""
vmm: Convert virtio devices to Arc<Mutex<T>>

Migratable devices can be virtio or legacy devices.
In any case, they can potentially be tracked through one of the IO bus
as an Arc<Mutex<dyn BusDevice>>. In order for the DeviceManager to also
keep track of such devices as Migratable trait objects, they must be
shared as mutable atomic references, i.e. Arc<Mutex<T>>. That forces all
Migratable objects to be tracked as Arc<Mutex<dyn Migratable>>.

Virtio devices are typically migratable, and thus for them to be
referenced by the DeviceManager, they now should be built as
Arc<Mutex<VirtioDevice>>.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
"""

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-01-03 10:20:53 +01:00
Rob Bradford
b2589d4f3f vm-virtio, vmm, vfio: Store GuestMemoryMmap in an Arc<ArcSwap<T>>
This allows us to change the memory map that is being used by the
devices via an atomic swap (by replacing the map with another one). The
ArcSwap provides the mechanism for atomically swapping from to another
whilst still giving good read performace. It is inside an Arc so that we
can use a single ArcSwap for all users.

Not covered by this change is replacing the GuestMemoryMmap itself.

This change also removes some vertical whitespace from use blocks in the
files that this commit also changed. Vertical whitespace was being used
inconsistently and broke rustfmt's behaviour of ordering the imports as
it would only do it within the block.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-01-02 13:20:11 +00:00
dependabot-preview[bot]
5c0bb38a65 build(deps): bump vm-memory from 09c119f to 291dc2e
Bumps [vm-memory](https://github.com/rust-vmm/vm-memory) from `09c119f` to `291dc2e`.
- [Release notes](https://github.com/rust-vmm/vm-memory/releases)
- [Commits](09c119f742...291dc2e236)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-02 08:03:05 +00:00
dependabot-preview[bot]
b9971302be build(deps): bump syn from 1.0.11 to 1.0.12
Bumps [syn](https://github.com/dtolnay/syn) from 1.0.11 to 1.0.12.
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/1.0.11...1.0.12)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-02 08:02:34 +00:00
dependabot-preview[bot]
fda8a04ec2 build(deps): bump vm-memory from 3ef06be to 09c119f
Bumps [vm-memory](https://github.com/rust-vmm/vm-memory) from `3ef06be` to `09c119f`.
- [Release notes](https://github.com/rust-vmm/vm-memory/releases)
- [Commits](3ef06beaea...09c119f742)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-26 07:07:41 +00:00
dependabot-preview[bot]
34e35c9ae0 build(deps): bump anyhow from 1.0.25 to 1.0.26
Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.25 to 1.0.26.
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.25...1.0.26)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-24 07:00:35 +00:00
dependabot-preview[bot]
95352db1e5 build(deps): bump vm-memory from ec69de9 to 3ef06be
Bumps [vm-memory](https://github.com/rust-vmm/vm-memory) from `ec69de9` to `3ef06be`.
- [Release notes](https://github.com/rust-vmm/vm-memory/releases)
- [Commits](ec69de9c07...3ef06beaea)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-24 07:00:00 +00:00
Rob Bradford
a551398135 vmm: device_manager: Use MemoryManager to create KVM mapping
Use the newly exported funtionality to reduce the amount of duplicated
code.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-12-23 10:25:40 +00:00
Rob Bradford
962dec2913 vmm: memory_manager: Refactor KVM userspace mapping creation
This function will be useful for other parts of the VMM that also
estabilish their own mappings.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-12-23 10:25:40 +00:00
Rob Bradford
7df88793a0 vmm: device_manager: Get device range from MemoryManager
This removes the duplication of these values.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-12-23 10:25:40 +00:00
Rob Bradford
61cfe3e72d vmm: Obtain sequential KVM memory slot numbers from MemoryManager
This removes the need to handle a mutable integer and also centralises
the allocation of these slot numbers.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-12-23 10:25:40 +00:00
Rob Bradford
260cebb8cf vmm: Introduce MemoryManager
The memory manager is responsible for setting up the guest memory and in
the long term will also handle addition of guest memory.

In this commit move code for creating the backing memory and populating
the allocator into the new implementation trying to make as minimal
changes to other code as possible.

Follow on commits will further reduce some of the duplicated code.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-12-23 10:25:40 +00:00
dependabot-preview[bot]
bcfe546ea2 build(deps): bump vm-memory from bb29ec8 to ec69de9
Bumps [vm-memory](https://github.com/rust-vmm/vm-memory) from `bb29ec8` to `ec69de9`.
- [Release notes](https://github.com/rust-vmm/vm-memory/releases)
- [Commits](bb29ec8713...ec69de9c07)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-23 07:11:13 +00:00
Sebastien Boeuf
f668603694 ci: Fix flaky test_memory_mergeable_on test
Because we don't always reach the expected footprint improvements with
KSM, let's review the numbers. By reducing the expectations and
increasing the amount of pages to scan, this should stabilize the CI.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-12-20 13:44:22 +00:00
Rob Bradford
1e3fd2f6a5 tests: Remove many redundant clones
As found by updated clippy:

e.g:

error: redundant clone
    --> src/main.rs:1590:57
     |
1590 |             let mut osdisk_raw_base_path = workload_path.clone();
     |                                                         ^^^^^^^^ help: remove this
     |
     = note: `-D clippy::redundant-clone` implied by `-D warnings`

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-12-20 00:52:03 +01:00
Rob Bradford
d5682cd306 vmm: device_manager: Rewrite if chain using match
To reflect updated clippy rules:

error: `if` chain can be rewritten with `match`
    --> vmm/src/device_manager.rs:1508:25
     |
1508 | /                         if ret > 0 {
1509 | |                             debug!("MSI message successfully delivered");
1510 | |                         } else if ret == 0 {
1511 | |                             warn!("failed to deliver MSI message, blocked by guest");
1512 | |                         }
     | |_________________________^
     |
     = note: `-D clippy::comparison-chain` implied by `-D warnings`
     = help: Consider rewriting the `if` chain to use `cmp` and `match`.
     = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#comparison_chain

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-12-20 00:52:03 +01:00
Rob Bradford
21b88c3ea0 vmm: cpu: Rewrite if chain using match
Address updated clippy error:

error: `if` chain can be rewritten with `match`
   --> vmm/src/cpu.rs:668:9
    |
668 | /         if desired_vcpus > self.present_vcpus() {
669 | |             self.activate_vcpus(desired_vcpus, None)?;
670 | |         } else if desired_vcpus < self.present_vcpus() {
671 | |             self.mark_vcpus_for_removal(desired_vcpus)?;
672 | |         }
    | |_________^
    |
    = note: `-D clippy::comparison-chain` implied by `-D warnings`
    = help: Consider rewriting the `if` chain to use `cmp` and `match`.
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#comparison_chain

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-12-20 00:52:03 +01:00
Rob Bradford
e25a47b32c vmm: device_manager: Remove redundant clones
Address updated clippy errors:

error: redundant clone
   --> vmm/src/device_manager.rs:699:32
    |
699 |             .insert(acpi_device.clone(), 0x3c0, 0x4)
    |                                ^^^^^^^^ help: remove this
    |
    = note: `-D clippy::redundant-clone` implied by `-D warnings`
note: this value is dropped without further use
   --> vmm/src/device_manager.rs:699:21
    |
699 |             .insert(acpi_device.clone(), 0x3c0, 0x4)
    |                     ^^^^^^^^^^^
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#redundant_clone

error: redundant clone
   --> vmm/src/device_manager.rs:737:26
    |
737 |             .insert(i8042.clone(), 0x61, 0x4)
    |                          ^^^^^^^^ help: remove this
    |
note: this value is dropped without further use
   --> vmm/src/device_manager.rs:737:21
    |
737 |             .insert(i8042.clone(), 0x61, 0x4)
    |                     ^^^^^
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#redundant_clone

error: redundant clone
   --> vmm/src/device_manager.rs:754:29
    |
754 |                 .insert(cmos.clone(), 0x70, 0x2)
    |                             ^^^^^^^^ help: remove this
    |
note: this value is dropped without further use
   --> vmm/src/device_manager.rs:754:25
    |
754 |                 .insert(cmos.clone(), 0x70, 0x2)
    |                         ^^^^
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#redundant_clone

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-12-20 00:52:03 +01:00
Rob Bradford
9fb1c46cd1 vm-virtio: Remove unnecessary cloning
Found by updated clippy:

error: redundant clone
   --> vm-virtio/src/block.rs:182:5
    |
182 |     .to_owned();
    |     ^^^^^^^^^^^ help: remove this
    |
    = note: `-D clippy::redundant-clone` implied by `-D warnings`
note: this value is dropped without further use
   --> vm-virtio/src/block.rs:176:21
    |
176 |       let device_id = format!(
    |  _____________________^
177 | |         "{}{}{}",
178 | |         blk_metadata.st_dev(),
179 | |         blk_metadata.st_rdev(),
180 | |         blk_metadata.st_ino()
181 | |     )
182 | |     .to_owned();
    | |____^
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#redundant_clone

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-12-20 00:52:03 +01:00
Rob Bradford
deb3cbd501 arch_gen: Remove unused bootparam module
We use the version that's included in the linux-loader crate. This old
version was also generating build errors after updating to the new
clippy:

e.g.:

error: unsafe function's docs miss `# Safety` section
  --> arch_gen/src/x86/bootparam.rs:23:5
   |
23 | /     pub unsafe fn as_ptr(&self) -> *const T {
24 | |         ::std::mem::transmute(self)
25 | |     }
   | |_____^
   |
   = note: `-D clippy::missing-safety-doc` implied by `-D warnings`
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#missing_safety_doc

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-12-20 00:52:03 +01:00
Sebastien Boeuf
9701fde209 vm-virtio: Add connection handshake to vsock
This patch has been cherry-picked from the Firecracker tree. The
reference commit is 1db04ccc69862f30b7814f30024d112d1b86b80e.

Changed the host-initiated vsock connection protocol to include a
trivial handshake.

The new protocol looks like this:
- [host] CONNECT <port><LF>
- [guest/success] OK <assigned_host_port><LF>

On connection failure, the host host connection is reset without any
accompanying message, as before.

This allows host software to more easily detect connection failures, for
instance when attempting to connect to a guest server that may have not
yet started listening for client connections.

Signed-off-by: Dan Horobeanu <dhr@amazon.com>
Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-12-19 09:48:17 +01:00
Yang Zhong
5c4e1726f8 ci: test the vhost-user-blk readonly function
This patch is to check if block device is readonly
when backend set readonly=true.

The lsblk command can show the RO value in the guest.

Signed-off-by: Yang Zhong <yang.zhong@intel.com>
2019-12-18 09:45:11 +01:00
Yang Zhong
cee01edb97 vhost-user-blk backend: add readonly support
The current backend only support rw, and we also need
add readonly support.

The new command:
vhost_user_blk \
  --backend "image=/home/test.img, \
            sock=/home/path/vhost.socket, \
            readonly=true"

Signed-off-by: Yang Zhong <yang.zhong@intel.com>
2019-12-18 09:45:11 +01:00
Rob Bradford
91549ddd71 tests: Add integration test for CPU unplug
Enhance the CPU hotplug test to also unplug the CPUs.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-12-18 08:23:53 +00:00
Rob Bradford
a6878accd5 vmm: cpu: Implement CPU removal
When the running OS has been told that a CPU should be removed it will
shutdown the CPU and then signal to the hypervisor via the "_EJ0" method
on the device that ultimately writes into an I/O port than the vCPU
should be shutdown. Upon notification the hypervisor signals to the
individual thread that it should shutdown and waits for that thread to
end.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-12-18 08:23:53 +00:00
Rob Bradford
7b3fc72aea vmm: cpu: Notify guest OS that it should offline vCPUs
Allow the resizing of the number of vCPUs to less than the current
active vCPUs. This does not currently remove them from the system but
the kernel will take them offline.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-12-18 08:23:53 +00:00
Rob Bradford
7e81b0ded7 vmm: cpu: Create vCPU state for all possible vCPUs
This will make it more straightforward when we attempt to remove vCPUs.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-12-18 08:23:53 +00:00
Rob Bradford
156ea392a2 vmm: cpu: Only do ACPI notify on newly added vCPUs
When we add a vCPU set an "inserting" boolean that is exposed as an ACPI
field that will be checked for and reset when the ACPI GED notification
for CPU devices happens.

This change is a precursor for CPU unplug.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-12-16 23:57:14 +01:00
Rob Bradford
e8313e3e69 vmm: acpi: Refactor ACPI CPU notification
Continue to notify on all vCPUs but instead separate the notification
functionality into two methods, CSCN that walks through all the CPUs
and CTFY which notifies based on the numerical CPU id. This is an
interim step towards only notifying on changed CPUs and ultimately CPU
removal.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-12-16 23:57:14 +01:00
Sebastien Boeuf
43d2e09e1f ci: Add unit tests to compare CLI and OpenAPI
The goal here is to ensure that CLI and OpenAPI both behave as closely
as possible, and also that they behave as expected.

Leveraging the reorganization of the code, we can now compare two
VmConfig structures generated from one CLI entry on one side, and from
an OpenAPI entry (JSON payload) on the other side.

Fixes #535

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-12-16 16:48:59 +01:00
Sebastien Boeuf
d1390906c8 vmm: config: Derive Debug and PartialEq for configuration structures
In anticipation for the writing of unit tests comparing two VmConfig
structures, this commit derives the PartialEq trait for VmConfig and
all embedded structures.

This patch also derives the Debug trait for the same set of structures
so that we can print them to facilitate debugging.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-12-16 16:48:59 +01:00
Sebastien Boeuf
93f5f6ed45 vmm: config: Provide a default empty command line through OpenAPI
The OpenAPI should not have to provide a command line since the CLI
considers the command line as an empty string if nothing is provided.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-12-16 16:48:59 +01:00
Sebastien Boeuf
43bd0e53c4 main: Move VmParams creation into a dedicated function
This brings more modularity to the code, which will be helpful when we
will later test the CLI and OpenAPI generate the same VmConfig output.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-12-16 16:48:59 +01:00
Sebastien Boeuf
17a167dbb6 main: Move default values preparation into a dedicated function
This brings more modularity to the code, which will be helpful when we
will later test the CLI and OpenAPI generate the same VmConfig output.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-12-16 16:48:59 +01:00
Sebastien Boeuf
9c3a7ddcc9 main: Move clap::App creation into a dedicated function
This brings more modularity to the code, which will be helpful when we
will later test the CLI and OpenAPI generate the same VmConfig output.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-12-16 16:48:59 +01:00
dependabot-preview[bot]
11750efb78 build(deps): bump log from 0.4.8 to 0.4.10
Bumps [log](https://github.com/rust-lang/log) from 0.4.8 to 0.4.10.
- [Release notes](https://github.com/rust-lang/log/releases)
- [Changelog](https://github.com/rust-lang/log/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/log/compare/0.4.8...0.4.10)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-16 14:46:05 +00:00
dependabot-preview[bot]
dea5776fb9 build(deps): bump serde from 1.0.103 to 1.0.104
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.103 to 1.0.104.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.103...v1.0.104)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-16 08:15:47 +00:00
dependabot-preview[bot]
7909f7a8c9 build(deps): bump serde_derive from 1.0.103 to 1.0.104
Bumps [serde_derive](https://github.com/serde-rs/serde) from 1.0.103 to 1.0.104.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.103...v1.0.104)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-16 07:05:03 +00:00
Rob Bradford
23150f8647 tests: Throttle tests based on disk space
On our CI the /tmp filesystem is mounted as tmpfs and this is the
location where the test disk images are located. When the CI worker
nodes have less memory and fewer CPUs the tmpfs fills up as the tests
run in parallel.

Introduce a mechanism to reduce the parallelism of the tests based on
starvation of the tmpfs disk availability.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-12-13 16:42:59 +01:00
Rob Bradford
d59fe4eee7 ci: Add some time measurements to CI scripts
Add some use of "time" to measure the wallclock time for various items
within our CI scripts.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-12-13 13:42:52 +00:00
Samuel Ortiz
fa0fdc6500 cargo: Update Cargo.lock for the 0.4.0 release
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-12-13 12:21:13 +01:00
Samuel Ortiz
cec884e863 release: v0.4.0
Expand release notes and bump Cargo.toml.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-12-13 11:42:59 +01:00
Rob Bradford
6444e29b04 docs: Add CPU hot plug documentation
Add details of how to add vCPUs to the running VM.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-12-13 10:34:41 +00:00
dependabot-preview[bot]
a002093a55 build(deps): bump anyhow from 1.0.23 to 1.0.25
Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.23 to 1.0.25.
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.23...1.0.25)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-13 08:29:30 +01:00
dependabot-preview[bot]
43f0478fa8 build(deps): bump thiserror from 1.0.6 to 1.0.9
Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.6 to 1.0.9.
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/1.0.6...1.0.9)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-13 06:38:08 +00:00
Samuel Ortiz
664431ff14 vsock: vhost_user: vfio: Fix potential host memory overflow
The vsock packets that we're building are resolving guest addresses to
host ones and use the latter as raw pointers.
If the corresponding guest mapped buffer spans across several regions in
the guest, they will do so in the host as well. Since we have no
guarantees that host regions are contiguous, it may lead the VMM into
trying to access memory outside of its memory space.

For now we fix that by ensuring that the guest buffers do not span
across several regions. If they do, we error out.
Ideally, we should enhance the rust-vmm memory model to support safe
acces across host regions.

Fixes CVE-2019-18960

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-12-12 22:15:50 +01:00
Rob Bradford
1e97d1413e README: Update for newer distribution support
Highlight that we support Ubuntu Bionic and Eoan as well as Clear Linux
and update the Clear Linux versions referenced.

Also update the firmware URL to point to the latest version.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-12-12 13:36:28 +01:00
Samuel Ortiz
e8e21aeb7e README: Update the --cpus command line examples
We recommend to specify the boot cpus, even though the old syntax is still
supported.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-12-12 11:55:37 +00:00
Sebastien Boeuf
a40a70ad86 ci: Rely on latest virtiofsd version
Virtio-fs maintainers recently updated the virtiofsd daemon through
their official branch on Gitlab. It includes fixes that were needed for
cloud-hypervisor to work correctly with it.

Jenkinsfile needs to be updated since the virtiofsd build requires both
libseccomp and libcap-ng to be present on the system.

One thing to notice, because the latest branch introduced a change
regarding libfuse behavior, the counterpart patch has been added to the
custom kernel branch "virtio-fs-virtio-iommu".

Fixes #536

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-12-12 11:09:36 +01:00
Samuel Ortiz
f0b7412495 vmm: device_manager: Add all virtio devices to the migratable list
We want to track all migratable devices through the DeviceManager.

Fixes: #341

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-12-12 08:50:36 +01:00
Samuel Ortiz
37557c8b35 vmm: vm: Implement the Pausable trait
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-12-12 08:50:36 +01:00
Samuel Ortiz
9756fc2dd0 vmm: cpu_manager: Implement the Pausable trait
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-12-12 08:50:36 +01:00
Samuel Ortiz
35dd1523c9 vmm: device_manager: Implement the Pausable trait
Since the Snapshotable placeholder and Migratable traits are provided as
well, the DeviceManager object and all its objects are now Migratable.

All Migratable devices are tracked as Arc<Mutex<dyn Migratable>>
references.

Keeping track of all migratable devices allows for implementing the
Migratable trait for the DeviceManager structure, making the whole
device model potentially migratable.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-12-12 08:50:36 +01:00
Samuel Ortiz
a122da4bef vm-virtio: vhost: Implement the Pausable trait for all vhost-user devices
Due to the amount of code currently duplicated across vhost-user devices,
the stats for this commit is on the large side but it's mostly more
duplicated code, unfortunately.

Migratable and Snapshotable placeholder implementations are provided as
well, making all vhost-user devices Migratable.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-12-12 08:50:36 +01:00
Samuel Ortiz
dae0b2ef72 vm-virtio: Implement the Pausable trait for all virtio devices
Due to the amount of code currently duplicated across virtio devices,
the stats for this commit is on the large side but it's mostly more
duplicated code, unfortunately.

Migratable and Snapshotable placeholder implementations are provided as
well, making all virtio devices Migratable.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-12-12 08:50:36 +01:00
Samuel Ortiz
35d7721683 vmm: Convert virtio devices to Arc<Mutex<T>>
Migratable devices can be virtio or legacy devices.
In any case, they can potentially be tracked through one of the IO bus
as an Arc<Mutex<dyn BusDevice>>. In order for the DeviceManager to also
keep track of such devices as Migratable trait objects, they must be
shared as mutable atomic references, i.e. Arc<Mutex<T>>. That forces all
Migratable objects to be tracked as Arc<Mutex<dyn Migratable>>.

Virtio devices are typically migratable, and thus for them to be
referenced by the DeviceManager, they now should be built as
Arc<Mutex<VirtioDevice>>.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-12-12 08:50:36 +01:00
Samuel Ortiz
5450de0f5e cargo: Do not run fmt on anyhow's build code
The anyhow crate generates some incorrectly indented code from its
build.rs code. We don't want to run cargo fmt on this code.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-12-12 08:50:36 +01:00
Samuel Ortiz
0361df4ae0 vm-device: Initial Migratable trait
The Migratable trait groups all expected capabilities of devices and
components that can be migrated.

For a component to be migrated, it must be able to pause and resume.
Once paused, it should be able to provide a snapshot of itself. It
should also be able to restore itself from a snaphot.

As a consequence, the Migratable trait will be split between the
Pausable and the Snapshotable traits. This commit only adds the
Pausable one.

All migratable devices will be tracked from the DeviceManager.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-12-12 08:50:36 +01:00
Rob Bradford
36daf9c0b0 ci: Skip testing RFC or WIP PRs
To alleviate load on the CI only test PRs that are not marked as RFC or
WIP.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-12-11 18:22:12 +01:00
Sebastien Boeuf
64c5e3d8cb vmm: api: Adjust FsConfig for OpenAPI
The FsConfig structure has been recently adjusted so that the default
value matches between OpenAPI and CLI. Unfortunately, with the current
description, there is no way from the OpenAPI to describe a cache_size
value "None", so that DAX does not get enabled. Usually, using a Rust
"Option" works because the default value is None. But in this case, the
default value is Some(8G), which means we cannot describe a None.

This commit tackles the problem, introducing an explicit parameter
"dax", and leaving "cache_size" as a simple u64 integer.

This way, the default value is dax=true and cache_size=8G, but it lets
the opportunity to disable DAX entirely with dax=false, which will
simply ignore the cache_size value.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-12-11 15:50:24 +00:00
Sebastien Boeuf
4bfd51cc42 vmm: api: Match VhostUserBlkConfig defaults between CLI and HTTP API
In order to let the CLI and the HTTP API behave the same regarding the
VhostUserBlkConfig structure, this patch defines some default values
for num_queues, queue_size and wce.

num_queues is 1, queue_size is 128 and wce is true.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-12-11 15:50:24 +00:00
Sebastien Boeuf
1c2587f8cb vmm: api: Match VhostUserNetConfig defaults between CLI and HTTP API
In order to let the CLI and the HTTP API behave the same regarding the
VhostUserNetConfig structure, this patch defines some default values
for num_queues, queue_size and mac.

num_queues is 2 since that's a pair of TX/RX queues, queue_size is 256
and mac is a randomly generated value.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-12-11 15:50:24 +00:00
Sebastien Boeuf
5e0bbf9c3b vmm: Don't factorize vhost-user configurations
We want to set different default configurations for vhost-user-net and
vhost-user-blk, which is the reason why the common part corresponding to
the number of queues and the queue size cannot be embedded.

This prepares for the following commit, matching API and CLI behaviors.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-12-11 15:50:24 +00:00
Sebastien Boeuf
793327cff8 vmm: api: Make ConsoleConfig default match between CLI and HTTP API
A simple patch making sure the field "file" is provisioned with the same
default value through CLI and OpenAPI.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-12-11 15:50:24 +00:00
Sebastien Boeuf
cc08c44cb9 vmm: api: Make MemoryConfig default match between CLI and HTTP API
Just making sure we have a serde default for the field "file" since it
is not a required field in the OpenAPI definition.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-12-11 15:50:24 +00:00
Sebastien Boeuf
5a72225856 vmm: api: Update CpuConfig name to match the internal name
All structures match between the OpenAPI definition and the internal
configuration code, that's why CpuConfig is being renamed into
CpusConfig.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-12-11 15:50:24 +00:00
Sebastien Boeuf
f7c215d92d cli: Fix default CPU argument
By default, and in order to avoid falling into the legacy CLI usage, the
CPU argument should at least include "boot=" to define the number of
CPUs.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-12-11 15:50:24 +00:00
Rob Bradford
59ae01ff71 ci: Cancel older builders on Jenkins
When a new build is triggered cancel any older builds to conserve
resources.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-12-11 15:50:01 +00:00
Rob Bradford
c61104df47 vmm: Port to latest vmm-sys-util
The signal handling for vCPU signals has changed in the latest release
so switch to the new API.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-12-11 14:11:11 +00:00
Sebastien Boeuf
4c92f89f0f ci: Add OpenAPI validation
We need to validate that OpenAPI YAML definition is not broken by each
and every pull request. The easiest way is to rely on the Docker image
provided by OpenAPITools, as it allows us to validate the definition
with one simple command.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-12-11 12:53:31 +00:00
Sebastien Boeuf
93bd88e681 ci: Simplify travis.yml
Now that Cargo tests are being run on the worker node directly, we can
leave the bare minimum in Travis, which is taking care of the release
deployment.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-12-11 12:53:31 +00:00
Sebastien Boeuf
d42ef186a1 ci: Offload cargo tests to the worker node VM
Because the resources on the amount of worker nodes we can have access
to through Travis is limited, we offload the burden of running all tests
related to Cargo inside the Azure VM directly.

This will have the positive effect of stopping the build very early in
case something goes wrong during the Cargo testing.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-12-11 12:53:31 +00:00
Sebastien Boeuf
ee528ae808 vmm: api: Make FsConfig defaults match between CLI and HTTP API
In order to let the CLI and the HTTP API behave the same regarding the
FsConfig structure, this patch defines some default values for
num_queues, queue_size and the cache_size.

num_queues is set to 1, queue_size is set to 1024, and cache_size is set
to Some(8G) which means that DAX is enabled by default with a shared
region of 8GiB.

Fixes #508

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-12-09 23:42:23 -08:00
Sebastien Boeuf
befd342da4 vmm: api: Make NetConfig defaults match between CLI and HTTP API
In order to let the CLI and the HTTP API behave the same regarding the
NetConfig structure, this patch defines some default values for tap, ip,
mask, mac and iommu.

tap is None, ip is 192.168.249.1, mask is 255.255.255.0, mac is a
randomly generated value, and iommu is false.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-12-09 23:19:24 -08:00
Sebastien Boeuf
eff78f746a resources: Prevent kernel config interactive shell from showing up
Following the recent addition of CONFIG_EXPERT=y, the kernel config now
asks for several options through interactive shell. This is not
convenient when locally running the integration tests script as the
shell asks for input.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-12-10 06:24:11 +01:00
Jose Carlos Venegas Munoz
99e608c240 openapi: Fix schema
Fix openapi schema to be a valid yaml.

Signed-off-by: Jose Carlos Venegas Munoz <jose.carlos.venegas.munoz@intel.com>
2019-12-09 14:30:15 -08:00
Rob Bradford
f994665610 vmm: Reduce the minimum IRQ constant
Now that the GED device does not use a hardcoded IRQ number the starting
IRQ number can be restored (needed for the hardcoded serial port IRQ.)

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-12-09 16:58:00 +00:00
Rob Bradford
ba59c62044 vmm, devices: Remove hardcoded IRQ number for GED device
Remove the previously hardcoded IRQ number used for the GED device.
Instead allocate the IRQ using the allocator and use that value in the
definition in the ACPI device.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-12-09 16:58:00 +00:00
Qiu Wenbo
ce1bd9c7ff resources: turn on CONFIG_ACPI_REDUCED_HARDWARE_ONLY
We need turn on CONFIG_ACPI_REDUCED_HARDWARE_ONLY to support cpu hotplug
feature.

Signed-off-by: Qiu Wenbo <qiuwenbo@phytium.com.cn>
2019-12-09 10:49:17 +01:00
dependabot-preview[bot]
0374c3dc71 build(deps): bump ssh2 from 0.5.0 to 0.6.0
Bumps [ssh2](https://github.com/alexcrichton/ssh2-rs) from 0.5.0 to 0.6.0.
- [Release notes](https://github.com/alexcrichton/ssh2-rs/releases)
- [Commits](https://github.com/alexcrichton/ssh2-rs/compare/0.5.0...0.6.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-09 05:53:30 +00:00
Sebastien Boeuf
aa94e9b8f3 Revert "vmm: api: Modify FsConfig to be OpenAPI friendly"
This reverts commit defc5dcd9c.
2019-12-06 18:08:10 +00:00
Rob Bradford
9b1ba14f2d vmm: Delegate device related ACPI DSDT table work to DeviceManager
Move the code for handling the creation of the DSDT entries for devices
into the DeviceManager.

This will make it easier to handle device hotplug and also in the future
remove some hardcoded ACPI constants.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-12-06 17:44:00 +00:00
Rob Bradford
60e6609011 vmm: Delegate CPU related ACPI tables to CpuManager
Move the code for generating the MADT (APIC) table and the DSDT
generation for CPU related functionality into the CpuManager.

There is no functional change just code rearrangement.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-12-06 17:44:00 +00:00
Sebastien Boeuf
defc5dcd9c vmm: api: Modify FsConfig to be OpenAPI friendly
When consumer of the HTTP API try to interact with cloud-hypervisor,
they have to provide the equivalent of the config structure related to
each component they need. Problem is, the Rust enum type "Option" cannot
be obtained from the OpenAPI YAML definition.

This patch intends to fix this inconsistency between what is possible
through the CLI and what's possible through the HTTP API by using simple
types bool and int64 instead of Option<u64>.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-12-06 06:38:48 -08:00
Sebastien Boeuf
66e00ce710 ci: Extend VFIO integration test
In order to validate that multiple devices can be passed through and
they are still fully functional, this patch extends the existing VFIO
test to pass a second virtio-net device, and verifies that both
interfaces are functional by ssh'ing into each network interface.

Fixes #503

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-12-06 12:53:09 +01:00
Rob Bradford
59d01712ad vmm: Remove kernel based IOAPIC handling from the device manager
Previously the device setup code assumed that if no IOAPIC was passed in
then the device should be added to the kernel irqchip. As an earlier
change meant that there was always a userspace IOAPIC this kernel based
code can be removed.

The accessor still returns an Option type to leave scope for
implementing a situation without an IOAPIC (no serial or GED device).
This change does not add support no-IOAPIC mode as the original code did
not either.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-12-06 12:34:06 +01:00
Rob Bradford
afea6a10a2 vmm: Stop initialising kernel based IOAPIC/PIC
Now that we require the modern capabilities we can stop creating a
kernel base irqchip.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-12-06 12:34:06 +01:00
Rob Bradford
9b1cb9621f vmm: Remove pin based interrupt setup for virtio devices
With MSI now required remove pin based interrupt support from all the
virtio PCI device setup.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-12-06 12:34:06 +01:00
Rob Bradford
72fb687e3f vmm: Check for required capabilities
We now require CAP_SIGNAL_MSI, CAP_TSC_DEADLINE_TIMER and
CAP_SPLIT_IRQCHIP.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-12-06 12:34:06 +01:00
dependabot-preview[bot]
904b1ea599 build(deps): bump unicode-width from 0.1.6 to 0.1.7
Bumps [unicode-width](https://github.com/unicode-rs/unicode-width) from 0.1.6 to 0.1.7.
- [Release notes](https://github.com/unicode-rs/unicode-width/releases)
- [Commits](https://github.com/unicode-rs/unicode-width/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-05 23:19:28 +00:00
Rob Bradford
fcf92d86b5 tests: Add rebooting to the CPU hotplug test
Check that the added vCPUs are still there after a reboot.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-12-05 16:39:19 +00:00
Rob Bradford
f98b16f308 vmm: Update the configuration to preserve hot-plug CPUs after reboot
Update the configuration after a resize to ensure that after a reboot
the added vCPUs are preserved.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-12-05 16:39:19 +00:00
Rob Bradford
1722708612 vmm: Switch to storing VmConfig inside an Arc<Mutex<>>
This permits the runtime reconfiguration of the VM.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-12-05 16:39:19 +00:00
Rob Bradford
c063bb8d30 vmm: acpi: Make GED interrupt edge triggered
This was causing issues when the kernel was trying to reset the
interrupt and making the reboot fail.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-12-05 16:39:19 +00:00
Qiu Wenbo
e1af17d93a vmm: Restore tty to canonical mode when SIGTERM or SIGINT received
The tty mode remains raw mode when cloud-hypervisor is terminted by
SIGTERM or SIGINT. The terminal is unusable due to echoing is
disabled which is really annoying.

Signed-off-by: Qiu Wenbo <qiuwenbo@phytium.com.cn>
2019-12-05 01:29:26 -08:00
dependabot-preview[bot]
44d026bfb1 build(deps): bump serde_json from 1.0.43 to 1.0.44
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.43 to 1.0.44.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.43...v1.0.44)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-04 22:53:40 +00:00
dependabot-preview[bot]
a1285ea57d build(deps): bump cc from 1.0.47 to 1.0.48
Bumps [cc](https://github.com/alexcrichton/cc-rs) from 1.0.47 to 1.0.48.
- [Release notes](https://github.com/alexcrichton/cc-rs/releases)
- [Commits](https://github.com/alexcrichton/cc-rs/compare/1.0.47...1.0.48)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-04 16:57:32 +00:00
Sebastien Boeuf
23929f41a7 vfio: Don't override MSI Enable bit through VFIO ioctl
This commit ensures device's PCI config space is being written after
MSI/MSI-X interrupts have been enabled/disabled. In case of MSI, when
the interrupts are enabled through VFIO (using VFIO_DEVICE_SET_IRQS),
the MSI Enable bit in the MSI capability structure found in the PCI
config space is disabled by default. That's why when the guest is
enabling this bit, we first need to enable the MSI interrupts with
VFIO through VFIO_DEVICE_SET_IRQS ioctl, and only after we can write
to the device region to update the MSI Enable bit.

Fixes #460

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-12-04 14:47:06 +00:00
Wu Zongyong
1dfd60b609 vfio: use correct flags to disable interrupts
The comments of vfio kernel module said that individual subindex
interrupts can be disabled using the -1 value for DATA_EVENTFD or
the index can be disabled as a whole with:
    flags = (DATA_NONE|ACTION_TRIGGER), count = 0.

Signed-off-by: Wu Zongyong <wuzongyong@linux.alibaba.com>
CC: Liu Jiang <gerry@linux.alibaba.com>
2019-12-04 14:47:06 +00:00
Qiu Wenbo
5208ff86c8 vmm: Detect and handle AMD SME (Secure Memory Encryption)
Some physical address bits may become reserved in page table when SME
is enabled on AMD platform. Guest will trigger a reserved bit
violation page fault in this case due to write these reserved bits to 1
in page table. We need reduce the reserved bits to get the right
physical address range.

Signed-off-by: Qiu Wenbo <qiuwenbo@phytium.com.cn>
2019-12-04 14:46:44 +00:00
dependabot-preview[bot]
dcfd6ffd4b build(deps): bump serde_json from 1.0.42 to 1.0.43
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.42 to 1.0.43.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.42...v1.0.43)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-04 11:31:05 +00:00
Sebastien Boeuf
08258d5dad vfio: pci: Allow multiple devices to be passed through
The KVM_SET_GSI_ROUTING ioctl is very simple, it overrides the previous
routes configuration with the new ones being applied. This means the
caller, in this case cloud-hypervisor, needs to maintain the list of all
interrupts which needs to be active at all times. This allows to
correctly support multiple devices to be passed through the VM and being
functional at the same time.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-12-04 08:48:17 +01:00
Sebastien Boeuf
4115fa8a87 vfio: pci: Update irqfd registration
In order to improve the existing VFIO code, this patch registers the
eventfds used to trigger KVM interrupts only when the interrupts are
enabled, and unregisters them when interrupts are disabled.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-12-04 08:48:17 +01:00
Sebastien Boeuf
1379abb94b pci: msi: Fix MSG_CTL update through 32 bits write
If the MSG_CTL is being written from a 32 bits write access, the offset
won't be 0x2, but 0x0 instead. That's simply because 32 bits access have
to be aligned on each double word.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-12-04 08:48:17 +01:00
Sebastien Boeuf
c81e808e98 docs: Update instructions regarding virtiofsd
The documentation was out of date since the URL to download virtiofsd
binary was not valid anymore. Instead, the updated documentation now
describe how to build virtiofsd from source.

Fixes #491

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-12-03 12:16:43 +00:00
Rob Bradford
17badfbff5 vmm: cpu: Call vcpu configure() on the vCPU thread
The function that programs the vCPUs is expected to be run from within
each vCPU thread.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-12-03 03:22:15 -08:00
Rob Bradford
13503061e6 api: Fix OpenAPI specification entries
Some renames from "cpu_count" were missing.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-12-03 03:28:06 +01:00
Rob Bradford
e1ff142392 tests: Remove MSI only test from test_serial_off
Now we have a GED device we are not MSI only when the serial port is
turned off.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-12-02 13:49:04 +00:00
Rob Bradford
e0830640b7 tests: Add integration test for hotplugging vCPUs
Resize the VM to increase the number of vCPUs.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-12-02 13:49:04 +00:00
Rob Bradford
66a31c19e8 vmm: acpi: Upon GED interrupt notify on all vCPUs
Call the "CTFY" method that will itself call Notify() on the CPU objects
in the ACPI namespace.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-12-02 13:49:04 +00:00
Rob Bradford
48bf141364 vmm: Trigger a hotplug device notification when resizing
When adjusting the number of vCPUs generate a hotplug notification.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-12-02 13:49:04 +00:00
Rob Bradford
b629727901 vmm: acpi: Add a CTFY method to notify on all CPU objects
This method calls Notify() on all the vCPU objects in the ACPI
namespace.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-12-02 13:49:04 +00:00
Rob Bradford
ae9359c859 vmm: acpi: Create the CPU entries in the DSDT for all vCPUs
CPU entries need to be created for every potential vCPU in the system.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-12-02 13:49:04 +00:00
Rob Bradford
791ca3388f vmm: device_manager: Add ability to notify via GED device
Add ability to notify via the GED device that there is some new hotplug
activity. This will be used by the CpuManager (and later DeviceManager
itself) to notify of new hotplug activity.

Currently it has a hardcoded IRQ of 5 as the ACPI tables also need to
refer to this IRQ and the IRQ allocation does not permit the allocation
of specific IRQs.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-12-02 13:49:04 +00:00
Rob Bradford
623755cc70 devices: Add ACPI GED device
This device provides the ability to notify the kernel via interrupt that
there is some new hotplug activity. The type is determined by reading
from the I/O port.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-12-02 13:49:04 +00:00
Rob Bradford
7ad68d499a vmm: device_manager: Allocate I/O port for ACPI shutdown device
The refactoring in ce1765c8af dropped the
code to allocate the I/O port.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-12-02 13:49:04 +00:00
Rob Bradford
86339b4cb4 vmm: Add HTTP API to resize the VM
Currently only increasing the number of vCPUs is supported but in the
future it will be extended.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-12-02 13:49:04 +00:00
Rob Bradford
e7d4eae527 vmm: cpu: Add support for starting more vCPU threads
Add support for starting vCPU threads after the initial boot ones.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-12-02 13:49:04 +00:00
Rob Bradford
0ef999978c vmm: cpu: Support only partially configuring the vCPU
When configuring a processor after boot as a hotplug CPU we only
configure a subset of the CPU state. In particular we should not
configure the FPU, segment registers (or reconfigure the paging which is
a side-effect of that) nor the main registers. Achieve this by making
the function take an Option type for the start address.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-12-02 13:49:04 +00:00
Rob Bradford
c8b3041e62 vmm: openapi: Update OpenAPI for CpuConfig struct
This struct has changed in order to support differentiating between boot
and max vCPUs.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-12-02 13:49:04 +00:00
Rob Bradford
b6801e355e vmm: cpu: Refactor vCPU thread starting
Refactor the vCPU thread starting so that there is the possibility to
bring on extra vCPU threads.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-12-02 13:49:04 +00:00
Rob Bradford
66d5163ee7 vmm: cpu: Encapsulate vCPU state into its own struct
Currently this just holds the thread handle but will be enlarged to
encompass details such as whether the vCPU is currently being inserted
or ejected.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-12-02 13:49:04 +00:00
Rob Bradford
ea19bb026f tests: Add a test to check that the boot vs max cpus work
The easiest way to detect if the kernel is willing to accept hotplug
vCPUs is to check the dmesg output.

Switch the test to bionic as the Clear Cloud image lacks "dmesg."

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-12-02 13:49:04 +00:00
Rob Bradford
1bbe48b24c vmm: acpi: Mark non-boot vCPUs as disabled in the MADT table
The MADT table contains the details of all the potential vCPUs and
whether they are present at boot (as indicated by the flags field.)

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-12-02 13:49:04 +00:00
Rob Bradford
4bc8635c59 tests: Use new "--cpus" syntax for integration tests
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-12-02 13:49:04 +00:00
Rob Bradford
82bc07cce4 vmm: Add boot and max vCPU handling to command line parser
Also retain support (with a warning for the old behaviour.)

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-12-02 13:49:04 +00:00
Rob Bradford
7543e00a07 vmm: Use new CpuManager accessor to get boot vCPUs
When initialising the ACPI tables and configuring the VM use the new
accessor on the CpuManager to get the number of boot vCPUs.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-12-02 13:49:04 +00:00
Rob Bradford
df0907845a vmm: cpu: Introduce concept of maximum vs boot vCPUs in CpuManager
For now the max vCPUs is the same as the boot vCPUs.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-12-02 13:49:04 +00:00
Sergio Lopez
669d9a8ae8 vhost_user_backend: fix memory region offsetting
The way in which offsets are currently use in memory regions is
derived from QEMU's contrib/libvhost-user, but while this one works
mainly by translating vmm va's to local va's, vm-memory expects us to
use proper guest addresses and thus, define memory regions that
actually match the guest's memory disposition.

With this change, we create the memory regions with the proper length
and offsets, extend AddrMapping to store the guest physical address,
and use the latter instead of offset in vmm_va_to_gpa().

Signed-off-by: Sergio Lopez <slp@redhat.com>
2019-12-02 01:57:25 -08:00
dependabot-preview[bot]
d378da64ee build(deps): bump vcpkg from 0.2.7 to 0.2.8
Bumps [vcpkg](https://github.com/mcgoo/vcpkg-rs) from 0.2.7 to 0.2.8.
- [Release notes](https://github.com/mcgoo/vcpkg-rs/releases)
- [Changelog](https://github.com/mcgoo/vcpkg-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mcgoo/vcpkg-rs/compare/vcpkg-rs-0.2.7...vcpkg-rs-0.2.8)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-02 07:50:00 +00:00
dependabot-preview[bot]
b1cfdc761a build(deps): bump syn from 1.0.9 to 1.0.11
Bumps [syn](https://github.com/dtolnay/syn) from 1.0.9 to 1.0.11.
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/1.0.9...1.0.11)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-01 11:33:30 +00:00
Samuel Ortiz
0f21781fbe cargo: Bump the kvm and vmm-sys-util crates
Since the kvm crates now depend on vmm-sys-util, the bump must be
atomic.
The kvm-bindings and ioctls 0.2.0 and 0.4.0 crates come with a few API
changes, one of them being the use of a kvm_ioctls specific error type.
Porting our code to that type makes for a fairly large diff stat.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-11-29 17:48:02 +00:00
dependabot-preview[bot]
ca97385da5 build(deps): bump libc from 0.2.65 to 0.2.66
Bumps [libc](https://github.com/rust-lang/libc) from 0.2.65 to 0.2.66.
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.65...0.2.66)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-11-29 13:00:02 +00:00
dependabot-preview[bot]
f7dace151c build(deps): bump syn from 1.0.8 to 1.0.9
Bumps [syn](https://github.com/dtolnay/syn) from 1.0.8 to 1.0.9.
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/1.0.8...1.0.9)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-11-29 08:48:13 +00:00
Qiu Wenbo
861d902c21 acpi_tables: aml: Add support for binary operators
Signed-off-by: Qiu Wenbo <qiuwenbo@phytium.com.cn>
2019-11-29 08:47:58 +00:00
Rob Bradford
f787139805 build: Reorder travis build commands to reuse build assets
Reorder the build commands so that more build assets in the target/
build directory can be reused. This will reduce the build time.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-11-28 14:29:38 +01:00
Rob Bradford
338beebc83 misc: Update locations to point to new kernel fork
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-11-28 14:28:12 +01:00
Wu Zongyong
4de04e84b5 vfio-pci: unmap regions when dropping VfioGroup
Introduce method unmap_mmio_regions to unmap all regions mapped to host.
This patch eliminate the error message "Could not unset container".

Signed-off-by: Wu Zongyong <wuzongyong@linux.alibaba.com>
2019-11-27 07:44:02 -08:00
Jose Carlos Venegas Munoz
62fa595ac6 cargo: set cloud-hypervisor as default crate
cloud-hypervisor is the main crate, useful to do cargo run.

Signed-off-by: Jose Carlos Venegas Munoz <jose.carlos.venegas.munoz@intel.com>
2019-11-26 10:33:48 +01:00
Jose Carlos Venegas Munoz
ab16af2941 openapi: make context ID vsock int64
context ID on vsock man defines a 32-bits value, openapi default integer
is a signed 32-bits value.

This could lead to miss one bit during castings for typed client
implmentations. Lets increase the range of valid values by requesting an
int64.

Signed-off-by: Jose Carlos Venegas Munoz <jose.carlos.venegas.munoz@intel.com>
2019-11-26 08:38:59 +01:00
dependabot-preview[bot]
9fd5ea4951 build(deps): bump signal-hook from 0.1.11 to 0.1.12
Bumps [signal-hook](https://github.com/vorner/signal-hook) from 0.1.11 to 0.1.12.
- [Release notes](https://github.com/vorner/signal-hook/releases)
- [Changelog](https://github.com/vorner/signal-hook/blob/master/CHANGELOG.md)
- [Commits](https://github.com/vorner/signal-hook/compare/v0.1.11...v0.1.12)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-11-25 21:09:19 +00:00
dependabot-preview[bot]
d6d1074ca3 build(deps): bump serde_derive from 1.0.102 to 1.0.103
Bumps [serde_derive](https://github.com/serde-rs/serde) from 1.0.102 to 1.0.103.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.102...v1.0.103)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-11-25 20:32:53 +00:00
dependabot-preview[bot]
4cff045cc6 build(deps): bump serde_json from 1.0.41 to 1.0.42
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.41 to 1.0.42.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.41...v1.0.42)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-11-25 19:54:16 +00:00
dependabot-preview[bot]
0ae9610e09 build(deps): bump serde from 1.0.102 to 1.0.103
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.102 to 1.0.103.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.102...v1.0.103)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-11-25 18:52:45 +00:00
dependabot-preview[bot]
0274b7923b build(deps): bump signal-hook-registry from 1.1.1 to 1.2.0
Bumps [signal-hook-registry](https://github.com/vorner/signal-hook) from 1.1.1 to 1.2.0.
- [Release notes](https://github.com/vorner/signal-hook/releases)
- [Changelog](https://github.com/vorner/signal-hook/blob/master/CHANGELOG.md)
- [Commits](https://github.com/vorner/signal-hook/compare/registry-v1.1.1...registry-v1.2.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-11-25 17:15:16 +00:00
dependabot-preview[bot]
edd59a0f97 build(deps): bump remain from 0.1.4 to 0.1.5
Bumps [remain](https://github.com/dtolnay/remain) from 0.1.4 to 0.1.5.
- [Release notes](https://github.com/dtolnay/remain/releases)
- [Commits](https://github.com/dtolnay/remain/compare/0.1.4...0.1.5)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-11-25 17:11:41 +00:00
dependabot-preview[bot]
c718225c5f build(deps): bump openssl-sys from 0.9.52 to 0.9.53
Bumps [openssl-sys](https://github.com/sfackler/rust-openssl) from 0.9.52 to 0.9.53.
- [Release notes](https://github.com/sfackler/rust-openssl/releases)
- [Commits](https://github.com/sfackler/rust-openssl/compare/openssl-sys-v0.9.52...openssl-sys-v0.9.53)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-11-25 17:06:23 +00:00
Sebastien Boeuf
360f0639f4 Revert "vfio: use correct flags to disable interrupts"
This reverts commit 66fde245b3.

The commit broke the VFIO support for MSI. Issue needs to be
investigated but in the meantime, it is safer to fix the codebase.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-11-24 12:43:06 +01:00
Rob Bradford
8fe5a43d9d tests: Only setcap on test binary
The setcap tool is unusual in its behaviour in that it will process each
parameter separately and abort if one of the parameters is invalid (e.g.
after a symlink.) But any previous parameters will be correctly
processed. This means that depending on the generated test binary name
an invalid entry might occur before it and thus abort the setcap.

Fix to only setcap on the test binary by excluding the other files.
Because the binary name is based on a hash the PR that introduced this
version worked but once merged the hash changed and broke the build.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-11-23 21:15:56 +00:00
Sebastien Boeuf
f1c7f0c0b8 ci: Add integration test for vhost_user_fs daemon
In order to validate the new virtio-fs daemon written in Rust is
behaving correctly, a new integration test has been added. Important to
note that for now, only a test with cache=none and dax=off can be added
since the daemon does not support shared memory region yet.

The long term goal being to replace virtiofsd with vhost_user_daemon
once it will reach parity regarding the supported features.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-11-22 22:17:47 +01:00
Sebastien Boeuf
50b0e58c88 vhost_user_fs: Allow specific shared directory to be specified
Because the vhost_user_backend crate needs some changes to support
moving the process to a different mount namespace and perform a pivot
root, it is not possible to change '/' to the given shared directory.

This commit, as a temporary measure, let the code point at the given
shared directory.

The long term solution is to perform the mount namespace change and the
pivot root as this will provide greater security.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-11-22 22:17:47 +01:00
Sebastien Boeuf
ba17758ac0 src: Add vhost-user-fs daemon
This patch implements a vhost-user-fs daemon based on Rust. It only
supports communicating through the virtqueues. The support for the
shared memory region associated with DAX will be added later.

It relies on all the code copied over from the crosvm repository, based
on the commit 961461350c0b6824e5f20655031bf6c6bf6b7c30.

It also relies on the vhost_user_backend crate, limiting the amount of
code needed to get this daemon up and running.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-11-22 22:17:47 +01:00
Sebastien Boeuf
5c128023da vhost_user_fs: Add Server structure to consume FileSystem implementation
Add a Server type that links the FUSE protocol with the virtio
transport. It parses messages sent on the virtio queue and then
calls the appropriate method of the Filesystem trait.

This code has been ported over from crosvm commit
961461350c0b6824e5f20655031bf6c6bf6b7c30.

One small modification has been applied to the original code. Because
cloud-hypervisor didn't have the macro used by crosvm, the match
statement in the function handle_message() has been updated.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-11-22 22:17:47 +01:00
Sebastien Boeuf
e1fccc3615 vhost_user_fs: Add virtio descriptor helper traits
Introduce helpers to split a virtio descriptor into its readable part on
one side, and into its writable part on the other side. This is useful
to separate the request from the reply.

This code has been ported over from crosvm commit
961461350c0b6824e5f20655031bf6c6bf6b7c30.

Two important modifications have been applied to the original code:
- GuestMemory is replaced by GuestMemoryMmap from the vm-memory crate,
  which comes with different ways of accessing the memory regions.
- VolatileSlice has different methods, which means the code has been
  updated accordingly.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-11-22 22:17:47 +01:00
Sebastien Boeuf
5f7935f8e0 vhost_user_fs: Add file traits to handle writing volatile memory
The vm_memory implementation for VolatileSlice is able to read and write
to a source or destination which implements a Read or Write trait.

Unfortunately, this is not enough for this specific use case as we need
to be able to write to a file at a specific offset, which is not
provided by the Read or Write trait.

This code has been ported over from crosvm commit
961461350c0b6824e5f20655031bf6c6bf6b7c30.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-11-22 22:17:47 +01:00
Sebastien Boeuf
e33ccb0c95 vhost_user_fs: Implement FileSystem trait for Passthrough
Add a "passthrough" file system implementation that just forwards its
requests to the appropriate system call.

This code has been ported over from crosvm commit
961461350c0b6824e5f20655031bf6c6bf6b7c30.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-11-22 22:17:47 +01:00
Sebastien Boeuf
1b76c0a183 vhost_user_fs: Add FileSystem trait
Add the `Filesystem` trait, which is the main interface between the
transport and the actual file system implementation.

This code has been ported over from crosvm commit
961461350c0b6824e5f20655031bf6c6bf6b7c30.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-11-22 22:17:47 +01:00
Sebastien Boeuf
1e65bda0a7 vhost_user_fs: Add multikey module
The multikey module provides a BTreeMap implementation that can use one
of 2 different kinds of keys to look up a value. This is needed by the
virtio-fs server since it needs to be able to look up keys either by
u64 or by a (ino_t, dev_t) pair.

This code has been ported over from crosvm commit
961461350c0b6824e5f20655031bf6c6bf6b7c30.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-11-22 22:17:47 +01:00
Sebastien Boeuf
cd1684bd2e vhost_user_fs: Add FUSE definitions
To be able to deal with FUSE requests, this commit introduces FUSE
definitions, copied over from the crosvm commit
961461350c0b6824e5f20655031bf6c6bf6b7c30.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-11-22 22:17:47 +01:00
Sebastien Boeuf
03361a6c29 vhost_user_fs: Add new crate
This new crate will be dedicated to vhost_user_fs specific code that can
be used as a library from the vhost-user-fs daemon.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-11-22 22:17:47 +01:00
Sebastien Boeuf
8845326aa2 vm-virtio: Introduce DescriptorChain iterator
In order to iterate over a chain of descriptor chains, this code has
been ported over from crosvm, based on the commit
961461350c0b6824e5f20655031bf6c6bf6b7c30.

The main modification compared to the original code is the way the
sorting between readable and writable descriptors happens.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-11-22 22:17:47 +01:00
Rob Bradford
3d6b5459ef ci: Make the integration test binary run with same caps
By giving the same caps to both cloud-hypervisor and the test binary, we
can access information under /proc related to the VM PID.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-11-22 15:28:10 +00:00
Rob Bradford
ac118c9924 ci: Parse the smaps file with Rust
Instead of using bash and awk, using Rust allows us to retrieve
information about a VM process with the right permissions as we are not
forced to spawn a new child process.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-11-22 15:28:10 +00:00
Sebastien Boeuf
bdb7bcdbe3 ci: Add integration test for mergeable memory
The test validates that when the mergeable option is enabled, the
resulting PSS for two instances of cloud-hypervisor is lower than two
instances not using the mergeable flag.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-11-22 15:28:10 +00:00
Sebastien Boeuf
f979380620 vmm: Mark guest persistent memory pages as mergeable
In case the VM is started with the flag "--pmem mergeable=on", it means
the user expects the guest persistent memory pages to be marked as
mergeable. This commit relies on the madvise(MADV_MERGEABLE) system call
to inform the host kernel about these pages.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-11-22 15:28:10 +00:00
Sebastien Boeuf
0f9afc3017 vmm: Add mergeable=on|off option to --pmem flag
In order to let the user indicate if the persistent memory pages should
be marked as mergeable or not, a new option is being introduced.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-11-22 15:28:10 +00:00
Sebastien Boeuf
e4e8062dda vmm: Mark guest RAM pages as mergeable
In case the VM is started with the flag "--memory mergeable=on", it
means the user expects the guest RAM pages to be marked as mergeable.
This commit relies on the madvise(MADV_MERGEABLE) system call to inform
the host kernel about these pages.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-11-22 15:28:10 +00:00
Sebastien Boeuf
880f62bab8 vmm: Add mergeable=on|off option to --memory flag
In order to let the user indicate if the guest RAM pages should be
marked as mergeable or not, a new option is being introduced.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-11-22 15:28:10 +00:00
Rob Bradford
0213177027 Jenkinsfile: Add timeout for build
In order to conserve CI resources limit build execution to one hour.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-11-22 06:52:21 -08:00
Rob Bradford
d642060378 Jenkinsfile: Switch to pipeline (declarative format)
Switch the Jenkinsfile from the scripted format over to the declarative
format.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-11-22 06:52:21 -08:00
Jose Carlos Venegas Munoz
1d852e9ce5 vmm: Provide vmm version to start_vmm_thread
When vmm.ping give a response, we expect get the version from
the VMM not the vmm create

Signed-off-by: Jose Carlos Venegas Munoz <jose.carlos.venegas.munoz@intel.com>
2019-11-21 15:04:11 -08:00
Jose Carlos Venegas Munoz
a518651402 http: api: implement vmm.ping
vmm.ping will help to check if http API server is up and
running.

This also removes the vmm.info endpoint.

Signed-off-by: Jose Carlos Venegas Munoz <jose.carlos.venegas.munoz@intel.com>
2019-11-21 15:04:11 -08:00
Rob Bradford
348a1bc30e vmm: cpu: Allocate I/O port for the CPU manager
The CPU manager uses an I/O port and to prevent potential clashes with
assignment for PCI devices ensure that it is allocated by the allocator.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-11-21 09:17:15 -08:00
Rob Bradford
07cdb37dda vmm: cpu & acpi: Query CPU manager for CPU status
Rather than hardcode the CPU status for all the CPUs instead query from
the CPU manager via the I/O port that is is on via the ACPI tables.

Each CPU device has a _STA method that calls into the CSTA method which
reads and writes the I/O ports via the PRST field which exposes the I/O
port through and OpRegion.

As we only support boot CPUS report that all the CPUs are enabled for
now.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-11-21 09:17:15 -08:00
Rob Bradford
5faf8b756c vmm: acpi: Add an _MAT for the CPU devices containing a LAPIC
The Linux kernel expects all CPUs, whether they be enabled or disabled
to have an _MAT entry containing the LAPIC details for this CPU with the
enabled bit set to 1 (in the flags.)

In the MADT table the same bit is used to determine if the CPU is
present at boot vs available later.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-11-21 09:17:15 -08:00
Rob Bradford
e51ebe045f acpi_tables: Add support for Buffer objects
These are necessary to populate "_MAT" entries for CPU devices.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-11-21 09:17:15 -08:00
Rob Bradford
1da0ff395d vmm: cpu: Add the CpuManager onto the IO bus
This allows the kernel (via ACPI based controls) to query and control
the CPU state.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-11-21 09:17:15 -08:00
Rob Bradford
39a1b8f4db acpi_tables: aml: Add support for calling methods
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-11-21 09:17:15 -08:00
Rob Bradford
d5bb0781e4 acpi_tables: aml: Add support for while loops
Which also requires adding support for addition.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-11-21 09:17:15 -08:00
Rob Bradford
89f0db2173 acpi_tables: aml: Add support for device notification
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-11-21 09:17:15 -08:00
Rob Bradford
d6696e1bdd acpi_tables: aml: Add support for mutexes
And add support for operations on them.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-11-21 09:17:15 -08:00
Rob Bradford
3d70ce9ad1 acpi_tables: aml: Add "if" with local variables and arguments
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-11-21 09:17:15 -08:00
Rob Bradford
d06623fb97 acpi_tables: aml: Add support for OpRegion
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-11-21 09:17:15 -08:00
Rob Bradford
93ee6f5e62 acpi_tables: aml: Add support for field definitions
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-11-21 09:17:15 -08:00
Rob Bradford
4b5ce23d97 acpi_tables: Add PkgLength variant that does not include itself
This is necessary as adding support for NamedFields requires a PkgLength
calculation that does not include the length itself.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-11-21 09:17:15 -08:00
Rob Bradford
50c8335d3d vmm: device_manager: Expose the SystemAllocator
This allows other code to allocate I/O ports for use on the (already)
exposed IO bus.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-11-21 09:17:15 -08:00
Rob Bradford
1ac1231292 vmm: Encase CpuManager within an Arc<Mutex<>>
This is necessary to be able to add the CpuManager onto the IO bus.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-11-21 09:17:15 -08:00
Wu Zongyong
d7dc1a9226 pci: don't cleanup msi/msix interrupts repeatedly
We disabled msi/msix twice inside Drop trait for VfioPciDevice,
which resulted in error message "Could not disable MSI-X". Eliminating
this error by check whether the msi/msix capability is enabled.

Signed-off-by: Wu Zongyong <wuzongyong@linux.alibaba.com>
CC: Liu Jiang <gerry@linux.alibaba.com>
2019-11-21 06:38:36 -08:00
Wu Zongyong
66fde245b3 vfio: use correct flags to disable interrupts
The comments of vfio kernel module said that individual subindex
interrupts can be disabled using the -1 value for DATA_EVENTFD or
the index can be disabled as a whole with:
    flags = (DATA_NONE|ACTION_TRIGGER), count = 0.

Signed-off-by: Wu Zongyong <wuzongyong@linux.alibaba.com>
CC: Liu Jiang <gerry@linux.alibaba.com>
2019-11-21 06:38:36 -08:00
Rob Bradford
8ec89bc884 misc: Update to new repository locations
Update all references to the new repository locations. Many of these will
redirect however the one used for the hypervisor-fw binary does not so
this is required to allow the builds to pass.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-11-21 11:39:11 +00:00
Sebastien Boeuf
64305dab16 docs: device_model: Fix formatting error
Fix the summary table format.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-11-19 15:45:52 -08:00
Sebastien Boeuf
b55d75ea62 docs: Add device model
This commit introduces a dedicated document describing the device model
supported by cloud-hypervisor VMM.

It needs to be updated anytime a new device will be added in the future.

Fixes #437

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-11-19 14:10:36 -08:00
Samuel Ortiz
f0e618431d vmm: device_manager: Use consistent naming when adding devices
When adding devices to the guest, and populating the device model, we
should prefix the routines with add_. When we're just creating the
device objects but not yet adding them we use make_.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-11-19 13:36:21 -08:00
Samuel Ortiz
a2ee681665 vmm: device_manager: Add an MMIO devices creation routine
In order to reduce the DeviceManager's new() complexity, we can move the
MMIO devices creation code into its own routine.

Fixes: #441

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-11-19 13:36:21 -08:00
Samuel Ortiz
79b8f8e477 vmm: device_manager: Add a PCI devices creation routine
In order to reduce the DeviceManager's new() complexity, we can move the
PCI devices creation code into its own routine.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-11-19 13:36:21 -08:00
Samuel Ortiz
5087f633f6 vmm: device_manager: Add an IOAPIC creation routine
In order to reduce the DeviceManager's new() complexity, we can move the
ACPI device creation code into its own routine.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-11-19 13:36:21 -08:00
Samuel Ortiz
ce1765c8af vmm: device_manager: Add an ACPI device creation routine
In order to reduce the DeviceManager's new() complexity, we can move the
ACPI device creation code into its own routine.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-11-19 13:36:21 -08:00
Samuel Ortiz
cfca2759fc vmm: device_manager: Add a legacy devices creation routine
In order to reduce the DeviceManager's new() complexity, we can move the
legacy devices creation code into its own routine.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-11-19 13:36:21 -08:00
Samuel Ortiz
4b469b98cf vmm: device_manager: Add a console creation routine
In order to reduce the DeviceManager's new() complexity, we can move the
console creation code into its own routine.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-11-19 13:36:21 -08:00
dependabot-preview[bot]
e1281b5488 build(deps): bump cast from 0.2.2 to 0.2.3
Bumps [cast](https://github.com/japaric/cast.rs) from 0.2.2 to 0.2.3.
- [Release notes](https://github.com/japaric/cast.rs/releases)
- [Changelog](https://github.com/japaric/cast.rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/japaric/cast.rs/compare/v0.2.2...v0.2.3)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-11-18 08:43:57 +00:00
dependabot-preview[bot]
fa0d573fef build(deps): bump arc-swap from 0.4.3 to 0.4.4
Bumps [arc-swap](https://github.com/vorner/arc-swap) from 0.4.3 to 0.4.4.
- [Release notes](https://github.com/vorner/arc-swap/releases)
- [Changelog](https://github.com/vorner/arc-swap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/vorner/arc-swap/compare/v0.4.3...v0.4.4)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-11-18 08:43:17 +00:00
Sebastien Boeuf
d9695a0fd9 docs: fs: Update virtio-fs documentation
The cloud-hypervisor supports the DAX shared region for better virtio-fs
performances, but it was not updated in the documentation. This commit
takes care of this and also update the mount command since it changed
with virtio-fs v0.3.

Fixes #433

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-11-15 10:51:31 -08:00
dependabot-preview[bot]
4d0872df5f build(deps): bump vm-memory from 8d6ca35 to bb29ec8
Bumps [vm-memory](https://github.com/rust-vmm/vm-memory) from `8d6ca35` to `bb29ec8`.
- [Release notes](https://github.com/rust-vmm/vm-memory/releases)
- [Commits](8d6ca3553c...bb29ec8713)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-11-15 06:35:41 +00:00
Emin Ghuliev
c204d5404b docs: networking: Fix typo in hyperlink
An extra double quote was introduced by mistake in one of the hyperlink.

Signed-off-by: Emin Ghuliev drmint80@gmail.com
2019-11-13 13:18:26 +01:00
Samuel Ortiz
b930b3fb41 vmm: api: Specify which integers are 64 bit wide
By default, client will assume 32-bits for OpenAPI interger types.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-11-12 08:39:05 -08:00
Samuel Ortiz
6af2f57644 vmm: api: Fix the vm.info response payload
We are returning a state and a config.

Fixes: #431

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-11-12 08:39:05 -08:00
dependabot-preview[bot]
2dcd36f2f0 build(deps): bump synstructure from 0.12.2 to 0.12.3
Bumps [synstructure](https://github.com/mystor/synstructure) from 0.12.2 to 0.12.3.
- [Release notes](https://github.com/mystor/synstructure/releases)
- [Commits](https://github.com/mystor/synstructure/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-11-11 19:29:20 +00:00
Rob Bradford
6958ec4922 vmm: Move CPU management code to its own module
Move CpuManager, Vcpu and related functionality to its own module (and
file) inside the VMM crate

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-11-11 15:46:24 +00:00
dependabot-preview[bot]
7b77189c80 build(deps): bump vm-memory from 366a907 to 8d6ca35
Bumps [vm-memory](https://github.com/rust-vmm/vm-memory) from `366a907` to `8d6ca35`.
- [Release notes](https://github.com/rust-vmm/vm-memory/releases)
- [Commits](366a907660...8d6ca3553c)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-11-11 12:43:48 +00:00
Samuel Ortiz
3dde848c8f vmm: api: Update our OpenAPI document
In most cases we return a 204 (No Content) and not a 201.
In those cases, we do not send any HTTP body back at all.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-11-10 14:51:55 +01:00
Samuel Ortiz
96aa2441ad vmm: http: Convert to micro_http HttpServer
The new micro_http package provides a built-in HttpServer wrapper for
running a more robust HTTP server based on the package HTTP API.

Switching to this implementation allows us to, among other things,
handle HTTP requests that are larger than 1024 bytes.

Fixes: #423

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-11-10 14:51:55 +01:00
Samuel Ortiz
f34ace7673 vmm: http_endpoint: Do not sent 200 status code when our body is empty
Otherwise HTTP client will not close the connection and wait for a
pending body.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-11-10 14:51:55 +01:00
Jose Carlos Venegas Munoz
ede262684d API: HTTP: change response content type to JSON
The HTTP API responses are encoded in json

Suggested-by:  Samuel Ortiz <sameo@linux.intel.com>
Tested-by: Jose Carlos Venegas Munoz <jose.carlos.venegas.munoz@intel.com>

Signed-off-by: Jose Carlos Venegas Munoz <jose.carlos.venegas.munoz@intel.com>
2019-11-08 22:49:08 +01:00
Jose Carlos Venegas Munoz
7498647e3f cargo: Update micro_http
Update micro_http create to allow set content type.

Suggested-by:  Samuel Ortiz <sameo@linux.intel.com>
Tested-by: Jose Carlos Venegas Munoz <jose.carlos.venegas.munoz@intel.com>

Signed-off-by: Jose Carlos Venegas Munoz <jose.carlos.venegas.munoz@intel.com>
2019-11-08 22:49:08 +01:00
dependabot-preview[bot]
fa94635282 build(deps): bump syn from 1.0.7 to 1.0.8
Bumps [syn](https://github.com/dtolnay/syn) from 1.0.7 to 1.0.8.
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/1.0.7...1.0.8)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-11-08 21:18:37 +00:00
Rob Bradford
ff36fa99e6 vm-virtio: Replace use of deprecated std::mem::uninitialized
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-11-08 20:43:52 +00:00
Rob Bradford
3c715daa9d vmm: Fix rustfmt failure by removing extra ";"
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-11-08 20:43:52 +00:00
Rob Bradford
73b4668bd9 acpi_tables: Fix rustfmt failure by removing extra ";"
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-11-08 20:43:52 +00:00
Rob Bradford
a1a5fe0c93 vmm: Split CPU management into it's own struct
Pull details of vCPU management (booting, pausing, resuming, shutdown)
into it's own structure. This will ultimately enable this to be moved to
its own file and encapsulate all the vCPU handling for the VMM.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-11-08 11:59:21 +01:00
Rob Bradford
0319a4a09a arch: vmm: Move ACPI tables creation to vmm crate
Remove ACPI table creation from arch crate to the vmm crate simplifying
arch::configure_system()

GuestAddress(0) is used to mean no RSDP table rather than adding
complexity with a conditional argument or an Option type as it will
evaluate to a zero value which would be the default anyway.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-11-07 14:02:27 +00:00
Sergio Lopez
c3eaa41b77 ci: use the new vhost-user-blk backend for integration tests
Use the new vhost-user-blk backend for the integration tests,
eliminating the need for building vubd using the implementation in
QEMU.

Signed-off-by: Sergio Lopez <slp@redhat.com>
2019-11-07 10:36:30 +00:00
Sergio Lopez
ceafd4cee7 vhost_user_backend: remove ownership check in set_features()
set_features() fails with InvalidOperation if !self.owned. I don't see
this as a requirement in the specification and, in fact, vm-virtio
implementation for resetting the device calls SET_FEATURES just after
RESET_OWNER.

Signed-off-by: Sergio Lopez <slp@redhat.com>
2019-11-07 10:36:30 +00:00
Sergio Lopez
5870452d25 src: add vhost-user-blk backend
Create a vhost-user-blk backend using vhost-user-backend and following
the conventions established by the existing vhost-user-net
implementation.

This backend is based on https://github.com/slp/vhost-user-backend,
but a bit simplified, making it closer to the original implementation
in Firecracker. The main features missing are EVENT_IDX, support for
asynchronous I/O and multiqueue, but it's still fully functional and
provides a good starting point for evolving it into a more complete
implementation.

Signed-off-by: Sergio Lopez <slp@redhat.com>
2019-11-07 10:36:30 +00:00
Sergio Lopez
3a3dd0096c vm-virtio: export block::Request and related funcs/structs
Export block::Request and related functions and structs so the code
can be shared with vhost-user-blk.

Signed-off-by: Sergio Lopez <slp@redhat.com>
2019-11-07 10:36:30 +00:00
Sergio Lopez
08bebaae4f vhost_user_backend: move protocol_features to the backend
Extend VhostUserBackend trait with protocol_features(), so device
backend implementations can freely define which protocol features they
want to support.

Signed-off-by: Sergio Lopez <slp@redhat.com>
2019-11-07 10:36:30 +00:00
Sergio Lopez
85e936d4bd vhost_rs: fix VhostUserConfig payload management
The VhostUserConfig carries a message with a payload, the contents of
which depend on the kind of device being emulated.

With this change, we calculate the offset of the payload within the
message, check its size corresponds to the expected one, and pass it
to the backend as a reference to a slice adjusted to the payload
dimensions.

The backend will be responsible of validating the payload, as it's the
one aware of its expected contents.

Signed-off-by: Sergio Lopez <slp@redhat.com>
2019-11-07 10:36:30 +00:00
dependabot-preview[bot]
2cc723f77b build(deps): bump blake2b_simd from 0.5.8 to 0.5.9
Bumps [blake2b_simd](https://github.com/oconnor663/blake2_simd) from 0.5.8 to 0.5.9.
- [Release notes](https://github.com/oconnor663/blake2_simd/releases)
- [Commits](https://github.com/oconnor663/blake2_simd/compare/0.5.8...0.5.9)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-11-07 07:40:51 +00:00
dependabot-preview[bot]
d1f03bae84 build(deps): bump pnet_macros from 0.22.0 to 0.23.0
Bumps [pnet_macros](https://github.com/libpnet/libpnet) from 0.22.0 to 0.23.0.
- [Release notes](https://github.com/libpnet/libpnet/releases)
- [Commits](https://github.com/libpnet/libpnet/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-11-06 14:35:29 +00:00
dependabot-preview[bot]
ae5e8c47ae build(deps): bump cc from 1.0.46 to 1.0.47
Bumps [cc](https://github.com/alexcrichton/cc-rs) from 1.0.46 to 1.0.47.
- [Release notes](https://github.com/alexcrichton/cc-rs/releases)
- [Commits](https://github.com/alexcrichton/cc-rs/compare/1.0.46...1.0.47)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-11-05 23:58:36 +00:00
dependabot-preview[bot]
8cd45e4ade build(deps): bump synstructure from 0.12.1 to 0.12.2
Bumps [synstructure](https://github.com/mystor/synstructure) from 0.12.1 to 0.12.2.
- [Release notes](https://github.com/mystor/synstructure/releases)
- [Commits](https://github.com/mystor/synstructure/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-11-05 23:58:17 +00:00
Rob Bradford
c999ea6471 arch: x86_64: acpi: Add basic processor details
Add basic processor details to the DSDT table. The code has to be
slightly convoluted (with the second pass over the cpu_devices vector)
in order to keep the objects alive long enough in order to be able to
take their reference.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-11-05 14:45:21 +01:00
Rob Bradford
64368a195a acpi_tables: aml: Add support for Method and Return
This allows the implementation of simple methods that return a constant
value.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-11-05 14:45:21 +01:00
Rob Bradford
08d6386482 acpi_tables: aml: Add support for strings
Support strings from both static strings (&'static str) and String.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-11-05 14:45:21 +01:00
Cathy Zhang
5cd4f5daeb vmm: Release the old vm before build a new one
In vm_reboot, while build the new vm, the old one pointed by self.vm
is not released, that is, the tap opened by self.vm is not closed
either. As a result, the associated dev name slot in host kernel is
still in use state, which prevents the new build from picking it up as
the new opened tap's name, but to use the name in next slot finally.
Call self.vm_shutdown instead here since it has call take() on vm reference,
which could ensure the old vm is destructed before the new vm build.

Signed-off-by: Cathy Zhang <cathy.zhang@intel.com>
2019-11-05 14:40:43 +01:00
Rob Bradford
b3388c343d vmm: device_manager: Ensure I/O ports are allocated
Ensure that we tell the allocator about all the I/O ports that we are
using for I/O bus attached devices (serial, i8042, ACPI device.)

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-11-05 10:13:01 +00:00
dependabot-preview[bot]
2d25862477 build(deps): bump pkg-config from 0.3.16 to 0.3.17
Bumps [pkg-config](https://github.com/rust-lang/pkg-config-rs) from 0.3.16 to 0.3.17.
- [Release notes](https://github.com/rust-lang/pkg-config-rs/releases)
- [Changelog](https://github.com/rust-lang/pkg-config-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/pkg-config-rs/compare/0.3.16...0.3.17)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-11-02 16:10:00 +00:00
Rob Bradford
ce386ba4c6 tests: Use release build for integration tests
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-10-31 10:07:37 -07:00
dependabot-preview[bot]
87e9ce6960 build(deps): bump num_cpus from 1.10.1 to 1.11.0
Bumps [num_cpus](https://github.com/seanmonstar/num_cpus) from 1.10.1 to 1.11.0.
- [Release notes](https://github.com/seanmonstar/num_cpus/releases)
- [Changelog](https://github.com/seanmonstar/num_cpus/blob/master/CHANGELOG.md)
- [Commits](https://github.com/seanmonstar/num_cpus/compare/v1.10.1...v1.11.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-10-31 14:03:37 +00:00
Sebastien Boeuf
5694ac2b1e vm-virtio: Create new VirtioTransport trait to abstract ioeventfds
In order to group together some functions that can be shared across
virtio transport layers, this commit introduces a new trait called
VirtioTransport.

The first function of this trait being ioeventfds() as it is needed from
both virtio-mmio and virtio-pci devices, represented by MmioDevice and
VirtioPciDevice structures respectively.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-10-31 09:30:59 +01:00
Sebastien Boeuf
3fa5df4161 vmm: Unregister old ioeventfds when reprogramming PCI BAR
Now that kvm-ioctls has been updated, the function unregister_ioevent()
can be used to remove eventfd previously associated with some specific
PIO or MMIO guest address. Particularly, it is useful for the PCI BAR
reprogramming case, as we want to ensure the eventfd will only get
triggered by the new BAR address, and not the old one.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-10-31 09:30:59 +01:00
Sebastien Boeuf
587a420429 cargo: Update to the latest kvm-ioctls version
We need to rely on the latest kvm-ioctls version to benefit from the
recent addition of unregister_ioevent(), allowing us to detach a
previously registered eventfd to a PIO or MMIO guest address.

Because of this update, we had to modify the current constraint we had
on the vmm-sys-util crate, using ">= 0.1.1" instead of being strictly
tied to "0.2.0".

Once the dependency conflict resolved, this commit took care of fixing
build issues caused by recent modification of kvm-ioctls relying on
EventFd reference instead of RawFd.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-10-31 09:30:59 +01:00
Sebastien Boeuf
c7cabc88b4 vmm: Conditionally update ioeventfds for virtio PCI device
The specific part of PCI BAR reprogramming that happens for a virtio PCI
device is the update of the ioeventfds addresses KVM should listen to.
This should not be triggered for every BAR reprogramming associated with
the virtio device since a virtio PCI device might have multiple BARs.

The update of the ioeventfds addresses should only happen when the BAR
related to those addresses is being moved.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-10-31 09:30:59 +01:00
Sebastien Boeuf
de21c9ba4f pci: Remove ioeventfds() from PciDevice trait
The PciDevice trait is supposed to describe only functions related to
PCI. The specific method ioeventfds() has nothing to do with PCI, but
instead would be more specific to virtio transport devices.

This commit removes the ioeventfds() method from the PciDevice trait,
adding some convenient helper as_any() to retrieve the Any trait from
the structure behing the PciDevice trait. This is the only way to keep
calling into ioeventfds() function from VirtioPciDevice, so that we can
still properly reprogram the PCI BAR.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-10-31 09:30:59 +01:00
Samuel Ortiz
3be95dbf93 pci: Remove KVM dependency
The PCI crate should not depend on the KVM crates.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-10-29 20:09:04 -07:00
Sebastien Boeuf
296f2e1182 ci: Add integration test for PCI BAR reprogramming
Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-10-29 16:48:02 +01:00
Sebastien Boeuf
d6c68e4738 pci: Add error propagation to PCI BAR reprogramming
Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-10-29 16:48:02 +01:00
Sebastien Boeuf
3e819ac797 pci: Use a weak reference to the AddressManager
Storing a strong reference to the AddressManager behind the
DeviceRelocation trait results in a cyclic reference count.
Use a weak reference to break that dependency.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-10-29 16:48:02 +01:00
Sebastien Boeuf
149b61b213 pci: Detect BAR reprogramming
Based on the value being written to the BAR, the implementation can
now detect if the BAR is being moved to another address. If that is the
case, it invokes move_bar() function from the DeviceRelocation trait.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-10-29 16:48:02 +01:00
Sebastien Boeuf
04a449d3f3 pci: Pass DeviceRelocation to PciBus
In order to trigger the PCI BAR reprogramming from PciConfigIo and
PciConfigMmmio, we need the PciBus to have a hold onto the trait
implementation of DeviceRelocation.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-10-29 16:48:02 +01:00
Sebastien Boeuf
e93467a96c vmm: Implement DeviceRelocation trait
By implementing the DeviceRelocation trait for the AddressManager
structure, we now have a way to let the PCI BAR reprogramming happen.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-10-29 16:48:02 +01:00
Sebastien Boeuf
4f8054fa82 pci: Store the type of BAR to return correct address
Based on the type of BAR, we can now provide the correct address related
to a BAR index provided by the caller.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-10-29 16:48:02 +01:00
Sebastien Boeuf
b51a9e1ef1 pci: Make PciBarRegionType implement PartialEq
Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-10-29 16:48:02 +01:00
Sebastien Boeuf
8746c16593 vmm: Create AddressManager to own SystemAllocator
In order to reuse the SystemAllocator later at runtime, it is moved into
the new structure AddressManager. The goal is to have a hold onto the
SystemAllocator and both IO and MMIO buses so that we can use them
later.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-10-29 16:48:02 +01:00
Sebastien Boeuf
1870eb4295 devices: Lock the BtreeMap inside to avoid deadlocks
Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-10-29 16:48:02 +01:00
Sebastien Boeuf
733e636f02 devices: Allow for bus range removal and update
Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-10-29 16:48:02 +01:00
Sebastien Boeuf
e536f88012 vfio: Implement move_bar() from PciDevice trait
Everytime a PCI BAR is moved, the KVM regions need to be updated.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-10-29 16:48:02 +01:00
Sebastien Boeuf
c865f93c9b pci: Extend PciDevice trait with move_bar() function
In order to support PCI BAR reprogramming, the PciDevice trait defines a
new method move_bar() dedicated for taking appropriate action when a BAR
is moved to a different guest address.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-10-29 16:48:02 +01:00
Sebastien Boeuf
3e37f59933 pci: Add new DeviceRelocation trait
This new trait will allow the VMM to implement the mechanism following a
BAR modification regarding its location in the guest address space.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-10-29 16:48:02 +01:00
Sebastien Boeuf
5cc1e73e52 Fix Cargo.lock
Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-10-29 16:48:02 +01:00
Rob Bradford
05eb567a7c build: Ensure there is a release build artifact for travis to upload
After some of the build changes there was no longer a release artifact
left to be used for the release builds.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-10-29 15:23:31 +00:00
Samuel Ortiz
75bf240b83 cargo: Move to 0.3.0
Our most recent release.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-10-28 21:32:30 +01:00
dependabot-preview[bot]
be6a1975d2 build(deps): bump serde_derive from 1.0.101 to 1.0.102
Bumps [serde_derive](https://github.com/serde-rs/serde) from 1.0.101 to 1.0.102.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.101...v1.0.102)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-10-28 07:34:31 +00:00
dependabot-preview[bot]
f27893f822 build(deps): bump vm-memory from 8669369 to 366a907
Bumps [vm-memory](https://github.com/rust-vmm/vm-memory) from `8669369` to `366a907`.
- [Release notes](https://github.com/rust-vmm/vm-memory/releases)
- [Commits](8669369d17...366a907660)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-10-28 07:17:32 +00:00
dependabot-preview[bot]
696f6cae47 build(deps): bump signal-hook from 0.1.10 to 0.1.11
Bumps [signal-hook](https://github.com/vorner/signal-hook) from 0.1.10 to 0.1.11.
- [Release notes](https://github.com/vorner/signal-hook/releases)
- [Changelog](https://github.com/vorner/signal-hook/blob/master/CHANGELOG.md)
- [Commits](https://github.com/vorner/signal-hook/compare/v0.1.10...v0.1.11)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-10-28 06:59:16 +00:00
dependabot-preview[bot]
1f7f484f61 build(deps): bump serde from 1.0.101 to 1.0.102
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.101 to 1.0.102.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.101...v1.0.102)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-10-28 06:28:18 +00:00
dependabot-preview[bot]
4e0b5e8a63 build(deps): bump syn from 1.0.6 to 1.0.7
Bumps [syn](https://github.com/dtolnay/syn) from 1.0.6 to 1.0.7.
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/1.0.6...1.0.7)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-10-28 06:27:34 +00:00
dependabot-preview[bot]
c159515e91 build(deps): bump syn from 1.0.5 to 1.0.6
Bumps [syn](https://github.com/dtolnay/syn) from 1.0.5 to 1.0.6.
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/1.0.5...1.0.6)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-10-27 09:21:34 +00:00
dependabot-preview[bot]
26336363cc build(deps): bump getrandom from 0.1.12 to 0.1.13
Bumps [getrandom](https://github.com/rust-random/getrandom) from 0.1.12 to 0.1.13.
- [Release notes](https://github.com/rust-random/getrandom/releases)
- [Changelog](https://github.com/rust-random/getrandom/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-random/getrandom/compare/v0.1.12...v0.1.13)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-10-25 13:03:06 +00:00
Rob Bradford
7c2c4fb3fc acpi_tables: aml: Rename to_bytes() to to_aml_bytes()
To avoid a clash with to_bytes() for the unsigned integer types that is
coming in a future release.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-10-24 10:55:02 +01:00
Rob Bradford
ad60fe110b arch: x86_64: acpi: Mark 64-bit device area uncacheable
This region was erroneously marked as cacheable.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-10-24 10:55:02 +01:00
Rob Bradford
025f1f9d9b arch: x86_64: acpi: Remove 16-bit PCI range
We don't use this range and it shouldn't be included in our DSDT.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-10-24 10:55:02 +01:00
Rob Bradford
555ac68ea5 arch: x86_64: acpi: Generate DSDT programatically
This was verified by comparing the ASL from disassembling the DSDT
before and after. All the individual AML components themselves are also
unit tested.

Fixes: #352

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-10-24 10:55:02 +01:00
Rob Bradford
dd539df633 acpi_tables: sdt: Add ability to add to the table from a slice
The generic version does not work in this case as it the size of the the
&[u8] is not the size of the slice's contents but how much memory the
slice object itself takes up.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-10-24 10:55:02 +01:00
Rob Bradford
c76fd6df21 acpi_tables: aml: Implement AML Zero, One, Ones
These are special encodings for representing zero, one or all ones in a
single byte.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-10-24 10:55:02 +01:00
Rob Bradford
9269e40ba5 acpi_tables: aml: Add support for device and scope
These both group a set of AML objects together.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-10-24 10:55:02 +01:00
Rob Bradford
e1e0ac2ee3 acpi_tables: aml: Add support for creating IO and interrupt resources
As per ACPI spec: "6.4.2.5 I/O Port Descriptor" and "6.4.3.6 Extended
Interrupt Descriptor"

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-10-24 10:55:02 +01:00
Rob Bradford
08aff4ed5a acpi_tables: aml: Add support for address spaces
Add support for Word/DWord/QWord address spaces for I/O, memory and
buses. Using sensible defaults for infrequently set flags.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-10-24 10:55:02 +01:00
Rob Bradford
c4c3540a1d acpi_tables: aml: Add resource templates and Memory32Fixed
Add support for generating resource templates (a kind of buffer) along
with generating Memory32Fixed to go into it.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-10-24 10:55:02 +01:00
Rob Bradford
03d8cdc4f2 acpi_tables: aml: Generate EISA name IDs
EISA names (of the the form UUUNNNN) are encoded into a 32-bit integer.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-10-24 10:55:02 +01:00
Rob Bradford
3cb73b3a45 acpi_tables: aml: Add Package support
Packages are a way of grouping values together and as such they require
an explicit length which is a variable length encoding calculated with
create_pkg_length().

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-10-24 10:55:02 +01:00
Rob Bradford
bf0d0d9f9b acpi_tables: aml: Add support for named definitions
Named definitions use op code 0x08 and are used to name some more
complex object.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-10-24 10:55:02 +01:00
Rob Bradford
5a7076442c acpi_tables: aml: Implement numbers
Add support for the numerical types. By using type aliases the
generation can be very ergonomic as From<T> is implemented.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-10-24 10:55:02 +01:00
Rob Bradford
a4ce596f7b acpi_tables: aml: Add support for generating AML name paths
Root ("\"), single, dual and multi-part names are supported. "^" is not
supported (not widely used.)

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-10-24 10:55:02 +01:00
Samuel Ortiz
1387ac5571 ci: Add cargo audit to the travis pipeline
cargo audit audits Cargo.lock for crates with security vulnerabvilities
reported by the RustSec Advisory Database.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-10-23 13:06:29 +03:00
Samuel Ortiz
de9eb3e0fa Bump vmm-sys-utils to 0.2.0
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-10-23 11:35:11 +03:00
dependabot-preview[bot]
dc951af167 build(deps): bump c2-chacha from 0.2.2 to 0.2.3
Bumps [c2-chacha](https://github.com/cryptocorrosion/cryptocorrosion) from 0.2.2 to 0.2.3.
- [Release notes](https://github.com/cryptocorrosion/cryptocorrosion/releases)
- [Commits](https://github.com/cryptocorrosion/cryptocorrosion/compare/c2-chacha-0.2.2...c2-chacha-0.2.3)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-10-21 17:44:17 +00:00
dependabot-preview[bot]
3dff551399 build(deps): bump ppv-lite86 from 0.2.5 to 0.2.6
Bumps [ppv-lite86](https://github.com/cryptocorrosion/cryptocorrosion) from 0.2.5 to 0.2.6.
- [Release notes](https://github.com/cryptocorrosion/cryptocorrosion/releases)
- [Commits](https://github.com/cryptocorrosion/cryptocorrosion/compare/ppv-lite86-0.2.5...ppv-lite86-0.2.6)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-10-21 17:10:47 +00:00
dependabot-preview[bot]
4783fe7402 build(deps): bump autocfg from 0.1.6 to 0.1.7
Bumps [autocfg](https://github.com/cuviper/autocfg) from 0.1.6 to 0.1.7.
- [Release notes](https://github.com/cuviper/autocfg/releases)
- [Commits](https://github.com/cuviper/autocfg/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-10-21 04:26:47 +00:00
dependabot-preview[bot]
e10413b96c build(deps): bump proc-macro2 from 1.0.5 to 1.0.6
Bumps [proc-macro2](https://github.com/alexcrichton/proc-macro2) from 1.0.5 to 1.0.6.
- [Release notes](https://github.com/alexcrichton/proc-macro2/releases)
- [Commits](https://github.com/alexcrichton/proc-macro2/compare/1.0.5...1.0.6)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-10-20 14:30:54 +00:00
dependabot-preview[bot]
317d754946 build(deps): bump ssh2 from 0.4.0 to 0.5.0
Bumps [ssh2](https://github.com/alexcrichton/ssh2-rs) from 0.4.0 to 0.5.0.
- [Release notes](https://github.com/alexcrichton/ssh2-rs/releases)
- [Commits](https://github.com/alexcrichton/ssh2-rs/compare/0.4.0...0.5.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-10-20 14:30:18 +00:00
dependabot-preview[bot]
eff1ece368 build(deps): bump openssl-sys from 0.9.51 to 0.9.52
Bumps [openssl-sys](https://github.com/sfackler/rust-openssl) from 0.9.51 to 0.9.52.
- [Release notes](https://github.com/sfackler/rust-openssl/releases)
- [Commits](https://github.com/sfackler/rust-openssl/compare/openssl-sys-v0.9.51...openssl-sys-v0.9.52)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-10-20 14:30:02 +00:00
Sebastien Boeuf
05c7130f06 ci: Update ClearLinux image
A new ClearLinux image has been uploaded to the Azure storage account.
It is based off of the ClearLinux cloudguest image 31310 version, with
two extra bundles added to it.

First bundle is sysadmin-basic to include utility like netcat, and the
second bundle is iperf, adding the iperf binary to the image.

The image is 2G in size.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-10-18 16:49:46 +01:00
130 changed files with 23768 additions and 12085 deletions

View File

@@ -3,22 +3,8 @@ language: rust
rust: rust:
- stable - stable
before_script:
- rustup component add clippy
- rustup component add rustfmt
script: script:
- cargo rustc --bin cloud-hypervisor -- -D warnings - cargo build --release
- cargo rustc --bin cloud-hypervisor --no-default-features --features "pci" -- -D warnings
- cargo rustc --bin cloud-hypervisor --no-default-features --features "pci,acpi" -- -D warnings
- cargo rustc --bin cloud-hypervisor --no-default-features --features "mmio" -- -D warnings
- cargo rustc --bin vhost_user_net -- -D warnings
- cargo rustc --bin vhost_user_net --no-default-features --features "pci" -- -D warnings
- cargo rustc --bin vhost_user_net --no-default-features --features "pci,acpi" -- -D warnings
- cargo rustc --bin vhost_user_net --no-default-features --features "mmio" -- -D warnings
- cargo test
- cargo clippy --all-targets --all-features -- -D warnings
- find . -name "*.rs" | xargs rustfmt --check
deploy: deploy:
provider: releases provider: releases

View File

@@ -51,7 +51,7 @@ Signed-off-by: Rob Bradford <robert.bradford@intel.com>
Cloud Hypervisor uses the “fork-and-pull” development model. Follow these steps if Cloud Hypervisor uses the “fork-and-pull” development model. Follow these steps if
you want to merge your changes to `cloud-hypervisor`: you want to merge your changes to `cloud-hypervisor`:
1. Fork the [cloud-hypervisor](https://github.com/intel/cloud-hypervisor) project 1. Fork the [cloud-hypervisor](https://github.com/cloud-hypervisor/cloud-hypervisor) project
into your github organization. into your github organization.
2. Within your fork, create a branch for your contribution. 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/) 3. [Create a pull request](https://help.github.com/articles/creating-a-pull-request-from-a-fork/)
@@ -65,7 +65,7 @@ you want to merge your changes to `cloud-hypervisor`:
## Issue tracking ## Issue tracking
If you have a problem, please let us know. We recommend using If you have a problem, please let us know. We recommend using
[github issues](https://github.com/intel/cloud-hypervisor/issues/new) for formally [github issues](https://github.com/cloud-hypervisor/cloud-hypervisor/issues/new) for formally
reporting and documenting them. reporting and documenting them.
To quickly and informally bring something up to us, you can also reach out on [Slack](https://cloud-hypervisor.slack.com). To quickly and informally bring something up to us, you can also reach out on [Slack](https://cloud-hypervisor.slack.com).

749
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,36 +1,40 @@
[package] [package]
name = "cloud-hypervisor" name = "cloud-hypervisor"
version = "0.1.0" version = "0.5.1"
authors = ["The Cloud Hypervisor Authors"] authors = ["The Cloud Hypervisor Authors"]
edition = "2018" edition = "2018"
default-run = "cloud-hypervisor"
[dependencies] [dependencies]
arc-swap = "0.4.4"
clap = "2.33.0" clap = "2.33.0"
epoll = "4.1.0" epoll = "4.1.0"
lazy_static = "1.4.0" lazy_static = "1.4.0"
libc = "0.2.65" libc = "0.2.66"
log = { version = "0.4.8", features = ["std"] } log = { version = "0.4.8", features = ["std"] }
net_gen = { path = "net_gen" }
net_util = { path = "net_util" }
vhost_user_backend = { path = "vhost_user_backend"} vhost_user_backend = { path = "vhost_user_backend"}
vhost_user_block = { path = "vhost_user_block"}
vhost_user_fs = { path = "vhost_user_fs"}
vhost_user_net = { path = "vhost_user_net"}
virtio-bindings = "0.1.0" virtio-bindings = "0.1.0"
vmm = { path = "vmm" } vmm = { path = "vmm" }
vm-device = { path = "vm-device" } vm-device = { path = "vm-device" }
vm-memory = { git = "https://github.com/rust-vmm/vm-memory" } vm-memory = { git = "https://github.com/rust-vmm/vm-memory" }
vmm-sys-util = "0.1.1" vmm-sys-util = "0.4.0"
vm-virtio = { path = "vm-virtio" } vm-virtio = { path = "vm-virtio" }
[dev-dependencies]
ssh2 = "0.4.0"
dirs = "2.0.2"
credibility = "0.1.3"
tempdir= "0.3.7"
lazy_static= "1.4.0"
[dependencies.vhost_rs] [dependencies.vhost_rs]
path = "vhost_rs" path = "vhost_rs"
features = ["vhost-user-slave"] features = ["vhost-user-slave"]
[dev-dependencies]
ssh2 = "0.7.1"
dirs = "2.0.2"
credibility = "0.1.3"
tempdir= "0.3.7"
lazy_static= "1.4.0"
serde_json = "1.0.48"
[features] [features]
default = ["acpi", "pci", "cmos"] default = ["acpi", "pci", "cmos"]
acpi = ["vmm/acpi"] acpi = ["vmm/acpi"]
@@ -43,5 +47,22 @@ integration_tests = []
[workspace] [workspace]
members = [ members = [
"arch",
"devices",
"vhost_rs",
"qcow",
"pci",
"vmm",
"vm-virtio",
"vm-device",
"vhost_user_block",
"vhost_user_backend", "vhost_user_backend",
"vhost_user_fs",
"vhost_user_net",
"vfio",
"net_util",
"acpi_tables",
"arch_gen",
"net_gen",
"vm-allocator",
] ]

97
Jenkinsfile vendored
View File

@@ -1,22 +1,85 @@
stage ("Builds") { pipeline{
node ('bionic') { agent none
stage ('Checkout') { stages {
checkout scm stage ('Master build') {
agent { node { label 'master' } }
stages {
stage ('Check for RFC/WIP builds') {
when {
changeRequest comparator: 'REGEXP', title: '.*(rfc|RFC|wip|WIP).*'
beforeAgent true
}
steps {
error("Failing as this is marked as a WIP or RFC PR.")
}
}
stage ('Cancel older builds') {
steps {
cancelPreviousBuilds()
}
}
}
} }
stage ('Install system packages') { stage ('Worker build') {
sh "sudo DEBIAN_FRONTEND=noninteractive apt-get install -yq build-essential mtools libssl-dev pkg-config" agent { node { label 'bionic' } }
sh "sudo apt-get install -yq flex bison libelf-dev qemu-utils qemu-system libglib2.0-dev libpixman-1-dev libseccomp-dev socat" options {
} timeout(time: 1, unit: 'HOURS')
stage ('Install Rust') { }
sh "nohup curl https://sh.rustup.rs -sSf | sh -s -- -y" stages {
} stage ('Checkout') {
stage ('Run unit tests') { steps {
sh "scripts/run_unit_tests.sh" checkout scm
} }
stage ('Run integration tests') { }
sh "sudo mount -t tmpfs tmpfs /tmp" stage ('Install system packages') {
sh "scripts/run_integration_tests.sh" steps {
sh "sudo DEBIAN_FRONTEND=noninteractive apt-get install -yq build-essential mtools libssl-dev pkg-config"
sh "sudo apt-get install -yq flex bison libelf-dev qemu-utils qemu-system libglib2.0-dev libpixman-1-dev libseccomp-dev libcap-ng-dev socat"
sh "sudo snap install docker"
}
}
stage ('Install Rust') {
steps {
sh "nohup curl https://sh.rustup.rs -sSf | sh -s -- -y"
}
}
stage ('Run Cargo tests') {
steps {
sh "scripts/run_cargo_tests.sh"
}
}
stage ('Run OpenAPI tests') {
steps {
sh "scripts/run_openapi_tests.sh"
}
}
stage ('Run unit tests') {
steps {
sh "scripts/run_unit_tests.sh"
}
}
stage ('Run integration tests') {
steps {
sh "sudo mount -t tmpfs tmpfs /tmp"
sh "scripts/run_integration_tests.sh"
}
}
}
} }
} }
} }
def cancelPreviousBuilds() {
// Check for other instances of this particular build, cancel any that are older than the current one
def jobName = env.JOB_NAME
def currentBuildNumber = env.BUILD_NUMBER.toInteger()
def currentJob = Jenkins.instance.getItemByFullName(jobName)
// Loop through all instances of this particular job/branch
for (def build : currentJob.builds) {
if (build.isBuilding() && (build.number.toInteger() < currentBuildNumber)) {
echo "Older build still queued. Sending kill signal to build number: ${build.number}"
build.doStop()
}
}
}

View File

@@ -1,4 +1,4 @@
[![Build Status](https://travis-ci.com/intel/cloud-hypervisor.svg?branch=master)](https://travis-ci.com/intel/cloud-hypervisor) [![Build Status](https://travis-ci.com/cloud-hypervisor/cloud-hypervisor.svg?branch=master)](https://travis-ci.com/cloud-hypervisor/cloud-hypervisor)
1. [What is Cloud Hypervisor?](#1-what-is-cloud-hypervisor) 1. [What is Cloud Hypervisor?](#1-what-is-cloud-hypervisor)
* [Requirements](#requirements) * [Requirements](#requirements)
@@ -14,6 +14,7 @@
- [Disk image](#disk-image) - [Disk image](#disk-image)
- [Booting the guest VM](#booting-the-guest-vm) - [Booting the guest VM](#booting-the-guest-vm)
3. [Status](#2-status) 3. [Status](#2-status)
* [Device Model](#device-model)
* [TODO](#todo) * [TODO](#todo)
4. [rust-vmm dependency](#4-rust-vmm-dependency) 4. [rust-vmm dependency](#4-rust-vmm-dependency)
* [Firecracker and crosvm](#firecracker-and-crosvm) * [Firecracker and crosvm](#firecracker-and-crosvm)
@@ -73,7 +74,7 @@ First you need to clone and build the cloud-hypervisor repo:
```shell ```shell
$ pushd $CLOUDH $ pushd $CLOUDH
$ git clone https://github.com/intel/cloud-hypervisor.git $ git clone https://github.com/cloud-hypervisor/cloud-hypervisor.git
$ cd cloud-hypervisor $ cd cloud-hypervisor
$ cargo build --release $ cargo build --release
@@ -85,6 +86,34 @@ $ popd
This will build a `cloud-hypervisor` binary under `$CLOUDH/cloud-hypervisor/target/release/cloud-hypervisor`. This will build a `cloud-hypervisor` binary under `$CLOUDH/cloud-hypervisor/target/release/cloud-hypervisor`.
### Containerized builds and tests
If you want to build and test Cloud Hypervisor without having to install all the
required dependencies (The rust toolchain, cargo tools, etc), you can also use
Cloud Hypervisor's development script: `dev_cli.sh`. Please note that upon its
first invocation, this script will pull a fairly large container image.
For example, to build the Cloud Hypervisor release binary:
```shell
$ pushd $CLOUDH
$ cd cloud-hypervisor
$ ./scripts/dev_cli.sh build --release
```
With `dev_cli.sh`, one can also run the Cloud Hypervisor CI locally. This can be
very convenient for debugging CI errors without having to fully rely on the
Cloud Hypervisor CI infrastructure.
For example, to run the Cloud Hypervisor unit tests:
```shell
$ ./scripts/dev_cli.sh tests --unit
```
Run the `./scripts/dev_cli.sh --help` command to view all the supported
development script commands and their related options.
## Run ## Run
You can run a guest VM by either using an existing cloud image or booting into your own kernel and disk image. You can run a guest VM by either using an existing cloud image or booting into your own kernel and disk image.
@@ -94,7 +123,7 @@ You can run a guest VM by either using an existing cloud image or booting into y
`cloud-hypervisor` supports booting disk images containing all needed `cloud-hypervisor` supports booting disk images containing all needed
components to run cloud workloads, a.k.a. cloud images. To do that we rely on components to run cloud workloads, a.k.a. cloud images. To do that we rely on
the [Rust Hypervisor the [Rust Hypervisor
Firmware](https://github.com/intel/rust-hypervisor-firmware) project to provide Firmware](https://github.com/cloud-hypervisor/rust-hypervisor-firmware) project to provide
an ELF an ELF
formatted KVM firmware for `cloud-hypervisor` to directly boot into. formatted KVM firmware for `cloud-hypervisor` to directly boot into.
@@ -102,9 +131,9 @@ We need to get the latest `rust-hypervisor-firmware` release and also a working
```shell ```shell
$ pushd $CLOUDH $ pushd $CLOUDH
$ wget https://download.clearlinux.org/releases/29160/clear/clear-29160-kvm.img.xz $ wget https://download.clearlinux.org/releases/31890/clear/clear-31890-kvm.img.xz
$ unxz clear-29160-kvm.img.xz $ unxz clear-31890-kvm.img.xz
$ wget https://github.com/intel/rust-hypervisor-firmware/releases/download/0.2.0/hypervisor-fw $ wget https://github.com/cloud-hypervisor/rust-hypervisor-firmware/releases/download/0.2.6/hypervisor-fw
$ popd $ popd
``` ```
@@ -113,8 +142,8 @@ $ pushd $CLOUDH
$ sudo setcap cap_net_admin+ep ./cloud-hypervisor/target/release/cloud-hypervisor $ sudo setcap cap_net_admin+ep ./cloud-hypervisor/target/release/cloud-hypervisor
$ ./cloud-hypervisor/target/release/cloud-hypervisor \ $ ./cloud-hypervisor/target/release/cloud-hypervisor \
--kernel ./hypervisor-fw \ --kernel ./hypervisor-fw \
--disk path=clear-29160-kvm.img \ --disk path=clear-31890-kvm.img \
--cpus 4 \ --cpus boot=4 \
--memory size=1024M \ --memory size=1024M \
--net "tap=,mac=,ip=,mask=" \ --net "tap=,mac=,ip=,mask=" \
--rng --rng
@@ -128,18 +157,19 @@ Multiple arguments can be given to the `--disk` parameter.
#### Building your kernel #### Building your kernel
`cloud-hypervisor` also supports direct kernel boot into a `vmlinux` ELF kernel `cloud-hypervisor` also supports direct kernel boot into a `vmlinux` ELF kernel
image. You want to build such an image first: image. In order to support virtio-fs and virtio-iommu we have our own development branch. You are of course able to use your own kernel but these instructions will continue with the version that we develop and test against.
To build the kernel:
```shell ```shell
# Clone a 5.0 Linux kernel # Clone the Cloud Hypervisor Linux branch
$ pushd $CLOUDH $ pushd $CLOUDH
$ git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git linux-cloud-hypervisor $ git clone --depth 1 https://github.com/cloud-hypervisor/linux.git -b virtio-fs-virtio-iommu-5.5-rc1 linux-cloud-hypervisor
$ cd linux-cloud-hypervisor $ pushd linux-cloud-hypervisor
$ git reset --hard v5.0
# Use the cloud-hypervisor kernel config to build your kernel # Use the cloud-hypervisor kernel config to build your kernel
$ cp $CLOUDH/cloud-hypervisor/resources/linux-5.0-config .config $ cp $CLOUDH/cloud-hypervisor/resources/linux-virtio-fs-virtio-iommu-config .config
$ make bzImage -j `nproc` $ make bzImage -j `nproc`
$ popd $ popd
``` ```
@@ -152,8 +182,8 @@ For the disk image, we will use a Clear Linux cloud image that contains a root p
```shell ```shell
$ pushd $CLOUDH $ pushd $CLOUDH
$ wget https://download.clearlinux.org/releases/29160/clear/clear-29160-kvm.img.xz $ wget https://download.clearlinux.org/releases/31890/clear/clear-31890-kvm.img.xz
$ unxz clear-29160-kvm.img.xz $ unxz clear-31890-kvm.img.xz
$ popd $ popd
``` ```
@@ -167,9 +197,9 @@ $ pushd $CLOUDH
$ sudo setcap cap_net_admin+ep ./cloud-hypervisor/target/release/cloud-hypervisor $ sudo setcap cap_net_admin+ep ./cloud-hypervisor/target/release/cloud-hypervisor
$ ./cloud-hypervisor/target/release/cloud-hypervisor \ $ ./cloud-hypervisor/target/release/cloud-hypervisor \
--kernel ./linux-cloud-hypervisor/arch/x86/boot/compressed/vmlinux.bin \ --kernel ./linux-cloud-hypervisor/arch/x86/boot/compressed/vmlinux.bin \
--disk path=clear-29160-kvm.img \ --disk path=clear-31890-kvm.img \
--cmdline "console=hvc0 reboot=k panic=1 nomodules i8042.noaux i8042.nomux i8042.nopnp i8042.dumbkbd root=/dev/vda3" \ --cmdline "console=hvc0 reboot=k panic=1 nomodules i8042.noaux i8042.nomux i8042.nopnp i8042.dumbkbd root=/dev/vda3" \
--cpus 4 \ --cpus boot=4 \
--memory size=1024M \ --memory size=1024M \
--net "tap=,mac=,ip=,mask=" \ --net "tap=,mac=,ip=,mask=" \
--rng --rng
@@ -187,27 +217,38 @@ $ ./cloud-hypervisor/target/release/cloud-hypervisor \
--kernel ./linux-cloud-hypervisor/arch/x86/boot/compressed/vmlinux.bin \ --kernel ./linux-cloud-hypervisor/arch/x86/boot/compressed/vmlinux.bin \
--console off \ --console off \
--serial tty \ --serial tty \
--disk path=clear-29160-kvm.img \ --disk path=clear-31890-kvm.img \
--cmdline "console=ttyS0 reboot=k panic=1 nomodules i8042.noaux i8042.nomux i8042.nopnp i8042.dumbkbd root=/dev/vda3" \ --cmdline "console=ttyS0 reboot=k panic=1 nomodules i8042.noaux i8042.nomux i8042.nopnp i8042.dumbkbd root=/dev/vda3" \
--cpus 4 \ --cpus boot=4 \
--memory size=1024M \ --memory size=1024M \
--net "tap=,mac=,ip=,mask=" \ --net "tap=,mac=,ip=,mask=" \
--rng --rng
``` ```
# 3. Status # 3. Status
`cloud-hypervisor` is in a very early, pre-alpha stage. Use at your own risk! `cloud-hypervisor` is in a very early, pre-alpha stage. Use at your own risk!
As of 2019/05/12, booting cloud images has only been tested with [Clear Linux images](https://download.clearlinux.org/current/). As of 2019-12-12, the following cloud images are supported:
Direct kernel boot to userspace should work with most rootfs and it's been tested with * [Clear Linux](https://download.clearlinux.org/current/) (cloudguest and kvm)
Clear Linux root partitions, and also basic initrd/initramfs images. * [Ubuntu Bionic](https://cloud-images.ubuntu.com/bionic/current/) (cloudimg)
* [Ubuntu Eoan](https://cloud-images.ubuntu.com/eoan/current/) (cloudimg)
Direct kernel boot to userspace should work with most rootfs.
## Hot Plug
This [document](https://github.com/cloud-hypervisor/cloud-hypervisor/blob/master/docs/hotplug.md) details how to add devices to
a running VM. Currently only CPU hot plug is supported.
## Device Model
Follow this [documentation](https://github.com/cloud-hypervisor/cloud-hypervisor/blob/master/docs/device_model.md).
## TODO ## TODO
We are not tracking the `cloud-hypervisor` TODO list from a specific git tracked file but through We are not tracking the `cloud-hypervisor` TODO list from a specific git tracked file but through
[github issues](https://github.com/intel/cloud-hypervisor/issues/new) instead. [github issues](https://github.com/cloud-hypervisor/cloud-hypervisor/issues/new) instead.
# 4. `rust-vmm` project dependency # 4. `rust-vmm` project dependency

1988
acpi_tables/src/aml.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -3,6 +3,7 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// //
pub mod aml;
pub mod rsdp; pub mod rsdp;
pub mod sdt; pub mod sdt;

View File

@@ -36,7 +36,7 @@ impl RSDP {
}; };
rsdp.checksum = super::generate_checksum(&rsdp.as_slice()[0..19]); rsdp.checksum = super::generate_checksum(&rsdp.as_slice()[0..19]);
rsdp.extended_checksum = super::generate_checksum(&rsdp.as_slice());; rsdp.extended_checksum = super::generate_checksum(&rsdp.as_slice());
rsdp rsdp
} }

View File

@@ -77,6 +77,14 @@ impl SDT {
self.write(orig_length, value); self.write(orig_length, value);
} }
pub fn append_slice(&mut self, data: &[u8]) {
let orig_length = self.data.len();
let new_length = orig_length + data.len();
self.write_u32(4, new_length as u32);
self.data.extend_from_slice(data);
self.update_checksum();
}
/// Write a value at the given offset /// Write a value at the given offset
pub fn write<T>(&mut self, offset: usize, value: T) { pub fn write<T>(&mut self, offset: usize, value: T) {
assert!((offset + (std::mem::size_of::<T>() - 1)) < self.data.len()); assert!((offset + (std::mem::size_of::<T>() - 1)) < self.data.len());

View File

@@ -5,13 +5,12 @@ authors = ["The Chromium OS Authors"]
[features] [features]
default = [] default = []
acpi = ["acpi_tables"]
[dependencies] [dependencies]
byteorder = "1.3.2" byteorder = "1.3.4"
kvm-bindings = "0.1.1" kvm-bindings = "0.2.0"
kvm-ioctls = { git = "https://github.com/rust-vmm/kvm-ioctls", branch = "master" } kvm-ioctls = "0.5.0"
libc = "0.2.60" libc = "0.2.66"
acpi_tables = { path = "../acpi_tables", optional = true } acpi_tables = { path = "../acpi_tables", optional = true }
arch_gen = { path = "../arch_gen" } arch_gen = { path = "../arch_gen" }
@@ -25,4 +24,4 @@ git = "https://github.com/rust-vmm/linux-loader"
features = ["elf", "bzimage"] features = ["elf", "bzimage"]
[dev-dependencies] [dev-dependencies]
rand = "0.7.0" rand = "0.7.3"

View File

@@ -16,8 +16,7 @@ pub fn configure_system(
_cmdline_addr: GuestAddress, _cmdline_addr: GuestAddress,
_cmdline_size: usize, _cmdline_size: usize,
_num_cpus: u8, _num_cpus: u8,
_serial_enabled: bool, _rsdp_addr: Option<GuestAddress>,
_virt_iommu: Option<(u32, &[u32])>,
) -> super::Result<()> { ) -> super::Result<()> {
Ok(()) Ok(())
} }

View File

@@ -22,7 +22,7 @@ extern crate vm_memory;
use std::result; use std::result;
#[derive(Debug, PartialEq)] #[derive(Debug)]
pub enum Error { pub enum Error {
#[cfg(target_arch = "x86_64")] #[cfg(target_arch = "x86_64")]
/// X86_64 specific error triggered during system configuration. /// X86_64 specific error triggered during system configuration.
@@ -30,7 +30,7 @@ pub enum Error {
/// The zero page extends past the end of guest_mem. /// The zero page extends past the end of guest_mem.
ZeroPagePastRamEnd, ZeroPagePastRamEnd,
/// Error writing the zero page of guest memory. /// Error writing the zero page of guest memory.
ZeroPageSetup, ZeroPageSetup(vm_memory::GuestMemoryError),
} }
pub type Result<T> = result::Result<T, Error>; pub type Result<T> = result::Result<T, Error>;

View File

@@ -1,484 +0,0 @@
// Copyright © 2019 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
use acpi_tables::{
rsdp::RSDP,
sdt::{GenericAddress, SDT},
};
use vm_memory::{GuestAddress, GuestMemoryMmap};
use vm_memory::{Address, ByteValued, Bytes};
use std::convert::TryInto;
use super::layout;
#[repr(packed)]
struct LocalAPIC {
pub r#type: u8,
pub length: u8,
pub processor_id: u8,
pub apic_id: u8,
pub flags: u32,
}
#[repr(packed)]
#[derive(Default)]
struct IOAPIC {
pub r#type: u8,
pub length: u8,
pub ioapic_id: u8,
_reserved: u8,
pub apic_address: u32,
pub gsi_base: u32,
}
#[repr(packed)]
#[derive(Default)]
struct InterruptSourceOverride {
pub r#type: u8,
pub length: u8,
pub bus: u8,
pub source: u8,
pub gsi: u32,
pub flags: u16,
}
#[repr(packed)]
#[derive(Default)]
struct PCIRangeEntry {
pub base_address: u64,
pub segment: u16,
pub start: u8,
pub end: u8,
_reserved: u32,
}
#[repr(packed)]
#[derive(Default)]
struct IortParavirtIommuNode {
pub type_: u8,
pub length: u16,
pub revision: u8,
_reserved1: u32,
pub num_id_mappings: u32,
pub ref_id_mappings: u32,
pub device_id: u32,
_reserved2: [u32; 3],
pub model: u32,
pub flags: u32,
_reserved3: [u32; 4],
}
#[repr(packed)]
#[derive(Default)]
struct IortPciRootComplexNode {
pub type_: u8,
pub length: u16,
pub revision: u8,
_reserved1: u32,
pub num_id_mappings: u32,
pub ref_id_mappings: u32,
pub mem_access_props: IortMemoryAccessProperties,
pub ats_attr: u32,
pub pci_seg_num: u32,
pub mem_addr_size_limit: u8,
_reserved2: [u8; 3],
}
#[repr(packed)]
#[derive(Default)]
struct IortMemoryAccessProperties {
pub cca: u32,
pub ah: u8,
_reserved: u16,
pub maf: u8,
}
#[repr(packed)]
#[derive(Default)]
struct IortIdMapping {
pub input_base: u32,
pub num_of_ids: u32,
pub ouput_base: u32,
pub output_ref: u32,
pub flags: u32,
}
pub fn create_dsdt_table(
serial_enabled: bool,
start_of_device_area: GuestAddress,
end_of_device_area: GuestAddress,
) -> SDT {
/*
The hex tables in this file are generated from the ASL below with:
"iasl -tc <dsdt.asl>"
As the output contains a table header that is not required the first 36 bytes
should be disregarded.
*/
/*
Device (_SB.PCI0)
{
Name (_HID, EisaId ("PNP0A08") /* PCI Express Bus */) // _HID: Hardware ID
Name (_CID, EisaId ("PNP0A03") /* PCI Bus */) // _CID: Compatible ID
Name (_ADR, Zero) // _ADR: Address
Name (_SEG, Zero) // _SEG: PCI Segment
Name (_UID, Zero) // _UID: Unique ID
Name (SUPP, Zero)
}
Scope (_SB.PCI0)
{
Name (_CRS, ResourceTemplate () // _CRS: Current Resource Settings
{
WordBusNumber (ResourceProducer, MinFixed, MaxFixed, PosDecode,
0x0000, // Granularity
0x0000, // Range Minimum
0x00FF, // Range Maximum
0x0000, // Translation Offset
0x0100, // Length
,, )
IO (Decode16,
0x0CF8, // Range Minimum
0x0CF8, // Range Maximum
0x01, // Alignment
0x08, // Length
)
WordIO (ResourceProducer, MinFixed, MaxFixed, PosDecode, EntireRange,
0x0000, // Granularity
0x0000, // Range Minimum
0x0CF7, // Range Maximum
0x0000, // Translation Offset
0x0CF8, // Length
,, , TypeStatic, DenseTranslation)
WordIO (ResourceProducer, MinFixed, MaxFixed, PosDecode, EntireRange,
0x0000, // Granularity
0x0D00, // Range Minimum
0xFFFF, // Range Maximum
0x0000, // Translation Offset
0xF300, // Length
,, , TypeStatic, DenseTranslation)
DWordMemory (ResourceProducer, PosDecode, MinFixed, MaxFixed, Cacheable, ReadWrite,
0x00000000, // Granularity
0x000A0000, // Range Minimum
0x000BFFFF, // Range Maximum
0x00000000, // Translation Offset
0x00020000, // Length
,, , AddressRangeMemory, TypeStatic)
DWordMemory (ResourceProducer, PosDecode, MinFixed, MaxFixed, NonCacheable, ReadWrite,
0x00000000, // Granularity
0xC0000000, // Range Minimum
0xFEBFFFFF, // Range Maximum
0x00000000, // Translation Offset
0x3EC00000, // Length
,, , AddressRangeMemory, TypeStatic)
QWordMemory (ResourceProducer, PosDecode, MinFixed, MaxFixed, Cacheable, ReadWrite,
0x0000000000000000, // Granularity
0x0000000800000000, // Range Minimum
0x0000000FFFFFFFFF, // Range Maximum
0x0000000000000000, // Translation Offset
0x0000000800000000, // Length
,, , AddressRangeMemory, TypeStatic)
})
}
*/
let mut pci_dsdt_data = [
0x5Bu8, 0x82, 0x36, 0x2E, 0x5F, 0x53, 0x42, 0x5F, 0x50, 0x43, 0x49, 0x30, 0x08, 0x5F, 0x48,
0x49, 0x44, 0x0C, 0x41, 0xD0, 0x0A, 0x08, 0x08, 0x5F, 0x43, 0x49, 0x44, 0x0C, 0x41, 0xD0,
0x0A, 0x03, 0x08, 0x5F, 0x41, 0x44, 0x52, 0x00, 0x08, 0x5F, 0x53, 0x45, 0x47, 0x00, 0x08,
0x5F, 0x55, 0x49, 0x44, 0x00, 0x08, 0x53, 0x55, 0x50, 0x50, 0x00, 0x10, 0x41, 0x0B, 0x2E,
0x5F, 0x53, 0x42, 0x5F, 0x50, 0x43, 0x49, 0x30, 0x08, 0x5F, 0x43, 0x52, 0x53, 0x11, 0x40,
0x0A, 0x0A, 0x9C, 0x88, 0x0D, 0x00, 0x02, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00,
0x00, 0x00, 0x00, 0x01, 0x47, 0x01, 0xF8, 0x0C, 0xF8, 0x0C, 0x01, 0x08, 0x88, 0x0D, 0x00,
0x01, 0x0C, 0x03, 0x00, 0x00, 0x00, 0x00, 0xF7, 0x0C, 0x00, 0x00, 0xF8, 0x0C, 0x88, 0x0D,
0x00, 0x01, 0x0C, 0x03, 0x00, 0x00, 0x00, 0x0D, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xF3, 0x87,
0x17, 0x00, 0x00, 0x0C, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0xFF, 0xFF,
0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x87, 0x17, 0x00, 0x00, 0x0C,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xFF, 0xFF, 0xBF, 0xFE, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xC0, 0x3E, 0x8A, 0x2B, 0x00, 0x00, 0x0C, 0x03, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0xFF, 0xFF,
0xFF, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x79, 0x00,
];
// Patch Range Minimum/Range Maximum/Length for the the 64-bit device area
pci_dsdt_data[170..174].copy_from_slice(&layout::MEM_32BIT_DEVICES_START.0.to_le_bytes()[0..4]);
pci_dsdt_data[174..178].copy_from_slice(
&(layout::MEM_32BIT_DEVICES_START.0 + layout::MEM_32BIT_DEVICES_SIZE - 1).to_le_bytes()
[0..4],
);
pci_dsdt_data[182..186].copy_from_slice(&layout::MEM_32BIT_DEVICES_SIZE.to_le_bytes()[0..4]);
// Patch the Range Minimum/Range Maximum/Length for the the 64-bit device area
pci_dsdt_data[200..208].copy_from_slice(&(start_of_device_area.0).to_le_bytes());
pci_dsdt_data[208..216].copy_from_slice(&end_of_device_area.0.to_le_bytes());
pci_dsdt_data[224..232].copy_from_slice(
&(end_of_device_area.unchecked_offset_from(start_of_device_area) + 1).to_le_bytes(),
);
/*
Device (_SB.MBRD)
{
Name (_HID, EisaId ("PNP0C02") /* PNP Motherboard Resources */) // _HID: Hardware ID
Name (_UID, Zero) // _UID: Unique ID
}
Scope (_SB.MBRD)
{
Name (_CRS, ResourceTemplate () // _CRS: Current Resource Settings
{
Memory32Fixed (ReadWrite,
0xE8000000, // Address Base
0x10000000, // Address Length
)
})
}
*/
let mut mbrd_dsdt_data = [
0x5Bu8, 0x82, 0x1A, 0x2E, 0x5F, 0x53, 0x42, 0x5F, 0x4D, 0x42, 0x52, 0x44, 0x08, 0x5F, 0x48,
0x49, 0x44, 0x0C, 0x41, 0xD0, 0x0C, 0x02, 0x08, 0x5F, 0x55, 0x49, 0x44, 0x00, 0x10, 0x21,
0x2E, 0x5F, 0x53, 0x42, 0x5F, 0x4D, 0x42, 0x52, 0x44, 0x08, 0x5F, 0x43, 0x52, 0x53, 0x11,
0x11, 0x0A, 0x0E, 0x86, 0x09, 0x00, 0x01, 0x00, 0x00, 0x00, 0xE8, 0x00, 0x00, 0x00, 0x10,
0x79, 0x00,
];
mbrd_dsdt_data[52..56].copy_from_slice(&layout::PCI_MMCONFIG_START.0.to_le_bytes()[0..4]);
mbrd_dsdt_data[56..60].copy_from_slice(&layout::PCI_MMCONFIG_SIZE.to_le_bytes()[0..4]);
/*
Device (_SB.COM1)
{
Name (_HID, EisaId ("PNP0501") /* 16550A-compatible COM Serial Port */) // _HID: Hardware ID
Name (_UID, Zero) // _UID: Unique ID
Name (_CRS, ResourceTemplate () // _CRS: Current Resource Settings
{
Interrupt (ResourceConsumer, Edge, ActiveHigh, Exclusive, ,, )
{
0x00000004,
}
IO (Decode16,
0x03F8, // Range Minimum
0x03F8, // Range Maximum
0x00, // Alignment
0x08, // Length
)
})
}
*/
let com1_dsdt_data = [
0x5Bu8, 0x82, 0x36, 0x2E, 0x5F, 0x53, 0x42, 0x5F, 0x43, 0x4F, 0x4D, 0x31, 0x08, 0x5F, 0x48,
0x49, 0x44, 0x0C, 0x41, 0xD0, 0x05, 0x01, 0x08, 0x5F, 0x55, 0x49, 0x44, 0x00, 0x08, 0x5F,
0x43, 0x52, 0x53, 0x11, 0x16, 0x0A, 0x13, 0x89, 0x06, 0x00, 0x03, 0x01, 0x04, 0x00, 0x00,
0x00, 0x47, 0x01, 0xF8, 0x03, 0xF8, 0x03, 0x00, 0x08, 0x79, 0x00,
];
/*
Name (\_S5, Package (0x01) // _S5_: S5 System State
{
0x05
})
*/
let s5_sleep_data = [0x08u8, 0x5F, 0x53, 0x35, 0x5F, 0x12, 0x04, 0x01, 0x0A, 0x05];
// DSDT
let mut dsdt = SDT::new(*b"DSDT", 36, 6, *b"CLOUDH", *b"CHDSDT ", 1);
dsdt.append(pci_dsdt_data);
dsdt.append(mbrd_dsdt_data);
if serial_enabled {
dsdt.append(com1_dsdt_data);
}
dsdt.append(s5_sleep_data);
dsdt
}
pub fn create_acpi_tables(
guest_mem: &GuestMemoryMmap,
num_cpus: u8,
serial_enabled: bool,
start_of_device_area: GuestAddress,
end_of_device_area: GuestAddress,
virt_iommu: Option<(u32, &[u32])>,
) -> GuestAddress {
// RSDP is at the EBDA
let rsdp_offset = layout::RSDP_POINTER;
let mut tables: Vec<u64> = Vec::new();
// DSDT
let dsdt = create_dsdt_table(serial_enabled, start_of_device_area, end_of_device_area);
let dsdt_offset = rsdp_offset.checked_add(RSDP::len() as u64).unwrap();
guest_mem
.write_slice(dsdt.as_slice(), dsdt_offset)
.expect("Error writing DSDT table");
// FACP aka FADT
// Revision 6 of the ACPI FADT table is 276 bytes long
let mut facp = SDT::new(*b"FACP", 276, 6, *b"CLOUDH", *b"CHFACP ", 1);
// HW_REDUCED_ACPI and RESET_REG_SUP
let fadt_flags: u32 = 1 << 20 | 1 << 10;
facp.write(112, fadt_flags);
// RESET_REG
facp.write(116, GenericAddress::io_port_address(0x3c0));
// RESET_VALUE
facp.write(128, 1u8);
facp.write(131, 3u8); // FADT minor version
facp.write(140, dsdt_offset.0); // X_DSDT
// SLEEP_CONTROL_REG
facp.write(244, GenericAddress::io_port_address(0x3c0));
// SLEEP_STATUS_REG
facp.write(256, GenericAddress::io_port_address(0x3c0));
facp.write(268, b"CLOUDHYP"); // Hypervisor Vendor Identity
facp.update_checksum();
let facp_offset = dsdt_offset.checked_add(dsdt.len() as u64).unwrap();
guest_mem
.write_slice(facp.as_slice(), facp_offset)
.expect("Error writing FACP table");
tables.push(facp_offset.0);
// MADT
let mut madt = SDT::new(*b"APIC", 44, 5, *b"CLOUDH", *b"CHMADT ", 1);
madt.write(36, layout::APIC_START);
for cpu in 0..num_cpus {
let lapic = LocalAPIC {
r#type: 0,
length: 8,
processor_id: cpu,
apic_id: cpu,
flags: 1,
};
madt.append(lapic);
}
madt.append(IOAPIC {
r#type: 1,
length: 12,
ioapic_id: 0,
apic_address: layout::IOAPIC_START.0 as u32,
gsi_base: 0,
..Default::default()
});
madt.append(InterruptSourceOverride {
r#type: 2,
length: 10,
bus: 0,
source: 4,
gsi: 4,
flags: 0,
});
let madt_offset = facp_offset.checked_add(facp.len() as u64).unwrap();
guest_mem
.write_slice(madt.as_slice(), madt_offset)
.expect("Error writing MADT table");
tables.push(madt_offset.0);
// MCFG
let mut mcfg = SDT::new(*b"MCFG", 36, 1, *b"CLOUDH", *b"CHMCFG ", 1);
// MCFG reserved 8 bytes
mcfg.append(0u64);
// 32-bit PCI enhanced configuration mechanism
mcfg.append(PCIRangeEntry {
base_address: layout::PCI_MMCONFIG_START.0,
segment: 0,
start: 0,
end: 0xff,
..Default::default()
});
let mcfg_offset = madt_offset.checked_add(madt.len() as u64).unwrap();
guest_mem
.write_slice(mcfg.as_slice(), mcfg_offset)
.expect("Error writing MCFG table");
tables.push(mcfg_offset.0);
let (prev_tbl_len, prev_tbl_off) = if let Some((iommu_id, dev_ids)) = &virt_iommu {
// IORT
let mut iort = SDT::new(*b"IORT", 36, 1, *b"CLOUDH", *b"CHIORT ", 1);
// IORT number of nodes
iort.append(2u32);
// IORT offset to array of IORT nodes
iort.append(48u32);
// IORT reserved 4 bytes
iort.append(0u32);
// IORT paravirtualized IOMMU node
iort.append(IortParavirtIommuNode {
type_: 128,
length: 56,
revision: 0,
num_id_mappings: 0,
ref_id_mappings: 56,
device_id: *iommu_id,
model: 1,
..Default::default()
});
let num_entries = dev_ids.len();
let length: u16 = (36 + (20 * num_entries)).try_into().unwrap();
// IORT PCI root complex node
iort.append(IortPciRootComplexNode {
type_: 2,
length,
revision: 0,
num_id_mappings: num_entries as u32,
ref_id_mappings: 36,
ats_attr: 0,
pci_seg_num: 0,
mem_addr_size_limit: 255,
..Default::default()
});
for dev_id in dev_ids.iter() {
// IORT ID mapping
iort.append(IortIdMapping {
input_base: *dev_id,
num_of_ids: 1,
ouput_base: *dev_id,
output_ref: 48,
flags: 0,
});
}
let iort_offset = mcfg_offset.checked_add(mcfg.len() as u64).unwrap();
guest_mem
.write_slice(iort.as_slice(), iort_offset)
.expect("Error writing IORT table");
tables.push(iort_offset.0);
(iort.len(), iort_offset)
} else {
(mcfg.len(), mcfg_offset)
};
// XSDT
let mut xsdt = SDT::new(*b"XSDT", 36, 1, *b"CLOUDH", *b"CHXSDT ", 1);
for table in tables {
xsdt.append(table);
}
xsdt.update_checksum();
let xsdt_offset = prev_tbl_off.checked_add(prev_tbl_len as u64).unwrap();
guest_mem
.write_slice(xsdt.as_slice(), xsdt_offset)
.expect("Error writing XSDT table");
// RSDP
let rsdp = RSDP::new(*b"CLOUDH", xsdt_offset.0);
guest_mem
.write_slice(rsdp.as_slice(), rsdp_offset)
.expect("Error writing RSDP");
rsdp_offset
}

View File

@@ -5,7 +5,7 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE-BSD-3-Clause file. // found in the LICENSE-BSD-3-Clause file.
use std::io::{self, Cursor}; use std::io::Cursor;
use std::mem; use std::mem;
use std::result; use std::result;
@@ -16,8 +16,8 @@ use kvm_ioctls;
#[derive(Debug)] #[derive(Debug)]
pub enum Error { pub enum Error {
GetLapic(io::Error), GetLapic(kvm_ioctls::Error),
SetLapic(io::Error), SetLapic(kvm_ioctls::Error),
} }
pub type Result<T> = result::Result<T, Error>; pub type Result<T> = result::Result<T, Error>;

View File

@@ -5,9 +5,6 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE-BSD-3-Clause file. // found in the LICENSE-BSD-3-Clause file.
#[cfg(feature = "acpi")]
mod acpi;
mod gdt; mod gdt;
pub mod interrupts; pub mod interrupts;
pub mod layout; pub mod layout;
@@ -35,7 +32,7 @@ struct BootParamsWrapper(boot_params);
// It is safe to initialize BootParamsWrap which is a wrapper over `boot_params` (a series of ints). // It is safe to initialize BootParamsWrap which is a wrapper over `boot_params` (a series of ints).
unsafe impl ByteValued for BootParamsWrapper {} unsafe impl ByteValued for BootParamsWrapper {}
#[derive(Debug, PartialEq)] #[derive(Debug)]
pub enum Error { pub enum Error {
/// Invalid e820 setup params. /// Invalid e820 setup params.
E820Configuration, E820Configuration,
@@ -111,9 +108,7 @@ pub fn configure_system(
cmdline_size: usize, cmdline_size: usize,
num_cpus: u8, num_cpus: u8,
setup_hdr: Option<setup_header>, setup_hdr: Option<setup_header>,
_serial_enabled: bool, rsdp_addr: Option<GuestAddress>,
_end_of_range: GuestAddress,
_virt_iommu: Option<(u32, &[u32])>,
) -> super::Result<()> { ) -> super::Result<()> {
const KERNEL_BOOT_FLAG_MAGIC: u16 = 0xaa55; const KERNEL_BOOT_FLAG_MAGIC: u16 = 0xaa55;
const KERNEL_HDR_MAGIC: u32 = 0x53726448; const KERNEL_HDR_MAGIC: u32 = 0x53726448;
@@ -140,7 +135,7 @@ pub fn configure_system(
add_e820_entry(&mut params.0, 0, layout::EBDA_START.raw_value(), E820_RAM)?; add_e820_entry(&mut params.0, 0, layout::EBDA_START.raw_value(), E820_RAM)?;
let mem_end = guest_mem.end_addr(); let mem_end = guest_mem.last_addr();
if mem_end < layout::MEM_32BIT_RESERVED_START { if mem_end < layout::MEM_32BIT_RESERVED_START {
add_e820_entry( add_e820_entry(
&mut params.0, &mut params.0,
@@ -172,21 +167,7 @@ pub fn configure_system(
E820_RESERVED, E820_RESERVED,
)?; )?;
#[cfg(feature = "acpi")] if let Some(rsdp_addr) = rsdp_addr {
{
let start_of_device_area = if mem_end < layout::MEM_32BIT_RESERVED_START {
layout::RAM_64BIT_START
} else {
guest_mem.end_addr().unchecked_add(1)
};
let rsdp_addr = acpi::create_acpi_tables(
guest_mem,
num_cpus,
_serial_enabled,
start_of_device_area,
_end_of_range,
_virt_iommu,
);
params.0.acpi_rsdp_addr = rsdp_addr.0; params.0.acpi_rsdp_addr = rsdp_addr.0;
} }
@@ -196,7 +177,7 @@ pub fn configure_system(
.ok_or(super::Error::ZeroPagePastRamEnd)?; .ok_or(super::Error::ZeroPagePastRamEnd)?;
guest_mem guest_mem
.write_obj(params, zero_page_addr) .write_obj(params, zero_page_addr)
.map_err(|_| super::Error::ZeroPageSetup)?; .map_err(super::Error::ZeroPageSetup)?;
Ok(()) Ok(())
} }
@@ -245,24 +226,9 @@ mod tests {
#[test] #[test]
fn test_system_configuration() { fn test_system_configuration() {
let no_vcpus = 4; let no_vcpus = 4;
let gm = GuestMemoryMmap::new(&vec![(GuestAddress(0), 0x10000)]).unwrap(); let gm = GuestMemoryMmap::from_ranges(&vec![(GuestAddress(0), 0x10000)]).unwrap();
let config_err = configure_system( let config_err = configure_system(&gm, GuestAddress(0), 0, 1, None, None);
&gm,
GuestAddress(0),
0,
1,
None,
false,
GuestAddress((1 << 36) - 1),
None,
);
assert!(config_err.is_err()); assert!(config_err.is_err());
assert_eq!(
config_err.unwrap_err(),
super::super::Error::X86_64Setup(super::Error::MpTableSetup(
mptable::Error::NotEnoughMemory
))
);
// Now assigning some memory that falls before the 32bit memory hole. // Now assigning some memory that falls before the 32bit memory hole.
let mem_size = 128 << 20; let mem_size = 128 << 20;
@@ -272,18 +238,8 @@ mod tests {
.filter(|r| r.2 == RegionType::Ram) .filter(|r| r.2 == RegionType::Ram)
.map(|r| (r.0, r.1)) .map(|r| (r.0, r.1))
.collect(); .collect();
let gm = GuestMemoryMmap::new(&ram_regions).unwrap(); let gm = GuestMemoryMmap::from_ranges(&ram_regions).unwrap();
configure_system( configure_system(&gm, GuestAddress(0), 0, no_vcpus, None, None).unwrap();
&gm,
GuestAddress(0),
0,
no_vcpus,
None,
false,
GuestAddress((1 << 36) - 1),
None,
)
.unwrap();
// Now assigning some memory that is equal to the start of the 32bit memory hole. // Now assigning some memory that is equal to the start of the 32bit memory hole.
let mem_size = 3328 << 20; let mem_size = 3328 << 20;
@@ -293,18 +249,8 @@ mod tests {
.filter(|r| r.2 == RegionType::Ram) .filter(|r| r.2 == RegionType::Ram)
.map(|r| (r.0, r.1)) .map(|r| (r.0, r.1))
.collect(); .collect();
let gm = GuestMemoryMmap::new(&ram_regions).unwrap(); let gm = GuestMemoryMmap::from_ranges(&ram_regions).unwrap();
configure_system( configure_system(&gm, GuestAddress(0), 0, no_vcpus, None, None).unwrap();
&gm,
GuestAddress(0),
0,
no_vcpus,
None,
false,
GuestAddress((1 << 36) - 1),
None,
)
.unwrap();
// Now assigning some memory that falls after the 32bit memory hole. // Now assigning some memory that falls after the 32bit memory hole.
let mem_size = 3330 << 20; let mem_size = 3330 << 20;
@@ -314,18 +260,8 @@ mod tests {
.filter(|r| r.2 == RegionType::Ram) .filter(|r| r.2 == RegionType::Ram)
.map(|r| (r.0, r.1)) .map(|r| (r.0, r.1))
.collect(); .collect();
let gm = GuestMemoryMmap::new(&ram_regions).unwrap(); let gm = GuestMemoryMmap::from_ranges(&ram_regions).unwrap();
configure_system( configure_system(&gm, GuestAddress(0), 0, no_vcpus, None, None).unwrap();
&gm,
GuestAddress(0),
0,
no_vcpus,
None,
false,
GuestAddress((1 << 36) - 1),
None,
)
.unwrap();
} }
#[test] #[test]

View File

@@ -14,7 +14,7 @@ use libc::c_char;
use arch_gen::x86::mpspec; use arch_gen::x86::mpspec;
use layout::{APIC_START, IOAPIC_START, MPTABLE_START}; use layout::{APIC_START, IOAPIC_START, MPTABLE_START};
use vm_memory::{Address, ByteValued, Bytes, GuestMemory, GuestMemoryMmap}; use vm_memory::{Address, ByteValued, Bytes, GuestMemory, GuestMemoryError, GuestMemoryMmap};
// This is a workaround to the Rust enforcement specifying that any implementation of a foreign // This is a workaround to the Rust enforcement specifying that any implementation of a foreign
// trait (in this case `ByteValued`) where: // trait (in this case `ByteValued`) where:
@@ -45,30 +45,30 @@ unsafe impl ByteValued for MpcTableWrapper {}
unsafe impl ByteValued for MpcLintsrcWrapper {} unsafe impl ByteValued for MpcLintsrcWrapper {}
unsafe impl ByteValued for MpfIntelWrapper {} unsafe impl ByteValued for MpfIntelWrapper {}
#[derive(Debug, PartialEq)] #[derive(Debug)]
pub enum Error { pub enum Error {
/// There was too little guest memory to store the entire MP table. /// There was too little guest memory to store the entire MP table.
NotEnoughMemory, NotEnoughMemory,
/// The MP table has too little address space to be stored. /// The MP table has too little address space to be stored.
AddressOverflow, AddressOverflow,
/// Failure while zeroing out the memory for the MP table. /// Failure while zeroing out the memory for the MP table.
Clear, Clear(GuestMemoryError),
/// Number of CPUs exceeds the maximum supported CPUs /// Number of CPUs exceeds the maximum supported CPUs
TooManyCpus, TooManyCpus,
/// Failure to write the MP floating pointer. /// Failure to write the MP floating pointer.
WriteMpfIntel, WriteMpfIntel(GuestMemoryError),
/// Failure to write MP CPU entry. /// Failure to write MP CPU entry.
WriteMpcCpu, WriteMpcCpu(GuestMemoryError),
/// Failure to write MP ioapic entry. /// Failure to write MP ioapic entry.
WriteMpcIoapic, WriteMpcIoapic(GuestMemoryError),
/// Failure to write MP bus entry. /// Failure to write MP bus entry.
WriteMpcBus, WriteMpcBus(GuestMemoryError),
/// Failure to write MP interrupt source entry. /// Failure to write MP interrupt source entry.
WriteMpcIntsrc, WriteMpcIntsrc(GuestMemoryError),
/// Failure to write MP local interrupt source entry. /// Failure to write MP local interrupt source entry.
WriteMpcLintsrc, WriteMpcLintsrc(GuestMemoryError),
/// Failure to write MP table header. /// Failure to write MP table header.
WriteMpcTable, WriteMpcTable(GuestMemoryError),
} }
pub type Result<T> = result::Result<T, Error>; pub type Result<T> = result::Result<T, Error>;
@@ -145,7 +145,7 @@ pub fn setup_mptable(mem: &GuestMemoryMmap, num_cpus: u8) -> Result<()> {
} }
mem.read_exact_from(base_mp, &mut io::repeat(0), mp_size) mem.read_exact_from(base_mp, &mut io::repeat(0), mp_size)
.map_err(|_| Error::Clear)?; .map_err(Error::Clear)?;
{ {
let mut mpf_intel = MpfIntelWrapper(mpspec::mpf_intel::default()); let mut mpf_intel = MpfIntelWrapper(mpspec::mpf_intel::default());
@@ -156,7 +156,7 @@ pub fn setup_mptable(mem: &GuestMemoryMmap, num_cpus: u8) -> Result<()> {
mpf_intel.0.physptr = (base_mp.raw_value() + size) as u32; mpf_intel.0.physptr = (base_mp.raw_value() + size) as u32;
mpf_intel.0.checksum = mpf_intel_compute_checksum(&mpf_intel.0); mpf_intel.0.checksum = mpf_intel_compute_checksum(&mpf_intel.0);
mem.write_obj(mpf_intel, base_mp) mem.write_obj(mpf_intel, base_mp)
.map_err(|_| Error::WriteMpfIntel)?; .map_err(Error::WriteMpfIntel)?;
base_mp = base_mp.unchecked_add(size); base_mp = base_mp.unchecked_add(size);
} }
@@ -181,7 +181,7 @@ pub fn setup_mptable(mem: &GuestMemoryMmap, num_cpus: u8) -> Result<()> {
mpc_cpu.0.cpufeature = CPU_STEPPING; mpc_cpu.0.cpufeature = CPU_STEPPING;
mpc_cpu.0.featureflag = CPU_FEATURE_APIC | CPU_FEATURE_FPU; mpc_cpu.0.featureflag = CPU_FEATURE_APIC | CPU_FEATURE_FPU;
mem.write_obj(mpc_cpu, base_mp) mem.write_obj(mpc_cpu, base_mp)
.map_err(|_| Error::WriteMpcCpu)?; .map_err(Error::WriteMpcCpu)?;
base_mp = base_mp.unchecked_add(size as u64); base_mp = base_mp.unchecked_add(size as u64);
checksum = checksum.wrapping_add(compute_checksum(&mpc_cpu.0)); checksum = checksum.wrapping_add(compute_checksum(&mpc_cpu.0));
} }
@@ -193,7 +193,7 @@ pub fn setup_mptable(mem: &GuestMemoryMmap, num_cpus: u8) -> Result<()> {
mpc_bus.0.busid = 0; mpc_bus.0.busid = 0;
mpc_bus.0.bustype = BUS_TYPE_ISA; mpc_bus.0.bustype = BUS_TYPE_ISA;
mem.write_obj(mpc_bus, base_mp) mem.write_obj(mpc_bus, base_mp)
.map_err(|_| Error::WriteMpcBus)?; .map_err(Error::WriteMpcBus)?;
base_mp = base_mp.unchecked_add(size as u64); base_mp = base_mp.unchecked_add(size as u64);
checksum = checksum.wrapping_add(compute_checksum(&mpc_bus.0)); checksum = checksum.wrapping_add(compute_checksum(&mpc_bus.0));
} }
@@ -206,7 +206,7 @@ pub fn setup_mptable(mem: &GuestMemoryMmap, num_cpus: u8) -> Result<()> {
mpc_ioapic.0.flags = mpspec::MPC_APIC_USABLE as u8; mpc_ioapic.0.flags = mpspec::MPC_APIC_USABLE as u8;
mpc_ioapic.0.apicaddr = IOAPIC_START.0 as u32; mpc_ioapic.0.apicaddr = IOAPIC_START.0 as u32;
mem.write_obj(mpc_ioapic, base_mp) mem.write_obj(mpc_ioapic, base_mp)
.map_err(|_| Error::WriteMpcIoapic)?; .map_err(Error::WriteMpcIoapic)?;
base_mp = base_mp.unchecked_add(size as u64); base_mp = base_mp.unchecked_add(size as u64);
checksum = checksum.wrapping_add(compute_checksum(&mpc_ioapic.0)); checksum = checksum.wrapping_add(compute_checksum(&mpc_ioapic.0));
} }
@@ -222,7 +222,7 @@ pub fn setup_mptable(mem: &GuestMemoryMmap, num_cpus: u8) -> Result<()> {
mpc_intsrc.0.dstapic = ioapicid; mpc_intsrc.0.dstapic = ioapicid;
mpc_intsrc.0.dstirq = i; mpc_intsrc.0.dstirq = i;
mem.write_obj(mpc_intsrc, base_mp) mem.write_obj(mpc_intsrc, base_mp)
.map_err(|_| Error::WriteMpcIntsrc)?; .map_err(Error::WriteMpcIntsrc)?;
base_mp = base_mp.unchecked_add(size as u64); base_mp = base_mp.unchecked_add(size as u64);
checksum = checksum.wrapping_add(compute_checksum(&mpc_intsrc.0)); checksum = checksum.wrapping_add(compute_checksum(&mpc_intsrc.0));
} }
@@ -237,7 +237,7 @@ pub fn setup_mptable(mem: &GuestMemoryMmap, num_cpus: u8) -> Result<()> {
mpc_lintsrc.0.destapic = 0; mpc_lintsrc.0.destapic = 0;
mpc_lintsrc.0.destapiclint = 0; mpc_lintsrc.0.destapiclint = 0;
mem.write_obj(mpc_lintsrc, base_mp) mem.write_obj(mpc_lintsrc, base_mp)
.map_err(|_| Error::WriteMpcLintsrc)?; .map_err(Error::WriteMpcLintsrc)?;
base_mp = base_mp.unchecked_add(size as u64); base_mp = base_mp.unchecked_add(size as u64);
checksum = checksum.wrapping_add(compute_checksum(&mpc_lintsrc.0)); checksum = checksum.wrapping_add(compute_checksum(&mpc_lintsrc.0));
} }
@@ -252,7 +252,7 @@ pub fn setup_mptable(mem: &GuestMemoryMmap, num_cpus: u8) -> Result<()> {
mpc_lintsrc.0.destapic = 0xFF; /* to all local APICs */ mpc_lintsrc.0.destapic = 0xFF; /* to all local APICs */
mpc_lintsrc.0.destapiclint = 1; mpc_lintsrc.0.destapiclint = 1;
mem.write_obj(mpc_lintsrc, base_mp) mem.write_obj(mpc_lintsrc, base_mp)
.map_err(|_| Error::WriteMpcLintsrc)?; .map_err(Error::WriteMpcLintsrc)?;
base_mp = base_mp.unchecked_add(size as u64); base_mp = base_mp.unchecked_add(size as u64);
checksum = checksum.wrapping_add(compute_checksum(&mpc_lintsrc.0)); checksum = checksum.wrapping_add(compute_checksum(&mpc_lintsrc.0));
} }
@@ -271,7 +271,7 @@ pub fn setup_mptable(mem: &GuestMemoryMmap, num_cpus: u8) -> Result<()> {
checksum = checksum.wrapping_add(compute_checksum(&mpc_table.0)); checksum = checksum.wrapping_add(compute_checksum(&mpc_table.0));
mpc_table.0.checksum = (!checksum).wrapping_add(1) as i8; mpc_table.0.checksum = (!checksum).wrapping_add(1) as i8;
mem.write_obj(mpc_table, table_base) mem.write_obj(mpc_table, table_base)
.map_err(|_| Error::WriteMpcTable)?; .map_err(Error::WriteMpcTable)?;
} }
Ok(()) Ok(())
@@ -296,7 +296,8 @@ mod tests {
#[test] #[test]
fn bounds_check() { fn bounds_check() {
let num_cpus = 4; let num_cpus = 4;
let mem = GuestMemoryMmap::new(&[(MPTABLE_START, compute_mp_size(num_cpus))]).unwrap(); let mem =
GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(num_cpus))]).unwrap();
setup_mptable(&mem, num_cpus).unwrap(); setup_mptable(&mem, num_cpus).unwrap();
} }
@@ -304,7 +305,8 @@ mod tests {
#[test] #[test]
fn bounds_check_fails() { fn bounds_check_fails() {
let num_cpus = 4; let num_cpus = 4;
let mem = GuestMemoryMmap::new(&[(MPTABLE_START, compute_mp_size(num_cpus) - 1)]).unwrap(); let mem = GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(num_cpus) - 1)])
.unwrap();
assert!(setup_mptable(&mem, num_cpus).is_err()); assert!(setup_mptable(&mem, num_cpus).is_err());
} }
@@ -312,7 +314,8 @@ mod tests {
#[test] #[test]
fn mpf_intel_checksum() { fn mpf_intel_checksum() {
let num_cpus = 1; let num_cpus = 1;
let mem = GuestMemoryMmap::new(&[(MPTABLE_START, compute_mp_size(num_cpus))]).unwrap(); let mem =
GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(num_cpus))]).unwrap();
setup_mptable(&mem, num_cpus).unwrap(); setup_mptable(&mem, num_cpus).unwrap();
@@ -327,7 +330,8 @@ mod tests {
#[test] #[test]
fn mpc_table_checksum() { fn mpc_table_checksum() {
let num_cpus = 4; let num_cpus = 4;
let mem = GuestMemoryMmap::new(&[(MPTABLE_START, compute_mp_size(num_cpus))]).unwrap(); let mem =
GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(num_cpus))]).unwrap();
setup_mptable(&mem, num_cpus).unwrap(); setup_mptable(&mem, num_cpus).unwrap();
@@ -356,9 +360,11 @@ mod tests {
#[test] #[test]
fn cpu_entry_count() { fn cpu_entry_count() {
let mem = let mem = GuestMemoryMmap::from_ranges(&[(
GuestMemoryMmap::new(&[(MPTABLE_START, compute_mp_size(MAX_SUPPORTED_CPUS as u8))]) MPTABLE_START,
.unwrap(); compute_mp_size(MAX_SUPPORTED_CPUS as u8),
)])
.unwrap();
for i in 0..MAX_SUPPORTED_CPUS as u8 { for i in 0..MAX_SUPPORTED_CPUS as u8 {
setup_mptable(&mem, i).unwrap(); setup_mptable(&mem, i).unwrap();
@@ -391,9 +397,10 @@ mod tests {
#[test] #[test]
fn cpu_entry_count_max() { fn cpu_entry_count_max() {
let cpus = MAX_SUPPORTED_CPUS + 1; let cpus = MAX_SUPPORTED_CPUS + 1;
let mem = GuestMemoryMmap::new(&[(MPTABLE_START, compute_mp_size(cpus as u8))]).unwrap(); let mem =
GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(cpus as u8))]).unwrap();
let result = setup_mptable(&mem, cpus as u8).unwrap_err(); let result = setup_mptable(&mem, cpus as u8);
assert_eq!(result, Error::TooManyCpus); assert!(result.is_err());
} }
} }

View File

@@ -5,14 +5,14 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE-BSD-3-Clause file. // found in the LICENSE-BSD-3-Clause file.
use std::{io, mem, result}; use std::{mem, result};
use super::gdt::{gdt_entry, kvm_segment_from_gdt}; use super::gdt::{gdt_entry, kvm_segment_from_gdt};
use arch_gen::x86::msr_index; use arch_gen::x86::msr_index;
use kvm_bindings::{kvm_fpu, kvm_msr_entry, kvm_msrs, kvm_regs, kvm_sregs}; use kvm_bindings::{kvm_fpu, kvm_msr_entry, kvm_regs, kvm_sregs, Msrs};
use kvm_ioctls::VcpuFd; use kvm_ioctls::VcpuFd;
use layout::{BOOT_GDT_START, BOOT_IDT_START, PDE_START, PDPTE_START, PML4_START}; use layout::{BOOT_GDT_START, BOOT_IDT_START, PDE_START, PDPTE_START, PML4_START};
use vm_memory::{Address, Bytes, GuestMemory, GuestMemoryMmap}; use vm_memory::{Address, Bytes, GuestMemory, GuestMemoryError, GuestMemoryMmap};
// MTRR constants // MTRR constants
const MTRR_ENABLE: u64 = 0x800; // IA32_MTRR_DEF_TYPE MSR: E (MTRRs enabled) flag, bit 11 const MTRR_ENABLE: u64 = 0x800; // IA32_MTRR_DEF_TYPE MSR: E (MTRRs enabled) flag, bit 11
@@ -21,25 +21,27 @@ const MTRR_MEM_TYPE_WB: u64 = 0x6;
#[derive(Debug)] #[derive(Debug)]
pub enum Error { pub enum Error {
/// Failed to get SREGs for this CPU. /// Failed to get SREGs for this CPU.
GetStatusRegisters(io::Error), GetStatusRegisters(kvm_ioctls::Error),
/// Failed to set base registers for this CPU. /// Failed to set base registers for this CPU.
SetBaseRegisters(io::Error), SetBaseRegisters(kvm_ioctls::Error),
/// Failed to configure the FPU. /// Failed to configure the FPU.
SetFPURegisters(io::Error), SetFPURegisters(kvm_ioctls::Error),
/// Setting up MSRs failed. /// Setting up MSRs failed.
SetModelSpecificRegisters(io::Error), SetModelSpecificRegisters(kvm_ioctls::Error),
/// Failed to set SREGs for this CPU. /// Failed to set SREGs for this CPU.
SetStatusRegisters(io::Error), SetStatusRegisters(kvm_ioctls::Error),
/// Checking the GDT address failed.
CheckGDTAddr,
/// Writing the GDT to RAM failed. /// Writing the GDT to RAM failed.
WriteGDT, WriteGDT(GuestMemoryError),
/// Writing the IDT to RAM failed. /// Writing the IDT to RAM failed.
WriteIDT, WriteIDT(GuestMemoryError),
/// Writing PDPTE to RAM failed. /// Writing PDPTE to RAM failed.
WritePDPTEAddress, WritePDPTEAddress(GuestMemoryError),
/// Writing PDE to RAM failed. /// Writing PDE to RAM failed.
WritePDEAddress, WritePDEAddress(GuestMemoryError),
/// Writing PML4 to RAM failed. /// Writing PML4 to RAM failed.
WritePML4Address, WritePML4Address(GuestMemoryError),
} }
pub type Result<T> = result::Result<T, Error>; pub type Result<T> = result::Result<T, Error>;
@@ -65,26 +67,10 @@ pub fn setup_fpu(vcpu: &VcpuFd) -> Result<()> {
/// ///
/// * `vcpu` - Structure for the VCPU that holds the VCPU's fd. /// * `vcpu` - Structure for the VCPU that holds the VCPU's fd.
pub fn setup_msrs(vcpu: &VcpuFd) -> Result<()> { pub fn setup_msrs(vcpu: &VcpuFd) -> Result<()> {
let entry_vec = create_msr_entries(); vcpu.set_msrs(&create_msr_entries())
let vec_size_bytes = .map_err(Error::SetModelSpecificRegisters)?;
mem::size_of::<kvm_msrs>() + (entry_vec.len() * mem::size_of::<kvm_msr_entry>());
let vec: Vec<u8> = Vec::with_capacity(vec_size_bytes);
let msrs: &mut kvm_msrs = unsafe {
// Converting the vector's memory to a struct is unsafe. Carefully using the read-only
// vector to size and set the members ensures no out-of-bounds errors below.
&mut *(vec.as_ptr() as *mut kvm_msrs)
};
unsafe { Ok(())
// Mapping the unsized array to a slice is unsafe because the length isn't known.
// Providing the length used to create the struct guarantees the entire slice is valid.
let entries: &mut [kvm_msr_entry] = msrs.entries.as_mut_slice(entry_vec.len());
entries.copy_from_slice(&entry_vec);
}
msrs.nmsrs = entry_vec.len() as u32;
vcpu.set_msrs(msrs)
.map_err(Error::SetModelSpecificRegisters)
} }
/// Configure base registers for a given CPU. /// Configure base registers for a given CPU.
@@ -137,10 +123,8 @@ fn write_gdt_table(table: &[u64], guest_mem: &GuestMemoryMmap) -> Result<()> {
for (index, entry) in table.iter().enumerate() { for (index, entry) in table.iter().enumerate() {
let addr = guest_mem let addr = guest_mem
.checked_offset(boot_gdt_addr, index * mem::size_of::<u64>()) .checked_offset(boot_gdt_addr, index * mem::size_of::<u64>())
.ok_or(Error::WriteGDT)?; .ok_or(Error::CheckGDTAddr)?;
guest_mem guest_mem.write_obj(*entry, addr).map_err(Error::WriteGDT)?;
.write_obj(*entry, addr)
.map_err(|_| Error::WriteGDT)?;
} }
Ok(()) Ok(())
} }
@@ -149,7 +133,7 @@ fn write_idt_value(val: u64, guest_mem: &GuestMemoryMmap) -> Result<()> {
let boot_idt_addr = BOOT_IDT_START; let boot_idt_addr = BOOT_IDT_START;
guest_mem guest_mem
.write_obj(val, boot_idt_addr) .write_obj(val, boot_idt_addr)
.map_err(|_| Error::WriteIDT) .map_err(Error::WriteIDT)
} }
fn configure_segments_and_sregs(mem: &GuestMemoryMmap, sregs: &mut kvm_sregs) -> Result<()> { fn configure_segments_and_sregs(mem: &GuestMemoryMmap, sregs: &mut kvm_sregs) -> Result<()> {
@@ -193,16 +177,16 @@ fn setup_page_tables(mem: &GuestMemoryMmap, sregs: &mut kvm_sregs) -> Result<()>
// Entry covering VA [0..512GB) // Entry covering VA [0..512GB)
mem.write_obj(PDPTE_START.raw_value() | 0x03, PML4_START) mem.write_obj(PDPTE_START.raw_value() | 0x03, PML4_START)
.map_err(|_| Error::WritePML4Address)?; .map_err(Error::WritePML4Address)?;
// Entry covering VA [0..1GB) // Entry covering VA [0..1GB)
mem.write_obj(PDE_START.raw_value() | 0x03, PDPTE_START) mem.write_obj(PDE_START.raw_value() | 0x03, PDPTE_START)
.map_err(|_| Error::WritePDPTEAddress)?; .map_err(Error::WritePDPTEAddress)?;
// 512 2MB entries together covering VA [0..1GB). Note we are assuming // 512 2MB entries together covering VA [0..1GB). Note we are assuming
// CPU supports 2MB pages (/proc/cpuinfo has 'pse'). All modern CPUs do. // CPU supports 2MB pages (/proc/cpuinfo has 'pse'). All modern CPUs do.
for i in 0..512 { for i in 0..512 {
mem.write_obj((i << 21) + 0x83u64, PDE_START.unchecked_add(i * 8)) mem.write_obj((i << 21) + 0x83u64, PDE_START.unchecked_add(i * 8))
.map_err(|_| Error::WritePDEAddress)?; .map_err(Error::WritePDEAddress)?;
} }
sregs.cr3 = PML4_START.raw_value(); sregs.cr3 = PML4_START.raw_value();
@@ -211,7 +195,7 @@ fn setup_page_tables(mem: &GuestMemoryMmap, sregs: &mut kvm_sregs) -> Result<()>
Ok(()) Ok(())
} }
fn create_msr_entries() -> Vec<kvm_msr_entry> { fn create_msr_entries() -> Msrs {
let mut entries = Vec::<kvm_msr_entry>::new(); let mut entries = Vec::<kvm_msr_entry>::new();
entries.push(kvm_msr_entry { entries.push(kvm_msr_entry {
@@ -272,7 +256,7 @@ fn create_msr_entries() -> Vec<kvm_msr_entry> {
..Default::default() ..Default::default()
}); });
entries Msrs::from_entries(&entries)
} }
#[cfg(test)] #[cfg(test)]
@@ -285,7 +269,7 @@ mod tests {
use vm_memory::{GuestAddress, GuestMemoryMmap}; use vm_memory::{GuestAddress, GuestMemoryMmap};
fn create_guest_mem() -> GuestMemoryMmap { fn create_guest_mem() -> GuestMemoryMmap {
GuestMemoryMmap::new(&vec![(GuestAddress(0), 0x10000)]).unwrap() GuestMemoryMmap::from_ranges(&vec![(GuestAddress(0), 0x10000)]).unwrap()
} }
fn read_u64(gm: &GuestMemoryMmap, offset: GuestAddress) -> u64 { fn read_u64(gm: &GuestMemoryMmap, offset: GuestAddress) -> u64 {
@@ -377,24 +361,11 @@ mod tests {
// This test will check against the last MSR entry configured (the tenth one). // This test will check against the last MSR entry configured (the tenth one).
// See create_msr_entries for details. // See create_msr_entries for details.
let test_kvm_msrs_entry = [kvm_msr_entry { let mut msrs = Msrs::from_entries(&[kvm_msr_entry {
index: msr_index::MSR_IA32_MISC_ENABLE, index: msr_index::MSR_IA32_MISC_ENABLE,
..Default::default() ..Default::default()
}]; }]);
let vec_size_bytes = mem::size_of::<kvm_msrs>() + mem::size_of::<kvm_msr_entry>();
let vec: Vec<u8> = Vec::with_capacity(vec_size_bytes);
let mut msrs: &mut kvm_msrs = unsafe {
// Converting the vector's memory to a struct is unsafe. Carefully using the read-only
// vector to size and set the members ensures no out-of-bounds errors below.
&mut *(vec.as_ptr() as *mut kvm_msrs)
};
unsafe {
let entries: &mut [kvm_msr_entry] = msrs.entries.as_mut_slice(1);
entries.copy_from_slice(&test_kvm_msrs_entry);
}
msrs.nmsrs = 1;
// get_msrs returns the number of msrs that it succeed in reading. We only want to read 1 // get_msrs returns the number of msrs that it succeed in reading. We only want to read 1
// in this test case scenario. // in this test case scenario.
let read_msrs = vcpu.get_msrs(&mut msrs).unwrap(); let read_msrs = vcpu.get_msrs(&mut msrs).unwrap();
@@ -404,9 +375,7 @@ mod tests {
// tenth one (i.e the one with index msr_index::MSR_IA32_MISC_ENABLE has the data we // tenth one (i.e the one with index msr_index::MSR_IA32_MISC_ENABLE has the data we
// expect. // expect.
let entry_vec = create_msr_entries(); let entry_vec = create_msr_entries();
unsafe { assert_eq!(entry_vec.as_slice()[9], msrs.as_slice()[0]);
assert_eq!(entry_vec[9], msrs.entries.as_slice(1)[0]);
}
} }
#[test] #[test]

File diff suppressed because it is too large Load Diff

View File

@@ -16,7 +16,6 @@
clippy::should_implement_trait, clippy::should_implement_trait,
clippy::transmute_ptr_to_ptr clippy::transmute_ptr_to_ptr
)] )]
pub mod bootparam;
#[allow(non_camel_case_types)] #[allow(non_camel_case_types)]
#[allow(non_upper_case_globals)] #[allow(non_upper_case_globals)]
#[allow(clippy::unreadable_literal, clippy::redundant_static_lifetimes)] #[allow(clippy::unreadable_literal, clippy::redundant_static_lifetimes)]

View File

@@ -4,14 +4,14 @@ version = "0.1.0"
authors = ["The Chromium OS Authors"] authors = ["The Chromium OS Authors"]
[dependencies] [dependencies]
byteorder = "1.3.2" bitflags = "1.2.1"
byteorder = "1.3.4"
epoll = "4.1.0" epoll = "4.1.0"
kvm-bindings = "0.1.1" libc = "0.2.66"
kvm-ioctls = { git = "https://github.com/rust-vmm/kvm-ioctls", branch = "master" }
libc = "0.2.60"
log = "0.4.8" log = "0.4.8"
vm-device = { path = "../vm-device" }
vm-memory = { git = "https://github.com/rust-vmm/vm-memory" } vm-memory = { git = "https://github.com/rust-vmm/vm-memory" }
vmm-sys-util = "0.1.1" vmm-sys-util = "0.4.0"
[dev-dependencies] [dev-dependencies]
tempfile = "3.1.0" tempfile = "3.1.0"

View File

@@ -3,8 +3,11 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// //
use std::sync::Arc;
use vm_device::interrupt::InterruptSourceGroup;
use vmm_sys_util::eventfd::EventFd; use vmm_sys_util::eventfd::EventFd;
use BusDevice; use BusDevice;
use HotPlugNotificationFlags;
/// A device for handling ACPI shutdown and reboot /// A device for handling ACPI shutdown and reboot
pub struct AcpiShutdownDevice { pub struct AcpiShutdownDevice {
@@ -44,9 +47,50 @@ impl BusDevice for AcpiShutdownDevice {
const SLEEP_VALUE_BIT: u8 = 2; const SLEEP_VALUE_BIT: u8 = 2;
if data[0] == (S5_SLEEP_VALUE << SLEEP_VALUE_BIT) | (1 << SLEEP_STATUS_EN_BIT) { if data[0] == (S5_SLEEP_VALUE << SLEEP_VALUE_BIT) | (1 << SLEEP_STATUS_EN_BIT) {
debug!("ACPI Shutdown signalled"); debug!("ACPI Shutdown signalled");
extern crate bitflags;
if let Err(e) = self.exit_evt.write(1) { if let Err(e) = self.exit_evt.write(1) {
error!("Error triggering ACPI shutdown event: {}", e); error!("Error triggering ACPI shutdown event: {}", e);
} }
} }
} }
} }
/// A device for handling ACPI GED event generation
pub struct AcpiGEDDevice {
interrupt: Arc<Box<dyn InterruptSourceGroup>>,
notification_type: HotPlugNotificationFlags,
ged_irq: u32,
}
impl AcpiGEDDevice {
pub fn new(interrupt: Arc<Box<dyn InterruptSourceGroup>>, ged_irq: u32) -> AcpiGEDDevice {
AcpiGEDDevice {
interrupt,
notification_type: HotPlugNotificationFlags::NO_DEVICES_CHANGED,
ged_irq,
}
}
pub fn notify(
&mut self,
notification_type: HotPlugNotificationFlags,
) -> Result<(), std::io::Error> {
self.notification_type |= notification_type;
self.interrupt.trigger(0)
}
pub fn irq(&self) -> u32 {
self.ged_irq
}
}
// I/O port reports what type of notification was made
impl BusDevice for AcpiGEDDevice {
// Spec has all fields as zero
fn read(&mut self, _base: u64, _offset: u64, data: &mut [u8]) {
data[0] = self.notification_type.bits();
self.notification_type = HotPlugNotificationFlags::NO_DEVICES_CHANGED;
}
fn write(&mut self, _base: u64, _offset: u64, _data: &[u8]) {}
}

View File

@@ -9,8 +9,8 @@
use std::cmp::{Ord, Ordering, PartialEq, PartialOrd}; use std::cmp::{Ord, Ordering, PartialEq, PartialOrd};
use std::collections::btree_map::BTreeMap; use std::collections::btree_map::BTreeMap;
use std::result; use std::sync::{Arc, Mutex, RwLock};
use std::sync::{Arc, Mutex}; use std::{convert, error, fmt, io, result};
/// Trait for devices that respond to reads or writes in an arbitrary address space. /// Trait for devices that respond to reads or writes in an arbitrary address space.
/// ///
@@ -30,10 +30,28 @@ pub trait BusDevice: Send {
pub enum Error { pub enum Error {
/// The insertion failed because the new device overlapped with an old device. /// The insertion failed because the new device overlapped with an old device.
Overlap, Overlap,
/// Failed to operate on zero sized range.
ZeroSizedRange,
/// Failed to find address range.
MissingAddressRange,
} }
pub type Result<T> = result::Result<T, Error>; pub type Result<T> = result::Result<T, Error>;
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "bus_error: {:?}", self)
}
}
impl error::Error for Error {}
impl convert::From<Error> for io::Error {
fn from(e: Error) -> Self {
io::Error::new(io::ErrorKind::Other, e)
}
}
/// Holds a base and length representing the address space occupied by a `BusDevice`. /// Holds a base and length representing the address space occupied by a `BusDevice`.
/// ///
/// * base - The address at which the range start. /// * base - The address at which the range start.
@@ -75,29 +93,30 @@ impl PartialOrd for BusRange {
/// ///
/// This doesn't have any restrictions on what kind of device or address space this applies to. The /// This doesn't have any restrictions on what kind of device or address space this applies to. The
/// only restriction is that no two devices can overlap in this address space. /// only restriction is that no two devices can overlap in this address space.
#[derive(Clone, Default)] #[derive(Default)]
pub struct Bus { pub struct Bus {
devices: BTreeMap<BusRange, Arc<Mutex<dyn BusDevice>>>, devices: RwLock<BTreeMap<BusRange, Arc<Mutex<dyn BusDevice>>>>,
} }
impl Bus { impl Bus {
/// Constructs an a bus with an empty address space. /// Constructs an a bus with an empty address space.
pub fn new() -> Bus { pub fn new() -> Bus {
Bus { Bus {
devices: BTreeMap::new(), devices: RwLock::new(BTreeMap::new()),
} }
} }
fn first_before(&self, addr: u64) -> Option<(BusRange, &Mutex<dyn BusDevice>)> { fn first_before(&self, addr: u64) -> Option<(BusRange, Arc<Mutex<dyn BusDevice>>)> {
let (range, dev) = self let devices = self.devices.read().unwrap();
.devices let (range, dev) = devices
.range(..=BusRange { base: addr, len: 1 }) .range(..=BusRange { base: addr, len: 1 })
.rev() .rev()
.next()?; .next()?;
Some((*range, dev)) Some((*range, dev.clone()))
} }
pub fn resolve(&self, addr: u64) -> Option<(u64, u64, &Mutex<dyn BusDevice>)> { #[allow(clippy::type_complexity)]
pub fn resolve(&self, addr: u64) -> Option<(u64, u64, Arc<Mutex<dyn BusDevice>>)> {
if let Some((range, dev)) = self.first_before(addr) { if let Some((range, dev)) = self.first_before(addr) {
let offset = addr - range.base; let offset = addr - range.base;
if offset < range.len { if offset < range.len {
@@ -108,14 +127,16 @@ impl Bus {
} }
/// Puts the given device at the given address space. /// Puts the given device at the given address space.
pub fn insert(&mut self, device: Arc<Mutex<dyn BusDevice>>, base: u64, len: u64) -> Result<()> { pub fn insert(&self, device: Arc<Mutex<dyn BusDevice>>, base: u64, len: u64) -> Result<()> {
if len == 0 { if len == 0 {
return Err(Error::Overlap); return Err(Error::ZeroSizedRange);
} }
// Reject all cases where the new device's range overlaps with an existing device. // Reject all cases where the new device's range overlaps with an existing device.
if self if self
.devices .devices
.read()
.unwrap()
.iter() .iter()
.any(|(range, _dev)| range.overlaps(base, len)) .any(|(range, _dev)| range.overlaps(base, len))
{ {
@@ -124,6 +145,8 @@ impl Bus {
if self if self
.devices .devices
.write()
.unwrap()
.insert(BusRange { base, len }, device) .insert(BusRange { base, len }, device)
.is_some() .is_some()
{ {
@@ -133,6 +156,43 @@ impl Bus {
Ok(()) Ok(())
} }
/// Removes the device at the given address space range.
pub fn remove(&self, base: u64, len: u64) -> Result<()> {
if len == 0 {
return Err(Error::ZeroSizedRange);
}
let bus_range = BusRange { base, len };
if self.devices.write().unwrap().remove(&bus_range).is_none() {
return Err(Error::MissingAddressRange);
}
Ok(())
}
/// Updates the address range for an existing device.
pub fn update_range(
&self,
old_base: u64,
old_len: u64,
new_base: u64,
new_len: u64,
) -> Result<()> {
// Retrieve the device corresponding to the range
let device = if let Some((_, _, dev)) = self.resolve(old_base) {
dev.clone()
} else {
return Err(Error::MissingAddressRange);
};
// Remove the old address range
self.remove(old_base, old_len)?;
// Insert the new address range
self.insert(device, new_base, new_len)
}
/// Reads data from the device that owns the range containing `addr` and puts it into `data`. /// Reads data from the device that owns the range containing `addr` and puts it into `data`.
/// ///
/// Returns true on success, otherwise `data` is untouched. /// Returns true on success, otherwise `data` is untouched.
@@ -188,7 +248,7 @@ mod tests {
#[test] #[test]
fn bus_insert() { fn bus_insert() {
let mut bus = Bus::new(); let bus = Bus::new();
let dummy = Arc::new(Mutex::new(DummyDevice)); let dummy = Arc::new(Mutex::new(DummyDevice));
assert!(bus.insert(dummy.clone(), 0x10, 0).is_err()); assert!(bus.insert(dummy.clone(), 0x10, 0).is_err());
assert!(bus.insert(dummy.clone(), 0x10, 0x10).is_ok()); assert!(bus.insert(dummy.clone(), 0x10, 0x10).is_ok());
@@ -209,7 +269,7 @@ mod tests {
#[test] #[test]
fn bus_read_write() { fn bus_read_write() {
let mut bus = Bus::new(); let bus = Bus::new();
let dummy = Arc::new(Mutex::new(DummyDevice)); let dummy = Arc::new(Mutex::new(DummyDevice));
assert!(bus.insert(dummy.clone(), 0x10, 0x10).is_ok()); assert!(bus.insert(dummy.clone(), 0x10, 0x10).is_ok());
assert!(bus.read(0x10, &mut [0, 0, 0, 0])); assert!(bus.read(0x10, &mut [0, 0, 0, 0]));
@@ -226,7 +286,7 @@ mod tests {
#[test] #[test]
fn bus_read_write_values() { fn bus_read_write_values() {
let mut bus = Bus::new(); let bus = Bus::new();
let dummy = Arc::new(Mutex::new(ConstantDevice)); let dummy = Arc::new(Mutex::new(ConstantDevice));
assert!(bus.insert(dummy.clone(), 0x10, 0x10).is_ok()); assert!(bus.insert(dummy.clone(), 0x10, 0x10).is_ok());
@@ -240,7 +300,7 @@ mod tests {
} }
#[test] #[test]
fn busrange_cmp_and_clone() { fn busrange_cmp() {
let range = BusRange { base: 0x10, len: 2 }; let range = BusRange { base: 0x10, len: 2 };
assert_eq!(range, BusRange { base: 0x10, len: 3 }); assert_eq!(range, BusRange { base: 0x10, len: 3 });
assert_eq!(range, BusRange { base: 0x10, len: 2 }); assert_eq!(range, BusRange { base: 0x10, len: 2 });
@@ -250,17 +310,14 @@ mod tests {
assert_eq!(range, range.clone()); assert_eq!(range, range.clone());
let mut bus = Bus::new(); let bus = Bus::new();
let mut data = [1, 2, 3, 4]; let mut data = [1, 2, 3, 4];
assert!(bus assert!(bus
.insert(Arc::new(Mutex::new(DummyDevice)), 0x10, 0x10) .insert(Arc::new(Mutex::new(DummyDevice)), 0x10, 0x10)
.is_ok()); .is_ok());
assert!(bus.write(0x10, &mut data)); assert!(bus.write(0x10, &mut data));
let bus_clone = bus.clone();
assert!(bus.read(0x10, &mut data)); assert!(bus.read(0x10, &mut data));
assert_eq!(data, [1, 2, 3, 4]); assert_eq!(data, [1, 2, 3, 4]);
assert!(bus_clone.read(0x10, &mut data));
assert_eq!(data, [1, 2, 3, 4]);
} }
#[test] #[test]

View File

@@ -11,22 +11,35 @@
use crate::BusDevice; use crate::BusDevice;
use byteorder::{ByteOrder, LittleEndian}; use byteorder::{ByteOrder, LittleEndian};
use kvm_bindings::kvm_msi; use std::io;
use kvm_ioctls::VmFd; use std::result;
use std::sync::Arc; use std::sync::Arc;
use std::{io, result}; use vm_device::interrupt::{
InterruptIndex, InterruptManager, InterruptSourceConfig, InterruptSourceGroup,
MsiIrqGroupConfig, MsiIrqSourceConfig,
};
use vm_memory::GuestAddress; use vm_memory::GuestAddress;
#[derive(Debug)] #[derive(Debug)]
pub enum Error { pub enum Error {
/// Failed to send an interrupt.
InterruptFailed(io::Error),
/// Invalid destination mode. /// Invalid destination mode.
InvalidDestinationMode, InvalidDestinationMode,
/// Invalid trigger mode. /// Invalid trigger mode.
InvalidTriggerMode, InvalidTriggerMode,
/// Invalid delivery mode. /// Invalid delivery mode.
InvalidDeliveryMode, InvalidDeliveryMode,
/// Failed creating the interrupt source group.
CreateInterruptSourceGroup(io::Error),
/// Failed triggering the interrupt.
TriggerInterrupt(io::Error),
/// Failed masking the interrupt.
MaskInterrupt(io::Error),
/// Failed unmasking the interrupt.
UnmaskInterrupt(io::Error),
/// Failed updating the interrupt.
UpdateInterrupt(io::Error),
/// Failed enabling the interrupt.
EnableInterrupt(io::Error),
} }
type Result<T> = result::Result<T, Error>; type Result<T> = result::Result<T, Error>;
@@ -156,8 +169,8 @@ pub struct Ioapic {
id: u32, id: u32,
reg_sel: u32, reg_sel: u32,
reg_entries: [RedirectionTableEntry; NUM_IOAPIC_PINS], reg_entries: [RedirectionTableEntry; NUM_IOAPIC_PINS],
vm_fd: Arc<VmFd>,
apic_address: GuestAddress, apic_address: GuestAddress,
interrupt_source_group: Arc<Box<dyn InterruptSourceGroup>>,
} }
impl BusDevice for Ioapic { impl BusDevice for Ioapic {
@@ -196,14 +209,28 @@ impl BusDevice for Ioapic {
} }
impl Ioapic { impl Ioapic {
pub fn new(vm_fd: Arc<VmFd>, apic_address: GuestAddress) -> Ioapic { pub fn new(
Ioapic { apic_address: GuestAddress,
interrupt_manager: Arc<dyn InterruptManager<GroupConfig = MsiIrqGroupConfig>>,
) -> Result<Ioapic> {
let interrupt_source_group = interrupt_manager
.create_group(MsiIrqGroupConfig {
base: 0 as InterruptIndex,
count: NUM_IOAPIC_PINS as InterruptIndex,
})
.map_err(Error::CreateInterruptSourceGroup)?;
interrupt_source_group
.enable()
.map_err(Error::EnableInterrupt)?;
Ok(Ioapic {
id: 0, id: 0,
reg_sel: 0, reg_sel: 0,
reg_entries: [0; NUM_IOAPIC_PINS], reg_entries: [0; NUM_IOAPIC_PINS],
vm_fd,
apic_address, apic_address,
} interrupt_source_group,
})
} }
// The ioapic must be informed about EOIs in order to deassert interrupts // The ioapic must be informed about EOIs in order to deassert interrupts
@@ -223,16 +250,30 @@ impl Ioapic {
pub fn service_irq(&mut self, irq: usize) -> Result<()> { pub fn service_irq(&mut self, irq: usize) -> Result<()> {
let entry = &mut self.reg_entries[irq]; let entry = &mut self.reg_entries[irq];
// Don't inject the interrupt if the IRQ is masked self.interrupt_source_group
if interrupt_mask(*entry) == 1 { .trigger(irq as InterruptIndex)
return Ok(()); .map_err(Error::TriggerInterrupt)?;
debug!("Interrupt successfully delivered");
// If trigger mode is level sensitive, set the Remote IRR bit.
// It will be cleared when the EOI is received.
if trigger_mode(*entry) == 1 {
set_remote_irr(entry, 1);
} }
// Clear the Delivery Status bit
set_delivery_status(entry, 0);
Ok(())
}
fn update_entry(&self, irq: usize) -> Result<()> {
let entry = self.reg_entries[irq];
// Validate Destination Mode value, and retrieve Destination ID // Validate Destination Mode value, and retrieve Destination ID
let destination_mode = destination_mode(*entry); let destination_mode = destination_mode(entry);
let destination_id: u8 = match destination_mode { let destination_id: u8 = match destination_mode {
x if x == DestinationMode::Physical as u8 => destination_field_physical(*entry), x if x == DestinationMode::Physical as u8 => destination_field_physical(entry),
x if x == DestinationMode::Logical as u8 => destination_field_logical(*entry), x if x == DestinationMode::Logical as u8 => destination_field_logical(entry),
_ => return Err(Error::InvalidDestinationMode), _ => return Err(Error::InvalidDestinationMode),
}; };
@@ -242,20 +283,20 @@ impl Ioapic {
let redirection_hint: u8 = 1; let redirection_hint: u8 = 1;
// Generate MSI message address // Generate MSI message address
let address_lo: u32 = self.apic_address.0 as u32 let low_addr: u32 = self.apic_address.0 as u32
| u32::from(destination_id) << 12 | u32::from(destination_id) << 12
| u32::from(redirection_hint) << 3 | u32::from(redirection_hint) << 3
| u32::from(destination_mode) << 2; | u32::from(destination_mode) << 2;
// Validate Trigger Mode value // Validate Trigger Mode value
let trigger_mode = trigger_mode(*entry); let trigger_mode = trigger_mode(entry);
match trigger_mode { match trigger_mode {
x if (x == TriggerMode::Edge as u8) || (x == TriggerMode::Level as u8) => {} x if (x == TriggerMode::Edge as u8) || (x == TriggerMode::Level as u8) => {}
_ => return Err(Error::InvalidTriggerMode), _ => return Err(Error::InvalidTriggerMode),
} }
// Validate Delivery Mode value // Validate Delivery Mode value
let delivery_mode = delivery_mode(*entry); let delivery_mode = delivery_mode(entry);
match delivery_mode { match delivery_mode {
x if (x == DeliveryMode::Fixed as u8) x if (x == DeliveryMode::Fixed as u8)
|| (x == DeliveryMode::Lowest as u8) || (x == DeliveryMode::Lowest as u8)
@@ -270,37 +311,31 @@ impl Ioapic {
// Generate MSI message data // Generate MSI message data
let data: u32 = u32::from(trigger_mode) << 15 let data: u32 = u32::from(trigger_mode) << 15
| u32::from(remote_irr(*entry)) << 14 | u32::from(remote_irr(entry)) << 14
| u32::from(delivery_mode) << 8 | u32::from(delivery_mode) << 8
| u32::from(vector(*entry)); | u32::from(vector(entry));
let msi = kvm_msi { let config = MsiIrqSourceConfig {
address_lo, high_addr: 0x0,
address_hi: 0x0, low_addr,
data, data,
flags: 0u32,
devid: 0u32,
pad: [0u8; 12],
}; };
match self.vm_fd.signal_msi(msi) { self.interrupt_source_group
Ok(ret) => { .update(irq as InterruptIndex, InterruptSourceConfig::MsiIrq(config))
if ret > 0 { .map_err(Error::UpdateInterrupt)?;
debug!("MSI message successfully delivered");
// If trigger mode is level sensitive, set the Remote IRR bit. if interrupt_mask(entry) == 1 {
// It will be cleared when the EOI is received. self.interrupt_source_group
if trigger_mode == 1 { .mask(irq as InterruptIndex)
set_remote_irr(entry, 1); .map_err(Error::MaskInterrupt)?;
} } else {
// Clear the Delivery Status bit self.interrupt_source_group
set_delivery_status(entry, 0); .unmask(irq as InterruptIndex)
} else { .map_err(Error::UnmaskInterrupt)?;
warn!("failed to deliver MSI message, blocked by guest");
}
Ok(())
}
Err(e) => Err(Error::InterruptFailed(e)),
} }
Ok(())
} }
fn ioapic_write(&mut self, val: u32) { fn ioapic_write(&mut self, val: u32) {
@@ -320,6 +355,11 @@ impl Ioapic {
self.reg_entries[index] &= 0xffff_ffff_0000_5000; self.reg_entries[index] &= 0xffff_ffff_0000_5000;
self.reg_entries[index] |= u64::from(val) & 0xffff_afff; self.reg_entries[index] |= u64::from(val) & 0xffff_afff;
} }
// The entry must be updated through the interrupt source
// group.
if let Err(e) = self.update_entry(index) {
error!("Failed updating IOAPIC entry: {:?}", e);
}
} }
_ => error!("IOAPIC: invalid write to register offset"), _ => error!("IOAPIC: invalid write to register offset"),
} }

View File

@@ -5,9 +5,11 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE-BSD-3-Clause file. // found in the LICENSE-BSD-3-Clause file.
use crate::{BusDevice, Interrupt}; use crate::BusDevice;
use std::collections::VecDeque; use std::collections::VecDeque;
use std::sync::Arc;
use std::{io, result}; use std::{io, result};
use vm_device::interrupt::InterruptSourceGroup;
use vmm_sys_util::errno::Result; use vmm_sys_util::errno::Result;
const LOOP_SIZE: usize = 0x40; const LOOP_SIZE: usize = 0x40;
@@ -55,7 +57,7 @@ const DEFAULT_BAUD_DIVISOR: u16 = 12; // 9600 bps
pub struct Serial { pub struct Serial {
interrupt_enable: u8, interrupt_enable: u8,
interrupt_identification: u8, interrupt_identification: u8,
interrupt: Box<dyn Interrupt>, interrupt: Arc<Box<dyn InterruptSourceGroup>>,
line_control: u8, line_control: u8,
line_status: u8, line_status: u8,
modem_control: u8, modem_control: u8,
@@ -67,7 +69,10 @@ pub struct Serial {
} }
impl Serial { impl Serial {
pub fn new(interrupt: Box<dyn Interrupt>, out: Option<Box<dyn io::Write + Send>>) -> Serial { pub fn new(
interrupt: Arc<Box<dyn InterruptSourceGroup>>,
out: Option<Box<dyn io::Write + Send>>,
) -> Serial {
Serial { Serial {
interrupt_enable: 0, interrupt_enable: 0,
interrupt_identification: DEFAULT_INTERRUPT_IDENTIFICATION, interrupt_identification: DEFAULT_INTERRUPT_IDENTIFICATION,
@@ -84,12 +89,15 @@ impl Serial {
} }
/// Constructs a Serial port ready for output. /// Constructs a Serial port ready for output.
pub fn new_out(interrupt: Box<dyn Interrupt>, out: Box<dyn io::Write + Send>) -> Serial { pub fn new_out(
interrupt: Arc<Box<dyn InterruptSourceGroup>>,
out: Box<dyn io::Write + Send>,
) -> Serial {
Self::new(interrupt, Some(out)) Self::new(interrupt, Some(out))
} }
/// Constructs a Serial port with no connected output. /// Constructs a Serial port with no connected output.
pub fn new_sink(interrupt: Box<dyn Interrupt>) -> Serial { pub fn new_sink(interrupt: Arc<Box<dyn InterruptSourceGroup>>) -> Serial {
Self::new(interrupt, None) Self::new(interrupt, None)
} }
@@ -149,7 +157,7 @@ impl Serial {
} }
fn trigger_interrupt(&mut self) -> result::Result<(), io::Error> { fn trigger_interrupt(&mut self) -> result::Result<(), io::Error> {
self.interrupt.deliver() self.interrupt.trigger(0)
} }
fn iir_reset(&mut self) { fn iir_reset(&mut self) {
@@ -233,16 +241,24 @@ mod tests {
use super::*; use super::*;
use std::io; use std::io;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use vm_device::interrupt::{InterruptIndex, InterruptSourceConfig};
use vmm_sys_util::eventfd::EventFd; use vmm_sys_util::eventfd::EventFd;
struct TestInterrupt { struct TestInterrupt {
event_fd: EventFd, event_fd: EventFd,
} }
impl Interrupt for TestInterrupt { impl InterruptSourceGroup for TestInterrupt {
fn deliver(&self) -> result::Result<(), std::io::Error> { fn trigger(&self, _index: InterruptIndex) -> result::Result<(), std::io::Error> {
self.event_fd.write(1) self.event_fd.write(1)
} }
fn update(
&self,
_index: InterruptIndex,
_config: InterruptSourceConfig,
) -> result::Result<(), std::io::Error> {
Ok(())
}
} }
impl TestInterrupt { impl TestInterrupt {
@@ -278,7 +294,7 @@ mod tests {
let intr_evt = EventFd::new(0).unwrap(); let intr_evt = EventFd::new(0).unwrap();
let serial_out = SharedBuffer::new(); let serial_out = SharedBuffer::new();
let mut serial = Serial::new_out( let mut serial = Serial::new_out(
Box::new(TestInterrupt::new(intr_evt.try_clone().unwrap())), Arc::new(Box::new(TestInterrupt::new(intr_evt.try_clone().unwrap()))),
Box::new(serial_out.clone()), Box::new(serial_out.clone()),
); );
@@ -297,7 +313,7 @@ mod tests {
let intr_evt = EventFd::new(0).unwrap(); let intr_evt = EventFd::new(0).unwrap();
let serial_out = SharedBuffer::new(); let serial_out = SharedBuffer::new();
let mut serial = Serial::new_out( let mut serial = Serial::new_out(
Box::new(TestInterrupt::new(intr_evt.try_clone().unwrap())), Arc::new(Box::new(TestInterrupt::new(intr_evt.try_clone().unwrap()))),
Box::new(serial_out.clone()), Box::new(serial_out.clone()),
); );
@@ -334,8 +350,9 @@ mod tests {
#[test] #[test]
fn serial_thr() { fn serial_thr() {
let intr_evt = EventFd::new(0).unwrap(); let intr_evt = EventFd::new(0).unwrap();
let mut serial = let mut serial = Serial::new_sink(Arc::new(Box::new(TestInterrupt::new(
Serial::new_sink(Box::new(TestInterrupt::new(intr_evt.try_clone().unwrap()))); intr_evt.try_clone().unwrap(),
))));
// write 1 to the interrupt event fd, so that read doesn't block in case the event fd // 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) // counter doesn't change (for 0 it blocks)
@@ -354,8 +371,9 @@ mod tests {
#[test] #[test]
fn serial_dlab() { fn serial_dlab() {
let intr_evt = EventFd::new(0).unwrap(); let intr_evt = EventFd::new(0).unwrap();
let mut serial = let mut serial = Serial::new_sink(Arc::new(Box::new(TestInterrupt::new(
Serial::new_sink(Box::new(TestInterrupt::new(intr_evt.try_clone().unwrap()))); intr_evt.try_clone().unwrap(),
))));
serial.write(0, LCR as u64, &[LCR_DLAB_BIT as u8]); serial.write(0, LCR as u64, &[LCR_DLAB_BIT as u8]);
serial.write(0, DLAB_LOW as u64, &[0x12 as u8]); serial.write(0, DLAB_LOW as u64, &[0x12 as u8]);
@@ -373,8 +391,9 @@ mod tests {
#[test] #[test]
fn serial_modem() { fn serial_modem() {
let intr_evt = EventFd::new(0).unwrap(); let intr_evt = EventFd::new(0).unwrap();
let mut serial = let mut serial = Serial::new_sink(Arc::new(Box::new(TestInterrupt::new(
Serial::new_sink(Box::new(TestInterrupt::new(intr_evt.try_clone().unwrap()))); intr_evt.try_clone().unwrap(),
))));
serial.write(0, MCR as u64, &[MCR_LOOP_BIT as u8]); serial.write(0, MCR as u64, &[MCR_LOOP_BIT as u8]);
serial.write(0, DATA as u64, &['a' as u8]); serial.write(0, DATA as u64, &['a' as u8]);
@@ -397,8 +416,9 @@ mod tests {
#[test] #[test]
fn serial_scratch() { fn serial_scratch() {
let intr_evt = EventFd::new(0).unwrap(); let intr_evt = EventFd::new(0).unwrap();
let mut serial = let mut serial = Serial::new_sink(Arc::new(Box::new(TestInterrupt::new(
Serial::new_sink(Box::new(TestInterrupt::new(intr_evt.try_clone().unwrap()))); intr_evt.try_clone().unwrap(),
))));
serial.write(0, SCR as u64, &[0x12 as u8]); serial.write(0, SCR as u64, &[0x12 as u8]);

View File

@@ -6,18 +6,19 @@
// found in the LICENSE-BSD-3-Clause file. // found in the LICENSE-BSD-3-Clause file.
//! Emulates virtual and hardware devices. //! Emulates virtual and hardware devices.
#[macro_use]
extern crate bitflags;
extern crate byteorder; extern crate byteorder;
extern crate epoll; extern crate epoll;
extern crate kvm_bindings;
extern crate kvm_ioctls;
extern crate libc; extern crate libc;
#[macro_use] #[macro_use]
extern crate log; extern crate log;
extern crate vm_device;
extern crate vm_memory; extern crate vm_memory;
extern crate vmm_sys_util; extern crate vmm_sys_util;
use std::fs::File; use std::fs::File;
use std::{io, result}; use std::io;
#[cfg(feature = "acpi")] #[cfg(feature = "acpi")]
mod acpi; mod acpi;
@@ -26,7 +27,7 @@ pub mod ioapic;
pub mod legacy; pub mod legacy;
#[cfg(feature = "acpi")] #[cfg(feature = "acpi")]
pub use self::acpi::AcpiShutdownDevice; pub use self::acpi::{AcpiGEDDevice, AcpiShutdownDevice};
pub use self::bus::{Bus, BusDevice, Error as BusError}; pub use self::bus::{Bus, BusDevice, Error as BusError};
pub type DeviceEventT = u16; pub type DeviceEventT = u16;
@@ -67,6 +68,10 @@ pub enum Error {
IoError(io::Error), IoError(io::Error),
} }
pub trait Interrupt: Send + Sync { bitflags! {
fn deliver(&self) -> result::Result<(), std::io::Error>; pub struct HotPlugNotificationFlags: u8 {
const NO_DEVICES_CHANGED = 0;
const CPU_DEVICES_CHANGED = 0b1;
const MEMORY_DEVICES_CHANGED = 0b10;
}
} }

371
docs/api.md Normal file
View File

@@ -0,0 +1,371 @@
- [Cloud Hypervisor API](#cloud-hypervisor-api)
* [External API](#external-api)
+ [REST API](#rest-api)
- [Location and availability](#location-and-availability)
- [Endpoints](#endpoints)
* [Virtual Machine Manager (VMM) Actions](#virtual-machine-manager-vmm-actions)
* [Virtual Machine (VM) Actions](#virtual-machine-vm-actions)
- [REST API Examples](#rest-api-examples)
* [Create a Virtual Machine](#create-a-virtual-machine)
* [Boot a Virtual Machine](#boot-a-virtual-machine)
* [Dump a Virtual Machine Information](#dump-a-virtual-machine-information)
* [Reboot a Virtual Machine](#reboot-a-virtual-machine)
* [Shut a Virtual Machine Down](#shut-a-virtual-machine-down)
+ [Command Line Interface](#command-line-interface)
+ [REST API and CLI Architecural Relationship](#rest-api-and-cli-architectural-relationship)
* [Internal API](#internal-api)
+ [Goals and Design](#goals-and-design)
* [End to End Example](#end-to-end-example)
# Cloud Hypervisor API
The Cloud Hypervisor API is made of 2 distinct interfaces:
1. **The external API**. This is the user facing API. Users and operators can
control and manage Cloud Hypervisor through either a REST API or a Command
Line Interface (CLI).
1. **The internal API**, based on [rust's Multi-Producer, Single-Consumer (MPSC)](https://doc.rust-lang.org/std/sync/mpsc/)
module. This API is used internally by the Cloud Hypervisor threads to
communicate between each others.
The goal of this document is to describe the Cloud Hypervisor API as a whole,
and to outline how the internal and external APIs are architecturally related.
## External API
### REST API
The Cloud Hypervisor [REST](https://en.wikipedia.org/wiki/Representational_state_transfer)
API triggers VM and VMM specific actions, and as such it is designed as a
collection of RPC-style, static methods.
The API is [OpenAPI 3.0](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md)
compliant. Please consult the [Cloud Hypervisor API](https://raw.githubusercontent.com/cloud-hypervisor/cloud-hypervisor/master/vmm/src/api/openapi/cloud-hypervisor.yaml)
document for more details about the API payloads and responses.
### Location and availability
The REST API is available as soon as the Cloud Hypervisor binary is started,
through a local UNIX socket.
By default, it is located at `/run/user/{user ID}/cloud-hypervisor.{Cloud Hypervisor PID}`.
For example, if you launched Cloud Hypervisor as user ID 1000 and its PID is
123456, the Cloud Hypervisor REST API will be available at `/run/user/1000/cloud-hypervisor.123456`.
The REST API default URL can be overridden through the Cloud Hypervisor
option `--api-socket`:
```
$ ./target/debug/cloud-hypervisor --api-socket /tmp/cloud-hypervisor.sock
Cloud Hypervisor Guest
API server: /tmp/cloud-hypervisor.sock
vCPUs: 1
Memory: 512 MB
Kernel: None
Kernel cmdline:
Disk(s): None
```
### Endpoints
The Cloud Hypervisor API exposes the following actions through its endpoints:
#### Virtual Machine Manager (VMM) Actions
Action | Endpoint | Request Body | Response Body | Prerequisites
------------------------------------|-----------------|--------------|----------------------------|---------------------------
Check for the REST API availability | `/vmm.ping` | N/A | `/schemas/VmmPingResponse` | N/A
Shut the VMM down | `/vmm.shutdown` | N/A | N/A | The VMM is running
#### Virtual Machine (VM) Actions
Action | Endpoint | Request Body | Response Body | Prerequisites
---------------------------------|----------------|---------------------|-------------------|---------------------------
Create the VM | `/vm.create` | `/schemas/VmConfig` | N/A | The VM is not created yet
Delete the VM | `/vm.delete` | N/A | N/A | The VM is created but not booted
Boot the VM | `/vm.boot` | N/A | N/A | The VM is created
Shut the VM down | `/vm.shutdown` | N/A | N/A | The VM is booted
Reboot the VM | `/vm.reboot` | N/A | N/A | The VM is booted
Pause the VM | `/vm.pause` | N/A | N/A | The VM is booted
Resume the VM | `/vm.resume` | N/A | N/A | The VM is paused
Add/remove CPUs to/from the VM | `/vm.resize` | `/schemas/VmResize` | N/A | The VM is booted
Remove memory from the VM | `/vm.resize` | `/schemas/VmResize` | N/A | The VM is booted
Dump the VM information | `/vm.info` | N/A | `/schemas/VmInfo` | The VM is created
### REST API Examples
For the following set of examples, we assume Cloud Hypervisor is started with
the REST API available at `/tmp/cloud-hypervisor.sock`:
```
$ ./target/debug/cloud-hypervisor --api-socket /tmp/cloud-hypervisor.sock
Cloud Hypervisor Guest
API server: /tmp/cloud-hypervisor.sock
vCPUs: 1
Memory: 512 MB
Kernel: None
Kernel cmdline:
Disk(s): None
```
#### Create a Virtual Machine
We want to create a virtual machine with the following characteristics:
* 4 vCPUs
* 1 GB of RAM
* 1 virtio based networking interface
* Direct kernel boot from a custom 5.5.0 Linux kernel located at
`/opt/clh/kernel/vmlinux-virtio-fs-virtio-iommu`
* Using a Clear Linux image as its root filesystem, located at
`/opt/clh/images/clear-30080-kvm.img`
```shell
#!/bin/bash
curl --unix-socket /tmp/cloud-hypervisor.sock -i \
-X PUT 'http://localhost/api/v1/vm.create' \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"cpus":{"boot_vcpus": 4, "max_vcpus": 4},
"kernel":{"path":"/opt/clh/kernel/vmlinux-virtio-fs-virtio-iommu"},
"cmdline":{"args":"console=hvc0 reboot=k panic=1 nomodules i8042.noaux i8042.nomux i8042.nopnp i8042.dumbkbd root=/dev/vda3"},
"disks":[{"path":"/opt/clh/images/clear-30080-kvm.img"}],
"rng":{"src":"/dev/urandom"},
"net":[{"ip":"192.168.10.10", "mask":"255.255.255.0", "mac":"12:34:56:78:90:01"}]
}'
```
#### Boot a Virtual Machine
Once the VM is created, we can boot it:
```shell
#!/bin/bash
curl --unix-socket /tmp/cloud-hypervisor.sock -i -X PUT 'http://localhost/api/v1/vm.boot'
```
#### Dump a Virtual Machine Information
We can fetch information about any VM, as soon as it's created:
```shell
#!/bin/bash
curl --unix-socket /tmp/cloud-hypervisor.sock -i \
-X GET 'http://localhost/api/v1/vm.info' \
-H 'Accept: application/json'
```
#### Reboot a Virtual Machine
We can reboot a VM that's already booted:
```shell
#!/bin/bash
curl --unix-socket /tmp/cloud-hypervisor.sock -i -X PUT 'http://localhost/api/v1/vm.reboot'
```
#### Shut a Virtual Machine Down
Once booted, we can shut a VM down from the REST API:
```shell
#!/bin/bash
curl --unix-socket /tmp/cloud-hypervisor.sock -i -X PUT 'http://localhost/api/v1/vm.shutdown'
```
### Command Line Interface
The Cloud Hypervisor Command Line Interface (CLI) can only be used for launching
the Cloud Hypervisor binary, i.e. it can not be used for controlling the VMM or
the launched VM once they're up and running.
If you want to inspect the VMM, or control the VM after launching Cloud
Hypervisor from the CLI, you must use the [REST API](#rest-api).
From the CLI, one can either:
1. Create and boot a complete virtual machine by using the CLI options to build
the VM config. Run `cloud-hypervisor --help` for a complete list of CLI
options. As soon as the `cloud-hypervisor` binary is launched, the
[REST API](#rest-api) is available for controlling and managing the VM.
1. Start the [REST API](#rest-api) server only, by not passing any VM
configuration options. The VM can then be asynchronously created and booted
by sending HTTP commands to the [REST API](#rest-api). Check the
[REST API examples](#rest-api-examples) section for more details.
### REST API and CLI Architectural Relationship
The REST API and the CLI both rely on a common, [internal API](#internal-api).
The CLI options are parsed by the
[clap crate](https://docs.rs/clap/2.33.0/clap/) and then translated into
[internal API](#internal-api) commands.
The REST API is processed by an HTTP thread using the
[Firecracker's `micro_http`](https://github.com/firecracker-microvm/firecracker/tree/master/src/micro_http)
crate. As with the CLI, the HTTP requests eventually get translated into
[internal API](#internal-api) commands.
As a summary, the REST API and the CLI are essentially frontends for the
[internal API](#internal-api):
```
+------------------+
REST API | |
+--------->+ micro_http +--------+
| | | |
| +------------------+ |
| | +------------------------+
| | | |
+------------+ | | | |
| | | | | +--------------+ |
| User +---------+ +------> | Internal API | |
| | | | | +--------------+ |
+------------+ | | | |
| | | |
| | +------------------------+
| +----------+ | VMM
| CLI | | |
+----------->+ clap +--------------+
| |
+----------+
```
## Internal API
The Cloud Hypervisor internal API, as its name suggests, is used internally
by the different Cloud Hypervisor threads (VMM, HTTP, control loop, etc) to
send commands and responses to each others.
It is based on [rust's Multi-Producer, Single-Consumer (MPSC)](https://doc.rust-lang.org/std/sync/mpsc/),
and the single consumer (a.k.a. the API receiver) is the Cloud Hypervisor
control loop.
API producers are the HTTP thread handling the [REST API](#rest-api) and the
main thread that initially parses the [CLI](#command-line-interface).
### Goals and Design
The internal API is designed for controlling, managing and inspecting a Cloud
Hypervisor VMM and its guest. It is a backend for handling external, user
visible requests through either the [REST API](#rest-api) or the
[CLI](#command-line-interface) interfaces.
The API follows a command-response scheme that closely maps the [REST API](#rest-api).
Any command must be replied to with a response.
Commands are [MPSC](https://doc.rust-lang.org/std/sync/mpsc/) based messages and
are received and processed by the VMM control loop.
In order for the VMM control loop to respond to any internal API command, it
must be able to send a response back to the MPSC sender. For that purpose, all
internal API command payload carry the [Sender](https://doc.rust-lang.org/std/sync/mpsc/struct.Sender.html)
end of an [MPSC](https://doc.rust-lang.org/std/sync/mpsc/) channel.
The sender of any internal API command is therefore responsible for:
1. Creating an [MPSC](https://doc.rust-lang.org/std/sync/mpsc/) response
channel.
1. Passing the [Sender](https://doc.rust-lang.org/std/sync/mpsc/struct.Sender.html)
end of the response channel as part of the internal API command payload.
1. Waiting for the internal API command's response on the [Receiver](https://doc.rust-lang.org/std/sync/mpsc/struct.Receiver.html)
end of the response channel.
## End to End Example
In order to further understand how the external and internal Cloud Hypervisor
APIs work together, let's look at a complete VM creation flow, from the
[REST API](#rest-api) call, to the reply the external user will receive:
1. A user or operator sends an HTTP request to the Cloud Hypervisor
[REST API](#rest-api) in order to creates a virtual machine:
```
shell
#!/bin/bash
curl --unix-socket /tmp/cloud-hypervisor.sock -i \
-X PUT 'http://localhost/api/v1/vm.create' \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"cpus":{"boot_vcpus": 4, "max_vcpus": 4},
"kernel":{"path":"/opt/clh/kernel/vmlinux-virtio-fs-virtio-iommu"},
"cmdline":{"args":"console=hvc0 reboot=k panic=1 nomodules i8042.noaux i8042.nomux i8042.nopnp i8042.dumbkbd root=/dev/vda3"},
"disks":[{"path":"/opt/clh/images/clear-30080-kvm.img"}],
"rng":{"src":"/dev/urandom"},
"net":[{"ip":"192.168.10.10", "mask":"255.255.255.0", "mac":"12:34:56:78:90:01"}]
}'
```
1. The Cloud Hypervisor HTTP thread processes the request and de-serializes the
HTTP request JSON body into an internal `VmConfig` structure.
1. The Cloud Hypervisor HTTP thread creates an
[MPSC](https://doc.rust-lang.org/std/sync/mpsc/) channel for the internal API
server to send its response back.
1. The Cloud Hypervisor HTTP thread prepares an internal API command for creating a
virtual machine. The command's payload is made of the de-serialized
`VmConfig` structure and the response channel:
```Rust
VmCreate(Arc<Mutex<VmConfig>>, Sender<ApiResponse>)
```
1. The Cloud Hypervisor HTTP thread sends the internal API command, and waits
for the response:
```Rust
// Send the VM creation request.
api_sender
.send(ApiRequest::VmCreate(config, response_sender))
.map_err(ApiError::RequestSend)?;
api_evt.write(1).map_err(ApiError::EventFdWrite)?;
response_receiver.recv().map_err(ApiError::ResponseRecv)??;
```
1. The Cloud Hypervisor control loop receives the command, as it listens on the
internal API [MPSC](https://doc.rust-lang.org/std/sync/mpsc/) channel:
```Rust
// Read from the API receiver channel
let api_request = api_receiver.recv().map_err(Error::ApiRequestRecv)?;
```
1. The Cloud Hypervisor control loop matches the received internal API against
the `VmCreate` payload, and extracts both the `VmConfig` structure and the
[Sender](https://doc.rust-lang.org/std/sync/mpsc/struct.Sender.html) from the
command payload. It stores the `VmConfig` structure and replies back to the
sender ((The HTTP thread):
```Rust
match api_request {
ApiRequest::VmCreate(config, sender) => {
// We only store the passed VM config.
// The VM will be created when being asked to boot it.
let response = if self.vm_config.is_none() {
self.vm_config = Some(config);
Ok(ApiResponsePayload::Empty)
} else {
Err(ApiError::VmAlreadyCreated)
};
sender.send(response).map_err(Error::ApiResponseSend)?;
}
```
1. The Cloud Hypervisor HTTP thread receives the internal API command response
as the return value from its `VmCreate` HTTP handler. Depending on the
control loop internal API response, it generates the appropriate HTTP
response:
```Rust
// Call vm_create()
match vm_create(api_notifier, api_sender, Arc::new(Mutex::new(vm_config)))
.map_err(HttpError::VmCreate)
{
Ok(_) => Response::new(Version::Http11, StatusCode::NoContent),
Err(e) => error_response(e, StatusCode::InternalServerError),
}
```
1. The Cloud Hypervisor HTTP thread sends the formed HTTP response back to the
user. This is abstracted by the
[micro_http](https://github.com/firecracker-microvm/firecracker/tree/master/src/micro_http)
crate.

208
docs/device_model.md Normal file
View File

@@ -0,0 +1,208 @@
# Device Model
This document describes the device model supported by `cloud-hypervisor`.
## Summary
| Device | Build configurable | Enabled by default | Runtime configurable |
| :----: | :----: | :----: | :----: |
| Serial port | :negative_squared_cross_mark: | :negative_squared_cross_mark: | :heavy_check_mark: |
| RTC/CMOS | :heavy_check_mark: | :heavy_check_mark: | :negative_squared_cross_mark: |
| I/O APIC | :negative_squared_cross_mark: | :negative_squared_cross_mark: | :heavy_check_mark: |
| i8042 shutdown/reboot | :negative_squared_cross_mark: | :negative_squared_cross_mark: | :negative_squared_cross_mark: |
| ACPI shutdown/reboot | :negative_squared_cross_mark: | :heavy_check_mark: | :negative_squared_cross_mark: |
| virtio-blk | :negative_squared_cross_mark: | :negative_squared_cross_mark: | :heavy_check_mark: |
| virtio-console | :negative_squared_cross_mark: | :negative_squared_cross_mark: | :heavy_check_mark: |
| virtio-iommu | :negative_squared_cross_mark: | :negative_squared_cross_mark: | :heavy_check_mark: |
| virtio-net | :negative_squared_cross_mark: | :negative_squared_cross_mark: | :heavy_check_mark: |
| virtio-pmem | :negative_squared_cross_mark: | :negative_squared_cross_mark: | :heavy_check_mark: |
| virtio-rng | :negative_squared_cross_mark: | :negative_squared_cross_mark: | :heavy_check_mark: |
| virtio-vsock | :negative_squared_cross_mark: | :negative_squared_cross_mark: | :heavy_check_mark: |
| vhost-user-blk | :negative_squared_cross_mark: | :negative_squared_cross_mark: | :heavy_check_mark: |
| vhost-user-fs | :negative_squared_cross_mark: | :negative_squared_cross_mark: | :heavy_check_mark: |
| vhost-user-net | :negative_squared_cross_mark: | :negative_squared_cross_mark: | :heavy_check_mark: |
| VFIO | :heavy_check_mark: | :negative_squared_cross_mark: | :heavy_check_mark: |
## Legacy devices
### Serial port
Simple emulation of a serial port by reading and writing to specific port I/O
addresses. Used as the default console for Linux when booting with the option
`console=ttyS0`, the serial port can be very useful to gather early logs from
the operating system booted inside the VM.
This device is always built-in, and it is disabled by default. It can be
enabled with the `--serial` option, as long as its parameter is not `off`.
### RTC/CMOS
For environments such as Windows or EFI which cannot rely on KVM clock, the
emulation of this legacy device makes the platform usable.
This device is built-in by default, but it can be compiled out with Rust
features. When compiled in, it is always enabled, and cannot be disabled
from the command line.
### I/O APIC
`cloud-hypervisor` supports a so-called split IRQ chip implementation by
implementing support for the [IOAPIC](https://wiki.osdev.org/IOAPIC).
By moving part of the IRQ chip implementation from kernel space to user space,
the IRQ chip emulation does not always run in a fully privileged mode.
The device is always built-in, and it is enabled depending on the presence of
the serial port. If the serial port is disabled, and because no other device
would require pin based interrupts (INTx), the I/O APIC is disabled.
### i8042
Simplified PS/2 port since it supports only one key to trigger a reboot or
shutdown, depending on the ACPI support.
This device is always built-in, but it is disabled by default. Because ACPI is
enabled by default, the handling of reboot/shutdown goes through the dedicated
ACPI device. In case ACPI is disabled, this device is enabled to bring to the
VM some reboot/shutdown support.
### ACPI device
This is a dedicated device for handling ACPI shutdown and reboot when ACPI is
enabled.
This device is always built-in, and it is enabled by default since the ACPI
feature is enabled by default.
## Virtio devices
For all virtio devices listed below, both `virtio-mmio` and `virtio-pci`
transport layers are supported, `virtio-pci` being the default.
Both `virtio-mmio` and `virtio-pci` can be compiled out. `virtio-pci` is
built-in by default, and enabled by default. If both transport layers were
built at the same time, `virtio-pci` would be the default transport layer.
### virtio-block
The `virtio-blk` device exposes a block device to the guest. This device is
usually used to boot the operating system running in the VM.
This device is always built-in, and it is enabled based on the presence of the
flag `--disk`.
### virtio-console
`cloud-hypervisor` exposes a `virtio-console` device to the guest. Although
using this device as a guest console can potentially cut some early boot
messages, it can reduce the guest boot time and provides a complete console
implementation.
This device is always built-in, and it is enabled by default to provide a guest
console. It can be disabled, switching back to the legacy serial port by
selecting `--serial tty --console off` from the command line.
### virtio-iommu
As we want to improve our nested guests support, we added support for exposing
a [paravirtualized IOMMU](https://github.com/cloud-hypervisor/cloud-hypervisor/blob/master/docs/iommu.md)
device through virtio. This allows for a safer nested virtio and directly
assigned devices support.
This device is always built-in, and it is enabled based on the presence of the
parameter `iommu=on` in any of the virtio or VFIO devices. If at least one of
these devices needs to be connected to the paravirtualized IOMMU, the
`virtio-iommu` device will be created.
### virtio-net
The `virtio-net` device provides network connectivity for the guest, as it
creates a network interface connected to a TAP interface automatically created
by the `cloud-hypervisor` on the host.
This device is always built-in, and it is enabled based on the presence of the
flag `--net`.
### virtio-pmem
The `virtio-pmem` implementation emulates a virtual persistent memory device
that `cloud-hypervisor` can e.g. boot from. Booting from a `virtio-pmem` device
allows to bypass the guest page cache and improve the guest memory footprint.
This device is always built-in, and it is enabled based on the presence of the
flag `--pmem`.
### virtio-rng
A VM does not generate entropy like a real machine would, which is an issue
when workloads running in the guest need random numbers to be generated. The
`virtio-rng` device provides entropy to the guest by relying on the generator
that can be found on the host. By default, the chosen source of entropy is
`/dev/urandom`.
This device is always built-in, and it is always enabled. The `--rng` flag can
be used to change the source of entropy.
### virtio-vsock
In order to more efficiently and securely communicate between host and guest,
we added a hybrid implementation of the [VSOCK](http://man7.org/linux/man-pages/man7/vsock.7.html)
socket address family over virtio.
Credits go to the [Firecracker](https://github.com/firecracker-microvm/firecracker/blob/master/docs/vsock.md)
project as our implementation is a copy of theirs.
This device is always built-in, and it is enabled based on the presence of the
flag `--vsock`.
## Vhost-user devices
Vhost-user devices are virtio backends running outside of the VMM, as its own
separate process. They are usually used to bring more flexibility and increased
isolation.
### vhost-user-blk
As part of the general effort to offload paravirtualized I/O to external
processes, we added support for vhost-user-blk backends. This enables
`cloud-hypervisor` users to plug a `vhost-user` based block device (e.g. SPDK)
into the VMM as their virtio block backend.
This device is always built-in, and it is enabled when `vhost_user=true` and
`socket` are provided to the `--disk` parameter.
### vhost-user-fs
`cloud-hypervisor` supports the [virtio-fs](https://virtio-fs.gitlab.io/)
shared file system, allowing for an efficient and reliable way of sharing
a filesystem between the host and the cloud-hypervisor guest.
See our [filesystem sharing](https://github.com/cloud-hypervisor/cloud-hypervisor/blob/master/docs/fs.md)
documentation for more details on how to use virtio-fs with cloud-hypervisor.
This device is always built-in, and it is enabled based on the presence of the
flag `--fs`.
### vhost-user-net
As part of the general effort to offload paravirtualized I/O to external
processes, we added support for [vhost-user-net](https://access.redhat.com/solutions/3394851)
backends. This enables `cloud-hypervisor` users to plug a `vhost-user` based
networking device (e.g. DPDK) into the VMM as their virtio network backend.
This device is always built-in, and it is enabled when `vhost_user=true` and
`socket` are provided to the `--net` parameter.
## VFIO
VFIO (Virtual Function I/O) is a kernel framework that exposes direct device
access to userspace. `cloud-hypervisor` uses VFIO to directly assign host
physical devices into its guest.
See our [VFIO documentation](https://github.com/cloud-hypervisor/cloud-hypervisor/blob/master/docs/vfio.md)
for more details on how to directly assign host devices to `cloud-hypervisor`
guests.
Because VFIO implies `vfio-pci` in the `cloud-hypervisor` context, the VFIO
support is built-in when the `pci` feature is selected. And because the `pci`
feature is built-in by default, VFIO support is also built-in by default.
When VFIO support is built-in, a physical device can be passed through, using
the flag `--device` in order to enable the VFIO code.

View File

@@ -10,11 +10,12 @@ __virtio-fs__, also known as __vhost-user-fs__ is a virtual device defined by th
This virtual device relies on the _vhost-user_ protocol, which assumes the backend (device emulation) is handled by a dedicated process running on the host. This daemon is called __virtiofsd__ and needs to be present on the host. This virtual device relies on the _vhost-user_ protocol, which assumes the backend (device emulation) is handled by a dedicated process running on the host. This daemon is called __virtiofsd__ and needs to be present on the host.
_Install virtiofsd_ _Build virtiofsd_
```bash ```bash
VIRTIOFSD_URL="$(curl --silent https://api.github.com/repos/intel/nemu/releases/latest | grep "browser_download_url" | grep "virtiofsd-x86_64" | grep -o 'https://.*[^ "]')" git clone --depth 1 "https://github.com/sboeuf/qemu.git" -b "virtio-fs" $VIRTIOFSD_DIR
wget --quiet $VIRTIOFSD_URL -O "virtiofsd" cd $VIRTIOFSD_DIR
chmod +x "virtiofsd" ./configure --prefix=$PWD --target-list=x86_64-softmmu
make virtiofsd -j `nproc`
sudo setcap cap_sys_admin+epi "virtiofsd" sudo setcap cap_sys_admin+epi "virtiofsd"
``` ```
_Create shared directory_ _Create shared directory_
@@ -25,17 +26,20 @@ _Run virtiofsd_
```bash ```bash
./virtiofsd \ ./virtiofsd \
-d \ -d \
-o vhost_user_socket=/tmp/virtiofs \ --socket-path=/tmp/virtiofs \
-o source=/tmp/shared_dir \ -o source=/tmp/shared_dir \
-o cache=none -o cache=none
``` ```
The `cache=none` option here is an important one as it tells the daemon not to try any memory mapping of the files, but instead to use the _virtqueues_ to convey the files content. The support for the memory mapping of the files will be added later.
The `cache=none` option should be the default when using `virtiofsd` with the __cloud-hypervisor__ VMM. This prevents from using the guest page cache, which reduces the memory footprint of the guest. When running multiple virtual machines on the same host, this will let the host deal with page cache, which will increase the density of virtual machines which can be launched.
The `cache=always` option will allow for the guest page cache to be used, which will increase the memory footprint of the guest. This option should be used only for specific use cases where a single VM is going to be running on a host.
### The kernel ### The kernel
In order to leverage __virtio-fs__ support from within the guest, and because the code has not been merged in upstream Linux kernel yet, it is required to build a custom kernel embedding the patches. In order to leverage __virtio-fs__ support from within the guest, and because the code has not been merged in upstream Linux kernel yet, it is required to build a custom kernel embedding the patches.
The following branch `virtio-pmem_and_virtio-fs` on the repository https://github.com/sboeuf/linux.git includes all the needed patches to support __virtio-fs__. The following branch `virtio-fs-virtio-iommu` on the repository https://github.com/cloud-hypervisor/linux.git includes all the needed patches to support __virtio-fs__.
Make sure to build a kernel out of this branch that can be then used to boot the VM. Make sure to build a kernel out of this branch that can be then used to boot the VM.
@@ -56,15 +60,29 @@ Assuming you have `clear-kvm.img` and `custom-vmlinux.bin` on your system, here
--disk path=clear-kvm.img \ --disk path=clear-kvm.img \
--kernel custom-vmlinux.bin \ --kernel custom-vmlinux.bin \
--cmdline "console=ttyS0 reboot=k panic=1 nomodules root=/dev/vda3" \ --cmdline "console=ttyS0 reboot=k panic=1 nomodules root=/dev/vda3" \
--fs tag=virtiofs,sock=/tmp/virtiofs,num_queues=1,queue_size=512 --fs tag=myfs,sock=/tmp/virtiofs,num_queues=1,queue_size=512
```
By default, DAX is enabled with a cache window of 8GiB. You can specify a custom size (let's say 4GiB for this example) for the cache by explicitly setting DAX and the cache size:
```bash
--fs tag=virtiofs,sock=/tmp/virtiofs,num_queues=1,queue_size=512,dax=on,cache_size=4G
```
In case you don't want to use a shared window of cache to pass the shared files content, this means you will have to explicitly disable DAX with `dax=off`. Note that in this case, the `cache_size` parameter will be ignored.
```bash
--fs tag=virtiofs,sock=/tmp/virtiofs,num_queues=1,queue_size=512,dax=off
``` ```
### Mount the shared directory ### Mount the shared directory
The last step is to mount the shared directory inside the guest, using the `virtio_fs` filesystem type. The last step is to mount the shared directory inside the guest, using the `virtiofs` filesystem type.
```bash ```bash
mkdir mount_dir mkdir mount_dir
mount \ mount -t virtiofs -o dax myfs mount_dir/
-t virtio_fs /dev/null mount_dir/ \
-o tag=virtiofs,rootmode=040000,user_id=0,group_id=0
``` ```
The `tag` needs to be consistent with what has been provided through the __cloud-hypervisor__ command line. The `tag` needs to be consistent with what has been provided through the __cloud-hypervisor__ command line, which happens to be `myfs` in this example.
The `-o dax` option must be removed in case the shared cache region is not enabled from the VMM.

113
docs/hotplug.md Normal file
View File

@@ -0,0 +1,113 @@
# Cloud Hypervisor Hot Plug
Currently Cloud Hypervisor only support hot plugging of CPU devices.
## Kernel support
For hotplug on Cloud Hypervisor ACPI GED support is needed. This can either be achieved by turning on `CONFIG_ACPI_REDUCED_HARDWARE_ONLY`
or by using this kernel patch (available in 5.5rc1 and later): https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/patch/drivers/acpi/Makefile?id=ac36d37e943635fc072e9d4f47e40a48fbcdb3f0
This patch is integrated into the Clear Linux KVM and cloudguest images.
## CPU Hot Plug
Extra vCPUs can be added and removed from a running Cloud Hypervisor instance. This is controlled by two mechanisms:
1. Specifying a number of maximum potential vCPUs that is greater than the number of default (boot) vCPUs.
2. Making a HTTP API request to the VMM to ask for the additional vCPUs to be added.
To use CPU hotplug start the VM with the number of max vCPUs greater than the number of boot vCPUs, e.g.
```shell
$ pushd $CLOUDH
$ sudo setcap cap_net_admin+ep ./cloud-hypervisor/target/release/cloud-hypervisor
$ ./cloud-hypervisor/target/release/cloud-hypervisor \
--kernel ./hypervisor-fw \
--disk path=clear-31890-kvm.img \
--cpus boot=4,max=8 \
--memory size=1024M \
--net "tap=,mac=,ip=,mask=" \
--rng \
--api-socket=/tmp/ch-socket
$ popd
```
Notice the addition of `--api-socket=/tmp/ch-socket` and a `max` parameter on `--cpus boot=4.max=8`.
To ask the VMM to add additional vCPUs then use the resize API:
```shell
curl -H "Accept: application/json" -H "Content-Type: application/json" -i -XPUT --unix-socket /tmp/ch-socket -d "{ \"desired_vcpus\":8}" http://localhost/api/v1/vm.resize
```
The extra vCPU threads will be created and advertised to the running kernel. The kernel does not bring up the CPUs immediately and instead the user must "online" them from inside the VM:
```shell
root@ch-guest ~ # lscpu | grep list:
On-line CPU(s) list: 0-3
Off-line CPU(s) list: 4-7
root@ch-guest ~ # echo 1 | tee /sys/devices/system/cpu/cpu[4,5,6,7]/online
1
root@ch-guest ~ # lscpu | grep list:
On-line CPU(s) list: 0-7
```
After a reboot the added CPUs will remain.
Removing CPUs works similarly by reducing the number in the "desired_vcpus" field of the reisze API. The CPUs will be automatically offlined inside the guest so there is no need to run any commands inside the guest:
```shell
curl -H "Accept: application/json" -H "Content-Type: application/json" -i -XPUT --unix-socket /tmp/ch-socket -d "{ \"desired_vcpus\":2}" http://localhost/api/v1/vm.resize
```
As per adding CPUs to the guest, after a reboot the VM will be running with the reduced number of vCPUs.
## Memory Hot Plug
Extra memory can be added from a runing Cloud Hypervisor instance. This is controlled by two mechanisms:
1. Allocating some of the guest physical address space for hotplug memory.
2. Making a HTTP API request to the VMM to ask for a new amount of RAM to be assigned to the VM. In the case of expanding the memory for the VM the new memory will be hotplugged into the running VM, if reducing the size of the memory then change will take effect after the next reboot.
To use memory hotplug start the VM specifying some size RAM in the "hotplug_size" parameter to the memory configuration. Not all the memory specified in this parameter will be available to hotplug as there are spacing and alignment requirements so it is recommended to make it larger than the hotplug RAM needed.
```shell
$ pushd $CLOUDH
$ sudo setcap cap_net_admin+ep ./cloud-hypervisor/target/release/cloud-hypervisor
$ ./cloud-hypervisor/target/release/cloud-hypervisor \
--kernel ./hypervisor-fw \
--disk path=clear-31890-kvm.img \
--cpus boot=4,max=8 \
--memory size=1024M,hotplug_size=8192M \
--net "tap=,mac=,ip=,mask=" \
--rng \
--api-socket=/tmp/ch-socket
$ popd
```
Before issuing the API request it is necessary to run the following command inside the VM to make it automatically online the added memory:
```shell
root@ch-guest ~ # echo online | sudo tee /sys/devices/system/memory/auto_online_blocks
```
To ask the VMM to add expand the RAM for the VM:
```shell
curl -H "Accept: application/json" -H "Content-Type: application/json" -i -XPUT --unix-socket /tmp/ch-socket -d "{ \"desired_vcpus\": 4, \"desired_ram\" : 3221225472}" http://localhost/api/v1/vm.resize
```
The new memory is now available to use inside the VM:
```shell
free -h
total used free shared buff/cache available
Mem: 3.0Gi 71Mi 2.8Gi 0.0Ki 47Mi 2.8Gi
Swap: 32Mi 0B 32Mi
```
Due to guest OS limitations is is necessary to ensure that amount of memory added (between currently assigned RAM and that which is desired) is a multiple of 128MiB.
The same API can also be used to reduce the desired RAM for a VM but the change will not be applied until the VM is rebooted.
Memory and CPU resizing can be combined together into the same HTTP API request.

View File

@@ -1,6 +1,21 @@
# How to use networking # How to use networking
cloud-hypervisor can emulate one or more virtual network interfaces, represented at the hypervisor host by [tap devices](https://www.kernel.org/doc/Documentation/networking/tuntap.txt"). This guide briefly describes, in a manual and distribution neutral way, how to setup and use networking with cloud-hypevisor. cloud-hypervisor can emulate one or more virtual network interfaces, represented at the hypervisor host by [tap devices](https://www.kernel.org/doc/Documentation/networking/tuntap.txt). This guide briefly describes, in a manual and distribution neutral way, how to setup and use networking with cloud-hypevisor.
## Multiple queue support for net devices ##
While multiple vcpus defined for guest, to gain the benefit of vcpu scalable to improve performance, it suggests to define multiple queue pairs for net devices, one Tx/Rx queue pair per one vcpu, that means the number of queue pairs at least is equal to the vcpu count. In that case, after virtnet driver set cpu affinity for virtqueues in guest kernel, vcpus could handle interrupt from different virtqueue pairs in parallel.
It will gain better performance for guest that has multiple queues defined for net devices while it has multiple net sessions running in userspace.
To enable multiple queue support in cloud-hypervisor, multiple queue pairs will be defined, while multiple tap fds will be opened for the same tap device, it will also have multiple threads started, each thread will monitor and handle the events from each virtqueue pairs and the associated tap fd.
Note:
- Currently, it does not support to use ethtool to change the combined queue numbers in guest.
- Multiple queue is enabled for vhost-user-net backend in cloud-hypervisor, however, multiple thread is not added to handle mq, thus, the performance for vhost-user-net backend is not supposed to be improved. The multiple thread will be added for backend later.
- Performance test for vhost-user-net will be covered once vhost-user-net backend has mulitple thread supported.
- Performance test for virtio-net is done by comparing 2 queue pairs with 1 queue pairs, that to run 2 iperf3 sessions in the same test environments, throughput is improved about 37%.
## Start cloud-hypervisor with net devices ## Start cloud-hypervisor with net devices
@@ -13,18 +28,29 @@ Use one `--net` command-line argument from cloud-hypervisor to specify the emula
--disk path=my-root-disk.img \ --disk path=my-root-disk.img \
--kernel my-vmlinux.bin \ --kernel my-vmlinux.bin \
--cmdline "console=ttyS0 reboot=k panic=1 nomodules root=/dev/vda3" \ --cmdline "console=ttyS0 reboot=k panic=1 nomodules root=/dev/vda3" \
--net tap=ich0,mac=a4:a1:c2:00:00:01,ip=192.168.4.2,mask=255.255.255.0 \ --net tap=ich0,mac=a4:a1:c2:00:00:01,ip=192.168.4.2,mask=255.255.255.0,num_queues=2,queue_size=256 \
tap=ich1,mac=a4:a1:c2:00:00:02,ip=10.0.1.2,mask=255.255.255.0 tap=ich1,mac=a4:a1:c2:00:00:02,ip=10.0.1.2,mask=255.255.255.0,num_queues=2,queue_size=256
``` ```
The `--net` argument takes 1 or more space-separated strings of key value pairs containing the following 4 keys or fields: The `--net` argument takes 1 or more space-separated strings of key value pairs containing the following 4 keys or fields:
| Name | Purpose | Optional | | Name | Purpose | Optional |
| -------- |----------------------------| ----------| | -----------|----------------------------| ----------|
| tap | tap device name | Yes | | tap | tap device name | Yes |
| mac | vNIC mac address | Yes | | mac | vNIC mac address | Yes |
| ip | tap IP IP address | yes | | ip | tap IP IP address | yes |
| mask | tap IP netmask | Yes | | mask | tap IP netmask | Yes |
| num_queues | the number of queues | yes |
| queue_size | the size of each queue | Yes |
num_queues is the total number of tx and rx queues, the default value is 2, and it could be increased by multiples of 2. Additionally, num_queues is suggested to be as 2 times of vcpu count. The default value for queue_size is 256.
If the tap device is pre-created on host before guest boot up. To use multiple queue support for net device in guest, the tap device should be opened like this from host.
```bash
[root@localhost ~]# ip tuntap add name ich0 mode tap multi_queue
```
## Configure the tap devices ## Configure the tap devices

View File

@@ -4,4 +4,4 @@ version = "0.1.0"
authors = ["The Chromium OS Authors"] authors = ["The Chromium OS Authors"]
[dependencies] [dependencies]
vmm-sys-util = "0.1.1" vmm-sys-util = "0.4.0"

View File

@@ -4,14 +4,14 @@ version = "0.1.0"
authors = ["The Chromium OS Authors"] authors = ["The Chromium OS Authors"]
[dependencies] [dependencies]
libc = "0.2.60" libc = "0.2.66"
rand = "0.7.0" rand = "0.7.3"
serde = "1.0.98" serde = "1.0.104"
vmm-sys-util = "0.1.1" vmm-sys-util = "0.4.0"
net_gen = { path = "../net_gen" } net_gen = { path = "../net_gen" }
[dev-dependencies] [dev-dependencies]
lazy_static = "1.3.0" lazy_static = "1.4.0"
pnet = "0.22.0" pnet = "0.25.0"
serde_json = "1.0.40" serde_json = "1.0.48"

View File

@@ -7,6 +7,7 @@
use rand::Rng; use rand::Rng;
use std::fmt; use std::fmt;
use std::io;
use std::result::Result; use std::result::Result;
use serde::de::{Deserialize, Deserializer, Error}; use serde::de::{Deserialize, Deserializer, Error};
@@ -20,23 +21,31 @@ pub struct MacAddr {
} }
impl MacAddr { impl MacAddr {
// The error contains the str that failed to be parsed, for nicer error message generation. pub fn parse_str<S>(s: &S) -> Result<MacAddr, io::Error>
pub fn parse_str<S>(s: &S) -> Result<MacAddr, &str>
where where
S: AsRef<str> + ?Sized, S: AsRef<str> + ?Sized,
{ {
let v: Vec<&str> = s.as_ref().split(':').collect(); let v: Vec<&str> = s.as_ref().split(':').collect();
let mut bytes = [0u8; MAC_ADDR_LEN]; let mut bytes = [0u8; MAC_ADDR_LEN];
let common_err = Err(io::Error::new(
io::ErrorKind::Other,
format!("parsing of {} into a MAC address failed", s.as_ref()),
));
if v.len() != MAC_ADDR_LEN { if v.len() != MAC_ADDR_LEN {
return Err(s.as_ref()); return common_err;
} }
for i in 0..MAC_ADDR_LEN { for i in 0..MAC_ADDR_LEN {
if v[i].len() != 2 { if v[i].len() != 2 {
return Err(s.as_ref()); return common_err;
} }
bytes[i] = u8::from_str_radix(v[i], 16).map_err(|_| s.as_ref())?; bytes[i] = u8::from_str_radix(v[i], 16).map_err(|e| {
io::Error::new(
io::ErrorKind::Other,
format!("parsing of {} into a MAC address failed: {}", s.as_ref(), e),
)
})?;
} }
Ok(MacAddr { bytes }) Ok(MacAddr { bytes })
@@ -106,7 +115,8 @@ impl<'de> Deserialize<'de> for MacAddr {
D: Deserializer<'de>, D: Deserializer<'de>,
{ {
let s = String::deserialize(deserializer)?; let s = String::deserialize(deserializer)?;
MacAddr::parse_str(&s).map_err(|_| D::Error::custom("The provided MAC address is invalid.")) MacAddr::parse_str(&s)
.map_err(|e| D::Error::custom(format!("The provided MAC address is invalid: {}", e)))
} }
} }

View File

@@ -40,7 +40,7 @@ pub type Result<T> = ::std::result::Result<T, Error>;
#[derive(Debug)] #[derive(Debug)]
pub struct Tap { pub struct Tap {
tap_file: File, tap_file: File,
if_name: [u8; 16usize], if_name: Vec<u8>,
} }
impl PartialEq for Tap { impl PartialEq for Tap {
@@ -53,7 +53,7 @@ impl std::clone::Clone for Tap {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Tap { Tap {
tap_file: self.tap_file.try_clone().unwrap(), tap_file: self.tap_file.try_clone().unwrap(),
if_name: self.if_name, if_name: self.if_name.clone(),
} }
} }
} }
@@ -78,7 +78,7 @@ fn build_terminated_if_name(if_name: &str) -> Result<Vec<u8>> {
} }
impl Tap { impl Tap {
pub fn open_named(if_name: &str) -> Result<Tap> { pub fn open_named(if_name: &str, num_queue_pairs: usize) -> Result<Tap> {
let terminated_if_name = build_terminated_if_name(if_name)?; let terminated_if_name = build_terminated_if_name(if_name)?;
let fd = unsafe { let fd = unsafe {
@@ -107,6 +107,9 @@ impl Tap {
name_slice.copy_from_slice(terminated_if_name.as_slice()); name_slice.copy_from_slice(terminated_if_name.as_slice());
*ifru_flags = *ifru_flags =
(net_gen::IFF_TAP | net_gen::IFF_NO_PI | net_gen::IFF_VNET_HDR) as c_short; (net_gen::IFF_TAP | net_gen::IFF_NO_PI | net_gen::IFF_VNET_HDR) as c_short;
if num_queue_pairs > 1 {
*ifru_flags |= net_gen::IFF_MULTI_QUEUE as c_short;
}
} }
// ioctl is safe since we call it with a valid tap fd and check the return // ioctl is safe since we call it with a valid tap fd and check the return
@@ -117,16 +120,19 @@ impl Tap {
return Err(Error::CreateTap(IoError::last_os_error())); return Err(Error::CreateTap(IoError::last_os_error()));
} }
let if_name_temp = unsafe { *ifreq.ifr_ifrn.ifrn_name.as_ref() };
let mut if_name = if_name_temp.to_vec();
if_name.truncate(terminated_if_name.len() - 1);
// Safe since only the name is accessed, and it's cloned out. // Safe since only the name is accessed, and it's cloned out.
Ok(Tap { Ok(Tap {
tap_file: tuntap, tap_file: tuntap,
if_name: unsafe { *ifreq.ifr_ifrn.ifrn_name.as_ref() }, if_name,
}) })
} }
/// Create a new tap interface. /// Create a new tap interface.
pub fn new() -> Result<Tap> { pub fn new(num_queue_pairs: usize) -> Result<Tap> {
Self::open_named("vmtap%d") Self::open_named("vmtap%d", num_queue_pairs)
} }
/// Set the host-side IP address for the tap interface. /// Set the host-side IP address for the tap interface.
@@ -232,11 +238,16 @@ impl Tap {
// in a single-field union. // in a single-field union.
unsafe { unsafe {
let ifrn_name = ifreq.ifr_ifrn.ifrn_name.as_mut(); let ifrn_name = ifreq.ifr_ifrn.ifrn_name.as_mut();
ifrn_name.clone_from_slice(&self.if_name); let name_slice = &mut ifrn_name[..self.if_name.len()];
name_slice.copy_from_slice(&self.if_name);
} }
ifreq ifreq
} }
pub fn get_if_name(&self) -> Vec<u8> {
self.if_name.clone()
}
} }
impl Read for Tap { impl Read for Tap {
@@ -423,7 +434,7 @@ mod tests {
#[test] #[test]
fn test_tap_create() { fn test_tap_create() {
let t = Tap::new().unwrap(); let t = Tap::new(1).unwrap();
println!("created tap: {:?}", t); println!("created tap: {:?}", t);
} }
@@ -435,7 +446,7 @@ mod tests {
// the end of the function. // the end of the function.
let tap_ip_guard = TAP_IP_LOCK.lock().unwrap(); let tap_ip_guard = TAP_IP_LOCK.lock().unwrap();
let tap = Tap::new().unwrap(); let tap = Tap::new(1).unwrap();
let ip_addr: net::Ipv4Addr = (*tap_ip_guard).parse().unwrap(); let ip_addr: net::Ipv4Addr = (*tap_ip_guard).parse().unwrap();
let netmask: net::Ipv4Addr = SUBNET_MASK.parse().unwrap(); let netmask: net::Ipv4Addr = SUBNET_MASK.parse().unwrap();
@@ -448,21 +459,21 @@ mod tests {
#[test] #[test]
fn test_set_options() { fn test_set_options() {
// This line will fail to provide an initialized FD if the test is not run as root. // This line will fail to provide an initialized FD if the test is not run as root.
let tap = Tap::new().unwrap(); let tap = Tap::new(1).unwrap();
tap.set_vnet_hdr_size(16).unwrap(); tap.set_vnet_hdr_size(16).unwrap();
tap.set_offload(0).unwrap(); tap.set_offload(0).unwrap();
} }
#[test] #[test]
fn test_tap_enable() { fn test_tap_enable() {
let tap = Tap::new().unwrap(); let tap = Tap::new(1).unwrap();
let ret = tap.enable(); let ret = tap.enable();
assert!(ret.is_ok()); assert!(ret.is_ok());
} }
#[test] #[test]
fn test_tap_get_ifreq() { fn test_tap_get_ifreq() {
let tap = Tap::new().unwrap(); let tap = Tap::new(1).unwrap();
let ret = tap.get_ifreq(); let ret = tap.get_ifreq();
assert_eq!( assert_eq!(
"__BindgenUnionField", "__BindgenUnionField",
@@ -472,7 +483,7 @@ mod tests {
#[test] #[test]
fn test_raw_fd() { fn test_raw_fd() {
let tap = Tap::new().unwrap(); let tap = Tap::new(1).unwrap();
assert_eq!(tap.as_raw_fd(), tap.tap_file.as_raw_fd()); assert_eq!(tap.as_raw_fd(), tap.tap_file.as_raw_fd());
} }
@@ -480,7 +491,7 @@ mod tests {
fn test_read() { fn test_read() {
let tap_ip_guard = TAP_IP_LOCK.lock().unwrap(); let tap_ip_guard = TAP_IP_LOCK.lock().unwrap();
let mut tap = Tap::new().unwrap(); let mut tap = Tap::new(1).unwrap();
tap.set_ip_addr((*tap_ip_guard).parse().unwrap()).unwrap(); tap.set_ip_addr((*tap_ip_guard).parse().unwrap()).unwrap();
tap.set_netmask(SUBNET_MASK.parse().unwrap()).unwrap(); tap.set_netmask(SUBNET_MASK.parse().unwrap()).unwrap();
tap.enable().unwrap(); tap.enable().unwrap();
@@ -541,7 +552,7 @@ mod tests {
fn test_write() { fn test_write() {
let tap_ip_guard = TAP_IP_LOCK.lock().unwrap(); let tap_ip_guard = TAP_IP_LOCK.lock().unwrap();
let mut tap = Tap::new().unwrap(); let mut tap = Tap::new(1).unwrap();
tap.set_ip_addr((*tap_ip_guard).parse().unwrap()).unwrap(); tap.set_ip_addr((*tap_ip_guard).parse().unwrap()).unwrap();
tap.set_netmask(SUBNET_MASK.parse().unwrap()).unwrap(); tap.set_netmask(SUBNET_MASK.parse().unwrap()).unwrap();
tap.enable().unwrap(); tap.enable().unwrap();

View File

@@ -6,11 +6,9 @@ edition = "2018"
[dependencies] [dependencies]
vm-allocator = { path = "../vm-allocator" } vm-allocator = { path = "../vm-allocator" }
byteorder = "1.3.2" byteorder = "1.3.4"
devices = { path = "../devices" } devices = { path = "../devices" }
kvm-bindings = "0.1.1" libc = "0.2.66"
kvm-ioctls = { git = "https://github.com/rust-vmm/kvm-ioctls", branch = "master" }
libc = "0.2.60"
log = "0.4.8" log = "0.4.8"
vm-device = { path = "../vm-device" }
vm-memory = { git = "https://github.com/rust-vmm/vm-memory" } vm-memory = { git = "https://github.com/rust-vmm/vm-memory" }
vmm-sys-util = "0.1.1"

View File

@@ -5,12 +5,13 @@
use crate::configuration::{ use crate::configuration::{
PciBarRegionType, PciBridgeSubclass, PciClassCode, PciConfiguration, PciHeaderType, PciBarRegionType, PciBridgeSubclass, PciClassCode, PciConfiguration, PciHeaderType,
}; };
use crate::device::{Error as PciDeviceError, PciDevice}; use crate::device::{DeviceRelocation, Error as PciDeviceError, PciDevice};
use byteorder::{ByteOrder, LittleEndian}; use byteorder::{ByteOrder, LittleEndian};
use devices::BusDevice; use devices::BusDevice;
use std; use std;
use std::sync::Arc; use std::any::Any;
use std::sync::Mutex; use std::ops::DerefMut;
use std::sync::{Arc, Mutex, Weak};
use vm_memory::{Address, GuestAddress, GuestUsize}; use vm_memory::{Address, GuestAddress, GuestUsize};
const VENDOR_ID_INTEL: u16 = 0x8086; const VENDOR_ID_INTEL: u16 = 0x8086;
@@ -66,31 +67,39 @@ impl PciDevice for PciRoot {
self.config.write_config_register(reg_idx, offset, data); self.config.write_config_register(reg_idx, offset, data);
} }
fn read_config_register(&self, reg_idx: usize) -> u32 { fn read_config_register(&mut self, reg_idx: usize) -> u32 {
self.config.read_reg(reg_idx) self.config.read_reg(reg_idx)
} }
fn as_any(&mut self) -> &mut dyn Any {
self
}
} }
pub struct PciBus { pub struct PciBus {
/// Devices attached to this bus. /// Devices attached to this bus.
/// Device 0 is host bridge. /// Device 0 is host bridge.
devices: Vec<Arc<Mutex<dyn PciDevice>>>, devices: Vec<Arc<Mutex<dyn PciDevice>>>,
device_reloc: Weak<dyn DeviceRelocation>,
} }
impl PciBus { impl PciBus {
pub fn new(pci_root: PciRoot) -> Self { pub fn new(pci_root: PciRoot, device_reloc: Weak<dyn DeviceRelocation>) -> Self {
let mut devices: Vec<Arc<Mutex<dyn PciDevice>>> = Vec::new(); let mut devices: Vec<Arc<Mutex<dyn PciDevice>>> = Vec::new();
devices.push(Arc::new(Mutex::new(pci_root))); devices.push(Arc::new(Mutex::new(pci_root)));
PciBus { devices } PciBus {
devices,
device_reloc,
}
} }
pub fn register_mapping( pub fn register_mapping(
&self, &self,
dev: Arc<Mutex<dyn BusDevice>>, dev: Arc<Mutex<dyn BusDevice>>,
io_bus: &mut devices::Bus, io_bus: &devices::Bus,
mmio_bus: &mut devices::Bus, mmio_bus: &devices::Bus,
bars: Vec<(GuestAddress, GuestUsize, PciBarRegionType)>, bars: Vec<(GuestAddress, GuestUsize, PciBarRegionType)>,
) -> Result<()> { ) -> Result<()> {
for (address, size, type_) in bars { for (address, size, type_) in bars {
@@ -181,10 +190,26 @@ impl PciConfigIo {
return; return;
} }
if let Some(d) = self.pci_bus.lock().unwrap().devices.get(device) { let pci_bus = self.pci_bus.lock().unwrap();
d.lock() if let Some(d) = pci_bus.devices.get(device) {
.unwrap() let mut device = d.lock().unwrap();
.write_config_register(register, offset, data);
// Find out if one of the device's BAR is being reprogrammed, and
// reprogram it if needed.
if let Some(params) = device.detect_bar_reprogramming(register, data) {
if let Err(e) = pci_bus.device_reloc.upgrade().unwrap().move_bar(
params.old_base,
params.new_base,
params.len,
device.deref_mut(),
params.region_type,
) {
error!("Failed moving device BAR: {}", e);
}
}
// Update the register value
device.write_config_register(register, offset, data);
} }
} }
@@ -281,10 +306,26 @@ impl PciConfigMmio {
return; return;
} }
if let Some(d) = self.pci_bus.lock().unwrap().devices.get(device) { let pci_bus = self.pci_bus.lock().unwrap();
d.lock() if let Some(d) = pci_bus.devices.get(device) {
.unwrap() let mut device = d.lock().unwrap();
.write_config_register(register, offset, data);
// Find out if one of the device's BAR is being reprogrammed, and
// reprogram it if needed.
if let Some(params) = device.detect_bar_reprogramming(register, data) {
if let Err(e) = pci_bus.device_reloc.upgrade().unwrap().move_bar(
params.old_base,
params.new_base,
params.len,
device.deref_mut(),
params.region_type,
) {
error!("Failed moving device BAR: {}", e);
}
}
// Update the register value
device.write_config_register(register, offset, data);
} }
} }
} }

View File

@@ -4,6 +4,7 @@
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use crate::device::BarReprogrammingParams;
use crate::{MsixConfig, PciInterruptPin}; use crate::{MsixConfig, PciInterruptPin};
use byteorder::{ByteOrder, LittleEndian}; use byteorder::{ByteOrder, LittleEndian};
use std::fmt::{self, Display}; use std::fmt::{self, Display};
@@ -249,8 +250,11 @@ pub trait PciCapability {
pub struct PciConfiguration { pub struct PciConfiguration {
registers: [u32; NUM_CONFIGURATION_REGISTERS], registers: [u32; NUM_CONFIGURATION_REGISTERS],
writable_bits: [u32; NUM_CONFIGURATION_REGISTERS], // writable bits for each register. writable_bits: [u32; NUM_CONFIGURATION_REGISTERS], // writable bits for each register.
bar_addr: [u32; NUM_BAR_REGS],
bar_size: [u32; NUM_BAR_REGS], bar_size: [u32; NUM_BAR_REGS],
bar_used: [bool; NUM_BAR_REGS], bar_used: [bool; NUM_BAR_REGS],
bar_type: [Option<PciBarRegionType>; NUM_BAR_REGS],
rom_bar_addr: u32,
rom_bar_size: u32, rom_bar_size: u32,
rom_bar_used: bool, rom_bar_used: bool,
// Contains the byte offset and size of the last capability. // Contains the byte offset and size of the last capability.
@@ -260,7 +264,7 @@ pub struct PciConfiguration {
} }
/// See pci_regs.h in kernel /// See pci_regs.h in kernel
#[derive(Copy, Clone)] #[derive(Copy, Clone, PartialEq)]
pub enum PciBarRegionType { pub enum PciBarRegionType {
Memory32BitRegion = 0, Memory32BitRegion = 0,
IORegion = 0x01, IORegion = 0x01,
@@ -343,6 +347,7 @@ impl PciConfiguration {
) -> Self { ) -> Self {
let mut registers = [0u32; NUM_CONFIGURATION_REGISTERS]; let mut registers = [0u32; NUM_CONFIGURATION_REGISTERS];
let mut writable_bits = [0u32; NUM_CONFIGURATION_REGISTERS]; let mut writable_bits = [0u32; NUM_CONFIGURATION_REGISTERS];
let bar_addr = [0u32; NUM_BAR_REGS];
let bar_size = [0u32; NUM_BAR_REGS]; let bar_size = [0u32; NUM_BAR_REGS];
registers[0] = u32::from(device_id) << 16 | u32::from(vendor_id); registers[0] = u32::from(device_id) << 16 | u32::from(vendor_id);
// TODO(dverkamp): Status should be write-1-to-clear // TODO(dverkamp): Status should be write-1-to-clear
@@ -372,8 +377,11 @@ impl PciConfiguration {
PciConfiguration { PciConfiguration {
registers, registers,
writable_bits, writable_bits,
bar_addr,
bar_size, bar_size,
bar_used: [false; NUM_BAR_REGS], bar_used: [false; NUM_BAR_REGS],
bar_type: [None; NUM_BAR_REGS],
rom_bar_addr: 0,
rom_bar_size: 0, rom_bar_size: 0,
rom_bar_used: false, rom_bar_used: false,
last_capability: None, last_capability: None,
@@ -390,15 +398,20 @@ impl PciConfiguration {
/// Writes a 32bit register to `reg_idx` in the register map. /// Writes a 32bit register to `reg_idx` in the register map.
pub fn write_reg(&mut self, reg_idx: usize, value: u32) { pub fn write_reg(&mut self, reg_idx: usize, value: u32) {
let mut mask = self.writable_bits[reg_idx]; let mut mask = self.writable_bits[reg_idx];
if reg_idx >= BAR0_REG
&& reg_idx < BAR0_REG + NUM_BAR_REGS if reg_idx >= BAR0_REG && reg_idx < BAR0_REG + NUM_BAR_REGS {
&& (value & BAR_MEM_ADDR_MASK) == BAR_MEM_ADDR_MASK
{
// Handle very specific case where the BAR is being written with // Handle very specific case where the BAR is being written with
// all 1's to retrieve the BAR size on next BAR reading. // all 1's to retrieve the BAR size during next BAR reading.
mask = self.bar_size[reg_idx - 4]; if value == 0xffff_ffff {
} else if reg_idx == ROM_BAR_REG && (value & ROM_BAR_ADDR_MASK) == ROM_BAR_ADDR_MASK { mask = self.bar_size[reg_idx - 4];
mask = self.rom_bar_size; }
} else if reg_idx == ROM_BAR_REG {
// Handle very specific case where the BAR is being written with
// all 1's on bits 31-11 to retrieve the BAR size during next BAR
// reading.
if value & ROM_BAR_ADDR_MASK == ROM_BAR_ADDR_MASK {
mask = self.rom_bar_size;
}
} }
if let Some(r) = self.registers.get_mut(reg_idx) { if let Some(r) = self.registers.get_mut(reg_idx) {
@@ -497,6 +510,7 @@ impl PciConfiguration {
self.registers[bar_idx + 1] = (config.addr >> 32) as u32; self.registers[bar_idx + 1] = (config.addr >> 32) as u32;
self.writable_bits[bar_idx + 1] = 0xffff_ffff; self.writable_bits[bar_idx + 1] = 0xffff_ffff;
self.bar_addr[config.reg_idx + 1] = self.registers[bar_idx + 1];
self.bar_size[config.reg_idx + 1] = (config.size >> 32) as u32; self.bar_size[config.reg_idx + 1] = (config.size >> 32) as u32;
self.bar_used[config.reg_idx + 1] = true; self.bar_used[config.reg_idx + 1] = true;
} }
@@ -512,8 +526,10 @@ impl PciConfiguration {
self.registers[bar_idx] = ((config.addr as u32) & mask) | lower_bits; self.registers[bar_idx] = ((config.addr as u32) & mask) | lower_bits;
self.writable_bits[bar_idx] = mask; self.writable_bits[bar_idx] = mask;
self.bar_addr[config.reg_idx] = self.registers[bar_idx];
self.bar_size[config.reg_idx] = config.size as u32; self.bar_size[config.reg_idx] = config.size as u32;
self.bar_used[config.reg_idx] = true; self.bar_used[config.reg_idx] = true;
self.bar_type[config.reg_idx] = Some(config.region_type);
Ok(config.reg_idx) Ok(config.reg_idx)
} }
@@ -542,24 +558,25 @@ impl PciConfiguration {
self.registers[config.reg_idx] = (config.addr as u32) | active; self.registers[config.reg_idx] = (config.addr as u32) | active;
self.writable_bits[config.reg_idx] = ROM_BAR_ADDR_MASK; self.writable_bits[config.reg_idx] = ROM_BAR_ADDR_MASK;
self.rom_bar_addr = self.registers[config.reg_idx];
self.rom_bar_size = config.size as u32; self.rom_bar_size = config.size as u32;
self.rom_bar_used = true; self.rom_bar_used = true;
Ok(config.reg_idx) Ok(config.reg_idx)
} }
/// Returns the address of the given 32 bits BAR region. /// Returns the address of the given BAR region.
pub fn get_bar32_addr(&self, bar_num: usize) -> u32 { pub fn get_bar_addr(&self, bar_num: usize) -> u64 {
let bar_idx = BAR0_REG + bar_num; let bar_idx = BAR0_REG + bar_num;
self.registers[bar_idx] & BAR_MEM_ADDR_MASK let mut addr = u64::from(self.bar_addr[bar_num] & self.writable_bits[bar_idx]);
}
/// Returns the address of the given 64 bits BAR region. if let Some(bar_type) = self.bar_type[bar_num] {
pub fn get_bar64_addr(&self, bar_num: usize) -> u64 { if bar_type == PciBarRegionType::Memory64BitRegion {
let bar_idx = BAR0_REG + bar_num; addr |= u64::from(self.bar_addr[bar_num + 1]) << 32;
}
}
u64::from(self.registers[bar_idx] & BAR_MEM_ADDR_MASK) addr
| (u64::from(self.registers[bar_idx + 1]) << 32)
} }
/// Configures the IRQ line and pin used by this device. /// Configures the IRQ line and pin used by this device.
@@ -645,6 +662,119 @@ impl PciConfiguration {
pub fn read_config_register(&self, reg_idx: usize) -> u32 { pub fn read_config_register(&self, reg_idx: usize) -> u32 {
self.read_reg(reg_idx) self.read_reg(reg_idx)
} }
pub fn detect_bar_reprogramming(
&mut self,
reg_idx: usize,
data: &[u8],
) -> Option<BarReprogrammingParams> {
if data.len() != 4 {
return None;
}
let value = LittleEndian::read_u32(data);
let mask = self.writable_bits[reg_idx];
if reg_idx >= BAR0_REG && reg_idx < BAR0_REG + NUM_BAR_REGS {
let bar_idx = reg_idx - 4;
if (value & mask) != (self.bar_addr[bar_idx] & mask) {
// Handle special case where the address being written is
// different from the address initially provided. This is a
// BAR reprogramming case which needs to be properly caught.
if let Some(bar_type) = self.bar_type[bar_idx] {
match bar_type {
PciBarRegionType::Memory64BitRegion => {}
_ => {
// Ignore the case where the BAR size is being
// asked for.
if value == 0xffff_ffff {
return None;
}
debug!(
"DETECT BAR REPROG: current 0x{:x}, new 0x{:x}",
self.registers[reg_idx], value
);
let old_base = u64::from(self.bar_addr[bar_idx] & mask);
let new_base = u64::from(value & mask);
let len = u64::from(self.bar_size[bar_idx]);
let region_type = bar_type;
self.bar_addr[bar_idx] = value;
return Some(BarReprogrammingParams {
old_base,
new_base,
len,
region_type,
});
}
}
} else if (reg_idx > BAR0_REG)
&& (self.registers[reg_idx - 1] & self.writable_bits[reg_idx - 1])
!= (self.bar_addr[bar_idx - 1] & self.writable_bits[reg_idx - 1])
{
// Ignore the case where the BAR size is being asked for.
// Because we are in the 64bits case here, we have to check
// if the lower 32bits of the current BAR have already been
// asked for the BAR size too.
if value == 0xffff_ffff
&& self.registers[reg_idx - 1] & self.writable_bits[reg_idx - 1]
== self.bar_size[bar_idx - 1] & self.writable_bits[reg_idx - 1]
{
return None;
}
debug!(
"DETECT BAR REPROG: current 0x{:x}, new 0x{:x}",
self.registers[reg_idx], value
);
let old_base = u64::from(self.bar_addr[bar_idx] & mask) << 32
| u64::from(self.bar_addr[bar_idx - 1] & self.writable_bits[reg_idx - 1]);
let new_base = u64::from(value & mask) << 32
| u64::from(self.registers[reg_idx - 1] & self.writable_bits[reg_idx - 1]);
let len = u64::from(self.bar_size[bar_idx]) << 32
| u64::from(self.bar_size[bar_idx - 1]);
let region_type = PciBarRegionType::Memory64BitRegion;
self.bar_addr[bar_idx] = value;
self.bar_addr[bar_idx - 1] = self.registers[reg_idx - 1];
return Some(BarReprogrammingParams {
old_base,
new_base,
len,
region_type,
});
}
}
} else if reg_idx == ROM_BAR_REG && (value & mask) != (self.rom_bar_addr & mask) {
// Ignore the case where the BAR size is being asked for.
if value & ROM_BAR_ADDR_MASK == ROM_BAR_ADDR_MASK {
return None;
}
debug!(
"DETECT ROM BAR REPROG: current 0x{:x}, new 0x{:x}",
self.registers[reg_idx], value
);
let old_base = u64::from(self.rom_bar_addr & mask);
let new_base = u64::from(value & mask);
let len = u64::from(self.rom_bar_size);
let region_type = PciBarRegionType::Memory32BitRegion;
self.rom_bar_addr = value;
return Some(BarReprogrammingParams {
old_base,
new_base,
len,
region_type,
});
}
None
}
} }
impl Default for PciBarConfiguration { impl Default for PciBarConfiguration {

View File

@@ -3,22 +3,12 @@
// found in the LICENSE-BSD-3-Clause file. // found in the LICENSE-BSD-3-Clause file.
use crate::configuration::{self, PciBarRegionType}; use crate::configuration::{self, PciBarRegionType};
use crate::msix::MsixTableEntry;
use crate::PciInterruptPin;
use devices::BusDevice; use devices::BusDevice;
use std; use std::any::Any;
use std::fmt::{self, Display}; use std::fmt::{self, Display};
use std::sync::Arc; use std::{self, io, result};
use vm_allocator::SystemAllocator; use vm_allocator::SystemAllocator;
use vm_memory::{GuestAddress, GuestUsize}; use vm_memory::{GuestAddress, GuestUsize};
use vmm_sys_util::eventfd::EventFd;
pub struct InterruptParameters<'a> {
pub msix: Option<&'a MsixTableEntry>,
}
pub type InterruptDelivery =
Box<dyn Fn(InterruptParameters) -> std::result::Result<(), std::io::Error> + Send + Sync>;
#[derive(Debug)] #[derive(Debug)]
pub enum Error { pub enum Error {
@@ -47,20 +37,15 @@ impl Display for Error {
} }
} }
#[derive(Clone, Copy)]
pub struct BarReprogrammingParams {
pub old_base: u64,
pub new_base: u64,
pub len: u64,
pub region_type: PciBarRegionType,
}
pub trait PciDevice: BusDevice { pub trait PciDevice: BusDevice {
/// Assign a legacy PCI IRQ to this device.
/// The device may write to `irq_evt` to trigger an interrupt.
fn assign_pin_irq(
&mut self,
_irq_cb: Arc<InterruptDelivery>,
_irq_num: u32,
_irq_pin: PciInterruptPin,
) {
}
/// Assign MSI-X to this device.
fn assign_msix(&mut self, _msi_cb: Arc<InterruptDelivery>) {}
/// Allocates the needed PCI BARs space using the `allocate` function which takes a size and /// Allocates the needed PCI BARs space using the `allocate` function which takes a size and
/// returns an address. Returns a Vec of (GuestAddress, GuestUsize) tuples. /// returns an address. Returns a Vec of (GuestAddress, GuestUsize) tuples.
fn allocate_bars( fn allocate_bars(
@@ -70,18 +55,21 @@ pub trait PciDevice: BusDevice {
Ok(Vec::new()) Ok(Vec::new())
} }
/// Gets a list of ioeventfds that should be registered with the running VM. The list is
/// returned as a Vec of (eventfd, addr, datamatch) tuples.
fn ioeventfds(&self) -> Vec<(&EventFd, u64, u64)> {
Vec::new()
}
/// Sets a register in the configuration space. /// Sets a register in the configuration space.
/// * `reg_idx` - The index of the config register to modify. /// * `reg_idx` - The index of the config register to modify.
/// * `offset` - Offset in to the register. /// * `offset` - Offset in to the register.
fn write_config_register(&mut self, reg_idx: usize, offset: u64, data: &[u8]); fn write_config_register(&mut self, reg_idx: usize, offset: u64, data: &[u8]);
/// Gets a register from the configuration space. /// Gets a register from the configuration space.
/// * `reg_idx` - The index of the config register to read. /// * `reg_idx` - The index of the config register to read.
fn read_config_register(&self, reg_idx: usize) -> u32; fn read_config_register(&mut self, reg_idx: usize) -> u32;
/// Detects if a BAR is being reprogrammed.
fn detect_bar_reprogramming(
&mut self,
_reg_idx: usize,
_data: &[u8],
) -> Option<BarReprogrammingParams> {
None
}
/// Reads from a BAR region mapped in to the device. /// Reads from a BAR region mapped in to the device.
/// * `addr` - The guest address inside the BAR. /// * `addr` - The guest address inside the BAR.
/// * `data` - Filled with the data from `addr`. /// * `data` - Filled with the data from `addr`.
@@ -90,4 +78,26 @@ pub trait PciDevice: BusDevice {
/// * `addr` - The guest address inside the BAR. /// * `addr` - The guest address inside the BAR.
/// * `data` - The data to write. /// * `data` - The data to write.
fn write_bar(&mut self, _base: u64, _offset: u64, _data: &[u8]) {} fn write_bar(&mut self, _base: u64, _offset: u64, _data: &[u8]) {}
/// Relocates the BAR to a different address in guest address space.
fn move_bar(&mut self, _old_base: u64, _new_base: u64) -> result::Result<(), io::Error> {
Ok(())
}
/// Provides a mutable reference to the Any trait. This is useful to let
/// the caller have access to the underlying type behind the trait.
fn as_any(&mut self) -> &mut dyn Any;
}
/// This trait defines a set of functions which can be triggered whenever a
/// PCI device is modified in any way.
pub trait DeviceRelocation: Send + Sync {
/// The BAR needs to be moved to a different location in the guest address
/// space. This follows a decision from the software running in the guest.
fn move_bar(
&self,
old_base: u64,
new_base: u64,
len: u64,
pci_dev: &mut dyn PciDevice,
region_type: PciBarRegionType,
) -> result::Result<(), io::Error>;
} }

View File

@@ -6,9 +6,7 @@
#[macro_use] #[macro_use]
extern crate log; extern crate log;
extern crate devices; extern crate devices;
extern crate kvm_ioctls;
extern crate vm_memory; extern crate vm_memory;
extern crate vmm_sys_util;
mod bus; mod bus;
mod configuration; mod configuration;
@@ -23,9 +21,9 @@ pub use self::configuration::{
PciNetworkControllerSubclass, PciProgrammingInterface, PciSerialBusSubClass, PciSubclass, PciNetworkControllerSubclass, PciProgrammingInterface, PciSerialBusSubClass, PciSubclass,
}; };
pub use self::device::{ pub use self::device::{
Error as PciDeviceError, InterruptDelivery, InterruptParameters, PciDevice, BarReprogrammingParams, DeviceRelocation, Error as PciDeviceError, PciDevice,
}; };
pub use self::msi::MsiCap; pub use self::msi::{msi_num_enabled_vectors, MsiCap, MsiConfig};
pub use self::msix::{MsixCap, MsixConfig, MsixTableEntry, MSIX_TABLE_ENTRY_SIZE}; pub use self::msix::{MsixCap, MsixConfig, MsixTableEntry, MSIX_TABLE_ENTRY_SIZE};
/// PCI has four interrupt pins A->D. /// PCI has four interrupt pins A->D.

View File

@@ -7,6 +7,10 @@ extern crate byteorder;
extern crate vm_memory; extern crate vm_memory;
use byteorder::{ByteOrder, LittleEndian}; use byteorder::{ByteOrder, LittleEndian};
use std::sync::Arc;
use vm_device::interrupt::{
InterruptIndex, InterruptSourceConfig, InterruptSourceGroup, MsiIrqSourceConfig,
};
// MSI control masks // MSI control masks
const MSI_CTL_ENABLE: u16 = 0x1; const MSI_CTL_ENABLE: u16 = 0x1;
@@ -21,6 +25,16 @@ const MSI_MSG_ADDR_LO_OFFSET: u64 = 0x4;
// MSI message masks // MSI message masks
const MSI_MSG_ADDR_LO_MASK: u32 = 0xffff_fffc; const MSI_MSG_ADDR_LO_MASK: u32 = 0xffff_fffc;
pub fn msi_num_enabled_vectors(msg_ctl: u16) -> usize {
let field = (msg_ctl >> 4) & 0x7;
if field > 5 {
return 0;
}
1 << field
}
#[derive(Clone, Copy, Default)] #[derive(Clone, Copy, Default)]
pub struct MsiCap { pub struct MsiCap {
// Message Control Register // Message Control Register
@@ -58,21 +72,15 @@ impl MsiCap {
self.msg_ctl & MSI_CTL_PER_VECTOR == MSI_CTL_PER_VECTOR self.msg_ctl & MSI_CTL_PER_VECTOR == MSI_CTL_PER_VECTOR
} }
pub fn enabled(&self) -> bool { fn enabled(&self) -> bool {
self.msg_ctl & MSI_CTL_ENABLE == MSI_CTL_ENABLE self.msg_ctl & MSI_CTL_ENABLE == MSI_CTL_ENABLE
} }
pub fn num_enabled_vectors(&self) -> usize { fn num_enabled_vectors(&self) -> usize {
let field = (self.msg_ctl >> 4) & 0x7; msi_num_enabled_vectors(self.msg_ctl)
if field > 5 {
return 0;
}
1 << field
} }
pub fn vector_masked(&self, vector: usize) -> bool { fn vector_masked(&self, vector: usize) -> bool {
if !self.per_vector_mask() { if !self.per_vector_mask() {
return false; return false;
} }
@@ -80,7 +88,7 @@ impl MsiCap {
(self.mask_bits >> vector) & 0x1 == 0x1 (self.mask_bits >> vector) & 0x1 == 0x1
} }
pub fn size(&self) -> u64 { fn size(&self) -> u64 {
let mut size: u64 = 0xa; let mut size: u64 = 0xa;
if self.addr_64_bits() { if self.addr_64_bits() {
@@ -93,7 +101,7 @@ impl MsiCap {
size size
} }
pub fn update(&mut self, offset: u64, data: &[u8]) { fn update(&mut self, offset: u64, data: &[u8]) {
// Calculate message data offset depending on the address being 32 or // Calculate message data offset depending on the address being 32 or
// 64 bits. // 64 bits.
// Calculate upper address offset if the address is 64 bits. // Calculate upper address offset if the address is 64 bits.
@@ -132,7 +140,7 @@ impl MsiCap {
4 => { 4 => {
let value = LittleEndian::read_u32(data); let value = LittleEndian::read_u32(data);
match offset { match offset {
MSI_MSG_CTL_OFFSET => { 0x0 => {
self.msg_ctl = (self.msg_ctl & !(MSI_CTL_ENABLE | MSI_CTL_MULTI_MSG_ENABLE)) self.msg_ctl = (self.msg_ctl & !(MSI_CTL_ENABLE | MSI_CTL_MULTI_MSG_ENABLE))
| ((value >> 16) as u16 & (MSI_CTL_ENABLE | MSI_CTL_MULTI_MSG_ENABLE)) | ((value >> 16) as u16 & (MSI_CTL_ENABLE | MSI_CTL_MULTI_MSG_ENABLE))
} }
@@ -151,3 +159,73 @@ impl MsiCap {
} }
} }
} }
pub struct MsiConfig {
cap: MsiCap,
interrupt_source_group: Arc<Box<dyn InterruptSourceGroup>>,
}
impl MsiConfig {
pub fn new(msg_ctl: u16, interrupt_source_group: Arc<Box<dyn InterruptSourceGroup>>) -> Self {
let cap = MsiCap {
msg_ctl,
..Default::default()
};
MsiConfig {
cap,
interrupt_source_group,
}
}
pub fn enabled(&self) -> bool {
self.cap.enabled()
}
pub fn size(&self) -> u64 {
self.cap.size()
}
pub fn num_enabled_vectors(&self) -> usize {
self.cap.num_enabled_vectors()
}
pub fn update(&mut self, offset: u64, data: &[u8]) {
let old_enabled = self.cap.enabled();
self.cap.update(offset, data);
if self.cap.enabled() {
for idx in 0..self.num_enabled_vectors() {
let config = MsiIrqSourceConfig {
high_addr: self.cap.msg_addr_hi,
low_addr: self.cap.msg_addr_lo,
data: self.cap.msg_data as u32,
};
if let Err(e) = self
.interrupt_source_group
.update(idx as InterruptIndex, InterruptSourceConfig::MsiIrq(config))
{
error!("Failed updating vector: {:?}", e);
}
if self.cap.vector_masked(idx) {
if let Err(e) = self.interrupt_source_group.mask(idx as InterruptIndex) {
error!("Failed masking vector: {:?}", e);
}
}
}
if !old_enabled {
if let Err(e) = self.interrupt_source_group.enable() {
error!("Failed enabling irq_fd: {:?}", e);
}
}
} else if old_enabled {
if let Err(e) = self.interrupt_source_group.disable() {
error!("Failed disabling irq_fd: {:?}", e);
}
}
}
}

View File

@@ -8,9 +8,11 @@ extern crate vm_memory;
use std::sync::Arc; use std::sync::Arc;
use crate::device::InterruptParameters; use crate::{PciCapability, PciCapabilityID};
use crate::{InterruptDelivery, PciCapability, PciCapabilityID};
use byteorder::{ByteOrder, LittleEndian}; use byteorder::{ByteOrder, LittleEndian};
use vm_device::interrupt::{
InterruptIndex, InterruptSourceConfig, InterruptSourceGroup, MsiIrqSourceConfig,
};
use vm_memory::ByteValued; use vm_memory::ByteValued;
const MAX_MSIX_VECTORS_PER_DEVICE: u16 = 2048; const MAX_MSIX_VECTORS_PER_DEVICE: u16 = 2048;
@@ -51,13 +53,16 @@ impl Default for MsixTableEntry {
pub struct MsixConfig { pub struct MsixConfig {
pub table_entries: Vec<MsixTableEntry>, pub table_entries: Vec<MsixTableEntry>,
pub pba_entries: Vec<u64>, pub pba_entries: Vec<u64>,
interrupt_cb: Option<Arc<InterruptDelivery>>, interrupt_source_group: Arc<Box<dyn InterruptSourceGroup>>,
masked: bool, masked: bool,
enabled: bool, enabled: bool,
} }
impl MsixConfig { impl MsixConfig {
pub fn new(msix_vectors: u16) -> Self { pub fn new(
msix_vectors: u16,
interrupt_source_group: Arc<Box<dyn InterruptSourceGroup>>,
) -> Self {
assert!(msix_vectors <= MAX_MSIX_VECTORS_PER_DEVICE); assert!(msix_vectors <= MAX_MSIX_VECTORS_PER_DEVICE);
let mut table_entries: Vec<MsixTableEntry> = Vec::new(); let mut table_entries: Vec<MsixTableEntry> = Vec::new();
@@ -69,16 +74,12 @@ impl MsixConfig {
MsixConfig { MsixConfig {
table_entries, table_entries,
pba_entries, pba_entries,
interrupt_cb: None, interrupt_source_group,
masked: false, masked: false,
enabled: false, enabled: false,
} }
} }
pub fn register_interrupt_cb(&mut self, cb: Arc<InterruptDelivery>) {
self.interrupt_cb = Some(cb);
}
pub fn masked(&self) -> bool { pub fn masked(&self) -> bool {
self.masked self.masked
} }
@@ -89,10 +90,47 @@ impl MsixConfig {
pub fn set_msg_ctl(&mut self, reg: u16) { pub fn set_msg_ctl(&mut self, reg: u16) {
let old_masked = self.masked; let old_masked = self.masked;
let old_enabled = self.enabled;
self.masked = ((reg >> FUNCTION_MASK_BIT) & 1u16) == 1u16; self.masked = ((reg >> FUNCTION_MASK_BIT) & 1u16) == 1u16;
self.enabled = ((reg >> MSIX_ENABLE_BIT) & 1u16) == 1u16; self.enabled = ((reg >> MSIX_ENABLE_BIT) & 1u16) == 1u16;
// Update KVM routes
if old_masked != self.masked || old_enabled != self.enabled {
if self.enabled && !self.masked {
for (idx, table_entry) in self.table_entries.iter().enumerate() {
let config = MsiIrqSourceConfig {
high_addr: table_entry.msg_addr_hi,
low_addr: table_entry.msg_addr_lo,
data: table_entry.msg_data,
};
if let Err(e) = self
.interrupt_source_group
.update(idx as InterruptIndex, InterruptSourceConfig::MsiIrq(config))
{
error!("Failed updating vector: {:?}", e);
}
if table_entry.masked() {
if let Err(e) = self.interrupt_source_group.mask(idx as InterruptIndex) {
error!("Failed masking vector: {:?}", e);
}
}
}
if !old_enabled || old_masked {
if let Err(e) = self.interrupt_source_group.enable() {
error!("Failed enabling irq_fd: {:?}", e);
}
}
} else if old_enabled || !old_masked {
if let Err(e) = self.interrupt_source_group.disable() {
error!("Failed disabling irq_fd: {:?}", e);
}
}
}
// If the Function Mask bit was set, and has just been cleared, it's // If the Function Mask bit was set, and has just been cleared, it's
// important to go through the entire PBA to check if there was any // important to go through the entire PBA to check if there was any
// pending MSI-X message to inject, given that the vector is not // pending MSI-X message to inject, given that the vector is not
@@ -198,6 +236,32 @@ impl MsixConfig {
_ => error!("invalid data length"), _ => error!("invalid data length"),
}; };
// Update interrupt routes
if self.enabled && !self.masked {
let table_entry = &self.table_entries[index];
let config = MsiIrqSourceConfig {
high_addr: table_entry.msg_addr_hi,
low_addr: table_entry.msg_addr_lo,
data: table_entry.msg_data,
};
if let Err(e) = self.interrupt_source_group.update(
index as InterruptIndex,
InterruptSourceConfig::MsiIrq(config),
) {
error!("Failed updating vector: {:?}", e);
}
if table_entry.masked() {
if let Err(e) = self.interrupt_source_group.mask(index as InterruptIndex) {
error!("Failed masking vector: {:?}", e);
}
} else if let Err(e) = self.interrupt_source_group.unmask(index as InterruptIndex) {
error!("Failed unmasking vector: {:?}", e);
}
}
// After the MSI-X table entry has been updated, it is necessary to // After the MSI-X table entry has been updated, it is necessary to
// check if the vector control masking bit has changed. In case the // check if the vector control masking bit has changed. In case the
// bit has been flipped from 1 to 0, we need to inject a MSI message // bit has been flipped from 1 to 0, we need to inject a MSI message
@@ -285,13 +349,12 @@ impl MsixConfig {
fn inject_msix_and_clear_pba(&mut self, vector: usize) { fn inject_msix_and_clear_pba(&mut self, vector: usize) {
// Inject the MSI message // Inject the MSI message
if let Some(cb) = &self.interrupt_cb { match self
match (cb)(InterruptParameters { .interrupt_source_group
msix: Some(&self.table_entries[vector]), .trigger(vector as InterruptIndex)
}) { {
Ok(_) => debug!("MSI-X injected on vector control flip"), Ok(_) => debug!("MSI-X injected on vector control flip"),
Err(e) => error!("failed to inject MSI-X: {}", e), Err(e) => error!("failed to inject MSI-X: {}", e),
};
} }
// Clear the bit from PBA // Clear the bit from PBA

9
qcow/Cargo.toml Executable file → Normal file
View File

@@ -9,11 +9,12 @@ license = "BSD-3-Clause"
path = "src/qcow.rs" path = "src/qcow.rs"
[dependencies] [dependencies]
byteorder = "1.3.2" byteorder = "1.3.4"
libc = "0.2.60" libc = "0.2.66"
log = "0.4.8" log = "0.4.8"
remain = "0.1.3" remain = "0.2.1"
vmm-sys-util = "0.1.1" vmm-sys-util = "0.4.0"
vm-virtio = { path = "../vm-virtio" }
[dev-dependencies] [dev-dependencies]
tempfile = "3.1.0" tempfile = "3.1.0"

View File

@@ -12,6 +12,7 @@ mod vec_cache;
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use libc::{EINVAL, ENOSPC, ENOTSUP}; use libc::{EINVAL, ENOSPC, ENOTSUP};
use remain::sorted; use remain::sorted;
use vm_virtio::RawFile;
use vmm_sys_util::{ use vmm_sys_util::{
file_traits::FileSetLen, file_traits::FileSync, seek_hole::SeekHole, write_zeroes::PunchHole, file_traits::FileSetLen, file_traits::FileSync, seek_hole::SeekHole, write_zeroes::PunchHole,
write_zeroes::WriteZeroes, write_zeroes::WriteZeroes,
@@ -19,10 +20,8 @@ use vmm_sys_util::{
use std::cmp::{max, min}; use std::cmp::{max, min};
use std::fmt::{self, Display}; use std::fmt::{self, Display};
use std::fs::File;
use std::io::{self, Read, Seek, SeekFrom, Write}; use std::io::{self, Read, Seek, SeekFrom, Write};
use std::mem::size_of; use std::mem::size_of;
use std::os::unix::io::{AsRawFd, RawFd};
use crate::qcow_raw_file::QcowRawFile; use crate::qcow_raw_file::QcowRawFile;
use crate::refcount::RefCount; use crate::refcount::RefCount;
@@ -190,7 +189,7 @@ pub struct QcowHeader {
impl QcowHeader { impl QcowHeader {
/// Creates a QcowHeader from a reference to a file. /// Creates a QcowHeader from a reference to a file.
pub fn new(f: &mut File) -> Result<QcowHeader> { pub fn new(f: &mut RawFile) -> Result<QcowHeader> {
f.seek(SeekFrom::Start(0)).map_err(Error::ReadingHeader)?; f.seek(SeekFrom::Start(0)).map_err(Error::ReadingHeader)?;
let magic = f.read_u32::<BigEndian>().map_err(Error::ReadingHeader)?; let magic = f.read_u32::<BigEndian>().map_err(Error::ReadingHeader)?;
if magic != QCOW_MAGIC { if magic != QCOW_MAGIC {
@@ -198,12 +197,12 @@ impl QcowHeader {
} }
// Reads the next u32 from the file. // Reads the next u32 from the file.
fn read_u32_from_file(f: &mut File) -> Result<u32> { fn read_u32_from_file(f: &mut RawFile) -> Result<u32> {
f.read_u32::<BigEndian>().map_err(Error::ReadingHeader) f.read_u32::<BigEndian>().map_err(Error::ReadingHeader)
} }
// Reads the next u64 from the file. // Reads the next u64 from the file.
fn read_u64_from_file(f: &mut File) -> Result<u64> { fn read_u64_from_file(f: &mut RawFile) -> Result<u64> {
f.read_u64::<BigEndian>().map_err(Error::ReadingHeader) f.read_u64::<BigEndian>().map_err(Error::ReadingHeader)
} }
@@ -368,9 +367,11 @@ fn max_refcount_clusters(refcount_order: u32, cluster_size: u32, num_clusters: u
/// ///
/// ``` /// ```
/// # use std::io::{Read, Seek, SeekFrom}; /// # use std::io::{Read, Seek, SeekFrom};
/// # use vm_virtio::RawFile;
/// # use qcow::{self, QcowFile}; /// # use qcow::{self, QcowFile};
/// # fn test(file: std::fs::File) -> std::io::Result<()> { /// # fn test(file: std::fs::File) -> std::io::Result<()> {
/// let mut q = QcowFile::from(file).expect("Can't open qcow file"); /// let mut raw_img = RawFile::new(file, false);
/// let mut q = QcowFile::from(raw_img).expect("Can't open qcow file");
/// let mut buf = [0u8; 12]; /// let mut buf = [0u8; 12];
/// q.seek(SeekFrom::Start(10 as u64))?; /// q.seek(SeekFrom::Start(10 as u64))?;
/// q.read(&mut buf[..])?; /// q.read(&mut buf[..])?;
@@ -395,7 +396,7 @@ pub struct QcowFile {
impl QcowFile { impl QcowFile {
/// Creates a QcowFile from `file`. File must be a valid qcow2 image. /// Creates a QcowFile from `file`. File must be a valid qcow2 image.
pub fn from(mut file: File) -> Result<QcowFile> { pub fn from(mut file: RawFile) -> Result<QcowFile> {
let header = QcowHeader::new(&mut file)?; let header = QcowHeader::new(&mut file)?;
// Only v2 and v3 files are supported. // Only v2 and v3 files are supported.
@@ -544,7 +545,7 @@ impl QcowFile {
} }
/// Creates a new QcowFile at the given path. /// Creates a new QcowFile at the given path.
pub fn new(mut file: File, version: u32, virtual_size: u64) -> Result<QcowFile> { pub fn new(mut file: RawFile, version: u32, virtual_size: u64) -> Result<QcowFile> {
let header = QcowHeader::create_for_size(version, virtual_size); let header = QcowHeader::create_for_size(version, virtual_size);
file.seek(SeekFrom::Start(0)).map_err(Error::SeekingFile)?; file.seek(SeekFrom::Start(0)).map_err(Error::SeekingFile)?;
header.write_to(&mut file)?; header.write_to(&mut file)?;
@@ -1220,7 +1221,12 @@ impl QcowFile {
let refcount = self let refcount = self
.refcounts .refcounts
.get_cluster_refcount(&mut self.raw_file, cluster_addr) .get_cluster_refcount(&mut self.raw_file, cluster_addr)
.map_err(|_| std::io::Error::from_raw_os_error(EINVAL))?; .map_err(|e| {
io::Error::new(
io::ErrorKind::InvalidData,
format!("failed to get cluster refcount: {}", e),
)
})?;
if refcount == 0 { if refcount == 0 {
return Err(std::io::Error::from_raw_os_error(EINVAL)); return Err(std::io::Error::from_raw_os_error(EINVAL));
} }
@@ -1394,12 +1400,6 @@ impl Drop for QcowFile {
} }
} }
impl AsRawFd for QcowFile {
fn as_raw_fd(&self) -> RawFd {
self.raw_file.file().as_raw_fd()
}
}
impl Read for QcowFile { impl Read for QcowFile {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> { fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
let address: u64 = self.current_offset as u64; let address: u64 = self.current_offset as u64;
@@ -1634,7 +1634,7 @@ where
Ok(()) Ok(())
} }
fn convert_reader<R>(reader: &mut R, dst_file: File, dst_type: ImageType) -> Result<()> fn convert_reader<R>(reader: &mut R, dst_file: RawFile, dst_type: ImageType) -> Result<()>
where where
R: Read + Seek + SeekHole, R: Read + Seek + SeekHole,
{ {
@@ -1666,8 +1666,8 @@ where
/// Copy the contents of a disk image in `src_file` into `dst_file`. /// 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 /// The type of `src_file` is automatically detected, and the output file type is
/// determined by `dst_type`. /// determined by `dst_type`.
pub fn convert(src_file: File, dst_file: File, dst_type: ImageType) -> Result<()> { pub fn convert(mut src_file: RawFile, dst_file: RawFile, dst_type: ImageType) -> Result<()> {
let src_type = detect_image_type(&src_file)?; let src_type = detect_image_type(&mut src_file)?;
match src_type { match src_type {
ImageType::Qcow2 => { ImageType::Qcow2 => {
let mut src_reader = QcowFile::from(src_file)?; let mut src_reader = QcowFile::from(src_file)?;
@@ -1682,17 +1682,18 @@ pub fn convert(src_file: File, dst_file: File, dst_type: ImageType) -> Result<()
} }
/// Detect the type of an image file by checking for a valid qcow2 header. /// Detect the type of an image file by checking for a valid qcow2 header.
pub fn detect_image_type(file: &File) -> Result<ImageType> { pub fn detect_image_type(file: &mut RawFile) -> Result<ImageType> {
let mut f = file; let orig_seek = file
let orig_seek = f.seek(SeekFrom::Current(0)).map_err(Error::SeekingFile)?; .seek(SeekFrom::Current(0))
f.seek(SeekFrom::Start(0)).map_err(Error::SeekingFile)?; .map_err(Error::SeekingFile)?;
let magic = f.read_u32::<BigEndian>().map_err(Error::ReadingHeader)?; file.seek(SeekFrom::Start(0)).map_err(Error::SeekingFile)?;
let magic = file.read_u32::<BigEndian>().map_err(Error::ReadingHeader)?;
let image_type = if magic == QCOW_MAGIC { let image_type = if magic == QCOW_MAGIC {
ImageType::Qcow2 ImageType::Qcow2
} else { } else {
ImageType::Raw ImageType::Raw
}; };
f.seek(SeekFrom::Start(orig_seek)) file.seek(SeekFrom::Start(orig_seek))
.map_err(Error::SeekingFile)?; .map_err(Error::SeekingFile)?;
Ok(image_type) Ok(image_type)
} }
@@ -1700,9 +1701,9 @@ pub fn detect_image_type(file: &File) -> Result<ImageType> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use std::fs::File;
use std::io::{Read, Seek, SeekFrom, Write}; use std::io::{Read, Seek, SeekFrom, Write};
use tempfile::tempfile; use tempfile::tempfile;
use vm_virtio::RawFile;
fn valid_header_v3() -> Vec<u8> { fn valid_header_v3() -> Vec<u8> {
vec![ vec![
@@ -1771,9 +1772,9 @@ mod tests {
fn with_basic_file<F>(header: &[u8], mut testfn: F) fn with_basic_file<F>(header: &[u8], mut testfn: F)
where where
F: FnMut(File), F: FnMut(RawFile),
{ {
let mut disk_file: File = tempfile().unwrap(); let mut disk_file: RawFile = RawFile::new(tempfile().unwrap(), false);
disk_file.write_all(&header).unwrap(); disk_file.write_all(&header).unwrap();
disk_file.set_len(0x1_0000_0000).unwrap(); disk_file.set_len(0x1_0000_0000).unwrap();
disk_file.seek(SeekFrom::Start(0)).unwrap(); disk_file.seek(SeekFrom::Start(0)).unwrap();
@@ -1781,11 +1782,11 @@ mod tests {
testfn(disk_file); // File closed when the function exits. testfn(disk_file); // File closed when the function exits.
} }
fn with_default_file<F>(file_size: u64, mut testfn: F) fn with_default_file<F>(file_size: u64, direct: bool, mut testfn: F)
where where
F: FnMut(QcowFile), F: FnMut(QcowFile),
{ {
let tmp = tempfile().unwrap(); let tmp: RawFile = RawFile::new(tempfile().unwrap(), direct);
let qcow_file = QcowFile::new(tmp, 3, file_size).unwrap(); let qcow_file = QcowFile::new(tmp, 3, file_size).unwrap();
testfn(qcow_file); // File closed when the function exits. testfn(qcow_file); // File closed when the function exits.
@@ -1794,7 +1795,7 @@ mod tests {
#[test] #[test]
fn default_header_v2() { fn default_header_v2() {
let header = QcowHeader::create_for_size(2, 0x10_0000); let header = QcowHeader::create_for_size(2, 0x10_0000);
let mut disk_file: File = tempfile().unwrap(); let mut disk_file: RawFile = RawFile::new(tempfile().unwrap(), false);
header header
.write_to(&mut disk_file) .write_to(&mut disk_file)
.expect("Failed to write header to temporary file."); .expect("Failed to write header to temporary file.");
@@ -1805,7 +1806,7 @@ mod tests {
#[test] #[test]
fn default_header_v3() { fn default_header_v3() {
let header = QcowHeader::create_for_size(3, 0x10_0000); let header = QcowHeader::create_for_size(3, 0x10_0000);
let mut disk_file: File = tempfile().unwrap(); let mut disk_file: RawFile = RawFile::new(tempfile().unwrap(), false);
header header
.write_to(&mut disk_file) .write_to(&mut disk_file)
.expect("Failed to write header to temporary file."); .expect("Failed to write header to temporary file.");
@@ -1815,13 +1816,13 @@ mod tests {
#[test] #[test]
fn header_read() { fn header_read() {
with_basic_file(&valid_header_v2(), |mut disk_file: File| { with_basic_file(&valid_header_v2(), |mut disk_file: RawFile| {
let header = QcowHeader::new(&mut disk_file).expect("Failed to create Header."); let header = QcowHeader::new(&mut disk_file).expect("Failed to create Header.");
assert_eq!(header.version, 2); assert_eq!(header.version, 2);
assert_eq!(header.refcount_order, DEFAULT_REFCOUNT_ORDER); assert_eq!(header.refcount_order, DEFAULT_REFCOUNT_ORDER);
assert_eq!(header.header_size, V2_BARE_HEADER_SIZE); assert_eq!(header.header_size, V2_BARE_HEADER_SIZE);
}); });
with_basic_file(&valid_header_v3(), |mut disk_file: File| { with_basic_file(&valid_header_v3(), |mut disk_file: RawFile| {
let header = QcowHeader::new(&mut disk_file).expect("Failed to create Header."); let header = QcowHeader::new(&mut disk_file).expect("Failed to create Header.");
assert_eq!(header.version, 3); assert_eq!(header.version, 3);
assert_eq!(header.refcount_order, DEFAULT_REFCOUNT_ORDER); assert_eq!(header.refcount_order, DEFAULT_REFCOUNT_ORDER);
@@ -1832,7 +1833,7 @@ mod tests {
#[test] #[test]
fn invalid_magic() { fn invalid_magic() {
let invalid_header = vec![0x51u8, 0x46, 0x4a, 0xfb]; let invalid_header = vec![0x51u8, 0x46, 0x4a, 0xfb];
with_basic_file(&invalid_header, |mut disk_file: File| { with_basic_file(&invalid_header, |mut disk_file: RawFile| {
QcowHeader::new(&mut disk_file).expect_err("Invalid header worked."); QcowHeader::new(&mut disk_file).expect_err("Invalid header worked.");
}); });
} }
@@ -1841,7 +1842,7 @@ mod tests {
fn invalid_refcount_order() { fn invalid_refcount_order() {
let mut header = valid_header_v3(); let mut header = valid_header_v3();
header[99] = 2; header[99] = 2;
with_basic_file(&header, |disk_file: File| { with_basic_file(&header, |disk_file: RawFile| {
QcowFile::from(disk_file).expect_err("Invalid refcount order worked."); QcowFile::from(disk_file).expect_err("Invalid refcount order worked.");
}); });
} }
@@ -1850,7 +1851,7 @@ mod tests {
fn invalid_cluster_bits() { fn invalid_cluster_bits() {
let mut header = valid_header_v3(); let mut header = valid_header_v3();
header[23] = 3; header[23] = 3;
with_basic_file(&header, |disk_file: File| { with_basic_file(&header, |disk_file: RawFile| {
QcowFile::from(disk_file).expect_err("Failed to create file."); QcowFile::from(disk_file).expect_err("Failed to create file.");
}); });
} }
@@ -1858,7 +1859,7 @@ mod tests {
#[test] #[test]
fn test_header_huge_file() { fn test_header_huge_file() {
let header = test_huge_header(); let header = test_huge_header();
with_basic_file(&header, |disk_file: File| { with_basic_file(&header, |disk_file: RawFile| {
QcowFile::from(disk_file).expect_err("Failed to create file."); QcowFile::from(disk_file).expect_err("Failed to create file.");
}); });
} }
@@ -1867,7 +1868,7 @@ mod tests {
fn test_header_crazy_file_size_rejected() { fn test_header_crazy_file_size_rejected() {
let mut header = valid_header_v3(); let mut header = valid_header_v3();
&mut header[24..32].copy_from_slice(&[0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1e]); &mut header[24..32].copy_from_slice(&[0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1e]);
with_basic_file(&header, |disk_file: File| { with_basic_file(&header, |disk_file: RawFile| {
QcowFile::from(disk_file).expect_err("Failed to create file."); QcowFile::from(disk_file).expect_err("Failed to create file.");
}); });
} }
@@ -1876,7 +1877,7 @@ mod tests {
fn test_huge_l1_table() { fn test_huge_l1_table() {
let mut header = valid_header_v3(); let mut header = valid_header_v3();
header[36] = 0x12; header[36] = 0x12;
with_basic_file(&header, |disk_file: File| { with_basic_file(&header, |disk_file: RawFile| {
QcowFile::from(disk_file).expect_err("Failed to create file."); QcowFile::from(disk_file).expect_err("Failed to create file.");
}); });
} }
@@ -1888,7 +1889,7 @@ mod tests {
header[26] = 1; header[26] = 1;
header[31] = 0; header[31] = 0;
// 1 TB with the min cluster size makes the arrays too big, it should fail. // 1 TB with the min cluster size makes the arrays too big, it should fail.
with_basic_file(&header, |disk_file: File| { with_basic_file(&header, |disk_file: RawFile| {
QcowFile::from(disk_file).expect_err("Failed to create file."); QcowFile::from(disk_file).expect_err("Failed to create file.");
}); });
} }
@@ -1902,7 +1903,7 @@ mod tests {
header[31] = 0; header[31] = 0;
// set cluster_bits // set cluster_bits
header[23] = 16; header[23] = 16;
with_basic_file(&header, |disk_file: File| { with_basic_file(&header, |disk_file: RawFile| {
let mut qcow = QcowFile::from(disk_file).expect("Failed to create file."); let mut qcow = QcowFile::from(disk_file).expect("Failed to create file.");
qcow.seek(SeekFrom::Start(0x100_0000_0000 - 8)) qcow.seek(SeekFrom::Start(0x100_0000_0000 - 8))
.expect("Failed to seek."); .expect("Failed to seek.");
@@ -1916,7 +1917,7 @@ mod tests {
fn test_header_huge_num_refcounts() { fn test_header_huge_num_refcounts() {
let mut header = valid_header_v3(); let mut header = valid_header_v3();
&mut header[56..60].copy_from_slice(&[0x02, 0x00, 0xe8, 0xff]); &mut header[56..60].copy_from_slice(&[0x02, 0x00, 0xe8, 0xff]);
with_basic_file(&header, |disk_file: File| { with_basic_file(&header, |disk_file: RawFile| {
QcowFile::from(disk_file).expect_err("Created disk with crazy refcount clusters"); QcowFile::from(disk_file).expect_err("Created disk with crazy refcount clusters");
}); });
} }
@@ -1925,14 +1926,14 @@ mod tests {
fn test_header_huge_refcount_offset() { fn test_header_huge_refcount_offset() {
let mut header = valid_header_v3(); let mut header = valid_header_v3();
&mut header[48..56].copy_from_slice(&[0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x02, 0x00]); &mut header[48..56].copy_from_slice(&[0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x02, 0x00]);
with_basic_file(&header, |disk_file: File| { with_basic_file(&header, |disk_file: RawFile| {
QcowFile::from(disk_file).expect_err("Created disk with crazy refcount offset"); QcowFile::from(disk_file).expect_err("Created disk with crazy refcount offset");
}); });
} }
#[test] #[test]
fn write_read_start() { fn write_read_start() {
with_basic_file(&valid_header_v3(), |disk_file: File| { with_basic_file(&valid_header_v3(), |disk_file: RawFile| {
let mut q = QcowFile::from(disk_file).unwrap(); let mut q = QcowFile::from(disk_file).unwrap();
q.write(b"test first bytes") q.write(b"test first bytes")
.expect("Failed to write test string."); .expect("Failed to write test string.");
@@ -1945,7 +1946,7 @@ mod tests {
#[test] #[test]
fn offset_write_read() { fn offset_write_read() {
with_basic_file(&valid_header_v3(), |disk_file: File| { with_basic_file(&valid_header_v3(), |disk_file: RawFile| {
let mut q = QcowFile::from(disk_file).unwrap(); let mut q = QcowFile::from(disk_file).unwrap();
let b = [0x55u8; 0x1000]; let b = [0x55u8; 0x1000];
q.seek(SeekFrom::Start(0xfff2000)).expect("Failed to seek."); q.seek(SeekFrom::Start(0xfff2000)).expect("Failed to seek.");
@@ -1959,7 +1960,7 @@ mod tests {
#[test] #[test]
fn write_zeroes_read() { fn write_zeroes_read() {
with_basic_file(&valid_header_v3(), |disk_file: File| { with_basic_file(&valid_header_v3(), |disk_file: RawFile| {
let mut q = QcowFile::from(disk_file).unwrap(); let mut q = QcowFile::from(disk_file).unwrap();
// Write some test data. // Write some test data.
let b = [0x55u8; 0x1000]; let b = [0x55u8; 0x1000];
@@ -1985,7 +1986,7 @@ mod tests {
// Choose a size that is larger than a cluster. // Choose a size that is larger than a cluster.
// valid_header uses cluster_bits = 12, which corresponds to a cluster size of 4096. // valid_header uses cluster_bits = 12, which corresponds to a cluster size of 4096.
const CHUNK_SIZE: usize = 4096 * 2 + 512; const CHUNK_SIZE: usize = 4096 * 2 + 512;
with_basic_file(&valid_header_v3(), |disk_file: File| { with_basic_file(&valid_header_v3(), |disk_file: RawFile| {
let mut q = QcowFile::from(disk_file).unwrap(); let mut q = QcowFile::from(disk_file).unwrap();
// Write some test data. // Write some test data.
let b = [0x55u8; CHUNK_SIZE]; let b = [0x55u8; CHUNK_SIZE];
@@ -2006,11 +2007,11 @@ mod tests {
#[test] #[test]
fn test_header() { fn test_header() {
with_basic_file(&valid_header_v2(), |disk_file: File| { with_basic_file(&valid_header_v2(), |disk_file: RawFile| {
let q = QcowFile::from(disk_file).unwrap(); let q = QcowFile::from(disk_file).unwrap();
assert_eq!(q.virtual_size(), 0x20_0000_0000); assert_eq!(q.virtual_size(), 0x20_0000_0000);
}); });
with_basic_file(&valid_header_v3(), |disk_file: File| { with_basic_file(&valid_header_v3(), |disk_file: RawFile| {
let q = QcowFile::from(disk_file).unwrap(); let q = QcowFile::from(disk_file).unwrap();
assert_eq!(q.virtual_size(), 0x20_0000_0000); assert_eq!(q.virtual_size(), 0x20_0000_0000);
}); });
@@ -2018,7 +2019,7 @@ mod tests {
#[test] #[test]
fn read_small_buffer() { fn read_small_buffer() {
with_basic_file(&valid_header_v3(), |disk_file: File| { with_basic_file(&valid_header_v3(), |disk_file: RawFile| {
let mut q = QcowFile::from(disk_file).unwrap(); let mut q = QcowFile::from(disk_file).unwrap();
let mut b = [5u8; 16]; let mut b = [5u8; 16];
q.seek(SeekFrom::Start(1000)).expect("Failed to seek."); q.seek(SeekFrom::Start(1000)).expect("Failed to seek.");
@@ -2030,7 +2031,7 @@ mod tests {
#[test] #[test]
fn replay_ext4() { fn replay_ext4() {
with_basic_file(&valid_header_v3(), |disk_file: File| { with_basic_file(&valid_header_v3(), |disk_file: RawFile| {
let mut q = QcowFile::from(disk_file).unwrap(); let mut q = QcowFile::from(disk_file).unwrap();
const BUF_SIZE: usize = 0x1000; const BUF_SIZE: usize = 0x1000;
let mut b = [0u8; BUF_SIZE]; let mut b = [0u8; BUF_SIZE];
@@ -2406,7 +2407,16 @@ mod tests {
#[test] #[test]
fn combo_write_read() { fn combo_write_read() {
with_default_file(1024 * 1024 * 1024 * 256, |mut qcow_file| { combo_write_read_common(false);
}
#[test]
fn combo_write_read_direct() {
combo_write_read_common(true);
}
fn combo_write_read_common(direct: bool) {
with_default_file(1024 * 1024 * 1024 * 256, direct, |mut qcow_file| {
const NUM_BLOCKS: usize = 555; const NUM_BLOCKS: usize = 555;
const BLOCK_SIZE: usize = 0x1_0000; const BLOCK_SIZE: usize = 0x1_0000;
const OFFSET: usize = 0x1_0000_0020; const OFFSET: usize = 0x1_0000_0020;
@@ -2459,7 +2469,16 @@ mod tests {
#[test] #[test]
fn seek_data() { fn seek_data() {
with_default_file(0x30000, |mut file| { seek_data_common(false);
}
#[test]
fn seek_data_direct() {
seek_data_common(true);
}
fn seek_data_common(direct: bool) {
with_default_file(0x30000, direct, |mut file| {
// seek_data at or after the end of the file should return None // seek_data at or after the end of the file should return None
assert_eq!(file.seek_data(0x10000).unwrap(), None); assert_eq!(file.seek_data(0x10000).unwrap(), None);
assert_eq!(seek_cur(&mut file), 0); assert_eq!(seek_cur(&mut file), 0);
@@ -2492,7 +2511,16 @@ mod tests {
#[test] #[test]
fn seek_hole() { fn seek_hole() {
with_default_file(0x30000, |mut file| { seek_hole_common(false);
}
#[test]
fn seek_hole_direct() {
seek_hole_common(true);
}
fn seek_hole_common(direct: bool) {
with_default_file(0x30000, direct, |mut file| {
// File consisting entirely of a hole // File consisting entirely of a hole
assert_eq!(file.seek_hole(0).unwrap(), Some(0)); assert_eq!(file.seek_hole(0).unwrap(), Some(0));
assert_eq!(seek_cur(&mut file), 0); assert_eq!(seek_cur(&mut file), 0);
@@ -2566,7 +2594,7 @@ mod tests {
#[test] #[test]
fn rebuild_refcounts() { fn rebuild_refcounts() {
with_basic_file(&valid_header_v3(), |mut disk_file: File| { with_basic_file(&valid_header_v3(), |mut disk_file: RawFile| {
let header = QcowHeader::new(&mut disk_file).expect("Failed to create Header."); let header = QcowHeader::new(&mut disk_file).expect("Failed to create Header.");
let cluster_size = 65536; let cluster_size = 65536;
let mut raw_file = let mut raw_file =

View File

@@ -2,17 +2,17 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE-BSD-3-Clause file. // found in the LICENSE-BSD-3-Clause file.
use std::fs::File;
use std::io::{self, BufWriter, Seek, SeekFrom}; use std::io::{self, BufWriter, Seek, SeekFrom};
use std::mem::size_of; use std::mem::size_of;
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use vm_virtio::RawFile;
use vmm_sys_util::write_zeroes::WriteZeroes; use vmm_sys_util::write_zeroes::WriteZeroes;
/// A qcow file. Allows reading/writing clusters and appending clusters. /// A qcow file. Allows reading/writing clusters and appending clusters.
#[derive(Debug)] #[derive(Debug)]
pub struct QcowRawFile { pub struct QcowRawFile {
file: File, file: RawFile,
cluster_size: u64, cluster_size: u64,
cluster_mask: u64, cluster_mask: u64,
} }
@@ -20,7 +20,7 @@ pub struct QcowRawFile {
impl QcowRawFile { impl QcowRawFile {
/// Creates a `QcowRawFile` from the given `File`, `None` is returned if `cluster_size` is not /// Creates a `QcowRawFile` from the given `File`, `None` is returned if `cluster_size` is not
/// a power of two. /// a power of two.
pub fn from(file: File, cluster_size: u64) -> Option<Self> { pub fn from(file: RawFile, cluster_size: u64) -> Option<Self> {
if cluster_size.count_ones() != 1 { if cluster_size.count_ones() != 1 {
return None; return None;
} }
@@ -67,7 +67,7 @@ impl QcowRawFile {
non_zero_flags: u64, non_zero_flags: u64,
) -> io::Result<()> { ) -> io::Result<()> {
self.file.seek(SeekFrom::Start(offset))?; self.file.seek(SeekFrom::Start(offset))?;
let mut buffer = BufWriter::with_capacity(table.len() * size_of::<u64>(), &self.file); let mut buffer = BufWriter::with_capacity(table.len() * size_of::<u64>(), &mut self.file);
for addr in table { for addr in table {
let val = if *addr == 0 { let val = if *addr == 0 {
0 0
@@ -92,7 +92,7 @@ impl QcowRawFile {
/// Writes a refcount block to the file. /// Writes a refcount block to the file.
pub fn write_refcount_block(&mut self, offset: u64, table: &[u16]) -> io::Result<()> { pub fn write_refcount_block(&mut self, offset: u64, table: &[u16]) -> io::Result<()> {
self.file.seek(SeekFrom::Start(offset))?; self.file.seek(SeekFrom::Start(offset))?;
let mut buffer = BufWriter::with_capacity(table.len() * size_of::<u16>(), &self.file); let mut buffer = BufWriter::with_capacity(table.len() * size_of::<u16>(), &mut self.file);
for count in table { for count in table {
buffer.write_u16::<BigEndian>(*count)?; buffer.write_u16::<BigEndian>(*count)?;
} }
@@ -115,13 +115,8 @@ impl QcowRawFile {
Ok(Some(new_cluster_address)) Ok(Some(new_cluster_address))
} }
/// Returns a reference to the underlying file.
pub fn file(&self) -> &File {
&self.file
}
/// Returns a mutable reference to the underlying file. /// Returns a mutable reference to the underlying file.
pub fn file_mut(&mut self) -> &mut File { pub fn file_mut(&mut self) -> &mut RawFile {
&mut self.file &mut self.file
} }

View File

@@ -1,32 +1,199 @@
- [v0.5.1](#v051)
- [v0.5.0](#v050)
- [Virtual Machine Dynamic Resizing](#virtual-machine-dynamic-resizing)
- [Multi-Queue, Multi-Threaded Paravirtualization](#multi-queue-multi-threaded-paravirtualization)
- [New Interrupt Management Framework](#new-interrupt-management-framework)
- [Development Tools](#development-tools)
- [Kata Containers Integration](#kata-containers-integration)
- [Contributors](#contributors)
- [v0.4.0](#v040)
- [Dynamic virtual CPUs addition](#dynamic-virtual-cpus-addition)
- [Programmatic firmware tables generation](#programmatic-firmware-tables-generation)
- [Filesystem and block devices vhost-user backends](#filesystem-and-block-devices-vhost-user-backends)
- [Guest pause and resume](#guest-pause-and-resume)
- [Userspace IOAPIC by default](#userspace-ioapic-by-default)
- [PCI BAR reprogramming](#pci-bar-reprogramming)
- [New `cloud-hypervisor` organization](#new-cloud-hypervisor-organization)
- [Contributors](#contributors-1)
- [v0.3.0](#v030) - [v0.3.0](#v030)
+ [Block device offloading](#block-device-offloading) - [Block device offloading](#block-device-offloading)
+ [Network device backend](#network-device-backend) - [Network device backend](#network-device-backend)
+ [Virtual sockets](#virtual-sockets) - [Virtual sockets](#virtual-sockets)
+ [HTTP based API](#http-based-api) - [HTTP based API](#http-based-api)
+ [Memory mapped virtio transport](#memory-mapped-virtio-transport) - [Memory mapped virtio transport](#memory-mapped-virtio-transport)
+ [Paravirtualized IOMMU](#paravirtualized-iommu) - [Paravirtualized IOMMU](#paravirtualized-iommu)
+ [Ubuntu 19.10](#ubuntu-1910) - [Ubuntu 19.10](#ubuntu-1910)
+ [Guest large memory](#guest-large-memory) - [Large memory guests](#large-memory-guests)
- [v0.2.0](#v020) - [v0.2.0](#v020)
+ [Network device offloading](#network-device-offloading) - [Network device offloading](#network-device-offloading)
+ [Minimal hardware-reduced ACPI](#minimal-hardware-reduced-acpi) - [Minimal hardware-reduced ACPI](#minimal-hardware-reduced-acpi)
+ [Debug I/O port](#debug-i-o-port) - [Debug I/O port](#debug-io-port)
+ [Improved direct device assignment](#improved-direct-device-assignment) - [Improved direct device assignment](#improved-direct-device-assignment)
+ [Improved shared filesystem](#improved-shared-filesystem) - [Improved shared filesystem](#improved-shared-filesystem)
+ [Ubuntu bionic based CI](#ubuntu-bionic-based-ci) - [Ubuntu bionic based CI](#ubuntu-bionic-based-ci)
- [v0.1.0](#v010) - [v0.1.0](#v010)
+ [Shared filesystem](#shared-filesystem) - [Shared filesystem](#shared-filesystem)
+ [Initial direct device assignment support](#initial-direct-device-assignment-support) - [Initial direct device assignment support](#initial-direct-device-assignment-support)
+ [Userspace IOAPIC](#userspace-ioapic) - [Userspace IOAPIC](#userspace-ioapic)
+ [Virtual persistent memory](#virtual-persistent-memory) - [Virtual persistent memory](#virtual-persistent-memory)
+ [Linux kernel bzImage](#linux-kernel-bzimage) - [Linux kernel bzImage](#linux-kernel-bzimage)
+ [Console over virtio](#console-over-virtio) - [Console over virtio](#console-over-virtio)
+ [Unit testing](#unit-testing) - [Unit testing](#unit-testing)
+ [Integration tests parallelization](#integration-tests-parallelization) - [Integration tests parallelization](#integration-tests-parallelization)
# v0.5.1
This is a bugfix release branched off v0.5.0. It contains the following fixes:
* Update DiskConfig to contain missing disk control features (#790) - Samuel Ortiz and Sergio Lopez
* Prevent memory overcommit via virtio-fs (#763) - Sebastien Boeuf
* Fixed error reporting for resize command - Samuel Ortiz
* Double reboot workaround (#783) - Rob Bradford
* Various CI and development tooling fixes - Sebastien Boeuf, Samuel Ortiz, Rob Bradford
# v0.5.0
This release has been tracked through the [0.5.0 project](https://github.com/cloud-hypervisor/cloud-hypervisor/projects/6).
Highlights for `cloud-hypervisor` version 0.5.0 include:
### Virtual Machine Dynamic Resizing
With 0.4.0 we added support for CPU hot plug, and 0.5.0 adds CPU hot unplug and
memory hot plug as well. This allows to dynamically resize Cloud Hypervisor
guests which is needed for e.g. Kubernetes related use cases.
The memory hot plug implementation is based on the same framework as the CPU hot
plug/unplug one, i.e. hardware-reduced ACPI notifications to the guest.
Next on our VM resizing roadmap is the PCI devices hotplug feature.
### Multi-Queue, Multi-Threaded Paravirtualization
We enhanced our virtio networking and block support by having both devices use
multiple I/O queues handled by multiple threads. This improves our default
paravirtualized networking and block devices throughput.
### New Interrupt Management Framework
We improved our interrupt management implementation by introducing an Interrupt
Manager framework, based on the currently on-going [rust-vmm vm-device](https://github.com/rust-vmm/vm-device)
crates discussions. This move made the code significantly cleaner, and allowed
us to remove several KVM related dependencies from crates like the PCI and
virtio ones.
### Development Tools
In order to provide a better developer experience, we worked on improving our
build, development and testing tools.
Somehow similar to the excellent
[Firecracker's devtool](https://github.com/firecracker-microvm/firecracker/blob/master/tools/devtool),
we now provide a [dev_cli script](https://github.com/cloud-hypervisor/cloud-hypervisor/blob/master/scripts/dev_cli.sh).
With this new tool, our users and contributors will be able to build and test
Cloud Hypervisor through a containerized environment.
### Kata Containers Integration
We spent some significant time and efforts debugging and fixing our integration
with the [Kata Containers](https://github.com/kata-containers) project. Cloud
Hypervisor is now a fully supported Kata Containers hypervisor, and is
integrated into the project's CI.
### Contributors
Many thanks to everyone that contributed to the 0.5.0 release:
* Bo Chen <chen.bo@intel.com>
* Cathy Zhang <cathy.zhang@intel.com>
* Qiu Wenbo <qiuwenbo@phytium.com.cn>
* Rob Bradford <robert.bradford@intel.com>
* Samuel Ortiz <sameo@linux.intel.com>
* Sebastien Boeuf <sebastien.boeuf@intel.com>
* Sergio Lopez <slp@redhat.com>
* Yang Zhong <yang.zhong@intel.com>
# v0.4.0
This release has been tracked through the [0.4.0 project](https://github.com/cloud-hypervisor/cloud-hypervisor/projects/4).
Highlights for `cloud-hypervisor` version 0.4.0 include:
### Dynamic virtual CPUs addition
As a way to vertically scale Cloud-Hypervisor guests, we now support dynamically
adding virtual CPUs to the guests, a mechanism also known as CPU hot plug.
Through hardware-reduced ACPI notifications, Cloud Hypervisor can now add CPUs
to an already running guest and the high level operations for that process are
documented [here](https://github.com/cloud-hypervisor/cloud-hypervisor/blob/master/docs/hotplug.md)
During the next release cycles we are planning to extend Cloud Hypervisor
hot plug framework to other resources, namely PCI devices and memory.
### Programmatic firmware tables generation
As part of the CPU hot plug feature enablement, and as a requirement for hot
plugging other resources like devices or RAM, we added support for
programmatically generating the needed ACPI tables. Through a dedicated
`acpi-tables` crate, we now have a flexible and clean way of generating those
tables based on the VMM device model and topology.
### Filesystem and block devices vhost-user backends
Our objective of running all Cloud Hypervisor paravirtualized I/O to a
vhost-user based framework is getting closer as we've added Rust based
implementations for vhost-user-blk and virtiofs backends. Together with the
vhost-user-net backend that came with the 0.3.0 release, this will form the
default Cloud Hypervisor I/O architecture.
### Guest pause and resume
As an initial requiremnt for enabling live migration, we added support for
pausing and resuming any VMM components. As an intermediate step towards live
migration, the upcoming guest snapshotting feature will be based on the pause
and resume capabilities.
### Userspace IOAPIC by default
As a way to simplify our device manager implementation, but also in order to
stay away from privileged rings as often as possible, any device that relies on
pin based interrupts will be using the userspace IOAPIC implementation by
default.
### PCI BAR reprogramming
In order to allow for a more flexible device model, and also support guests
that would want to move PCI devices, we added support for PCI devices BAR
reprogramming.
### New `cloud-hypervisor` organization
As we wanted to be more flexible on how we manage the Cloud Hypervisor project,
we decided to move it under a [dedicated GitHub organization](https://github.com/cloud-hypervisor).
Together with the [cloud-hypervisor](https://github.com/cloud-hypervisor/cloud-hypervisor)
project, this new organization also now hosts our [kernel](https://github.com/cloud-hypervisor/linux)
and [firmware](https://github.com/cloud-hypervisor/rust-hypervisor-firmware)
repositories. We may also use it to host any rust-vmm that we'd need to
temporarily fork.
Thanks to GitHub's seamless repository redirections, the move is completely
transparent to all Cloud Hypervisor contributors, users and followers.
### Contributors
Many thanks to everyone that contributed to the 0.4.0 release:
* Cathy Zhang <cathy.zhang@intel.com>
* Emin Ghuliev <drmint80@gmail.com>
* Jose Carlos Venegas Munoz <jose.carlos.venegas.munoz@intel.com>
* Qiu Wenbo <qiuwenbo@phytium.com.cn>
* Rob Bradford <robert.bradford@intel.com>
* Samuel Ortiz <sameo@linux.intel.com>
* Sebastien Boeuf <sebastien.boeuf@intel.com>
* Sergio Lopez <slp@redhat.com>
* Wu Zongyong <wuzongyong@linux.alibaba.com>
# v0.3.0 # v0.3.0
This release has been tracked through the [0.3.0 project](https://github.com/intel/cloud-hypervisor/projects/3). This release has been tracked through the [0.3.0 project](https://github.com/cloud-hypervisor/cloud-hypervisor/projects/3).
Highlights for `cloud-hypervisor` version 0.3.0 include: Highlights for `cloud-hypervisor` version 0.3.0 include:
@@ -73,7 +240,7 @@ configurations that do not require a PCI bus emulation.
### Paravirtualized IOMMU ### Paravirtualized IOMMU
As we want to improve our nested guests support, we added support for exposing As we want to improve our nested guests support, we added support for exposing
a [paravirtualized IOMMU](https://github.com/intel/cloud-hypervisor/blob/master/docs/iommu.md) a [paravirtualized IOMMU](https://github.com/cloud-hypervisor/cloud-hypervisor/blob/master/docs/iommu.md)
device through virtio. This allows for a safer nested virtio and directly device through virtio. This allows for a safer nested virtio and directly
assigned devices support. assigned devices support.
@@ -85,7 +252,7 @@ setting.
### Ubuntu 19.10 ### Ubuntu 19.10
With the latest [hypervisor firmware](https://github.com/intel/rust-hypervisor-firmware), With the latest [hypervisor firmware](https://github.com/cloud-hypervisor/rust-hypervisor-firmware),
we can now support the latest we can now support the latest
[Ubuntu 19.10 (Eoan Ermine)](http://releases.ubuntu.com/19.10/) cloud images. [Ubuntu 19.10 (Eoan Ermine)](http://releases.ubuntu.com/19.10/) cloud images.
@@ -96,7 +263,7 @@ support guests with large amount of memory (more than 64GB).
# v0.2.0 # v0.2.0
This release has been tracked through the [0.2.0 project](https://github.com/intel/cloud-hypervisor/projects/2). This release has been tracked through the [0.2.0 project](https://github.com/cloud-hypervisor/cloud-hypervisor/projects/2).
Highlights for `cloud-hypervisor` version 0.2.0 include: Highlights for `cloud-hypervisor` version 0.2.0 include:
@@ -124,7 +291,7 @@ Based on the Firecracker idea of using a dedicated I/O port to measure guest
boot times, we added support for logging guest events through the boot times, we added support for logging guest events through the
[0x80](https://www.intel.com/content/www/us/en/support/articles/000005500/boards-and-kits.html) [0x80](https://www.intel.com/content/www/us/en/support/articles/000005500/boards-and-kits.html)
PC debug port. This allows, among other things, for granular guest boot time PC debug port. This allows, among other things, for granular guest boot time
measurements. See our [debug port documentation](https://github.com/intel/cloud-hypervisor/blob/master/docs/debug-port.md) measurements. See our [debug port documentation](https://github.com/cloud-hypervisor/cloud-hypervisor/blob/master/docs/debug-port.md)
for more details. for more details.
### Improved direct device assignment ### Improved direct device assignment
@@ -144,13 +311,13 @@ memory footprint.
### Ubuntu bionic based CI ### Ubuntu bionic based CI
Thanks to our [simple KVM firmware](https://github.com/intel/rust-hypervisor-firmware) Thanks to our [simple KVM firmware](https://github.com/cloud-hypervisor/rust-hypervisor-firmware)
improvements, we are now able to boot Ubuntu bionic images. We added those to improvements, we are now able to boot Ubuntu bionic images. We added those to
our CI pipeline. our CI pipeline.
# v0.1.0 # v0.1.0
This release has been tracked through the [0.1.0 project](https://github.com/intel/cloud-hypervisor/projects/1). This release has been tracked through the [0.1.0 project](https://github.com/cloud-hypervisor/cloud-hypervisor/projects/1).
Highlights for `cloud-hypervisor` version 0.1.0 include: Highlights for `cloud-hypervisor` version 0.1.0 include:
@@ -160,7 +327,7 @@ We added support for the [virtio-fs](https://virtio-fs.gitlab.io/) shared file
system, allowing for an efficient and reliable way of sharing a filesystem system, allowing for an efficient and reliable way of sharing a filesystem
between the host and the `cloud-hypervisor` guest. between the host and the `cloud-hypervisor` guest.
See our [filesystem sharing](https://github.com/intel/cloud-hypervisor/blob/master/docs/fs.md) See our [filesystem sharing](https://github.com/cloud-hypervisor/cloud-hypervisor/blob/master/docs/fs.md)
documentation for more details on how to use virtio-fs with `cloud-hypervisor`. documentation for more details on how to use virtio-fs with `cloud-hypervisor`.
### Initial direct device assignment support ### Initial direct device assignment support
@@ -169,7 +336,7 @@ VFIO (Virtual Function I/O) is a kernel framework that exposes direct device
access to userspace. `cloud-hypervisor` uses VFIO to directly assign host access to userspace. `cloud-hypervisor` uses VFIO to directly assign host
physical devices into its guest. physical devices into its guest.
See our [VFIO](https://github.com/intel/cloud-hypervisor/blob/master/docs/vfio.md) See our [VFIO](https://github.com/cloud-hypervisor/cloud-hypervisor/blob/master/docs/vfio.md)
documentation for more detail on how to directly assign host devices to documentation for more detail on how to directly assign host devices to
`cloud-hypervisor` guests. `cloud-hypervisor` guests.

53
resources/Dockerfile Normal file
View File

@@ -0,0 +1,53 @@
FROM ubuntu:18.04 as dev
ARG RUST_TOOLCHAIN="1.40.0"
ARG CLH_SRC_DIR="/cloud-hypervisor"
ARG CLH_BUILD_DIR="$CLH_SRC_DIR/build"
ARG CARGO_REGISTRY_DIR="$CLH_BUILD_DIR/cargo_registry"
ARG CARGO_GIT_REGISTRY_DIR="$CLH_BUILD_DIR/cargo_git_registry"
ENV CARGO_HOME=/usr/local/rust
ENV RUSTUP_HOME=$CARGO_HOME
ENV PATH="$PATH:$CARGO_HOME/bin"
# Install all CI dependencies
RUN apt-get update
RUN apt-get -yq upgrade
RUN DEBIAN_FRONTEND=noninteractive apt-get install -yq \
build-essential \
bc \
docker.io \
curl \
wget \
sudo \
mtools \
libssl-dev \
pkg-config \
flex \
bison \
libelf-dev \
qemu-utils \
qemu-system \
libglib2.0-dev \
libpixman-1-dev \
libseccomp-dev \
libcap-ng-dev \
socat \
dosfstools \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# Install the rust toolchain
RUN nohup curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain "$RUST_TOOLCHAIN" \
&& rustup component add rustfmt \
&& rustup component add clippy \
&& cargo install cargo-audit \
&& rm -rf "$CARGO_HOME/registry" \
&& ln -s "$CARGO_REGISTRY_DIR" "$CARGO_HOME/registry" \
&& rm -rf "$CARGO_HOME/git" \
&& ln -s "$CARGO_GIT_REGISTRY_DIR" "$CARGO_HOME/git"
# Set the rust environment
RUN echo 'source $CARGO_HOME/env' >> $HOME/.bashrc \
&& mkdir $HOME/.cargo \
&& ln -s $CARGO_HOME/env $HOME/.cargo/env

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
# #
# Automatically generated file; DO NOT EDIT. # Automatically generated file; DO NOT EDIT.
# Linux/x86 5.3.0-rc3 Kernel Configuration # Linux/x86 5.5.0-rc1 Kernel Configuration
# #
# #
@@ -11,6 +11,7 @@ CONFIG_GCC_VERSION=80300
CONFIG_CLANG_VERSION=0 CONFIG_CLANG_VERSION=0
CONFIG_CC_CAN_LINK=y CONFIG_CC_CAN_LINK=y
CONFIG_CC_HAS_ASM_GOTO=y CONFIG_CC_HAS_ASM_GOTO=y
CONFIG_CC_HAS_ASM_INLINE=y
CONFIG_CC_HAS_WARN_MAYBE_UNINITIALIZED=y CONFIG_CC_HAS_WARN_MAYBE_UNINITIALIZED=y
CONFIG_IRQ_WORK=y CONFIG_IRQ_WORK=y
CONFIG_BUILDTIME_EXTABLE_SORT=y CONFIG_BUILDTIME_EXTABLE_SORT=y
@@ -21,7 +22,6 @@ CONFIG_THREAD_INFO_IN_TASK=y
# #
CONFIG_INIT_ENV_ARG_LIMIT=32 CONFIG_INIT_ENV_ARG_LIMIT=32
# CONFIG_COMPILE_TEST is not set # CONFIG_COMPILE_TEST is not set
# CONFIG_HEADER_TEST is not set
CONFIG_LOCALVERSION="" CONFIG_LOCALVERSION=""
# CONFIG_LOCALVERSION_AUTO is not set # CONFIG_LOCALVERSION_AUTO is not set
CONFIG_BUILD_SALT="" CONFIG_BUILD_SALT=""
@@ -139,6 +139,7 @@ CONFIG_HAVE_UNSTABLE_SCHED_CLOCK=y
CONFIG_ARCH_SUPPORTS_NUMA_BALANCING=y CONFIG_ARCH_SUPPORTS_NUMA_BALANCING=y
CONFIG_ARCH_WANT_BATCHED_UNMAP_TLB_FLUSH=y CONFIG_ARCH_WANT_BATCHED_UNMAP_TLB_FLUSH=y
CONFIG_CC_HAS_INT128=y
CONFIG_ARCH_SUPPORTS_INT128=y CONFIG_ARCH_SUPPORTS_INT128=y
CONFIG_NUMA_BALANCING=y CONFIG_NUMA_BALANCING=y
# CONFIG_NUMA_BALANCING_DEFAULT_ENABLED is not set # CONFIG_NUMA_BALANCING_DEFAULT_ENABLED is not set
@@ -183,7 +184,7 @@ CONFIG_SYSCTL=y
CONFIG_SYSCTL_EXCEPTION_TRACE=y CONFIG_SYSCTL_EXCEPTION_TRACE=y
CONFIG_HAVE_PCSPKR_PLATFORM=y CONFIG_HAVE_PCSPKR_PLATFORM=y
CONFIG_BPF=y CONFIG_BPF=y
# CONFIG_EXPERT is not set CONFIG_EXPERT=y
CONFIG_MULTIUSER=y CONFIG_MULTIUSER=y
CONFIG_SGETMASK_SYSCALL=y CONFIG_SGETMASK_SYSCALL=y
CONFIG_SYSFS_SYSCALL=y CONFIG_SYSFS_SYSCALL=y
@@ -214,8 +215,10 @@ CONFIG_BPF_SYSCALL=y
CONFIG_USERFAULTFD=y CONFIG_USERFAULTFD=y
CONFIG_ARCH_HAS_MEMBARRIER_SYNC_CORE=y CONFIG_ARCH_HAS_MEMBARRIER_SYNC_CORE=y
CONFIG_RSEQ=y CONFIG_RSEQ=y
# CONFIG_DEBUG_RSEQ is not set
# CONFIG_EMBEDDED is not set # CONFIG_EMBEDDED is not set
CONFIG_HAVE_PERF_EVENTS=y CONFIG_HAVE_PERF_EVENTS=y
# CONFIG_PC104 is not set
# #
# Kernel Performance Events And Counters # Kernel Performance Events And Counters
@@ -226,9 +229,11 @@ CONFIG_PERF_EVENTS=y
CONFIG_VM_EVENT_COUNTERS=y CONFIG_VM_EVENT_COUNTERS=y
CONFIG_SLUB_DEBUG=y CONFIG_SLUB_DEBUG=y
# CONFIG_SLUB_MEMCG_SYSFS_ON is not set
# CONFIG_COMPAT_BRK is not set # CONFIG_COMPAT_BRK is not set
# CONFIG_SLAB is not set # CONFIG_SLAB is not set
CONFIG_SLUB=y CONFIG_SLUB=y
# CONFIG_SLOB is not set
CONFIG_SLAB_MERGE_DEFAULT=y CONFIG_SLAB_MERGE_DEFAULT=y
# CONFIG_SLAB_FREELIST_RANDOM is not set # CONFIG_SLAB_FREELIST_RANDOM is not set
CONFIG_SLAB_FREELIST_HARDENED=y CONFIG_SLAB_FREELIST_HARDENED=y
@@ -296,6 +301,7 @@ CONFIG_PARAVIRT=y
CONFIG_PARAVIRT_SPINLOCKS=y CONFIG_PARAVIRT_SPINLOCKS=y
# CONFIG_XEN is not set # CONFIG_XEN is not set
CONFIG_KVM_GUEST=y CONFIG_KVM_GUEST=y
CONFIG_ARCH_CPUIDLE_HALTPOLL=y
CONFIG_PVH=y CONFIG_PVH=y
CONFIG_KVM_DEBUG_FS=y CONFIG_KVM_DEBUG_FS=y
CONFIG_PARAVIRT_TIME_ACCOUNTING=y CONFIG_PARAVIRT_TIME_ACCOUNTING=y
@@ -314,6 +320,7 @@ CONFIG_X86_CMPXCHG64=y
CONFIG_X86_CMOV=y CONFIG_X86_CMOV=y
CONFIG_X86_MINIMUM_CPU_FAMILY=64 CONFIG_X86_MINIMUM_CPU_FAMILY=64
CONFIG_X86_DEBUGCTLMSR=y CONFIG_X86_DEBUGCTLMSR=y
# CONFIG_PROCESSOR_SELECT is not set
CONFIG_CPU_SUP_INTEL=y CONFIG_CPU_SUP_INTEL=y
CONFIG_CPU_SUP_AMD=y CONFIG_CPU_SUP_AMD=y
CONFIG_CPU_SUP_HYGON=y CONFIG_CPU_SUP_HYGON=y
@@ -322,7 +329,6 @@ CONFIG_CPU_SUP_ZHAOXIN=y
CONFIG_HPET_TIMER=y CONFIG_HPET_TIMER=y
CONFIG_DMI=y CONFIG_DMI=y
# CONFIG_GART_IOMMU is not set # CONFIG_GART_IOMMU is not set
# CONFIG_CALGARY_IOMMU is not set
# CONFIG_MAXSMP is not set # CONFIG_MAXSMP is not set
CONFIG_NR_CPUS_RANGE_BEGIN=2 CONFIG_NR_CPUS_RANGE_BEGIN=2
CONFIG_NR_CPUS_RANGE_END=512 CONFIG_NR_CPUS_RANGE_END=512
@@ -348,6 +354,7 @@ CONFIG_PERF_EVENTS_INTEL_CSTATE=y
CONFIG_X86_16BIT=y CONFIG_X86_16BIT=y
CONFIG_X86_ESPFIX64=y CONFIG_X86_ESPFIX64=y
CONFIG_X86_VSYSCALL_EMULATION=y CONFIG_X86_VSYSCALL_EMULATION=y
# CONFIG_X86_IOPL_IOPERM is not set
# CONFIG_I8K is not set # CONFIG_I8K is not set
# CONFIG_MICROCODE is not set # CONFIG_MICROCODE is not set
CONFIG_X86_MSR=y CONFIG_X86_MSR=y
@@ -355,7 +362,6 @@ CONFIG_X86_CPUID=y
# CONFIG_X86_5LEVEL is not set # CONFIG_X86_5LEVEL is not set
CONFIG_X86_DIRECT_GBPAGES=y CONFIG_X86_DIRECT_GBPAGES=y
# CONFIG_X86_CPA_STATISTICS is not set # CONFIG_X86_CPA_STATISTICS is not set
CONFIG_ARCH_HAS_MEM_ENCRYPT=y
CONFIG_AMD_MEM_ENCRYPT=y CONFIG_AMD_MEM_ENCRYPT=y
# CONFIG_AMD_MEM_ENCRYPT_ACTIVE_BY_DEFAULT is not set # CONFIG_AMD_MEM_ENCRYPT_ACTIVE_BY_DEFAULT is not set
CONFIG_NUMA=y CONFIG_NUMA=y
@@ -382,9 +388,12 @@ CONFIG_X86_PAT=y
CONFIG_ARCH_USES_PG_UNCACHED=y CONFIG_ARCH_USES_PG_UNCACHED=y
CONFIG_ARCH_RANDOM=y CONFIG_ARCH_RANDOM=y
CONFIG_X86_SMAP=y CONFIG_X86_SMAP=y
CONFIG_X86_INTEL_UMIP=y # CONFIG_X86_UMIP is not set
# CONFIG_X86_INTEL_MPX is not set # CONFIG_X86_INTEL_MPX is not set
# CONFIG_X86_INTEL_MEMORY_PROTECTION_KEYS is not set # CONFIG_X86_INTEL_MEMORY_PROTECTION_KEYS is not set
CONFIG_X86_INTEL_TSX_MODE_OFF=y
# CONFIG_X86_INTEL_TSX_MODE_ON is not set
# CONFIG_X86_INTEL_TSX_MODE_AUTO is not set
CONFIG_EFI=y CONFIG_EFI=y
CONFIG_EFI_STUB=y CONFIG_EFI_STUB=y
# CONFIG_EFI_MIXED is not set # CONFIG_EFI_MIXED is not set
@@ -398,7 +407,7 @@ CONFIG_SCHED_HRTICK=y
# CONFIG_KEXEC is not set # CONFIG_KEXEC is not set
CONFIG_KEXEC_FILE=y CONFIG_KEXEC_FILE=y
CONFIG_ARCH_HAS_KEXEC_PURGATORY=y CONFIG_ARCH_HAS_KEXEC_PURGATORY=y
CONFIG_KEXEC_VERIFY_SIG=y # CONFIG_KEXEC_SIG is not set
# CONFIG_CRASH_DUMP is not set # CONFIG_CRASH_DUMP is not set
CONFIG_PHYSICAL_START=0x1000000 CONFIG_PHYSICAL_START=0x1000000
CONFIG_RELOCATABLE=y CONFIG_RELOCATABLE=y
@@ -467,18 +476,19 @@ CONFIG_ACPI_PROCESSOR=y
CONFIG_ACPI_HOTPLUG_CPU=y CONFIG_ACPI_HOTPLUG_CPU=y
# CONFIG_ACPI_PROCESSOR_AGGREGATOR is not set # CONFIG_ACPI_PROCESSOR_AGGREGATOR is not set
CONFIG_ACPI_THERMAL=y CONFIG_ACPI_THERMAL=y
CONFIG_ACPI_NUMA=y
CONFIG_ARCH_HAS_ACPI_TABLE_UPGRADE=y CONFIG_ARCH_HAS_ACPI_TABLE_UPGRADE=y
# CONFIG_ACPI_DEBUG is not set # CONFIG_ACPI_DEBUG is not set
# CONFIG_ACPI_PCI_SLOT is not set # CONFIG_ACPI_PCI_SLOT is not set
CONFIG_ACPI_CONTAINER=y CONFIG_ACPI_CONTAINER=y
# CONFIG_ACPI_HOTPLUG_MEMORY is not set CONFIG_ACPI_HOTPLUG_MEMORY=y
CONFIG_ACPI_HOTPLUG_IOAPIC=y CONFIG_ACPI_HOTPLUG_IOAPIC=y
# CONFIG_ACPI_SBS is not set # CONFIG_ACPI_SBS is not set
# CONFIG_ACPI_HED is not set # CONFIG_ACPI_HED is not set
# CONFIG_ACPI_CUSTOM_METHOD is not set # CONFIG_ACPI_CUSTOM_METHOD is not set
# CONFIG_ACPI_BGRT is not set # CONFIG_ACPI_BGRT is not set
CONFIG_ACPI_REDUCED_HARDWARE_ONLY=y
# CONFIG_ACPI_NFIT is not set # CONFIG_ACPI_NFIT is not set
CONFIG_ACPI_NUMA=y
# CONFIG_ACPI_HMAT is not set # CONFIG_ACPI_HMAT is not set
CONFIG_HAVE_ACPI_APEI=y CONFIG_HAVE_ACPI_APEI=y
CONFIG_HAVE_ACPI_APEI_NMI=y CONFIG_HAVE_ACPI_APEI_NMI=y
@@ -486,7 +496,6 @@ CONFIG_HAVE_ACPI_APEI_NMI=y
# CONFIG_DPTF_POWER is not set # CONFIG_DPTF_POWER is not set
# CONFIG_PMIC_OPREGION is not set # CONFIG_PMIC_OPREGION is not set
# CONFIG_ACPI_CONFIGFS is not set # CONFIG_ACPI_CONFIGFS is not set
CONFIG_ACPI_IORT=y
CONFIG_X86_PM_TIMER=y CONFIG_X86_PM_TIMER=y
# CONFIG_SFI is not set # CONFIG_SFI is not set
@@ -529,6 +538,8 @@ CONFIG_CPU_IDLE=y
CONFIG_CPU_IDLE_GOV_LADDER=y CONFIG_CPU_IDLE_GOV_LADDER=y
CONFIG_CPU_IDLE_GOV_MENU=y CONFIG_CPU_IDLE_GOV_MENU=y
# CONFIG_CPU_IDLE_GOV_TEO is not set # CONFIG_CPU_IDLE_GOV_TEO is not set
# CONFIG_CPU_IDLE_GOV_HALTPOLL is not set
# CONFIG_HALTPOLL_CPUIDLE is not set
# end of CPU Idle # end of CPU Idle
CONFIG_INTEL_IDLE=y CONFIG_INTEL_IDLE=y
@@ -540,6 +551,8 @@ CONFIG_INTEL_IDLE=y
CONFIG_PCI_DIRECT=y CONFIG_PCI_DIRECT=y
CONFIG_PCI_MMCONFIG=y CONFIG_PCI_MMCONFIG=y
CONFIG_MMCONF_FAM10H=y CONFIG_MMCONF_FAM10H=y
# CONFIG_PCI_CNB20LE_QUIRK is not set
# CONFIG_ISA_BUS is not set
CONFIG_ISA_DMA_API=y CONFIG_ISA_DMA_API=y
CONFIG_AMD_NB=y CONFIG_AMD_NB=y
# CONFIG_X86_SYSFB is not set # CONFIG_X86_SYSFB is not set
@@ -575,6 +588,7 @@ CONFIG_EFI_RUNTIME_WRAPPERS=y
# CONFIG_EFI_TEST is not set # CONFIG_EFI_TEST is not set
# CONFIG_APPLE_PROPERTIES is not set # CONFIG_APPLE_PROPERTIES is not set
# CONFIG_RESET_ATTACK_MITIGATION is not set # CONFIG_RESET_ATTACK_MITIGATION is not set
# CONFIG_EFI_RCI2_TABLE is not set
# end of EFI (Extensible Firmware Interface) Support # end of EFI (Extensible Firmware Interface) Support
CONFIG_EFI_EARLYCON=y CONFIG_EFI_EARLYCON=y
@@ -634,6 +648,7 @@ CONFIG_ARCH_HAS_SET_MEMORY=y
CONFIG_ARCH_HAS_SET_DIRECT_MAP=y CONFIG_ARCH_HAS_SET_DIRECT_MAP=y
CONFIG_HAVE_ARCH_THREAD_STRUCT_WHITELIST=y CONFIG_HAVE_ARCH_THREAD_STRUCT_WHITELIST=y
CONFIG_ARCH_WANTS_DYNAMIC_TASK_STRUCT=y CONFIG_ARCH_WANTS_DYNAMIC_TASK_STRUCT=y
CONFIG_HAVE_ASM_MODVERSIONS=y
CONFIG_HAVE_REGS_AND_STACK_ACCESS_API=y CONFIG_HAVE_REGS_AND_STACK_ACCESS_API=y
CONFIG_HAVE_RSEQ=y CONFIG_HAVE_RSEQ=y
CONFIG_HAVE_FUNCTION_ARG_ACCESS_API=y CONFIG_HAVE_FUNCTION_ARG_ACCESS_API=y
@@ -679,17 +694,16 @@ CONFIG_ARCH_MMAP_RND_BITS=28
CONFIG_HAVE_COPY_THREAD_TLS=y CONFIG_HAVE_COPY_THREAD_TLS=y
CONFIG_HAVE_STACK_VALIDATION=y CONFIG_HAVE_STACK_VALIDATION=y
CONFIG_HAVE_RELIABLE_STACKTRACE=y CONFIG_HAVE_RELIABLE_STACKTRACE=y
CONFIG_64BIT_TIME=y # CONFIG_COMPAT_32BIT_TIME is not set
CONFIG_HAVE_ARCH_VMAP_STACK=y CONFIG_HAVE_ARCH_VMAP_STACK=y
CONFIG_VMAP_STACK=y CONFIG_VMAP_STACK=y
CONFIG_ARCH_HAS_STRICT_KERNEL_RWX=y CONFIG_ARCH_HAS_STRICT_KERNEL_RWX=y
CONFIG_STRICT_KERNEL_RWX=y CONFIG_STRICT_KERNEL_RWX=y
CONFIG_ARCH_HAS_STRICT_MODULE_RWX=y CONFIG_ARCH_HAS_STRICT_MODULE_RWX=y
CONFIG_ARCH_HAS_REFCOUNT=y
# CONFIG_REFCOUNT_FULL is not set
CONFIG_HAVE_ARCH_PREL32_RELOCATIONS=y CONFIG_HAVE_ARCH_PREL32_RELOCATIONS=y
CONFIG_ARCH_USE_MEMREMAP_PROT=y CONFIG_ARCH_USE_MEMREMAP_PROT=y
# CONFIG_LOCK_EVENT_COUNTS is not set # CONFIG_LOCK_EVENT_COUNTS is not set
CONFIG_ARCH_HAS_MEM_ENCRYPT=y
# #
# GCOV-based kernel profiling # GCOV-based kernel profiling
@@ -708,6 +722,7 @@ CONFIG_BASE_SMALL=0
CONFIG_MODULES_TREE_LOOKUP=y CONFIG_MODULES_TREE_LOOKUP=y
CONFIG_BLOCK=y CONFIG_BLOCK=y
CONFIG_BLK_SCSI_REQUEST=y CONFIG_BLK_SCSI_REQUEST=y
CONFIG_BLK_CGROUP_RWSTAT=y
CONFIG_BLK_DEV_BSG=y CONFIG_BLK_DEV_BSG=y
CONFIG_BLK_DEV_BSGLIB=y CONFIG_BLK_DEV_BSGLIB=y
CONFIG_BLK_DEV_INTEGRITY=y CONFIG_BLK_DEV_INTEGRITY=y
@@ -717,6 +732,7 @@ CONFIG_BLK_DEV_THROTTLING=y
CONFIG_BLK_CMDLINE_PARSER=y CONFIG_BLK_CMDLINE_PARSER=y
CONFIG_BLK_WBT=y CONFIG_BLK_WBT=y
# CONFIG_BLK_CGROUP_IOLATENCY is not set # CONFIG_BLK_CGROUP_IOLATENCY is not set
# CONFIG_BLK_CGROUP_IOCOST is not set
CONFIG_BLK_WBT_MQ=y CONFIG_BLK_WBT_MQ=y
CONFIG_BLK_DEBUG_FS=y CONFIG_BLK_DEBUG_FS=y
# CONFIG_BLK_SED_OPAL is not set # CONFIG_BLK_SED_OPAL is not set
@@ -836,10 +852,10 @@ CONFIG_GENERIC_EARLY_IOREMAP=y
CONFIG_ARCH_HAS_PTE_DEVMAP=y CONFIG_ARCH_HAS_PTE_DEVMAP=y
CONFIG_ZONE_DEVICE=y CONFIG_ZONE_DEVICE=y
CONFIG_DEV_PAGEMAP_OPS=y CONFIG_DEV_PAGEMAP_OPS=y
# CONFIG_HMM_MIRROR is not set
# CONFIG_DEVICE_PRIVATE is not set # CONFIG_DEVICE_PRIVATE is not set
CONFIG_PERCPU_STATS=y CONFIG_PERCPU_STATS=y
# CONFIG_GUP_BENCHMARK is not set # CONFIG_GUP_BENCHMARK is not set
# CONFIG_READ_ONLY_THP_FOR_FS is not set
CONFIG_ARCH_HAS_PTE_SPECIAL=y CONFIG_ARCH_HAS_PTE_SPECIAL=y
# end of Memory Management options # end of Memory Management options
@@ -856,6 +872,7 @@ CONFIG_UNIX_SCM=y
CONFIG_UNIX_DIAG=y CONFIG_UNIX_DIAG=y
CONFIG_TLS=y CONFIG_TLS=y
# CONFIG_TLS_DEVICE is not set # CONFIG_TLS_DEVICE is not set
# CONFIG_TLS_TOE is not set
CONFIG_XFRM=y CONFIG_XFRM=y
CONFIG_XFRM_ALGO=y CONFIG_XFRM_ALGO=y
CONFIG_XFRM_USER=y CONFIG_XFRM_USER=y
@@ -979,7 +996,6 @@ CONFIG_PCIEAER=y
# CONFIG_PCIEAER_INJECT is not set # CONFIG_PCIEAER_INJECT is not set
# CONFIG_PCIE_ECRC is not set # CONFIG_PCIE_ECRC is not set
CONFIG_PCIEASPM=y CONFIG_PCIEASPM=y
# CONFIG_PCIEASPM_DEBUG is not set
CONFIG_PCIEASPM_DEFAULT=y CONFIG_PCIEASPM_DEFAULT=y
# CONFIG_PCIEASPM_POWERSAVE is not set # CONFIG_PCIEASPM_POWERSAVE is not set
# CONFIG_PCIEASPM_POWER_SUPERSAVE is not set # CONFIG_PCIEASPM_POWER_SUPERSAVE is not set
@@ -1004,12 +1020,6 @@ CONFIG_PCI_LABEL=y
# #
# PCI controller drivers # PCI controller drivers
# #
#
# Cadence PCIe controllers support
#
# end of Cadence PCIe controllers support
# CONFIG_VMD is not set # CONFIG_VMD is not set
# #
@@ -1018,6 +1028,11 @@ CONFIG_PCI_LABEL=y
# CONFIG_PCIE_DW_PLAT_HOST is not set # CONFIG_PCIE_DW_PLAT_HOST is not set
# CONFIG_PCI_MESON is not set # CONFIG_PCI_MESON is not set
# end of DesignWare PCI Core Support # end of DesignWare PCI Core Support
#
# Cadence PCIe controllers support
#
# end of Cadence PCIe controllers support
# end of PCI controller drivers # end of PCI controller drivers
# #
@@ -1054,6 +1069,7 @@ CONFIG_EXTRA_FIRMWARE=""
CONFIG_FW_LOADER_USER_HELPER=y CONFIG_FW_LOADER_USER_HELPER=y
# CONFIG_FW_LOADER_USER_HELPER_FALLBACK is not set # CONFIG_FW_LOADER_USER_HELPER_FALLBACK is not set
# CONFIG_FW_LOADER_COMPRESS is not set # CONFIG_FW_LOADER_COMPRESS is not set
# CONFIG_FW_CACHE is not set
# end of Firmware loader # end of Firmware loader
CONFIG_ALLOW_DEV_COREDUMP=y CONFIG_ALLOW_DEV_COREDUMP=y
@@ -1122,7 +1138,6 @@ CONFIG_VIRTIO_BLK=y
# CONFIG_DUMMY_IRQ is not set # CONFIG_DUMMY_IRQ is not set
# CONFIG_IBM_ASM is not set # CONFIG_IBM_ASM is not set
# CONFIG_PHANTOM is not set # CONFIG_PHANTOM is not set
# CONFIG_SGI_IOC4 is not set
# CONFIG_TIFM_CORE is not set # CONFIG_TIFM_CORE is not set
# CONFIG_ENCLOSURE_SERVICES is not set # CONFIG_ENCLOSURE_SERVICES is not set
# CONFIG_HP_ILO is not set # CONFIG_HP_ILO is not set
@@ -1156,41 +1171,9 @@ CONFIG_VIRTIO_BLK=y
# #
# Intel MIC & related support # Intel MIC & related support
# #
#
# Intel MIC Bus Driver
#
# CONFIG_INTEL_MIC_BUS is not set # CONFIG_INTEL_MIC_BUS is not set
#
# SCIF Bus Driver
#
# CONFIG_SCIF_BUS is not set # CONFIG_SCIF_BUS is not set
#
# VOP Bus Driver
#
# CONFIG_VOP_BUS is not set # CONFIG_VOP_BUS is not set
#
# Intel MIC Host Driver
#
#
# Intel MIC Card Driver
#
#
# SCIF Driver
#
#
# Intel MIC Coprocessor State Management (COSM) Drivers
#
#
# VOP Driver
#
# end of Intel MIC & related support # end of Intel MIC & related support
# CONFIG_GENWQE is not set # CONFIG_GENWQE is not set
@@ -1244,10 +1227,6 @@ CONFIG_VIRTIO_NET=y
# CONFIG_NLMON is not set # CONFIG_NLMON is not set
# CONFIG_ARCNET is not set # CONFIG_ARCNET is not set
#
# CAIF transport drivers
#
# #
# Distributed Switch Architecture drivers # Distributed Switch Architecture drivers
# #
@@ -1370,11 +1349,11 @@ CONFIG_SERIAL_8250_EXAR=y
CONFIG_SERIAL_8250_NR_UARTS=1 CONFIG_SERIAL_8250_NR_UARTS=1
CONFIG_SERIAL_8250_RUNTIME_UARTS=1 CONFIG_SERIAL_8250_RUNTIME_UARTS=1
# CONFIG_SERIAL_8250_EXTENDED is not set # CONFIG_SERIAL_8250_EXTENDED is not set
CONFIG_SERIAL_8250_DWLIB=y
# CONFIG_SERIAL_8250_DW is not set # CONFIG_SERIAL_8250_DW is not set
# CONFIG_SERIAL_8250_RT288X is not set # CONFIG_SERIAL_8250_RT288X is not set
CONFIG_SERIAL_8250_LPSS=y CONFIG_SERIAL_8250_LPSS=y
CONFIG_SERIAL_8250_MID=y CONFIG_SERIAL_8250_MID=y
# CONFIG_SERIAL_8250_MOXA is not set
# #
# Non-8250 serial port support # Non-8250 serial port support
@@ -1391,10 +1370,12 @@ CONFIG_SERIAL_ARC=y
CONFIG_SERIAL_ARC_NR_PORTS=1 CONFIG_SERIAL_ARC_NR_PORTS=1
# CONFIG_SERIAL_RP2 is not set # CONFIG_SERIAL_RP2 is not set
# CONFIG_SERIAL_FSL_LPUART is not set # CONFIG_SERIAL_FSL_LPUART is not set
# CONFIG_SERIAL_FSL_LINFLEXUART is not set
# end of Serial drivers # end of Serial drivers
CONFIG_SERIAL_DEV_BUS=y CONFIG_SERIAL_DEV_BUS=y
CONFIG_SERIAL_DEV_CTRL_TTYPORT=y CONFIG_SERIAL_DEV_CTRL_TTYPORT=y
# CONFIG_TTY_PRINTK is not set
CONFIG_HVC_DRIVER=y CONFIG_HVC_DRIVER=y
CONFIG_VIRTIO_CONSOLE=y CONFIG_VIRTIO_CONSOLE=y
# CONFIG_IPMI_HANDLER is not set # CONFIG_IPMI_HANDLER is not set
@@ -1418,6 +1399,7 @@ CONFIG_DEVPORT=y
# end of Character devices # end of Character devices
# CONFIG_RANDOM_TRUST_CPU is not set # CONFIG_RANDOM_TRUST_CPU is not set
# CONFIG_RANDOM_TRUST_BOOTLOADER is not set
# #
# I2C support # I2C support
@@ -1452,6 +1434,7 @@ CONFIG_PTP_1588_CLOCK=y
# Enable PHYLIB and NETWORK_PHY_TIMESTAMPING to see the additional clocks. # Enable PHYLIB and NETWORK_PHY_TIMESTAMPING to see the additional clocks.
# #
CONFIG_PTP_1588_CLOCK_KVM=y CONFIG_PTP_1588_CLOCK_KVM=y
# CONFIG_PTP_1588_CLOCK_IDTCM is not set
# end of PTP clock support # end of PTP clock support
# CONFIG_PINCTRL is not set # CONFIG_PINCTRL is not set
@@ -1473,7 +1456,6 @@ CONFIG_THERMAL_GOV_FAIR_SHARE=y
CONFIG_THERMAL_GOV_STEP_WISE=y CONFIG_THERMAL_GOV_STEP_WISE=y
# CONFIG_THERMAL_GOV_BANG_BANG is not set # CONFIG_THERMAL_GOV_BANG_BANG is not set
CONFIG_THERMAL_GOV_USER_SPACE=y CONFIG_THERMAL_GOV_USER_SPACE=y
# CONFIG_THERMAL_GOV_POWER_ALLOCATOR is not set
# CONFIG_THERMAL_EMULATION is not set # CONFIG_THERMAL_EMULATION is not set
# #
@@ -1500,7 +1482,6 @@ CONFIG_BCMA_POSSIBLE=y
# #
# Multifunction device drivers # Multifunction device drivers
# #
# CONFIG_MFD_CROS_EC is not set
# CONFIG_MFD_MADERA is not set # CONFIG_MFD_MADERA is not set
# CONFIG_HTC_PASIC3 is not set # CONFIG_HTC_PASIC3 is not set
# CONFIG_MFD_INTEL_QUARK_I2C_GPIO is not set # CONFIG_MFD_INTEL_QUARK_I2C_GPIO is not set
@@ -1609,7 +1590,7 @@ CONFIG_LCD_CLASS_DEVICE=y
CONFIG_BACKLIGHT_CLASS_DEVICE=y CONFIG_BACKLIGHT_CLASS_DEVICE=y
CONFIG_BACKLIGHT_GENERIC=y CONFIG_BACKLIGHT_GENERIC=y
# CONFIG_BACKLIGHT_APPLE is not set # CONFIG_BACKLIGHT_APPLE is not set
# CONFIG_BACKLIGHT_PM8941_WLED is not set # CONFIG_BACKLIGHT_QCOM_WLED is not set
# CONFIG_BACKLIGHT_SAHARA is not set # CONFIG_BACKLIGHT_SAHARA is not set
# end of Backlight & LCD device support # end of Backlight & LCD device support
@@ -1673,7 +1654,6 @@ CONFIG_HID_GENERIC=y
# CONFIG_HID_KENSINGTON is not set # CONFIG_HID_KENSINGTON is not set
# CONFIG_HID_LCPOWER is not set # CONFIG_HID_LCPOWER is not set
# CONFIG_HID_LENOVO is not set # CONFIG_HID_LENOVO is not set
# CONFIG_HID_LOGITECH is not set
# CONFIG_HID_MAGICMOUSE is not set # CONFIG_HID_MAGICMOUSE is not set
# CONFIG_HID_MALTRON is not set # CONFIG_HID_MALTRON is not set
# CONFIG_HID_MAYFLASH is not set # CONFIG_HID_MAYFLASH is not set
@@ -1717,7 +1697,6 @@ CONFIG_HID_REDRAGON=y
CONFIG_USB_OHCI_LITTLE_ENDIAN=y CONFIG_USB_OHCI_LITTLE_ENDIAN=y
# CONFIG_USB_SUPPORT is not set # CONFIG_USB_SUPPORT is not set
# CONFIG_UWB is not set
# CONFIG_MMC is not set # CONFIG_MMC is not set
# CONFIG_MEMSTICK is not set # CONFIG_MEMSTICK is not set
# CONFIG_NEW_LEDS is not set # CONFIG_NEW_LEDS is not set
@@ -1749,6 +1728,7 @@ CONFIG_DW_DMAC_CORE=y
# CONFIG_DW_EDMA is not set # CONFIG_DW_EDMA is not set
# CONFIG_DW_EDMA_PCIE is not set # CONFIG_DW_EDMA_PCIE is not set
CONFIG_HSU_DMA=y CONFIG_HSU_DMA=y
# CONFIG_SF_PDMA is not set
# #
# DMA Clients # DMA Clients
@@ -1801,9 +1781,11 @@ CONFIG_VIRTIO_MMIO_CMDLINE_DEVICES=y
# CONFIG_HYPERV is not set # CONFIG_HYPERV is not set
# end of Microsoft Hyper-V guest support # end of Microsoft Hyper-V guest support
# CONFIG_GREYBUS is not set
# CONFIG_STAGING is not set # CONFIG_STAGING is not set
# CONFIG_X86_PLATFORM_DEVICES is not set # CONFIG_X86_PLATFORM_DEVICES is not set
CONFIG_PMC_ATOM=y CONFIG_PMC_ATOM=y
# CONFIG_MFD_CROS_EC is not set
# CONFIG_CHROME_PLATFORMS is not set # CONFIG_CHROME_PLATFORMS is not set
# CONFIG_MELLANOX_PLATFORM is not set # CONFIG_MELLANOX_PLATFORM is not set
CONFIG_CLKDEV_LOOKUP=y CONFIG_CLKDEV_LOOKUP=y
@@ -1844,6 +1826,7 @@ CONFIG_IOMMU_DMA=y
# CONFIG_INTEL_IOMMU is not set # CONFIG_INTEL_IOMMU is not set
# CONFIG_IRQ_REMAP is not set # CONFIG_IRQ_REMAP is not set
CONFIG_VIRTIO_IOMMU=y CONFIG_VIRTIO_IOMMU=y
CONFIG_VIRTIO_IOMMU_TOPOLOGY=y
# #
# Remoteproc drivers # Remoteproc drivers
@@ -1889,13 +1872,6 @@ CONFIG_VIRTIO_IOMMU=y
# #
# end of i.MX SoC drivers # end of i.MX SoC drivers
#
# IXP4xx SoC drivers
#
# CONFIG_IXP4XX_QMGR is not set
# CONFIG_IXP4XX_NPE is not set
# end of IXP4xx SoC drivers
# #
# Qualcomm SoC drivers # Qualcomm SoC drivers
# #
@@ -2013,6 +1989,7 @@ CONFIG_EXPORTFS=y
CONFIG_FILE_LOCKING=y CONFIG_FILE_LOCKING=y
CONFIG_MANDATORY_FILE_LOCKING=y CONFIG_MANDATORY_FILE_LOCKING=y
CONFIG_FS_ENCRYPTION=y CONFIG_FS_ENCRYPTION=y
# CONFIG_FS_VERITY is not set
CONFIG_FSNOTIFY=y CONFIG_FSNOTIFY=y
CONFIG_DNOTIFY=y CONFIG_DNOTIFY=y
CONFIG_INOTIFY_USER=y CONFIG_INOTIFY_USER=y
@@ -2139,6 +2116,7 @@ CONFIG_NLS_MAC_TURKISH=y
CONFIG_NLS_UTF8=y CONFIG_NLS_UTF8=y
# CONFIG_DLM is not set # CONFIG_DLM is not set
# CONFIG_UNICODE is not set # CONFIG_UNICODE is not set
CONFIG_IO_WQ=y
# end of File systems # end of File systems
# #
@@ -2185,8 +2163,8 @@ CONFIG_CRYPTO_ALGAPI=y
CONFIG_CRYPTO_ALGAPI2=y CONFIG_CRYPTO_ALGAPI2=y
CONFIG_CRYPTO_AEAD=y CONFIG_CRYPTO_AEAD=y
CONFIG_CRYPTO_AEAD2=y CONFIG_CRYPTO_AEAD2=y
CONFIG_CRYPTO_BLKCIPHER=y CONFIG_CRYPTO_SKCIPHER=y
CONFIG_CRYPTO_BLKCIPHER2=y CONFIG_CRYPTO_SKCIPHER2=y
CONFIG_CRYPTO_HASH=y CONFIG_CRYPTO_HASH=y
CONFIG_CRYPTO_HASH2=y CONFIG_CRYPTO_HASH2=y
CONFIG_CRYPTO_RNG=y CONFIG_CRYPTO_RNG=y
@@ -2216,6 +2194,8 @@ CONFIG_CRYPTO_GLUE_HELPER_X86=y
# CONFIG_CRYPTO_DH is not set # CONFIG_CRYPTO_DH is not set
# CONFIG_CRYPTO_ECDH is not set # CONFIG_CRYPTO_ECDH is not set
# CONFIG_CRYPTO_ECRDSA is not set # CONFIG_CRYPTO_ECRDSA is not set
# CONFIG_CRYPTO_CURVE25519 is not set
# CONFIG_CRYPTO_CURVE25519_X86 is not set
# #
# Authenticated Encryption with Associated Data # Authenticated Encryption with Associated Data
@@ -2224,16 +2204,7 @@ CONFIG_CRYPTO_GLUE_HELPER_X86=y
CONFIG_CRYPTO_GCM=y CONFIG_CRYPTO_GCM=y
# CONFIG_CRYPTO_CHACHA20POLY1305 is not set # CONFIG_CRYPTO_CHACHA20POLY1305 is not set
# CONFIG_CRYPTO_AEGIS128 is not set # CONFIG_CRYPTO_AEGIS128 is not set
# CONFIG_CRYPTO_AEGIS128L is not set
# CONFIG_CRYPTO_AEGIS256 is not set
# CONFIG_CRYPTO_AEGIS128_AESNI_SSE2 is not set # CONFIG_CRYPTO_AEGIS128_AESNI_SSE2 is not set
# CONFIG_CRYPTO_AEGIS128L_AESNI_SSE2 is not set
# CONFIG_CRYPTO_AEGIS256_AESNI_SSE2 is not set
# CONFIG_CRYPTO_MORUS640 is not set
# CONFIG_CRYPTO_MORUS640_SSE2 is not set
# CONFIG_CRYPTO_MORUS1280 is not set
# CONFIG_CRYPTO_MORUS1280_SSE2 is not set
# CONFIG_CRYPTO_MORUS1280_AVX2 is not set
CONFIG_CRYPTO_SEQIV=y CONFIG_CRYPTO_SEQIV=y
CONFIG_CRYPTO_ECHAINIV=y CONFIG_CRYPTO_ECHAINIV=y
@@ -2253,6 +2224,7 @@ CONFIG_CRYPTO_XTS=y
# CONFIG_CRYPTO_NHPOLY1305_SSE2 is not set # CONFIG_CRYPTO_NHPOLY1305_SSE2 is not set
# CONFIG_CRYPTO_NHPOLY1305_AVX2 is not set # CONFIG_CRYPTO_NHPOLY1305_AVX2 is not set
# CONFIG_CRYPTO_ADIANTUM is not set # CONFIG_CRYPTO_ADIANTUM is not set
# CONFIG_CRYPTO_ESSIV is not set
# #
# Hash modes # Hash modes
@@ -2270,6 +2242,9 @@ CONFIG_CRYPTO_CRC32C_INTEL=y
# CONFIG_CRYPTO_CRC32 is not set # CONFIG_CRYPTO_CRC32 is not set
# CONFIG_CRYPTO_CRC32_PCLMUL is not set # CONFIG_CRYPTO_CRC32_PCLMUL is not set
# CONFIG_CRYPTO_XXHASH is not set # CONFIG_CRYPTO_XXHASH is not set
# CONFIG_CRYPTO_BLAKE2B is not set
# CONFIG_CRYPTO_BLAKE2S is not set
# CONFIG_CRYPTO_BLAKE2S_X86 is not set
CONFIG_CRYPTO_CRCT10DIF=y CONFIG_CRYPTO_CRCT10DIF=y
# CONFIG_CRYPTO_CRCT10DIF_PCLMUL is not set # CONFIG_CRYPTO_CRCT10DIF_PCLMUL is not set
CONFIG_CRYPTO_GHASH=y CONFIG_CRYPTO_GHASH=y
@@ -2300,10 +2275,8 @@ CONFIG_CRYPTO_SHA512=y
# #
CONFIG_CRYPTO_AES=y CONFIG_CRYPTO_AES=y
# CONFIG_CRYPTO_AES_TI is not set # CONFIG_CRYPTO_AES_TI is not set
CONFIG_CRYPTO_AES_X86_64=y
CONFIG_CRYPTO_AES_NI_INTEL=y CONFIG_CRYPTO_AES_NI_INTEL=y
# CONFIG_CRYPTO_ANUBIS is not set # CONFIG_CRYPTO_ANUBIS is not set
CONFIG_CRYPTO_LIB_ARC4=y
CONFIG_CRYPTO_ARC4=y CONFIG_CRYPTO_ARC4=y
# CONFIG_CRYPTO_BLOWFISH is not set # CONFIG_CRYPTO_BLOWFISH is not set
# CONFIG_CRYPTO_BLOWFISH_X86_64 is not set # CONFIG_CRYPTO_BLOWFISH_X86_64 is not set
@@ -2360,6 +2333,21 @@ CONFIG_CRYPTO_USER_API=y
# CONFIG_CRYPTO_USER_API_SKCIPHER is not set # CONFIG_CRYPTO_USER_API_SKCIPHER is not set
CONFIG_CRYPTO_USER_API_RNG=y CONFIG_CRYPTO_USER_API_RNG=y
# CONFIG_CRYPTO_USER_API_AEAD is not set # CONFIG_CRYPTO_USER_API_AEAD is not set
#
# Crypto library routines
#
CONFIG_CRYPTO_LIB_AES=y
CONFIG_CRYPTO_LIB_ARC4=y
# CONFIG_CRYPTO_LIB_BLAKE2S is not set
# CONFIG_CRYPTO_LIB_CHACHA is not set
# CONFIG_CRYPTO_LIB_CURVE25519 is not set
CONFIG_CRYPTO_LIB_DES=y
CONFIG_CRYPTO_LIB_POLY1305_RSIZE=4
CONFIG_CRYPTO_LIB_POLY1305_GENERIC=y
# CONFIG_CRYPTO_LIB_POLY1305 is not set
# CONFIG_CRYPTO_LIB_CHACHA20POLY1305 is not set
CONFIG_CRYPTO_LIB_SHA256=y
# CONFIG_CRYPTO_HW is not set # CONFIG_CRYPTO_HW is not set
# CONFIG_ASYMMETRIC_KEY_TYPE is not set # CONFIG_ASYMMETRIC_KEY_TYPE is not set
@@ -2424,7 +2412,6 @@ CONFIG_CPU_RMAP=y
CONFIG_DQL=y CONFIG_DQL=y
CONFIG_NLATTR=y CONFIG_NLATTR=y
CONFIG_IRQ_POLL=y CONFIG_IRQ_POLL=y
# CONFIG_DIMLIB is not set
CONFIG_UCS2_STRING=y CONFIG_UCS2_STRING=y
CONFIG_HAVE_GENERIC_VDSO=y CONFIG_HAVE_GENERIC_VDSO=y
CONFIG_GENERIC_GETTIMEOFDAY=y CONFIG_GENERIC_GETTIMEOFDAY=y
@@ -2433,6 +2420,7 @@ CONFIG_FONT_SUPPORT=y
CONFIG_FONT_8x8=y CONFIG_FONT_8x8=y
CONFIG_FONT_8x16=y CONFIG_FONT_8x16=y
CONFIG_ARCH_HAS_PMEM_API=y CONFIG_ARCH_HAS_PMEM_API=y
CONFIG_MEMREGION=y
CONFIG_ARCH_HAS_UACCESS_FLUSHCACHE=y CONFIG_ARCH_HAS_UACCESS_FLUSHCACHE=y
CONFIG_ARCH_STACKWALK=y CONFIG_ARCH_STACKWALK=y
CONFIG_SBITMAP=y CONFIG_SBITMAP=y
@@ -2453,6 +2441,8 @@ CONFIG_CONSOLE_LOGLEVEL_QUIET=4
CONFIG_MESSAGE_LOGLEVEL_DEFAULT=4 CONFIG_MESSAGE_LOGLEVEL_DEFAULT=4
# CONFIG_BOOT_PRINTK_DELAY is not set # CONFIG_BOOT_PRINTK_DELAY is not set
CONFIG_DYNAMIC_DEBUG=y CONFIG_DYNAMIC_DEBUG=y
CONFIG_SYMBOLIC_ERRNAME=y
# CONFIG_DEBUG_BUGVERBOSE is not set
# end of printk and dmesg options # end of printk and dmesg options
# #
@@ -2463,8 +2453,6 @@ CONFIG_DYNAMIC_DEBUG=y
CONFIG_FRAME_WARN=2048 CONFIG_FRAME_WARN=2048
CONFIG_STRIP_ASM_SYMS=y CONFIG_STRIP_ASM_SYMS=y
# CONFIG_READABLE_ASM is not set # CONFIG_READABLE_ASM is not set
CONFIG_UNUSED_SYMBOLS=y
CONFIG_DEBUG_FS=y
# CONFIG_HEADERS_INSTALL is not set # CONFIG_HEADERS_INSTALL is not set
CONFIG_OPTIMIZE_INLINING=y CONFIG_OPTIMIZE_INLINING=y
CONFIG_DEBUG_SECTION_MISMATCH=y CONFIG_DEBUG_SECTION_MISMATCH=y
@@ -2474,9 +2462,20 @@ CONFIG_STACK_VALIDATION=y
# CONFIG_DEBUG_FORCE_WEAK_PER_CPU is not set # CONFIG_DEBUG_FORCE_WEAK_PER_CPU is not set
# end of Compile-time checks and compiler options # end of Compile-time checks and compiler options
#
# Generic Kernel Debugging Instruments
#
CONFIG_MAGIC_SYSRQ=y CONFIG_MAGIC_SYSRQ=y
CONFIG_MAGIC_SYSRQ_DEFAULT_ENABLE=0x1 CONFIG_MAGIC_SYSRQ_DEFAULT_ENABLE=0x1
CONFIG_MAGIC_SYSRQ_SERIAL=y CONFIG_MAGIC_SYSRQ_SERIAL=y
CONFIG_DEBUG_FS=y
CONFIG_HAVE_ARCH_KGDB=y
# CONFIG_KGDB is not set
CONFIG_ARCH_HAS_UBSAN_SANITIZE_ALL=y
# CONFIG_UBSAN is not set
CONFIG_UBSAN_ALIGNMENT=y
# end of Generic Kernel Debugging Instruments
CONFIG_DEBUG_KERNEL=y CONFIG_DEBUG_KERNEL=y
CONFIG_DEBUG_MISC=y CONFIG_DEBUG_MISC=y
@@ -2494,39 +2493,42 @@ CONFIG_DEBUG_MISC=y
CONFIG_HAVE_DEBUG_KMEMLEAK=y CONFIG_HAVE_DEBUG_KMEMLEAK=y
# CONFIG_DEBUG_KMEMLEAK is not set # CONFIG_DEBUG_KMEMLEAK is not set
# CONFIG_DEBUG_STACK_USAGE is not set # CONFIG_DEBUG_STACK_USAGE is not set
# CONFIG_SCHED_STACK_END_CHECK is not set
# CONFIG_DEBUG_VM is not set # CONFIG_DEBUG_VM is not set
CONFIG_ARCH_HAS_DEBUG_VIRTUAL=y CONFIG_ARCH_HAS_DEBUG_VIRTUAL=y
# CONFIG_DEBUG_VIRTUAL is not set # CONFIG_DEBUG_VIRTUAL is not set
CONFIG_DEBUG_MEMORY_INIT=y CONFIG_DEBUG_MEMORY_INIT=y
# CONFIG_DEBUG_PER_CPU_MAPS is not set # CONFIG_DEBUG_PER_CPU_MAPS is not set
CONFIG_HAVE_ARCH_KASAN=y CONFIG_HAVE_ARCH_KASAN=y
CONFIG_HAVE_ARCH_KASAN_VMALLOC=y
CONFIG_CC_HAS_KASAN_GENERIC=y CONFIG_CC_HAS_KASAN_GENERIC=y
# CONFIG_KASAN is not set # CONFIG_KASAN is not set
CONFIG_KASAN_STACK=1 CONFIG_KASAN_STACK=1
# end of Memory Debugging # end of Memory Debugging
CONFIG_ARCH_HAS_KCOV=y
CONFIG_CC_HAS_SANCOV_TRACE_PC=y
# CONFIG_KCOV is not set
# CONFIG_DEBUG_SHIRQ is not set # CONFIG_DEBUG_SHIRQ is not set
# #
# Debug Lockups and Hangs # Debug Oops, Lockups and Hangs
# #
# CONFIG_PANIC_ON_OOPS is not set
CONFIG_PANIC_ON_OOPS_VALUE=0
CONFIG_PANIC_TIMEOUT=0
# CONFIG_SOFTLOCKUP_DETECTOR is not set # CONFIG_SOFTLOCKUP_DETECTOR is not set
CONFIG_HARDLOCKUP_CHECK_TIMESTAMP=y CONFIG_HARDLOCKUP_CHECK_TIMESTAMP=y
# CONFIG_HARDLOCKUP_DETECTOR is not set # CONFIG_HARDLOCKUP_DETECTOR is not set
# CONFIG_DETECT_HUNG_TASK is not set # CONFIG_DETECT_HUNG_TASK is not set
# CONFIG_WQ_WATCHDOG is not set # CONFIG_WQ_WATCHDOG is not set
# end of Debug Lockups and Hangs # end of Debug Oops, Lockups and Hangs
# CONFIG_PANIC_ON_OOPS is not set #
CONFIG_PANIC_ON_OOPS_VALUE=0 # Scheduler Debugging
CONFIG_PANIC_TIMEOUT=0 #
# CONFIG_SCHED_DEBUG is not set # CONFIG_SCHED_DEBUG is not set
CONFIG_SCHED_INFO=y CONFIG_SCHED_INFO=y
# CONFIG_SCHEDSTATS is not set # CONFIG_SCHEDSTATS is not set
# CONFIG_SCHED_STACK_END_CHECK is not set # end of Scheduler Debugging
# CONFIG_DEBUG_TIMEKEEPING is not set # CONFIG_DEBUG_TIMEKEEPING is not set
# #
@@ -2550,11 +2552,17 @@ CONFIG_LOCK_DEBUGGING_SUPPORT=y
CONFIG_STACKTRACE=y CONFIG_STACKTRACE=y
# CONFIG_WARN_ALL_UNSEEDED_RANDOM is not set # CONFIG_WARN_ALL_UNSEEDED_RANDOM is not set
# CONFIG_DEBUG_KOBJECT is not set # CONFIG_DEBUG_KOBJECT is not set
CONFIG_DEBUG_BUGVERBOSE=y
#
# Debug kernel data structures
#
CONFIG_DEBUG_LIST=y CONFIG_DEBUG_LIST=y
# CONFIG_DEBUG_PLIST is not set # CONFIG_DEBUG_PLIST is not set
# CONFIG_DEBUG_SG is not set # CONFIG_DEBUG_SG is not set
# CONFIG_DEBUG_NOTIFIERS is not set # CONFIG_DEBUG_NOTIFIERS is not set
CONFIG_BUG_ON_DATA_CORRUPTION=y
# end of Debug kernel data structures
# CONFIG_DEBUG_CREDENTIALS is not set # CONFIG_DEBUG_CREDENTIALS is not set
# #
@@ -2570,14 +2578,13 @@ CONFIG_RCU_CPU_STALL_TIMEOUT=59
# CONFIG_DEBUG_WQ_FORCE_RR_CPU is not set # CONFIG_DEBUG_WQ_FORCE_RR_CPU is not set
# CONFIG_DEBUG_BLOCK_EXT_DEVT is not set # CONFIG_DEBUG_BLOCK_EXT_DEVT is not set
# CONFIG_CPU_HOTPLUG_STATE_CONTROL is not set # CONFIG_CPU_HOTPLUG_STATE_CONTROL is not set
# CONFIG_NOTIFIER_ERROR_INJECTION is not set
# CONFIG_FAULT_INJECTION is not set
# CONFIG_LATENCYTOP is not set # CONFIG_LATENCYTOP is not set
CONFIG_USER_STACKTRACE_SUPPORT=y CONFIG_USER_STACKTRACE_SUPPORT=y
CONFIG_HAVE_FUNCTION_TRACER=y CONFIG_HAVE_FUNCTION_TRACER=y
CONFIG_HAVE_FUNCTION_GRAPH_TRACER=y CONFIG_HAVE_FUNCTION_GRAPH_TRACER=y
CONFIG_HAVE_DYNAMIC_FTRACE=y CONFIG_HAVE_DYNAMIC_FTRACE=y
CONFIG_HAVE_DYNAMIC_FTRACE_WITH_REGS=y CONFIG_HAVE_DYNAMIC_FTRACE_WITH_REGS=y
CONFIG_HAVE_DYNAMIC_FTRACE_WITH_DIRECT_CALLS=y
CONFIG_HAVE_FTRACE_MCOUNT_RECORD=y CONFIG_HAVE_FTRACE_MCOUNT_RECORD=y
CONFIG_HAVE_SYSCALL_TRACEPOINTS=y CONFIG_HAVE_SYSCALL_TRACEPOINTS=y
CONFIG_HAVE_FENTRY=y CONFIG_HAVE_FENTRY=y
@@ -2585,6 +2592,17 @@ CONFIG_HAVE_C_RECORDMCOUNT=y
CONFIG_TRACING_SUPPORT=y CONFIG_TRACING_SUPPORT=y
# CONFIG_FTRACE is not set # CONFIG_FTRACE is not set
# CONFIG_PROVIDE_OHCI1394_DMA_INIT is not set # CONFIG_PROVIDE_OHCI1394_DMA_INIT is not set
# CONFIG_KUNIT is not set
# CONFIG_NOTIFIER_ERROR_INJECTION is not set
# CONFIG_FAULT_INJECTION is not set
# end of Kernel hacking
#
# Kernel Testing and Coverage
#
CONFIG_ARCH_HAS_KCOV=y
CONFIG_CC_HAS_SANCOV_TRACE_PC=y
# CONFIG_KCOV is not set
CONFIG_RUNTIME_TESTING_MENU=y CONFIG_RUNTIME_TESTING_MENU=y
# CONFIG_LKDTM is not set # CONFIG_LKDTM is not set
# CONFIG_TEST_LIST_SORT is not set # CONFIG_TEST_LIST_SORT is not set
@@ -2615,16 +2633,14 @@ CONFIG_RUNTIME_TESTING_MENU=y
# CONFIG_TEST_STACKINIT is not set # CONFIG_TEST_STACKINIT is not set
# CONFIG_TEST_MEMINIT is not set # CONFIG_TEST_MEMINIT is not set
# CONFIG_MEMTEST is not set # CONFIG_MEMTEST is not set
CONFIG_BUG_ON_DATA_CORRUPTION=y
# CONFIG_SAMPLES is not set # CONFIG_SAMPLES is not set
CONFIG_HAVE_ARCH_KGDB=y
# CONFIG_KGDB is not set
CONFIG_ARCH_HAS_UBSAN_SANITIZE_ALL=y
# CONFIG_UBSAN is not set
CONFIG_UBSAN_ALIGNMENT=y
CONFIG_ARCH_HAS_DEVMEM_IS_ALLOWED=y CONFIG_ARCH_HAS_DEVMEM_IS_ALLOWED=y
CONFIG_STRICT_DEVMEM=y CONFIG_STRICT_DEVMEM=y
# CONFIG_IO_STRICT_DEVMEM is not set # CONFIG_IO_STRICT_DEVMEM is not set
#
# x86 Debugging
#
CONFIG_TRACE_IRQFLAGS_SUPPORT=y CONFIG_TRACE_IRQFLAGS_SUPPORT=y
CONFIG_X86_VERBOSE_BOOTUP=y CONFIG_X86_VERBOSE_BOOTUP=y
CONFIG_EARLY_PRINTK=y CONFIG_EARLY_PRINTK=y
@@ -2636,6 +2652,7 @@ CONFIG_EARLY_PRINTK=y
CONFIG_DOUBLEFAULT=y CONFIG_DOUBLEFAULT=y
# CONFIG_DEBUG_TLBFLUSH is not set # CONFIG_DEBUG_TLBFLUSH is not set
CONFIG_HAVE_MMIOTRACE_SUPPORT=y CONFIG_HAVE_MMIOTRACE_SUPPORT=y
# CONFIG_X86_DECODER_SELFTEST is not set
CONFIG_IO_DELAY_0X80=y CONFIG_IO_DELAY_0X80=y
# CONFIG_IO_DELAY_0XED is not set # CONFIG_IO_DELAY_0XED is not set
# CONFIG_IO_DELAY_UDELAY is not set # CONFIG_IO_DELAY_UDELAY is not set
@@ -2648,4 +2665,6 @@ CONFIG_IO_DELAY_0X80=y
# CONFIG_PUNIT_ATOM_DEBUG is not set # CONFIG_PUNIT_ATOM_DEBUG is not set
# CONFIG_UNWINDER_ORC is not set # CONFIG_UNWINDER_ORC is not set
CONFIG_UNWINDER_FRAME_POINTER=y CONFIG_UNWINDER_FRAME_POINTER=y
# end of Kernel hacking # CONFIG_UNWINDER_GUESS is not set
# end of x86 Debugging
# end of Kernel Testing and Coverage

314
scripts/dev_cli.sh Executable file
View File

@@ -0,0 +1,314 @@
#!/bin/bash
# Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
# Copyright © 2020 Intel Corporation
# SPDX-License-Identifier: Apache-2.0
CLI_NAME="Cloud Hypervisor"
CTR_IMAGE_TAG="cloudhypervisor/dev"
CTR_IMAGE_VERSION="v1"
CTR_IMAGE="${CTR_IMAGE_TAG}:${CTR_IMAGE_VERSION}"
DOCKER_RUNTIME="docker"
# Host paths
CLH_SCRIPTS_DIR=$(cd "$(dirname "$0")" && pwd)
CLH_ROOT_DIR=$(cd "${CLH_SCRIPTS_DIR}/.." && pwd)
CLH_BUILD_DIR="${CLH_ROOT_DIR}/build"
CLH_CARGO_TARGET="${CLH_BUILD_DIR}/cargo_target"
CLH_DOCKERFILE="${CLH_SCRIPTS_DIR}/../resources/Dockerfile"
CLH_CTR_BUILD_DIR="/tmp/cloud-hypervisor/ctr-build"
CLH_INTEGRATION_WORKLOADS="${HOME}/workloads"
# Container paths
CTR_CLH_ROOT_DIR="/cloud-hypervisor"
CTR_CLH_CARGO_TARGET="${CTR_CLH_ROOT_DIR}/build/cargo_target"
CTR_CLH_INTEGRATION_WORKLOADS="/root/workloads"
# Cargo paths
# Full path to the cargo registry dir on the host. This appears on the host
# because we want to persist the cargo registry across container invocations.
# Otherwise, any rust crates from crates.io would be downloaded again each time
# we build or test.
CARGO_REGISTRY_DIR="${CLH_BUILD_DIR}/cargo_registry"
# Full path to the cargo git registry on the host. This serves the same purpose
# as CARGO_REGISTRY_DIR, for crates downloaded from GitHub repos instead of
# crates.io.
CARGO_GIT_REGISTRY_DIR="${CLH_BUILD_DIR}/cargo_git_registry"
# Full path to the cargo target dir on the host.
CARGO_TARGET_DIR="${CLH_BUILD_DIR}/cargo_target"
# Send a decorated message to stdout, followed by a new line
#
say() {
[ -t 1 ] && [ -n "$TERM" ] \
&& echo "$(tput setaf 2)[$CLI_NAME]$(tput sgr0) $*" \
|| echo "[$CLI_NAME] $*"
}
# Send a decorated message to stdout, without a trailing new line
#
say_noln() {
[ -t 1 ] && [ -n "$TERM" ] \
&& echo -n "$(tput setaf 2)[$CLI_NAME]$(tput sgr0) $*" \
|| echo "[$CLI_NAME] $*"
}
# Send a text message to stderr
#
say_err() {
[ -t 2 ] && [ -n "$TERM" ] \
&& echo "$(tput setaf 1)[$CLI_NAME] $*$(tput sgr0)" 1>&2 \
|| echo "[$CLI_NAME] $*" 1>&2
}
# Send a warning-highlighted text to stdout
say_warn() {
[ -t 1 ] && [ -n "$TERM" ] \
&& echo "$(tput setaf 3)[$CLI_NAME] $*$(tput sgr0)" \
|| echo "[$CLI_NAME] $*"
}
# Exit with an error message and (optional) code
# Usage: die [-c <error code>] <error message>
#
die() {
code=1
[[ "$1" = "-c" ]] && {
code="$2"
shift 2
}
say_err "$@"
exit $code
}
# Exit with an error message if the last exit code is not 0
#
ok_or_die() {
code=$?
[[ $code -eq 0 ]] || die -c $code "$@"
}
# Make sure the build/ dirs are available. Exit if we can't create them.
# Upon returning from this call, the caller can be certain the build/ dirs exist.
#
ensure_build_dir() {
for dir in "$CLH_BUILD_DIR" \
"$CLH_INTEGRATION_WORKLOADS" \
"$CLH_CTR_BUILD_DIR" \
"$CARGO_TARGET_DIR" \
"$CARGO_REGISTRY_DIR" \
"$CARGO_GIT_REGISTRY_DIR"; do
mkdir -p "$dir" || die "Error: cannot create dir $dir"
[ -x "$dir" ] && [ -w "$dir" ] || \
{
say "Wrong permissions for $dir. Attempting to fix them ..."
chmod +x+w "$dir"
} || \
die "Error: wrong permissions for $dir. Should be +x+w"
done
}
cmd_help() {
echo ""
echo "Cloud Hypervisor $(basename $0)"
echo "Usage: $(basename $0) <command> [<command args>]"
echo ""
echo "Available commands:"
echo ""
echo " build [--debug|--release] [-- [<cargo args>]]"
echo " Build the Cloud Hypervisor binaries."
echo " --debug Build the debug binaries. This is the default."
echo " --release Build the release binaries."
echo ""
echo " tests [--unit|--cargo|--all] [-- [<cargo test args>]]"
echo " Run the Cloud Hypervisor tests."
echo " --unit Run the unit tests."
echo " --cargo Run the cargo tests."
echo " --integration Run the integration tests."
echo " --all Run all tests."
echo ""
echo " build-container [--type]"
echo " Build the Cloud Hypervisor container."
echo " --dev Build dev container. This is the default."
echo ""
echo " clean [<cargo args>]]"
echo " Remove the Cloud Hypervisor artifacts."
echo ""
echo " help"
echo " Display this help message."
echo ""
}
cmd_build() {
build="debug"
while [ $# -gt 0 ]; do
case "$1" in
"-h"|"--help") { cmd_help; exit 1; } ;;
"--debug") { build="debug"; } ;;
"--release") { build="release"; } ;;
"--") { shift; break; } ;;
*)
die "Unknown build argument: $1. Please use --help for help."
;;
esac
shift
done
cargo_args=("$@")
[ $build = "release" ] && cargo_args+=("--release")
$DOCKER_RUNTIME run \
-ti \
--workdir "$CTR_CLH_ROOT_DIR" \
--rm \
--volume /dev:/dev \
--volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" \
"$CTR_IMAGE" \
cargo build \
--target-dir "$CTR_CLH_CARGO_TARGET" \
"${cargo_args[@]}"
ret=$?
# If `cargo build` was successful, let's copy the binaries to a more
# accessible location.
[ $ret -eq 0 ] && {
cargo_bin_dir="$CLH_CARGO_TARGET/$build"
say "Binaries placed under $cargo_bin_dir"
}
}
cmd_clean() {
cargo_args=("$@")
$DOCKER_RUNTIME run \
-ti \
--workdir "$CTR_CLH_ROOT_DIR" \
--rm \
--volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" \
"$CTR_IMAGE" \
cargo clean \
--target-dir "$CTR_CLH_CARGO_TARGET" \
"${cargo_args[@]}"
}
cmd_tests() {
unit=false
cargo=false
integration=false
while [ $# -gt 0 ]; do
case "$1" in
"-h"|"--help") { cmd_help; exit 1; } ;;
"--unit") { unit=true; } ;;
"--cargo") { cargo=true; } ;;
"--integration") { integration=true; } ;;
"--all") { cargo=true; unit=true; integration=true; } ;;
"--") { shift; break; } ;;
*)
die "Unknown tests argument: $1. Please use --help for help."
;;
esac
shift
done
if [ "$unit" = true ] ; then
say "Running unit tests..."
$DOCKER_RUNTIME run \
-ti \
--workdir "$CTR_CLH_ROOT_DIR" \
--rm \
--device /dev/kvm \
--device /dev/net/tun \
--cap-add net_admin \
--volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" \
"$CTR_IMAGE" \
./scripts/run_unit_tests.sh "$@"
fi
if [ "$cargo" = true ] ; then
say "Running cargo tests..."
$DOCKER_RUNTIME run \
-ti \
--workdir "$CTR_CLH_ROOT_DIR" \
--rm \
--volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" \
"$CTR_IMAGE" \
./scripts/run_cargo_tests.sh
fi
if [ "$integration" = true ] ; then
say "Running integration tests..."
$DOCKER_RUNTIME run \
-ti \
--workdir "$CTR_CLH_ROOT_DIR" \
--rm \
--privileged \
--volume /dev:/dev \
--volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" \
--volume "$CLH_INTEGRATION_WORKLOADS:$CTR_CLH_INTEGRATION_WORKLOADS" \
"$CTR_IMAGE" \
./scripts/run_integration_tests.sh "$@"
fi
}
cmd_build-container() {
container_type="dev"
while [ $# -gt 0 ]; do
case "$1" in
"-h"|"--help") { cmd_help; exit 1; } ;;
"--dev") { container_type="dev"; } ;;
"--") { shift; break; } ;;
*)
die "Unknown build-container argument: $1. Please use --help for help."
;;
esac
shift
done
BUILD_DIR=/tmp/cloud-hypervisor/container/
mkdir -p $BUILD_DIR
cp $CLH_DOCKERFILE $BUILD_DIR
$DOCKER_RUNTIME build \
--target $container_type \
-t $CTR_IMAGE \
-f $BUILD_DIR/Dockerfile \
$BUILD_DIR
}
# Parse main command line args.
#
while [ $# -gt 0 ]; do
case "$1" in
-h|--help) { cmd_help; exit 1; } ;;
-y|--unattended) { OPT_UNATTENDED=true; } ;;
-*)
die "Unknown arg: $1. Please use \`$0 help\` for help."
;;
*)
break
;;
esac
shift
done
# $1 is now a command name. Check if it is a valid command and, if so,
# run it.
#
declare -f "cmd_$1" > /dev/null
ok_or_die "Unknown command: $1. Please use \`$0 help\` for help."
cmd=cmd_$1
shift
ensure_build_dir
$cmd "$@"

28
scripts/run_cargo_tests.sh Executable file
View File

@@ -0,0 +1,28 @@
#!/bin/bash
set -e
set -x
source $HOME/.cargo/env
# Install cargo components
time rustup component add clippy
time rustup component add rustfmt
time which cargo-audit || cargo install cargo-audit
# Run cargo builds and checks
time cargo clippy --all-targets --all-features -- -D warnings
time cargo rustc --bin cloud-hypervisor -- -D warnings
time cargo rustc --bin vhost_user_net -- -D warnings
time cargo test
time cargo audit
time cargo clippy --all-targets --no-default-features --features "pci,acpi" -- -D warnings
time cargo rustc --bin cloud-hypervisor --no-default-features --features "pci,acpi" -- -D warnings
time cargo rustc --bin vhost_user_net --no-default-features --features "pci,acpi" -- -D warnings
time cargo clippy --all-targets --no-default-features --features "pci" -- -D warnings
time cargo rustc --bin cloud-hypervisor --no-default-features --features "pci" -- -D warnings
time cargo rustc --bin vhost_user_net --no-default-features --features "pci" -- -D warnings
time cargo clippy --all-targets --no-default-features --features "mmio" -- -D warnings
time cargo rustc --bin cloud-hypervisor --no-default-features --features "mmio" -- -D warnings
time cargo rustc --bin vhost_user_net --no-default-features --features "mmio" -- -D warnings
time cargo fmt -- --check
time cargo build --release

View File

@@ -6,28 +6,28 @@ source $HOME/.cargo/env
WORKLOADS_DIR="$HOME/workloads" WORKLOADS_DIR="$HOME/workloads"
mkdir -p "$WORKLOADS_DIR" mkdir -p "$WORKLOADS_DIR"
FW_URL=$(curl --silent https://api.github.com/repos/intel/rust-hypervisor-firmware/releases/latest | grep "browser_download_url" | grep -o 'https://.*[^ "]') FW_URL=$(curl --silent https://api.github.com/repos/cloud-hypervisor/rust-hypervisor-firmware/releases/latest | grep "browser_download_url" | grep -o 'https://.*[^ "]')
FW="$WORKLOADS_DIR/hypervisor-fw" FW="$WORKLOADS_DIR/hypervisor-fw"
if [ ! -f "$FW" ]; then if [ ! -f "$FW" ]; then
pushd $WORKLOADS_DIR pushd $WORKLOADS_DIR
wget --quiet $FW_URL time wget --quiet $FW_URL
popd popd
fi fi
CLEAR_OS_IMAGE_NAME="clear-cloudguest.img" CLEAR_OS_IMAGE_NAME="clear-31310-cloudguest.img"
CLEAR_OS_IMAGE_URL="https://cloudhypervisorstorage.blob.core.windows.net/images/$CLEAR_OS_IMAGE_NAME" CLEAR_OS_IMAGE_URL="https://cloudhypervisorstorage.blob.core.windows.net/images/$CLEAR_OS_IMAGE_NAME"
CLEAR_OS_IMAGE="$WORKLOADS_DIR/$CLEAR_OS_IMAGE_NAME" CLEAR_OS_IMAGE="$WORKLOADS_DIR/$CLEAR_OS_IMAGE_NAME"
if [ ! -f "$CLEAR_OS_IMAGE" ]; then if [ ! -f "$CLEAR_OS_IMAGE" ]; then
pushd $WORKLOADS_DIR pushd $WORKLOADS_DIR
wget --quiet $CLEAR_OS_IMAGE_URL time wget --quiet $CLEAR_OS_IMAGE_URL
popd popd
fi fi
CLEAR_OS_RAW_IMAGE_NAME="clear-cloudguest-raw.img" CLEAR_OS_RAW_IMAGE_NAME="clear-31310-cloudguest-raw.img"
CLEAR_OS_RAW_IMAGE="$WORKLOADS_DIR/$CLEAR_OS_RAW_IMAGE_NAME" CLEAR_OS_RAW_IMAGE="$WORKLOADS_DIR/$CLEAR_OS_RAW_IMAGE_NAME"
if [ ! -f "$CLEAR_OS_RAW_IMAGE" ]; then if [ ! -f "$CLEAR_OS_RAW_IMAGE" ]; then
pushd $WORKLOADS_DIR pushd $WORKLOADS_DIR
qemu-img convert -p -f qcow2 -O raw $CLEAR_OS_IMAGE_NAME $CLEAR_OS_RAW_IMAGE_NAME time qemu-img convert -p -f qcow2 -O raw $CLEAR_OS_IMAGE_NAME $CLEAR_OS_RAW_IMAGE_NAME
popd popd
fi fi
@@ -36,7 +36,7 @@ BIONIC_OS_IMAGE_URL="https://cloudhypervisorstorage.blob.core.windows.net/images
BIONIC_OS_IMAGE="$WORKLOADS_DIR/$BIONIC_OS_IMAGE_NAME" BIONIC_OS_IMAGE="$WORKLOADS_DIR/$BIONIC_OS_IMAGE_NAME"
if [ ! -f "$BIONIC_OS_IMAGE" ]; then if [ ! -f "$BIONIC_OS_IMAGE" ]; then
pushd $WORKLOADS_DIR pushd $WORKLOADS_DIR
wget --quiet $BIONIC_OS_IMAGE_URL time wget --quiet $BIONIC_OS_IMAGE_URL
popd popd
fi fi
@@ -44,7 +44,7 @@ BIONIC_OS_RAW_IMAGE_NAME="bionic-server-cloudimg-amd64-raw.img"
BIONIC_OS_RAW_IMAGE="$WORKLOADS_DIR/$BIONIC_OS_RAW_IMAGE_NAME" BIONIC_OS_RAW_IMAGE="$WORKLOADS_DIR/$BIONIC_OS_RAW_IMAGE_NAME"
if [ ! -f "$BIONIC_OS_RAW_IMAGE" ]; then if [ ! -f "$BIONIC_OS_RAW_IMAGE" ]; then
pushd $WORKLOADS_DIR pushd $WORKLOADS_DIR
qemu-img convert -p -f qcow2 -O raw $BIONIC_OS_IMAGE_NAME $BIONIC_OS_RAW_IMAGE_NAME time qemu-img convert -p -f qcow2 -O raw $BIONIC_OS_IMAGE_NAME $BIONIC_OS_RAW_IMAGE_NAME
popd popd
fi fi
@@ -54,7 +54,7 @@ EOAN_OS_IMAGE_URL="https://cloudhypervisorstorage.blob.core.windows.net/images/$
EOAN_OS_IMAGE="$WORKLOADS_DIR/$EOAN_OS_IMAGE_NAME" EOAN_OS_IMAGE="$WORKLOADS_DIR/$EOAN_OS_IMAGE_NAME"
if [ ! -f "$EOAN_OS_IMAGE" ]; then if [ ! -f "$EOAN_OS_IMAGE" ]; then
pushd $WORKLOADS_DIR pushd $WORKLOADS_DIR
wget --quiet $EOAN_OS_IMAGE_URL time wget --quiet $EOAN_OS_IMAGE_URL
popd popd
fi fi
@@ -62,10 +62,17 @@ EOAN_OS_RAW_IMAGE_NAME="eoan-server-cloudimg-amd64-raw.img"
EOAN_OS_RAW_IMAGE="$WORKLOADS_DIR/$EOAN_OS_RAW_IMAGE_NAME" EOAN_OS_RAW_IMAGE="$WORKLOADS_DIR/$EOAN_OS_RAW_IMAGE_NAME"
if [ ! -f "$EOAN_OS_RAW_IMAGE" ]; then if [ ! -f "$EOAN_OS_RAW_IMAGE" ]; then
pushd $WORKLOADS_DIR pushd $WORKLOADS_DIR
qemu-img convert -p -f qcow2 -O raw $EOAN_OS_IMAGE_NAME $EOAN_OS_RAW_IMAGE_NAME time qemu-img convert -p -f qcow2 -O raw $EOAN_OS_IMAGE_NAME $EOAN_OS_RAW_IMAGE_NAME
popd popd
fi fi
pushd $WORKLOADS_DIR
curl --silent "https://cloudhypervisorstorage.blob.core.windows.net/images/sha1sums" | sha1sum --check
if [ $? -ne 0 ]; then
echo "sha1sum validation of images failed, remove invalid images to fix the issue."
exit 1
fi
popd
# Build custom kernel based on virtio-pmem and virtio-fs upstream patches # Build custom kernel based on virtio-pmem and virtio-fs upstream patches
VMLINUX_IMAGE="$WORKLOADS_DIR/vmlinux" VMLINUX_IMAGE="$WORKLOADS_DIR/vmlinux"
@@ -76,10 +83,10 @@ LINUX_CUSTOM_DIR="linux-custom"
if [ ! -f "$VMLINUX_IMAGE" ]; then if [ ! -f "$VMLINUX_IMAGE" ]; then
SRCDIR=$PWD SRCDIR=$PWD
pushd $WORKLOADS_DIR pushd $WORKLOADS_DIR
git clone --depth 1 "https://github.com/sboeuf/linux.git" -b "virtio-fs-virtio-iommu" $LINUX_CUSTOM_DIR time git clone --depth 1 "https://github.com/cloud-hypervisor/linux.git" -b "virtio-fs-virtio-iommu-topo-5.5-rc1" $LINUX_CUSTOM_DIR
pushd $LINUX_CUSTOM_DIR pushd $LINUX_CUSTOM_DIR
cp $SRCDIR/resources/linux-virtio-fs-virtio-iommu-config .config cp $SRCDIR/resources/linux-virtio-fs-virtio-iommu-config .config
make bzImage -j `nproc` time make bzImage -j `nproc`
cp vmlinux $VMLINUX_IMAGE cp vmlinux $VMLINUX_IMAGE
cp arch/x86/boot/bzImage $BZIMAGE_IMAGE cp arch/x86/boot/bzImage $BZIMAGE_IMAGE
popd popd
@@ -88,16 +95,14 @@ if [ ! -f "$VMLINUX_IMAGE" ]; then
fi fi
VIRTIOFSD="$WORKLOADS_DIR/virtiofsd" VIRTIOFSD="$WORKLOADS_DIR/virtiofsd"
VUBD="$WORKLOADS_DIR/vubd"
QEMU_DIR="qemu_build" QEMU_DIR="qemu_build"
if [ ! -f "$VIRTIOFSD" ] || [ ! -f "$VUBD" ]; then if [ ! -f "$VIRTIOFSD" ]; then
pushd $WORKLOADS_DIR pushd $WORKLOADS_DIR
git clone --depth 1 "https://github.com/sboeuf/qemu.git" -b "virtio-fs" $QEMU_DIR git clone --depth 1 "https://github.com/sboeuf/qemu.git" -b "virtio-fs" $QEMU_DIR
pushd $QEMU_DIR pushd $QEMU_DIR
./configure --prefix=$PWD --target-list=x86_64-softmmu time ./configure --prefix=$PWD --target-list=x86_64-softmmu
make virtiofsd vhost-user-blk -j `nproc` time make virtiofsd -j `nproc`
cp virtiofsd $VIRTIOFSD cp virtiofsd $VIRTIOFSD
cp vhost-user-blk $VUBD
popd popd
rm -rf $QEMU_DIR rm -rf $QEMU_DIR
sudo setcap cap_dac_override,cap_sys_admin+epi "virtiofsd" sudo setcap cap_dac_override,cap_sys_admin+epi "virtiofsd"
@@ -126,12 +131,11 @@ if [ ! -d "$SHARED_DIR" ]; then
fi fi
VFIO_DIR="$WORKLOADS_DIR/vfio" VFIO_DIR="$WORKLOADS_DIR/vfio"
if [ ! -d "$VFIO_DIR" ]; then rm -rf $VFIO_DIR
mkdir -p $VFIO_DIR mkdir -p $VFIO_DIR
cp $CLEAR_OS_IMAGE $VFIO_DIR cp $CLEAR_OS_IMAGE $VFIO_DIR
cp $FW $VFIO_DIR cp $FW $VFIO_DIR
cp $VMLINUX_IMAGE $VFIO_DIR cp $VMLINUX_IMAGE $VFIO_DIR
fi
# VFIO test network setup. # VFIO test network setup.
# We reserve a different IP class for it: 172.17.0.0/24. # We reserve a different IP class for it: 172.17.0.0/24.
@@ -147,28 +151,47 @@ sudo ip tuntap add vfio-tap1 mode tap
sudo ip link set vfio-tap1 master vfio-br0 sudo ip link set vfio-tap1 master vfio-br0
sudo ip link set vfio-tap1 up sudo ip link set vfio-tap1 up
cargo build sudo ip tuntap add vfio-tap2 mode tap
sudo setcap cap_net_admin+ep target/debug/cloud-hypervisor sudo ip link set vfio-tap2 master vfio-br0
sudo setcap cap_net_admin+ep target/debug/vhost_user_net sudo ip link set vfio-tap2 up
cargo build --release
sudo setcap cap_net_admin+ep target/release/cloud-hypervisor
sudo setcap cap_net_admin+ep target/release/vhost_user_net
sudo setcap cap_dac_override,cap_sys_admin+epi target/release/vhost_user_fs
# We always copy a fresh version of our binary for our L2 guest. # We always copy a fresh version of our binary for our L2 guest.
cp target/debug/cloud-hypervisor $VFIO_DIR cp target/release/cloud-hypervisor $VFIO_DIR
# Enable KSM with some reasonable parameters so that it won't take too long
# for the memory to be merged between two processes.
sudo bash -c "echo 1000000 > /sys/kernel/mm/ksm/pages_to_scan"
sudo bash -c "echo 10 > /sys/kernel/mm/ksm/sleep_millisecs"
sudo bash -c "echo 1 > /sys/kernel/mm/ksm/run"
# Ensure test binary has the same caps as the cloud-hypervisor one
time cargo test --no-run --features "integration_tests" -- --nocapture
ls target/debug/deps/cloud_hypervisor-* | xargs -n 1 sudo setcap cap_net_admin+ep
# test_vfio relies on hugepages
echo 4096 | sudo tee /proc/sys/vm/nr_hugepages
sudo chmod a+rwX /dev/hugepages
sudo adduser $USER kvm sudo adduser $USER kvm
newgrp kvm << EOF newgrp kvm << EOF
export RUST_BACKTRACE=1 export RUST_BACKTRACE=1
cargo test --features "integration_tests" -- --nocapture time cargo test --features "integration_tests" "$@" -- --nocapture
EOF EOF
RES=$? RES=$?
if [ $RES -eq 0 ]; then if [ $RES -eq 0 ]; then
# virtio-mmio based testing # virtio-mmio based testing
cargo build --no-default-features --features "mmio" cargo build --release --no-default-features --features "mmio"
sudo setcap cap_net_admin+ep target/debug/cloud-hypervisor sudo setcap cap_net_admin+ep target/release/cloud-hypervisor
newgrp kvm << EOF newgrp kvm << EOF
export RUST_BACKTRACE=1 export RUST_BACKTRACE=1
cargo test --features "integration_tests,mmio" -- --nocapture time cargo test --features "integration_tests,mmio" "$@" -- --nocapture
EOF EOF
RES=$? RES=$?
@@ -178,5 +201,6 @@ fi
sudo ip link del vfio-br0 sudo ip link del vfio-br0
sudo ip link del vfio-tap0 sudo ip link del vfio-tap0
sudo ip link del vfio-tap1 sudo ip link del vfio-tap1
sudo ip link del vfio-tap2
exit $RES exit $RES

5
scripts/run_openapi_tests.sh Executable file
View File

@@ -0,0 +1,5 @@
#!/bin/bash
set -e
set -x
sudo docker run --rm -v ${PWD}:/local openapitools/openapi-generator-cli validate -i /local/vmm/src/api/openapi/cloud-hypervisor.yaml

View File

@@ -2,23 +2,13 @@
source $HOME/.cargo/env source $HOME/.cargo/env
# More effective than just cargo test --all as it captures crates within crates cargo test --workspace --no-run
for f in $(find . -name Cargo.toml -printf '%h\n' | sort -u); do
pushd $f > /dev/null;
cargo test --no-run || exit 1;
popd > /dev/null;
done
pushd target/debug pushd target/debug
ls | grep net_util | grep -v "\.d" | xargs -n 1 sudo setcap cap_net_admin,cap_net_raw+ep ls | grep net_util | grep -v "\.d" | xargs -n 1 sudo setcap cap_net_admin,cap_net_raw+ep
popd popd
sudo adduser $USER kvm sudo adduser $USER kvm
newgrp kvm << EOF || exit 1 newgrp kvm << EOF || exit 1
for f in \$(find . -name Cargo.toml -printf '%h\n' | sort -u); do
pushd \$f > /dev/null;
export RUST_BACKTRACE=1 export RUST_BACKTRACE=1
cargo test || exit 1; cargo test --workspace "$@" || exit 1;
popd > /dev/null;
done
EOF EOF

38
src/bin/vhost_user_blk.rs Normal file
View File

@@ -0,0 +1,38 @@
// Copyright 2019 Red Hat, Inc. All Rights Reserved.
//
// Portions Copyright 2019 Intel Corporation. All Rights Reserved.
//
// Portions Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
//
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
//
// SPDX-License-Identifier: (Apache-2.0 AND BSD-3-Clause)
#[macro_use(crate_version, crate_authors)]
extern crate clap;
extern crate vhost_user_block;
use clap::{App, Arg};
use vhost_user_block::start_block_backend;
fn main() {
let cmd_arguments = App::new("vhost-user-blk backend")
.version(crate_version!())
.author(crate_authors!())
.about("Launch a vhost-user-blk backend.")
.arg(
Arg::with_name("block-backend")
.long("block-backend")
.help(
"vhost-user-block backend parameters \"image=<image_path>,\
sock=<socket_path>,num_queues=<number_of_queues>,\
readonly=true|false,direct=true|false\"",
)
.takes_value(true)
.min_values(1),
)
.get_matches();
let backend_command = cmd_arguments.value_of("block-backend").unwrap();
start_block_backend(backend_command);
}

246
src/bin/vhost_user_fs.rs Normal file
View File

@@ -0,0 +1,246 @@
// Copyright 2019 Intel Corporation. All Rights Reserved.
//
// SPDX-License-Identifier: (Apache-2.0 AND BSD-3-Clause)
#[macro_use(crate_version, crate_authors)]
extern crate clap;
extern crate log;
extern crate vhost_rs;
extern crate vhost_user_backend;
extern crate vm_virtio;
use clap::{App, Arg};
use epoll;
use libc::EFD_NONBLOCK;
use log::*;
use std::os::unix::io::AsRawFd;
use std::sync::{Arc, RwLock};
use std::{convert, error, fmt, io, process};
use vhost_rs::vhost_user::message::*;
use vhost_user_backend::{VhostUserBackend, VhostUserDaemon, Vring};
use vhost_user_fs::descriptor_utils::{Reader, Writer};
use vhost_user_fs::filesystem::FileSystem;
use vhost_user_fs::passthrough::{self, PassthroughFs};
use vhost_user_fs::server::Server;
use vhost_user_fs::Error as VhostUserFsError;
use virtio_bindings::bindings::virtio_net::*;
use vm_memory::GuestMemoryMmap;
use vmm_sys_util::eventfd::EventFd;
const QUEUE_SIZE: usize = 1024;
const NUM_QUEUES: usize = 2;
// The guest queued an available buffer for the high priority queue.
const HIPRIO_QUEUE_EVENT: u16 = 0;
// The guest queued an available buffer for the request queue.
const REQ_QUEUE_EVENT: u16 = 1;
// The device has been dropped.
const KILL_EVENT: u16 = 2;
type Result<T> = std::result::Result<T, Error>;
type VhostUserBackendResult<T> = std::result::Result<T, std::io::Error>;
#[derive(Debug)]
enum Error {
/// Failed to create kill eventfd.
CreateKillEventFd(io::Error),
/// Failed to handle event other than input event.
HandleEventNotEpollIn,
/// Failed to handle unknown event.
HandleEventUnknownEvent,
/// No memory configured.
NoMemoryConfigured,
/// Processing queue failed.
ProcessQueue(VhostUserFsError),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "vhost_user_fs_error: {:?}", self)
}
}
impl error::Error for Error {}
impl convert::From<Error> for io::Error {
fn from(e: Error) -> Self {
io::Error::new(io::ErrorKind::Other, e)
}
}
struct VhostUserFsBackend<F: FileSystem + Send + Sync + 'static> {
mem: Option<GuestMemoryMmap>,
kill_evt: EventFd,
server: Arc<Server<F>>,
}
impl<F: FileSystem + Send + Sync + 'static> Clone for VhostUserFsBackend<F> {
fn clone(&self) -> Self {
VhostUserFsBackend {
mem: self.mem.clone(),
kill_evt: self.kill_evt.try_clone().unwrap(),
server: self.server.clone(),
}
}
}
impl<F: FileSystem + Send + Sync + 'static> VhostUserFsBackend<F> {
fn new(fs: F) -> Result<Self> {
Ok(VhostUserFsBackend {
mem: None,
kill_evt: EventFd::new(EFD_NONBLOCK).map_err(Error::CreateKillEventFd)?,
server: Arc::new(Server::new(fs)),
})
}
fn process_queue(&mut self, vring: &mut Vring) -> Result<()> {
let mem = self.mem.as_ref().ok_or(Error::NoMemoryConfigured)?;
let mut used_desc_heads = [(0, 0); QUEUE_SIZE];
let mut used_count = 0;
while let Some(avail_desc) = vring.mut_queue().iter(&mem).next() {
let head_index = avail_desc.index;
let reader = Reader::new(mem, avail_desc.clone()).unwrap();
let writer = Writer::new(mem, avail_desc.clone()).unwrap();
let total = self
.server
.handle_message(reader, writer)
.map_err(Error::ProcessQueue)?;
used_desc_heads[used_count] = (head_index, total);
used_count += 1;
}
if used_count > 0 {
for &(desc_index, _) in &used_desc_heads[..used_count] {
vring.mut_queue().add_used(&mem, desc_index, 0);
}
vring.signal_used_queue().unwrap();
}
Ok(())
}
}
impl<F: FileSystem + Send + Sync + 'static> VhostUserBackend for VhostUserFsBackend<F> {
fn num_queues(&self) -> usize {
NUM_QUEUES
}
fn max_queue_size(&self) -> usize {
QUEUE_SIZE
}
fn features(&self) -> u64 {
1 << VIRTIO_F_VERSION_1 | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits()
}
fn protocol_features(&self) -> VhostUserProtocolFeatures {
VhostUserProtocolFeatures::all()
}
fn update_memory(&mut self, mem: GuestMemoryMmap) -> VhostUserBackendResult<()> {
self.mem = Some(mem);
Ok(())
}
fn handle_event(
&mut self,
device_event: u16,
evset: epoll::Events,
vrings: &[Arc<RwLock<Vring>>],
) -> VhostUserBackendResult<bool> {
if evset != epoll::Events::EPOLLIN {
return Err(Error::HandleEventNotEpollIn.into());
}
match device_event {
HIPRIO_QUEUE_EVENT => {
debug!("HIPRIO_QUEUE_EVENT");
}
REQ_QUEUE_EVENT => {
debug!("REQ_QUEUE_EVENT");
let mut vring = vrings[1].write().unwrap();
self.process_queue(&mut vring)?;
}
KILL_EVENT => {
debug!("KILL_EVENT");
self.kill_evt.read().unwrap();
return Ok(true);
}
_ => return Err(Error::HandleEventUnknownEvent.into()),
}
Ok(false)
}
}
fn main() {
let cmd_arguments = App::new("vhost-user-fs backend")
.version(crate_version!())
.author(crate_authors!())
.about("Launch a vhost-user-fs backend.")
.arg(
Arg::with_name("shared-dir")
.long("shared-dir")
.help("Shared directory path")
.takes_value(true)
.min_values(1),
)
.arg(
Arg::with_name("sock")
.long("sock")
.help("vhost-user socket path")
.takes_value(true)
.min_values(1),
)
.get_matches();
// Retrieve arguments
let shared_dir = cmd_arguments
.value_of("shared-dir")
.expect("Failed to retrieve shared directory path");
let sock = cmd_arguments
.value_of("sock")
.expect("Failed to retrieve vhost-user socket path");
// Convert into appropriate types
let sock = String::from(sock);
let fs_cfg = passthrough::Config {
root_dir: shared_dir.to_string(),
..Default::default()
};
let fs = PassthroughFs::new(fs_cfg).unwrap();
let fs_backend = Arc::new(RwLock::new(VhostUserFsBackend::new(fs).unwrap()));
let mut daemon = VhostUserDaemon::new(
String::from("vhost-user-fs-backend"),
sock,
fs_backend.clone(),
)
.unwrap();
let vring_worker = daemon.get_vring_worker();
if let Err(e) = vring_worker.register_listener(
fs_backend.read().unwrap().kill_evt.as_raw_fd(),
epoll::Events::EPOLLIN,
u64::from(KILL_EVENT),
) {
println!("Failed to register listener for kill event: {:?}", e);
process::exit(1);
}
if let Err(e) = daemon.start() {
println!("Failed to start daemon: {:?}", e);
process::exit(1);
}
if let Err(e) = daemon.wait() {
println!("Waiting for daemon failed: {:?}", e);
process::exit(1);
}
}

View File

@@ -8,532 +8,10 @@
#[macro_use(crate_version, crate_authors)] #[macro_use(crate_version, crate_authors)]
extern crate clap; extern crate clap;
extern crate log; extern crate vhost_user_net;
extern crate net_util;
extern crate vhost_rs;
extern crate vhost_user_backend;
extern crate vm_virtio;
use clap::{App, Arg}; use clap::{App, Arg};
use epoll; use vhost_user_net::start_net_backend;
use libc::{self, EAGAIN, EFD_NONBLOCK};
use log::*;
use std::cmp;
use std::fmt;
use std::io::Read;
use std::io::{self, Write};
use std::mem;
use std::net::Ipv4Addr;
use std::os::unix::io::AsRawFd;
use std::process;
use std::sync::{Arc, RwLock};
use std::vec::Vec;
use vhost_rs::vhost_user::message::*;
use vhost_rs::vhost_user::Error as VhostUserError;
use vhost_user_backend::{VhostUserBackend, VhostUserDaemon, Vring, VringWorker};
use net_gen;
use net_util::{Tap, TapError};
use virtio_bindings::bindings::virtio_net::*;
use vm_memory::{Bytes, GuestAddress, GuestMemoryMmap};
use vmm_sys_util::eventfd::EventFd;
/// The maximum buffer size when segmentation offload is enabled. This
/// includes the 12-byte virtio net header.
/// http://docs.oasis-open.org/virtio/virtio/v1.0/virtio-v1.0.html#x1-1740003
const MAX_BUFFER_SIZE: usize = 65562;
const QUEUE_SIZE: usize = 1024;
const NUM_QUEUES: usize = 2;
// The guest has made a buffer available to receive a frame into.
const RX_QUEUE_EVENT: u16 = 0;
// The transmit queue has a frame that is ready to send from the guest.
const TX_QUEUE_EVENT: u16 = 1;
// A frame is available for reading from the tap device to receive in the guest.
const RX_TAP_EVENT: u16 = 2;
// The device has been dropped.
const KILL_EVENT: u16 = 3;
pub type VhostUserResult<T> = std::result::Result<T, VhostUserError>;
pub type Result<T> = std::result::Result<T, Error>;
pub type VhostUserBackendResult<T> = std::result::Result<T, std::io::Error>;
#[derive(Debug)]
pub enum Error {
/// Failed to activate device.
BadActivate,
/// Failed to create kill eventfd
CreateKillEventFd,
/// Failed to add event.
EpollCtl(io::Error),
/// Fail to wait event.
EpollWait(io::Error),
/// Failed to create EventFd.
EpollCreateFd,
/// Failed to read Tap.
FailedReadTap,
/// Failed to signal used queue.
FailedSignalingUsedQueue,
/// Failed to handle event other than input event.
HandleEventNotEpollIn,
/// Failed to handle unknown event.
HandleEventUnknownEvent,
/// Invalid vring address.
InvalidVringAddr,
/// No vring call fd to notify.
NoVringCallFdNotify,
/// No memory configured.
NoMemoryConfigured,
/// Failed to parse sock parameter.
ParseSockParam,
/// Failed to parse ip parameter.
ParseIpParam,
/// Failed to parse mask parameter.
ParseMaskParam,
/// Open tap device failed.
TapOpen(TapError),
/// Setting tap IP failed.
TapSetIp(TapError),
/// Setting tap netmask failed.
TapSetNetmask(TapError),
/// Setting tap interface offload flags failed.
TapSetOffload(TapError),
/// Setting vnet header size failed.
TapSetVnetHdrSize(TapError),
/// Enabling tap interface failed.
TapEnable(TapError),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "vhost_user_net_error: {:?}", self)
}
}
impl std::error::Error for Error {}
impl std::convert::From<Error> for std::io::Error {
fn from(e: Error) -> Self {
std::io::Error::new(io::ErrorKind::Other, e)
}
}
#[derive(Clone)]
struct TxVirtio {
iovec: Vec<(GuestAddress, usize)>,
frame_buf: [u8; MAX_BUFFER_SIZE],
}
impl TxVirtio {
fn new() -> Self {
TxVirtio {
iovec: Vec::new(),
frame_buf: [0u8; MAX_BUFFER_SIZE],
}
}
}
#[derive(Clone)]
struct RxVirtio {
deferred_frame: bool,
deferred_irqs: bool,
bytes_read: usize,
frame_buf: [u8; MAX_BUFFER_SIZE],
}
impl RxVirtio {
fn new() -> Self {
RxVirtio {
deferred_frame: false,
deferred_irqs: false,
bytes_read: 0,
frame_buf: [0u8; MAX_BUFFER_SIZE],
}
}
}
fn vnet_hdr_len() -> usize {
mem::size_of::<virtio_net_hdr_v1>()
}
struct VhostUserNetBackend {
mem: Option<GuestMemoryMmap>,
vring_worker: Option<Arc<VringWorker>>,
kill_evt: EventFd,
tap: Tap,
rx: RxVirtio,
tx: TxVirtio,
rx_tap_listening: bool,
}
impl std::clone::Clone for VhostUserNetBackend {
fn clone(&self) -> Self {
VhostUserNetBackend {
mem: self.mem.clone(),
vring_worker: self.vring_worker.clone(),
kill_evt: self.kill_evt.try_clone().unwrap(),
tap: self.tap.clone(),
rx: self.rx.clone(),
tx: self.tx.clone(),
rx_tap_listening: self.rx_tap_listening,
}
}
}
impl VhostUserNetBackend {
/// Create a new virtio network device with the given TAP interface.
pub fn new_with_tap(tap: Tap) -> Result<Self> {
// Set offload flags to match the virtio features below.
tap.set_offload(
net_gen::TUN_F_CSUM | net_gen::TUN_F_UFO | net_gen::TUN_F_TSO4 | net_gen::TUN_F_TSO6,
)
.map_err(Error::TapSetOffload)?;
let vnet_hdr_size = vnet_hdr_len() as i32;
tap.set_vnet_hdr_size(vnet_hdr_size)
.map_err(Error::TapSetVnetHdrSize)?;
let rx = RxVirtio::new();
let tx = TxVirtio::new();
Ok(VhostUserNetBackend {
mem: None,
vring_worker: None,
kill_evt: EventFd::new(EFD_NONBLOCK).map_err(|_| Error::CreateKillEventFd)?,
tap,
rx,
tx,
rx_tap_listening: false,
})
}
/// Create a new virtio network device with the given IP address and
/// netmask.
pub fn new(ip_addr: Ipv4Addr, netmask: Ipv4Addr) -> Result<Self> {
let tap = Tap::new().map_err(Error::TapOpen)?;
tap.set_ip_addr(ip_addr).map_err(Error::TapSetIp)?;
tap.set_netmask(netmask).map_err(Error::TapSetNetmask)?;
tap.enable().map_err(Error::TapEnable)?;
Self::new_with_tap(tap)
}
// Copies a single frame from `self.rx.frame_buf` into the guest. Returns true
// if a buffer was used, and false if the frame must be deferred until a buffer
// is made available by the driver.
fn rx_single_frame(&mut self, vring: &mut Vring) -> Result<bool> {
let mem = self.mem.as_ref().ok_or(Error::NoMemoryConfigured)?;
let mut next_desc = vring.mut_queue().iter(&mem).next();
if next_desc.is_none() {
// Queue has no available descriptors
if self.rx_tap_listening {
self.vring_worker
.as_ref()
.unwrap()
.unregister_listener(
self.tap.as_raw_fd(),
epoll::Events::EPOLLIN,
u64::from(RX_TAP_EVENT),
)
.unwrap();
self.rx_tap_listening = false;
}
return Ok(false);
}
// We just checked that the head descriptor exists.
let head_index = next_desc.as_ref().unwrap().index;
let mut write_count = 0;
// Copy from frame into buffer, which may span multiple descriptors.
loop {
match next_desc {
Some(desc) => {
if !desc.is_write_only() {
break;
}
let limit = cmp::min(write_count + desc.len as usize, self.rx.bytes_read);
let source_slice = &self.rx.frame_buf[write_count..limit];
let write_result = mem.write_slice(source_slice, desc.addr);
match write_result {
Ok(_) => {
write_count = limit;
}
Err(e) => {
error!("Failed to write slice: {:?}", e);
break;
}
};
if write_count >= self.rx.bytes_read {
break;
}
next_desc = desc.next_descriptor();
}
None => {
warn!("Receiving buffer is too small to hold frame of current size");
break;
}
}
}
vring
.mut_queue()
.add_used(&mem, head_index, write_count as u32);
// Mark that we have at least one pending packet and we need to interrupt the guest.
self.rx.deferred_irqs = true;
Ok(write_count >= self.rx.bytes_read)
}
fn process_rx(&mut self, vring: &mut Vring) -> Result<()> {
// Read as many frames as possible.
loop {
match self.read_tap() {
Ok(count) => {
self.rx.bytes_read = count;
if !self.rx_single_frame(vring)? {
self.rx.deferred_frame = true;
break;
}
}
Err(e) => {
// The tap device is non-blocking, so any error aside from EAGAIN is
// unexpected.
match e.raw_os_error() {
Some(err) if err == EAGAIN => (),
_ => {
error!("Failed to read tap: {:?}", e);
return Err(Error::FailedReadTap);
}
};
break;
}
}
}
if self.rx.deferred_irqs {
self.rx.deferred_irqs = false;
vring.signal_used_queue().unwrap();
Ok(())
} else {
Ok(())
}
}
fn resume_rx(&mut self, vring: &mut Vring) -> Result<()> {
if self.rx.deferred_frame {
if self.rx_single_frame(vring)? {
self.rx.deferred_frame = false;
// process_rx() was interrupted possibly before consuming all
// packets in the tap; try continuing now.
self.process_rx(vring)
} else if self.rx.deferred_irqs {
self.rx.deferred_irqs = false;
vring.signal_used_queue().unwrap();
Ok(())
} else {
Ok(())
}
} else {
Ok(())
}
}
fn process_tx(&mut self, vring: &mut Vring) -> Result<()> {
let mem = self.mem.as_ref().ok_or(Error::NoMemoryConfigured)?;
let mut used_desc_heads = [(0, 0); QUEUE_SIZE];
let mut used_count = 0;
while let Some(avail_desc) = vring.mut_queue().iter(&mem).next() {
let head_index = avail_desc.index;
let mut read_count = 0;
let mut next_desc = Some(avail_desc);
self.tx.iovec.clear();
while let Some(desc) = next_desc {
if desc.is_write_only() {
break;
}
self.tx.iovec.push((desc.addr, desc.len as usize));
read_count += desc.len as usize;
next_desc = desc.next_descriptor();
}
used_desc_heads[used_count] = (head_index, read_count);
used_count += 1;
read_count = 0;
// Copy buffer from across multiple descriptors.
// TODO(performance - Issue #420): change this to use `writev()` instead of `write()`
// and get rid of the intermediate buffer.
for (desc_addr, desc_len) in self.tx.iovec.drain(..) {
let limit = cmp::min((read_count + desc_len) as usize, self.tx.frame_buf.len());
let read_result = mem.read_slice(
&mut self.tx.frame_buf[read_count..limit as usize],
desc_addr,
);
match read_result {
Ok(_) => {
// Increment by number of bytes actually read
read_count += limit - read_count;
}
Err(e) => {
error!("Failed to read slice: {:?}", e);
break;
}
}
}
let write_result = self.tap.write(&self.tx.frame_buf[..read_count as usize]);
match write_result {
Ok(_) => {}
Err(e) => {
error!("net: tx: error failed to write to tap: {}", e);
}
};
}
if used_count > 0 {
for &(desc_index, _) in &used_desc_heads[..used_count] {
vring.mut_queue().add_used(&mem, desc_index, 0);
}
vring.signal_used_queue().unwrap();
}
Ok(())
}
fn read_tap(&mut self) -> io::Result<usize> {
self.tap.read(&mut self.rx.frame_buf)
}
}
impl VhostUserBackend for VhostUserNetBackend {
fn num_queues(&self) -> usize {
NUM_QUEUES
}
fn max_queue_size(&self) -> usize {
QUEUE_SIZE
}
fn features(&self) -> u64 {
1 << VIRTIO_NET_F_GUEST_CSUM
| 1 << VIRTIO_NET_F_CSUM
| 1 << VIRTIO_NET_F_GUEST_TSO4
| 1 << VIRTIO_NET_F_GUEST_UFO
| 1 << VIRTIO_NET_F_HOST_TSO4
| 1 << VIRTIO_NET_F_HOST_UFO
| 1 << VIRTIO_F_VERSION_1
| VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits()
}
fn update_memory(&mut self, mem: GuestMemoryMmap) -> VhostUserBackendResult<()> {
self.mem = Some(mem);
Ok(())
}
fn handle_event(
&mut self,
device_event: u16,
evset: epoll::Events,
vrings: &[Arc<RwLock<Vring>>],
) -> VhostUserBackendResult<bool> {
if evset != epoll::Events::EPOLLIN {
return Err(Error::HandleEventNotEpollIn.into());
}
match device_event {
RX_QUEUE_EVENT => {
let mut vring = vrings[0].write().unwrap();
self.resume_rx(&mut vring)?;
if !self.rx_tap_listening {
self.vring_worker.as_ref().unwrap().register_listener(
self.tap.as_raw_fd(),
epoll::Events::EPOLLIN,
u64::from(RX_TAP_EVENT),
)?;
self.rx_tap_listening = true;
}
}
TX_QUEUE_EVENT => {
let mut vring = vrings[1].write().unwrap();
self.process_tx(&mut vring)?;
}
RX_TAP_EVENT => {
let mut vring = vrings[0].write().unwrap();
if self.rx.deferred_frame
// Process a deferred frame first if available. Don't read from tap again
// until we manage to receive this deferred frame.
{
if self.rx_single_frame(&mut vring)? {
self.rx.deferred_frame = false;
self.process_rx(&mut vring)?;
} else if self.rx.deferred_irqs {
self.rx.deferred_irqs = false;
vring.signal_used_queue()?;
}
} else {
self.process_rx(&mut vring)?;
}
}
KILL_EVENT => {
self.kill_evt.read().unwrap();
return Ok(true);
}
_ => return Err(Error::HandleEventUnknownEvent.into()),
}
Ok(false)
}
}
pub struct VhostUserNetBackendConfig<'a> {
pub ip: Ipv4Addr,
pub mask: Ipv4Addr,
pub sock: &'a str,
}
impl<'a> VhostUserNetBackendConfig<'a> {
pub fn parse(backend: &'a str) -> Result<Self> {
let params_list: Vec<&str> = backend.split(',').collect();
let mut ip_str: &str = "";
let mut mask_str: &str = "";
let mut sock: &str = "";
for param in params_list.iter() {
if param.starts_with("ip=") {
ip_str = &param[3..];
} else if param.starts_with("mask=") {
mask_str = &param[5..];
} else if param.starts_with("sock=") {
sock = &param[5..];
}
}
let mut ip: Ipv4Addr = Ipv4Addr::new(192, 168, 100, 1);
let mut mask: Ipv4Addr = Ipv4Addr::new(255, 255, 255, 0);
if sock.is_empty() {
return Err(Error::ParseSockParam);
}
if !ip_str.is_empty() {
ip = ip_str.parse().map_err(|_| Error::ParseIpParam)?;
}
if !mask_str.is_empty() {
mask = mask_str.parse().map_err(|_| Error::ParseMaskParam)?;
}
Ok(VhostUserNetBackendConfig { ip, mask, sock })
}
}
fn main() { fn main() {
let cmd_arguments = App::new("vhost-user-net backend") let cmd_arguments = App::new("vhost-user-net backend")
@@ -541,57 +19,19 @@ fn main() {
.author(crate_authors!()) .author(crate_authors!())
.about("Launch a vhost-user-net backend.") .about("Launch a vhost-user-net backend.")
.arg( .arg(
Arg::with_name("backend") Arg::with_name("net-backend")
.long("backend") .long("net-backend")
.help( .help(
"Backend parameters \"ip=<ip_addr>,\ "vhost-user-net backend parameters \"ip=<ip_addr>,\
mask=<net_mask>,sock=<socket_path>\"", mask=<net_mask>,sock=<socket_path>,\
num_queues=<number_of_queues>,\
queue_size=<size_of_each_queue>\"",
) )
.takes_value(true) .takes_value(true)
.min_values(1), .min_values(1),
) )
.get_matches(); .get_matches();
let vhost_user_net_backend = cmd_arguments.value_of("backend").unwrap(); let backend_command = cmd_arguments.value_of("net-backend").unwrap();
start_net_backend(backend_command);
let backend_config = match VhostUserNetBackendConfig::parse(vhost_user_net_backend) {
Ok(config) => config,
Err(e) => {
println!("Failed parsing parameters {:?}", e);
process::exit(1);
}
};
let net_backend = Arc::new(RwLock::new(
VhostUserNetBackend::new(backend_config.ip, backend_config.mask).unwrap(),
));
let name = "vhost-user-net-backend";
let mut net_daemon = VhostUserDaemon::new(
name.to_string(),
backend_config.sock.to_string(),
net_backend.clone(),
)
.unwrap();
let vring_worker = net_daemon.get_vring_worker();
if let Err(e) = vring_worker.register_listener(
net_backend.read().unwrap().kill_evt.as_raw_fd(),
epoll::Events::EPOLLIN,
u64::from(KILL_EVENT),
) {
println!("failed to register listener for kill event: {:?}", e);
process::exit(1);
}
net_backend.write().unwrap().vring_worker = Some(vring_worker);
if let Err(e) = net_daemon.start() {
println!(
"failed to start daemon for vhost-user-net with error: {:?}",
e
);
process::exit(1);
}
net_daemon.wait().unwrap();
} }

File diff suppressed because it is too large Load Diff

View File

@@ -17,7 +17,7 @@ write_files:
Gateway=192.168.2.1 Gateway=192.168.2.1
- -
path: /etc/systemd/network/00-static-l2.network path: /etc/systemd/network/00-static-l2-1.network
permissions: 0644 permissions: 0644
content: | content: |
[Match] [Match]
@@ -27,6 +27,17 @@ write_files:
Address=192.168.2.3/24 Address=192.168.2.3/24
Gateway=192.168.2.1 Gateway=192.168.2.1
-
path: /etc/systemd/network/00-static-l2-2.network
permissions: 0644
content: |
[Match]
MACAddress=de:ad:be:ef:34:56
[Network]
Address=192.168.2.4/24
Gateway=192.168.2.1
- -
path: /etc/systemd/system/vfio.service path: /etc/systemd/system/vfio.service
permissions: 0644 permissions: 0644
@@ -47,8 +58,12 @@ write_files:
content: | content: |
#!/bin/bash #!/bin/bash
mount -t virtio_fs virtiofs /mnt -o rootmode=040000,user_id=0,group_id=0,dax mount -t virtiofs -o dax myfs /mnt
bash -c "echo 0000:00:05.0 > /sys/bus/pci/devices/0000\:00\:05.0/driver/unbind" bash -c "echo 0000:00:05.0 > /sys/bus/pci/devices/0000\:00\:05.0/driver/unbind"
bash -c "echo 1af4 1041 > /sys/bus/pci/drivers/vfio-pci/new_id" bash -c "echo 1af4 1041 > /sys/bus/pci/drivers/vfio-pci/new_id"
bash -c "echo 0000:00:06.0 > /sys/bus/pci/devices/0000\:00\:06.0/driver/unbind"
/mnt/cloud-hypervisor --kernel /mnt/vmlinux --cmdline "console=hvc0 reboot=k panic=1 nomodules i8042.noaux i8042.nomux i8042.nopnp i8042.dumbkbd root=/dev/vda2 VFIOTAG" --disk path=/mnt/clear-cloudguest.img path=/mnt/cloudinit.img --cpus 1 --memory size=512M --rng --device path=/sys/bus/pci/devices/0000:00:05.0/ bash -c "echo 1af4 1041 > /sys/bus/pci/drivers/vfio-pci/new_id"
# 512M ram requires 256 pages
echo 256 | sudo tee /proc/sys/vm/nr_hugepages
sudo chmod a+rwX /dev/hugepages
/mnt/cloud-hypervisor --kernel /mnt/vmlinux --cmdline "console=hvc0 reboot=k panic=1 nomodules i8042.noaux i8042.nomux i8042.nopnp i8042.dumbkbd root=/dev/vda2 VFIOTAG" --disk path=/mnt/clear-31310-cloudguest.img path=/mnt/cloudinit.img --cpus boot=1 --memory size=512M,file=/dev/hugepages --device path=/sys/bus/pci/devices/0000:00:05.0/ path=/sys/bus/pci/devices/0000:00:06.0/

View File

@@ -4,18 +4,22 @@ version = "0.0.1"
authors = ["The Cloud Hypervisor Authors"] authors = ["The Cloud Hypervisor Authors"]
[dependencies] [dependencies]
byteorder = "1.3.2" arc-swap = "0.4.4"
byteorder = "1.3.4"
devices = { path = "../devices" } devices = { path = "../devices" }
kvm-bindings = "0.1.1" kvm-bindings = "0.2.0"
kvm-ioctls = { git = "https://github.com/rust-vmm/kvm-ioctls", branch = "master" } kvm-ioctls = "0.5.0"
libc = "0.2.60" libc = "0.2.66"
log = "0.4.8" log = "0.4.8"
pci = { path = "../pci" } pci = { path = "../pci" }
vfio-bindings = "0.1.0"
vm-allocator = { path = "../vm-allocator" } vm-allocator = { path = "../vm-allocator" }
vm-device = { path = "../vm-device" } vm-device = { path = "../vm-device" }
vmm-sys-util = "0.1.1" vmm-sys-util = "0.4.0"
[dependencies.vm-memory] [dependencies.vm-memory]
git = "https://github.com/rust-vmm/vm-memory" git = "https://github.com/rust-vmm/vm-memory"
features = ["backend-mmap"] features = ["backend-mmap"]
[dependencies.vfio-bindings]
git = "https://github.com/rust-vmm/vfio-bindings"
features = ["fam-wrappers"]

View File

@@ -5,6 +5,7 @@
//#![deny(missing_docs)] //#![deny(missing_docs)]
//! Virtual Function I/O (VFIO) API //! Virtual Function I/O (VFIO) API
extern crate arc_swap;
extern crate byteorder; extern crate byteorder;
extern crate devices; extern crate devices;
extern crate kvm_bindings; extern crate kvm_bindings;

View File

@@ -3,25 +3,30 @@
// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause // SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
// //
use crate::vec_with_array_field; use crate::vec_with_array_field;
use arc_swap::ArcSwap;
use byteorder::{ByteOrder, LittleEndian}; use byteorder::{ByteOrder, LittleEndian};
use kvm_ioctls::*; use kvm_ioctls::*;
use std::collections::HashMap; use std::collections::HashMap;
use std::convert::TryInto;
use std::ffi::CString; use std::ffi::CString;
use std::fmt; use std::fmt;
use std::fs::{File, OpenOptions}; use std::fs::{File, OpenOptions};
use std::io; use std::io;
use std::mem; use std::mem;
use std::num;
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
use std::os::unix::prelude::FileExt; use std::os::unix::prelude::FileExt;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::result; use std::result;
use std::sync::{Arc, RwLock}; use std::sync::Arc;
use std::u32; use std::u32;
use vfio_bindings::bindings::vfio::*; use vfio_bindings::bindings::vfio::*;
use vfio_bindings::bindings::IrqSet;
use vfio_ioctls::*; use vfio_ioctls::*;
use vm_device::ExternalDmaMapping; use vm_device::{get_host_address_range, ExternalDmaMapping};
use vm_memory::{Address, GuestAddress, GuestMemory, GuestMemoryMmap, GuestMemoryRegion}; use vm_memory::{Address, GuestAddress, GuestMemory, GuestMemoryMmap, GuestMemoryRegion};
use vmm_sys_util::eventfd::EventFd; use vmm_sys_util::eventfd::EventFd;
use vmm_sys_util::fam::FamStruct;
use vmm_sys_util::ioctl::*; use vmm_sys_util::ioctl::*;
#[derive(Debug)] #[derive(Debug)]
@@ -38,7 +43,7 @@ pub enum VfioError {
UnsetContainer, UnsetContainer,
ContainerSetIOMMU, ContainerSetIOMMU,
GroupGetDeviceFD, GroupGetDeviceFD,
KvmSetDeviceAttr(io::Error), KvmSetDeviceAttr(kvm_ioctls::Error),
VfioDeviceGetInfo, VfioDeviceGetInfo,
VfioDeviceGetRegionInfo, VfioDeviceGetRegionInfo,
InvalidPath, InvalidPath,
@@ -46,6 +51,8 @@ pub enum VfioError {
IommuDmaUnmap, IommuDmaUnmap,
VfioDeviceGetIrqInfo, VfioDeviceGetIrqInfo,
VfioDeviceSetIrq, VfioDeviceSetIrq,
ReadLink(io::Error),
ParseInt(num::ParseIntError),
} }
pub type Result<T> = std::result::Result<T, VfioError>; pub type Result<T> = std::result::Result<T, VfioError>;
@@ -94,6 +101,8 @@ impl fmt::Display for VfioError {
} }
VfioError::VfioDeviceGetIrqInfo => write!(f, "failed to get vfio device irq info"), VfioError::VfioDeviceGetIrqInfo => write!(f, "failed to get vfio device irq info"),
VfioError::VfioDeviceSetIrq => write!(f, "failed to set vfio deviece irq"), VfioError::VfioDeviceSetIrq => write!(f, "failed to set vfio deviece irq"),
VfioError::ReadLink(e) => write!(f, "failed to read link from path: {}", e),
VfioError::ParseInt(e) => write!(f, "failed to parse integer: {}", e),
} }
} }
} }
@@ -268,7 +277,7 @@ impl VfioGroup {
.map_err(VfioError::KvmSetDeviceAttr) .map_err(VfioError::KvmSetDeviceAttr)
} }
fn kvm_device_del_group(&self) -> std::result::Result<(), io::Error> { fn kvm_device_del_group(&self) -> std::result::Result<(), kvm_ioctls::Error> {
let group_fd = self.as_raw_fd(); let group_fd = self.as_raw_fd();
let group_fd_ptr = &group_fd as *const i32; let group_fd_ptr = &group_fd as *const i32;
let dev_attr = kvm_bindings::kvm_device_attr { let dev_attr = kvm_bindings::kvm_device_attr {
@@ -435,7 +444,7 @@ impl VfioDeviceInfo {
ioctl_with_mut_ref(&self.device, VFIO_DEVICE_GET_REGION_INFO(), &mut reg_info) ioctl_with_mut_ref(&self.device, VFIO_DEVICE_GET_REGION_INFO(), &mut reg_info)
}; };
if ret < 0 { if ret < 0 {
error!("Could not get region #{} info", i); warn!("Could not get region #{} info", i);
continue; continue;
} }
@@ -461,7 +470,7 @@ impl VfioDeviceInfo {
) )
}; };
if ret < 0 { if ret < 0 {
error!("Could not get region #{} info", i); warn!("Could not get region #{} info", i);
continue; continue;
} }
// region_with_cap[0].cap_info may contain vfio_region_info_cap_sparse_mmap // region_with_cap[0].cap_info may contain vfio_region_info_cap_sparse_mmap
@@ -513,23 +522,22 @@ impl VfioDeviceInfo {
/// associated with a specific VFIO container. /// associated with a specific VFIO container.
pub struct VfioDmaMapping { pub struct VfioDmaMapping {
container: Arc<VfioContainer>, container: Arc<VfioContainer>,
memory: Arc<RwLock<GuestMemoryMmap>>, memory: Arc<ArcSwap<GuestMemoryMmap>>,
} }
impl VfioDmaMapping { impl VfioDmaMapping {
pub fn new(container: Arc<VfioContainer>, memory: Arc<RwLock<GuestMemoryMmap>>) -> Self { pub fn new(container: Arc<VfioContainer>, memory: Arc<ArcSwap<GuestMemoryMmap>>) -> Self {
VfioDmaMapping { container, memory } VfioDmaMapping { container, memory }
} }
} }
impl ExternalDmaMapping for VfioDmaMapping { impl ExternalDmaMapping for VfioDmaMapping {
fn map(&self, iova: u64, gpa: u64, size: u64) -> result::Result<(), io::Error> { fn map(&self, iova: u64, gpa: u64, size: u64) -> result::Result<(), io::Error> {
let user_addr = if let Some(addr) = self let user_addr = if let Some(addr) = get_host_address_range(
.memory &self.memory.load(),
.read() GuestAddress(gpa),
.unwrap() size.try_into().unwrap(),
.get_host_address(GuestAddress(gpa)) ) {
{
addr as u64 addr as u64
} else { } else {
return Err(io::Error::new( return Err(io::Error::new(
@@ -577,7 +585,7 @@ pub struct VfioDevice {
group: VfioGroup, group: VfioGroup,
regions: Vec<VfioRegion>, regions: Vec<VfioRegion>,
irqs: HashMap<u32, VfioIrq>, irqs: HashMap<u32, VfioIrq>,
mem: Arc<RwLock<GuestMemoryMmap>>, mem: Arc<ArcSwap<GuestMemoryMmap>>,
iommu_attached: bool, iommu_attached: bool,
} }
@@ -588,16 +596,14 @@ impl VfioDevice {
pub fn new( pub fn new(
sysfspath: &Path, sysfspath: &Path,
device_fd: Arc<DeviceFd>, device_fd: Arc<DeviceFd>,
mem: Arc<RwLock<GuestMemoryMmap>>, mem: Arc<ArcSwap<GuestMemoryMmap>>,
iommu_attached: bool, iommu_attached: bool,
) -> Result<Self> { ) -> Result<Self> {
let uuid_path: PathBuf = [sysfspath, Path::new("iommu_group")].iter().collect(); let uuid_path: PathBuf = [sysfspath, Path::new("iommu_group")].iter().collect();
let group_path = uuid_path.read_link().map_err(|_| VfioError::InvalidPath)?; let group_path = uuid_path.read_link().map_err(VfioError::ReadLink)?;
let group_osstr = group_path.file_name().ok_or(VfioError::InvalidPath)?; let group_osstr = group_path.file_name().ok_or(VfioError::InvalidPath)?;
let group_str = group_osstr.to_str().ok_or(VfioError::InvalidPath)?; let group_str = group_osstr.to_str().ok_or(VfioError::InvalidPath)?;
let group_id = group_str let group_id = group_str.parse::<u32>().map_err(VfioError::ParseInt)?;
.parse::<u32>()
.map_err(|_| VfioError::InvalidPath)?;
let group = VfioGroup::new(group_id, device_fd)?; let group = VfioGroup::new(group_id, device_fd)?;
let device_info = group.get_device(sysfspath)?; let device_info = group.get_device(sysfspath)?;
@@ -641,34 +647,25 @@ impl VfioDevice {
return Err(VfioError::VfioDeviceSetIrq); return Err(VfioError::VfioDeviceSetIrq);
} }
let mut irq_set = vec_with_array_field::<vfio_irq_set, u32>(event_fds.len()); let mut irq_set_wrapper = IrqSet::new(event_fds.len() * mem::size_of::<u32>());
irq_set[0].argsz = mem::size_of::<vfio_irq_set>() as u32 let mut irq_set = irq_set_wrapper.as_mut_fam_struct();
+ (event_fds.len() * mem::size_of::<u32>()) as u32; let fds = irq_set.as_mut_slice();
irq_set[0].flags = VFIO_IRQ_SET_DATA_EVENTFD | VFIO_IRQ_SET_ACTION_TRIGGER;
irq_set[0].index = irq_index;
irq_set[0].start = 0;
irq_set[0].count = irq.count;
{ for (index, event_fd) in event_fds.iter().enumerate() {
// irq_set.data could be none, bool or fd according to flags, so irq_set.data let fds_offset = index * mem::size_of::<u32>();
// is u8 default, here irq_set.data is a vector of fds as u32, so 4 default u8 let fd = &mut fds[fds_offset..fds_offset + mem::size_of::<u32>()];
// are combined together as u32 for each fd. LittleEndian::write_u32(fd, event_fd.as_raw_fd() as u32);
// It is safe as enough space is reserved through
// vec_with_array_field(u32)<event_fds.len()>.
let fds = unsafe {
irq_set[0]
.data
.as_mut_slice(event_fds.len() * mem::size_of::<u32>())
};
for (index, event_fd) in event_fds.iter().enumerate() {
let fds_offset = index * mem::size_of::<u32>();
let fd = &mut fds[fds_offset..fds_offset + mem::size_of::<u32>()];
LittleEndian::write_u32(fd, event_fd.as_raw_fd() as u32);
}
} }
irq_set.argsz = mem::size_of::<vfio_irq_set>() as u32
+ (event_fds.len() * mem::size_of::<u32>()) as u32;
irq_set.flags = VFIO_IRQ_SET_DATA_EVENTFD | VFIO_IRQ_SET_ACTION_TRIGGER;
irq_set.index = irq_index;
irq_set.start = 0;
irq_set.count = irq.count;
// Safe as we are the owner of self and irq_set which are valid value // Safe as we are the owner of self and irq_set which are valid value
let ret = unsafe { ioctl_with_ref(self, VFIO_DEVICE_SET_IRQS(), &irq_set[0]) }; let ret = unsafe { ioctl_with_ref(self, VFIO_DEVICE_SET_IRQS(), irq_set) };
if ret < 0 { if ret < 0 {
return Err(VfioError::VfioDeviceSetIrq); return Err(VfioError::VfioDeviceSetIrq);
} }
@@ -692,10 +689,10 @@ impl VfioDevice {
let mut irq_set = vec_with_array_field::<vfio_irq_set, u32>(0); let mut irq_set = vec_with_array_field::<vfio_irq_set, u32>(0);
irq_set[0].argsz = mem::size_of::<vfio_irq_set>() as u32; irq_set[0].argsz = mem::size_of::<vfio_irq_set>() as u32;
irq_set[0].flags = VFIO_IRQ_SET_ACTION_MASK; irq_set[0].flags = VFIO_IRQ_SET_ACTION_TRIGGER | VFIO_IRQ_SET_DATA_NONE;
irq_set[0].index = irq_index; irq_set[0].index = irq_index;
irq_set[0].start = 0; irq_set[0].start = 0;
irq_set[0].count = irq.count; irq_set[0].count = 0;
// Safe as we are the owner of self and irq_set which are valid value // Safe as we are the owner of self and irq_set which are valid value
let ret = unsafe { ioctl_with_ref(self, VFIO_DEVICE_SET_IRQS(), &irq_set[0]) }; let ret = unsafe { ioctl_with_ref(self, VFIO_DEVICE_SET_IRQS(), &irq_set[0]) };
@@ -845,7 +842,7 @@ impl VfioDevice {
/// then vfio kernel driver could access guest memory from gfn /// then vfio kernel driver could access guest memory from gfn
pub fn setup_dma_map(&self) -> Result<()> { pub fn setup_dma_map(&self) -> Result<()> {
if !self.iommu_attached { if !self.iommu_attached {
self.mem.read().unwrap().with_regions(|_index, region| { self.mem.load().with_regions(|_index, region| {
self.vfio_dma_map( self.vfio_dma_map(
region.start_addr().raw_value(), region.start_addr().raw_value(),
region.len() as u64, region.len() as u64,
@@ -860,7 +857,7 @@ impl VfioDevice {
/// then vfio kernel driver couldn't access this guest memory /// then vfio kernel driver couldn't access this guest memory
pub fn unset_dma_map(&self) -> Result<()> { pub fn unset_dma_map(&self) -> Result<()> {
if !self.iommu_attached { if !self.iommu_attached {
self.mem.read().unwrap().with_regions(|_index, region| { self.mem.load().with_regions(|_index, region| {
self.vfio_dma_unmap(region.start_addr().raw_value(), region.len() as u64) self.vfio_dma_unmap(region.start_addr().raw_value(), region.len() as u64)
})?; })?;
} }

View File

@@ -7,25 +7,26 @@ extern crate devices;
extern crate pci; extern crate pci;
extern crate vm_allocator; extern crate vm_allocator;
use crate::vec_with_array_field;
use crate::vfio_device::VfioDevice; use crate::vfio_device::VfioDevice;
use byteorder::{ByteOrder, LittleEndian}; use byteorder::{ByteOrder, LittleEndian};
use devices::BusDevice; use devices::BusDevice;
use kvm_bindings::{ use kvm_bindings::kvm_userspace_memory_region;
kvm_irq_routing, kvm_irq_routing_entry, kvm_userspace_memory_region, KVM_IRQ_ROUTING_MSI,
};
use kvm_ioctls::*; use kvm_ioctls::*;
use pci::{ use pci::{
MsiCap, MsixCap, MsixConfig, PciBarConfiguration, PciBarRegionType, PciCapabilityID, msi_num_enabled_vectors, BarReprogrammingParams, MsiConfig, MsixCap, MsixConfig,
PciClassCode, PciConfiguration, PciDevice, PciDeviceError, PciHeaderType, PciSubclass, PciBarConfiguration, PciBarRegionType, PciCapabilityID, PciClassCode, PciConfiguration,
MSIX_TABLE_ENTRY_SIZE, PciDevice, PciDeviceError, PciHeaderType, PciSubclass, MSIX_TABLE_ENTRY_SIZE,
}; };
use std::any::Any;
use std::os::unix::io::AsRawFd; use std::os::unix::io::AsRawFd;
use std::ptr::null_mut; use std::ptr::null_mut;
use std::sync::Arc; use std::sync::Arc;
use std::{fmt, io}; use std::{fmt, io, result};
use vfio_bindings::bindings::vfio::*; use vfio_bindings::bindings::vfio::*;
use vm_allocator::SystemAllocator; use vm_allocator::SystemAllocator;
use vm_device::interrupt::{
InterruptIndex, InterruptManager, InterruptSourceGroup, MsiIrqGroupConfig,
};
use vm_memory::{Address, GuestAddress, GuestUsize}; use vm_memory::{Address, GuestAddress, GuestUsize};
use vmm_sys_util::eventfd::EventFd; use vmm_sys_util::eventfd::EventFd;
@@ -33,10 +34,15 @@ use vmm_sys_util::eventfd::EventFd;
pub enum VfioPciError { pub enum VfioPciError {
AllocateGsi, AllocateGsi,
EventFd(io::Error), EventFd(io::Error),
IrqFd(io::Error), InterruptSourceGroupCreate(io::Error),
IrqFd(kvm_ioctls::Error),
NewVfioPciDevice, NewVfioPciDevice,
MapRegionGuest(io::Error), MapRegionGuest(kvm_ioctls::Error),
SetGsiRouting(io::Error), SetGsiRouting(kvm_ioctls::Error),
MsiNotConfigured,
MsixNotConfigured,
UpdateMsiEventFd,
UpdateMsixEventFd,
} }
pub type Result<T> = std::result::Result<T, VfioPciError>; pub type Result<T> = std::result::Result<T, VfioPciError>;
@@ -45,12 +51,19 @@ impl fmt::Display for VfioPciError {
match self { match self {
VfioPciError::AllocateGsi => write!(f, "failed to allocate GSI"), VfioPciError::AllocateGsi => write!(f, "failed to allocate GSI"),
VfioPciError::EventFd(e) => write!(f, "failed to create eventfd: {}", e), VfioPciError::EventFd(e) => write!(f, "failed to create eventfd: {}", e),
VfioPciError::InterruptSourceGroupCreate(e) => {
write!(f, "failed to create interrupt source group: {}", e)
}
VfioPciError::IrqFd(e) => write!(f, "failed to register irqfd: {}", e), VfioPciError::IrqFd(e) => write!(f, "failed to register irqfd: {}", e),
VfioPciError::NewVfioPciDevice => write!(f, "failed to create VFIO PCI device"), VfioPciError::NewVfioPciDevice => write!(f, "failed to create VFIO PCI device"),
VfioPciError::MapRegionGuest(e) => { VfioPciError::MapRegionGuest(e) => {
write!(f, "failed to map VFIO PCI region into guest: {}", e) write!(f, "failed to map VFIO PCI region into guest: {}", e)
} }
VfioPciError::SetGsiRouting(e) => write!(f, "failed to set GSI routes for KVM: {}", e), VfioPciError::SetGsiRouting(e) => write!(f, "failed to set GSI routes for KVM: {}", e),
VfioPciError::MsiNotConfigured => write!(f, "MSI interrupt not yet configured"),
VfioPciError::MsixNotConfigured => write!(f, "MSI-X interrupt not yet configured"),
VfioPciError::UpdateMsiEventFd => write!(f, "failed to update MSI eventfd"),
VfioPciError::UpdateMsixEventFd => write!(f, "failed to update MSI-X eventfd"),
} }
} }
} }
@@ -73,19 +86,19 @@ enum InterruptUpdateAction {
DisableMsix, DisableMsix,
} }
#[derive(Copy, Clone)]
struct VfioMsi { struct VfioMsi {
cap: MsiCap, cfg: MsiConfig,
cap_offset: u32, cap_offset: u32,
interrupt_source_group: Arc<Box<dyn InterruptSourceGroup>>,
} }
impl VfioMsi { impl VfioMsi {
fn update(&mut self, offset: u64, data: &[u8]) -> Option<InterruptUpdateAction> { fn update(&mut self, offset: u64, data: &[u8]) -> Option<InterruptUpdateAction> {
let old_enabled = self.cap.enabled(); let old_enabled = self.cfg.enabled();
self.cap.update(offset, data); self.cfg.update(offset, data);
let new_enabled = self.cap.enabled(); let new_enabled = self.cfg.enabled();
if !old_enabled && new_enabled { if !old_enabled && new_enabled {
return Some(InterruptUpdateAction::EnableMsi); return Some(InterruptUpdateAction::EnableMsi);
@@ -103,18 +116,19 @@ struct VfioMsix {
bar: MsixConfig, bar: MsixConfig,
cap: MsixCap, cap: MsixCap,
cap_offset: u32, cap_offset: u32,
interrupt_source_group: Arc<Box<dyn InterruptSourceGroup>>,
} }
impl VfioMsix { impl VfioMsix {
fn update(&mut self, offset: u64, data: &[u8]) -> Option<InterruptUpdateAction> { fn update(&mut self, offset: u64, data: &[u8]) -> Option<InterruptUpdateAction> {
let old_enabled = self.cap.enabled(); let old_enabled = self.bar.enabled();
// Update "Message Control" word // Update "Message Control" word
if offset == 2 && data.len() == 2 { if offset == 2 && data.len() == 2 {
self.cap.set_msg_ctl(LittleEndian::read_u16(data)); self.bar.set_msg_ctl(LittleEndian::read_u16(data));
} }
let new_enabled = self.cap.enabled(); let new_enabled = self.bar.enabled();
if !old_enabled && new_enabled { if !old_enabled && new_enabled {
return Some(InterruptUpdateAction::EnableMsix); return Some(InterruptUpdateAction::EnableMsix);
@@ -163,7 +177,7 @@ impl Interrupt {
fn accessed(&self, offset: u64) -> Option<(PciCapabilityID, u64)> { fn accessed(&self, offset: u64) -> Option<(PciCapabilityID, u64)> {
if let Some(msi) = &self.msi { if let Some(msi) = &self.msi {
if offset >= u64::from(msi.cap_offset) if offset >= u64::from(msi.cap_offset)
&& offset < u64::from(msi.cap_offset) + msi.cap.size() && offset < u64::from(msi.cap_offset) + msi.cfg.size()
{ {
return Some(( return Some((
PciCapabilityID::MessageSignalledInterrupts, PciCapabilityID::MessageSignalledInterrupts,
@@ -181,22 +195,6 @@ impl Interrupt {
None None
} }
fn msix_enabled(&self) -> bool {
if let Some(msix) = &self.msix {
return msix.cap.enabled();
}
false
}
fn msix_function_masked(&self) -> bool {
if let Some(msix) = &self.msix {
return msix.cap.masked();
}
false
}
fn msix_table_accessed(&self, bar_index: u32, offset: u64) -> bool { fn msix_table_accessed(&self, bar_index: u32, offset: u64) -> bool {
if let Some(msix) = &self.msix { if let Some(msix) = &self.msix {
return msix.table_accessed(bar_index, offset); return msix.table_accessed(bar_index, offset);
@@ -220,41 +218,14 @@ impl Interrupt {
} }
} }
#[derive(Copy, Clone, Default)]
struct MsiVector {
msg_addr_lo: u32,
msg_addr_hi: u32,
msg_data: u32,
masked: bool,
}
struct InterruptRoute {
gsi: u32,
irq_fd: EventFd,
msi_vector: MsiVector,
}
impl InterruptRoute {
fn new(vm: &Arc<VmFd>, allocator: &mut SystemAllocator, msi_vector: MsiVector) -> Result<Self> {
let irq_fd = EventFd::new(libc::EFD_NONBLOCK).map_err(VfioPciError::EventFd)?;
let gsi = allocator.allocate_gsi().ok_or(VfioPciError::AllocateGsi)?;
vm.register_irqfd(irq_fd.as_raw_fd(), gsi)
.map_err(VfioPciError::IrqFd)?;
Ok(InterruptRoute {
gsi,
irq_fd,
msi_vector,
})
}
}
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
struct MmioRegion { struct MmioRegion {
start: GuestAddress, start: GuestAddress,
length: GuestUsize, length: GuestUsize,
index: u32, index: u32,
mem_slot: Option<u32>,
host_addr: Option<u64>,
mmap_size: Option<usize>,
} }
struct VfioPciConfig { struct VfioPciConfig {
@@ -310,15 +281,14 @@ pub struct VfioPciDevice {
configuration: PciConfiguration, configuration: PciConfiguration,
mmio_regions: Vec<MmioRegion>, mmio_regions: Vec<MmioRegion>,
interrupt: Interrupt, interrupt: Interrupt,
interrupt_routes: Vec<InterruptRoute>,
} }
impl VfioPciDevice { impl VfioPciDevice {
/// Constructs a new Vfio Pci device for the given Vfio device /// Constructs a new Vfio Pci device for the given Vfio device
pub fn new( pub fn new(
vm_fd: &Arc<VmFd>, vm_fd: &Arc<VmFd>,
allocator: &mut SystemAllocator,
device: VfioDevice, device: VfioDevice,
interrupt_manager: &Arc<dyn InterruptManager<GroupConfig = MsiIrqGroupConfig>>,
) -> Result<Self> { ) -> Result<Self> {
let device = Arc::new(device); let device = Arc::new(device);
device.reset(); device.reset();
@@ -347,71 +317,18 @@ impl VfioPciDevice {
msi: None, msi: None,
msix: None, msix: None,
}, },
interrupt_routes: Vec::new(),
}; };
vfio_pci_device.parse_capabilities(); vfio_pci_device.parse_capabilities(interrupt_manager);
// Allocate temporary interrupt routes for now.
// The MSI vectors will be filled when the guest driver programs the device.
let max_interrupts = vfio_pci_device.device.max_interrupts();
for _ in 0..max_interrupts {
let msi_vector: MsiVector = Default::default();
let route = InterruptRoute::new(vm_fd, allocator, msi_vector)?;
vfio_pci_device.interrupt_routes.push(route);
}
Ok(vfio_pci_device) Ok(vfio_pci_device)
} }
fn irq_fds(&self) -> Result<Vec<&EventFd>> { fn parse_msix_capabilities(
let mut irq_fds: Vec<&EventFd> = Vec::new(); &mut self,
cap: u8,
for r in &self.interrupt_routes { interrupt_manager: &Arc<dyn InterruptManager<GroupConfig = MsiIrqGroupConfig>>,
irq_fds.push(&r.irq_fd); ) {
}
Ok(irq_fds)
}
fn set_kvm_routes(&self) -> Result<()> {
let mut entry_vec: Vec<kvm_irq_routing_entry> = Vec::new();
for route in self.interrupt_routes.iter() {
// Do not add masked vectors to the GSI mapping
if route.msi_vector.masked {
continue;
}
let mut entry = kvm_irq_routing_entry {
gsi: route.gsi,
type_: KVM_IRQ_ROUTING_MSI,
..Default::default()
};
entry.u.msi.address_lo = route.msi_vector.msg_addr_lo;
entry.u.msi.address_hi = route.msi_vector.msg_addr_hi;
entry.u.msi.data = route.msi_vector.msg_data;
entry_vec.push(entry);
}
let mut irq_routing =
vec_with_array_field::<kvm_irq_routing, kvm_irq_routing_entry>(entry_vec.len());
irq_routing[0].nr = entry_vec.len() as u32;
irq_routing[0].flags = 0;
unsafe {
let entries: &mut [kvm_irq_routing_entry] =
irq_routing[0].entries.as_mut_slice(entry_vec.len());
entries.copy_from_slice(&entry_vec);
}
self.vm_fd
.set_gsi_routing(&irq_routing[0])
.map_err(VfioPciError::SetGsiRouting)
}
fn parse_msix_capabilities(&mut self, cap: u8) {
let msg_ctl = self let msg_ctl = self
.vfio_pci_configuration .vfio_pci_configuration
.read_config_word((cap + 2).into()); .read_config_word((cap + 2).into());
@@ -429,30 +346,53 @@ impl VfioPciDevice {
table, table,
pba, pba,
}; };
let msix_config = MsixConfig::new(msix_cap.table_size());
let interrupt_source_group = interrupt_manager
.create_group(MsiIrqGroupConfig {
base: 0,
count: msix_cap.table_size() as InterruptIndex,
})
.unwrap();
let msix_config = MsixConfig::new(msix_cap.table_size(), interrupt_source_group.clone());
self.interrupt.msix = Some(VfioMsix { self.interrupt.msix = Some(VfioMsix {
bar: msix_config, bar: msix_config,
cap: msix_cap, cap: msix_cap,
cap_offset: cap.into(), cap_offset: cap.into(),
interrupt_source_group,
}); });
} }
fn parse_msi_capabilities(&mut self, cap: u8) { fn parse_msi_capabilities(
&mut self,
cap: u8,
interrupt_manager: &Arc<dyn InterruptManager<GroupConfig = MsiIrqGroupConfig>>,
) {
let msg_ctl = self let msg_ctl = self
.vfio_pci_configuration .vfio_pci_configuration
.read_config_word((cap + 2).into()); .read_config_word((cap + 2).into());
let interrupt_source_group = interrupt_manager
.create_group(MsiIrqGroupConfig {
base: 0,
count: msi_num_enabled_vectors(msg_ctl) as InterruptIndex,
})
.unwrap();
let msi_config = MsiConfig::new(msg_ctl, interrupt_source_group.clone());
self.interrupt.msi = Some(VfioMsi { self.interrupt.msi = Some(VfioMsi {
cap: MsiCap { cfg: msi_config,
msg_ctl,
..Default::default()
},
cap_offset: cap.into(), cap_offset: cap.into(),
interrupt_source_group,
}); });
} }
fn parse_capabilities(&mut self) { fn parse_capabilities(
&mut self,
interrupt_manager: &Arc<dyn InterruptManager<GroupConfig = MsiIrqGroupConfig>>,
) {
let mut cap_next = self let mut cap_next = self
.vfio_pci_configuration .vfio_pci_configuration
.read_config_byte(PCI_CONFIG_CAPABILITY_OFFSET); .read_config_byte(PCI_CONFIG_CAPABILITY_OFFSET);
@@ -464,10 +404,10 @@ impl VfioPciDevice {
match PciCapabilityID::from(cap_id) { match PciCapabilityID::from(cap_id) {
PciCapabilityID::MessageSignalledInterrupts => { PciCapabilityID::MessageSignalledInterrupts => {
self.parse_msi_capabilities(cap_next); self.parse_msi_capabilities(cap_next, interrupt_manager);
} }
PciCapabilityID::MSIX => { PciCapabilityID::MSIX => {
self.parse_msix_capabilities(cap_next); self.parse_msix_capabilities(cap_next, interrupt_manager);
} }
_ => {} _ => {}
}; };
@@ -478,67 +418,26 @@ impl VfioPciDevice {
} }
} }
fn update_msi_interrupt_routes(&mut self, msi: &VfioMsi) -> Result<()> {
let num_vectors = msi.cap.num_enabled_vectors();
for (idx, route) in self.interrupt_routes.iter_mut().enumerate() {
// Mask the MSI vector if the amount of vectors supported by the
// guest OS does not match the expected amount. This is related
// to "Multiple Message Capable" and "Multiple Message Enable"
// fields from the "Message Control" register.
if idx >= num_vectors {
route.msi_vector.masked = true;
continue;
}
route.msi_vector.msg_addr_lo = msi.cap.msg_addr_lo;
route.msi_vector.msg_addr_hi = msi.cap.msg_addr_hi;
route.msi_vector.msg_data = u32::from(msi.cap.msg_data) | (idx as u32);
route.msi_vector.masked = msi.cap.vector_masked(idx);
}
// Check if we need to update KVM GSI mapping, based on the status of
// the "MSI Enable" bit.
if msi.cap.enabled() {
return self.set_kvm_routes();
}
Ok(())
}
fn read_msix_table(&mut self, offset: u64, data: &mut [u8]) {
self.interrupt.msix_read_table(offset, data);
}
fn update_msix_table(&mut self, offset: u64, data: &[u8]) -> Result<()> {
self.interrupt.msix_write_table(offset, data);
if self.interrupt.msix_enabled() && !self.interrupt.msix_function_masked() {
// Fill tables
if let Some(msix) = &self.interrupt.msix {
for (idx, entry) in msix.bar.table_entries.iter().enumerate() {
self.interrupt_routes[idx].msi_vector.msg_addr_lo = entry.msg_addr_lo;
self.interrupt_routes[idx].msi_vector.msg_addr_hi = entry.msg_addr_hi;
self.interrupt_routes[idx].msi_vector.msg_data = entry.msg_data;
self.interrupt_routes[idx].msi_vector.masked = entry.masked();
}
}
return self.set_kvm_routes();
}
Ok(())
}
fn update_msi_capabilities(&mut self, offset: u64, data: &[u8]) -> Result<()> { fn update_msi_capabilities(&mut self, offset: u64, data: &[u8]) -> Result<()> {
match self.interrupt.update_msi(offset, data) { match self.interrupt.update_msi(offset, data) {
Some(InterruptUpdateAction::EnableMsi) => match self.irq_fds() { Some(InterruptUpdateAction::EnableMsi) => {
Ok(fds) => { if let Some(msi) = &self.interrupt.msi {
if let Err(e) = self.device.enable_msi(fds) { let mut irq_fds: Vec<&EventFd> = Vec::new();
for i in 0..msi.cfg.num_enabled_vectors() {
if let Some(eventfd) =
msi.interrupt_source_group.notifier(i as InterruptIndex)
{
irq_fds.push(eventfd);
} else {
return Err(VfioPciError::UpdateMsiEventFd);
}
}
if let Err(e) = self.device.enable_msi(irq_fds) {
warn!("Could not enable MSI: {}", e); warn!("Could not enable MSI: {}", e);
} }
} }
Err(e) => warn!("Could not get IRQ fds: {}", e), }
},
Some(InterruptUpdateAction::DisableMsi) => { Some(InterruptUpdateAction::DisableMsi) => {
if let Err(e) = self.device.disable_msi() { if let Err(e) = self.device.disable_msi() {
warn!("Could not disable MSI: {}", e); warn!("Could not disable MSI: {}", e);
@@ -547,34 +446,38 @@ impl VfioPciDevice {
_ => {} _ => {}
} }
// Update the interrupt_routes table now that the MSI cache has been
// updated. The point is to always update the table based on latest
// changes to the cache, and based on the state of masking flags, the
// KVM GSI routes should be configured.
if let Some(msi) = self.interrupt.msi {
return self.update_msi_interrupt_routes(&msi);
}
Ok(()) Ok(())
} }
fn update_msix_capabilities(&mut self, offset: u64, data: &[u8]) { fn update_msix_capabilities(&mut self, offset: u64, data: &[u8]) -> Result<()> {
match self.interrupt.update_msix(offset, data) { match self.interrupt.update_msix(offset, data) {
Some(InterruptUpdateAction::EnableMsix) => match self.irq_fds() { Some(InterruptUpdateAction::EnableMsix) => {
Ok(fds) => { if let Some(msix) = &self.interrupt.msix {
if let Err(e) = self.device.enable_msix(fds) { let mut irq_fds: Vec<&EventFd> = Vec::new();
for i in 0..msix.bar.table_entries.len() {
if let Some(eventfd) =
msix.interrupt_source_group.notifier(i as InterruptIndex)
{
irq_fds.push(eventfd);
} else {
return Err(VfioPciError::UpdateMsiEventFd);
}
}
if let Err(e) = self.device.enable_msix(irq_fds) {
warn!("Could not enable MSI-X: {}", e); warn!("Could not enable MSI-X: {}", e);
} }
} }
Err(e) => warn!("Could not get IRQ fds: {}", e), }
},
Some(InterruptUpdateAction::DisableMsix) => { Some(InterruptUpdateAction::DisableMsix) => {
if let Err(e) = self.device.disable_msix() { if let Err(e) = self.device.disable_msix() {
warn!("Could not disable MSI: {}", e); warn!("Could not disable MSI-X: {}", e);
} }
} }
_ => {} _ => {}
} }
Ok(())
} }
fn find_region(&self, addr: u64) -> Option<MmioRegion> { fn find_region(&self, addr: u64) -> Option<MmioRegion> {
@@ -603,7 +506,7 @@ impl VfioPciDevice {
let fd = self.device.as_raw_fd(); let fd = self.device.as_raw_fd();
let mut new_mem_slot = mem_slot; let mut new_mem_slot = mem_slot;
for region in self.mmio_regions.iter() { for region in self.mmio_regions.iter_mut() {
// We want to skip the mapping of the BAR containing the MSI-X // We want to skip the mapping of the BAR containing the MSI-X
// table even if it is mappable. The reason is we need to trap // table even if it is mappable. The reason is we need to trap
// any access to the MSI-X table and update the GSI routing // any access to the MSI-X table and update the GSI routing
@@ -658,22 +561,48 @@ impl VfioPciDevice {
vm.set_user_memory_region(mem_region) vm.set_user_memory_region(mem_region)
.map_err(VfioPciError::MapRegionGuest)?; .map_err(VfioPciError::MapRegionGuest)?;
} }
// Update the region with memory mapped info.
region.mem_slot = Some(new_mem_slot);
region.host_addr = Some(host_addr as u64);
region.mmap_size = Some(mmap_size as usize);
new_mem_slot += 1; new_mem_slot += 1;
} }
} }
Ok(new_mem_slot) Ok(new_mem_slot)
} }
pub fn unmap_mmio_regions(&mut self) {
for region in self.mmio_regions.iter() {
if let (Some(addr), Some(size)) = (region.host_addr, region.mmap_size) {
let ret = unsafe { libc::munmap(addr as *mut libc::c_void, size) };
if ret != 0 {
error!(
"Could not unmap regions, error:{}",
io::Error::last_os_error()
);
}
}
}
}
} }
impl Drop for VfioPciDevice { impl Drop for VfioPciDevice {
fn drop(&mut self) { fn drop(&mut self) {
if self.interrupt.msi.is_some() && self.device.disable_msi().is_err() { self.unmap_mmio_regions();
error!("Could not disable MSI");
if let Some(msix) = &self.interrupt.msix {
if msix.bar.enabled() && self.device.disable_msix().is_err() {
error!("Could not disable MSI-X");
}
} }
if self.interrupt.msix.is_some() && self.device.disable_msix().is_err() { if let Some(msi) = &self.interrupt.msi {
error!("Could not disable MSI-X"); if msi.cfg.enabled() && self.device.disable_msi().is_err() {
error!("Could not disable MSI");
}
} }
if self.device.unset_dma_map().is_err() { if self.device.unset_dma_map().is_err() {
@@ -800,7 +729,7 @@ impl PciDevice for VfioPciDevice {
let msb_bar_offset: u32 = PCI_CONFIG_BAR_OFFSET + (bar_id + 1) * 4; let msb_bar_offset: u32 = PCI_CONFIG_BAR_OFFSET + (bar_id + 1) * 4;
self.vfio_pci_configuration self.vfio_pci_configuration
.write_config_dword(msb_bar_offset, msb_size); .write_config_dword(msb_size, msb_bar_offset);
msb_size = self msb_size = self
.vfio_pci_configuration .vfio_pci_configuration
@@ -870,6 +799,9 @@ impl PciDevice for VfioPciDevice {
start: bar_addr, start: bar_addr,
length: region_size, length: region_size,
index: bar_id as u32, index: bar_id as u32,
mem_slot: None,
host_addr: None,
mmap_size: None,
}); });
bar_id += 1; bar_id += 1;
@@ -901,8 +833,6 @@ impl PciDevice for VfioPciDevice {
} }
let reg = (reg_idx * PCI_CONFIG_REGISTER_SIZE) as u64; let reg = (reg_idx * PCI_CONFIG_REGISTER_SIZE) as u64;
self.device
.region_write(VFIO_PCI_CONFIG_REGION_INDEX, data, reg + offset);
// If the MSI or MSI-X capabilities are accessed, we need to // If the MSI or MSI-X capabilities are accessed, we need to
// update our local cache accordingly. // update our local cache accordingly.
@@ -917,14 +847,27 @@ impl PciDevice for VfioPciDevice {
} }
} }
PciCapabilityID::MSIX => { PciCapabilityID::MSIX => {
self.update_msix_capabilities(cap_offset, data); if let Err(e) = self.update_msix_capabilities(cap_offset, data) {
error!("Could not update MSI-X capabilities: {}", e);
}
} }
_ => {} _ => {}
} }
} }
// Make sure to write to the device's PCI config space after MSI/MSI-X
// interrupts have been enabled/disabled. In case of MSI, when the
// interrupts are enabled through VFIO (using VFIO_DEVICE_SET_IRQS),
// the MSI Enable bit in the MSI capability structure found in the PCI
// config space is disabled by default. That's why when the guest is
// enabling this bit, we first need to enable the MSI interrupts with
// VFIO through VFIO_DEVICE_SET_IRQS ioctl, and only after we can write
// to the device region to update the MSI Enable bit.
self.device
.region_write(VFIO_PCI_CONFIG_REGION_INDEX, data, reg + offset);
} }
fn read_config_register(&self, reg_idx: usize) -> u32 { fn read_config_register(&mut self, reg_idx: usize) -> u32 {
// When reading the BARs, we trap it and return what comes // When reading the BARs, we trap it and return what comes
// from our local configuration space. We want the guest to // from our local configuration space. We want the guest to
// use that and not the VFIO device BARs as it does not map // use that and not the VFIO device BARs as it does not map
@@ -958,13 +901,21 @@ impl PciDevice for VfioPciDevice {
& mask & mask
} }
fn detect_bar_reprogramming(
&mut self,
reg_idx: usize,
data: &[u8],
) -> Option<BarReprogrammingParams> {
self.configuration.detect_bar_reprogramming(reg_idx, data)
}
fn read_bar(&mut self, base: u64, offset: u64, data: &mut [u8]) { fn read_bar(&mut self, base: u64, offset: u64, data: &mut [u8]) {
let addr = base + offset; let addr = base + offset;
if let Some(region) = self.find_region(addr) { if let Some(region) = self.find_region(addr) {
let offset = addr - region.start.raw_value(); let offset = addr - region.start.raw_value();
if self.interrupt.msix_table_accessed(region.index, offset) { if self.interrupt.msix_table_accessed(region.index, offset) {
self.read_msix_table(offset, data); self.interrupt.msix_read_table(offset, data);
} else { } else {
self.device.region_read(region.index, data, offset); self.device.region_read(region.index, data, offset);
} }
@@ -978,12 +929,60 @@ impl PciDevice for VfioPciDevice {
// If the MSI-X table is written to, we need to update our cache. // If the MSI-X table is written to, we need to update our cache.
if self.interrupt.msix_table_accessed(region.index, offset) { if self.interrupt.msix_table_accessed(region.index, offset) {
if let Err(e) = self.update_msix_table(offset, data) { self.interrupt.msix_write_table(offset, data);
error!("Could not update MSI-X table: {}", e);
}
} else { } else {
self.device.region_write(region.index, data, offset); self.device.region_write(region.index, data, offset);
} }
} }
} }
fn move_bar(&mut self, old_base: u64, new_base: u64) -> result::Result<(), io::Error> {
for region in self.mmio_regions.iter_mut() {
if region.start.raw_value() == old_base {
region.start = GuestAddress(new_base);
if let Some(mem_slot) = region.mem_slot {
if let Some(host_addr) = region.host_addr {
let (mmap_offset, mmap_size) = self.device.get_region_mmap(region.index);
// Remove old region from KVM
let old_mem_region = kvm_userspace_memory_region {
slot: mem_slot,
guest_phys_addr: old_base + mmap_offset,
memory_size: 0,
userspace_addr: host_addr,
flags: 0,
};
// Safe because the guest regions are guaranteed not to overlap.
unsafe {
self.vm_fd
.set_user_memory_region(old_mem_region)
.map_err(|e| io::Error::from_raw_os_error(e.errno()))?;
}
// Insert new region to KVM
let new_mem_region = kvm_userspace_memory_region {
slot: mem_slot,
guest_phys_addr: new_base + mmap_offset,
memory_size: mmap_size as u64,
userspace_addr: host_addr,
flags: 0,
};
// Safe because the guest regions are guaranteed not to overlap.
unsafe {
self.vm_fd
.set_user_memory_region(new_mem_region)
.map_err(|e| io::Error::from_raw_os_error(e.errno()))?;
}
}
}
}
}
Ok(())
}
fn as_any(&mut self) -> &mut dyn Any {
self
}
} }

View File

@@ -13,9 +13,9 @@ vhost-user-master = []
vhost-user-slave = [] vhost-user-slave = []
[dependencies] [dependencies]
bitflags = "1.1.0" bitflags = "1.2.1"
libc = "0.2.60" libc = "0.2.66"
vmm-sys-util = "0.1.1" vmm-sys-util = "0.4.0"
[dependencies.vm-memory] [dependencies.vm-memory]
git = "https://github.com/rust-vmm/vm-memory" git = "https://github.com/rust-vmm/vm-memory"

View File

@@ -489,7 +489,6 @@ mod tests {
use self::tempfile::tempfile; use self::tempfile::tempfile;
use super::*; use super::*;
use libc;
use std::fs::File; use std::fs::File;
use std::io::{Read, Seek, SeekFrom, Write}; use std::io::{Read, Seek, SeekFrom, Write};
use std::os::unix::io::FromRawFd; use std::os::unix::io::FromRawFd;
@@ -513,18 +512,10 @@ mod tests {
// accept on a fd without incoming connection // accept on a fd without incoming connection
let conn = listener.accept().unwrap(); let conn = listener.accept().unwrap();
assert!(conn.is_none()); assert!(conn.is_none());
listener.set_nonblocking(true).unwrap();
// accept on a closed fd
unsafe {
libc::close(listener.as_raw_fd());
}
let conn2 = listener.accept();
assert!(conn2.is_err());
} }
#[test] #[test]
#[ignore]
fn send_data() { fn send_data() {
let listener = Listener::new(UNIX_SOCKET_DATA, true).unwrap(); let listener = Listener::new(UNIX_SOCKET_DATA, true).unwrap();
listener.set_nonblocking(true).unwrap(); listener.set_nonblocking(true).unwrap();
@@ -550,6 +541,7 @@ mod tests {
} }
#[test] #[test]
#[ignore]
fn send_fd() { fn send_fd() {
let listener = Listener::new(UNIX_SOCKET_FD, true).unwrap(); let listener = Listener::new(UNIX_SOCKET_FD, true).unwrap();
listener.set_nonblocking(true).unwrap(); listener.set_nonblocking(true).unwrap();
@@ -703,6 +695,7 @@ mod tests {
} }
#[test] #[test]
#[ignore]
fn send_recv() { fn send_recv() {
let listener = Listener::new(UNIX_SOCKET_SEND, true).unwrap(); let listener = Listener::new(UNIX_SOCKET_SEND, true).unwrap();
listener.set_nonblocking(true).unwrap(); listener.set_nonblocking(true).unwrap();

View File

@@ -631,6 +631,7 @@ mod tests {
} }
#[test] #[test]
#[ignore]
fn create_master() { fn create_master() {
let listener = Listener::new(UNIX_SOCKET_MASTER, true).unwrap(); let listener = Listener::new(UNIX_SOCKET_MASTER, true).unwrap();
listener.set_nonblocking(true).unwrap(); listener.set_nonblocking(true).unwrap();
@@ -656,6 +657,7 @@ mod tests {
} }
#[test] #[test]
#[ignore]
fn test_create_failure() { fn test_create_failure() {
let _ = Listener::new(UNIX_SOCKET_MASTER2, true).unwrap(); let _ = Listener::new(UNIX_SOCKET_MASTER2, true).unwrap();
let _ = Listener::new(UNIX_SOCKET_MASTER2, false).is_err(); let _ = Listener::new(UNIX_SOCKET_MASTER2, false).is_err();
@@ -670,6 +672,7 @@ mod tests {
} }
#[test] #[test]
#[ignore]
fn test_features() { fn test_features() {
let (mut master, mut peer) = create_pair(UNIX_SOCKET_MASTER3); let (mut master, mut peer) = create_pair(UNIX_SOCKET_MASTER3);
@@ -701,6 +704,7 @@ mod tests {
} }
#[test] #[test]
#[ignore]
fn test_protocol_features() { fn test_protocol_features() {
let (mut master, mut peer) = create_pair(UNIX_SOCKET_MASTER4); let (mut master, mut peer) = create_pair(UNIX_SOCKET_MASTER4);

View File

@@ -631,7 +631,7 @@ bitflags! {
} }
/// Max entries in one virtio-fs slave request. /// Max entries in one virtio-fs slave request.
const VHOST_USER_FS_SLAVE_ENTRIES: usize = 8; pub const VHOST_USER_FS_SLAVE_ENTRIES: usize = 8;
/// Slave request message to update the MMIO window. /// Slave request message to update the MMIO window.
#[repr(packed)] #[repr(packed)]
@@ -782,6 +782,7 @@ mod tests {
} }
#[test] #[test]
#[ignore]
fn check_user_config_msg() { fn check_user_config_msg() {
let mut msg = VhostUserConfig::new( let mut msg = VhostUserConfig::new(
VHOST_USER_CONFIG_OFFSET, VHOST_USER_CONFIG_OFFSET,

View File

@@ -178,7 +178,7 @@ mod tests {
backend: Arc<Mutex<S>>, backend: Arc<Mutex<S>>,
) -> (Master, SlaveReqHandler<S>) { ) -> (Master, SlaveReqHandler<S>) {
let mut slave_listener = SlaveListener::new(path, true, backend).unwrap(); let mut slave_listener = SlaveListener::new(path, true, backend).unwrap();
let master = Master::connect(path).unwrap(); let master = Master::connect(path, 1).unwrap();
(master, slave_listener.accept().unwrap().unwrap()) (master, slave_listener.accept().unwrap().unwrap())
} }

View File

@@ -265,7 +265,6 @@ impl<S: VhostUserSlaveReqHandler> SlaveReqHandler<S> {
if self.acked_protocol_features & VhostUserProtocolFeatures::CONFIG.bits() == 0 { if self.acked_protocol_features & VhostUserProtocolFeatures::CONFIG.bits() == 0 {
return Err(Error::InvalidOperation); return Err(Error::InvalidOperation);
} }
self.check_request_size(&hdr, size, mem::size_of::<VhostUserConfig>())?;
self.get_config(&hdr, &buf)?; self.get_config(&hdr, &buf)?;
} }
MasterReq::SET_CONFIG => { MasterReq::SET_CONFIG => {
@@ -341,6 +340,10 @@ impl<S: VhostUserSlaveReqHandler> SlaveReqHandler<S> {
if !msg.is_valid() { if !msg.is_valid() {
return Err(Error::InvalidMessage); return Err(Error::InvalidMessage);
} }
let payload_offset = mem::size_of::<VhostUserConfig>();
if buf.len() - payload_offset != msg.size as usize {
return Err(Error::InvalidMessage);
}
let flags = match VhostUserConfigFlags::from_bits(msg.flags) { let flags = match VhostUserConfigFlags::from_bits(msg.flags) {
Some(val) => val, Some(val) => val,
None => return Err(Error::InvalidMessage), None => return Err(Error::InvalidMessage),
@@ -519,6 +522,7 @@ impl<S: VhostUserSlaveReqHandler> SlaveReqHandler<S> {
fn new_reply_header<T: Sized>( fn new_reply_header<T: Sized>(
&self, &self,
req: &VhostUserMsgHeader<MasterReq>, req: &VhostUserMsgHeader<MasterReq>,
payload_size: usize,
) -> Result<VhostUserMsgHeader<MasterReq>> { ) -> Result<VhostUserMsgHeader<MasterReq>> {
if mem::size_of::<T>() > MAX_MSG_SIZE { if mem::size_of::<T>() > MAX_MSG_SIZE {
return Err(Error::InvalidParam); return Err(Error::InvalidParam);
@@ -527,7 +531,7 @@ impl<S: VhostUserSlaveReqHandler> SlaveReqHandler<S> {
Ok(VhostUserMsgHeader::new( Ok(VhostUserMsgHeader::new(
req.get_code(), req.get_code(),
VhostUserHeaderFlag::REPLY.bits(), VhostUserHeaderFlag::REPLY.bits(),
mem::size_of::<T>() as u32, (mem::size_of::<T>() + payload_size) as u32,
)) ))
} }
@@ -537,7 +541,7 @@ impl<S: VhostUserSlaveReqHandler> SlaveReqHandler<S> {
res: Result<()>, res: Result<()>,
) -> Result<()> { ) -> Result<()> {
if self.reply_ack_enabled { if self.reply_ack_enabled {
let hdr = self.new_reply_header::<VhostUserU64>(req)?; let hdr = self.new_reply_header::<VhostUserU64>(req, 0)?;
let val = match res { let val = match res {
Ok(_) => 0, Ok(_) => 0,
Err(_) => 1, Err(_) => 1,
@@ -553,7 +557,7 @@ impl<S: VhostUserSlaveReqHandler> SlaveReqHandler<S> {
req: &VhostUserMsgHeader<MasterReq>, req: &VhostUserMsgHeader<MasterReq>,
msg: &T, msg: &T,
) -> Result<()> { ) -> Result<()> {
let hdr = self.new_reply_header::<T>(req)?; let hdr = self.new_reply_header::<T>(req, 0)?;
self.main_sock.send_message(&hdr, msg, None)?; self.main_sock.send_message(&hdr, msg, None)?;
Ok(()) Ok(())
} }
@@ -568,7 +572,7 @@ impl<S: VhostUserSlaveReqHandler> SlaveReqHandler<S> {
T: Sized, T: Sized,
P: Sized, P: Sized,
{ {
let hdr = self.new_reply_header::<T>(req)?; let hdr = self.new_reply_header::<T>(req, payload.len())?;
self.main_sock self.main_sock
.send_message_with_payload(&hdr, msg, payload, None)?; .send_message_with_payload(&hdr, msg, payload, None)?;
Ok(()) Ok(())

View File

@@ -11,10 +11,10 @@ mmio_support = ["vm-virtio/mmio_support"]
[dependencies] [dependencies]
epoll = "4.1.0" epoll = "4.1.0"
libc = "0.2.65" libc = "0.2.66"
vm-memory = { git = "https://github.com/rust-vmm/vm-memory" } vm-memory = { git = "https://github.com/rust-vmm/vm-memory" }
vm-virtio = { path = "../vm-virtio" } vm-virtio = { path = "../vm-virtio" }
vmm-sys-util = "0.1.1" vmm-sys-util = "0.4.0"
[dependencies.vhost_rs] [dependencies.vhost_rs]
path = "../vhost_rs" path = "../vhost_rs"

View File

@@ -15,7 +15,6 @@ use std::thread;
use vhost_rs::vhost_user::message::{ use vhost_rs::vhost_user::message::{
VhostUserConfigFlags, VhostUserMemoryRegion, VhostUserProtocolFeatures, VhostUserConfigFlags, VhostUserMemoryRegion, VhostUserProtocolFeatures,
VhostUserVirtioFeatures, VhostUserVringAddrFlags, VhostUserVringState, VhostUserVirtioFeatures, VhostUserVringAddrFlags, VhostUserVringState,
VHOST_USER_CONFIG_OFFSET, VHOST_USER_CONFIG_SIZE,
}; };
use vhost_rs::vhost_user::{ use vhost_rs::vhost_user::{
Error as VhostUserError, Result as VhostUserResult, SlaveListener, VhostUserSlaveReqHandler, Error as VhostUserError, Result as VhostUserResult, SlaveListener, VhostUserSlaveReqHandler,
@@ -63,6 +62,9 @@ pub trait VhostUserBackend: Send + Sync + 'static {
/// Virtio features. /// Virtio features.
fn features(&self) -> u64; fn features(&self) -> u64;
/// Virtio protocol features.
fn protocol_features(&self) -> VhostUserProtocolFeatures;
/// Update guest memory regions. /// Update guest memory regions.
fn update_memory(&mut self, mem: GuestMemoryMmap) -> result::Result<(), io::Error>; fn update_memory(&mut self, mem: GuestMemoryMmap) -> result::Result<(), io::Error>;
@@ -167,7 +169,7 @@ impl<S: VhostUserBackend> VhostUserDaemon<S> {
struct AddrMapping { struct AddrMapping {
vmm_addr: u64, vmm_addr: u64,
size: u64, size: u64,
offset: u64, gpa_base: u64,
} }
struct Memory { struct Memory {
@@ -444,7 +446,7 @@ impl<S: VhostUserBackend> VhostUserHandler<S> {
if let Some(memory) = &self.memory { if let Some(memory) = &self.memory {
for mapping in memory.mappings.iter() { for mapping in memory.mappings.iter() {
if vmm_va >= mapping.vmm_addr && vmm_va < mapping.vmm_addr + mapping.size { if vmm_va >= mapping.vmm_addr && vmm_va < mapping.vmm_addr + mapping.size {
return Ok(vmm_va - mapping.vmm_addr + mapping.offset); return Ok(vmm_va - mapping.vmm_addr + mapping.gpa_base);
} }
} }
} }
@@ -475,9 +477,7 @@ impl<S: VhostUserBackend> VhostUserSlaveReqHandler for VhostUserHandler<S> {
} }
fn set_features(&mut self, features: u64) -> VhostUserResult<()> { fn set_features(&mut self, features: u64) -> VhostUserResult<()> {
if !self.owned { if (features & !self.backend.read().unwrap().features()) != 0 {
return Err(VhostUserError::InvalidOperation);
} else if (features & !self.backend.read().unwrap().features()) != 0 {
return Err(VhostUserError::InvalidParam); return Err(VhostUserError::InvalidParam);
} }
@@ -501,7 +501,7 @@ impl<S: VhostUserBackend> VhostUserSlaveReqHandler for VhostUserHandler<S> {
} }
fn get_protocol_features(&mut self) -> VhostUserResult<VhostUserProtocolFeatures> { fn get_protocol_features(&mut self) -> VhostUserResult<VhostUserProtocolFeatures> {
Ok(VhostUserProtocolFeatures::all()) Ok(self.backend.read().unwrap().protocol_features())
} }
fn set_protocol_features(&mut self, features: u64) -> VhostUserResult<()> { fn set_protocol_features(&mut self, features: u64) -> VhostUserResult<()> {
@@ -524,19 +524,19 @@ impl<S: VhostUserBackend> VhostUserSlaveReqHandler for VhostUserHandler<S> {
for (idx, region) in ctx.iter().enumerate() { for (idx, region) in ctx.iter().enumerate() {
let g_addr = GuestAddress(region.guest_phys_addr); let g_addr = GuestAddress(region.guest_phys_addr);
let len = (region.memory_size + region.mmap_offset) as usize; let len = region.memory_size as usize;
let file = unsafe { File::from_raw_fd(fds[idx]) }; let file = unsafe { File::from_raw_fd(fds[idx]) };
let f_off = FileOffset::new(file, 0); let f_off = FileOffset::new(file, region.mmap_offset);
regions.push((g_addr, len, Some(f_off))); regions.push((g_addr, len, Some(f_off)));
mappings.push(AddrMapping { mappings.push(AddrMapping {
vmm_addr: region.user_addr, vmm_addr: region.user_addr,
size: region.memory_size, size: region.memory_size,
offset: region.mmap_offset, gpa_base: region.guest_phys_addr,
}); });
} }
let mem = GuestMemoryMmap::with_files(regions).map_err(|e| { let mem = GuestMemoryMmap::from_ranges_with_files(regions).map_err(|e| {
VhostUserError::ReqHandlerError(io::Error::new(io::ErrorKind::Other, e)) VhostUserError::ReqHandlerError(io::Error::new(io::ErrorKind::Other, e))
})?; })?;
self.backend self.backend
@@ -720,16 +720,6 @@ impl<S: VhostUserBackend> VhostUserSlaveReqHandler for VhostUserHandler<S> {
size: u32, size: u32,
_flags: VhostUserConfigFlags, _flags: VhostUserConfigFlags,
) -> VhostUserResult<Vec<u8>> { ) -> VhostUserResult<Vec<u8>> {
if self.acked_features & VhostUserProtocolFeatures::CONFIG.bits() == 0 {
return Err(VhostUserError::InvalidOperation);
} else if offset < VHOST_USER_CONFIG_OFFSET
|| offset >= VHOST_USER_CONFIG_SIZE
|| size > VHOST_USER_CONFIG_SIZE - VHOST_USER_CONFIG_OFFSET
|| size + offset > VHOST_USER_CONFIG_SIZE
{
return Err(VhostUserError::InvalidParam);
}
Ok(self.backend.read().unwrap().get_config(offset, size)) Ok(self.backend.read().unwrap().get_config(offset, size))
} }
@@ -739,17 +729,6 @@ impl<S: VhostUserBackend> VhostUserSlaveReqHandler for VhostUserHandler<S> {
buf: &[u8], buf: &[u8],
_flags: VhostUserConfigFlags, _flags: VhostUserConfigFlags,
) -> VhostUserResult<()> { ) -> VhostUserResult<()> {
let size = buf.len() as u32;
if self.acked_features & VhostUserProtocolFeatures::CONFIG.bits() == 0 {
return Err(VhostUserError::InvalidOperation);
} else if offset < VHOST_USER_CONFIG_OFFSET
|| offset >= VHOST_USER_CONFIG_SIZE
|| size > VHOST_USER_CONFIG_SIZE - VHOST_USER_CONFIG_OFFSET
|| size + offset > VHOST_USER_CONFIG_SIZE
{
return Err(VhostUserError::InvalidParam);
}
self.backend self.backend
.write() .write()
.unwrap() .unwrap()

View File

@@ -0,0 +1,18 @@
[package]
name = "vhost_user_block"
version = "0.1.0"
authors = ["The Cloud Hypervisor Authors"]
edition = "2018"
[dependencies]
bitflags = "1.2.1"
epoll = "4.1.0"
libc = "0.2.66"
log = "0.4.8"
qcow = { path = "../qcow" }
vhost_user_backend = { path = "../vhost_user_backend" }
vhost_rs = { path = "../vhost_rs" }
virtio-bindings = "0.1.0"
vm-memory = { git = "https://github.com/rust-vmm/vm-memory" }
vm-virtio = { path = "../vm-virtio" }
vmm-sys-util = "0.4.0"

336
vhost_user_block/src/lib.rs Normal file
View File

@@ -0,0 +1,336 @@
// Copyright 2019 Red Hat, Inc. All Rights Reserved.
//
// Portions Copyright 2019 Intel Corporation. All Rights Reserved.
//
// Portions Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
//
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
//
// SPDX-License-Identifier: (Apache-2.0 AND BSD-3-Clause)
extern crate log;
extern crate vhost_rs;
extern crate vhost_user_backend;
extern crate vm_virtio;
use epoll;
use log::*;
use qcow::{self, ImageType, QcowFile};
use std::fs::File;
use std::fs::OpenOptions;
use std::io::Read;
use std::io::{Seek, SeekFrom, Write};
use std::mem;
use std::os::unix::fs::OpenOptionsExt;
use std::path::PathBuf;
use std::process;
use std::slice;
use std::sync::{Arc, RwLock};
use std::vec::Vec;
use vhost_rs::vhost_user::message::*;
use vhost_user_backend::{VhostUserBackend, VhostUserDaemon, Vring, VringWorker};
use virtio_bindings::bindings::virtio_blk::*;
use vm_memory::{Bytes, GuestMemoryError, GuestMemoryMmap};
use vm_virtio::block::{build_disk_image_id, Request};
const QUEUE_SIZE: usize = 1024;
const SECTOR_SHIFT: u8 = 9;
const SECTOR_SIZE: u64 = (0x01 as u64) << SECTOR_SHIFT;
const BLK_SIZE: u32 = 512;
trait DiskFile: Read + Seek + Write + Send + Sync {}
impl<D: Read + Seek + Write + Send + Sync> DiskFile for D {}
pub type Result<T> = std::result::Result<T, Error>;
pub type VhostUserBackendResult<T> = std::result::Result<T, std::io::Error>;
#[derive(Debug)]
pub enum Error {
/// Failed to detect image type.
DetectImageType,
/// Bad memory address.
GuestMemory(GuestMemoryError),
/// Can't open image file.
OpenImage,
/// Failed to parse direct parameter.
ParseDirectParam,
/// Failed to parse image parameter.
ParseImageParam,
/// Failed to parse sock parameter.
ParseSockParam,
/// Failed to parse readonly parameter.
ParseReadOnlyParam,
/// Failed parsing fs number of queues parameter.
ParseBlkNumQueuesParam(std::num::ParseIntError),
}
pub struct VhostUserBlkBackend {
mem: Option<GuestMemoryMmap>,
vring_worker: Option<Arc<VringWorker>>,
disk_image: Box<dyn DiskFile>,
disk_image_id: Vec<u8>,
disk_nsectors: u64,
config: virtio_blk_config,
rdonly: bool,
}
impl VhostUserBlkBackend {
pub fn new(image_path: String, num_queues: usize, rdonly: bool, direct: bool) -> Result<Self> {
let mut options = OpenOptions::new();
options.read(true);
options.write(!rdonly);
if direct {
options.custom_flags(libc::O_DIRECT);
}
let image: File = options.open(&image_path).unwrap();
let mut raw_img: vm_virtio::RawFile = vm_virtio::RawFile::new(image, direct);
let image_id = build_disk_image_id(&PathBuf::from(&image_path));
let image_type = qcow::detect_image_type(&mut raw_img).unwrap();
let mut image = match image_type {
ImageType::Raw => Box::new(raw_img) as Box<dyn DiskFile>,
ImageType::Qcow2 => Box::new(QcowFile::from(raw_img).unwrap()) as Box<dyn DiskFile>,
};
let nsectors = (image.seek(SeekFrom::End(0)).unwrap() as u64) / SECTOR_SIZE;
let mut config = virtio_blk_config::default();
config.capacity = nsectors;
config.blk_size = BLK_SIZE;
config.size_max = 65535;
config.seg_max = 128 - 2;
config.min_io_size = 1;
config.opt_io_size = 1;
config.num_queues = num_queues as u16;
config.wce = 1;
Ok(VhostUserBlkBackend {
mem: None,
vring_worker: None,
disk_image: image,
disk_image_id: image_id,
disk_nsectors: nsectors,
config,
rdonly,
})
}
pub fn process_queue(&mut self, vring: &mut Vring) -> bool {
let mut used_any = false;
let mem = match self.mem.as_ref() {
Some(m) => m,
None => return false,
};
while let Some(head) = vring.mut_queue().iter(mem).next() {
debug!("got an element in the queue");
let len;
match Request::parse(&head, mem) {
Ok(request) => {
debug!("element is a valid request");
let status = match request.execute(
&mut self.disk_image,
self.disk_nsectors,
mem,
&self.disk_image_id,
) {
Ok(l) => {
len = l;
VIRTIO_BLK_S_OK
}
Err(e) => {
len = 1;
e.status()
}
};
mem.write_obj(status, request.status_addr).unwrap();
}
Err(err) => {
error!("failed to parse available descriptor chain: {:?}", err);
len = 0;
}
}
vring.mut_queue().add_used(mem, head.index, len);
used_any = true;
}
used_any
}
pub fn set_vring_worker(&mut self, vring_worker: Option<Arc<VringWorker>>) {
self.vring_worker = vring_worker;
}
}
impl VhostUserBackend for VhostUserBlkBackend {
fn num_queues(&self) -> usize {
self.config.num_queues as usize
}
fn max_queue_size(&self) -> usize {
QUEUE_SIZE
}
fn features(&self) -> u64 {
let mut avail_features = 1 << VIRTIO_BLK_F_MQ
| 1 << VIRTIO_BLK_F_CONFIG_WCE
| 1 << VIRTIO_F_VERSION_1
| VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
if self.rdonly {
avail_features |= 1 << VIRTIO_BLK_F_RO;
}
avail_features
}
fn protocol_features(&self) -> VhostUserProtocolFeatures {
VhostUserProtocolFeatures::CONFIG
}
fn update_memory(&mut self, mem: GuestMemoryMmap) -> VhostUserBackendResult<()> {
self.mem = Some(mem);
Ok(())
}
fn handle_event(
&mut self,
device_event: u16,
evset: epoll::Events,
vrings: &[Arc<RwLock<Vring>>],
) -> VhostUserBackendResult<bool> {
if evset != epoll::Events::EPOLLIN {
warn!("invalid events operation");
return Ok(false);
}
debug!("event received: {:?}", device_event);
let mut vring = vrings[device_event as usize].write().unwrap();
if self.process_queue(&mut vring) {
debug!("signalling queue");
vring.signal_used_queue().unwrap();
}
Ok(false)
}
fn get_config(&self, _offset: u32, _size: u32) -> Vec<u8> {
// self.config is a statically allocated virtio_blk_config
let buf = unsafe {
slice::from_raw_parts(
&self.config as *const virtio_blk_config as *const _,
mem::size_of::<virtio_blk_config>(),
)
};
buf.to_vec()
}
}
pub struct VhostUserBlkBackendConfig<'a> {
pub image: &'a str,
pub sock: &'a str,
pub num_queues: usize,
pub readonly: bool,
pub direct: bool,
}
impl<'a> VhostUserBlkBackendConfig<'a> {
pub fn parse(backend: &'a str) -> Result<Self> {
let params_list: Vec<&str> = backend.split(',').collect();
let mut image: &str = "";
let mut sock: &str = "";
let mut num_queues_str: &str = "";
let mut readonly: bool = false;
let mut direct: bool = false;
for param in params_list.iter() {
if param.starts_with("image=") {
image = &param[6..];
} else if param.starts_with("sock=") {
sock = &param[5..];
} else if param.starts_with("num_queues=") {
num_queues_str = &param[11..];
} else if param.starts_with("readonly=") {
readonly = match param[9..].parse::<bool>() {
Ok(b) => b,
Err(_) => return Err(Error::ParseReadOnlyParam),
}
} else if param.starts_with("direct=") {
direct = match param[7..].parse::<bool>() {
Ok(b) => b,
Err(_) => return Err(Error::ParseDirectParam),
}
}
}
let mut num_queues: usize = 1;
if image.is_empty() {
return Err(Error::ParseImageParam);
}
if sock.is_empty() {
return Err(Error::ParseSockParam);
}
if !num_queues_str.is_empty() {
num_queues = num_queues_str
.parse()
.map_err(Error::ParseBlkNumQueuesParam)?;
}
Ok(VhostUserBlkBackendConfig {
image,
sock,
num_queues,
readonly,
direct,
})
}
}
pub fn start_block_backend(backend_command: &str) {
let backend_config = match VhostUserBlkBackendConfig::parse(backend_command) {
Ok(config) => config,
Err(e) => {
println!("Failed parsing parameters {:?}", e);
process::exit(1);
}
};
let blk_backend = Arc::new(RwLock::new(
VhostUserBlkBackend::new(
backend_config.image.to_string(),
backend_config.num_queues,
backend_config.readonly,
backend_config.direct,
)
.unwrap(),
));
debug!("blk_backend is created!\n");
let name = "vhost-user-blk-backend";
let mut blk_daemon = VhostUserDaemon::new(
name.to_string(),
backend_config.sock.to_string(),
blk_backend.clone(),
)
.unwrap();
debug!("blk_daemon is created!\n");
let vring_worker = blk_daemon.get_vring_worker();
blk_backend
.write()
.unwrap()
.set_vring_worker(Some(vring_worker));
if let Err(e) = blk_daemon.start() {
println!(
"failed to start daemon for vhost-user-blk with error: {:?}\n",
e
);
process::exit(1);
}
blk_daemon.wait().unwrap();
}

12
vhost_user_fs/Cargo.toml Normal file
View File

@@ -0,0 +1,12 @@
[package]
name = "vhost_user_fs"
version = "0.1.0"
authors = ["The Cloud Hypervisor Authors"]
edition = "2018"
[dependencies]
bitflags = "1.2.1"
libc = "0.2.66"
log = "0.4.8"
vm-memory = { git = "https://github.com/rust-vmm/vm-memory" }
vm-virtio = { path = "../vm-virtio" }

View File

@@ -0,0 +1,985 @@
// Copyright 2019 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use std::cmp;
use std::collections::VecDeque;
use std::fmt::{self, Display};
use std::io::{self, Read, Write};
use std::mem::{size_of, MaybeUninit};
use std::ops::Deref;
use std::ptr::copy_nonoverlapping;
use std::result;
use vm_memory::{
Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryError, GuestMemoryMmap,
GuestMemoryRegion, Le16, Le32, Le64, VolatileMemory, VolatileMemoryError, VolatileSlice,
};
use vm_virtio::DescriptorChain;
use crate::file_traits::{FileReadWriteAtVolatile, FileReadWriteVolatile};
#[derive(Debug)]
pub enum Error {
DescriptorChainOverflow,
FindMemoryRegion,
GuestMemoryError(GuestMemoryError),
InvalidChain,
IoError(io::Error),
SplitOutOfBounds(usize),
VolatileMemoryError(VolatileMemoryError),
}
impl Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::Error::*;
match self {
DescriptorChainOverflow => write!(
f,
"the combined length of all the buffers in a `DescriptorChain` would overflow"
),
FindMemoryRegion => write!(f, "no memory region for this address range"),
GuestMemoryError(e) => write!(f, "descriptor guest memory error: {}", e),
InvalidChain => write!(f, "invalid descriptor chain"),
IoError(e) => write!(f, "descriptor I/O error: {}", e),
SplitOutOfBounds(off) => write!(f, "`DescriptorChain` split is out of bounds: {}", off),
VolatileMemoryError(e) => write!(f, "volatile memory error: {}", e),
}
}
}
pub type Result<T> = result::Result<T, Error>;
impl std::error::Error for Error {}
#[derive(Clone)]
struct DescriptorChainConsumer<'a> {
buffers: VecDeque<VolatileSlice<'a>>,
bytes_consumed: usize,
}
impl<'a> DescriptorChainConsumer<'a> {
fn available_bytes(&self) -> usize {
// This is guaranteed not to overflow because the total length of the chain
// is checked during all creations of `DescriptorChainConsumer` (see
// `Reader::new()` and `Writer::new()`).
self.buffers
.iter()
.fold(0usize, |count, vs| count + vs.len() as usize)
}
fn bytes_consumed(&self) -> usize {
self.bytes_consumed
}
/// Consumes at most `count` bytes from the `DescriptorChain`. Callers must provide a function
/// that takes a `&[VolatileSlice]` and returns the total number of bytes consumed. This
/// function guarantees that the combined length of all the slices in the `&[VolatileSlice]` is
/// less than or equal to `count`.
///
/// # Errors
///
/// If the provided function returns any error then no bytes are consumed from the buffer and
/// the error is returned to the caller.
fn consume<F>(&mut self, count: usize, f: F) -> io::Result<usize>
where
F: FnOnce(&[VolatileSlice]) -> io::Result<usize>,
{
let mut buflen = 0;
let mut bufs = Vec::with_capacity(self.buffers.len());
for &vs in &self.buffers {
if buflen >= count {
break;
}
bufs.push(vs);
let rem = count - buflen;
if rem < vs.len() {
buflen += rem;
} else {
buflen += vs.len() as usize;
}
}
if bufs.is_empty() {
return Ok(0);
}
let bytes_consumed = f(&*bufs)?;
// This can happen if a driver tricks a device into reading/writing more data than
// fits in a `usize`.
let total_bytes_consumed =
self.bytes_consumed
.checked_add(bytes_consumed)
.ok_or_else(|| {
io::Error::new(io::ErrorKind::InvalidData, Error::DescriptorChainOverflow)
})?;
let mut rem = bytes_consumed;
while let Some(vs) = self.buffers.pop_front() {
if rem < vs.len() {
// Split the slice and push the remainder back into the buffer list. Safe because we
// know that `rem` is not out of bounds due to the check and we checked the bounds
// on `vs` when we added it to the buffer list.
self.buffers.push_front(vs.offset(rem).unwrap());
break;
}
// No need for checked math because we know that `vs.size() <= rem`.
rem -= vs.len();
}
self.bytes_consumed = total_bytes_consumed;
Ok(bytes_consumed)
}
fn split_at(&mut self, offset: usize) -> Result<DescriptorChainConsumer<'a>> {
let mut rem = offset;
let pos = self.buffers.iter().position(|vs| {
if rem < vs.len() {
true
} else {
rem -= vs.len();
false
}
});
if let Some(at) = pos {
let mut other = self.buffers.split_off(at);
if rem > 0 {
// There must be at least one element in `other` because we checked
// its `size` value in the call to `position` above.
let front = other.pop_front().expect("empty VecDeque after split");
self.buffers
.push_back(front.offset(rem).map_err(Error::VolatileMemoryError)?);
other.push_front(front.offset(rem).map_err(Error::VolatileMemoryError)?);
}
Ok(DescriptorChainConsumer {
buffers: other,
bytes_consumed: 0,
})
} else if rem == 0 {
Ok(DescriptorChainConsumer {
buffers: VecDeque::new(),
bytes_consumed: 0,
})
} else {
Err(Error::SplitOutOfBounds(offset))
}
}
}
/// Provides high-level interface over the sequence of memory regions
/// defined by readable descriptors in the descriptor chain.
///
/// Note that virtio spec requires driver to place any device-writable
/// descriptors after any device-readable descriptors (2.6.4.2 in Virtio Spec v1.1).
/// Reader will skip iterating over descriptor chain when first writable
/// descriptor is encountered.
#[derive(Clone)]
pub struct Reader<'a> {
buffer: DescriptorChainConsumer<'a>,
}
impl<'a> Reader<'a> {
/// Construct a new Reader wrapper over `desc_chain`.
pub fn new(mem: &'a GuestMemoryMmap, desc_chain: DescriptorChain<'a>) -> Result<Reader<'a>> {
let mut total_len: usize = 0;
let buffers = desc_chain
.into_iter()
.readable()
.map(|desc| {
// Verify that summing the descriptor sizes does not overflow.
// This can happen if a driver tricks a device into reading more data than
// fits in a `usize`.
total_len = total_len
.checked_add(desc.len as usize)
.ok_or(Error::DescriptorChainOverflow)?;
let region = mem.find_region(desc.addr).ok_or(Error::FindMemoryRegion)?;
let offset = desc
.addr
.checked_sub(region.start_addr().raw_value())
.unwrap();
region
.deref()
.get_slice(offset.raw_value() as usize, desc.len as usize)
.map_err(Error::VolatileMemoryError)
})
.collect::<Result<VecDeque<VolatileSlice<'a>>>>()?;
Ok(Reader {
buffer: DescriptorChainConsumer {
buffers,
bytes_consumed: 0,
},
})
}
/// Reads an object from the descriptor chain buffer.
pub fn read_obj<T: ByteValued>(&mut self) -> io::Result<T> {
let mut obj = MaybeUninit::<T>::uninit();
// Safe because `MaybeUninit` guarantees that the pointer is valid for
// `size_of::<T>()` bytes.
let buf = unsafe {
::std::slice::from_raw_parts_mut(obj.as_mut_ptr() as *mut u8, size_of::<T>())
};
self.read_exact(buf)?;
// Safe because any type that implements `ByteValued` can be considered initialized
// even if it is filled with random data.
Ok(unsafe { obj.assume_init() })
}
/// Reads data from the descriptor chain buffer into a file descriptor.
/// Returns the number of bytes read from the descriptor chain buffer.
/// The number of bytes read can be less than `count` if there isn't
/// enough data in the descriptor chain buffer.
pub fn read_to<F: FileReadWriteVolatile>(
&mut self,
mut dst: F,
count: usize,
) -> io::Result<usize> {
self.buffer
.consume(count, |bufs| dst.write_vectored_volatile(bufs))
}
/// Reads data from the descriptor chain buffer into a File at offset `off`.
/// Returns the number of bytes read from the descriptor chain buffer.
/// The number of bytes read can be less than `count` if there isn't
/// enough data in the descriptor chain buffer.
pub fn read_to_at<F: FileReadWriteAtVolatile>(
&mut self,
mut dst: F,
count: usize,
off: u64,
) -> io::Result<usize> {
self.buffer
.consume(count, |bufs| dst.write_vectored_at_volatile(bufs, off))
}
pub fn read_exact_to<F: FileReadWriteVolatile>(
&mut self,
mut dst: F,
mut count: usize,
) -> io::Result<()> {
while count > 0 {
match self.read_to(&mut dst, count) {
Ok(0) => {
return Err(io::Error::new(
io::ErrorKind::UnexpectedEof,
"failed to fill whole buffer",
))
}
Ok(n) => count -= n,
Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {}
Err(e) => return Err(e),
}
}
Ok(())
}
/// Returns number of bytes available for reading. May return an error if the combined
/// lengths of all the buffers in the DescriptorChain would cause an integer overflow.
pub fn available_bytes(&self) -> usize {
self.buffer.available_bytes()
}
/// Returns number of bytes already read from the descriptor chain buffer.
pub fn bytes_read(&self) -> usize {
self.buffer.bytes_consumed()
}
/// Splits this `Reader` into two at the given offset in the `DescriptorChain` buffer.
/// After the split, `self` will be able to read up to `offset` bytes while the returned
/// `Reader` can read up to `available_bytes() - offset` bytes. Returns an error if
/// `offset > self.available_bytes()`.
pub fn split_at(&mut self, offset: usize) -> Result<Reader<'a>> {
self.buffer.split_at(offset).map(|buffer| Reader { buffer })
}
}
impl<'a> io::Read for Reader<'a> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.buffer.consume(buf.len(), |bufs| {
let mut rem = buf;
let mut total = 0;
for vs in bufs {
let copy_len = cmp::min(rem.len(), vs.len());
// Safe because we have already verified that `vs` points to valid memory.
unsafe {
copy_nonoverlapping(vs.as_ptr() as *const u8, rem.as_mut_ptr(), copy_len);
}
rem = &mut rem[copy_len..];
total += copy_len;
}
Ok(total)
})
}
}
/// Provides high-level interface over the sequence of memory regions
/// defined by writable descriptors in the descriptor chain.
///
/// Note that virtio spec requires driver to place any device-writable
/// descriptors after any device-readable descriptors (2.6.4.2 in Virtio Spec v1.1).
/// Writer will start iterating the descriptors from the first writable one and will
/// assume that all following descriptors are writable.
#[derive(Clone)]
pub struct Writer<'a> {
buffer: DescriptorChainConsumer<'a>,
}
impl<'a> Writer<'a> {
/// Construct a new Writer wrapper over `desc_chain`.
pub fn new(mem: &'a GuestMemoryMmap, desc_chain: DescriptorChain<'a>) -> Result<Writer<'a>> {
let mut total_len: usize = 0;
let buffers = desc_chain
.into_iter()
.writable()
.map(|desc| {
// Verify that summing the descriptor sizes does not overflow.
// This can happen if a driver tricks a device into writing more data than
// fits in a `usize`.
total_len = total_len
.checked_add(desc.len as usize)
.ok_or(Error::DescriptorChainOverflow)?;
let region = mem.find_region(desc.addr).ok_or(Error::FindMemoryRegion)?;
let offset = desc
.addr
.checked_sub(region.start_addr().raw_value())
.unwrap();
region
.deref()
.get_slice(offset.raw_value() as usize, desc.len as usize)
.map_err(Error::VolatileMemoryError)
})
.collect::<Result<VecDeque<VolatileSlice<'a>>>>()?;
Ok(Writer {
buffer: DescriptorChainConsumer {
buffers,
bytes_consumed: 0,
},
})
}
/// Writes an object to the descriptor chain buffer.
pub fn write_obj<T: ByteValued>(&mut self, val: T) -> io::Result<()> {
self.write_all(val.as_slice())
}
/// Returns number of bytes available for writing. May return an error if the combined
/// lengths of all the buffers in the DescriptorChain would cause an overflow.
pub fn available_bytes(&self) -> usize {
self.buffer.available_bytes()
}
/// Writes data to the descriptor chain buffer from a file descriptor.
/// Returns the number of bytes written to the descriptor chain buffer.
/// The number of bytes written can be less than `count` if
/// there isn't enough data in the descriptor chain buffer.
pub fn write_from<F: FileReadWriteVolatile>(
&mut self,
mut src: F,
count: usize,
) -> io::Result<usize> {
self.buffer
.consume(count, |bufs| src.read_vectored_volatile(bufs))
}
/// Writes data to the descriptor chain buffer from a File at offset `off`.
/// Returns the number of bytes written to the descriptor chain buffer.
/// The number of bytes written can be less than `count` if
/// there isn't enough data in the descriptor chain buffer.
pub fn write_from_at<F: FileReadWriteAtVolatile>(
&mut self,
mut src: F,
count: usize,
off: u64,
) -> io::Result<usize> {
self.buffer
.consume(count, |bufs| src.read_vectored_at_volatile(bufs, off))
}
pub fn write_all_from<F: FileReadWriteVolatile>(
&mut self,
mut src: F,
mut count: usize,
) -> io::Result<()> {
while count > 0 {
match self.write_from(&mut src, count) {
Ok(0) => {
return Err(io::Error::new(
io::ErrorKind::WriteZero,
"failed to write whole buffer",
))
}
Ok(n) => count -= n,
Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {}
Err(e) => return Err(e),
}
}
Ok(())
}
/// Returns number of bytes already written to the descriptor chain buffer.
pub fn bytes_written(&self) -> usize {
self.buffer.bytes_consumed()
}
/// Splits this `Writer` into two at the given offset in the `DescriptorChain` buffer.
/// After the split, `self` will be able to write up to `offset` bytes while the returned
/// `Writer` can write up to `available_bytes() - offset` bytes. Returns an error if
/// `offset > self.available_bytes()`.
pub fn split_at(&mut self, offset: usize) -> Result<Writer<'a>> {
self.buffer.split_at(offset).map(|buffer| Writer { buffer })
}
}
impl<'a> io::Write for Writer<'a> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.buffer.consume(buf.len(), |bufs| {
let mut rem = buf;
let mut total = 0;
for vs in bufs {
let copy_len = cmp::min(rem.len(), vs.len());
// Safe because we have already verified that `vs` points to valid memory.
unsafe {
copy_nonoverlapping(rem.as_ptr(), vs.as_ptr(), copy_len);
}
rem = &rem[copy_len..];
total += copy_len;
}
Ok(total)
})
}
fn flush(&mut self) -> io::Result<()> {
// Nothing to flush since the writes go straight into the buffer.
Ok(())
}
}
const VIRTQ_DESC_F_NEXT: u16 = 0x1;
const VIRTQ_DESC_F_WRITE: u16 = 0x2;
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum DescriptorType {
Readable,
Writable,
}
#[derive(Copy, Clone, Debug, Default)]
#[repr(C)]
struct virtq_desc {
addr: Le64,
len: Le32,
flags: Le16,
next: Le16,
}
// Safe because it only has data and has no implicit padding.
unsafe impl ByteValued for virtq_desc {}
/// Test utility function to create a descriptor chain in guest memory.
pub fn create_descriptor_chain(
memory: &GuestMemoryMmap,
descriptor_array_addr: GuestAddress,
mut buffers_start_addr: GuestAddress,
descriptors: Vec<(DescriptorType, u32)>,
spaces_between_regions: u32,
) -> Result<DescriptorChain> {
let descriptors_len = descriptors.len();
for (index, (type_, size)) in descriptors.into_iter().enumerate() {
let mut flags = 0;
if let DescriptorType::Writable = type_ {
flags |= VIRTQ_DESC_F_WRITE;
}
if index + 1 < descriptors_len {
flags |= VIRTQ_DESC_F_NEXT;
}
let index = index as u16;
let desc = virtq_desc {
addr: buffers_start_addr.raw_value().into(),
len: size.into(),
flags: flags.into(),
next: (index + 1).into(),
};
let offset = size + spaces_between_regions;
buffers_start_addr = buffers_start_addr
.checked_add(u64::from(offset))
.ok_or(Error::InvalidChain)?;
let _ = memory.write_obj(
desc,
descriptor_array_addr
.checked_add(u64::from(index) * std::mem::size_of::<virtq_desc>() as u64)
.ok_or(Error::InvalidChain)?,
);
}
DescriptorChain::checked_new(memory, descriptor_array_addr, 0x100, 0, None)
.ok_or(Error::InvalidChain)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn reader_test_simple_chain() {
use DescriptorType::*;
let memory_start_addr = GuestAddress(0x0);
let memory = GuestMemoryMmap::from_ranges(&vec![(memory_start_addr, 0x10000)]).unwrap();
let chain = create_descriptor_chain(
&memory,
GuestAddress(0x0),
GuestAddress(0x100),
vec![
(Readable, 8),
(Readable, 16),
(Readable, 18),
(Readable, 64),
],
0,
)
.expect("create_descriptor_chain failed");
let mut reader = Reader::new(&memory, chain).expect("failed to create Reader");
assert_eq!(reader.available_bytes(), 106);
assert_eq!(reader.bytes_read(), 0);
let mut buffer = [0 as u8; 64];
if let Err(_) = reader.read_exact(&mut buffer) {
panic!("read_exact should not fail here");
}
assert_eq!(reader.available_bytes(), 42);
assert_eq!(reader.bytes_read(), 64);
match reader.read(&mut buffer) {
Err(_) => panic!("read should not fail here"),
Ok(length) => assert_eq!(length, 42),
}
assert_eq!(reader.available_bytes(), 0);
assert_eq!(reader.bytes_read(), 106);
}
#[test]
fn writer_test_simple_chain() {
use DescriptorType::*;
let memory_start_addr = GuestAddress(0x0);
let memory = GuestMemoryMmap::from_ranges(&vec![(memory_start_addr, 0x10000)]).unwrap();
let chain = create_descriptor_chain(
&memory,
GuestAddress(0x0),
GuestAddress(0x100),
vec![
(Writable, 8),
(Writable, 16),
(Writable, 18),
(Writable, 64),
],
0,
)
.expect("create_descriptor_chain failed");
let mut writer = Writer::new(&memory, chain).expect("failed to create Writer");
assert_eq!(writer.available_bytes(), 106);
assert_eq!(writer.bytes_written(), 0);
let mut buffer = [0 as u8; 64];
if let Err(_) = writer.write_all(&mut buffer) {
panic!("write_all should not fail here");
}
assert_eq!(writer.available_bytes(), 42);
assert_eq!(writer.bytes_written(), 64);
match writer.write(&mut buffer) {
Err(_) => panic!("write should not fail here"),
Ok(length) => assert_eq!(length, 42),
}
assert_eq!(writer.available_bytes(), 0);
assert_eq!(writer.bytes_written(), 106);
}
#[test]
fn reader_test_incompatible_chain() {
use DescriptorType::*;
let memory_start_addr = GuestAddress(0x0);
let memory = GuestMemoryMmap::from_ranges(&vec![(memory_start_addr, 0x10000)]).unwrap();
let chain = create_descriptor_chain(
&memory,
GuestAddress(0x0),
GuestAddress(0x100),
vec![(Writable, 8)],
0,
)
.expect("create_descriptor_chain failed");
let mut reader = Reader::new(&memory, chain).expect("failed to create Reader");
assert_eq!(reader.available_bytes(), 0);
assert_eq!(reader.bytes_read(), 0);
assert!(reader.read_obj::<u8>().is_err());
assert_eq!(reader.available_bytes(), 0);
assert_eq!(reader.bytes_read(), 0);
}
#[test]
fn writer_test_incompatible_chain() {
use DescriptorType::*;
let memory_start_addr = GuestAddress(0x0);
let memory = GuestMemoryMmap::from_ranges(&vec![(memory_start_addr, 0x10000)]).unwrap();
let chain = create_descriptor_chain(
&memory,
GuestAddress(0x0),
GuestAddress(0x100),
vec![(Readable, 8)],
0,
)
.expect("create_descriptor_chain failed");
let mut writer = Writer::new(&memory, chain).expect("failed to create Writer");
assert_eq!(writer.available_bytes(), 0);
assert_eq!(writer.bytes_written(), 0);
assert!(writer.write_obj(0u8).is_err());
assert_eq!(writer.available_bytes(), 0);
assert_eq!(writer.bytes_written(), 0);
}
#[test]
fn reader_writer_shared_chain() {
use DescriptorType::*;
let memory_start_addr = GuestAddress(0x0);
let memory = GuestMemoryMmap::from_ranges(&vec![(memory_start_addr, 0x10000)]).unwrap();
let chain = create_descriptor_chain(
&memory,
GuestAddress(0x0),
GuestAddress(0x100),
vec![
(Readable, 16),
(Readable, 16),
(Readable, 96),
(Writable, 64),
(Writable, 1),
(Writable, 3),
],
0,
)
.expect("create_descriptor_chain failed");
let mut reader = Reader::new(&memory, chain.clone()).expect("failed to create Reader");
let mut writer = Writer::new(&memory, chain).expect("failed to create Writer");
assert_eq!(reader.bytes_read(), 0);
assert_eq!(writer.bytes_written(), 0);
let mut buffer = Vec::with_capacity(200);
assert_eq!(
reader
.read_to_end(&mut buffer)
.expect("read should not fail here"),
128
);
// The writable descriptors are only 68 bytes long.
writer
.write_all(&buffer[..68])
.expect("write should not fail here");
assert_eq!(reader.available_bytes(), 0);
assert_eq!(reader.bytes_read(), 128);
assert_eq!(writer.available_bytes(), 0);
assert_eq!(writer.bytes_written(), 68);
}
#[test]
fn reader_writer_shattered_object() {
use DescriptorType::*;
let memory_start_addr = GuestAddress(0x0);
let memory = GuestMemoryMmap::from_ranges(&vec![(memory_start_addr, 0x10000)]).unwrap();
let secret: Le32 = 0x12345678.into();
// Create a descriptor chain with memory regions that are properly separated.
let chain_writer = create_descriptor_chain(
&memory,
GuestAddress(0x0),
GuestAddress(0x100),
vec![(Writable, 1), (Writable, 1), (Writable, 1), (Writable, 1)],
123,
)
.expect("create_descriptor_chain failed");
let mut writer = Writer::new(&memory, chain_writer).expect("failed to create Writer");
if let Err(_) = writer.write_obj(secret) {
panic!("write_obj should not fail here");
}
// Now create new descriptor chain pointing to the same memory and try to read it.
let chain_reader = create_descriptor_chain(
&memory,
GuestAddress(0x0),
GuestAddress(0x100),
vec![(Readable, 1), (Readable, 1), (Readable, 1), (Readable, 1)],
123,
)
.expect("create_descriptor_chain failed");
let mut reader = Reader::new(&memory, chain_reader).expect("failed to create Reader");
match reader.read_obj::<Le32>() {
Err(_) => panic!("read_obj should not fail here"),
Ok(read_secret) => assert_eq!(read_secret, secret),
}
}
#[test]
fn reader_unexpected_eof() {
use DescriptorType::*;
let memory_start_addr = GuestAddress(0x0);
let memory = GuestMemoryMmap::from_ranges(&vec![(memory_start_addr, 0x10000)]).unwrap();
let chain = create_descriptor_chain(
&memory,
GuestAddress(0x0),
GuestAddress(0x100),
vec![(Readable, 256), (Readable, 256)],
0,
)
.expect("create_descriptor_chain failed");
let mut reader = Reader::new(&memory, chain).expect("failed to create Reader");
let mut buf = Vec::with_capacity(1024);
buf.resize(1024, 0);
assert_eq!(
reader
.read_exact(&mut buf[..])
.expect_err("read more bytes than available")
.kind(),
io::ErrorKind::UnexpectedEof
);
}
#[test]
fn split_border() {
use DescriptorType::*;
let memory_start_addr = GuestAddress(0x0);
let memory = GuestMemoryMmap::from_ranges(&vec![(memory_start_addr, 0x10000)]).unwrap();
let chain = create_descriptor_chain(
&memory,
GuestAddress(0x0),
GuestAddress(0x100),
vec![
(Readable, 16),
(Readable, 16),
(Readable, 96),
(Writable, 64),
(Writable, 1),
(Writable, 3),
],
0,
)
.expect("create_descriptor_chain failed");
let mut reader = Reader::new(&memory, chain).expect("failed to create Reader");
let other = reader.split_at(32).expect("failed to split Reader");
assert_eq!(reader.available_bytes(), 32);
assert_eq!(other.available_bytes(), 96);
}
#[test]
fn split_middle() {
use DescriptorType::*;
let memory_start_addr = GuestAddress(0x0);
let memory = GuestMemoryMmap::from_ranges(&vec![(memory_start_addr, 0x10000)]).unwrap();
let chain = create_descriptor_chain(
&memory,
GuestAddress(0x0),
GuestAddress(0x100),
vec![
(Readable, 16),
(Readable, 16),
(Readable, 96),
(Writable, 64),
(Writable, 1),
(Writable, 3),
],
0,
)
.expect("create_descriptor_chain failed");
let mut reader = Reader::new(&memory, chain).expect("failed to create Reader");
let other = reader.split_at(24).expect("failed to split Reader");
assert_eq!(reader.available_bytes(), 24);
assert_eq!(other.available_bytes(), 104);
}
#[test]
fn split_end() {
use DescriptorType::*;
let memory_start_addr = GuestAddress(0x0);
let memory = GuestMemoryMmap::from_ranges(&vec![(memory_start_addr, 0x10000)]).unwrap();
let chain = create_descriptor_chain(
&memory,
GuestAddress(0x0),
GuestAddress(0x100),
vec![
(Readable, 16),
(Readable, 16),
(Readable, 96),
(Writable, 64),
(Writable, 1),
(Writable, 3),
],
0,
)
.expect("create_descriptor_chain failed");
let mut reader = Reader::new(&memory, chain).expect("failed to create Reader");
let other = reader.split_at(128).expect("failed to split Reader");
assert_eq!(reader.available_bytes(), 128);
assert_eq!(other.available_bytes(), 0);
}
#[test]
fn split_beginning() {
use DescriptorType::*;
let memory_start_addr = GuestAddress(0x0);
let memory = GuestMemoryMmap::from_ranges(&vec![(memory_start_addr, 0x10000)]).unwrap();
let chain = create_descriptor_chain(
&memory,
GuestAddress(0x0),
GuestAddress(0x100),
vec![
(Readable, 16),
(Readable, 16),
(Readable, 96),
(Writable, 64),
(Writable, 1),
(Writable, 3),
],
0,
)
.expect("create_descriptor_chain failed");
let mut reader = Reader::new(&memory, chain).expect("failed to create Reader");
let other = reader.split_at(0).expect("failed to split Reader");
assert_eq!(reader.available_bytes(), 0);
assert_eq!(other.available_bytes(), 128);
}
#[test]
fn split_outofbounds() {
use DescriptorType::*;
let memory_start_addr = GuestAddress(0x0);
let memory = GuestMemoryMmap::from_ranges(&vec![(memory_start_addr, 0x10000)]).unwrap();
let chain = create_descriptor_chain(
&memory,
GuestAddress(0x0),
GuestAddress(0x100),
vec![
(Readable, 16),
(Readable, 16),
(Readable, 96),
(Writable, 64),
(Writable, 1),
(Writable, 3),
],
0,
)
.expect("create_descriptor_chain failed");
let mut reader = Reader::new(&memory, chain).expect("failed to create Reader");
if let Ok(_) = reader.split_at(256) {
panic!("successfully split Reader with out of bounds offset");
}
}
#[test]
fn read_full() {
use DescriptorType::*;
let memory_start_addr = GuestAddress(0x0);
let memory = GuestMemoryMmap::from_ranges(&vec![(memory_start_addr, 0x10000)]).unwrap();
let chain = create_descriptor_chain(
&memory,
GuestAddress(0x0),
GuestAddress(0x100),
vec![(Readable, 16), (Readable, 16), (Readable, 16)],
0,
)
.expect("create_descriptor_chain failed");
let mut reader = Reader::new(&memory, chain).expect("failed to create Reader");
let mut buf = vec![0u8; 64];
assert_eq!(
reader.read(&mut buf[..]).expect("failed to read to buffer"),
48
);
}
#[test]
fn write_full() {
use DescriptorType::*;
let memory_start_addr = GuestAddress(0x0);
let memory = GuestMemoryMmap::from_ranges(&vec![(memory_start_addr, 0x10000)]).unwrap();
let chain = create_descriptor_chain(
&memory,
GuestAddress(0x0),
GuestAddress(0x100),
vec![(Writable, 16), (Writable, 16), (Writable, 16)],
0,
)
.expect("create_descriptor_chain failed");
let mut writer = Writer::new(&memory, chain).expect("failed to create Writer");
let buf = vec![0xdeu8; 64];
assert_eq!(
writer.write(&buf[..]).expect("failed to write from buffer"),
48
);
}
}

View File

@@ -0,0 +1,409 @@
// 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 file.
use std::fs::File;
use std::io::{Error, ErrorKind, Result};
use std::os::unix::io::AsRawFd;
use vm_memory::VolatileSlice;
use libc::{
c_int, c_void, off64_t, pread64, preadv64, pwrite64, pwritev64, read, readv, size_t, write,
writev,
};
/// A trait for setting the size of a file.
/// This is equivalent to File's `set_len` method, but
/// wrapped in a trait so that it can be implemented for
/// other types.
pub trait FileSetLen {
// Set the size of this file.
// This is the moral equivalent of `ftruncate()`.
fn set_len(&self, _len: u64) -> Result<()>;
}
impl FileSetLen for File {
fn set_len(&self, len: u64) -> Result<()> {
File::set_len(self, len)
}
}
/// A trait similar to `Read` and `Write`, but uses volatile memory as buffers.
pub trait FileReadWriteVolatile {
/// Read bytes from this file into the given slice, returning the number of bytes read on
/// success.
fn read_volatile(&mut self, slice: VolatileSlice) -> Result<usize>;
/// Like `read_volatile`, except it reads to a slice of buffers. Data is copied to fill each
/// buffer in order, with the final buffer written to possibly being only partially filled. This
/// method must behave as a single call to `read_volatile` with the buffers concatenated would.
/// The default implementation calls `read_volatile` with either the first nonempty buffer
/// provided, or returns `Ok(0)` if none exists.
fn read_vectored_volatile(&mut self, bufs: &[VolatileSlice]) -> Result<usize> {
bufs.iter()
.find(|b| !b.is_empty())
.map(|&b| self.read_volatile(b))
.unwrap_or(Ok(0))
}
/// Reads bytes from this into the given slice until all bytes in the slice are written, or an
/// error is returned.
fn read_exact_volatile(&mut self, mut slice: VolatileSlice) -> Result<()> {
while !slice.is_empty() {
let bytes_read = self.read_volatile(slice)?;
if bytes_read == 0 {
return Err(Error::from(ErrorKind::UnexpectedEof));
}
// Will panic if read_volatile read more bytes than we gave it, which would be worthy of
// a panic.
slice = slice.offset(bytes_read).unwrap();
}
Ok(())
}
/// Write bytes from the slice to the given file, returning the number of bytes written on
/// success.
fn write_volatile(&mut self, slice: VolatileSlice) -> Result<usize>;
/// Like `write_volatile`, except that it writes from a slice of buffers. Data is copied from
/// each buffer in order, with the final buffer read from possibly being only partially
/// consumed. This method must behave as a call to `write_volatile` with the buffers
/// concatenated would. The default implementation calls `write_volatile` with either the first
/// nonempty buffer provided, or returns `Ok(0)` if none exists.
fn write_vectored_volatile(&mut self, bufs: &[VolatileSlice]) -> Result<usize> {
bufs.iter()
.find(|b| !b.is_empty())
.map(|&b| self.write_volatile(b))
.unwrap_or(Ok(0))
}
/// Write bytes from the slice to the given file until all the bytes from the slice have been
/// written, or an error is returned.
fn write_all_volatile(&mut self, mut slice: VolatileSlice) -> Result<()> {
while !slice.is_empty() {
let bytes_written = self.write_volatile(slice)?;
if bytes_written == 0 {
return Err(Error::from(ErrorKind::WriteZero));
}
// Will panic if read_volatile read more bytes than we gave it, which would be worthy of
// a panic.
slice = slice.offset(bytes_written).unwrap();
}
Ok(())
}
}
impl<'a, T: FileReadWriteVolatile + ?Sized> FileReadWriteVolatile for &'a mut T {
fn read_volatile(&mut self, slice: VolatileSlice) -> Result<usize> {
(**self).read_volatile(slice)
}
fn read_vectored_volatile(&mut self, bufs: &[VolatileSlice]) -> Result<usize> {
(**self).read_vectored_volatile(bufs)
}
fn read_exact_volatile(&mut self, slice: VolatileSlice) -> Result<()> {
(**self).read_exact_volatile(slice)
}
fn write_volatile(&mut self, slice: VolatileSlice) -> Result<usize> {
(**self).write_volatile(slice)
}
fn write_vectored_volatile(&mut self, bufs: &[VolatileSlice]) -> Result<usize> {
(**self).write_vectored_volatile(bufs)
}
fn write_all_volatile(&mut self, slice: VolatileSlice) -> Result<()> {
(**self).write_all_volatile(slice)
}
}
/// A trait similar to the unix `ReadExt` and `WriteExt` traits, but for volatile memory.
pub trait FileReadWriteAtVolatile {
/// Reads bytes from this file at `offset` into the given slice, returning the number of bytes
/// read on success.
fn read_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> Result<usize>;
/// Like `read_at_volatile`, except it reads to a slice of buffers. Data is copied to fill each
/// buffer in order, with the final buffer written to possibly being only partially filled. This
/// method must behave as a single call to `read_at_volatile` with the buffers concatenated
/// would. The default implementation calls `read_at_volatile` with either the first nonempty
/// buffer provided, or returns `Ok(0)` if none exists.
fn read_vectored_at_volatile(&mut self, bufs: &[VolatileSlice], offset: u64) -> Result<usize> {
if let Some(&slice) = bufs.first() {
self.read_at_volatile(slice, offset)
} else {
Ok(0)
}
}
/// Reads bytes from this file at `offset` into the given slice until all bytes in the slice are
/// read, or an error is returned.
fn read_exact_at_volatile(&mut self, mut slice: VolatileSlice, mut offset: u64) -> Result<()> {
while !slice.is_empty() {
match self.read_at_volatile(slice, offset) {
Ok(0) => return Err(Error::from(ErrorKind::UnexpectedEof)),
Ok(n) => {
slice = slice.offset(n).unwrap();
offset = offset.checked_add(n as u64).unwrap();
}
Err(ref e) if e.kind() == ErrorKind::Interrupted => {}
Err(e) => return Err(e),
}
}
Ok(())
}
/// Writes bytes from this file at `offset` into the given slice, returning the number of bytes
/// written on success.
fn write_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> Result<usize>;
/// Like `write_at_at_volatile`, except that it writes from a slice of buffers. Data is copied
/// from each buffer in order, with the final buffer read from possibly being only partially
/// consumed. This method must behave as a call to `write_at_volatile` with the buffers
/// concatenated would. The default implementation calls `write_at_volatile` with either the
/// first nonempty buffer provided, or returns `Ok(0)` if none exists.
fn write_vectored_at_volatile(&mut self, bufs: &[VolatileSlice], offset: u64) -> Result<usize> {
if let Some(&slice) = bufs.first() {
self.write_at_volatile(slice, offset)
} else {
Ok(0)
}
}
/// Writes bytes from this file at `offset` into the given slice until all bytes in the slice
/// are written, or an error is returned.
fn write_all_at_volatile(&mut self, mut slice: VolatileSlice, mut offset: u64) -> Result<()> {
while !slice.is_empty() {
match self.write_at_volatile(slice, offset) {
Ok(0) => return Err(Error::from(ErrorKind::WriteZero)),
Ok(n) => {
slice = slice.offset(n).unwrap();
offset = offset.checked_add(n as u64).unwrap();
}
Err(ref e) if e.kind() == ErrorKind::Interrupted => {}
Err(e) => return Err(e),
}
}
Ok(())
}
}
impl<'a, T: FileReadWriteAtVolatile + ?Sized> FileReadWriteAtVolatile for &'a mut T {
fn read_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> Result<usize> {
(**self).read_at_volatile(slice, offset)
}
fn read_vectored_at_volatile(&mut self, bufs: &[VolatileSlice], offset: u64) -> Result<usize> {
(**self).read_vectored_at_volatile(bufs, offset)
}
fn read_exact_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> Result<()> {
(**self).read_exact_at_volatile(slice, offset)
}
fn write_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> Result<usize> {
(**self).write_at_volatile(slice, offset)
}
fn write_vectored_at_volatile(&mut self, bufs: &[VolatileSlice], offset: u64) -> Result<usize> {
(**self).write_vectored_at_volatile(bufs, offset)
}
fn write_all_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> Result<()> {
(**self).write_all_at_volatile(slice, offset)
}
}
macro_rules! volatile_impl {
($ty:ty) => {
impl FileReadWriteVolatile for $ty {
fn read_volatile(&mut self, slice: VolatileSlice) -> Result<usize> {
// Safe because only bytes inside the slice are accessed and the kernel is expected
// to handle arbitrary memory for I/O.
let ret =
unsafe { read(self.as_raw_fd(), slice.as_ptr() as *mut c_void, slice.len()) };
if ret >= 0 {
Ok(ret as usize)
} else {
Err(Error::last_os_error())
}
}
fn read_vectored_volatile(&mut self, bufs: &[VolatileSlice]) -> Result<usize> {
let iovecs: Vec<libc::iovec> = bufs
.iter()
.map(|s| libc::iovec {
iov_base: s.as_ptr() as *mut c_void,
iov_len: s.len() as size_t,
})
.collect();
if iovecs.is_empty() {
return Ok(0);
}
// Safe because only bytes inside the buffers are accessed and the kernel is
// expected to handle arbitrary memory for I/O.
let ret = unsafe { readv(self.as_raw_fd(), &iovecs[0], iovecs.len() as c_int) };
if ret >= 0 {
Ok(ret as usize)
} else {
Err(Error::last_os_error())
}
}
fn write_volatile(&mut self, slice: VolatileSlice) -> Result<usize> {
// Safe because only bytes inside the slice are accessed and the kernel is expected
// to handle arbitrary memory for I/O.
let ret = unsafe {
write(
self.as_raw_fd(),
slice.as_ptr() as *const c_void,
slice.len(),
)
};
if ret >= 0 {
Ok(ret as usize)
} else {
Err(Error::last_os_error())
}
}
fn write_vectored_volatile(&mut self, bufs: &[VolatileSlice]) -> Result<usize> {
let iovecs: Vec<libc::iovec> = bufs
.iter()
.map(|s| libc::iovec {
iov_base: s.as_ptr() as *mut c_void,
iov_len: s.len() as size_t,
})
.collect();
if iovecs.is_empty() {
return Ok(0);
}
// Safe because only bytes inside the buffers are accessed and the kernel is
// expected to handle arbitrary memory for I/O.
let ret = unsafe { writev(self.as_raw_fd(), &iovecs[0], iovecs.len() as c_int) };
if ret >= 0 {
Ok(ret as usize)
} else {
Err(Error::last_os_error())
}
}
}
impl FileReadWriteAtVolatile for $ty {
fn read_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> Result<usize> {
// Safe because only bytes inside the slice are accessed and the kernel is expected
// to handle arbitrary memory for I/O.
let ret = unsafe {
pread64(
self.as_raw_fd(),
slice.as_ptr() as *mut c_void,
slice.len(),
offset as off64_t,
)
};
if ret >= 0 {
Ok(ret as usize)
} else {
Err(Error::last_os_error())
}
}
fn read_vectored_at_volatile(
&mut self,
bufs: &[VolatileSlice],
offset: u64,
) -> Result<usize> {
let iovecs: Vec<libc::iovec> = bufs
.iter()
.map(|s| libc::iovec {
iov_base: s.as_ptr() as *mut c_void,
iov_len: s.len() as size_t,
})
.collect();
if iovecs.is_empty() {
return Ok(0);
}
// Safe because only bytes inside the buffers are accessed and the kernel is
// expected to handle arbitrary memory for I/O.
let ret = unsafe {
preadv64(
self.as_raw_fd(),
&iovecs[0],
iovecs.len() as c_int,
offset as off64_t,
)
};
if ret >= 0 {
Ok(ret as usize)
} else {
Err(Error::last_os_error())
}
}
fn write_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> Result<usize> {
// Safe because only bytes inside the slice are accessed and the kernel is expected
// to handle arbitrary memory for I/O.
let ret = unsafe {
pwrite64(
self.as_raw_fd(),
slice.as_ptr() as *const c_void,
slice.len(),
offset as off64_t,
)
};
if ret >= 0 {
Ok(ret as usize)
} else {
Err(Error::last_os_error())
}
}
fn write_vectored_at_volatile(
&mut self,
bufs: &[VolatileSlice],
offset: u64,
) -> Result<usize> {
let iovecs: Vec<libc::iovec> = bufs
.iter()
.map(|s| libc::iovec {
iov_base: s.as_ptr() as *mut c_void,
iov_len: s.len() as size_t,
})
.collect();
if iovecs.is_empty() {
return Ok(0);
}
// Safe because only bytes inside the buffers are accessed and the kernel is
// expected to handle arbitrary memory for I/O.
let ret = unsafe {
pwritev64(
self.as_raw_fd(),
&iovecs[0],
iovecs.len() as c_int,
offset as off64_t,
)
};
if ret >= 0 {
Ok(ret as usize)
} else {
Err(Error::last_os_error())
}
}
}
};
}
volatile_impl!(File);

File diff suppressed because it is too large Load Diff

1047
vhost_user_fs/src/fuse.rs Normal file

File diff suppressed because it is too large Load Diff

57
vhost_user_fs/src/lib.rs Normal file
View File

@@ -0,0 +1,57 @@
// Copyright © 2019 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
#[macro_use]
extern crate log;
pub mod descriptor_utils;
pub mod file_traits;
pub mod filesystem;
pub mod fuse;
pub mod multikey;
pub mod passthrough;
pub mod server;
use std::ffi::FromBytesWithNulError;
use std::{error, fmt, io};
#[derive(Debug)]
pub enum Error {
/// Failed to decode protocol messages.
DecodeMessage(io::Error),
/// Failed to encode protocol messages.
EncodeMessage(io::Error),
/// One or more parameters are missing.
MissingParameter,
/// A C string parameter is invalid.
InvalidCString(FromBytesWithNulError),
/// The `len` field of the header is too small.
InvalidHeaderLength,
/// The `size` field of the `SetxattrIn` message does not match the length
/// of the decoded value.
InvalidXattrSize((u32, usize)),
}
impl error::Error for Error {}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use Error::*;
match self {
DecodeMessage(err) => write!(f, "failed to decode fuse message: {}", err),
EncodeMessage(err) => write!(f, "failed to encode fuse message: {}", err),
MissingParameter => write!(f, "one or more parameters are missing"),
InvalidHeaderLength => write!(f, "the `len` field of the header is too small"),
InvalidCString(err) => write!(f, "a c string parameter is invalid: {}", err),
InvalidXattrSize((size, len)) => write!(
f,
"The `size` field of the `SetxattrIn` message does not match the length of the\
decoded value: size = {}, value.len() = {}",
size, len
),
}
}
}
pub type Result<T> = ::std::result::Result<T, Error>;

View File

@@ -0,0 +1,274 @@
// Copyright 2019 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use std::borrow::Borrow;
use std::collections::BTreeMap;
/// A BTreeMap that supports 2 types of keys per value. All the usual restrictions and warnings for
/// `std::collections::BTreeMap` also apply to this struct. Additionally, there is a 1:1
/// relationship between the 2 key types. In other words, for each `K1` in the map, there is exactly
/// one `K2` in the map and vice versa.
#[derive(Default)]
pub struct MultikeyBTreeMap<K1, K2, V>
where
K1: Ord,
K2: Ord,
{
// We need to keep a copy of the second key in the main map so that we can remove entries using
// just the main key. Otherwise we would require the caller to provide both keys when calling
// `remove`.
main: BTreeMap<K1, (K2, V)>,
alt: BTreeMap<K2, K1>,
}
impl<K1, K2, V> MultikeyBTreeMap<K1, K2, V>
where
K1: Clone + Ord,
K2: Clone + Ord,
{
/// Create a new empty MultikeyBTreeMap.
pub fn new() -> Self {
MultikeyBTreeMap {
main: BTreeMap::default(),
alt: BTreeMap::default(),
}
}
/// Returns a reference to the value corresponding to the key.
///
/// The key may be any borrowed form of `K1``, but the ordering on the borrowed form must match
/// the ordering on `K1`.
pub fn get<Q>(&self, key: &Q) -> Option<&V>
where
K1: Borrow<Q>,
Q: Ord + ?Sized,
{
self.main.get(key).map(|(_, v)| v)
}
/// Returns a reference to the value corresponding to the alternate key.
///
/// The key may be any borrowed form of the `K2``, but the ordering on the borrowed form must
/// match the ordering on `K2`.
///
/// Note that this method performs 2 lookups: one to get the main key and another to get the
/// value associated with that key. For best performance callers should prefer the `get` method
/// over this method whenever possible as `get` only needs to perform one lookup.
pub fn get_alt<Q2>(&self, key: &Q2) -> Option<&V>
where
K2: Borrow<Q2>,
Q2: Ord + ?Sized,
{
if let Some(k) = self.alt.get(key) {
self.get(k)
} else {
None
}
}
/// Inserts a new entry into the map with the given keys and value.
///
/// Returns `None` if the map did not have an entry with `k1` or `k2` present. If exactly one
/// key was present, then the value associated with that key is updated, the other key is
/// removed, and the old value is returned. If **both** keys were present then the value
/// associated with the main key is updated, the value associated with the alternate key is
/// removed, and the old value associated with the main key is returned.
pub fn insert(&mut self, k1: K1, k2: K2, v: V) -> Option<V> {
let oldval = if let Some(oldkey) = self.alt.insert(k2.clone(), k1.clone()) {
self.main.remove(&oldkey)
} else {
None
};
self.main
.insert(k1, (k2.clone(), v))
.or(oldval)
.map(|(oldk2, v)| {
if oldk2 != k2 {
self.alt.remove(&oldk2);
}
v
})
}
/// Remove a key from the map, returning the value associated with that key if it was previously
/// in the map.
///
/// The key may be any borrowed form of `K1``, but the ordering on the borrowed form must match
/// the ordering on `K1`.
pub fn remove<Q>(&mut self, key: &Q) -> Option<V>
where
K1: Borrow<Q>,
Q: Ord + ?Sized,
{
self.main.remove(key).map(|(k2, v)| {
self.alt.remove(&k2);
v
})
}
/// Clears the map, removing all values.
pub fn clear(&mut self) {
self.alt.clear();
self.main.clear()
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn get() {
let mut m = MultikeyBTreeMap::<u64, i64, u32>::new();
let k1 = 0xc6c8_f5e0_b13e_ed40;
let k2 = 0x1a04_ce4b_8329_14fe;
let val = 0xf4e3_c360;
assert!(m.insert(k1, k2, val).is_none());
assert_eq!(*m.get(&k1).expect("failed to look up main key"), val);
assert_eq!(*m.get_alt(&k2).expect("failed to look up alt key"), val);
}
#[test]
fn update_main_key() {
let mut m = MultikeyBTreeMap::<u64, i64, u32>::new();
let k1 = 0xc6c8_f5e0_b13e_ed40;
let k2 = 0x1a04_ce4b_8329_14fe;
let val = 0xf4e3_c360;
assert!(m.insert(k1, k2, val).is_none());
let new_k1 = 0x3add_f8f8_c7c5_df5e;
let val2 = 0x7389_f8a7;
assert_eq!(
m.insert(new_k1, k2, val2)
.expect("failed to update main key"),
val
);
assert!(m.get(&k1).is_none());
assert_eq!(*m.get(&new_k1).expect("failed to look up main key"), val2);
assert_eq!(*m.get_alt(&k2).expect("failed to look up alt key"), val2);
}
#[test]
fn update_alt_key() {
let mut m = MultikeyBTreeMap::<u64, i64, u32>::new();
let k1 = 0xc6c8_f5e0_b13e_ed40;
let k2 = 0x1a04_ce4b_8329_14fe;
let val = 0xf4e3_c360;
assert!(m.insert(k1, k2, val).is_none());
let new_k2 = 0x6825_a60b_61ac_b333;
let val2 = 0xbb14_8f2c;
assert_eq!(
m.insert(k1, new_k2, val2)
.expect("failed to update alt key"),
val
);
assert!(m.get_alt(&k2).is_none());
assert_eq!(*m.get(&k1).expect("failed to look up main key"), val2);
assert_eq!(
*m.get_alt(&new_k2).expect("failed to look up alt key"),
val2
);
}
#[test]
fn update_value() {
let mut m = MultikeyBTreeMap::<u64, i64, u32>::new();
let k1 = 0xc6c8_f5e0_b13e_ed40;
let k2 = 0x1a04_ce4b_8329_14fe;
let val = 0xf4e3_c360;
assert!(m.insert(k1, k2, val).is_none());
let val2 = 0xe42d_79ba;
assert_eq!(
m.insert(k1, k2, val2).expect("failed to update alt key"),
val
);
assert_eq!(*m.get(&k1).expect("failed to look up main key"), val2);
assert_eq!(*m.get_alt(&k2).expect("failed to look up alt key"), val2);
}
#[test]
fn update_both_keys_main() {
let mut m = MultikeyBTreeMap::<u64, i64, u32>::new();
let k1 = 0xc6c8_f5e0_b13e_ed40;
let k2 = 0x1a04_ce4b_8329_14fe;
let val = 0xf4e3_c360;
assert!(m.insert(k1, k2, val).is_none());
let new_k1 = 0xc980_587a_24b3_ae30;
let new_k2 = 0x2773_c5ee_8239_45a2;
let val2 = 0x31f4_33f9;
assert!(m.insert(new_k1, new_k2, val2).is_none());
let val3 = 0x8da1_9cf7;
assert_eq!(
m.insert(k1, new_k2, val3)
.expect("failed to update main key"),
val
);
// Both new_k1 and k2 should now be gone from the map.
assert!(m.get(&new_k1).is_none());
assert!(m.get_alt(&k2).is_none());
assert_eq!(*m.get(&k1).expect("failed to look up main key"), val3);
assert_eq!(
*m.get_alt(&new_k2).expect("failed to look up alt key"),
val3
);
}
#[test]
fn update_both_keys_alt() {
let mut m = MultikeyBTreeMap::<u64, i64, u32>::new();
let k1 = 0xc6c8_f5e0_b13e_ed40;
let k2 = 0x1a04_ce4b_8329_14fe;
let val = 0xf4e3_c360;
assert!(m.insert(k1, k2, val).is_none());
let new_k1 = 0xc980_587a_24b3_ae30;
let new_k2 = 0x2773_c5ee_8239_45a2;
let val2 = 0x31f4_33f9;
assert!(m.insert(new_k1, new_k2, val2).is_none());
let val3 = 0x8da1_9cf7;
assert_eq!(
m.insert(new_k1, k2, val3)
.expect("failed to update main key"),
val2
);
// Both k1 and new_k2 should now be gone from the map.
assert!(m.get(&k1).is_none());
assert!(m.get_alt(&new_k2).is_none());
assert_eq!(*m.get(&new_k1).expect("failed to look up main key"), val3);
assert_eq!(*m.get_alt(&k2).expect("failed to look up alt key"), val3);
}
#[test]
fn remove() {
let mut m = MultikeyBTreeMap::<u64, i64, u32>::new();
let k1 = 0xc6c8_f5e0_b13e_ed40;
let k2 = 0x1a04_ce4b_8329_14fe;
let val = 0xf4e3_c360;
assert!(m.insert(k1, k2, val).is_none());
assert_eq!(m.remove(&k1).expect("failed to remove entry"), val);
assert!(m.get(&k1).is_none());
assert!(m.get_alt(&k2).is_none());
}
}

File diff suppressed because it is too large Load Diff

1269
vhost_user_fs/src/server.rs Normal file

File diff suppressed because it is too large Load Diff

18
vhost_user_net/Cargo.toml Normal file
View File

@@ -0,0 +1,18 @@
[package]
name = "vhost_user_net"
version = "0.1.0"
authors = ["The Cloud Hypervisor Authors"]
edition = "2018"
[dependencies]
bitflags = "1.2.1"
epoll = "4.1.0"
libc = "0.2.66"
log = "0.4.8"
net_util = { path = "../net_util" }
vhost_user_backend = { path = "../vhost_user_backend" }
vhost_rs = { path = "../vhost_rs" }
virtio-bindings = "0.1.0"
vm-memory = { git = "https://github.com/rust-vmm/vm-memory" }
vm-virtio = { path = "../vm-virtio" }
vmm-sys-util = "0.4.0"

483
vhost_user_net/src/lib.rs Normal file
View File

@@ -0,0 +1,483 @@
// Copyright 2019 Intel Corporation. All Rights Reserved.
//
// Portions Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
//
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
//
// SPDX-License-Identifier: (Apache-2.0 AND BSD-3-Clause)
extern crate log;
extern crate net_util;
extern crate vhost_rs;
extern crate vhost_user_backend;
extern crate vm_virtio;
use epoll;
use libc::{self, EAGAIN, EFD_NONBLOCK};
use log::*;
use net_util::Tap;
use std::convert::TryFrom;
use std::fmt;
use std::io::Read;
use std::io::{self};
use std::net::Ipv4Addr;
use std::os::unix::io::AsRawFd;
use std::os::unix::io::RawFd;
use std::process;
use std::sync::{Arc, RwLock};
use std::vec::Vec;
use vhost_rs::vhost_user::message::*;
use vhost_rs::vhost_user::Error as VhostUserError;
use vhost_user_backend::{VhostUserBackend, VhostUserDaemon, Vring, VringWorker};
use virtio_bindings::bindings::virtio_net::*;
use vm_memory::GuestMemoryMmap;
use vm_virtio::net_util::{open_tap, RxVirtio, TxVirtio};
use vm_virtio::Queue;
use vmm_sys_util::eventfd::EventFd;
pub type VhostUserResult<T> = std::result::Result<T, VhostUserError>;
pub type Result<T> = std::result::Result<T, Error>;
pub type VhostUserBackendResult<T> = std::result::Result<T, std::io::Error>;
#[derive(Debug)]
pub enum Error {
/// Failed to activate device.
BadActivate,
/// Failed to create kill eventfd
CreateKillEventFd(io::Error),
/// Failed to add event.
EpollCtl(io::Error),
/// Fail to wait event.
EpollWait(io::Error),
/// Failed to create EventFd.
EpollCreateFd,
/// Failed to read Tap.
FailedReadTap,
/// Failed to signal used queue.
FailedSignalingUsedQueue,
/// Failed to handle event other than input event.
HandleEventNotEpollIn,
/// Failed to handle unknown event.
HandleEventUnknownEvent,
/// Invalid vring address.
InvalidVringAddr,
/// No vring call fd to notify.
NoVringCallFdNotify,
/// No memory configured.
NoMemoryConfigured,
/// Failed to parse sock parameter.
ParseSockParam,
/// Failed to parse ip parameter.
ParseIpParam(std::net::AddrParseError),
/// Failed to parse mask parameter.
ParseMaskParam(std::net::AddrParseError),
/// Failed to parse queue number.
ParseQueueNumParam(std::num::ParseIntError),
/// Failed to parse queue size.
ParseQueueSizeParam(std::num::ParseIntError),
/// Open tap device failed.
OpenTap(vm_virtio::net_util::Error),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "vhost_user_net_error: {:?}", self)
}
}
impl std::error::Error for Error {}
impl std::convert::From<Error> for std::io::Error {
fn from(e: Error) -> Self {
std::io::Error::new(io::ErrorKind::Other, e)
}
}
pub struct VhostUserNetBackend {
mem: Option<GuestMemoryMmap>,
vring_worker: Option<Arc<VringWorker>>,
kill_evt: EventFd,
taps: Vec<(Tap, usize)>,
rxs: Vec<RxVirtio>,
txs: Vec<TxVirtio>,
rx_tap_listenings: Vec<bool>,
num_queues: usize,
queue_size: u16,
}
impl std::clone::Clone for VhostUserNetBackend {
fn clone(&self) -> Self {
VhostUserNetBackend {
mem: self.mem.clone(),
vring_worker: self.vring_worker.clone(),
kill_evt: self.kill_evt.try_clone().unwrap(),
taps: self.taps.clone(),
rxs: self.rxs.clone(),
txs: self.txs.clone(),
rx_tap_listenings: self.rx_tap_listenings.clone(),
num_queues: self.num_queues,
queue_size: self.queue_size,
}
}
}
impl VhostUserNetBackend {
/// Create a new virtio network device with the given TAP interface.
pub fn new_with_tap(taps: Vec<Tap>, num_queues: usize, queue_size: u16) -> Result<Self> {
let mut taps_v: Vec<(Tap, usize)> = Vec::new();
for (i, tap) in taps.iter().enumerate() {
taps_v.push((tap.clone(), num_queues + i));
}
let mut rxs: Vec<RxVirtio> = Vec::new();
let mut txs: Vec<TxVirtio> = Vec::new();
let mut rx_tap_listenings: Vec<bool> = Vec::new();
for _ in 0..taps.len() {
let rx = RxVirtio::new();
rxs.push(rx);
let tx = TxVirtio::new();
txs.push(tx);
rx_tap_listenings.push(false);
}
Ok(VhostUserNetBackend {
mem: None,
vring_worker: None,
kill_evt: EventFd::new(EFD_NONBLOCK).map_err(Error::CreateKillEventFd)?,
taps: taps_v,
rxs,
txs,
rx_tap_listenings,
num_queues,
queue_size,
})
}
/// Create a new virtio network device with the given IP address and
/// netmask.
pub fn new(
ip_addr: Ipv4Addr,
netmask: Ipv4Addr,
num_queues: usize,
queue_size: u16,
) -> Result<Self> {
let taps =
open_tap(None, Some(ip_addr), Some(netmask), num_queues / 2).map_err(Error::OpenTap)?;
Self::new_with_tap(taps, num_queues, queue_size)
}
// Copies a single frame from `self.rx.frame_buf` into the guest. Returns true
// if a buffer was used, and false if the frame must be deferred until a buffer
// is made available by the driver.
fn rx_single_frame(&mut self, mut queue: &mut Queue, index: usize) -> Result<bool> {
let mem = self.mem.as_ref().ok_or(Error::NoMemoryConfigured)?;
let next_desc = queue.iter(&mem).next();
if next_desc.is_none() {
// Queue has no available descriptors
if self.rx_tap_listenings[index] {
self.vring_worker
.as_ref()
.unwrap()
.unregister_listener(
self.taps[index].0.as_raw_fd(),
epoll::Events::EPOLLIN,
u64::try_from(self.taps[index].1).unwrap(),
)
.unwrap();
self.rx_tap_listenings[index] = false;
}
return Ok(false);
}
let write_complete = self.rxs[index].process_desc_chain(&mem, next_desc, &mut queue);
Ok(write_complete)
}
fn process_rx(&mut self, vring: &mut Vring, index: usize) -> Result<()> {
// Read as many frames as possible.
loop {
match self.read_tap(index) {
Ok(count) => {
self.rxs[index].bytes_read = count;
if !self.rx_single_frame(&mut vring.mut_queue(), index)? {
self.rxs[index].deferred_frame = true;
break;
}
}
Err(e) => {
// The tap device is non-blocking, so any error aside from EAGAIN is
// unexpected.
match e.raw_os_error() {
Some(err) if err == EAGAIN => (),
_ => {
error!("Failed to read tap: {:?}", e);
return Err(Error::FailedReadTap);
}
};
break;
}
}
}
if self.rxs[index].deferred_irqs {
self.rxs[index].deferred_irqs = false;
vring.signal_used_queue().unwrap();
Ok(())
} else {
Ok(())
}
}
fn resume_rx(&mut self, vring: &mut Vring, index: usize) -> Result<()> {
if self.rxs[index].deferred_frame {
if self.rx_single_frame(&mut vring.mut_queue(), index)? {
self.rxs[index].deferred_frame = false;
// process_rx() was interrupted possibly before consuming all
// packets in the tap; try continuing now.
self.process_rx(vring, index)
} else if self.rxs[index].deferred_irqs {
self.rxs[index].deferred_irqs = false;
vring.signal_used_queue().unwrap();
Ok(())
} else {
Ok(())
}
} else {
Ok(())
}
}
fn process_tx(&mut self, mut queue: &mut Queue, index: usize) -> Result<()> {
let mem = self.mem.as_ref().ok_or(Error::NoMemoryConfigured)?;
self.txs[index].process_desc_chain(&mem, &mut self.taps[index].0, &mut queue);
Ok(())
}
fn read_tap(&mut self, index: usize) -> io::Result<usize> {
self.taps[index].0.read(&mut self.rxs[index].frame_buf)
}
pub fn set_vring_worker(&mut self, vring_worker: Option<Arc<VringWorker>>) {
self.vring_worker = vring_worker;
}
pub fn get_kill_event(&self) -> (u16, RawFd) {
(
(self.num_queues + (self.num_queues / 2)) as u16,
self.kill_evt.as_raw_fd(),
)
}
}
impl VhostUserBackend for VhostUserNetBackend {
fn num_queues(&self) -> usize {
self.num_queues
}
fn max_queue_size(&self) -> usize {
self.queue_size as usize
}
fn features(&self) -> u64 {
1 << VIRTIO_NET_F_GUEST_CSUM
| 1 << VIRTIO_NET_F_CSUM
| 1 << VIRTIO_NET_F_GUEST_TSO4
| 1 << VIRTIO_NET_F_GUEST_UFO
| 1 << VIRTIO_NET_F_HOST_TSO4
| 1 << VIRTIO_NET_F_HOST_UFO
| 1 << VIRTIO_F_VERSION_1
| VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits()
}
fn protocol_features(&self) -> VhostUserProtocolFeatures {
VhostUserProtocolFeatures::all()
}
fn update_memory(&mut self, mem: GuestMemoryMmap) -> VhostUserBackendResult<()> {
self.mem = Some(mem);
Ok(())
}
fn handle_event(
&mut self,
device_event: u16,
evset: epoll::Events,
vrings: &[Arc<RwLock<Vring>>],
) -> VhostUserBackendResult<bool> {
if evset != epoll::Events::EPOLLIN {
return Err(Error::HandleEventNotEpollIn.into());
}
let tap_start_index = self.num_queues as u16;
let tap_end_index = (self.num_queues + self.num_queues / 2 - 1) as u16;
let kill_index = tap_end_index + 1;
match device_event {
x if ((x < self.num_queues as u16) && (x % 2 == 0)) => {
let index = (x / 2) as usize;
let mut vring = vrings[x as usize].write().unwrap();
self.resume_rx(&mut vring, index)?;
if !self.rx_tap_listenings[index] {
self.vring_worker.as_ref().unwrap().register_listener(
self.taps[index].0.as_raw_fd(),
epoll::Events::EPOLLIN,
u64::try_from(self.taps[index].1).unwrap(),
)?;
self.rx_tap_listenings[index] = true;
}
}
x if ((x < self.num_queues as u16) && (x % 2 != 0)) => {
let index = ((x - 1) / 2) as usize;
let mut vring = vrings[x as usize].write().unwrap();
self.process_tx(&mut vring.mut_queue(), index)?;
}
x if x >= tap_start_index && x <= tap_end_index => {
let index = x as usize - self.num_queues;
let mut vring = vrings[2 * index].write().unwrap();
if self.rxs[index].deferred_frame
// Process a deferred frame first if available. Don't read from tap again
// until we manage to receive this deferred frame.
{
if self.rx_single_frame(&mut vring.mut_queue(), index)? {
self.rxs[index].deferred_frame = false;
self.process_rx(&mut vring, index)?;
} else if self.rxs[index].deferred_irqs {
self.rxs[index].deferred_irqs = false;
vring.signal_used_queue()?;
}
} else {
self.process_rx(&mut vring, index)?;
}
}
x if x == kill_index => {
self.kill_evt.read().unwrap();
return Ok(true);
}
_ => return Err(Error::HandleEventUnknownEvent.into()),
}
Ok(false)
}
}
pub struct VhostUserNetBackendConfig<'a> {
pub ip: Ipv4Addr,
pub mask: Ipv4Addr,
pub sock: &'a str,
pub num_queues: usize,
pub queue_size: u16,
}
impl<'a> VhostUserNetBackendConfig<'a> {
pub fn parse(backend: &'a str) -> Result<Self> {
let params_list: Vec<&str> = backend.split(',').collect();
let mut ip_str: &str = "";
let mut mask_str: &str = "";
let mut sock: &str = "";
let mut num_queues_str: &str = "";
let mut queue_size_str: &str = "";
for param in params_list.iter() {
if param.starts_with("ip=") {
ip_str = &param[3..];
} else if param.starts_with("mask=") {
mask_str = &param[5..];
} else if param.starts_with("sock=") {
sock = &param[5..];
} else if param.starts_with("num_queues=") {
num_queues_str = &param[11..];
} else if param.starts_with("queue_size=") {
queue_size_str = &param[11..];
}
}
let mut ip: Ipv4Addr = Ipv4Addr::new(192, 168, 100, 1);
let mut mask: Ipv4Addr = Ipv4Addr::new(255, 255, 255, 0);
let mut num_queues: usize = 2;
let mut queue_size: u16 = 256;
if sock.is_empty() {
return Err(Error::ParseSockParam);
}
if !ip_str.is_empty() {
ip = ip_str.parse().map_err(Error::ParseIpParam)?;
}
if !mask_str.is_empty() {
mask = mask_str.parse().map_err(Error::ParseMaskParam)?;
}
if !num_queues_str.is_empty() {
num_queues = num_queues_str.parse().map_err(Error::ParseQueueNumParam)?;
}
if !queue_size_str.is_empty() {
queue_size = queue_size_str.parse().map_err(Error::ParseQueueSizeParam)?;
}
Ok(VhostUserNetBackendConfig {
ip,
mask,
sock,
num_queues,
queue_size,
})
}
}
pub fn start_net_backend(backend_command: &str) {
let backend_config = match VhostUserNetBackendConfig::parse(backend_command) {
Ok(config) => config,
Err(e) => {
println!("Failed parsing parameters {:?}", e);
process::exit(1);
}
};
let net_backend = Arc::new(RwLock::new(
VhostUserNetBackend::new(
backend_config.ip,
backend_config.mask,
backend_config.num_queues,
backend_config.queue_size,
)
.unwrap(),
));
let mut net_daemon = VhostUserDaemon::new(
"vhost-user-net-backend".to_string(),
backend_config.sock.to_string(),
net_backend.clone(),
)
.unwrap();
let (kill_index, kill_evt_fd) = net_backend.read().unwrap().get_kill_event();
let vring_worker = net_daemon.get_vring_worker();
if let Err(e) =
vring_worker.register_listener(kill_evt_fd, epoll::Events::EPOLLIN, u64::from(kill_index))
{
println!("failed to register listener for kill event: {:?}", e);
process::exit(1);
}
net_backend
.write()
.unwrap()
.set_vring_worker(Some(vring_worker));
if let Err(e) = net_daemon.start() {
println!(
"failed to start daemon for vhost-user-net with error: {:?}",
e
);
process::exit(1);
}
net_daemon.wait().unwrap();
}

View File

@@ -5,5 +5,5 @@ authors = ["The Chromium OS Authors"]
edition = "2018" edition = "2018"
[dependencies] [dependencies]
libc = "0.2.60" libc = "0.2.66"
vm-memory = { git = "https://github.com/rust-vmm/vm-memory" } vm-memory = { git = "https://github.com/rust-vmm/vm-memory" }

View File

@@ -5,3 +5,13 @@ authors = ["The Cloud Hypervisor Authors"]
edition = "2018" edition = "2018"
[dependencies] [dependencies]
anyhow = "1.0.26"
thiserror = "1.0.11"
serde = { version = "1.0.104", features = ["rc"] }
serde_derive = "1.0.104"
serde_json = "1.0.48"
vmm-sys-util = "0.4.0"
[dependencies.vm-memory]
git = "https://github.com/rust-vmm/vm-memory"
features = ["backend-mmap"]

View File

@@ -0,0 +1,196 @@
// Copyright (C) 2019 Alibaba Cloud. All rights reserved.
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// Copyright © 2019 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
//! Traits and Structs to manage interrupt sources for devices.
//!
//! In system programming, an interrupt is a signal to the processor emitted by hardware or
//! software indicating an event that needs immediate attention. An interrupt alerts the processor
//! to a high-priority condition requiring the interruption of the current code the processor is
//! executing. The processor responds by suspending its current activities, saving its state, and
//! executing a function called an interrupt handler (or an interrupt service routine, ISR) to deal
//! with the event. This interruption is temporary, and, after the interrupt handler finishes,
//! unless handling the interrupt has emitted a fatal error, the processor resumes normal
//! activities.
//!
//! Hardware interrupts are used by devices to communicate that they require attention from the
//! operating system, or a bare-metal program running on the CPU if there are no OSes. The act of
//! initiating a hardware interrupt is referred to as an interrupt request (IRQ). Different devices
//! are usually associated with different interrupts using a unique value associated with each
//! interrupt. This makes it possible to know which hardware device caused which interrupts.
//! These interrupt values are often called IRQ lines, or just interrupt lines.
//!
//! Nowadays, IRQ lines is not the only mechanism to deliver device interrupts to processors.
//! MSI [(Message Signaled Interrupt)](https://en.wikipedia.org/wiki/Message_Signaled_Interrupts)
//! is another commonly used alternative in-band method of signaling an interrupt, using special
//! in-band messages to replace traditional out-of-band assertion of dedicated interrupt lines.
//! While more complex to implement in a device, message signaled interrupts have some significant
//! advantages over pin-based out-of-band interrupt signaling. Message signaled interrupts are
//! supported in PCI bus since its version 2.2, and in later available PCI Express bus. Some
//! non-PCI architectures also use message signaled interrupts.
//!
//! While IRQ is a term commonly used by Operating Systems when dealing with hardware
//! interrupts, the IRQ numbers managed by OSes are independent of the ones managed by VMM.
//! For simplicity sake, the term `Interrupt Source` is used instead of IRQ to represent both
//! pin-based interrupts and MSI interrupts.
//!
//! A device may support multiple types of interrupts, and each type of interrupt may support one
//! or multiple interrupt sources. For example, a PCI device may support:
//! * Legacy Irq: exactly one interrupt source.
//! * PCI MSI Irq: 1,2,4,8,16,32 interrupt sources.
//! * PCI MSIx Irq: 2^n(n=0-11) interrupt sources.
//!
//! A distinct Interrupt Source Identifier (ISID) will be assigned to each interrupt source.
//! An ID allocator will be used to allocate and free Interrupt Source Identifiers for devices.
//! To decouple the vm-device crate from the ID allocator, the vm-device crate doesn't take the
//! responsibility to allocate/free Interrupt Source IDs but only makes use of assigned IDs.
//!
//! The overall flow to deal with interrupts is:
//! * The VMM creates an interrupt manager
//! * The VMM creates a device manager, passing on an reference to the interrupt manager
//! * The device manager passes on an reference to the interrupt manager to all registered devices
//! * The guest kernel loads drivers for virtual devices
//! * The guest device driver determines the type and number of interrupts needed, and update the
//! device configuration
//! * The virtual device backend requests the interrupt manager to create an interrupt group
//! according to guest configuration information
use std::sync::Arc;
use vmm_sys_util::eventfd::EventFd;
/// Reuse std::io::Result to simplify interoperability among crates.
pub type Result<T> = std::io::Result<T>;
/// Data type to store an interrupt source identifier.
pub type InterruptIndex = u32;
/// Configuration data for legacy interrupts.
///
/// On x86 platforms, legacy interrupts means those interrupts routed through PICs or IOAPICs.
#[derive(Copy, Clone, Debug)]
pub struct LegacyIrqSourceConfig {}
/// Configuration data for MSI/MSI-X interrupts.
///
/// On x86 platforms, these interrupts are vectors delivered directly to the LAPIC.
#[derive(Copy, Clone, Debug, Default)]
pub struct MsiIrqSourceConfig {
/// High address to delivery message signaled interrupt.
pub high_addr: u32,
/// Low address to delivery message signaled interrupt.
pub low_addr: u32,
/// Data to write to delivery message signaled interrupt.
pub data: u32,
}
/// Configuration data for an interrupt source.
#[derive(Copy, Clone, Debug)]
pub enum InterruptSourceConfig {
/// Configuration data for Legacy interrupts.
LegacyIrq(LegacyIrqSourceConfig),
/// Configuration data for PciMsi, PciMsix and generic MSI interrupts.
MsiIrq(MsiIrqSourceConfig),
}
/// Configuration data for legacy, pin based interrupt groups.
///
/// A legacy interrupt group only takes one irq number as its configuration.
#[derive(Copy, Clone, Debug)]
pub struct LegacyIrqGroupConfig {
/// Legacy irq number.
pub irq: InterruptIndex,
}
/// Configuration data for MSI/MSI-X interrupt groups
///
/// MSI/MSI-X interrupt groups are basically a set of vectors.
#[derive(Copy, Clone, Debug)]
pub struct MsiIrqGroupConfig {
/// First index of the MSI/MSI-X interrupt vectors
pub base: InterruptIndex,
/// Number of vectors in the MSI/MSI-X group.
pub count: InterruptIndex,
}
/// Trait to manage interrupt sources for virtual device backends.
///
/// The InterruptManager implementations should protect itself from concurrent accesses internally,
/// so it could be invoked from multi-threaded context.
pub trait InterruptManager {
type GroupConfig;
/// Create an [InterruptSourceGroup](trait.InterruptSourceGroup.html) object to manage
/// interrupt sources for a virtual device
///
/// An [InterruptSourceGroup](trait.InterruptSourceGroup.html) object manages all interrupt
/// sources of the same type for a virtual device.
///
/// # Arguments
/// * interrupt_type: type of interrupt source.
/// * base: base Interrupt Source ID to be managed by the group object.
/// * count: number of Interrupt Sources to be managed by the group object.
fn create_group(&self, config: Self::GroupConfig)
-> Result<Arc<Box<dyn InterruptSourceGroup>>>;
/// Destroy an [InterruptSourceGroup](trait.InterruptSourceGroup.html) object created by
/// [create_group()](trait.InterruptManager.html#tymethod.create_group).
///
/// Assume the caller takes the responsibility to disable all interrupt sources of the group
/// before calling destroy_group(). This assumption helps to simplify InterruptSourceGroup
/// implementations.
fn destroy_group(&self, group: Arc<Box<dyn InterruptSourceGroup>>) -> Result<()>;
}
pub trait InterruptSourceGroup: Send + Sync {
/// Enable the interrupt sources in the group to generate interrupts.
fn enable(&self) -> Result<()> {
// Not all interrupt sources can be enabled.
// To accommodate this, we can have a no-op here.
Ok(())
}
/// Disable the interrupt sources in the group to generate interrupts.
fn disable(&self) -> Result<()> {
// Not all interrupt sources can be disabled.
// To accommodate this, we can have a no-op here.
Ok(())
}
/// Inject an interrupt from this interrupt source into the guest.
fn trigger(&self, index: InterruptIndex) -> Result<()>;
/// Returns an interrupt notifier from this interrupt.
///
/// An interrupt notifier allows for external components and processes
/// to inject interrupts into a guest, by writing to the file returned
/// by this method.
#[allow(unused_variables)]
fn notifier(&self, index: InterruptIndex) -> Option<&EventFd> {
// One use case of the notifier is to implement vhost user backends.
// For all other implementations we can just return None here.
None
}
/// Update the interrupt source group configuration.
///
/// # Arguments
/// * index: sub-index into the group.
/// * config: configuration data for the interrupt source.
fn update(&self, index: InterruptIndex, config: InterruptSourceConfig) -> Result<()>;
/// Mask an interrupt from this interrupt source.
fn mask(&self, _index: InterruptIndex) -> Result<()> {
// Not all interrupt sources can be disabled.
// To accommodate this, we can have a no-op here.
Ok(())
}
/// Unmask an interrupt from this interrupt source.
fn unmask(&self, _index: InterruptIndex) -> Result<()> {
// Not all interrupt sources can be disabled.
// To accommodate this, we can have a no-op here.
Ok(())
}
}

View File

@@ -1,3 +1,16 @@
extern crate serde;
extern crate thiserror;
extern crate vm_memory;
pub mod interrupt;
use vm_memory::{
Address, GuestAddress, GuestMemory, GuestMemoryMmap, GuestMemoryRegion, GuestRegionMmap,
MemoryRegionAddress,
};
use thiserror::Error;
/// Trait meant for triggering the DMA mapping update related to an external /// Trait meant for triggering the DMA mapping update related to an external
/// device not managed fully through virtio. It is dedicated to virtio-iommu /// device not managed fully through virtio. It is dedicated to virtio-iommu
/// in order to trigger the map update anytime the mapping is updated from the /// in order to trigger the map update anytime the mapping is updated from the
@@ -9,3 +22,95 @@ pub trait ExternalDmaMapping: Send + Sync {
/// Unmap a memory range /// Unmap a memory range
fn unmap(&self, iova: u64, size: u64) -> std::result::Result<(), std::io::Error>; fn unmap(&self, iova: u64, size: u64) -> std::result::Result<(), std::io::Error>;
} }
#[derive(Error, Debug)]
pub enum MigratableError {
#[error("Failed to pause migratable component: {0}")]
Pause(#[source] anyhow::Error),
#[error("Failed to resume migratable component: {0}")]
Resume(#[source] anyhow::Error),
}
/// A Pausable component can be paused and resumed.
pub trait Pausable {
/// Pause the component.
fn pause(&mut self) -> std::result::Result<(), MigratableError>;
/// Resume the component.
fn resume(&mut self) -> std::result::Result<(), MigratableError>;
}
/// A snapshotable component can be snapshoted.
pub trait Snapshotable {}
/// Trait to be implemented by any component (device, CPU, RAM, etc) that
/// can be migrated.
/// All migratable components are paused before being snapshotted, and then
/// eventually resumed. Thus any Migratable component must be both Pausable
/// and Snapshotable.
pub trait Migratable: Pausable + Snapshotable {}
fn get_region_host_address_range(
region: &GuestRegionMmap,
addr: MemoryRegionAddress,
size: usize,
) -> Option<*mut u8> {
region.check_address(addr).and_then(|addr| {
region
.checked_offset(addr, size)
.map(|_| region.as_ptr().wrapping_offset(addr.raw_value() as isize))
})
}
/// Convert an absolute address into an address space (GuestMemory)
/// to a host pointer and verify that the provided size define a valid
/// range within a single memory region.
/// Return None if it is out of bounds or if addr+size overlaps a single region.
///
/// This is a temporary vm-memory wrapper.
pub fn get_host_address_range(
mem: &GuestMemoryMmap,
addr: GuestAddress,
size: usize,
) -> Option<*mut u8> {
mem.to_region_addr(addr)
.and_then(|(r, addr)| get_region_host_address_range(r, addr, size))
}
#[cfg(test)]
mod tests {
use super::*;
use vm_memory::{GuestAddress, GuestMemoryMmap};
#[test]
fn test_get_host_address_range() {
let start_addr1 = GuestAddress(0x0);
let start_addr2 = GuestAddress(0x1000);
let guest_mem =
GuestMemoryMmap::from_ranges(&[(start_addr1, 0x400), (start_addr2, 0x400)]).unwrap();
assert!(get_host_address_range(&guest_mem, GuestAddress(0x600), 0x100).is_none());
// Overlapping range
assert!(get_host_address_range(&guest_mem, GuestAddress(0x1000), 0x500).is_none());
// Overlapping range
assert!(get_host_address_range(&guest_mem, GuestAddress(0x1200), 0x500).is_none());
let ptr = get_host_address_range(&guest_mem, GuestAddress(0x1000), 0x100).unwrap();
let ptr0 = get_host_address_range(&guest_mem, GuestAddress(0x1100), 0x100).unwrap();
let ptr1 = guest_mem.get_host_address(GuestAddress(0x1200)).unwrap();
assert_eq!(
ptr,
guest_mem
.find_region(GuestAddress(0x1100))
.unwrap()
.as_ptr()
);
assert_eq!(unsafe { ptr0.offset(0x100) }, ptr1);
}
}

View File

@@ -10,19 +10,20 @@ pci_support = ["pci"]
mmio_support = [] mmio_support = []
[dependencies] [dependencies]
byteorder = "1.3.2" arc-swap = "0.4.4"
byteorder = "1.3.4"
devices = { path = "../devices" } devices = { path = "../devices" }
epoll = "4.1.0" epoll = "4.1.0"
libc = "0.2.60" libc = "0.2.66"
log = "0.4.8" log = "0.4.8"
net_gen = { path = "../net_gen" } net_gen = { path = "../net_gen" }
net_util = { path = "../net_util" } net_util = { path = "../net_util" }
pci = { path = "../pci", optional = true } pci = { path = "../pci", optional = true }
tempfile = "3.1.0" tempfile = "3.1.0"
virtio-bindings = { git = "https://github.com/rust-vmm/virtio-bindings", version = "0.1", features = ["virtio-v5_0_0"]} virtio-bindings = { version = "0.1.0", features = ["virtio-v5_0_0"] }
vm-allocator = { path = "../vm-allocator" } vm-allocator = { path = "../vm-allocator" }
vm-device = { path = "../vm-device" } vm-device = { path = "../vm-device" }
vmm-sys-util = "0.1.1" vmm-sys-util = "0.4.0"
[dependencies.vhost_rs] [dependencies.vhost_rs]
path = "../vhost_rs" path = "../vhost_rs"

View File

@@ -8,34 +8,36 @@
// //
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause // SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
use epoll;
use libc::EFD_NONBLOCK;
use std::cmp;
use std::fs::File;
use std::io::{self, Read, Seek, SeekFrom, Write};
use std::os::linux::fs::MetadataExt;
use std::os::unix::io::AsRawFd;
use std::path::PathBuf;
use std::result;
use std::sync::{Arc, RwLock};
use std::thread;
use super::Error as DeviceError; use super::Error as DeviceError;
use super::{ use super::{
ActivateError, ActivateResult, DescriptorChain, DeviceEventT, Queue, VirtioDevice, ActivateError, ActivateResult, DescriptorChain, DeviceEventT, Queue, VirtioDevice,
VirtioDeviceType, VirtioInterruptType, VirtioDeviceType, VirtioInterruptType,
}; };
use crate::VirtioInterrupt; use crate::VirtioInterrupt;
use arc_swap::ArcSwap;
use epoll;
use libc::{c_void, EFD_NONBLOCK};
use std::alloc::{alloc_zeroed, dealloc, Layout};
use std::cmp;
use std::convert::TryInto;
use std::fs::{File, Metadata};
use std::io::{self, Read, Seek, SeekFrom, Write};
use std::ops::DerefMut;
use std::os::linux::fs::MetadataExt;
use std::os::unix::io::{AsRawFd, RawFd};
use std::path::PathBuf;
use std::result;
use std::slice;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
use std::thread;
use virtio_bindings::bindings::virtio_blk::*; use virtio_bindings::bindings::virtio_blk::*;
use vm_memory::{Bytes, GuestAddress, GuestMemory, GuestMemoryError, GuestMemoryMmap}; use vm_device::{Migratable, MigratableError, Pausable, Snapshotable};
use vmm_sys_util::eventfd::EventFd; use vm_memory::{ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryError, GuestMemoryMmap};
use vmm_sys_util::{eventfd::EventFd, seek_hole::SeekHole, write_zeroes::PunchHole};
const CONFIG_SPACE_SIZE: usize = 8;
const SECTOR_SHIFT: u8 = 9; const SECTOR_SHIFT: u8 = 9;
pub const SECTOR_SIZE: u64 = (0x01 as u64) << SECTOR_SHIFT; pub const SECTOR_SIZE: u64 = (0x01 as u64) << SECTOR_SHIFT;
const QUEUE_SIZE: u16 = 256;
const NUM_QUEUES: usize = 1;
const QUEUE_SIZES: &[u16] = &[QUEUE_SIZE];
// New descriptors are pending on the virtio queue. // New descriptors are pending on the virtio queue.
const QUEUE_AVAIL_EVENT: DeviceEventT = 0; const QUEUE_AVAIL_EVENT: DeviceEventT = 0;
@@ -43,9 +45,11 @@ const QUEUE_AVAIL_EVENT: DeviceEventT = 0;
pub const KILL_EVENT: DeviceEventT = 1; pub const KILL_EVENT: DeviceEventT = 1;
// Number of DeviceEventT events supported by this implementation. // Number of DeviceEventT events supported by this implementation.
pub const BLOCK_EVENTS_COUNT: usize = 2; pub const BLOCK_EVENTS_COUNT: usize = 2;
// The device should be paused.
const PAUSE_EVENT: DeviceEventT = 3;
#[derive(Debug)] #[derive(Debug)]
enum Error { pub enum Error {
/// Guest gave us bad memory addresses. /// Guest gave us bad memory addresses.
GuestMemory(GuestMemoryError), GuestMemory(GuestMemoryError),
/// Guest gave us offsets that would have overflowed a usize. /// Guest gave us offsets that would have overflowed a usize.
@@ -65,7 +69,7 @@ enum Error {
} }
#[derive(Debug)] #[derive(Debug)]
enum ExecuteError { pub enum ExecuteError {
BadRequest(Error), BadRequest(Error),
Flush(io::Error), Flush(io::Error),
Read(GuestMemoryError), Read(GuestMemoryError),
@@ -75,7 +79,7 @@ enum ExecuteError {
} }
impl ExecuteError { impl ExecuteError {
fn status(&self) -> u32 { pub fn status(&self) -> u32 {
match *self { match *self {
ExecuteError::BadRequest(_) => VIRTIO_BLK_S_IOERR, ExecuteError::BadRequest(_) => VIRTIO_BLK_S_IOERR,
ExecuteError::Flush(_) => VIRTIO_BLK_S_IOERR, ExecuteError::Flush(_) => VIRTIO_BLK_S_IOERR,
@@ -90,31 +94,251 @@ impl ExecuteError {
pub trait DiskFile: Read + Seek + Write + Clone {} pub trait DiskFile: Read + Seek + Write + Clone {}
impl<D: Read + Seek + Write + Clone> DiskFile for D {} impl<D: Read + Seek + Write + Clone> DiskFile for D {}
#[derive(Debug)]
pub struct RawFile { pub struct RawFile {
file: File, file: File,
alignment: usize,
position: u64,
}
const BLK_ALIGNMENTS: [usize; 2] = [512, 4096];
fn is_valid_alignment(fd: RawFd, alignment: usize) -> bool {
let layout = Layout::from_size_align(alignment, alignment).unwrap();
let ptr = unsafe { alloc_zeroed(layout) };
let ret = unsafe {
::libc::pread(
fd,
ptr as *mut c_void,
alignment,
alignment.try_into().unwrap(),
)
};
unsafe { dealloc(ptr, layout) };
ret >= 0
} }
impl RawFile { impl RawFile {
pub fn new(file: File) -> Self { pub fn new(file: File, direct_io: bool) -> Self {
RawFile { file } // Assume no alignment restrictions if we aren't using O_DIRECT.
let mut alignment = 0;
if direct_io {
for align in &BLK_ALIGNMENTS {
if is_valid_alignment(file.as_raw_fd(), *align) {
alignment = *align;
break;
}
}
}
RawFile {
file,
alignment: alignment.try_into().unwrap(),
position: 0,
}
}
fn round_up(&self, offset: u64) -> u64 {
let align: u64 = self.alignment.try_into().unwrap();
((offset / (align + 1)) + 1) * align
}
fn round_down(&self, offset: u64) -> u64 {
let align: u64 = self.alignment.try_into().unwrap();
(offset / align) * align
}
fn is_aligned(&self, buf: &[u8]) -> bool {
if self.alignment == 0 {
return true;
}
let align64: u64 = self.alignment.try_into().unwrap();
(self.position % align64 == 0)
&& ((buf.as_ptr() as usize) % self.alignment == 0)
&& (buf.len() % self.alignment == 0)
}
pub fn set_len(&self, size: u64) -> std::io::Result<()> {
self.file.set_len(size)
}
pub fn metadata(&self) -> std::io::Result<Metadata> {
self.file.metadata()
}
pub fn try_clone(&self) -> std::io::Result<RawFile> {
Ok(RawFile {
file: self.file.try_clone().expect("RawFile cloning failed"),
alignment: self.alignment,
position: self.position,
})
}
pub fn sync_all(&self) -> std::io::Result<()> {
self.file.sync_all()
}
pub fn sync_data(&self) -> std::io::Result<()> {
self.file.sync_data()
} }
} }
impl Read for RawFile { impl Read for RawFile {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> { fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
self.file.read(buf) if self.is_aligned(buf) {
} match self.file.read(buf) {
} Ok(r) => {
self.position = self.position.checked_add(r.try_into().unwrap()).unwrap();
Ok(r)
}
Err(e) => Err(e),
}
} else {
let rounded_pos: u64 = self.round_down(self.position);
let file_offset: usize = self
.position
.checked_sub(rounded_pos)
.unwrap()
.try_into()
.unwrap();
let buf_len: usize = buf.len();
let rounded_len: usize = self
.round_up(
file_offset
.checked_add(buf_len)
.unwrap()
.try_into()
.unwrap(),
)
.try_into()
.unwrap();
impl Seek for RawFile { let layout = Layout::from_size_align(rounded_len, self.alignment).unwrap();
fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> { let tmp_ptr = unsafe { alloc_zeroed(layout) };
self.file.seek(pos) let tmp_buf = unsafe { slice::from_raw_parts_mut(tmp_ptr, rounded_len) };
// This can eventually replaced with read_at once its interface
// has been stabilized.
let ret = unsafe {
::libc::pread64(
self.file.as_raw_fd(),
tmp_buf.as_mut_ptr() as *mut c_void,
tmp_buf.len(),
rounded_pos.try_into().unwrap(),
)
};
if ret < 0 {
unsafe { dealloc(tmp_ptr, layout) };
return Err(io::Error::last_os_error());
}
let read: usize = ret.try_into().unwrap();
if read < file_offset {
unsafe { dealloc(tmp_ptr, layout) };
return Ok(0);
}
let mut to_copy = read - file_offset;
if to_copy > buf_len {
to_copy = buf_len;
}
buf.copy_from_slice(&tmp_buf[file_offset..(file_offset + buf_len)]);
unsafe { dealloc(tmp_ptr, layout) };
self.seek(SeekFrom::Current(to_copy.try_into().unwrap()))
.unwrap();
Ok(to_copy.try_into().unwrap())
}
} }
} }
impl Write for RawFile { impl Write for RawFile {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> { fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.file.write(buf) if self.is_aligned(buf) {
match self.file.write(buf) {
Ok(r) => {
self.position = self.position.checked_add(r.try_into().unwrap()).unwrap();
Ok(r)
}
Err(e) => Err(e),
}
} else {
let rounded_pos: u64 = self.round_down(self.position);
let file_offset: usize = self
.position
.checked_sub(rounded_pos)
.unwrap()
.try_into()
.unwrap();
let buf_len: usize = buf.len();
let rounded_len: usize = self
.round_up(
file_offset
.checked_add(buf_len)
.unwrap()
.try_into()
.unwrap(),
)
.try_into()
.unwrap();
let layout = Layout::from_size_align(rounded_len, self.alignment).unwrap();
let tmp_ptr = unsafe { alloc_zeroed(layout) };
let tmp_buf = unsafe { slice::from_raw_parts_mut(tmp_ptr, rounded_len) };
// This can eventually replaced with read_at once its interface
// has been stabilized.
let ret = unsafe {
::libc::pread64(
self.file.as_raw_fd(),
tmp_buf.as_mut_ptr() as *mut c_void,
tmp_buf.len(),
rounded_pos.try_into().unwrap(),
)
};
if ret < 0 {
unsafe { dealloc(tmp_ptr, layout) };
return Err(io::Error::last_os_error());
};
tmp_buf[file_offset..(file_offset + buf_len)].copy_from_slice(buf);
// This can eventually replaced with write_at once its interface
// has been stabilized.
let ret = unsafe {
::libc::pwrite64(
self.file.as_raw_fd(),
tmp_buf.as_ptr() as *const c_void,
tmp_buf.len(),
rounded_pos.try_into().unwrap(),
)
};
unsafe { dealloc(tmp_ptr, layout) };
if ret < 0 {
return Err(io::Error::last_os_error());
}
let written: usize = ret.try_into().unwrap();
if written < file_offset {
Ok(0)
} else {
let mut to_seek = written - file_offset;
if to_seek > buf_len {
to_seek = buf_len;
}
self.seek(SeekFrom::Current(to_seek.try_into().unwrap()))
.unwrap();
Ok(to_seek.try_into().unwrap())
}
}
} }
fn flush(&mut self) -> std::io::Result<()> { fn flush(&mut self) -> std::io::Result<()> {
@@ -122,16 +346,62 @@ impl Write for RawFile {
} }
} }
impl Seek for RawFile {
fn seek(&mut self, newpos: SeekFrom) -> std::io::Result<u64> {
match self.file.seek(newpos) {
Ok(pos) => {
self.position = pos;
Ok(pos)
}
Err(e) => Err(e),
}
}
}
impl PunchHole for RawFile {
fn punch_hole(&mut self, offset: u64, length: u64) -> std::io::Result<()> {
self.file.punch_hole(offset, length)
}
}
impl SeekHole for RawFile {
fn seek_hole(&mut self, offset: u64) -> std::io::Result<Option<u64>> {
match self.file.seek_hole(offset) {
Ok(pos) => {
if let Some(p) = pos {
self.position = p;
}
Ok(pos)
}
Err(e) => Err(e),
}
}
fn seek_data(&mut self, offset: u64) -> std::io::Result<Option<u64>> {
match self.file.seek_data(offset) {
Ok(pos) => {
if let Some(p) = pos {
self.position = p;
}
Ok(pos)
}
Err(e) => Err(e),
}
}
}
impl Clone for RawFile { impl Clone for RawFile {
fn clone(&self) -> Self { fn clone(&self) -> Self {
RawFile { RawFile {
file: self.file.try_clone().expect("RawFile cloning failed"), file: self.file.try_clone().expect("RawFile cloning failed"),
alignment: self.alignment,
position: self.position,
} }
} }
} }
#[derive(Clone, Copy, Debug, PartialEq)] #[derive(Clone, Copy, Debug, PartialEq)]
enum RequestType { pub enum RequestType {
In, In,
Out, Out,
Flush, Flush,
@@ -139,7 +409,7 @@ enum RequestType {
Unsupported(u32), Unsupported(u32),
} }
fn request_type( pub fn request_type(
mem: &GuestMemoryMmap, mem: &GuestMemoryMmap,
desc_addr: GuestAddress, desc_addr: GuestAddress,
) -> result::Result<RequestType, Error> { ) -> result::Result<RequestType, Error> {
@@ -174,12 +444,11 @@ fn build_device_id(disk_path: &PathBuf) -> result::Result<String, Error> {
blk_metadata.st_dev(), blk_metadata.st_dev(),
blk_metadata.st_rdev(), blk_metadata.st_rdev(),
blk_metadata.st_ino() blk_metadata.st_ino()
) );
.to_owned();
Ok(device_id) Ok(device_id)
} }
fn build_disk_image_id(disk_path: &PathBuf) -> Vec<u8> { pub fn build_disk_image_id(disk_path: &PathBuf) -> Vec<u8> {
let mut default_disk_image_id = vec![0; VIRTIO_BLK_ID_BYTES as usize]; let mut default_disk_image_id = vec![0; VIRTIO_BLK_ID_BYTES as usize];
match build_device_id(disk_path) { match build_device_id(disk_path) {
Err(_) => { Err(_) => {
@@ -196,16 +465,16 @@ fn build_disk_image_id(disk_path: &PathBuf) -> Vec<u8> {
default_disk_image_id default_disk_image_id
} }
struct Request { pub struct Request {
request_type: RequestType, request_type: RequestType,
sector: u64, sector: u64,
data_addr: GuestAddress, data_addr: GuestAddress,
data_len: u32, data_len: u32,
status_addr: GuestAddress, pub status_addr: GuestAddress,
} }
impl Request { impl Request {
fn parse( pub fn parse(
avail_desc: &DescriptorChain, avail_desc: &DescriptorChain,
mem: &GuestMemoryMmap, mem: &GuestMemoryMmap,
) -> result::Result<Request, Error> { ) -> result::Result<Request, Error> {
@@ -269,7 +538,7 @@ impl Request {
} }
#[allow(clippy::ptr_arg)] #[allow(clippy::ptr_arg)]
fn execute<T: Seek + Read + Write>( pub fn execute<T: Seek + Read + Write>(
&self, &self,
disk: &mut T, disk: &mut T,
disk_nsectors: u64, disk_nsectors: u64,
@@ -320,27 +589,31 @@ impl Request {
} }
struct BlockEpollHandler<T: DiskFile> { struct BlockEpollHandler<T: DiskFile> {
queues: Vec<Queue>, queue: Queue,
mem: Arc<RwLock<GuestMemoryMmap>>, mem: Arc<ArcSwap<GuestMemoryMmap>>,
disk_image: T, disk_image: Arc<Mutex<T>>,
disk_nsectors: u64, disk_nsectors: u64,
interrupt_cb: Arc<VirtioInterrupt>, interrupt_cb: Arc<dyn VirtioInterrupt>,
disk_image_id: Vec<u8>, disk_image_id: Vec<u8>,
kill_evt: EventFd,
pause_evt: EventFd,
} }
impl<T: DiskFile> BlockEpollHandler<T> { impl<T: DiskFile> BlockEpollHandler<T> {
fn process_queue(&mut self, queue_index: usize) -> bool { fn process_queue(&mut self) -> bool {
let queue = &mut self.queues[queue_index]; let queue = &mut self.queue;
let mut used_desc_heads = [(0, 0); QUEUE_SIZE as usize]; let mut used_desc_heads = Vec::new();
let mut used_count = 0; let mut used_count = 0;
let mem = self.mem.read().unwrap(); let mem = self.mem.load();
for avail_desc in queue.iter(&mem) { for avail_desc in queue.iter(&mem) {
let len; let len;
match Request::parse(&avail_desc, &mem) { match Request::parse(&avail_desc, &mem) {
Ok(request) => { Ok(request) => {
let mut disk_image_locked = self.disk_image.lock().unwrap();
let mut disk_image = disk_image_locked.deref_mut();
let status = match request.execute( let status = match request.execute(
&mut self.disk_image, &mut disk_image,
self.disk_nsectors, self.disk_nsectors,
&mem, &mem,
&self.disk_image_id, &self.disk_image_id,
@@ -364,42 +637,45 @@ impl<T: DiskFile> BlockEpollHandler<T> {
len = 0; len = 0;
} }
} }
used_desc_heads[used_count] = (avail_desc.index, len); used_desc_heads.push((avail_desc.index, len));
used_count += 1; used_count += 1;
} }
for &(desc_index, len) in &used_desc_heads[..used_count] { for &(desc_index, len) in used_desc_heads.iter() {
queue.add_used(&mem, desc_index, len); queue.add_used(&mem, desc_index, len);
} }
used_count > 0 used_count > 0
} }
fn signal_used_queue(&self, queue_index: usize) -> result::Result<(), DeviceError> { fn signal_used_queue(&self) -> result::Result<(), DeviceError> {
(self.interrupt_cb)(&VirtioInterruptType::Queue, Some(&self.queues[queue_index])).map_err( self.interrupt_cb
|e| { .trigger(&VirtioInterruptType::Queue, Some(&self.queue))
.map_err(|e| {
error!("Failed to signal used queue: {:?}", e); error!("Failed to signal used queue: {:?}", e);
DeviceError::FailedSignalingUsedQueue(e) DeviceError::FailedSignalingUsedQueue(e)
}, })
)
} }
#[allow(dead_code)] #[allow(dead_code)]
fn update_disk_image( fn update_disk_image(
&mut self, &mut self,
disk_image: T, mut disk_image: T,
disk_path: &PathBuf, disk_path: &PathBuf,
) -> result::Result<(), DeviceError> { ) -> result::Result<(), DeviceError> {
self.disk_image = disk_image; self.disk_nsectors = disk_image
self.disk_nsectors = self
.disk_image
.seek(SeekFrom::End(0)) .seek(SeekFrom::End(0))
.map_err(DeviceError::IoError)? .map_err(DeviceError::IoError)?
/ SECTOR_SIZE; / SECTOR_SIZE;
self.disk_image_id = build_disk_image_id(disk_path); self.disk_image_id = build_disk_image_id(disk_path);
self.disk_image = Arc::new(Mutex::new(disk_image));
Ok(()) Ok(())
} }
fn run(&mut self, queue_evt: EventFd, kill_evt: EventFd) -> result::Result<(), DeviceError> { fn run(
&mut self,
queue_evt: EventFd,
paused: Arc<AtomicBool>,
) -> result::Result<(), DeviceError> {
// Create the epoll file descriptor // Create the epoll file descriptor
let epoll_fd = epoll::create(true).map_err(DeviceError::EpollCreateFd)?; let epoll_fd = epoll::create(true).map_err(DeviceError::EpollCreateFd)?;
@@ -414,10 +690,17 @@ impl<T: DiskFile> BlockEpollHandler<T> {
epoll::ctl( epoll::ctl(
epoll_fd, epoll_fd,
epoll::ControlOptions::EPOLL_CTL_ADD, epoll::ControlOptions::EPOLL_CTL_ADD,
kill_evt.as_raw_fd(), self.kill_evt.as_raw_fd(),
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(KILL_EVENT)), epoll::Event::new(epoll::Events::EPOLLIN, u64::from(KILL_EVENT)),
) )
.map_err(DeviceError::EpollCtl)?; .map_err(DeviceError::EpollCtl)?;
epoll::ctl(
epoll_fd,
epoll::ControlOptions::EPOLL_CTL_ADD,
self.pause_evt.as_raw_fd(),
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(PAUSE_EVENT)),
)
.map_err(DeviceError::EpollCtl)?;
const EPOLL_EVENTS_LEN: usize = 100; const EPOLL_EVENTS_LEN: usize = 100;
let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN]; let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN];
@@ -448,8 +731,8 @@ impl<T: DiskFile> BlockEpollHandler<T> {
if let Err(e) = queue_evt.read() { if let Err(e) = queue_evt.read() {
error!("Failed to get queue event: {:?}", e); error!("Failed to get queue event: {:?}", e);
break 'epoll; break 'epoll;
} else if self.process_queue(0) { } else if self.process_queue() {
if let Err(e) = self.signal_used_queue(0) { if let Err(e) = self.signal_used_queue() {
error!("Failed to signal used queue: {:?}", e); error!("Failed to signal used queue: {:?}", e);
break 'epoll; break 'epoll;
} }
@@ -459,6 +742,15 @@ impl<T: DiskFile> BlockEpollHandler<T> {
debug!("KILL_EVENT received, stopping epoll loop"); debug!("KILL_EVENT received, stopping epoll loop");
break 'epoll; break 'epoll;
} }
PAUSE_EVENT => {
debug!("PAUSE_EVENT received, pausing virtio-block epoll loop");
// We loop here to handle spurious park() returns.
// Until we have not resumed, the paused boolean will
// be true.
while paused.load(Ordering::SeqCst) {
thread::park();
}
}
_ => { _ => {
error!("Unknown event for virtio-block"); error!("Unknown event for virtio-block");
} }
@@ -470,29 +762,57 @@ impl<T: DiskFile> BlockEpollHandler<T> {
} }
} }
#[derive(Copy, Clone, Debug, Default)]
#[repr(C, packed)]
pub struct VirtioBlockGeometry {
pub cylinders: u16,
pub heads: u8,
pub sectors: u8,
}
unsafe impl ByteValued for VirtioBlockGeometry {}
#[derive(Copy, Clone, Debug, Default)]
#[repr(C, packed)]
pub struct VirtioBlockConfig {
pub capacity: u64,
pub size_max: u32,
pub seg_max: u32,
pub geometry: VirtioBlockGeometry,
pub blk_size: u32,
pub physical_block_exp: u8,
pub alignment_offset: u8,
pub min_io_size: u16,
pub opt_io_size: u32,
pub wce: u8,
unused: u8,
pub num_queues: u16,
pub max_discard_sectors: u32,
pub max_discard_seg: u32,
pub discard_sector_alignment: u32,
pub max_write_zeroes_sectors: u32,
pub max_write_zeroes_seg: u32,
pub write_zeroes_may_unmap: u8,
unused1: [u8; 3],
}
unsafe impl ByteValued for VirtioBlockConfig {}
/// Virtio device for exposing block level read/write operations on a host file. /// Virtio device for exposing block level read/write operations on a host file.
pub struct Block<T: DiskFile> { pub struct Block<T: DiskFile> {
kill_evt: Option<EventFd>, kill_evt: Option<EventFd>,
disk_image: Option<T>, disk_image: Arc<Mutex<T>>,
disk_path: PathBuf, disk_path: PathBuf,
disk_nsectors: u64, disk_nsectors: u64,
avail_features: u64, avail_features: u64,
acked_features: u64, acked_features: u64,
config_space: Vec<u8>, config: VirtioBlockConfig,
queue_evt: Option<EventFd>, queue_evts: Option<Vec<EventFd>>,
interrupt_cb: Option<Arc<VirtioInterrupt>>, interrupt_cb: Option<Arc<dyn VirtioInterrupt>>,
} epoll_threads: Option<Vec<thread::JoinHandle<result::Result<(), DeviceError>>>>,
pause_evt: Option<EventFd>,
pub fn build_config_space(disk_size: u64) -> Vec<u8> { paused: Arc<AtomicBool>,
// We only support disk size, which uses the first two words of the configuration space. queue_size: Vec<u16>,
// If the image is not a multiple of the sector size, the tail bits are not exposed.
// The config space is little endian.
let mut config = Vec::with_capacity(CONFIG_SPACE_SIZE);
let num_sectors = disk_size >> SECTOR_SHIFT;
for i in 0..8 {
config.push((num_sectors >> (8 * i)) as u8);
}
config
} }
impl<T: DiskFile> Block<T> { impl<T: DiskFile> Block<T> {
@@ -504,6 +824,8 @@ impl<T: DiskFile> Block<T> {
disk_path: PathBuf, disk_path: PathBuf,
is_disk_read_only: bool, is_disk_read_only: bool,
iommu: bool, iommu: bool,
num_queues: usize,
queue_size: u16,
) -> io::Result<Block<T>> { ) -> io::Result<Block<T>> {
let disk_size = disk_image.seek(SeekFrom::End(0))? as u64; let disk_size = disk_image.seek(SeekFrom::End(0))? as u64;
if disk_size % SECTOR_SIZE != 0 { if disk_size % SECTOR_SIZE != 0 {
@@ -522,18 +844,33 @@ impl<T: DiskFile> Block<T> {
if is_disk_read_only { if is_disk_read_only {
avail_features |= 1u64 << VIRTIO_BLK_F_RO; avail_features |= 1u64 << VIRTIO_BLK_F_RO;
}
let disk_nsectors = disk_size / SECTOR_SIZE;
let mut config = VirtioBlockConfig {
capacity: disk_nsectors,
..Default::default()
}; };
if num_queues > 1 {
avail_features |= 1u64 << VIRTIO_BLK_F_MQ;
config.num_queues = num_queues as u16;
}
Ok(Block { Ok(Block {
kill_evt: None, kill_evt: None,
disk_image: Some(disk_image), disk_image: Arc::new(Mutex::new(disk_image)),
disk_path, disk_path,
disk_nsectors: disk_size / SECTOR_SIZE, disk_nsectors,
avail_features, avail_features,
acked_features: 0u64, acked_features: 0u64,
config_space: build_config_space(disk_size), config,
queue_evt: None, queue_evts: None,
interrupt_cb: None, interrupt_cb: None,
epoll_threads: None,
pause_evt: None,
paused: Arc::new(AtomicBool::new(false)),
queue_size: vec![queue_size; num_queues],
}) })
} }
} }
@@ -553,32 +890,15 @@ impl<T: 'static + DiskFile + Send> VirtioDevice for Block<T> {
} }
fn queue_max_sizes(&self) -> &[u16] { fn queue_max_sizes(&self) -> &[u16] {
QUEUE_SIZES self.queue_size.as_slice()
} }
fn features(&self, page: u32) -> u32 { fn features(&self) -> u64 {
match page { self.avail_features
// Get the lower 32-bits of the features bitfield.
0 => self.avail_features as u32,
// Get the upper 32-bits of the features bitfield.
1 => (self.avail_features >> 32) as u32,
_ => {
warn!("Received request for unknown features page.");
0u32
}
}
} }
fn ack_features(&mut self, page: u32, value: u32) { fn ack_features(&mut self, value: u64) {
let mut v = match page { let mut v = value;
0 => u64::from(value),
1 => u64::from(value) << 32,
_ => {
warn!("Cannot acknowledge unknown features page.");
0u64
}
};
// Check if the guest is ACK'ing a feature that we didn't claim to have. // Check if the guest is ACK'ing a feature that we didn't claim to have.
let unrequested_features = v & !self.avail_features; let unrequested_features = v & !self.avail_features;
if unrequested_features != 0 { if unrequested_features != 0 {
@@ -591,95 +911,128 @@ impl<T: 'static + DiskFile + Send> VirtioDevice for Block<T> {
} }
fn read_config(&self, offset: u64, mut data: &mut [u8]) { fn read_config(&self, offset: u64, mut data: &mut [u8]) {
let config_len = self.config_space.len() as u64; let config_slice = self.config.as_slice();
let config_len = config_slice.len() as u64;
if offset >= config_len { if offset >= config_len {
error!("Failed to read config space"); error!("Failed to read config space");
return; return;
} }
if let Some(end) = offset.checked_add(data.len() as u64) { if let Some(end) = offset.checked_add(data.len() as u64) {
// This write can't fail, offset and end are checked against config_len. // This write can't fail, offset and end are checked against config_len.
data.write_all(&self.config_space[offset as usize..cmp::min(end, config_len) as usize]) data.write_all(&config_slice[offset as usize..cmp::min(end, config_len) as usize])
.unwrap(); .unwrap();
} }
} }
fn write_config(&mut self, offset: u64, data: &[u8]) { fn write_config(&mut self, offset: u64, data: &[u8]) {
let config_slice = self.config.as_mut_slice();
let data_len = data.len() as u64; let data_len = data.len() as u64;
let config_len = self.config_space.len() as u64; let config_len = config_slice.len() as u64;
if offset + data_len > config_len { if offset + data_len > config_len {
error!("Failed to write config space"); error!("Failed to write config space");
return; return;
} }
let (_, right) = self.config_space.split_at_mut(offset as usize); let (_, right) = config_slice.split_at_mut(offset as usize);
right.copy_from_slice(&data[..]); right.copy_from_slice(&data[..]);
} }
fn activate( fn activate(
&mut self, &mut self,
mem: Arc<RwLock<GuestMemoryMmap>>, mem: Arc<ArcSwap<GuestMemoryMmap>>,
interrupt_cb: Arc<VirtioInterrupt>, interrupt_cb: Arc<dyn VirtioInterrupt>,
queues: Vec<Queue>, mut queues: Vec<Queue>,
mut queue_evts: Vec<EventFd>, mut queue_evts: Vec<EventFd>,
) -> ActivateResult { ) -> ActivateResult {
if queues.len() != NUM_QUEUES || queue_evts.len() != NUM_QUEUES { if queues.len() != self.queue_size.len() || queue_evts.len() != self.queue_size.len() {
error!( error!(
"Cannot perform activate. Expected {} queue(s), got {}", "Cannot perform activate. Expected {} queue(s), got {}",
NUM_QUEUES, self.queue_size.len(),
queues.len() queues.len()
); );
return Err(ActivateError::BadActivate); return Err(ActivateError::BadActivate);
} }
let (self_kill_evt, kill_evt) = let (self_kill_evt, kill_evt) = EventFd::new(EFD_NONBLOCK)
match EventFd::new(EFD_NONBLOCK).and_then(|e| Ok((e.try_clone()?, e))) { .and_then(|e| Ok((e.try_clone()?, e)))
Ok(v) => v, .map_err(|e| {
Err(e) => { error!("failed creating kill EventFd pair: {}", e);
error!("failed creating kill EventFd pair: {}", e);
return Err(ActivateError::BadActivate);
}
};
self.kill_evt = Some(self_kill_evt);
if let Some(disk_image) = self.disk_image.clone() {
let disk_image_id = build_disk_image_id(&self.disk_path);
// Save the interrupt EventFD as we need to return it on reset
// but clone it to pass into the thread.
self.interrupt_cb = Some(interrupt_cb);
let interrupt_cb = self.interrupt_cb.as_ref().unwrap().clone();
// Save the queue EventFD as we need to return it on reset
// but clone it to pass into the thread.
self.queue_evt = Some(queue_evts.remove(0));
let queue_evt = self.queue_evt.as_ref().unwrap().try_clone().map_err(|e| {
error!("failed to clone queue EventFd: {}", e);
ActivateError::BadActivate ActivateError::BadActivate
})?; })?;
self.kill_evt = Some(self_kill_evt);
let (self_pause_evt, pause_evt) = EventFd::new(EFD_NONBLOCK)
.and_then(|e| Ok((e.try_clone()?, e)))
.map_err(|e| {
error!("failed creating pause EventFd pair: {}", e);
ActivateError::BadActivate
})?;
self.pause_evt = Some(self_pause_evt);
let disk_image_id = build_disk_image_id(&self.disk_path);
let mut tmp_queue_evts: Vec<EventFd> = Vec::new();
for queue_evt in queue_evts.iter() {
// Save the queue EventFD as we need to return it on reset
// but clone it to pass into the thread.
tmp_queue_evts.push(queue_evt.try_clone().map_err(|e| {
error!("failed to clone queue EventFd: {}", e);
ActivateError::BadActivate
})?);
}
self.queue_evts = Some(tmp_queue_evts);
let mut tmp_queue_evts: Vec<EventFd> = Vec::new();
for queue_evt in queue_evts.iter() {
// Save the queue EventFD as we need to return it on reset
// but clone it to pass into the thread.
tmp_queue_evts.push(queue_evt.try_clone().map_err(|e| {
error!("failed to clone queue EventFd: {}", e);
ActivateError::BadActivate
})?);
}
self.queue_evts = Some(tmp_queue_evts);
let mut epoll_threads = Vec::new();
for _ in 0..self.queue_size.len() {
let mut handler = BlockEpollHandler { let mut handler = BlockEpollHandler {
queues, queue: queues.remove(0),
mem, mem: mem.clone(),
disk_image, disk_image: self.disk_image.clone(),
disk_nsectors: self.disk_nsectors, disk_nsectors: self.disk_nsectors,
interrupt_cb, interrupt_cb: interrupt_cb.clone(),
disk_image_id, disk_image_id: disk_image_id.clone(),
kill_evt: kill_evt.try_clone().unwrap(),
pause_evt: pause_evt.try_clone().unwrap(),
}; };
let worker_result = thread::Builder::new() let queue_evt = queue_evts.remove(0);
let paused = self.paused.clone();
thread::Builder::new()
.name("virtio_blk".to_string()) .name("virtio_blk".to_string())
.spawn(move || handler.run(queue_evt, kill_evt)); .spawn(move || handler.run(queue_evt, paused))
.map(|thread| epoll_threads.push(thread))
if let Err(e) = worker_result { .map_err(|e| {
error!("failed to spawn virtio_blk worker: {}", e); error!("failed to clone the virtio-blk epoll thread: {}", e);
return Err(ActivateError::BadActivate); ActivateError::BadActivate
} })?;
return Ok(());
} }
Err(ActivateError::BadActivate)
// Save the interrupt EventFD as we need to return it on reset
// but clone it to pass into the thread.
self.interrupt_cb = Some(interrupt_cb);
self.epoll_threads = Some(epoll_threads);
Ok(())
} }
fn reset(&mut self) -> Option<(Arc<VirtioInterrupt>, Vec<EventFd>)> { fn reset(&mut self) -> Option<(Arc<dyn VirtioInterrupt>, Vec<EventFd>)> {
// We first must resume the virtio thread if it was paused.
if self.pause_evt.take().is_some() {
self.resume().ok()?;
}
if let Some(kill_evt) = self.kill_evt.take() { if let Some(kill_evt) = self.kill_evt.take() {
// Ignore the result because there is nothing we can do about it. // Ignore the result because there is nothing we can do about it.
let _ = kill_evt.write(1); let _ = kill_evt.write(1);
@@ -688,7 +1041,11 @@ impl<T: 'static + DiskFile + Send> VirtioDevice for Block<T> {
// Return the interrupt and queue EventFDs // Return the interrupt and queue EventFDs
Some(( Some((
self.interrupt_cb.take().unwrap(), self.interrupt_cb.take().unwrap(),
vec![self.queue_evt.take().unwrap()], self.queue_evts.take().unwrap(),
)) ))
} }
} }
virtio_pausable!(Block, T: 'static + DiskFile + Send);
impl<T: 'static + DiskFile + Send> Snapshotable for Block<T> {}
impl<T: 'static + DiskFile + Send> Migratable for Block<T> {}

View File

@@ -1,6 +1,13 @@
// Copyright 2019 Intel Corporation. All Rights Reserved. // Copyright 2019 Intel Corporation. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use super::Error as DeviceError;
use super::{
ActivateError, ActivateResult, DeviceEventT, Queue, VirtioDevice, VirtioDeviceType,
VirtioInterruptType, VIRTIO_F_IOMMU_PLATFORM, VIRTIO_F_VERSION_1,
};
use crate::VirtioInterrupt;
use arc_swap::ArcSwap;
use epoll; use epoll;
use libc::EFD_NONBLOCK; use libc::EFD_NONBLOCK;
use std; use std;
@@ -11,16 +18,10 @@ use std::io::Write;
use std::ops::DerefMut; use std::ops::DerefMut;
use std::os::unix::io::AsRawFd; use std::os::unix::io::AsRawFd;
use std::result; use std::result;
use std::sync::{Arc, Mutex, RwLock}; use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::{Arc, Mutex};
use std::thread; use std::thread;
use vm_device::{Migratable, MigratableError, Pausable, Snapshotable};
use super::Error as DeviceError;
use super::{
ActivateError, ActivateResult, DeviceEventT, Queue, VirtioDevice, VirtioDeviceType,
VirtioInterruptType, VIRTIO_F_IOMMU_PLATFORM, VIRTIO_F_VERSION_1,
};
use crate::VirtioInterrupt;
use std::sync::atomic::{AtomicU64, Ordering};
use vm_memory::{ByteValued, Bytes, GuestMemoryMmap}; use vm_memory::{ByteValued, Bytes, GuestMemoryMmap};
use vmm_sys_util::eventfd::EventFd; use vmm_sys_util::eventfd::EventFd;
@@ -37,6 +38,8 @@ const INPUT_EVENT: DeviceEventT = 2;
const KILL_EVENT: DeviceEventT = 3; const KILL_EVENT: DeviceEventT = 3;
// Console configuration change event is triggered. // Console configuration change event is triggered.
const CONFIG_EVENT: DeviceEventT = 4; const CONFIG_EVENT: DeviceEventT = 4;
// The device should be paused.
const PAUSE_EVENT: DeviceEventT = 5;
//Console size feature bit //Console size feature bit
const VIRTIO_CONSOLE_F_SIZE: u64 = 0; const VIRTIO_CONSOLE_F_SIZE: u64 = 0;
@@ -55,8 +58,8 @@ unsafe impl ByteValued for VirtioConsoleConfig {}
struct ConsoleEpollHandler { struct ConsoleEpollHandler {
queues: Vec<Queue>, queues: Vec<Queue>,
mem: Arc<RwLock<GuestMemoryMmap>>, mem: Arc<ArcSwap<GuestMemoryMmap>>,
interrupt_cb: Arc<VirtioInterrupt>, interrupt_cb: Arc<dyn VirtioInterrupt>,
in_buffer: Arc<Mutex<VecDeque<u8>>>, in_buffer: Arc<Mutex<VecDeque<u8>>>,
out: Arc<Mutex<Box<dyn io::Write + Send + Sync + 'static>>>, out: Arc<Mutex<Box<dyn io::Write + Send + Sync + 'static>>>,
input_queue_evt: EventFd, input_queue_evt: EventFd,
@@ -64,6 +67,7 @@ struct ConsoleEpollHandler {
input_evt: EventFd, input_evt: EventFd,
config_evt: EventFd, config_evt: EventFd,
kill_evt: EventFd, kill_evt: EventFd,
pause_evt: EventFd,
} }
impl ConsoleEpollHandler { impl ConsoleEpollHandler {
@@ -75,37 +79,28 @@ impl ConsoleEpollHandler {
*/ */
fn process_input_queue(&mut self) -> bool { fn process_input_queue(&mut self) -> bool {
let mut in_buffer = self.in_buffer.lock().unwrap(); let mut in_buffer = self.in_buffer.lock().unwrap();
let count = in_buffer.len();
let recv_queue = &mut self.queues[0]; //receiveq let recv_queue = &mut self.queues[0]; //receiveq
let mut used_desc_heads = [(0, 0); QUEUE_SIZE as usize]; let mut used_desc_heads = [(0, 0); QUEUE_SIZE as usize];
let mut used_count = 0; let mut used_count = 0;
let mut write_count = 0;
let mem = self.mem.read().unwrap(); if in_buffer.is_empty() {
return false;
}
let mem = self.mem.load();
for avail_desc in recv_queue.iter(&mem) { for avail_desc in recv_queue.iter(&mem) {
let len; let len = cmp::min(avail_desc.len as u32, in_buffer.len() as u32);
let source_slice = in_buffer.drain(..len as usize).collect::<Vec<u8>>();
let limit = cmp::min(write_count + avail_desc.len as u32, count as u32); if let Err(e) = mem.write_slice(&source_slice[..], avail_desc.addr) {
let source_slice = in_buffer error!("Failed to write slice: {:?}", e);
.drain(write_count as usize..limit as usize) recv_queue.go_to_previous_position();
.collect::<Vec<u8>>(); break;
let write_result = mem.write_slice(&source_slice[..], avail_desc.addr);
match write_result {
Ok(_) => {
len = limit - write_count; //avail_desc.len;
write_count = limit;
}
Err(e) => {
error!("Failed to write slice: {:?}", e);
break;
}
} }
used_desc_heads[used_count] = (avail_desc.index, len); used_desc_heads[used_count] = (avail_desc.index, len);
used_count += 1; used_count += 1;
if write_count >= count as u32 { if in_buffer.is_empty() {
break; break;
} }
} }
@@ -113,6 +108,7 @@ impl ConsoleEpollHandler {
for &(desc_index, len) in &used_desc_heads[..used_count] { for &(desc_index, len) in &used_desc_heads[..used_count] {
recv_queue.add_used(&mem, desc_index, len); recv_queue.add_used(&mem, desc_index, len);
} }
used_count > 0 used_count > 0
} }
@@ -128,7 +124,7 @@ impl ConsoleEpollHandler {
let mut used_desc_heads = [(0, 0); QUEUE_SIZE as usize]; let mut used_desc_heads = [(0, 0); QUEUE_SIZE as usize];
let mut used_count = 0; let mut used_count = 0;
let mem = self.mem.read().unwrap(); let mem = self.mem.load();
for avail_desc in trans_queue.iter(&mem) { for avail_desc in trans_queue.iter(&mem) {
let len; let len;
let mut out = self.out.lock().unwrap(); let mut out = self.out.lock().unwrap();
@@ -151,13 +147,15 @@ impl ConsoleEpollHandler {
} }
fn signal_used_queue(&self) -> result::Result<(), DeviceError> { fn signal_used_queue(&self) -> result::Result<(), DeviceError> {
(self.interrupt_cb)(&VirtioInterruptType::Queue, Some(&self.queues[0])).map_err(|e| { self.interrupt_cb
error!("Failed to signal used queue: {:?}", e); .trigger(&VirtioInterruptType::Queue, Some(&self.queues[0]))
DeviceError::FailedSignalingUsedQueue(e) .map_err(|e| {
}) error!("Failed to signal used queue: {:?}", e);
DeviceError::FailedSignalingUsedQueue(e)
})
} }
fn run(&mut self) -> result::Result<(), DeviceError> { fn run(&mut self, paused: Arc<AtomicBool>) -> result::Result<(), DeviceError> {
// Create the epoll file descriptor // Create the epoll file descriptor
let epoll_fd = epoll::create(true).map_err(DeviceError::EpollCreateFd)?; let epoll_fd = epoll::create(true).map_err(DeviceError::EpollCreateFd)?;
@@ -198,6 +196,13 @@ impl ConsoleEpollHandler {
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(KILL_EVENT)), epoll::Event::new(epoll::Events::EPOLLIN, u64::from(KILL_EVENT)),
) )
.map_err(DeviceError::EpollCtl)?; .map_err(DeviceError::EpollCtl)?;
epoll::ctl(
epoll_fd,
epoll::ControlOptions::EPOLL_CTL_ADD,
self.pause_evt.as_raw_fd(),
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(PAUSE_EVENT)),
)
.map_err(DeviceError::EpollCtl)?;
const EPOLL_EVENTS_LEN: usize = 100; const EPOLL_EVENTS_LEN: usize = 100;
let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN]; let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN];
@@ -228,6 +233,11 @@ impl ConsoleEpollHandler {
if let Err(e) = self.input_queue_evt.read() { if let Err(e) = self.input_queue_evt.read() {
error!("Failed to get queue event: {:?}", e); error!("Failed to get queue event: {:?}", e);
break 'epoll; break 'epoll;
} else if self.process_input_queue() {
if let Err(e) = self.signal_used_queue() {
error!("Failed to signal used queue: {:?}", e);
break 'epoll;
}
} }
} }
OUTPUT_QUEUE_EVENT => { OUTPUT_QUEUE_EVENT => {
@@ -253,8 +263,9 @@ impl ConsoleEpollHandler {
if let Err(e) = self.config_evt.read() { if let Err(e) = self.config_evt.read() {
error!("Failed to get config event: {:?}", e); error!("Failed to get config event: {:?}", e);
break 'epoll; break 'epoll;
} else if let Err(e) = } else if let Err(e) = self
(self.interrupt_cb)(&VirtioInterruptType::Config, None) .interrupt_cb
.trigger(&VirtioInterruptType::Config, None)
{ {
error!("Failed to signal console driver: {:?}", e); error!("Failed to signal console driver: {:?}", e);
} }
@@ -264,6 +275,15 @@ impl ConsoleEpollHandler {
debug!("KILL_EVENT received, stopping epoll loop"); debug!("KILL_EVENT received, stopping epoll loop");
break 'epoll; break 'epoll;
} }
PAUSE_EVENT => {
debug!("PAUSE_EVENT received, pausing virtio-console epoll loop");
// We loop here to handle spurious park() returns.
// Until we have not resumed, the paused boolean will
// be true.
while paused.load(Ordering::SeqCst) {
thread::park();
}
}
_ => { _ => {
error!("Unknown event for virtio-console"); error!("Unknown event for virtio-console");
} }
@@ -323,13 +343,16 @@ impl VirtioConsoleConfig {
/// Virtio device for exposing console to the guest OS through virtio. /// Virtio device for exposing console to the guest OS through virtio.
pub struct Console { pub struct Console {
kill_evt: Option<EventFd>, kill_evt: Option<EventFd>,
pause_evt: Option<EventFd>,
avail_features: u64, avail_features: u64,
acked_features: u64, acked_features: u64,
config: Arc<Mutex<VirtioConsoleConfig>>, config: Arc<Mutex<VirtioConsoleConfig>>,
input: Arc<ConsoleInput>, input: Arc<ConsoleInput>,
out: Arc<Mutex<Box<dyn io::Write + Send + Sync + 'static>>>, out: Arc<Mutex<Box<dyn io::Write + Send + Sync + 'static>>>,
queue_evts: Option<Vec<EventFd>>, queue_evts: Option<Vec<EventFd>>,
interrupt_cb: Option<Arc<VirtioInterrupt>>, interrupt_cb: Option<Arc<dyn VirtioInterrupt>>,
epoll_threads: Option<Vec<thread::JoinHandle<result::Result<(), DeviceError>>>>,
paused: Arc<AtomicBool>,
} }
impl Console { impl Console {
@@ -360,6 +383,7 @@ impl Console {
Ok(( Ok((
Console { Console {
kill_evt: None, kill_evt: None,
pause_evt: None,
avail_features, avail_features,
acked_features: 0u64, acked_features: 0u64,
config: console_config, config: console_config,
@@ -367,6 +391,8 @@ impl Console {
out: Arc::new(Mutex::new(out)), out: Arc::new(Mutex::new(out)),
queue_evts: None, queue_evts: None,
interrupt_cb: None, interrupt_cb: None,
epoll_threads: None,
paused: Arc::new(AtomicBool::new(false)),
}, },
console_input, console_input,
)) ))
@@ -391,29 +417,12 @@ impl VirtioDevice for Console {
QUEUE_SIZES QUEUE_SIZES
} }
fn features(&self, page: u32) -> u32 { fn features(&self) -> u64 {
match page { self.avail_features
// Get the lower 32-bits of the features bitfield.
0 => self.avail_features as u32,
// Get the upper 32-bits of the features bitfield.
1 => (self.avail_features >> 32) as u32,
_ => {
warn!("Received request for unknown features page.");
0u32
}
}
} }
fn ack_features(&mut self, page: u32, value: u32) { fn ack_features(&mut self, value: u64) {
let mut v = match page { let mut v = value;
0 => u64::from(value),
1 => u64::from(value) << 32,
_ => {
warn!("Cannot acknowledge unknown features page.");
0u64
}
};
// Check if the guest is ACK'ing a feature that we didn't claim to have. // Check if the guest is ACK'ing a feature that we didn't claim to have.
let unrequested_features = v & !self.avail_features; let unrequested_features = v & !self.avail_features;
if unrequested_features != 0 { if unrequested_features != 0 {
@@ -447,8 +456,8 @@ impl VirtioDevice for Console {
fn activate( fn activate(
&mut self, &mut self,
mem: Arc<RwLock<GuestMemoryMmap>>, mem: Arc<ArcSwap<GuestMemoryMmap>>,
interrupt_cb: Arc<VirtioInterrupt>, interrupt_cb: Arc<dyn VirtioInterrupt>,
queues: Vec<Queue>, queues: Vec<Queue>,
mut queue_evts: Vec<EventFd>, mut queue_evts: Vec<EventFd>,
) -> ActivateResult { ) -> ActivateResult {
@@ -461,16 +470,23 @@ impl VirtioDevice for Console {
return Err(ActivateError::BadActivate); return Err(ActivateError::BadActivate);
} }
let (self_kill_evt, kill_evt) = let (self_kill_evt, kill_evt) = EventFd::new(EFD_NONBLOCK)
match EventFd::new(EFD_NONBLOCK).and_then(|e| Ok((e.try_clone()?, e))) { .and_then(|e| Ok((e.try_clone()?, e)))
Ok(v) => v, .map_err(|e| {
Err(e) => { error!("failed creating kill EventFd pair: {}", e);
error!("failed creating kill EventFd pair: {}", e); ActivateError::BadActivate
return Err(ActivateError::BadActivate); })?;
}
};
self.kill_evt = Some(self_kill_evt); self.kill_evt = Some(self_kill_evt);
let (self_pause_evt, pause_evt) = EventFd::new(EFD_NONBLOCK)
.and_then(|e| Ok((e.try_clone()?, e)))
.map_err(|e| {
error!("failed creating pause EventFd pair: {}", e);
ActivateError::BadActivate
})?;
self.pause_evt = Some(self_pause_evt);
// Save the interrupt EventFD as we need to return it on reset // Save the interrupt EventFD as we need to return it on reset
// but clone it to pass into the thread. // but clone it to pass into the thread.
self.interrupt_cb = Some(interrupt_cb.clone()); self.interrupt_cb = Some(interrupt_cb.clone());
@@ -491,7 +507,7 @@ impl VirtioDevice for Console {
.store(self.acked_features, Ordering::Relaxed); .store(self.acked_features, Ordering::Relaxed);
if (self.acked_features & (1u64 << VIRTIO_CONSOLE_F_SIZE)) != 0 { if (self.acked_features & (1u64 << VIRTIO_CONSOLE_F_SIZE)) != 0 {
if let Err(e) = (interrupt_cb)(&VirtioInterruptType::Config, None) { if let Err(e) = interrupt_cb.trigger(&VirtioInterruptType::Config, None) {
error!("Failed to signal console driver: {:?}", e); error!("Failed to signal console driver: {:?}", e);
} }
} }
@@ -507,21 +523,31 @@ impl VirtioDevice for Console {
input_evt: self.input.input_evt.try_clone().unwrap(), input_evt: self.input.input_evt.try_clone().unwrap(),
config_evt: self.input.config_evt.try_clone().unwrap(), config_evt: self.input.config_evt.try_clone().unwrap(),
kill_evt, kill_evt,
pause_evt,
}; };
let worker_result = thread::Builder::new() let paused = self.paused.clone();
let mut epoll_threads = Vec::new();
thread::Builder::new()
.name("virtio_console".to_string()) .name("virtio_console".to_string())
.spawn(move || handler.run()); .spawn(move || handler.run(paused))
.map(|thread| epoll_threads.push(thread))
.map_err(|e| {
error!("failed to clone the virtio-console epoll thread: {}", e);
ActivateError::BadActivate
})?;
if let Err(e) = worker_result { self.epoll_threads = Some(epoll_threads);
error!("failed to spawn virtio_console worker: {}", e);
return Err(ActivateError::BadActivate);
}
Ok(()) Ok(())
} }
fn reset(&mut self) -> Option<(Arc<VirtioInterrupt>, Vec<EventFd>)> { fn reset(&mut self) -> Option<(Arc<dyn VirtioInterrupt>, Vec<EventFd>)> {
// We first must resume the virtio thread if it was paused.
if self.pause_evt.take().is_some() {
self.resume().ok()?;
}
if let Some(kill_evt) = self.kill_evt.take() { if let Some(kill_evt) = self.kill_evt.take() {
// Ignore the result because there is nothing we can do about it. // Ignore the result because there is nothing we can do about it.
let _ = kill_evt.write(1); let _ = kill_evt.write(1);
@@ -534,3 +560,7 @@ impl VirtioDevice for Console {
)) ))
} }
} }
virtio_pausable!(Console);
impl Snapshotable for Console {}
impl Migratable for Console {}

View File

@@ -7,7 +7,8 @@
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause // SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
use super::*; use super::*;
use std::sync::{Arc, RwLock}; use arc_swap::ArcSwap;
use std::sync::Arc;
use vm_memory::{GuestAddress, GuestMemoryMmap, GuestUsize}; use vm_memory::{GuestAddress, GuestMemoryMmap, GuestUsize};
use vmm_sys_util::eventfd::EventFd; use vmm_sys_util::eventfd::EventFd;
@@ -16,11 +17,20 @@ pub enum VirtioInterruptType {
Queue, Queue,
} }
pub type VirtioInterrupt = Box< pub trait VirtioInterrupt: Send + Sync {
dyn Fn(&VirtioInterruptType, Option<&Queue>) -> std::result::Result<(), std::io::Error> fn trigger(
+ Send &self,
+ Sync, int_type: &VirtioInterruptType,
>; queue: Option<&Queue>,
) -> std::result::Result<(), std::io::Error>;
fn notifier(
&self,
_int_type: &VirtioInterruptType,
_queue: Option<&Queue>,
) -> Option<&EventFd> {
None
}
}
pub type VirtioIommuRemapping = pub type VirtioIommuRemapping =
Box<dyn Fn(u64) -> std::result::Result<u64, std::io::Error> + Send + Sync>; Box<dyn Fn(u64) -> std::result::Result<u64, std::io::Error> + Send + Sync>;
@@ -52,14 +62,15 @@ pub trait VirtioDevice: Send {
/// The maximum size of each queue that this device supports. /// The maximum size of each queue that this device supports.
fn queue_max_sizes(&self) -> &[u16]; fn queue_max_sizes(&self) -> &[u16];
/// The set of feature bits shifted by `page * 32`. /// The set of feature bits that this device supports.
fn features(&self, page: u32) -> u32 { fn features(&self) -> u64 {
let _ = page;
0 0
} }
/// Acknowledges that this set of features should be enabled. /// Acknowledges that this set of features should be enabled.
fn ack_features(&mut self, page: u32, value: u32); fn ack_features(&mut self, value: u64) {
let _ = value;
}
/// Reads this device configuration space at `offset`. /// Reads this device configuration space at `offset`.
fn read_config(&self, offset: u64, data: &mut [u8]); fn read_config(&self, offset: u64, data: &mut [u8]);
@@ -70,15 +81,15 @@ pub trait VirtioDevice: Send {
/// Activates this device for real usage. /// Activates this device for real usage.
fn activate( fn activate(
&mut self, &mut self,
mem: Arc<RwLock<GuestMemoryMmap>>, mem: Arc<ArcSwap<GuestMemoryMmap>>,
interrupt_evt: Arc<VirtioInterrupt>, interrupt_evt: Arc<dyn VirtioInterrupt>,
queues: Vec<Queue>, queues: Vec<Queue>,
queue_evts: Vec<EventFd>, queue_evts: Vec<EventFd>,
) -> ActivateResult; ) -> ActivateResult;
/// Optionally deactivates this device and returns ownership of the guest memory map, interrupt /// Optionally deactivates this device and returns ownership of the guest memory map, interrupt
/// event, and queue events. /// event, and queue events.
fn reset(&mut self) -> Option<(Arc<VirtioInterrupt>, Vec<EventFd>)> { fn reset(&mut self) -> Option<(Arc<dyn VirtioInterrupt>, Vec<EventFd>)> {
None None
} }
@@ -101,3 +112,123 @@ pub trait VirtioDevice: Send {
pub trait DmaRemapping: Send + Sync { pub trait DmaRemapping: Send + Sync {
fn translate(&self, id: u32, addr: u64) -> std::result::Result<u64, std::io::Error>; fn translate(&self, id: u32, addr: u64) -> std::result::Result<u64, std::io::Error>;
} }
#[macro_export]
macro_rules! virtio_pausable_trait_definition {
() => {
trait VirtioPausable {
fn virtio_pause(&mut self) -> std::result::Result<(), MigratableError>;
fn virtio_resume(&mut self) -> std::result::Result<(), MigratableError>;
}
};
}
#[macro_export]
macro_rules! virtio_pausable_trait_inner {
() => {
// This is the common Pausable trait implementation for virtio.
fn virtio_pause(&mut self) -> result::Result<(), MigratableError> {
debug!(
"Pausing virtio-{}",
VirtioDeviceType::from(self.device_type())
);
self.paused.store(true, Ordering::SeqCst);
if let Some(pause_evt) = &self.pause_evt {
pause_evt
.write(1)
.map_err(|e| MigratableError::Pause(e.into()))?;
}
Ok(())
}
fn virtio_resume(&mut self) -> result::Result<(), MigratableError> {
debug!(
"Resuming virtio-{}",
VirtioDeviceType::from(self.device_type())
);
self.paused.store(false, Ordering::SeqCst);
if let Some(epoll_threads) = &self.epoll_threads {
for i in 0..epoll_threads.len() {
epoll_threads[i].thread().unpark();
}
}
Ok(())
}
};
}
#[macro_export]
macro_rules! virtio_pausable_trait {
($type:ident) => {
virtio_pausable_trait_definition!();
impl VirtioPausable for $type {
virtio_pausable_trait_inner!();
}
};
($type:ident, T: $($bounds:tt)+) => {
virtio_pausable_trait_definition!();
impl<T: $($bounds)+ > VirtioPausable for $type<T> {
virtio_pausable_trait_inner!();
}
};
}
#[macro_export]
macro_rules! virtio_pausable_inner {
($type:ident) => {
fn pause(&mut self) -> result::Result<(), MigratableError> {
self.virtio_pause()
}
fn resume(&mut self) -> result::Result<(), MigratableError> {
self.virtio_resume()
}
};
}
#[macro_export]
macro_rules! virtio_pausable {
($type:ident) => {
virtio_pausable_trait!($type);
impl Pausable for $type {
virtio_pausable_inner!($type);
}
};
// For type bound virtio types
($type:ident, T: $($bounds:tt)+) => {
virtio_pausable_trait!($type, T: $($bounds)+);
impl<T: $($bounds)+ > Pausable for $type<T> {
virtio_pausable_inner!($type);
}
};
}
#[macro_export]
macro_rules! virtio_ctrl_q_pausable {
($type:ident) => {
virtio_pausable_trait!($type);
impl Pausable for $type {
fn pause(&mut self) -> result::Result<(), MigratableError> {
self.virtio_pause()
}
fn resume(&mut self) -> result::Result<(), MigratableError> {
self.virtio_resume()?;
if let Some(ctrl_queue_epoll_thread) = &self.ctrl_queue_epoll_thread {
ctrl_queue_epoll_thread.thread().unpark();
}
Ok(())
}
}
};
}

View File

@@ -2,6 +2,13 @@
// //
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause // SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
use super::Error as DeviceError;
use super::{
ActivateError, ActivateResult, DescriptorChain, DeviceEventT, Queue, VirtioDevice,
VirtioDeviceType, VIRTIO_F_VERSION_1,
};
use crate::{DmaRemapping, VirtioInterrupt, VirtioInterruptType};
use arc_swap::ArcSwap;
use epoll; use epoll;
use libc::EFD_NONBLOCK; use libc::EFD_NONBLOCK;
use std::cmp; use std::cmp;
@@ -12,16 +19,10 @@ use std::mem::size_of;
use std::ops::Bound::Included; use std::ops::Bound::Included;
use std::os::unix::io::AsRawFd; use std::os::unix::io::AsRawFd;
use std::result; use std::result;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, RwLock}; use std::sync::{Arc, RwLock};
use std::thread; use std::thread;
use vm_device::{ExternalDmaMapping, Migratable, MigratableError, Pausable, Snapshotable};
use super::Error as DeviceError;
use super::{
ActivateError, ActivateResult, DescriptorChain, DeviceEventT, Queue, VirtioDevice,
VirtioDeviceType, VIRTIO_F_VERSION_1,
};
use crate::{DmaRemapping, VirtioInterrupt, VirtioInterruptType};
use vm_device::ExternalDmaMapping;
use vm_memory::{Address, ByteValued, Bytes, GuestAddress, GuestMemoryError, GuestMemoryMmap}; use vm_memory::{Address, ByteValued, Bytes, GuestAddress, GuestMemoryError, GuestMemoryMmap};
use vmm_sys_util::eventfd::EventFd; use vmm_sys_util::eventfd::EventFd;
@@ -40,6 +41,19 @@ const REQUEST_Q_EVENT: DeviceEventT = 0;
const EVENT_Q_EVENT: DeviceEventT = 1; const EVENT_Q_EVENT: DeviceEventT = 1;
/// The device has been dropped. /// The device has been dropped.
const KILL_EVENT: DeviceEventT = 2; const KILL_EVENT: DeviceEventT = 2;
/// The device should be paused.
const PAUSE_EVENT: DeviceEventT = 3;
/// PROBE properties size.
/// This is the minimal size to provide at least one RESV_MEM property.
/// Because virtio-iommu expects one MSI reserved region, we must provide it,
/// otherwise the driver in the guest will define a predefined one between
/// 0x8000000 and 0x80FFFFF, which is only relevant for ARM architecture, but
/// will conflict with x86.
const PROBE_PROP_SIZE: u32 =
(size_of::<VirtioIommuProbeProperty>() + size_of::<VirtioIommuProbeResvMem>()) as u32;
const MSI_IOVA_START: u64 = 0xfee0_0000;
const MSI_IOVA_END: u64 = 0xfeef_ffff;
/// Virtio IOMMU features /// Virtio IOMMU features
#[allow(unused)] #[allow(unused)]
@@ -50,35 +64,83 @@ const VIRTIO_IOMMU_F_DOMAIN_BITS: u32 = 1;
const VIRTIO_IOMMU_F_MAP_UNMAP: u32 = 2; const VIRTIO_IOMMU_F_MAP_UNMAP: u32 = 2;
#[allow(unused)] #[allow(unused)]
const VIRTIO_IOMMU_F_BYPASS: u32 = 3; const VIRTIO_IOMMU_F_BYPASS: u32 = 3;
#[allow(unused)]
const VIRTIO_IOMMU_F_PROBE: u32 = 4; const VIRTIO_IOMMU_F_PROBE: u32 = 4;
#[allow(unused)]
const VIRTIO_IOMMU_F_MMIO: u32 = 5;
#[allow(unused)]
const VIRTIO_IOMMU_F_TOPOLOGY: u32 = 6;
// Support 2MiB and 4KiB page sizes. // Support 2MiB and 4KiB page sizes.
#[allow(unused)]
const VIRTIO_IOMMU_PAGE_SIZE_MASK: u64 = (2 << 20) | (4 << 10); const VIRTIO_IOMMU_PAGE_SIZE_MASK: u64 = (2 << 20) | (4 << 10);
#[allow(unused)]
#[derive(Copy, Clone, Debug, Default)] #[derive(Copy, Clone, Debug, Default)]
#[repr(packed)] #[repr(packed)]
struct VirtioIommuRange { struct VirtioIommuRange32 {
start: u32,
end: u32,
}
unsafe impl ByteValued for VirtioIommuRange32 {}
#[derive(Copy, Clone, Debug, Default)]
#[repr(packed)]
struct VirtioIommuRange64 {
start: u64, start: u64,
end: u64, end: u64,
} }
unsafe impl ByteValued for VirtioIommuRange {} unsafe impl ByteValued for VirtioIommuRange64 {}
#[derive(Copy, Clone, Debug, Default)]
#[repr(packed)]
struct VirtioIommuTopoConfig {
offset: u32,
num_items: u32,
item_length: u32,
}
unsafe impl ByteValued for VirtioIommuTopoConfig {}
#[derive(Copy, Clone, Debug, Default)] #[derive(Copy, Clone, Debug, Default)]
#[repr(packed)] #[repr(packed)]
struct VirtioIommuConfig { struct VirtioIommuConfig {
page_size_mask: u64, page_size_mask: u64,
input_range: VirtioIommuRange, input_range: VirtioIommuRange64,
domain_bits: u8, domain_range: VirtioIommuRange32,
padding: [u8; 3],
probe_size: u32, probe_size: u32,
topo_config: VirtioIommuTopoConfig,
} }
unsafe impl ByteValued for VirtioIommuConfig {} unsafe impl ByteValued for VirtioIommuConfig {}
#[allow(unused)]
const VIRTIO_IOMMU_TOPO_PCI_RANGE: u16 = 1;
#[allow(unused)]
const VIRTIO_IOMMU_TOPO_ENDPOINT: u16 = 2;
#[derive(Copy, Clone, Debug, Default)]
#[repr(packed)]
struct VirtioIommuTopoPciRange {
type_: u16,
hierarchy: u16,
requester_start: u16,
requester_end: u16,
endpoint_start: u32,
}
unsafe impl ByteValued for VirtioIommuTopoPciRange {}
#[derive(Copy, Clone, Debug, Default)]
#[repr(packed)]
struct VirtioIommuTopoEndpoint {
type_: u16,
reserved: u16,
endpoint: u32,
address: u64,
}
unsafe impl ByteValued for VirtioIommuTopoEndpoint {}
/// Virtio IOMMU request type /// Virtio IOMMU request type
const VIRTIO_IOMMU_T_ATTACH: u8 = 1; const VIRTIO_IOMMU_T_ATTACH: u8 = 1;
const VIRTIO_IOMMU_T_DETACH: u8 = 2; const VIRTIO_IOMMU_T_DETACH: u8 = 2;
@@ -86,7 +148,6 @@ const VIRTIO_IOMMU_T_MAP: u8 = 3;
const VIRTIO_IOMMU_T_UNMAP: u8 = 4; const VIRTIO_IOMMU_T_UNMAP: u8 = 4;
const VIRTIO_IOMMU_T_PROBE: u8 = 5; const VIRTIO_IOMMU_T_PROBE: u8 = 5;
#[allow(unused)]
#[derive(Copy, Clone, Debug, Default)] #[derive(Copy, Clone, Debug, Default)]
#[repr(packed)] #[repr(packed)]
struct VirtioIommuReqHead { struct VirtioIommuReqHead {
@@ -113,7 +174,6 @@ const VIRTIO_IOMMU_S_NOENT: u8 = 6;
#[allow(unused)] #[allow(unused)]
const VIRTIO_IOMMU_S_FAULT: u8 = 7; const VIRTIO_IOMMU_S_FAULT: u8 = 7;
#[allow(unused)]
#[derive(Copy, Clone, Debug, Default)] #[derive(Copy, Clone, Debug, Default)]
#[repr(packed)] #[repr(packed)]
struct VirtioIommuReqTail { struct VirtioIommuReqTail {
@@ -124,7 +184,6 @@ struct VirtioIommuReqTail {
unsafe impl ByteValued for VirtioIommuReqTail {} unsafe impl ByteValued for VirtioIommuReqTail {}
/// ATTACH request /// ATTACH request
#[allow(unused)]
#[derive(Copy, Clone, Debug, Default)] #[derive(Copy, Clone, Debug, Default)]
#[repr(packed)] #[repr(packed)]
struct VirtioIommuReqAttach { struct VirtioIommuReqAttach {
@@ -136,7 +195,6 @@ struct VirtioIommuReqAttach {
unsafe impl ByteValued for VirtioIommuReqAttach {} unsafe impl ByteValued for VirtioIommuReqAttach {}
/// DETACH request /// DETACH request
#[allow(unused)]
#[derive(Copy, Clone, Debug, Default)] #[derive(Copy, Clone, Debug, Default)]
#[repr(packed)] #[repr(packed)]
struct VirtioIommuReqDetach { struct VirtioIommuReqDetach {
@@ -158,7 +216,6 @@ const VIRTIO_IOMMU_MAP_F_EXEC: u32 = 1 << 2;
const VIRTIO_IOMMU_MAP_F_MMIO: u32 = 1 << 3; const VIRTIO_IOMMU_MAP_F_MMIO: u32 = 1 << 3;
/// MAP request /// MAP request
#[allow(unused)]
#[derive(Copy, Clone, Debug, Default)] #[derive(Copy, Clone, Debug, Default)]
#[repr(packed)] #[repr(packed)]
struct VirtioIommuReqMap { struct VirtioIommuReqMap {
@@ -172,7 +229,6 @@ struct VirtioIommuReqMap {
unsafe impl ByteValued for VirtioIommuReqMap {} unsafe impl ByteValued for VirtioIommuReqMap {}
/// UNMAP request /// UNMAP request
#[allow(unused)]
#[derive(Copy, Clone, Debug, Default)] #[derive(Copy, Clone, Debug, Default)]
#[repr(packed)] #[repr(packed)]
struct VirtioIommuReqUnmap { struct VirtioIommuReqUnmap {
@@ -186,14 +242,12 @@ unsafe impl ByteValued for VirtioIommuReqUnmap {}
/// Virtio IOMMU request PROBE types /// Virtio IOMMU request PROBE types
#[allow(unused)] #[allow(unused)]
const VIRTIO_IOMMU_PROBE_T_MASK: u32 = 0xfff; const VIRTIO_IOMMU_PROBE_T_MASK: u16 = 0xfff;
#[allow(unused)] #[allow(unused)]
const VIRTIO_IOMMU_PROBE_T_NONE: u32 = 0; const VIRTIO_IOMMU_PROBE_T_NONE: u16 = 0;
#[allow(unused)] const VIRTIO_IOMMU_PROBE_T_RESV_MEM: u16 = 1;
const VIRTIO_IOMMU_PROBE_T_RESV_MEM: u32 = 1;
/// PROBE request /// PROBE request
#[allow(unused)]
#[derive(Copy, Clone, Debug, Default)] #[derive(Copy, Clone, Debug, Default)]
#[repr(packed)] #[repr(packed)]
struct VirtioIommuReqProbe { struct VirtioIommuReqProbe {
@@ -203,7 +257,6 @@ struct VirtioIommuReqProbe {
unsafe impl ByteValued for VirtioIommuReqProbe {} unsafe impl ByteValued for VirtioIommuReqProbe {}
#[allow(unused)]
#[derive(Copy, Clone, Debug, Default)] #[derive(Copy, Clone, Debug, Default)]
#[repr(packed)] #[repr(packed)]
struct VirtioIommuProbeProperty { struct VirtioIommuProbeProperty {
@@ -215,15 +268,12 @@ unsafe impl ByteValued for VirtioIommuProbeProperty {}
/// Virtio IOMMU request PROBE property RESV_MEM subtypes /// Virtio IOMMU request PROBE property RESV_MEM subtypes
#[allow(unused)] #[allow(unused)]
const VIRTIO_IOMMU_RESV_MEM_T_RESERVED: u32 = 0; const VIRTIO_IOMMU_RESV_MEM_T_RESERVED: u8 = 0;
#[allow(unused)] const VIRTIO_IOMMU_RESV_MEM_T_MSI: u8 = 1;
const VIRTIO_IOMMU_RESV_MEM_T_MSI: u32 = 1;
#[allow(unused)]
#[derive(Copy, Clone, Debug, Default)] #[derive(Copy, Clone, Debug, Default)]
#[repr(packed)] #[repr(packed)]
struct VirtioIommuProbeResvMem { struct VirtioIommuProbeResvMem {
head: VirtioIommuProbeProperty,
subtype: u8, subtype: u8,
reserved: [u8; 3], reserved: [u8; 3],
start: u64, start: u64,
@@ -317,20 +367,7 @@ impl Display for Error {
} }
} }
#[derive(Debug, PartialEq)] struct Request {}
enum RequestType {
Attach,
Detach,
Map,
Unmap,
Probe,
}
struct Request {
#[allow(unused)]
type_: RequestType,
status_addr: GuestAddress,
}
impl Request { impl Request {
// Parse the available vring buffer. Based on the hashmap table of external // Parse the available vring buffer. Based on the hashmap table of external
@@ -347,7 +384,7 @@ impl Request {
mapping: &Arc<IommuMapping>, mapping: &Arc<IommuMapping>,
ext_mapping: &BTreeMap<u32, Arc<dyn ExternalDmaMapping>>, ext_mapping: &BTreeMap<u32, Arc<dyn ExternalDmaMapping>>,
ext_domain_mapping: &mut BTreeMap<u32, Arc<dyn ExternalDmaMapping>>, ext_domain_mapping: &mut BTreeMap<u32, Arc<dyn ExternalDmaMapping>>,
) -> result::Result<Request, Error> { ) -> result::Result<usize, Error> {
// The head contains the request type which MUST be readable. // The head contains the request type which MUST be readable.
if avail_desc.is_write_only() { if avail_desc.is_write_only() {
return Err(Error::UnexpectedWriteOnlyDescriptor); return Err(Error::UnexpectedWriteOnlyDescriptor);
@@ -367,7 +404,10 @@ impl Request {
return Err(Error::InvalidRequest); return Err(Error::InvalidRequest);
}; };
let request_type = match req_head.type_ { // Create the reply
let mut reply: Vec<u8> = Vec::new();
let hdr_len = match req_head.type_ {
VIRTIO_IOMMU_T_ATTACH => { VIRTIO_IOMMU_T_ATTACH => {
if desc_size_left != size_of::<VirtioIommuReqAttach>() { if desc_size_left != size_of::<VirtioIommuReqAttach>() {
return Err(Error::InvalidAttachRequest); return Err(Error::InvalidAttachRequest);
@@ -398,7 +438,7 @@ impl Request {
mappings.insert(domain, BTreeMap::new()); mappings.insert(domain, BTreeMap::new());
} }
RequestType::Attach 0
} }
VIRTIO_IOMMU_T_DETACH => { VIRTIO_IOMMU_T_DETACH => {
if desc_size_left != size_of::<VirtioIommuReqDetach>() { if desc_size_left != size_of::<VirtioIommuReqDetach>() {
@@ -424,7 +464,7 @@ impl Request {
// Remove endpoint associated with specific domain // Remove endpoint associated with specific domain
mapping.endpoints.write().unwrap().remove(&endpoint); mapping.endpoints.write().unwrap().remove(&endpoint);
RequestType::Detach 0
} }
VIRTIO_IOMMU_T_MAP => { VIRTIO_IOMMU_T_MAP => {
if desc_size_left != size_of::<VirtioIommuReqMap>() { if desc_size_left != size_of::<VirtioIommuReqMap>() {
@@ -460,7 +500,7 @@ impl Request {
return Err(Error::InvalidMapRequest); return Err(Error::InvalidMapRequest);
} }
RequestType::Map 0
} }
VIRTIO_IOMMU_T_UNMAP => { VIRTIO_IOMMU_T_UNMAP => {
if desc_size_left != size_of::<VirtioIommuReqUnmap>() { if desc_size_left != size_of::<VirtioIommuReqUnmap>() {
@@ -489,7 +529,7 @@ impl Request {
entry.remove(&virt_start); entry.remove(&virt_start);
} }
RequestType::Unmap 0
} }
VIRTIO_IOMMU_T_PROBE => { VIRTIO_IOMMU_T_PROBE => {
if desc_size_left != size_of::<VirtioIommuReqProbe>() { if desc_size_left != size_of::<VirtioIommuReqProbe>() {
@@ -501,7 +541,21 @@ impl Request {
.map_err(Error::GuestMemory)?; .map_err(Error::GuestMemory)?;
debug!("Probe request {:?}", req); debug!("Probe request {:?}", req);
RequestType::Probe let probe_prop = VirtioIommuProbeProperty {
type_: VIRTIO_IOMMU_PROBE_T_RESV_MEM,
length: size_of::<VirtioIommuProbeResvMem>() as u16,
};
reply.extend_from_slice(probe_prop.as_slice());
let resv_mem = VirtioIommuProbeResvMem {
subtype: VIRTIO_IOMMU_RESV_MEM_T_MSI,
start: MSI_IOVA_START,
end: MSI_IOVA_END,
..Default::default()
};
reply.extend_from_slice(resv_mem.as_slice());
PROBE_PROP_SIZE
} }
_ => return Err(Error::InvalidRequest), _ => return Err(Error::InvalidRequest),
}; };
@@ -515,23 +569,30 @@ impl Request {
return Err(Error::UnexpectedReadOnlyDescriptor); return Err(Error::UnexpectedReadOnlyDescriptor);
} }
if (status_desc.len as usize) < size_of::<VirtioIommuReqTail>() { if status_desc.len < hdr_len + size_of::<VirtioIommuReqTail>() as u32 {
return Err(Error::BufferLengthTooSmall); return Err(Error::BufferLengthTooSmall);
} }
Ok(Request { let tail = VirtioIommuReqTail {
type_: request_type, status: VIRTIO_IOMMU_S_OK,
status_addr: status_desc.addr, ..Default::default()
}) };
reply.extend_from_slice(tail.as_slice());
mem.write_slice(reply.as_slice(), status_desc.addr)
.map_err(Error::GuestMemory)?;
Ok((hdr_len as usize) + size_of::<VirtioIommuReqTail>())
} }
} }
struct IommuEpollHandler { struct IommuEpollHandler {
queues: Vec<Queue>, queues: Vec<Queue>,
mem: Arc<RwLock<GuestMemoryMmap>>, mem: Arc<ArcSwap<GuestMemoryMmap>>,
interrupt_cb: Arc<VirtioInterrupt>, interrupt_cb: Arc<dyn VirtioInterrupt>,
queue_evts: Vec<EventFd>, queue_evts: Vec<EventFd>,
kill_evt: EventFd, kill_evt: EventFd,
pause_evt: EventFd,
mapping: Arc<IommuMapping>, mapping: Arc<IommuMapping>,
ext_mapping: BTreeMap<u32, Arc<dyn ExternalDmaMapping>>, ext_mapping: BTreeMap<u32, Arc<dyn ExternalDmaMapping>>,
ext_domain_mapping: BTreeMap<u32, Arc<dyn ExternalDmaMapping>>, ext_domain_mapping: BTreeMap<u32, Arc<dyn ExternalDmaMapping>>,
@@ -541,7 +602,7 @@ impl IommuEpollHandler {
fn request_queue(&mut self) -> bool { fn request_queue(&mut self) -> bool {
let mut used_desc_heads = [(0, 0); QUEUE_SIZE as usize]; let mut used_desc_heads = [(0, 0); QUEUE_SIZE as usize];
let mut used_count = 0; let mut used_count = 0;
let mem = self.mem.read().unwrap(); let mem = self.mem.load();
for avail_desc in self.queues[0].iter(&mem) { for avail_desc in self.queues[0].iter(&mem) {
let len = match Request::parse( let len = match Request::parse(
&avail_desc, &avail_desc,
@@ -550,22 +611,9 @@ impl IommuEpollHandler {
&self.ext_mapping, &self.ext_mapping,
&mut self.ext_domain_mapping, &mut self.ext_domain_mapping,
) { ) {
Ok(ref req) => { Ok(len) => len as u32,
let reply = VirtioIommuReqTail {
status: VIRTIO_IOMMU_S_OK,
..Default::default()
};
match mem.write_obj(reply, req.status_addr) {
Ok(_) => size_of::<VirtioIommuReqTail>() as u32,
Err(e) => {
error!("bad guest memory address: {}", e);
0
}
}
}
Err(e) => { Err(e) => {
error!("Failed to parse available descriptor chain: {:?}", e); error!("failed parsing descriptor: {}", e);
0 0
} }
}; };
@@ -585,13 +633,15 @@ impl IommuEpollHandler {
} }
fn signal_used_queue(&self, queue: &Queue) -> result::Result<(), DeviceError> { fn signal_used_queue(&self, queue: &Queue) -> result::Result<(), DeviceError> {
(self.interrupt_cb)(&VirtioInterruptType::Queue, Some(queue)).map_err(|e| { self.interrupt_cb
error!("Failed to signal used queue: {:?}", e); .trigger(&VirtioInterruptType::Queue, Some(queue))
DeviceError::FailedSignalingUsedQueue(e) .map_err(|e| {
}) error!("Failed to signal used queue: {:?}", e);
DeviceError::FailedSignalingUsedQueue(e)
})
} }
fn run(&mut self) -> result::Result<(), DeviceError> { fn run(&mut self, paused: Arc<AtomicBool>) -> result::Result<(), DeviceError> {
// Create the epoll file descriptor // Create the epoll file descriptor
let epoll_fd = epoll::create(true).map_err(DeviceError::EpollCreateFd)?; let epoll_fd = epoll::create(true).map_err(DeviceError::EpollCreateFd)?;
@@ -617,6 +667,13 @@ impl IommuEpollHandler {
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(KILL_EVENT)), epoll::Event::new(epoll::Events::EPOLLIN, u64::from(KILL_EVENT)),
) )
.map_err(DeviceError::EpollCtl)?; .map_err(DeviceError::EpollCtl)?;
epoll::ctl(
epoll_fd,
epoll::ControlOptions::EPOLL_CTL_ADD,
self.pause_evt.as_raw_fd(),
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(PAUSE_EVENT)),
)
.map_err(DeviceError::EpollCtl)?;
const EPOLL_EVENTS_LEN: usize = 100; const EPOLL_EVENTS_LEN: usize = 100;
let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN]; let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN];
@@ -669,6 +726,15 @@ impl IommuEpollHandler {
debug!("kill_evt received, stopping epoll loop"); debug!("kill_evt received, stopping epoll loop");
break 'epoll; break 'epoll;
} }
PAUSE_EVENT => {
debug!("PAUSE_EVENT received, pausing virtio-iommu epoll loop");
// We loop here to handle spurious park() returns.
// Until we have not resumed, the paused boolean will
// be true.
while paused.load(Ordering::SeqCst) {
thread::park();
}
}
_ => { _ => {
error!("Unknown event for virtio-iommu"); error!("Unknown event for virtio-iommu");
break 'epoll; break 'epoll;
@@ -723,19 +789,24 @@ impl DmaRemapping for IommuMapping {
pub struct Iommu { pub struct Iommu {
kill_evt: Option<EventFd>, kill_evt: Option<EventFd>,
pause_evt: Option<EventFd>,
avail_features: u64, avail_features: u64,
acked_features: u64, acked_features: u64,
config: VirtioIommuConfig, config: VirtioIommuConfig,
config_topo_pci_ranges: Vec<VirtioIommuTopoPciRange>,
mapping: Arc<IommuMapping>, mapping: Arc<IommuMapping>,
ext_mapping: BTreeMap<u32, Arc<dyn ExternalDmaMapping>>, ext_mapping: BTreeMap<u32, Arc<dyn ExternalDmaMapping>>,
queue_evts: Option<Vec<EventFd>>, queue_evts: Option<Vec<EventFd>>,
interrupt_cb: Option<Arc<VirtioInterrupt>>, interrupt_cb: Option<Arc<dyn VirtioInterrupt>>,
epoll_threads: Option<Vec<thread::JoinHandle<result::Result<(), DeviceError>>>>,
paused: Arc<AtomicBool>,
} }
impl Iommu { impl Iommu {
pub fn new() -> io::Result<(Self, Arc<IommuMapping>)> { pub fn new() -> io::Result<(Self, Arc<IommuMapping>)> {
let config = VirtioIommuConfig { let config = VirtioIommuConfig {
page_size_mask: VIRTIO_IOMMU_PAGE_SIZE_MASK, page_size_mask: VIRTIO_IOMMU_PAGE_SIZE_MASK,
probe_size: PROBE_PROP_SIZE,
..Default::default() ..Default::default()
}; };
@@ -747,18 +818,62 @@ impl Iommu {
Ok(( Ok((
Iommu { Iommu {
kill_evt: None, kill_evt: None,
avail_features: 1u64 << VIRTIO_F_VERSION_1 | 1u64 << VIRTIO_IOMMU_F_MAP_UNMAP, pause_evt: None,
avail_features: 1u64 << VIRTIO_F_VERSION_1
| 1u64 << VIRTIO_IOMMU_F_MAP_UNMAP
| 1u64 << VIRTIO_IOMMU_F_PROBE,
acked_features: 0u64, acked_features: 0u64,
config, config,
config_topo_pci_ranges: Vec::new(),
mapping: mapping.clone(), mapping: mapping.clone(),
ext_mapping: BTreeMap::new(), ext_mapping: BTreeMap::new(),
queue_evts: None, queue_evts: None,
interrupt_cb: None, interrupt_cb: None,
epoll_threads: None,
paused: Arc::new(AtomicBool::new(false)),
}, },
mapping, mapping,
)) ))
} }
// This function lets the caller specify a list of devices attached to the
// virtual IOMMU. This list is translated into a virtio-iommu configuration
// topology, so that it can be understood by the guest driver.
//
// The topology is overriden everytime this function is being invoked.
//
// This function is dedicated to PCI, which means it will exclusively
// create VIRTIO_IOMMU_TOPO_PCI_RANGE entries.
pub fn attach_pci_devices(&mut self, domain: u16, device_ids: Vec<u32>) {
if device_ids.is_empty() {
warn!("No device to attach to virtual IOMMU");
return;
}
// If there is at least one device attached to the virtual IOMMU, we
// need the topology feature to be enabled.
self.avail_features |= 1u64 << VIRTIO_IOMMU_F_TOPOLOGY;
// Update the topology.
let mut topo_pci_ranges = Vec::new();
for device_id in device_ids.iter() {
let dev_id = *device_id;
topo_pci_ranges.push(VirtioIommuTopoPciRange {
type_: VIRTIO_IOMMU_TOPO_PCI_RANGE,
hierarchy: domain,
requester_start: dev_id as u16,
requester_end: dev_id as u16,
endpoint_start: dev_id,
});
}
self.config_topo_pci_ranges = topo_pci_ranges;
// Update the configuration to include the topology.
self.config.topo_config.offset = size_of::<VirtioIommuConfig>() as u32;
self.config.topo_config.num_items = self.config_topo_pci_ranges.len() as u32;
self.config.topo_config.item_length = size_of::<VirtioIommuTopoPciRange>() as u32;
}
pub fn add_external_mapping(&mut self, device_id: u32, mapping: Arc<dyn ExternalDmaMapping>) { pub fn add_external_mapping(&mut self, device_id: u32, mapping: Arc<dyn ExternalDmaMapping>) {
self.ext_mapping.insert(device_id, mapping); self.ext_mapping.insert(device_id, mapping);
} }
@@ -782,29 +897,12 @@ impl VirtioDevice for Iommu {
QUEUE_SIZES QUEUE_SIZES
} }
fn features(&self, page: u32) -> u32 { fn features(&self) -> u64 {
match page { self.avail_features
// Get the lower 32-bits of the features bitfield.
0 => self.avail_features as u32,
// Get the upper 32-bits of the features bitfield.
1 => (self.avail_features >> 32) as u32,
_ => {
warn!("Received request for unknown features page.");
0u32
}
}
} }
fn ack_features(&mut self, page: u32, value: u32) { fn ack_features(&mut self, value: u64) {
let mut v = match page { let mut v = value;
0 => u64::from(value),
1 => u64::from(value) << 32,
_ => {
warn!("Cannot acknowledge unknown features page.");
0u64
}
};
// Check if the guest is ACK'ing a feature that we didn't claim to have. // Check if the guest is ACK'ing a feature that we didn't claim to have.
let unrequested_features = v & !self.avail_features; let unrequested_features = v & !self.avail_features;
if unrequested_features != 0 { if unrequested_features != 0 {
@@ -817,7 +915,13 @@ impl VirtioDevice for Iommu {
} }
fn read_config(&self, offset: u64, mut data: &mut [u8]) { fn read_config(&self, offset: u64, mut data: &mut [u8]) {
let config_slice = self.config.as_slice(); let mut config: Vec<u8> = Vec::new();
config.extend_from_slice(self.config.as_slice());
for config_topo_pci_range in self.config_topo_pci_ranges.iter() {
config.extend_from_slice(config_topo_pci_range.as_slice());
}
let config_slice = config.as_slice();
let config_len = config_slice.len() as u64; let config_len = config_slice.len() as u64;
if offset >= config_len { if offset >= config_len {
error!("Failed to read config space"); error!("Failed to read config space");
@@ -837,8 +941,8 @@ impl VirtioDevice for Iommu {
fn activate( fn activate(
&mut self, &mut self,
mem: Arc<RwLock<GuestMemoryMmap>>, mem: Arc<ArcSwap<GuestMemoryMmap>>,
interrupt_cb: Arc<VirtioInterrupt>, interrupt_cb: Arc<dyn VirtioInterrupt>,
queues: Vec<Queue>, queues: Vec<Queue>,
queue_evts: Vec<EventFd>, queue_evts: Vec<EventFd>,
) -> ActivateResult { ) -> ActivateResult {
@@ -851,16 +955,22 @@ impl VirtioDevice for Iommu {
return Err(ActivateError::BadActivate); return Err(ActivateError::BadActivate);
} }
let (self_kill_evt, kill_evt) = let (self_kill_evt, kill_evt) = EventFd::new(EFD_NONBLOCK)
match EventFd::new(EFD_NONBLOCK).and_then(|e| Ok((e.try_clone()?, e))) { .and_then(|e| Ok((e.try_clone()?, e)))
Ok(v) => v, .map_err(|e| {
Err(e) => { error!("failed creating kill EventFd pair: {}", e);
error!("failed creating kill EventFd pair: {}", e); ActivateError::BadActivate
return Err(ActivateError::BadActivate); })?;
}
};
self.kill_evt = Some(self_kill_evt); self.kill_evt = Some(self_kill_evt);
let (self_pause_evt, pause_evt) = EventFd::new(EFD_NONBLOCK)
.and_then(|e| Ok((e.try_clone()?, e)))
.map_err(|e| {
error!("failed creating pause EventFd pair: {}", e);
ActivateError::BadActivate
})?;
self.pause_evt = Some(self_pause_evt);
// Save the interrupt EventFD as we need to return it on reset // Save the interrupt EventFD as we need to return it on reset
// but clone it to pass into the thread. // but clone it to pass into the thread.
self.interrupt_cb = Some(interrupt_cb.clone()); self.interrupt_cb = Some(interrupt_cb.clone());
@@ -882,24 +992,34 @@ impl VirtioDevice for Iommu {
interrupt_cb, interrupt_cb,
queue_evts, queue_evts,
kill_evt, kill_evt,
pause_evt,
mapping: self.mapping.clone(), mapping: self.mapping.clone(),
ext_mapping: self.ext_mapping.clone(), ext_mapping: self.ext_mapping.clone(),
ext_domain_mapping: BTreeMap::new(), ext_domain_mapping: BTreeMap::new(),
}; };
let worker_result = thread::Builder::new() let paused = self.paused.clone();
.name("virtio-iommu".to_string()) let mut epoll_threads = Vec::new();
.spawn(move || handler.run()); thread::Builder::new()
.name("virtio_iommu".to_string())
.spawn(move || handler.run(paused))
.map(|thread| epoll_threads.push(thread))
.map_err(|e| {
error!("failed to clone the virtio-iommu epoll thread: {}", e);
ActivateError::BadActivate
})?;
if let Err(e) = worker_result { self.epoll_threads = Some(epoll_threads);
error!("failed to spawn virtio-iommu worker: {}", e);
return Err(ActivateError::BadActivate);
}
Ok(()) Ok(())
} }
fn reset(&mut self) -> Option<(Arc<VirtioInterrupt>, Vec<EventFd>)> { fn reset(&mut self) -> Option<(Arc<dyn VirtioInterrupt>, Vec<EventFd>)> {
// We first must resume the virtio thread if it was paused.
if self.pause_evt.take().is_some() {
self.resume().ok()?;
}
if let Some(kill_evt) = self.kill_evt.take() { if let Some(kill_evt) = self.kill_evt.take() {
// Ignore the result because there is nothing we can do about it. // Ignore the result because there is nothing we can do about it.
let _ = kill_evt.write(1); let _ = kill_evt.write(1);
@@ -912,3 +1032,7 @@ impl VirtioDevice for Iommu {
)) ))
} }
} }
virtio_pausable!(Iommu);
impl Snapshotable for Iommu {}
impl Migratable for Iommu {}

View File

@@ -9,6 +9,8 @@
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause // SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
//! Implements virtio devices, queues, and transport mechanisms. //! Implements virtio devices, queues, and transport mechanisms.
extern crate arc_swap;
extern crate epoll; extern crate epoll;
#[macro_use] #[macro_use]
extern crate log; extern crate log;
@@ -16,16 +18,19 @@ extern crate log;
extern crate pci; extern crate pci;
extern crate vhost_rs; extern crate vhost_rs;
extern crate virtio_bindings; extern crate virtio_bindings;
extern crate vm_device;
extern crate vm_memory; extern crate vm_memory;
use std::fmt; use std::fmt;
use std::io; use std::io;
mod block; #[macro_use]
mod console;
mod device; mod device;
pub mod block;
mod console;
mod iommu; mod iommu;
pub mod net; pub mod net;
pub mod net_util;
mod pmem; mod pmem;
mod queue; mod queue;
mod rng; mod rng;
@@ -39,6 +44,7 @@ pub use self::console::*;
pub use self::device::*; pub use self::device::*;
pub use self::iommu::*; pub use self::iommu::*;
pub use self::net::*; pub use self::net::*;
pub use self::net_util::*;
pub use self::pmem::*; pub use self::pmem::*;
pub use self::queue::*; pub use self::queue::*;
pub use self::rng::*; pub use self::rng::*;
@@ -81,6 +87,7 @@ impl From<u32> for VirtioDeviceType {
match t { match t {
1 => VirtioDeviceType::TYPE_NET, 1 => VirtioDeviceType::TYPE_NET,
2 => VirtioDeviceType::TYPE_BLOCK, 2 => VirtioDeviceType::TYPE_BLOCK,
3 => VirtioDeviceType::TYPE_CONSOLE,
4 => VirtioDeviceType::TYPE_RNG, 4 => VirtioDeviceType::TYPE_RNG,
5 => VirtioDeviceType::TYPE_BALLOON, 5 => VirtioDeviceType::TYPE_BALLOON,
9 => VirtioDeviceType::TYPE_9P, 9 => VirtioDeviceType::TYPE_9P,
@@ -103,14 +110,17 @@ impl fmt::Display for VirtioDeviceType {
let output = match *self { let output = match *self {
VirtioDeviceType::TYPE_NET => "net", VirtioDeviceType::TYPE_NET => "net",
VirtioDeviceType::TYPE_BLOCK => "block", VirtioDeviceType::TYPE_BLOCK => "block",
VirtioDeviceType::TYPE_CONSOLE => "console",
VirtioDeviceType::TYPE_RNG => "rng", VirtioDeviceType::TYPE_RNG => "rng",
VirtioDeviceType::TYPE_BALLOON => "balloon", VirtioDeviceType::TYPE_BALLOON => "balloon",
VirtioDeviceType::TYPE_GPU => "gpu", VirtioDeviceType::TYPE_GPU => "gpu",
VirtioDeviceType::TYPE_9P => "9p", VirtioDeviceType::TYPE_9P => "9p",
VirtioDeviceType::TYPE_INPUT => "input",
VirtioDeviceType::TYPE_VSOCK => "vsock", VirtioDeviceType::TYPE_VSOCK => "vsock",
VirtioDeviceType::TYPE_IOMMU => "iommu",
VirtioDeviceType::TYPE_FS => "fs", VirtioDeviceType::TYPE_FS => "fs",
VirtioDeviceType::TYPE_PMEM => "pmem", VirtioDeviceType::TYPE_PMEM => "pmem",
_ => return Err(std::fmt::Error), VirtioDeviceType::TYPE_UNKNOWN => "UNKNOWN",
}; };
write!(f, "{}", output) write!(f, "{}", output)
} }
@@ -120,6 +130,8 @@ impl fmt::Display for VirtioDeviceType {
const INTERRUPT_STATUS_USED_RING: u32 = 0x1; const INTERRUPT_STATUS_USED_RING: u32 = 0x1;
#[allow(dead_code)] #[allow(dead_code)]
const INTERRUPT_STATUS_CONFIG_CHANGED: u32 = 0x2; const INTERRUPT_STATUS_CONFIG_CHANGED: u32 = 0x2;
#[cfg(feature = "pci_support")]
const VIRTIO_MSI_NO_VECTOR: u16 = 0xffff;
#[derive(Debug)] #[derive(Debug)]
pub enum ActivateError { pub enum ActivateError {

View File

@@ -5,202 +5,98 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the THIRD-PARTY file. // found in the THIRD-PARTY file.
use super::net_util::{
build_net_config_space, build_net_config_space_with_mq, open_tap, register_listener,
unregister_listener, CtrlVirtio, NetCtrlEpollHandler, RxVirtio, TxVirtio, VirtioNetConfig,
KILL_EVENT, NET_EVENTS_COUNT, PAUSE_EVENT, RX_QUEUE_EVENT, RX_TAP_EVENT, TX_QUEUE_EVENT,
};
use super::Error as DeviceError;
use super::{
ActivateError, ActivateResult, Queue, VirtioDevice, VirtioDeviceType, VirtioInterruptType,
};
use crate::VirtioInterrupt;
use arc_swap::ArcSwap;
use epoll; use epoll;
use libc::EAGAIN; use libc::EAGAIN;
use libc::EFD_NONBLOCK; use libc::EFD_NONBLOCK;
use net_util::{MacAddr, Tap};
use std::cmp; use std::cmp;
use std::io::Read; use std::io::Read;
use std::io::{self, Write}; use std::io::{self, Write};
use std::mem;
use std::net::Ipv4Addr; use std::net::Ipv4Addr;
use std::os::unix::io::{AsRawFd, RawFd}; use std::os::unix::io::{AsRawFd, RawFd};
use std::result; use std::result;
use std::sync::{Arc, RwLock}; use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::thread; use std::thread;
use std::vec::Vec; use std::vec::Vec;
use net_gen;
use super::Error as DeviceError;
use super::{
ActivateError, ActivateResult, DeviceEventT, Queue, VirtioDevice, VirtioDeviceType,
VirtioInterruptType,
};
use crate::VirtioInterrupt;
use net_util::{MacAddr, Tap, TapError, MAC_ADDR_LEN};
use virtio_bindings::bindings::virtio_net::*; use virtio_bindings::bindings::virtio_net::*;
use vm_memory::{Bytes, GuestAddress, GuestMemoryMmap}; use vm_device::{Migratable, MigratableError, Pausable, Snapshotable};
use vm_memory::{ByteValued, GuestMemoryMmap};
use vmm_sys_util::eventfd::EventFd; use vmm_sys_util::eventfd::EventFd;
/// The maximum buffer size when segmentation offload is enabled. This
/// includes the 12-byte virtio net header.
/// http://docs.oasis-open.org/virtio/virtio/v1.0/virtio-v1.0.html#x1-1740003
const MAX_BUFFER_SIZE: usize = 65562;
const QUEUE_SIZE: u16 = 256;
const NUM_QUEUES: usize = 2;
const QUEUE_SIZES: &[u16] = &[QUEUE_SIZE; NUM_QUEUES];
// A frame is available for reading from the tap device to receive in the guest.
const RX_TAP_EVENT: DeviceEventT = 0;
// The guest has made a buffer available to receive a frame into.
const RX_QUEUE_EVENT: DeviceEventT = 1;
// The transmit queue has a frame that is ready to send from the guest.
const TX_QUEUE_EVENT: DeviceEventT = 2;
// The device has been dropped.
pub const KILL_EVENT: DeviceEventT = 3;
// Number of DeviceEventT events supported by this implementation.
pub const NET_EVENTS_COUNT: usize = 4;
#[derive(Debug)] #[derive(Debug)]
pub enum Error { pub enum Error {
/// Open tap device failed. /// Failed to open taps.
TapOpen(TapError), OpenTap(super::net_util::Error),
/// Setting tap IP failed.
TapSetIp(TapError),
/// Setting tap netmask failed.
TapSetNetmask(TapError),
/// Setting tap interface offload flags failed.
TapSetOffload(TapError),
/// Setting vnet header size failed.
TapSetVnetHdrSize(TapError),
/// Enabling tap interface failed.
TapEnable(TapError),
} }
pub type Result<T> = result::Result<T, Error>; pub type Result<T> = result::Result<T, Error>;
struct TxVirtio {
queue_evt: EventFd,
queue: Queue,
iovec: Vec<(GuestAddress, usize)>,
frame_buf: [u8; MAX_BUFFER_SIZE],
}
impl TxVirtio {
fn new(queue: Queue, queue_evt: EventFd) -> Self {
let tx_queue_max_size = queue.get_max_size() as usize;
TxVirtio {
queue_evt,
queue,
iovec: Vec::with_capacity(tx_queue_max_size),
frame_buf: [0u8; MAX_BUFFER_SIZE],
}
}
}
struct RxVirtio {
queue_evt: EventFd,
deferred_frame: bool,
deferred_irqs: bool,
queue: Queue,
bytes_read: usize,
frame_buf: [u8; MAX_BUFFER_SIZE],
}
impl RxVirtio {
fn new(queue: Queue, queue_evt: EventFd) -> Self {
RxVirtio {
queue_evt,
deferred_frame: false,
deferred_irqs: false,
queue,
bytes_read: 0,
frame_buf: [0u8; MAX_BUFFER_SIZE],
}
}
}
fn vnet_hdr_len() -> usize {
mem::size_of::<virtio_net_hdr_v1>()
}
struct NetEpollHandler { struct NetEpollHandler {
mem: Arc<RwLock<GuestMemoryMmap>>, mem: Arc<ArcSwap<GuestMemoryMmap>>,
tap: Tap, tap: Tap,
rx: RxVirtio, rx: RxVirtio,
tx: TxVirtio, tx: TxVirtio,
interrupt_cb: Arc<VirtioInterrupt>, interrupt_cb: Arc<dyn VirtioInterrupt>,
kill_evt: EventFd, kill_evt: EventFd,
pause_evt: EventFd,
epoll_fd: RawFd, epoll_fd: RawFd,
rx_tap_listening: bool, rx_tap_listening: bool,
} }
impl NetEpollHandler { impl NetEpollHandler {
fn signal_used_queue(&self, queue: &Queue) -> result::Result<(), DeviceError> { fn signal_used_queue(&self, queue: &Queue) -> result::Result<(), DeviceError> {
(self.interrupt_cb)(&VirtioInterruptType::Queue, Some(queue)).map_err(|e| { self.interrupt_cb
error!("Failed to signal used queue: {:?}", e); .trigger(&VirtioInterruptType::Queue, Some(queue))
DeviceError::FailedSignalingUsedQueue(e) .map_err(|e| {
}) error!("Failed to signal used queue: {:?}", e);
DeviceError::FailedSignalingUsedQueue(e)
})
} }
// Copies a single frame from `self.rx.frame_buf` into the guest. Returns true // Copies a single frame from `self.rx.frame_buf` into the guest. Returns true
// if a buffer was used, and false if the frame must be deferred until a buffer // if a buffer was used, and false if the frame must be deferred until a buffer
// is made available by the driver. // is made available by the driver.
fn rx_single_frame(&mut self) -> bool { fn rx_single_frame(&mut self, mut queue: &mut Queue) -> bool {
let mem = self.mem.read().unwrap(); let mem = self.mem.load();
let mut next_desc = self.rx.queue.iter(&mem).next(); let next_desc = queue.iter(&mem).next();
if next_desc.is_none() { if next_desc.is_none() {
// Queue has no available descriptors // Queue has no available descriptors
if self.rx_tap_listening { if self.rx_tap_listening {
self.unregister_tap_rx_listener().unwrap(); unregister_listener(
self.epoll_fd,
self.tap.as_raw_fd(),
epoll::Events::EPOLLIN,
u64::from(RX_TAP_EVENT),
)
.unwrap();
self.rx_tap_listening = false; self.rx_tap_listening = false;
} }
return false; return false;
} }
// We just checked that the head descriptor exists. self.rx.process_desc_chain(&mem, next_desc, &mut queue)
let head_index = next_desc.as_ref().unwrap().index;
let mut write_count = 0;
// Copy from frame into buffer, which may span multiple descriptors.
loop {
match next_desc {
Some(desc) => {
if !desc.is_write_only() {
break;
}
let limit = cmp::min(write_count + desc.len as usize, self.rx.bytes_read);
let source_slice = &self.rx.frame_buf[write_count..limit];
let write_result = mem.write_slice(source_slice, desc.addr);
match write_result {
Ok(_) => {
write_count = limit;
}
Err(e) => {
error!("Failed to write slice: {:?}", e);
break;
}
};
if write_count >= self.rx.bytes_read {
break;
}
next_desc = desc.next_descriptor();
}
None => {
warn!("Receiving buffer is too small to hold frame of current size");
break;
}
}
}
self.rx.queue.add_used(&mem, head_index, write_count as u32);
// Mark that we have at least one pending packet and we need to interrupt the guest.
self.rx.deferred_irqs = true;
write_count >= self.rx.bytes_read
} }
fn process_rx(&mut self) -> result::Result<(), DeviceError> { fn process_rx(&mut self, queue: &mut Queue) -> result::Result<(), DeviceError> {
// Read as many frames as possible. // Read as many frames as possible.
loop { loop {
match self.read_tap() { match self.read_tap() {
Ok(count) => { Ok(count) => {
self.rx.bytes_read = count; self.rx.bytes_read = count;
if !self.rx_single_frame() { if !self.rx_single_frame(queue) {
self.rx.deferred_frame = true; self.rx.deferred_frame = true;
break; break;
} }
@@ -221,22 +117,22 @@ impl NetEpollHandler {
} }
if self.rx.deferred_irqs { if self.rx.deferred_irqs {
self.rx.deferred_irqs = false; self.rx.deferred_irqs = false;
self.signal_used_queue(&self.rx.queue) self.signal_used_queue(queue)
} else { } else {
Ok(()) Ok(())
} }
} }
fn resume_rx(&mut self) -> result::Result<(), DeviceError> { fn resume_rx(&mut self, queue: &mut Queue) -> result::Result<(), DeviceError> {
if self.rx.deferred_frame { if self.rx.deferred_frame {
if self.rx_single_frame() { if self.rx_single_frame(queue) {
self.rx.deferred_frame = false; self.rx.deferred_frame = false;
// process_rx() was interrupted possibly before consuming all // process_rx() was interrupted possibly before consuming all
// packets in the tap; try continuing now. // packets in the tap; try continuing now.
self.process_rx() self.process_rx(queue)
} else if self.rx.deferred_irqs { } else if self.rx.deferred_irqs {
self.rx.deferred_irqs = false; self.rx.deferred_irqs = false;
self.signal_used_queue(&self.rx.queue) self.signal_used_queue(queue)
} else { } else {
Ok(()) Ok(())
} }
@@ -245,56 +141,10 @@ impl NetEpollHandler {
} }
} }
fn process_tx(&mut self) -> result::Result<(), DeviceError> { fn process_tx(&mut self, mut queue: &mut Queue) -> result::Result<(), DeviceError> {
let mem = self.mem.read().unwrap(); let mem = self.mem.load();
while let Some(avail_desc) = self.tx.queue.iter(&mem).next() {
let head_index = avail_desc.index;
let mut read_count = 0;
let mut next_desc = Some(avail_desc);
self.tx.iovec.clear(); self.tx.process_desc_chain(&mem, &mut self.tap, &mut queue);
while let Some(desc) = next_desc {
if desc.is_write_only() {
break;
}
self.tx.iovec.push((desc.addr, desc.len as usize));
read_count += desc.len as usize;
next_desc = desc.next_descriptor();
}
read_count = 0;
// Copy buffer from across multiple descriptors.
// TODO(performance - Issue #420): change this to use `writev()` instead of `write()`
// and get rid of the intermediate buffer.
for (desc_addr, desc_len) in self.tx.iovec.drain(..) {
let limit = cmp::min((read_count + desc_len) as usize, self.tx.frame_buf.len());
let read_result = mem.read_slice(
&mut self.tx.frame_buf[read_count..limit as usize],
desc_addr,
);
match read_result {
Ok(_) => {
// Increment by number of bytes actually read
read_count += limit - read_count;
}
Err(e) => {
error!("Failed to read slice: {:?}", e);
break;
}
}
}
let write_result = self.tap.write(&self.tx.frame_buf[..read_count as usize]);
match write_result {
Ok(_) => {}
Err(e) => {
warn!("net: tx: error failed to write to tap: {}", e);
}
};
self.tx.queue.add_used(&mem, head_index, 0);
}
Ok(()) Ok(())
} }
@@ -303,47 +153,73 @@ impl NetEpollHandler {
self.tap.read(&mut self.rx.frame_buf) self.tap.read(&mut self.rx.frame_buf)
} }
fn register_tap_rx_listener(&self) -> std::result::Result<(), std::io::Error> { fn handle_rx_event(&mut self, mut queue: &mut Queue, queue_evt: &EventFd) {
epoll::ctl( if let Err(e) = queue_evt.read() {
self.epoll_fd, error!("Failed to get rx queue event: {:?}", e);
epoll::ControlOptions::EPOLL_CTL_ADD, }
self.tap.as_raw_fd(),
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(RX_TAP_EVENT)), self.resume_rx(&mut queue).unwrap();
)?; if !self.rx_tap_listening {
Ok(()) register_listener(
self.epoll_fd,
self.tap.as_raw_fd(),
epoll::Events::EPOLLIN,
u64::from(RX_TAP_EVENT),
)
.unwrap();
self.rx_tap_listening = true;
}
} }
fn unregister_tap_rx_listener(&self) -> std::result::Result<(), std::io::Error> { fn handle_tx_event(&mut self, mut queue: &mut Queue, queue_evt: &EventFd) {
epoll::ctl( if let Err(e) = queue_evt.read() {
self.epoll_fd, error!("Failed to get tx queue event: {:?}", e);
epoll::ControlOptions::EPOLL_CTL_DEL, }
self.tap.as_raw_fd(),
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(RX_TAP_EVENT)), self.process_tx(&mut queue).unwrap();
)?;
Ok(())
} }
fn run(&mut self) -> result::Result<(), DeviceError> { fn handle_rx_tap_event(&mut self, mut queue: &mut Queue) {
if self.rx.deferred_frame
// Process a deferred frame first if available. Don't read from tap again
// until we manage to receive this deferred frame.
{
if self.rx_single_frame(&mut queue) {
self.rx.deferred_frame = false;
self.process_rx(&mut queue).unwrap();
} else if self.rx.deferred_irqs {
self.rx.deferred_irqs = false;
self.signal_used_queue(&queue).unwrap();
}
} else {
self.process_rx(&mut queue).unwrap();
}
}
fn run(
&mut self,
paused: Arc<AtomicBool>,
mut queues: Vec<Queue>,
queue_evts: Vec<EventFd>,
) -> result::Result<(), DeviceError> {
// Create the epoll file descriptor // Create the epoll file descriptor
self.epoll_fd = epoll::create(true).map_err(DeviceError::EpollCreateFd)?; self.epoll_fd = epoll::create(true).map_err(DeviceError::EpollCreateFd)?;
// Add events // Add events
// Add events
epoll::ctl( epoll::ctl(
self.epoll_fd, self.epoll_fd,
epoll::ControlOptions::EPOLL_CTL_ADD, epoll::ControlOptions::EPOLL_CTL_ADD,
self.rx.queue_evt.as_raw_fd(), queue_evts[0].as_raw_fd(),
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(RX_QUEUE_EVENT)), epoll::Event::new(epoll::Events::EPOLLIN, u64::from(RX_QUEUE_EVENT)),
) )
.map_err(DeviceError::EpollCtl)?; .map_err(DeviceError::EpollCtl)?;
epoll::ctl( epoll::ctl(
self.epoll_fd, self.epoll_fd,
epoll::ControlOptions::EPOLL_CTL_ADD, epoll::ControlOptions::EPOLL_CTL_ADD,
self.tx.queue_evt.as_raw_fd(), queue_evts[1].as_raw_fd(),
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(TX_QUEUE_EVENT)), epoll::Event::new(epoll::Events::EPOLLIN, u64::from(TX_QUEUE_EVENT)),
) )
.map_err(DeviceError::EpollCtl)?; .map_err(DeviceError::EpollCtl)?;
self.register_tap_rx_listener()
.map_err(DeviceError::EpollCtl)?;
self.rx_tap_listening = true;
epoll::ctl( epoll::ctl(
self.epoll_fd, self.epoll_fd,
epoll::ControlOptions::EPOLL_CTL_ADD, epoll::ControlOptions::EPOLL_CTL_ADD,
@@ -351,9 +227,15 @@ impl NetEpollHandler {
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(KILL_EVENT)), epoll::Event::new(epoll::Events::EPOLLIN, u64::from(KILL_EVENT)),
) )
.map_err(DeviceError::EpollCtl)?; .map_err(DeviceError::EpollCtl)?;
epoll::ctl(
self.epoll_fd,
epoll::ControlOptions::EPOLL_CTL_ADD,
self.pause_evt.as_raw_fd(),
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(PAUSE_EVENT)),
)
.map_err(DeviceError::EpollCtl)?;
const EPOLL_EVENTS_LEN: usize = 100; let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); NET_EVENTS_COUNT];
let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN];
'epoll: loop { 'epoll: loop {
let num_events = match epoll::wait(self.epoll_fd, -1, &mut events[..]) { let num_events = match epoll::wait(self.epoll_fd, -1, &mut events[..]) {
@@ -378,84 +260,61 @@ impl NetEpollHandler {
match ev_type { match ev_type {
RX_QUEUE_EVENT => { RX_QUEUE_EVENT => {
debug!("RX_QUEUE_EVENT received"); self.handle_rx_event(&mut queues[0], &queue_evts[0]);
if let Err(e) = self.rx.queue_evt.read() {
error!("Failed to get rx queue event: {:?}", e);
break 'epoll;
}
self.resume_rx().unwrap();
if !self.rx_tap_listening {
self.register_tap_rx_listener().unwrap();
self.rx_tap_listening = true;
}
} }
TX_QUEUE_EVENT => { TX_QUEUE_EVENT => {
debug!("TX_QUEUE_EVENT received"); self.handle_tx_event(&mut queues[1], &queue_evts[1]);
if let Err(e) = self.tx.queue_evt.read() {
error!("Failed to get tx queue event: {:?}", e);
break 'epoll;
}
self.process_tx().unwrap();
} }
RX_TAP_EVENT => { RX_TAP_EVENT => {
debug!("RX_TAP_EVENT received"); self.handle_rx_tap_event(&mut queues[0]);
if self.rx.deferred_frame
// Process a deferred frame first if available. Don't read from tap again
// until we manage to receive this deferred frame.
{
if self.rx_single_frame() {
self.rx.deferred_frame = false;
self.process_rx().unwrap();
} else if self.rx.deferred_irqs {
self.rx.deferred_irqs = false;
self.signal_used_queue(&self.rx.queue).unwrap();
}
} else {
self.process_rx().unwrap();
}
} }
KILL_EVENT => { KILL_EVENT => {
debug!("KILL_EVENT received, stopping epoll loop"); debug!("KILL_EVENT received, stopping epoll loop");
break 'epoll; break 'epoll;
} }
PAUSE_EVENT => {
debug!("PAUSE_EVENT received, pausing virtio-net epoll loop");
// We loop here to handle spurious park() returns.
// Until we have not resumed, the paused boolean will
// be true.
while paused.load(Ordering::SeqCst) {
thread::park();
}
}
_ => { _ => {
error!("Unknown event for virtio-net"); error!("Unknown event for virtio-net");
} }
} }
} }
} }
Ok(()) Ok(())
} }
} }
pub struct Net { pub struct Net {
kill_evt: Option<EventFd>, kill_evt: Option<EventFd>,
tap: Option<Tap>, pause_evt: Option<EventFd>,
taps: Option<Vec<Tap>>,
avail_features: u64, avail_features: u64,
acked_features: u64, acked_features: u64,
// The config space will only consist of the MAC address specified by the user, config: VirtioNetConfig,
// or nothing, if no such address if provided.
config_space: Vec<u8>,
queue_evts: Option<Vec<EventFd>>, queue_evts: Option<Vec<EventFd>>,
interrupt_cb: Option<Arc<VirtioInterrupt>>, interrupt_cb: Option<Arc<dyn VirtioInterrupt>>,
epoll_threads: Option<Vec<thread::JoinHandle<result::Result<(), DeviceError>>>>,
ctrl_queue_epoll_thread: Option<thread::JoinHandle<result::Result<(), DeviceError>>>,
paused: Arc<AtomicBool>,
queue_size: Vec<u16>,
} }
impl Net { impl Net {
/// Create a new virtio network device with the given TAP interface. /// Create a new virtio network device with the given TAP interface.
pub fn new_with_tap(tap: Tap, guest_mac: Option<&MacAddr>, iommu: bool) -> Result<Self> { pub fn new_with_tap(
// Set offload flags to match the virtio features below. taps: Vec<Tap>,
tap.set_offload( guest_mac: Option<MacAddr>,
net_gen::TUN_F_CSUM | net_gen::TUN_F_UFO | net_gen::TUN_F_TSO4 | net_gen::TUN_F_TSO6, iommu: bool,
) num_queues: usize,
.map_err(Error::TapSetOffload)?; queue_size: u16,
) -> Result<Self> {
let vnet_hdr_size = vnet_hdr_len() as i32;
tap.set_vnet_hdr_size(vnet_hdr_size)
.map_err(Error::TapSetVnetHdrSize)?;
let mut avail_features = 1 << VIRTIO_NET_F_GUEST_CSUM let mut avail_features = 1 << VIRTIO_NET_F_GUEST_CSUM
| 1 << VIRTIO_NET_F_CSUM | 1 << VIRTIO_NET_F_CSUM
| 1 << VIRTIO_NET_F_GUEST_TSO4 | 1 << VIRTIO_NET_F_GUEST_TSO4
@@ -468,44 +327,46 @@ impl Net {
avail_features |= 1u64 << VIRTIO_F_IOMMU_PLATFORM; avail_features |= 1u64 << VIRTIO_F_IOMMU_PLATFORM;
} }
let mut config_space; avail_features |= 1 << VIRTIO_NET_F_CTRL_VQ;
let queue_num = num_queues + 1;
let mut config = VirtioNetConfig::default();
if let Some(mac) = guest_mac { if let Some(mac) = guest_mac {
config_space = Vec::with_capacity(MAC_ADDR_LEN); build_net_config_space(&mut config, mac, num_queues, &mut avail_features);
// This is safe, because we know the capacity is large enough.
unsafe { config_space.set_len(MAC_ADDR_LEN) }
config_space[..].copy_from_slice(mac.get_bytes());
// When this feature isn't available, the driver generates a random MAC address.
// Otherwise, it should attempt to read the device MAC address from the config space.
avail_features |= 1 << VIRTIO_NET_F_MAC;
} else { } else {
config_space = Vec::new(); build_net_config_space_with_mq(&mut config, num_queues, &mut avail_features);
} }
Ok(Net { Ok(Net {
kill_evt: None, kill_evt: None,
tap: Some(tap), pause_evt: None,
taps: Some(taps),
avail_features, avail_features,
acked_features: 0u64, acked_features: 0u64,
config_space, config,
queue_evts: None, queue_evts: None,
interrupt_cb: None, interrupt_cb: None,
epoll_threads: None,
ctrl_queue_epoll_thread: None,
paused: Arc::new(AtomicBool::new(false)),
queue_size: vec![queue_size; queue_num],
}) })
} }
/// Create a new virtio network device with the given IP address and /// Create a new virtio network device with the given IP address and
/// netmask. /// netmask.
pub fn new( pub fn new(
ip_addr: Ipv4Addr, if_name: Option<&str>,
netmask: Ipv4Addr, ip_addr: Option<Ipv4Addr>,
guest_mac: Option<&MacAddr>, netmask: Option<Ipv4Addr>,
guest_mac: Option<MacAddr>,
iommu: bool, iommu: bool,
num_queues: usize,
queue_size: u16,
) -> Result<Self> { ) -> Result<Self> {
let tap = Tap::new().map_err(Error::TapOpen)?; let taps = open_tap(if_name, ip_addr, netmask, num_queues / 2).map_err(Error::OpenTap)?;
tap.set_ip_addr(ip_addr).map_err(Error::TapSetIp)?;
tap.set_netmask(netmask).map_err(Error::TapSetNetmask)?;
tap.enable().map_err(Error::TapEnable)?;
Self::new_with_tap(tap, guest_mac, iommu) Self::new_with_tap(taps, guest_mac, iommu, num_queues, queue_size)
} }
} }
@@ -524,32 +385,15 @@ impl VirtioDevice for Net {
} }
fn queue_max_sizes(&self) -> &[u16] { fn queue_max_sizes(&self) -> &[u16] {
QUEUE_SIZES &self.queue_size.as_slice()
} }
fn features(&self, page: u32) -> u32 { fn features(&self) -> u64 {
match page { self.avail_features
// Get the lower 32-bits of the features bitfield.
0 => self.avail_features as u32,
// Get the upper 32-bits of the features bitfield.
1 => (self.avail_features >> 32) as u32,
_ => {
warn!("Received request for unknown features page: {}", page);
0u32
}
}
} }
fn ack_features(&mut self, page: u32, value: u32) { fn ack_features(&mut self, value: u64) {
let mut v = match page { let mut v = value;
0 => u64::from(value),
1 => u64::from(value) << 32,
_ => {
warn!("Cannot acknowledge unknown features page: {}", page);
0u64
}
};
// Check if the guest is ACK'ing a feature that we didn't claim to have. // Check if the guest is ACK'ing a feature that we didn't claim to have.
let unrequested_features = v & !self.avail_features; let unrequested_features = v & !self.avail_features;
if unrequested_features != 0 { if unrequested_features != 0 {
@@ -561,56 +405,64 @@ impl VirtioDevice for Net {
} }
fn read_config(&self, offset: u64, mut data: &mut [u8]) { fn read_config(&self, offset: u64, mut data: &mut [u8]) {
let config_len = self.config_space.len() as u64; let config_slice = self.config.as_slice();
let config_len = config_slice.len() as u64;
if offset >= config_len { if offset >= config_len {
error!("Failed to read config space"); error!("Failed to read config space");
return; return;
} }
if let Some(end) = offset.checked_add(data.len() as u64) { if let Some(end) = offset.checked_add(data.len() as u64) {
// This write can't fail, offset and end are checked against config_len. // This write can't fail, offset and end are checked against config_len.
data.write_all(&self.config_space[offset as usize..cmp::min(end, config_len) as usize]) data.write_all(&config_slice[offset as usize..cmp::min(end, config_len) as usize])
.unwrap(); .unwrap();
} }
} }
fn write_config(&mut self, offset: u64, data: &[u8]) { fn write_config(&mut self, offset: u64, data: &[u8]) {
let config_slice = self.config.as_mut_slice();
let data_len = data.len() as u64; let data_len = data.len() as u64;
let config_len = self.config_space.len() as u64; let config_len = config_slice.len() as u64;
if offset + data_len > config_len { if offset + data_len > config_len {
error!("Failed to write config space"); error!("Failed to write config space");
return; return;
} }
let (_, right) = self.config_space.split_at_mut(offset as usize); let (_, right) = config_slice.split_at_mut(offset as usize);
right.copy_from_slice(&data[..]); right.copy_from_slice(&data[..]);
} }
fn activate( fn activate(
&mut self, &mut self,
mem: Arc<RwLock<GuestMemoryMmap>>, mem: Arc<ArcSwap<GuestMemoryMmap>>,
interrupt_cb: Arc<VirtioInterrupt>, interrupt_cb: Arc<dyn VirtioInterrupt>,
mut queues: Vec<Queue>, mut queues: Vec<Queue>,
mut queue_evts: Vec<EventFd>, mut queue_evts: Vec<EventFd>,
) -> ActivateResult { ) -> ActivateResult {
if queues.len() != NUM_QUEUES || queue_evts.len() != NUM_QUEUES { if queues.len() != self.queue_size.len() || queue_evts.len() != self.queue_size.len() {
error!( error!(
"Cannot perform activate. Expected {} queue(s), got {}", "Cannot perform activate. Expected {} queue(s), got {}",
NUM_QUEUES, self.queue_size.len(),
queues.len() queues.len()
); );
return Err(ActivateError::BadActivate); return Err(ActivateError::BadActivate);
} }
let (self_kill_evt, kill_evt) = let (self_kill_evt, kill_evt) = EventFd::new(EFD_NONBLOCK)
match EventFd::new(EFD_NONBLOCK).and_then(|e| Ok((e.try_clone()?, e))) { .and_then(|e| Ok((e.try_clone()?, e)))
Ok(v) => v, .map_err(|e| {
Err(e) => { error!("failed creating kill EventFd pair: {}", e);
error!("failed creating kill EventFd pair: {}", e); ActivateError::BadActivate
return Err(ActivateError::BadActivate); })?;
}
};
self.kill_evt = Some(self_kill_evt); self.kill_evt = Some(self_kill_evt);
if let Some(tap) = self.tap.clone() { let (self_pause_evt, pause_evt) = EventFd::new(EFD_NONBLOCK)
.and_then(|e| Ok((e.try_clone()?, e)))
.map_err(|e| {
error!("failed creating pause EventFd pair: {}", e);
ActivateError::BadActivate
})?;
self.pause_evt = Some(self_pause_evt);
if let Some(mut taps) = self.taps.clone() {
// Save the interrupt EventFD as we need to return it on reset // Save the interrupt EventFD as we need to return it on reset
// but clone it to pass into the thread. // but clone it to pass into the thread.
self.interrupt_cb = Some(interrupt_cb.clone()); self.interrupt_cb = Some(interrupt_cb.clone());
@@ -626,36 +478,80 @@ impl VirtioDevice for Net {
} }
self.queue_evts = Some(tmp_queue_evts); self.queue_evts = Some(tmp_queue_evts);
let rx_queue = queues.remove(0); let queue_num = queues.len();
let tx_queue = queues.remove(0); if (self.acked_features & 1 << VIRTIO_NET_F_CTRL_VQ) != 0 && queue_num % 2 != 0 {
let rx_queue_evt = queue_evts.remove(0); let cvq_queue = queues.remove(queue_num - 1);
let tx_queue_evt = queue_evts.remove(0); let cvq_queue_evt = queue_evts.remove(queue_num - 1);
let mut handler = NetEpollHandler {
mem,
tap,
rx: RxVirtio::new(rx_queue, rx_queue_evt),
tx: TxVirtio::new(tx_queue, tx_queue_evt),
interrupt_cb,
kill_evt,
epoll_fd: 0,
rx_tap_listening: false,
};
let worker_result = thread::Builder::new() let mut ctrl_handler = NetCtrlEpollHandler {
.name("virtio_net".to_string()) mem: mem.clone(),
.spawn(move || handler.run()); kill_evt: kill_evt.try_clone().unwrap(),
pause_evt: pause_evt.try_clone().unwrap(),
ctrl_q: CtrlVirtio::new(cvq_queue, cvq_queue_evt),
epoll_fd: 0,
};
if let Err(e) = worker_result { let paused = self.paused.clone();
error!("failed to spawn virtio_blk worker: {}", e); thread::Builder::new()
return Err(ActivateError::BadActivate); .name("virtio_net".to_string())
.spawn(move || ctrl_handler.run_ctrl(paused))
.map(|thread| self.ctrl_queue_epoll_thread = Some(thread))
.map_err(|e| {
error!("failed to clone queue EventFd: {}", e);
ActivateError::BadActivate
})?;
} }
let mut epoll_threads = Vec::new();
for _ in 0..taps.len() {
let rx = RxVirtio::new();
let tx = TxVirtio::new();
let rx_tap_listening = false;
let mut queue_pair = Vec::new();
queue_pair.push(queues.remove(0));
queue_pair.push(queues.remove(0));
let mut queue_evt_pair = Vec::new();
queue_evt_pair.push(queue_evts.remove(0));
queue_evt_pair.push(queue_evts.remove(0));
let mut handler = NetEpollHandler {
mem: mem.clone(),
tap: taps.remove(0),
rx,
tx,
interrupt_cb: interrupt_cb.clone(),
kill_evt: kill_evt.try_clone().unwrap(),
pause_evt: pause_evt.try_clone().unwrap(),
epoll_fd: 0,
rx_tap_listening,
};
let paused = self.paused.clone();
thread::Builder::new()
.name("virtio_net".to_string())
.spawn(move || handler.run(paused, queue_pair, queue_evt_pair))
.map(|thread| epoll_threads.push(thread))
.map_err(|e| {
error!("failed to clone queue EventFd: {}", e);
ActivateError::BadActivate
})?;
}
self.epoll_threads = Some(epoll_threads);
return Ok(()); return Ok(());
} }
Err(ActivateError::BadActivate) Err(ActivateError::BadActivate)
} }
fn reset(&mut self) -> Option<(Arc<VirtioInterrupt>, Vec<EventFd>)> { fn reset(&mut self) -> Option<(Arc<dyn VirtioInterrupt>, Vec<EventFd>)> {
// We first must resume the virtio thread if it was paused.
if self.pause_evt.take().is_some() {
self.resume().ok()?;
}
if let Some(kill_evt) = self.kill_evt.take() { if let Some(kill_evt) = self.kill_evt.take() {
// Ignore the result because there is nothing we can do about it. // Ignore the result because there is nothing we can do about it.
let _ = kill_evt.write(1); let _ = kill_evt.write(1);
@@ -668,3 +564,7 @@ impl VirtioDevice for Net {
)) ))
} }
} }
virtio_ctrl_q_pausable!(Net);
impl Snapshotable for Net {}
impl Migratable for Net {}

498
vm-virtio/src/net_util.rs Normal file
View File

@@ -0,0 +1,498 @@
// Copyright (c) 2019 Intel Corporation. All rights reserved.
//
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
use super::Error as DeviceError;
use super::{DescriptorChain, DeviceEventT, Queue};
use arc_swap::ArcSwap;
use net_util::{MacAddr, Tap, TapError};
use std::cmp;
use std::io::{self, Write};
use std::mem;
use std::net::Ipv4Addr;
use std::os::unix::io::{AsRawFd, RawFd};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use virtio_bindings::bindings::virtio_net::*;
use vm_memory::{ByteValued, Bytes, GuestAddress, GuestMemoryError, GuestMemoryMmap};
use vmm_sys_util::eventfd::EventFd;
type Result<T> = std::result::Result<T, Error>;
/// The maximum buffer size when segmentation offload is enabled. This
/// includes the 12-byte virtio net header.
/// http://docs.oasis-open.org/virtio/virtio/v1.0/virtio-v1.0.html#x1-1740003
const MAX_BUFFER_SIZE: usize = 65562;
const QUEUE_SIZE: usize = 256;
// The guest has made a buffer available to receive a frame into.
pub const RX_QUEUE_EVENT: DeviceEventT = 0;
// The transmit queue has a frame that is ready to send from the guest.
pub const TX_QUEUE_EVENT: DeviceEventT = 1;
// A frame is available for reading from the tap device to receive in the guest.
pub const RX_TAP_EVENT: DeviceEventT = 2;
// The device has been dropped.
pub const KILL_EVENT: DeviceEventT = 3;
// The device should be paused.
pub const PAUSE_EVENT: DeviceEventT = 4;
// Number of DeviceEventT events supported by this implementation.
pub const NET_EVENTS_COUNT: usize = 5;
// The device has been dropped.
const CTRL_QUEUE_EVENT: DeviceEventT = 0;
// Number of DeviceEventT events supported by this implementation.
const CTRL_EVENT_COUNT: usize = 3;
#[repr(C, packed)]
#[derive(Copy, Clone, Debug, Default)]
pub struct VirtioNetConfig {
pub mac: [u8; 6],
pub status: u16,
pub max_virtqueue_pairs: u16,
pub mtu: u16,
pub speed: u32,
pub duplex: u8,
}
// Safe because it only has data and has no implicit padding.
unsafe impl ByteValued for VirtioNetConfig {}
#[derive(Debug)]
pub enum Error {
/// Read process MQ.
FailedProcessMQ,
/// Read queue failed.
GuestMemory(GuestMemoryError),
/// Invalid ctrl class
InvalidCtlClass,
/// Invalid ctrl command
InvalidCtlCmd,
/// Invalid descriptor
InvalidDesc,
/// Invalid queue pairs number
InvalidQueuePairsNum,
/// No memory passed in.
NoMemory,
/// No ueue pairs nummber.
NoQueuePairsNum,
/// Open tap device failed.
TapOpen(TapError),
/// Setting tap IP failed.
TapSetIp(TapError),
/// Setting tap netmask failed.
TapSetNetmask(TapError),
/// Setting tap interface offload flags failed.
TapSetOffload(TapError),
/// Setting vnet header size failed.
TapSetVnetHdrSize(TapError),
/// Enabling tap interface failed.
TapEnable(TapError),
}
pub struct CtrlVirtio {
pub queue_evt: EventFd,
pub queue: Queue,
}
impl std::clone::Clone for CtrlVirtio {
fn clone(&self) -> Self {
CtrlVirtio {
queue_evt: self.queue_evt.try_clone().unwrap(),
queue: self.queue.clone(),
}
}
}
impl CtrlVirtio {
pub fn new(queue: Queue, queue_evt: EventFd) -> Self {
CtrlVirtio { queue_evt, queue }
}
fn process_mq(&self, mem: &GuestMemoryMmap, avail_desc: DescriptorChain) -> Result<()> {
let mq_desc = if avail_desc.has_next() {
avail_desc.next_descriptor().unwrap()
} else {
return Err(Error::NoQueuePairsNum);
};
let queue_pairs = mem
.read_obj::<u16>(mq_desc.addr)
.map_err(Error::GuestMemory)?;
if (queue_pairs < VIRTIO_NET_CTRL_MQ_VQ_PAIRS_MIN as u16)
|| (queue_pairs > VIRTIO_NET_CTRL_MQ_VQ_PAIRS_MAX as u16)
{
return Err(Error::InvalidQueuePairsNum);
}
let status_desc = if mq_desc.has_next() {
mq_desc.next_descriptor().unwrap()
} else {
return Err(Error::NoQueuePairsNum);
};
mem.write_obj::<u8>(0, status_desc.addr)
.map_err(Error::GuestMemory)?;
Ok(())
}
pub fn process_cvq(&mut self, mem: &GuestMemoryMmap) -> Result<()> {
let mut used_desc_heads = [(0, 0); QUEUE_SIZE];
let mut used_count = 0;
if let Some(avail_desc) = self.queue.iter(&mem).next() {
used_desc_heads[used_count] = (avail_desc.index, avail_desc.len);
used_count += 1;
let ctrl_hdr = mem
.read_obj::<u16>(avail_desc.addr)
.map_err(Error::GuestMemory)?;
let ctrl_hdr_v = ctrl_hdr.as_slice();
let class = ctrl_hdr_v[0];
let cmd = ctrl_hdr_v[1];
match u32::from(class) {
VIRTIO_NET_CTRL_MQ => {
if u32::from(cmd) != VIRTIO_NET_CTRL_MQ_VQ_PAIRS_SET {
return Err(Error::InvalidCtlCmd);
}
if let Err(_e) = self.process_mq(&mem, avail_desc) {
return Err(Error::FailedProcessMQ);
}
}
_ => return Err(Error::InvalidCtlClass),
}
} else {
return Err(Error::InvalidDesc);
}
for &(desc_index, len) in &used_desc_heads[..used_count] {
self.queue.add_used(&mem, desc_index, len);
}
Ok(())
}
}
pub fn register_listener(
epoll_fd: RawFd,
fd: RawFd,
ev_type: epoll::Events,
data: u64,
) -> std::result::Result<(), io::Error> {
epoll::ctl(
epoll_fd,
epoll::ControlOptions::EPOLL_CTL_ADD,
fd,
epoll::Event::new(ev_type, data),
)
}
pub fn unregister_listener(
epoll_fd: RawFd,
fd: RawFd,
ev_type: epoll::Events,
data: u64,
) -> std::result::Result<(), io::Error> {
epoll::ctl(
epoll_fd,
epoll::ControlOptions::EPOLL_CTL_DEL,
fd,
epoll::Event::new(ev_type, data),
)
}
pub struct NetCtrlEpollHandler {
pub mem: Arc<ArcSwap<GuestMemoryMmap>>,
pub kill_evt: EventFd,
pub pause_evt: EventFd,
pub ctrl_q: CtrlVirtio,
pub epoll_fd: RawFd,
}
impl NetCtrlEpollHandler {
pub fn run_ctrl(&mut self, paused: Arc<AtomicBool>) -> std::result::Result<(), DeviceError> {
// Create the epoll file descriptor
self.epoll_fd = epoll::create(true).map_err(DeviceError::EpollCreateFd)?;
register_listener(
self.epoll_fd,
self.ctrl_q.queue_evt.as_raw_fd(),
epoll::Events::EPOLLIN,
u64::from(CTRL_QUEUE_EVENT),
)
.unwrap();
register_listener(
self.epoll_fd,
self.kill_evt.as_raw_fd(),
epoll::Events::EPOLLIN,
u64::from(KILL_EVENT),
)
.unwrap();
register_listener(
self.epoll_fd,
self.pause_evt.as_raw_fd(),
epoll::Events::EPOLLIN,
u64::from(PAUSE_EVENT),
)
.unwrap();
let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); CTRL_EVENT_COUNT];
'epoll: loop {
let num_events = match epoll::wait(self.epoll_fd, -1, &mut events[..]) {
Ok(res) => res,
Err(e) => {
if e.kind() == std::io::ErrorKind::Interrupted {
continue;
}
return Err(DeviceError::EpollWait(e));
}
};
for event in events.iter().take(num_events) {
let ev_type = event.data as u16;
match ev_type {
CTRL_QUEUE_EVENT => {
let mem = self.mem.load();
if let Err(e) = self.ctrl_q.queue_evt.read() {
error!("failed to get ctl queue event: {:?}", e);
}
if let Err(e) = self.ctrl_q.process_cvq(&mem) {
error!("failed to process ctrl queue: {:?}", e);
}
}
KILL_EVENT => {
break 'epoll;
}
PAUSE_EVENT => {
debug!("PAUSE_EVENT received, pausing vhost-user epoll loop");
// We loop here to handle spurious park() returns.
// Until we have not resumed, the paused boolean will
// be true.
while paused.load(Ordering::SeqCst) {
std::thread::park();
}
}
_ => {
error!("Unknown event for virtio-net");
}
}
}
}
Ok(())
}
}
#[derive(Clone)]
pub struct TxVirtio {
pub iovec: Vec<(GuestAddress, usize)>,
pub frame_buf: [u8; MAX_BUFFER_SIZE],
}
impl Default for TxVirtio {
fn default() -> Self {
Self::new()
}
}
impl TxVirtio {
pub fn new() -> Self {
TxVirtio {
iovec: Vec::new(),
frame_buf: [0u8; MAX_BUFFER_SIZE],
}
}
pub fn process_desc_chain(&mut self, mem: &GuestMemoryMmap, tap: &mut Tap, queue: &mut Queue) {
while let Some(avail_desc) = queue.iter(&mem).next() {
let head_index = avail_desc.index;
let mut read_count = 0;
let mut next_desc = Some(avail_desc);
self.iovec.clear();
while let Some(desc) = next_desc {
if desc.is_write_only() {
break;
}
self.iovec.push((desc.addr, desc.len as usize));
read_count += desc.len as usize;
next_desc = desc.next_descriptor();
}
read_count = 0;
// Copy buffer from across multiple descriptors.
// TODO(performance - Issue #420): change this to use `writev()` instead of `write()`
// and get rid of the intermediate buffer.
for (desc_addr, desc_len) in self.iovec.drain(..) {
let limit = cmp::min((read_count + desc_len) as usize, self.frame_buf.len());
let read_result =
mem.read_slice(&mut self.frame_buf[read_count..limit as usize], desc_addr);
match read_result {
Ok(_) => {
// Increment by number of bytes actually read
read_count += limit - read_count;
}
Err(e) => {
println!("Failed to read slice: {:?}", e);
break;
}
}
}
let write_result = tap.write(&self.frame_buf[..read_count]);
match write_result {
Ok(_) => {}
Err(e) => {
println!("net: tx: error failed to write to tap: {}", e);
}
};
queue.add_used(&mem, head_index, 0);
}
}
}
#[derive(Clone)]
pub struct RxVirtio {
pub deferred_frame: bool,
pub deferred_irqs: bool,
pub bytes_read: usize,
pub frame_buf: [u8; MAX_BUFFER_SIZE],
}
impl Default for RxVirtio {
fn default() -> Self {
Self::new()
}
}
impl RxVirtio {
pub fn new() -> Self {
RxVirtio {
deferred_frame: false,
deferred_irqs: false,
bytes_read: 0,
frame_buf: [0u8; MAX_BUFFER_SIZE],
}
}
pub fn process_desc_chain(
&mut self,
mem: &GuestMemoryMmap,
mut next_desc: Option<DescriptorChain>,
queue: &mut Queue,
) -> bool {
let head_index = next_desc.as_ref().unwrap().index;
let mut write_count = 0;
// Copy from frame into buffer, which may span multiple descriptors.
loop {
match next_desc {
Some(desc) => {
if !desc.is_write_only() {
break;
}
let limit = cmp::min(write_count + desc.len as usize, self.bytes_read);
let source_slice = &self.frame_buf[write_count..limit];
let write_result = mem.write_slice(source_slice, desc.addr);
match write_result {
Ok(_) => {
write_count = limit;
}
Err(e) => {
error!("Failed to write slice: {:?}", e);
break;
}
};
if write_count >= self.bytes_read {
break;
}
next_desc = desc.next_descriptor();
}
None => {
warn!("Receiving buffer is too small to hold frame of current size");
break;
}
}
}
queue.add_used(&mem, head_index, write_count as u32);
// Mark that we have at least one pending packet and we need to interrupt the guest.
self.deferred_irqs = true;
write_count >= self.bytes_read
}
}
pub fn build_net_config_space(
mut config: &mut VirtioNetConfig,
mac: MacAddr,
num_queues: usize,
mut avail_features: &mut u64,
) {
config.mac.copy_from_slice(mac.get_bytes());
*avail_features |= 1 << VIRTIO_NET_F_MAC;
build_net_config_space_with_mq(&mut config, num_queues, &mut avail_features);
}
pub fn build_net_config_space_with_mq(
config: &mut VirtioNetConfig,
num_queues: usize,
avail_features: &mut u64,
) {
let num_queue_pairs = (num_queues / 2) as u16;
if (num_queue_pairs >= VIRTIO_NET_CTRL_MQ_VQ_PAIRS_MIN as u16)
&& (num_queue_pairs <= VIRTIO_NET_CTRL_MQ_VQ_PAIRS_MAX as u16)
{
config.max_virtqueue_pairs = num_queue_pairs;
*avail_features |= 1 << VIRTIO_NET_F_MQ;
}
}
fn vnet_hdr_len() -> usize {
mem::size_of::<virtio_net_hdr_v1>()
}
/// Create a new virtio network device with the given IP address and
/// netmask.
pub fn open_tap(
if_name: Option<&str>,
ip_addr: Option<Ipv4Addr>,
netmask: Option<Ipv4Addr>,
num_rx_q: usize,
) -> Result<Vec<Tap>> {
let mut taps: Vec<Tap> = Vec::new();
let mut ifname: String = String::new();
let vnet_hdr_size = vnet_hdr_len() as i32;
let flag = net_gen::TUN_F_CSUM | net_gen::TUN_F_UFO | net_gen::TUN_F_TSO4 | net_gen::TUN_F_TSO6;
for i in 0..num_rx_q {
let tap: Tap;
if i == 0 {
tap = match if_name {
Some(name) => Tap::open_named(name, num_rx_q).map_err(Error::TapOpen)?,
None => Tap::new(num_rx_q).map_err(Error::TapOpen)?,
};
if let Some(ip) = ip_addr {
tap.set_ip_addr(ip).map_err(Error::TapSetIp)?;
}
if let Some(mask) = netmask {
tap.set_netmask(mask).map_err(Error::TapSetNetmask)?;
}
tap.enable().map_err(Error::TapEnable)?;
tap.set_offload(flag).map_err(Error::TapSetOffload)?;
tap.set_vnet_hdr_size(vnet_hdr_size)
.map_err(Error::TapSetVnetHdrSize)?;
ifname = String::from_utf8(tap.get_if_name()).unwrap();
} else {
tap = Tap::open_named(ifname.as_str(), num_rx_q).map_err(Error::TapOpen)?;
tap.set_offload(flag).map_err(Error::TapSetOffload)?;
tap.set_vnet_hdr_size(vnet_hdr_size)
.map_err(Error::TapSetVnetHdrSize)?;
}
taps.push(tap);
}
Ok(taps)
}

View File

@@ -6,6 +6,13 @@
// //
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause // SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
use super::Error as DeviceError;
use super::{
ActivateError, ActivateResult, DescriptorChain, DeviceEventT, Queue, VirtioDevice,
VirtioDeviceType, VIRTIO_F_IOMMU_PLATFORM, VIRTIO_F_VERSION_1,
};
use crate::{VirtioInterrupt, VirtioInterruptType};
use arc_swap::ArcSwap;
use epoll; use epoll;
use libc::EFD_NONBLOCK; use libc::EFD_NONBLOCK;
use std::cmp; use std::cmp;
@@ -15,15 +22,10 @@ use std::io::{self, Write};
use std::mem::size_of; use std::mem::size_of;
use std::os::unix::io::AsRawFd; use std::os::unix::io::AsRawFd;
use std::result; use std::result;
use std::sync::{Arc, RwLock}; use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::thread; use std::thread;
use vm_device::{Migratable, MigratableError, Pausable, Snapshotable};
use super::Error as DeviceError;
use super::{
ActivateError, ActivateResult, DescriptorChain, DeviceEventT, Queue, VirtioDevice,
VirtioDeviceType, VIRTIO_F_IOMMU_PLATFORM, VIRTIO_F_VERSION_1,
};
use crate::{VirtioInterrupt, VirtioInterruptType};
use vm_memory::{ use vm_memory::{
Address, ByteValued, Bytes, GuestAddress, GuestMemoryError, GuestMemoryMmap, GuestUsize, Address, ByteValued, Bytes, GuestAddress, GuestMemoryError, GuestMemoryMmap, GuestUsize,
}; };
@@ -41,6 +43,8 @@ const VIRTIO_PMEM_RESP_TYPE_EIO: u32 = 1;
const QUEUE_AVAIL_EVENT: DeviceEventT = 0; const QUEUE_AVAIL_EVENT: DeviceEventT = 0;
// The device has been dropped. // The device has been dropped.
const KILL_EVENT: DeviceEventT = 1; const KILL_EVENT: DeviceEventT = 1;
// The device should be paused.
const PAUSE_EVENT: DeviceEventT = 2;
#[derive(Copy, Clone, Debug, Default)] #[derive(Copy, Clone, Debug, Default)]
#[repr(C)] #[repr(C)]
@@ -154,18 +158,19 @@ impl Request {
struct PmemEpollHandler { struct PmemEpollHandler {
queue: Queue, queue: Queue,
mem: Arc<RwLock<GuestMemoryMmap>>, mem: Arc<ArcSwap<GuestMemoryMmap>>,
disk: File, disk: File,
interrupt_cb: Arc<VirtioInterrupt>, interrupt_cb: Arc<dyn VirtioInterrupt>,
queue_evt: EventFd, queue_evt: EventFd,
kill_evt: EventFd, kill_evt: EventFd,
pause_evt: EventFd,
} }
impl PmemEpollHandler { impl PmemEpollHandler {
fn process_queue(&mut self) -> bool { fn process_queue(&mut self) -> bool {
let mut used_desc_heads = [(0, 0); QUEUE_SIZE as usize]; let mut used_desc_heads = [(0, 0); QUEUE_SIZE as usize];
let mut used_count = 0; let mut used_count = 0;
let mem = self.mem.read().unwrap(); let mem = self.mem.load();
for avail_desc in self.queue.iter(&mem) { for avail_desc in self.queue.iter(&mem) {
let len = match Request::parse(&avail_desc, &mem) { let len = match Request::parse(&avail_desc, &mem) {
Ok(ref req) if (req.type_ == RequestType::Flush) => { Ok(ref req) if (req.type_ == RequestType::Flush) => {
@@ -208,13 +213,15 @@ impl PmemEpollHandler {
} }
fn signal_used_queue(&self) -> result::Result<(), DeviceError> { fn signal_used_queue(&self) -> result::Result<(), DeviceError> {
(self.interrupt_cb)(&VirtioInterruptType::Queue, Some(&self.queue)).map_err(|e| { self.interrupt_cb
error!("Failed to signal used queue: {:?}", e); .trigger(&VirtioInterruptType::Queue, Some(&self.queue))
DeviceError::FailedSignalingUsedQueue(e) .map_err(|e| {
}) error!("Failed to signal used queue: {:?}", e);
DeviceError::FailedSignalingUsedQueue(e)
})
} }
fn run(&mut self) -> result::Result<(), DeviceError> { fn run(&mut self, paused: Arc<AtomicBool>) -> result::Result<(), DeviceError> {
// Create the epoll file descriptor // Create the epoll file descriptor
let epoll_fd = epoll::create(true).map_err(DeviceError::EpollCreateFd)?; let epoll_fd = epoll::create(true).map_err(DeviceError::EpollCreateFd)?;
@@ -234,6 +241,14 @@ impl PmemEpollHandler {
) )
.map_err(DeviceError::EpollCtl)?; .map_err(DeviceError::EpollCtl)?;
epoll::ctl(
epoll_fd,
epoll::ControlOptions::EPOLL_CTL_ADD,
self.pause_evt.as_raw_fd(),
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(PAUSE_EVENT)),
)
.map_err(DeviceError::EpollCtl)?;
const EPOLL_EVENTS_LEN: usize = 100; const EPOLL_EVENTS_LEN: usize = 100;
let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN]; let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN];
@@ -274,6 +289,15 @@ impl PmemEpollHandler {
debug!("kill_evt received, stopping epoll loop"); debug!("kill_evt received, stopping epoll loop");
break 'epoll; break 'epoll;
} }
PAUSE_EVENT => {
debug!("PAUSE_EVENT received, pausing virtio-pmem epoll loop");
// We loop here to handle spurious park() returns.
// Until we have not resumed, the paused boolean will
// be true.
while paused.load(Ordering::SeqCst) {
thread::park();
}
}
_ => { _ => {
error!("Unknown event for virtio-block"); error!("Unknown event for virtio-block");
} }
@@ -287,12 +311,15 @@ impl PmemEpollHandler {
pub struct Pmem { pub struct Pmem {
kill_evt: Option<EventFd>, kill_evt: Option<EventFd>,
pause_evt: Option<EventFd>,
disk: Option<File>, disk: Option<File>,
avail_features: u64, avail_features: u64,
acked_features: u64, acked_features: u64,
config: VirtioPmemConfig, config: VirtioPmemConfig,
queue_evts: Option<Vec<EventFd>>, queue_evts: Option<Vec<EventFd>>,
interrupt_cb: Option<Arc<VirtioInterrupt>>, interrupt_cb: Option<Arc<dyn VirtioInterrupt>>,
epoll_threads: Option<Vec<thread::JoinHandle<result::Result<(), DeviceError>>>>,
paused: Arc<AtomicBool>,
} }
impl Pmem { impl Pmem {
@@ -310,12 +337,15 @@ impl Pmem {
Ok(Pmem { Ok(Pmem {
kill_evt: None, kill_evt: None,
pause_evt: None,
disk: Some(disk), disk: Some(disk),
avail_features, avail_features,
acked_features: 0u64, acked_features: 0u64,
config, config,
queue_evts: None, queue_evts: None,
interrupt_cb: None, interrupt_cb: None,
epoll_threads: None,
paused: Arc::new(AtomicBool::new(false)),
}) })
} }
} }
@@ -338,29 +368,12 @@ impl VirtioDevice for Pmem {
QUEUE_SIZES QUEUE_SIZES
} }
fn features(&self, page: u32) -> u32 { fn features(&self) -> u64 {
match page { self.avail_features
// Get the lower 32-bits of the features bitfield.
0 => self.avail_features as u32,
// Get the upper 32-bits of the features bitfield.
1 => (self.avail_features >> 32) as u32,
_ => {
warn!("Received request for unknown features page.");
0u32
}
}
} }
fn ack_features(&mut self, page: u32, value: u32) { fn ack_features(&mut self, value: u64) {
let mut v = match page { let mut v = value;
0 => u64::from(value),
1 => u64::from(value) << 32,
_ => {
warn!("Cannot acknowledge unknown features page.");
0u64
}
};
// Check if the guest is ACK'ing a feature that we didn't claim to have. // Check if the guest is ACK'ing a feature that we didn't claim to have.
let unrequested_features = v & !self.avail_features; let unrequested_features = v & !self.avail_features;
if unrequested_features != 0 { if unrequested_features != 0 {
@@ -393,8 +406,8 @@ impl VirtioDevice for Pmem {
fn activate( fn activate(
&mut self, &mut self,
mem: Arc<RwLock<GuestMemoryMmap>>, mem: Arc<ArcSwap<GuestMemoryMmap>>,
interrupt_cb: Arc<VirtioInterrupt>, interrupt_cb: Arc<dyn VirtioInterrupt>,
mut queues: Vec<Queue>, mut queues: Vec<Queue>,
mut queue_evts: Vec<EventFd>, mut queue_evts: Vec<EventFd>,
) -> ActivateResult { ) -> ActivateResult {
@@ -407,16 +420,22 @@ impl VirtioDevice for Pmem {
return Err(ActivateError::BadActivate); return Err(ActivateError::BadActivate);
} }
let (self_kill_evt, kill_evt) = let (self_kill_evt, kill_evt) = EventFd::new(EFD_NONBLOCK)
match EventFd::new(EFD_NONBLOCK).and_then(|e| Ok((e.try_clone()?, e))) { .and_then(|e| Ok((e.try_clone()?, e)))
Ok(v) => v, .map_err(|e| {
Err(e) => { error!("failed creating kill EventFd pair: {}", e);
error!("failed creating kill EventFd pair: {}", e); ActivateError::BadActivate
return Err(ActivateError::BadActivate); })?;
}
};
self.kill_evt = Some(self_kill_evt); self.kill_evt = Some(self_kill_evt);
let (self_pause_evt, pause_evt) = EventFd::new(EFD_NONBLOCK)
.and_then(|e| Ok((e.try_clone()?, e)))
.map_err(|e| {
error!("failed creating pause EventFd pair: {}", e);
ActivateError::BadActivate
})?;
self.pause_evt = Some(self_pause_evt);
// Save the interrupt EventFD as we need to return it on reset // Save the interrupt EventFD as we need to return it on reset
// but clone it to pass into the thread. // but clone it to pass into the thread.
self.interrupt_cb = Some(interrupt_cb.clone()); self.interrupt_cb = Some(interrupt_cb.clone());
@@ -444,23 +463,33 @@ impl VirtioDevice for Pmem {
interrupt_cb, interrupt_cb,
queue_evt: queue_evts.remove(0), queue_evt: queue_evts.remove(0),
kill_evt, kill_evt,
pause_evt,
}; };
let worker_result = thread::Builder::new() let paused = self.paused.clone();
let mut epoll_threads = Vec::new();
thread::Builder::new()
.name("virtio_pmem".to_string()) .name("virtio_pmem".to_string())
.spawn(move || handler.run()); .spawn(move || handler.run(paused))
.map(|thread| epoll_threads.push(thread))
.map_err(|e| {
error!("failed to clone virtio-pmem epoll thread: {}", e);
ActivateError::BadActivate
})?;
if let Err(e) = worker_result { self.epoll_threads = Some(epoll_threads);
error!("failed to spawn virtio_pmem worker: {}", e);
return Err(ActivateError::BadActivate);
}
return Ok(()); return Ok(());
} }
Err(ActivateError::BadActivate) Err(ActivateError::BadActivate)
} }
fn reset(&mut self) -> Option<(Arc<VirtioInterrupt>, Vec<EventFd>)> { fn reset(&mut self) -> Option<(Arc<dyn VirtioInterrupt>, Vec<EventFd>)> {
// We first must resume the virtio thread if it was paused.
if self.pause_evt.take().is_some() {
self.resume().ok()?;
}
if let Some(kill_evt) = self.kill_evt.take() { if let Some(kill_evt) = self.kill_evt.take() {
// Ignore the result because there is nothing we can do about it. // Ignore the result because there is nothing we can do about it.
let _ = kill_evt.write(1); let _ = kill_evt.write(1);
@@ -473,3 +502,7 @@ impl VirtioDevice for Pmem {
)) ))
} }
} }
virtio_pausable!(Pmem);
impl Snapshotable for Pmem {}
impl Migratable for Pmem {}

View File

@@ -29,10 +29,41 @@ pub(super) const VIRTQ_DESC_F_WRITE: u16 = 0x2;
// The Virtio Spec 1.0 defines the alignment of VirtIO descriptor is 16 bytes, // The Virtio Spec 1.0 defines the alignment of VirtIO descriptor is 16 bytes,
// which fulfills the explicit constraint of GuestMemoryMmap::read_obj(). // which fulfills the explicit constraint of GuestMemoryMmap::read_obj().
/// An iterator over a single descriptor chain. Not to be confused with AvailIter,
/// which iterates over the descriptor chain heads in a queue.
pub struct DescIter<'a> {
next: Option<DescriptorChain<'a>>,
}
impl<'a> DescIter<'a> {
/// Returns an iterator that only yields the readable descriptors in the chain.
pub fn readable(self) -> impl Iterator<Item = DescriptorChain<'a>> {
self.filter(|d| !d.is_write_only())
}
/// Returns an iterator that only yields the writable descriptors in the chain.
pub fn writable(self) -> impl Iterator<Item = DescriptorChain<'a>> {
self.filter(DescriptorChain::is_write_only)
}
}
impl<'a> Iterator for DescIter<'a> {
type Item = DescriptorChain<'a>;
fn next(&mut self) -> Option<Self::Item> {
if let Some(current) = self.next.take() {
self.next = current.next_descriptor();
Some(current)
} else {
None
}
}
}
/// A virtio descriptor constraints with C representive. /// A virtio descriptor constraints with C representive.
#[repr(C)] #[repr(C)]
#[derive(Default, Clone, Copy)] #[derive(Default, Clone, Copy)]
struct Descriptor { pub struct Descriptor {
addr: u64, addr: u64,
len: u32, len: u32,
flags: u16, flags: u16,
@@ -42,6 +73,7 @@ struct Descriptor {
unsafe impl ByteValued for Descriptor {} unsafe impl ByteValued for Descriptor {}
/// A virtio descriptor chain. /// A virtio descriptor chain.
#[derive(Clone)]
pub struct DescriptorChain<'a> { pub struct DescriptorChain<'a> {
desc_table: GuestAddress, desc_table: GuestAddress,
queue_size: u16, queue_size: u16,
@@ -69,7 +101,7 @@ pub struct DescriptorChain<'a> {
} }
impl<'a> DescriptorChain<'a> { impl<'a> DescriptorChain<'a> {
fn checked_new( pub fn checked_new(
mem: &GuestMemoryMmap, mem: &GuestMemoryMmap,
desc_table: GuestAddress, desc_table: GuestAddress,
queue_size: u16, queue_size: u16,
@@ -167,6 +199,15 @@ impl<'a> DescriptorChain<'a> {
} }
} }
impl<'a> IntoIterator for DescriptorChain<'a> {
type Item = DescriptorChain<'a>;
type IntoIter = DescIter<'a>;
fn into_iter(self) -> Self::IntoIter {
DescIter { next: Some(self) }
}
}
/// Consuming iterator over all available descriptor chain heads in the queue. /// Consuming iterator over all available descriptor chain heads in the queue.
pub struct AvailIter<'a, 'b> { pub struct AvailIter<'a, 'b> {
mem: &'a GuestMemoryMmap, mem: &'a GuestMemoryMmap,
@@ -690,7 +731,7 @@ pub(crate) mod tests {
#[test] #[test]
fn test_checked_new_descriptor_chain() { fn test_checked_new_descriptor_chain() {
let m = &GuestMemoryMmap::new(&[(GuestAddress(0), 0x10000)]).unwrap(); let m = &GuestMemoryMmap::from_ranges(&[(GuestAddress(0), 0x10000)]).unwrap();
let vq = VirtQueue::new(GuestAddress(0), m, 16); let vq = VirtQueue::new(GuestAddress(0), m, 16);
assert!(vq.end().0 < 0x1000); assert!(vq.end().0 < 0x1000);
@@ -751,7 +792,7 @@ pub(crate) mod tests {
#[test] #[test]
fn test_queue_and_iterator() { fn test_queue_and_iterator() {
let m = &GuestMemoryMmap::new(&[(GuestAddress(0), 0x10000)]).unwrap(); let m = &GuestMemoryMmap::from_ranges(&[(GuestAddress(0), 0x10000)]).unwrap();
let vq = VirtQueue::new(GuestAddress(0), m, 16); let vq = VirtQueue::new(GuestAddress(0), m, 16);
let mut q = vq.create_queue(); let mut q = vq.create_queue();
@@ -856,7 +897,7 @@ pub(crate) mod tests {
#[test] #[test]
fn test_add_used() { fn test_add_used() {
let m = &GuestMemoryMmap::new(&[(GuestAddress(0), 0x10000)]).unwrap(); let m = &GuestMemoryMmap::from_ranges(&[(GuestAddress(0), 0x10000)]).unwrap();
let vq = VirtQueue::new(GuestAddress(0), m, 16); let vq = VirtQueue::new(GuestAddress(0), m, 16);
let mut q = vq.create_queue(); let mut q = vq.create_queue();

View File

@@ -2,22 +2,24 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
use epoll;
use libc::EFD_NONBLOCK;
use std;
use std::fs::File;
use std::io;
use std::os::unix::io::AsRawFd;
use std::result;
use std::sync::{Arc, RwLock};
use std::thread;
use super::Error as DeviceError; use super::Error as DeviceError;
use super::{ use super::{
ActivateError, ActivateResult, DeviceEventT, Queue, VirtioDevice, VirtioDeviceType, ActivateError, ActivateResult, DeviceEventT, Queue, VirtioDevice, VirtioDeviceType,
VIRTIO_F_IOMMU_PLATFORM, VIRTIO_F_VERSION_1, VIRTIO_F_IOMMU_PLATFORM, VIRTIO_F_VERSION_1,
}; };
use crate::{VirtioInterrupt, VirtioInterruptType}; use crate::{VirtioInterrupt, VirtioInterruptType};
use arc_swap::ArcSwap;
use epoll;
use libc::EFD_NONBLOCK;
use std;
use std::fs::File;
use std::io;
use std::os::unix::io::AsRawFd;
use std::result;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::thread;
use vm_device::{Migratable, MigratableError, Pausable, Snapshotable};
use vm_memory::{Bytes, GuestMemoryMmap}; use vm_memory::{Bytes, GuestMemoryMmap};
use vmm_sys_util::eventfd::EventFd; use vmm_sys_util::eventfd::EventFd;
@@ -29,14 +31,17 @@ const QUEUE_SIZES: &[u16] = &[QUEUE_SIZE];
const QUEUE_AVAIL_EVENT: DeviceEventT = 0; const QUEUE_AVAIL_EVENT: DeviceEventT = 0;
// The device has been dropped. // The device has been dropped.
const KILL_EVENT: DeviceEventT = 1; const KILL_EVENT: DeviceEventT = 1;
// The device should be paused.
const PAUSE_EVENT: DeviceEventT = 2;
struct RngEpollHandler { struct RngEpollHandler {
queues: Vec<Queue>, queues: Vec<Queue>,
mem: Arc<RwLock<GuestMemoryMmap>>, mem: Arc<ArcSwap<GuestMemoryMmap>>,
random_file: File, random_file: File,
interrupt_cb: Arc<VirtioInterrupt>, interrupt_cb: Arc<dyn VirtioInterrupt>,
queue_evt: EventFd, queue_evt: EventFd,
kill_evt: EventFd, kill_evt: EventFd,
pause_evt: EventFd,
} }
impl RngEpollHandler { impl RngEpollHandler {
@@ -45,7 +50,7 @@ impl RngEpollHandler {
let mut used_desc_heads = [(0, 0); QUEUE_SIZE as usize]; let mut used_desc_heads = [(0, 0); QUEUE_SIZE as usize];
let mut used_count = 0; let mut used_count = 0;
let mem = self.mem.read().unwrap(); let mem = self.mem.load();
for avail_desc in queue.iter(&mem) { for avail_desc in queue.iter(&mem) {
let mut len = 0; let mut len = 0;
@@ -75,13 +80,15 @@ impl RngEpollHandler {
} }
fn signal_used_queue(&self) -> result::Result<(), DeviceError> { fn signal_used_queue(&self) -> result::Result<(), DeviceError> {
(self.interrupt_cb)(&VirtioInterruptType::Queue, Some(&self.queues[0])).map_err(|e| { self.interrupt_cb
error!("Failed to signal used queue: {:?}", e); .trigger(&VirtioInterruptType::Queue, Some(&self.queues[0]))
DeviceError::FailedSignalingUsedQueue(e) .map_err(|e| {
}) error!("Failed to signal used queue: {:?}", e);
DeviceError::FailedSignalingUsedQueue(e)
})
} }
fn run(&mut self) -> result::Result<(), DeviceError> { fn run(&mut self, paused: Arc<AtomicBool>) -> result::Result<(), DeviceError> {
// Create the epoll file descriptor // Create the epoll file descriptor
let epoll_fd = epoll::create(true).map_err(DeviceError::EpollCreateFd)?; let epoll_fd = epoll::create(true).map_err(DeviceError::EpollCreateFd)?;
@@ -100,6 +107,13 @@ impl RngEpollHandler {
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(KILL_EVENT)), epoll::Event::new(epoll::Events::EPOLLIN, u64::from(KILL_EVENT)),
) )
.map_err(DeviceError::EpollCtl)?; .map_err(DeviceError::EpollCtl)?;
epoll::ctl(
epoll_fd,
epoll::ControlOptions::EPOLL_CTL_ADD,
self.pause_evt.as_raw_fd(),
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(PAUSE_EVENT)),
)
.map_err(DeviceError::EpollCtl)?;
const EPOLL_EVENTS_LEN: usize = 100; const EPOLL_EVENTS_LEN: usize = 100;
let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN]; let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN];
@@ -141,6 +155,15 @@ impl RngEpollHandler {
debug!("KILL_EVENT received, stopping epoll loop"); debug!("KILL_EVENT received, stopping epoll loop");
break 'epoll; break 'epoll;
} }
PAUSE_EVENT => {
debug!("PAUSE_EVENT received, pausing virtio-rng epoll loop");
// We loop here to handle spurious park() returns.
// Until we have not resumed, the paused boolean will
// be true.
while paused.load(Ordering::SeqCst) {
thread::park();
}
}
_ => { _ => {
error!("Unknown event for virtio-block"); error!("Unknown event for virtio-block");
} }
@@ -155,11 +178,14 @@ impl RngEpollHandler {
/// Virtio device for exposing entropy to the guest OS through virtio. /// Virtio device for exposing entropy to the guest OS through virtio.
pub struct Rng { pub struct Rng {
kill_evt: Option<EventFd>, kill_evt: Option<EventFd>,
pause_evt: Option<EventFd>,
random_file: Option<File>, random_file: Option<File>,
avail_features: u64, avail_features: u64,
acked_features: u64, acked_features: u64,
queue_evts: Option<Vec<EventFd>>, queue_evts: Option<Vec<EventFd>>,
interrupt_cb: Option<Arc<VirtioInterrupt>>, interrupt_cb: Option<Arc<dyn VirtioInterrupt>>,
epoll_threads: Option<Vec<thread::JoinHandle<result::Result<(), DeviceError>>>>,
paused: Arc<AtomicBool>,
} }
impl Rng { impl Rng {
@@ -174,11 +200,14 @@ impl Rng {
Ok(Rng { Ok(Rng {
kill_evt: None, kill_evt: None,
pause_evt: None,
random_file: Some(random_file), random_file: Some(random_file),
avail_features, avail_features,
acked_features: 0u64, acked_features: 0u64,
queue_evts: None, queue_evts: None,
interrupt_cb: None, interrupt_cb: None,
epoll_threads: None,
paused: Arc::new(AtomicBool::new(false)),
}) })
} }
} }
@@ -201,29 +230,12 @@ impl VirtioDevice for Rng {
QUEUE_SIZES QUEUE_SIZES
} }
fn features(&self, page: u32) -> u32 { fn features(&self) -> u64 {
match page { self.avail_features
// Get the lower 32-bits of the features bitfield.
0 => self.avail_features as u32,
// Get the upper 32-bits of the features bitfield.
1 => (self.avail_features >> 32) as u32,
_ => {
warn!("Received request for unknown features page.");
0u32
}
}
} }
fn ack_features(&mut self, page: u32, value: u32) { fn ack_features(&mut self, value: u64) {
let mut v = match page { let mut v = value;
0 => u64::from(value),
1 => u64::from(value) << 32,
_ => {
warn!("Cannot acknowledge unknown features page.");
0u64
}
};
// Check if the guest is ACK'ing a feature that we didn't claim to have. // Check if the guest is ACK'ing a feature that we didn't claim to have.
let unrequested_features = v & !self.avail_features; let unrequested_features = v & !self.avail_features;
if unrequested_features != 0 { if unrequested_features != 0 {
@@ -245,8 +257,8 @@ impl VirtioDevice for Rng {
fn activate( fn activate(
&mut self, &mut self,
mem: Arc<RwLock<GuestMemoryMmap>>, mem: Arc<ArcSwap<GuestMemoryMmap>>,
interrupt_cb: Arc<VirtioInterrupt>, interrupt_cb: Arc<dyn VirtioInterrupt>,
queues: Vec<Queue>, queues: Vec<Queue>,
mut queue_evts: Vec<EventFd>, mut queue_evts: Vec<EventFd>,
) -> ActivateResult { ) -> ActivateResult {
@@ -259,16 +271,22 @@ impl VirtioDevice for Rng {
return Err(ActivateError::BadActivate); return Err(ActivateError::BadActivate);
} }
let (self_kill_evt, kill_evt) = let (self_kill_evt, kill_evt) = EventFd::new(EFD_NONBLOCK)
match EventFd::new(EFD_NONBLOCK).and_then(|e| Ok((e.try_clone()?, e))) { .and_then(|e| Ok((e.try_clone()?, e)))
Ok(v) => v, .map_err(|e| {
Err(e) => { error!("failed creating kill EventFd pair: {}", e);
error!("failed creating kill EventFd pair: {}", e); ActivateError::BadActivate
return Err(ActivateError::BadActivate); })?;
}
};
self.kill_evt = Some(self_kill_evt); self.kill_evt = Some(self_kill_evt);
let (self_pause_evt, pause_evt) = EventFd::new(EFD_NONBLOCK)
.and_then(|e| Ok((e.try_clone()?, e)))
.map_err(|e| {
error!("failed creating pause EventFd pair: {}", e);
ActivateError::BadActivate
})?;
self.pause_evt = Some(self_pause_evt);
// Save the interrupt EventFD as we need to return it on reset // Save the interrupt EventFD as we need to return it on reset
// but clone it to pass into the thread. // but clone it to pass into the thread.
self.interrupt_cb = Some(interrupt_cb.clone()); self.interrupt_cb = Some(interrupt_cb.clone());
@@ -296,23 +314,34 @@ impl VirtioDevice for Rng {
interrupt_cb, interrupt_cb,
queue_evt: queue_evts.remove(0), queue_evt: queue_evts.remove(0),
kill_evt, kill_evt,
pause_evt,
}; };
let worker_result = thread::Builder::new() let paused = self.paused.clone();
let mut epoll_threads = Vec::new();
thread::Builder::new()
.name("virtio_rng".to_string()) .name("virtio_rng".to_string())
.spawn(move || handler.run()); .spawn(move || handler.run(paused))
.map(|thread| epoll_threads.push(thread))
.map_err(|e| {
error!("failed to clone the virtio-rng epoll thread: {}", e);
ActivateError::BadActivate
})?;
if let Err(e) = worker_result { self.epoll_threads = Some(epoll_threads);
error!("failed to spawn virtio_rng worker: {}", e);
return Err(ActivateError::BadActivate);
}
return Ok(()); return Ok(());
} }
Err(ActivateError::BadActivate) Err(ActivateError::BadActivate)
} }
fn reset(&mut self) -> Option<(Arc<VirtioInterrupt>, Vec<EventFd>)> { fn reset(&mut self) -> Option<(Arc<dyn VirtioInterrupt>, Vec<EventFd>)> {
// We first must resume the virtio thread if it was paused.
if self.pause_evt.take().is_some() {
self.resume().ok()?;
}
// Then kill it.
if let Some(kill_evt) = self.kill_evt.take() { if let Some(kill_evt) = self.kill_evt.take() {
// Ignore the result because there is nothing we can do about it. // Ignore the result because there is nothing we can do about it.
let _ = kill_evt.write(1); let _ = kill_evt.write(1);
@@ -325,3 +354,7 @@ impl VirtioDevice for Rng {
)) ))
} }
} }
virtio_pausable!(Rng);
impl Snapshotable for Rng {}
impl Migratable for Rng {}

View File

@@ -2,18 +2,21 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
use std::sync::atomic::{AtomicUsize, Ordering}; use crate::transport::{VirtioTransport, NOTIFY_REG_OFFSET};
use std::sync::{Arc, RwLock};
use byteorder::{ByteOrder, LittleEndian};
use libc::EFD_NONBLOCK;
use crate::{ use crate::{
Queue, VirtioDevice, VirtioInterrupt, VirtioInterruptType, DEVICE_ACKNOWLEDGE, DEVICE_DRIVER, Queue, VirtioDevice, VirtioInterrupt, VirtioInterruptType, DEVICE_ACKNOWLEDGE, DEVICE_DRIVER,
DEVICE_DRIVER_OK, DEVICE_FAILED, DEVICE_FEATURES_OK, DEVICE_INIT, DEVICE_DRIVER_OK, DEVICE_FAILED, DEVICE_FEATURES_OK, DEVICE_INIT,
INTERRUPT_STATUS_CONFIG_CHANGED, INTERRUPT_STATUS_USED_RING, INTERRUPT_STATUS_CONFIG_CHANGED, INTERRUPT_STATUS_USED_RING,
}; };
use devices::{BusDevice, Interrupt}; use arc_swap::ArcSwap;
use byteorder::{ByteOrder, LittleEndian};
use devices::BusDevice;
use libc::EFD_NONBLOCK;
use std::result;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, Mutex};
use vm_device::interrupt::InterruptSourceGroup;
use vm_device::{Migratable, MigratableError, Pausable, Snapshotable};
use vm_memory::{GuestAddress, GuestMemoryMmap}; use vm_memory::{GuestAddress, GuestMemoryMmap};
use vmm_sys_util::{errno::Result, eventfd::EventFd}; use vmm_sys_util::{errno::Result, eventfd::EventFd};
@@ -22,6 +25,40 @@ const VENDOR_ID: u32 = 0;
const MMIO_MAGIC_VALUE: u32 = 0x7472_6976; const MMIO_MAGIC_VALUE: u32 = 0x7472_6976;
const MMIO_VERSION: u32 = 2; const MMIO_VERSION: u32 = 2;
pub struct VirtioInterruptIntx {
interrupt_status: Arc<AtomicUsize>,
interrupt: Arc<Box<dyn InterruptSourceGroup>>,
}
impl VirtioInterruptIntx {
pub fn new(
interrupt_status: Arc<AtomicUsize>,
interrupt: Arc<Box<dyn InterruptSourceGroup>>,
) -> Self {
VirtioInterruptIntx {
interrupt_status,
interrupt,
}
}
}
impl VirtioInterrupt for VirtioInterruptIntx {
fn trigger(
&self,
int_type: &VirtioInterruptType,
_queue: Option<&Queue>,
) -> std::result::Result<(), std::io::Error> {
let status = match int_type {
VirtioInterruptType::Config => INTERRUPT_STATUS_CONFIG_CHANGED,
VirtioInterruptType::Queue => INTERRUPT_STATUS_USED_RING,
};
self.interrupt_status
.fetch_or(status as usize, Ordering::SeqCst);
self.interrupt.trigger(0)
}
}
/// Implements the /// Implements the
/// [MMIO](http://docs.oasis-open.org/virtio/virtio/v1.0/cs04/virtio-v1.0-cs04.html#x1-1090002) /// [MMIO](http://docs.oasis-open.org/virtio/virtio/v1.0/cs04/virtio-v1.0-cs04.html#x1-1090002)
/// transport for virtio devices. /// transport for virtio devices.
@@ -37,32 +74,34 @@ const MMIO_VERSION: u32 = 2;
/// Typically one page (4096 bytes) of MMIO address space is sufficient to handle this transport /// Typically one page (4096 bytes) of MMIO address space is sufficient to handle this transport
/// and inner virtio device. /// and inner virtio device.
pub struct MmioDevice { pub struct MmioDevice {
device: Box<dyn VirtioDevice>, device: Arc<Mutex<dyn VirtioDevice>>,
device_activated: bool, device_activated: bool,
features_select: u32, features_select: u32,
acked_features_select: u32, acked_features_select: u32,
queue_select: u32, queue_select: u32,
interrupt_status: Arc<AtomicUsize>, interrupt_status: Arc<AtomicUsize>,
interrupt_cb: Option<Arc<VirtioInterrupt>>, interrupt_cb: Option<Arc<dyn VirtioInterrupt>>,
driver_status: u32, driver_status: u32,
config_generation: u32, config_generation: u32,
queues: Vec<Queue>, queues: Vec<Queue>,
queue_evts: Vec<EventFd>, queue_evts: Vec<EventFd>,
mem: Option<Arc<RwLock<GuestMemoryMmap>>>, mem: Option<Arc<ArcSwap<GuestMemoryMmap>>>,
} }
impl MmioDevice { impl MmioDevice {
/// Constructs a new MMIO transport for the given virtio device. /// Constructs a new MMIO transport for the given virtio device.
pub fn new( pub fn new(
mem: Arc<RwLock<GuestMemoryMmap>>, mem: Arc<ArcSwap<GuestMemoryMmap>>,
device: Box<dyn VirtioDevice>, device: Arc<Mutex<dyn VirtioDevice>>,
) -> Result<MmioDevice> { ) -> Result<MmioDevice> {
let device_clone = device.clone();
let locked_device = device_clone.lock().unwrap();
let mut queue_evts = Vec::new(); let mut queue_evts = Vec::new();
for _ in device.queue_max_sizes().iter() { for _ in locked_device.queue_max_sizes().iter() {
queue_evts.push(EventFd::new(EFD_NONBLOCK)?) queue_evts.push(EventFd::new(EFD_NONBLOCK)?)
} }
let queues = device let queues = locked_device
.queue_max_sizes() .queue_max_sizes()
.iter() .iter()
.map(|&s| Queue::new(s)) .map(|&s| Queue::new(s))
@@ -86,7 +125,7 @@ impl MmioDevice {
/// Gets the list of queue events that must be triggered whenever the VM writes to /// Gets the list of queue events that must be triggered whenever the VM writes to
/// `virtio::NOTIFY_REG_OFFSET` past the MMIO base. Each event must be triggered when the /// `virtio::NOTIFY_REG_OFFSET` past the MMIO base. Each event must be triggered when the
/// value being written equals the index of the event in this list. /// value being written equals the index of the event in this list.
pub fn queue_evts(&self) -> &[EventFd] { fn queue_evts(&self) -> &[EventFd] {
self.queue_evts.as_slice() self.queue_evts.as_slice()
} }
@@ -97,7 +136,7 @@ impl MmioDevice {
fn are_queues_valid(&self) -> bool { fn are_queues_valid(&self) -> bool {
if let Some(mem) = self.mem.as_ref() { if let Some(mem) = self.mem.as_ref() {
self.queues.iter().all(|q| q.is_valid(&mem.read().unwrap())) self.queues.iter().all(|q| q.is_valid(mem.load().as_ref()))
} else { } else {
false false
} }
@@ -122,21 +161,21 @@ impl MmioDevice {
} }
} }
pub fn assign_interrupt(&mut self, interrupt: Box<dyn Interrupt>) { pub fn assign_interrupt(&mut self, interrupt: Arc<Box<dyn InterruptSourceGroup>>) {
let interrupt_status = self.interrupt_status.clone(); self.interrupt_cb = Some(Arc::new(VirtioInterruptIntx::new(
let cb = Arc::new(Box::new( self.interrupt_status.clone(),
move |int_type: &VirtioInterruptType, _queue: Option<&Queue>| { interrupt,
let status = match int_type { )));
VirtioInterruptType::Config => INTERRUPT_STATUS_CONFIG_CHANGED, }
VirtioInterruptType::Queue => INTERRUPT_STATUS_USED_RING, }
};
interrupt_status.fetch_or(status as usize, Ordering::SeqCst);
interrupt.deliver() impl VirtioTransport for MmioDevice {
}, fn ioeventfds(&self, base_addr: u64) -> Vec<(&EventFd, u64)> {
) as VirtioInterrupt); let notify_base = base_addr + u64::from(NOTIFY_REG_OFFSET);
self.queue_evts()
self.interrupt_cb = Some(cb); .iter()
.map(|event| (event, notify_base))
.collect()
} }
} }
@@ -147,11 +186,15 @@ impl BusDevice for MmioDevice {
let v = match offset { let v = match offset {
0x0 => MMIO_MAGIC_VALUE, 0x0 => MMIO_MAGIC_VALUE,
0x04 => MMIO_VERSION, 0x04 => MMIO_VERSION,
0x08 => self.device.device_type(), 0x08 => self.device.lock().unwrap().device_type(),
0x0c => VENDOR_ID, // vendor id 0x0c => VENDOR_ID, // vendor id
0x10 => { 0x10 => {
self.device.features(self.features_select) if self.features_select < 2 {
| if self.features_select == 1 { 0x1 } else { 0x0 } (self.device.lock().unwrap().features() >> (self.features_select * 32))
as u32
} else {
0
}
} }
0x34 => self.with_queue(0, |q| u32::from(q.get_max_size())), 0x34 => self.with_queue(0, |q| u32::from(q.get_max_size())),
0x44 => self.with_queue(0, |q| q.ready as u32), 0x44 => self.with_queue(0, |q| q.ready as u32),
@@ -165,7 +208,11 @@ impl BusDevice for MmioDevice {
}; };
LittleEndian::write_u32(data, v); LittleEndian::write_u32(data, v);
} }
0x100..=0xfff => self.device.read_config(offset - 0x100, data), 0x100..=0xfff => self
.device
.lock()
.unwrap()
.read_config(offset - 0x100, data),
_ => { _ => {
warn!( warn!(
"invalid virtio mmio read: 0x{:x}:0x{:x}", "invalid virtio mmio read: 0x{:x}:0x{:x}",
@@ -191,7 +238,19 @@ impl BusDevice for MmioDevice {
let v = LittleEndian::read_u32(data); let v = LittleEndian::read_u32(data);
match offset { match offset {
0x14 => self.features_select = v, 0x14 => self.features_select = v,
0x20 => self.device.ack_features(self.acked_features_select, v), 0x20 => {
if self.acked_features_select < 2 {
self.device
.lock()
.unwrap()
.ack_features(u64::from(v) << (self.acked_features_select * 32));
} else {
warn!(
"invalid ack_features (page {}, value 0x{:x})",
self.acked_features_select, v
);
}
}
0x24 => self.acked_features_select = v, 0x24 => self.acked_features_select = v,
0x30 => self.queue_select = v, 0x30 => self.queue_select = v,
0x38 => mut_q = self.with_queue_mut(|q| q.size = v as u16), 0x38 => mut_q = self.with_queue_mut(|q| q.size = v as u16),
@@ -213,7 +272,13 @@ impl BusDevice for MmioDevice {
} }
} }
} }
0x100..=0xfff => return self.device.write_config(offset - 0x100, data), 0x100..=0xfff => {
return self
.device
.lock()
.unwrap()
.write_config(offset - 0x100, data)
}
_ => { _ => {
warn!( warn!(
"invalid virtio mmio write: 0x{:x}:0x{:x}", "invalid virtio mmio write: 0x{:x}:0x{:x}",
@@ -233,6 +298,8 @@ impl BusDevice for MmioDevice {
if self.mem.is_some() { if self.mem.is_some() {
let mem = self.mem.as_ref().unwrap().clone(); let mem = self.mem.as_ref().unwrap().clone();
self.device self.device
.lock()
.unwrap()
.activate( .activate(
mem, mem,
interrupt_cb, interrupt_cb,
@@ -246,3 +313,16 @@ impl BusDevice for MmioDevice {
} }
} }
} }
impl Pausable for MmioDevice {
fn pause(&mut self) -> result::Result<(), MigratableError> {
Ok(())
}
fn resume(&mut self) -> result::Result<(), MigratableError> {
Ok(())
}
}
impl Snapshotable for MmioDevice {}
impl Migratable for MmioDevice {}

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