Compare commits

...

255 Commits

Author SHA1 Message Date
Samuel Ortiz
8e8a7b6d07 release-notes: v0.3.0 release
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-10-18 11:55:13 +02:00
dependabot-preview[bot]
2d7bfdd920 build(deps): bump libc from 0.2.64 to 0.2.65
Bumps [libc](https://github.com/rust-lang/libc) from 0.2.64 to 0.2.65.
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.64...0.2.65)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-10-18 08:53:20 +00:00
Sebastien Boeuf
5822969afa docs: Update instructions to create custom ClearLinux image
Based on recent update of the cloudguest image used by Cloud Hypervisor,
we identified some missing or incorrect details in the instructions.
This patch is here to fix those.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-10-18 08:10:06 +02:00
Sebastien Boeuf
defc33927f docs: iommu: Improve VM boot time and performance
This patch extends the existing virtual IOMMU documentation, explaining
how the use of huge pages can drastically improve the VM boot time.

Particularly, how in case of nested VFIO, the impact is significant and
the rationales behind it.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-10-18 07:21:40 +02:00
Sebastien Boeuf
efbafdf9ed vm-virtio: Allow 2MiB mappings
In order to speed up the boot time and reduce the amount of mappings,
this patch exposes the virtio-iommu device as supporting both 2M and 4k
page sizes.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-10-18 07:21:40 +02:00
dependabot-preview[bot]
2b60452195 build(deps): bump backtrace from 0.3.39 to 0.3.40
Bumps [backtrace](https://github.com/rust-lang/backtrace-rs) from 0.3.39 to 0.3.40.
- [Release notes](https://github.com/rust-lang/backtrace-rs/releases)
- [Commits](https://github.com/rust-lang/backtrace-rs/compare/0.3.39...0.3.40)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-10-17 22:53:26 +00:00
dependabot-preview[bot]
e8a25c22f6 build(deps): bump backtrace from 0.3.38 to 0.3.39
Bumps [backtrace](https://github.com/rust-lang/backtrace-rs) from 0.3.38 to 0.3.39.
- [Release notes](https://github.com/rust-lang/backtrace-rs/releases)
- [Commits](https://github.com/rust-lang/backtrace-rs/compare/0.3.38...0.3.39)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-10-17 19:14:34 +00:00
dependabot-preview[bot]
b8be1dc24a build(deps): bump backtrace-sys from 0.1.31 to 0.1.32
Bumps [backtrace-sys](https://github.com/alexcrichton/backtrace-rs) from 0.1.31 to 0.1.32.
- [Release notes](https://github.com/alexcrichton/backtrace-rs/releases)
- [Commits](https://github.com/alexcrichton/backtrace-rs/compare/backtrace-sys-0.1.31...backtrace-sys-0.1.32)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-10-17 18:40:05 +00:00
dependabot-preview[bot]
83631599a3 build(deps): bump cc from 1.0.45 to 1.0.46
Bumps [cc](https://github.com/alexcrichton/cc-rs) from 1.0.45 to 1.0.46.
- [Release notes](https://github.com/alexcrichton/cc-rs/releases)
- [Commits](https://github.com/alexcrichton/cc-rs/compare/1.0.45...1.0.46)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-10-17 06:37:13 +00:00
Jose Carlos Venegas Munoz
78e2f7a99a api: http: handle cpu according to openapi
openapi definition defines an object for cpus not an integer

Signed-off-by: Jose Carlos Venegas Munoz <jose.carlos.venegas.munoz@intel.com>
2019-10-17 07:39:56 +02:00
Jose Carlos Venegas Munoz
205b8c1cd5 api: http: make consistent api and implementation
vsocks: vsocks is implemented as an array

Signed-off-by: Jose Carlos Venegas Munoz <jose.carlos.venegas.munoz@intel.com>
2019-10-17 07:39:56 +02:00
Sebastien Boeuf
3acf9dfcf3 vfio: Don't map guest memory for VFIO devices attached to vIOMMU
In case a VFIO devices is being attached behind a virtual IOMMU, we
should not automatically map the entire guest memory for the specific
device.

A VFIO device attached to the virtual IOMMU will be driven with IOVAs,
hence we should simply wait for the requests coming from the virtual
IOMMU.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-10-16 07:27:06 +02:00
Sebastien Boeuf
63c30a6e79 vmm: Build and set the list of external mappings for VFIO
When VFIO devices are created and if the device is attached to the
virtual IOMMU, the ExternalDmaMapping trait implementation is created
and associated with the device. The idea is to build a hash map of
device IDs with their associated trait implementation.

This hash map is provided to the virtual IOMMU device so that it knows
how to properly trigger external mappings associated with VFIO devices.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-10-16 07:27:06 +02:00
Sebastien Boeuf
c65ead5de8 vm-virtio: Trigger external map/unmap from virtio-iommu
This patch relies on the trait implementation provided for each device
which requires some sort of external update based on a map or unmap.

Whenever a MAP or UNMAP request comes through the virtqueues, it
triggers a call to the external mapping trait with map()/unmap()
functions being invoked.

Those external mappings are meant to be used from VFIO and vhost-user
devices as they need to update their own mappings. In case of VFIO, the
goal is to update the DMAR table in the physical IOMMU, while vhost-user
devices needs to update their internal representation of the virtqueues.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-10-16 07:27:06 +02:00
Sebastien Boeuf
837bcbc6ba vfio: Create VFIO implementation of ExternalDmaMapping
With this implementation of the trait ExternalDmaMapping, we now have
the tool to provide to the virtual IOMMU to trigger the map/unmap on
behalf of the guest.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-10-16 07:27:06 +02:00
Sebastien Boeuf
3598e603d5 vfio: Add a public function to retrive VFIO container
The VFIO container is the object needed to update the VFIO mapping
associated with a VFIO device. This patch allows the device manager
to have access to the VFIO container.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-10-16 07:27:06 +02:00
Sebastien Boeuf
34bb31791b vm-device: Add new crate for virtio and VFIO agnostic traits
The new crate vm-device is created here to host the definitions of
traits not meant to be tied to virtio of VFIO specifically. We need to
add a new trait to update external DMA mappings for devices, which is
why the vm-device crate is the right fit for this.

We can expect this crate to be extended later once the design gets
approved from a rust-vmm perspective.

In this specific use case, we can have some devices like VFIO or
vhost-user ones requiring to be notified about mapping updates. This
new trait ExternalDmaMapping will allow such devices to implement their
own way to handle such event.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-10-16 07:27:06 +02:00
Sebastien Boeuf
9085a39c7d vmm: Attach VFIO devices to IORT table
This patch attaches VFIO devices to the virtual IOMMU if they are
identified as they should be, based on the option "iommu=on". This
simply takes care of adding the PCI device ID to the ACPI IORT table.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-10-16 07:27:06 +02:00
Sebastien Boeuf
5fc3f37c9b vmm: Add iommu=on|off option for --device
Having the virtual IOMMU created with --iommu is one thing, but we also
need a way to decide if a VFIO device should be attached to the virtual
IOMMU or not. That's why we introduce an extra option "iommu" with the
value "on" or "off". By default, the device is not attached, which means
"iommu=off".

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-10-16 07:27:06 +02:00
dependabot-preview[bot]
3bb51d4d5e build(deps): bump libc from 0.2.62 to 0.2.64
Bumps [libc](https://github.com/rust-lang/libc) from 0.2.62 to 0.2.64.
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.62...0.2.64)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-10-15 19:46:54 +00:00
Rob Bradford
cc72ed1cc9 vhost_user_net: Propagate errors correctly
Clean up the error handling and ensure that where possible errors are
propagated. Make use of std::convert::From in order to translate error
types.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-10-15 09:27:01 -07:00
Rob Bradford
8663b429b3 vhost_user_net: Remove unnecessary checks for unconfigured memory
Simplify the check for the unusual situation where the memory is not
configured by using .ok_or() on the option to convert it to a result.
This cleans up a bunch of extra indentation.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-10-15 09:27:01 -07:00
Rob Bradford
df336ade57 vhost_user_net: Remove debugging println! messages
Remove messages that are left over from the development of the project
that represent normal operation for the backend. This cleans up the
console output and improves performance.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-10-15 09:27:01 -07:00
dependabot-preview[bot]
9e78c2e686 build(deps): bump ryu from 1.0.1 to 1.0.2
Bumps [ryu](https://github.com/dtolnay/ryu) from 1.0.1 to 1.0.2.
- [Release notes](https://github.com/dtolnay/ryu/releases)
- [Commits](https://github.com/dtolnay/ryu/compare/1.0.1...1.0.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-10-15 05:16:47 +00:00
Jose Carlos Venegas Munoz
786e33931f api: http: Fix openpi schema.
Fix invalid type for version:

- VmInfo.version.type string

Change Null value from enum as it has problems to build clients with
openapi tools.

- ConsoleConfig.mode.enum Null -> Nil

Signed-off-by: Jose Carlos Venegas Munoz <jose.carlos.venegas.munoz@intel.com>
2019-10-15 07:16:24 +02:00
dependabot-preview[bot]
90d1083bda build(deps): bump syn from 1.0.3 to 1.0.5
Bumps [syn](https://github.com/dtolnay/syn) from 1.0.3 to 1.0.5.
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/1.0.3...1.0.5)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-10-14 11:25:01 +02:00
dependabot-preview[bot]
5ca068a068 build(deps): bump proc-macro2 from 1.0.1 to 1.0.5
Bumps [proc-macro2](https://github.com/alexcrichton/proc-macro2) from 1.0.1 to 1.0.5.
- [Release notes](https://github.com/alexcrichton/proc-macro2/releases)
- [Commits](https://github.com/alexcrichton/proc-macro2/compare/1.0.1...1.0.5)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-10-14 09:23:47 +00:00
Samuel Ortiz
2a0ba7aef8 vmm: vm: Add state validation unit test
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-10-14 06:35:36 +02:00
Samuel Ortiz
097b30669f vmm: vm: Verify that state transitions are valid
We should return an explicit error when the transition from on VM state
to another is invalid.
The valid_transition() routine for the VmState enum essentially
describes the VM state machine.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-10-14 06:35:36 +02:00
dependabot-preview[bot]
ef090cf37d build(deps): bump ryu from 1.0.0 to 1.0.1
Bumps [ryu](https://github.com/dtolnay/ryu) from 1.0.0 to 1.0.1.
- [Release notes](https://github.com/dtolnay/ryu/releases)
- [Commits](https://github.com/dtolnay/ryu/compare/1.0.0...1.0.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-10-12 09:33:07 +01:00
dependabot-preview[bot]
db3ece8ef2 build(deps): bump failure from 0.1.5 to 0.1.6
Bumps [failure](https://github.com/rust-lang-nursery/failure) from 0.1.5 to 0.1.6.
- [Release notes](https://github.com/rust-lang-nursery/failure/releases)
- [Changelog](https://github.com/rust-lang-nursery/failure/blob/master/RELEASES.md)
- [Commits](https://github.com/rust-lang-nursery/failure/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-10-12 08:32:38 +00:00
Samuel Ortiz
af41d6fc88 main: Add VM pause/resume test
We pause a VM from the API, then SSH'ing into it should fail.
After resuming, SSH'ing should work again.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-10-10 17:13:44 -07:00
Samuel Ortiz
d2d3abb13c vmm: Rename Booted vm state to Running
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-10-10 17:13:44 -07:00
Samuel Ortiz
dbbd04a4cf vmm: Implement VM resume
To resume a VM, we unpark all its vCPU threads.

Fixes: #333

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-10-10 17:13:44 -07:00
Samuel Ortiz
4ac0cb9cff vmm: Implement VM pause
In order to pause a VM, we signal all the vCPU threads to get them out
of vmx non-root. Once out, the vCPU thread will check for a an atomic
pause boolean. If it's set to true, then the thread will park until
being resumed.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-10-10 17:13:44 -07:00
Sebastien Boeuf
80c3fd922a ci: Allow enough time for L2 VM to boot
Because the L2 VM running in the VFIO integration test is actually
running as L3 (since the CI runs in a VM), it can take quite some
time for this VM to boot.

The way to solve this issue is to extend the sleep time before to try
communicating with the L2 VM, but also to speed up the boot time by
using virtio-console instead of serial. We suspect the use of serial,
implying PIO VM exits for each character on the serial port is quite
expensive compared to the paravirtualized console.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-10-11 01:40:11 +02:00
Sebastien Boeuf
37a7000fdd ci: Make sure VFIO test don't conflict with Azure private IP
Azure virtual machines can have private IPs in the 172.16.x.x range,
causing some issues with the VFIO test. By using 172.17.x.x for this
test, we avoid IP conflicts.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-10-10 11:29:17 -07:00
Sebastien Boeuf
6e9e24ec0f ci: Extend virtio-iommu integration test
Now that our custom kernel includes all the patches for the full support
of virtio-iommu, we can go one step further by attaching the virtio-net
device to the virtual IOMMU and use it to SSH some commands validating
both disks and the network card are isolated into their own IOMMU group.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-10-10 16:07:50 +02:00
Sebastien Boeuf
1fc8ee945a ci: Remove QEMU dependency for nested VFIO test
Now that cloud-hypervisor can expose a virtual IOMMU to its guest VM,
the integration test validating the VFIO support with virtio-net can be
updated to use cloud-hypervisor exclusively.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-10-10 16:07:50 +02:00
Sebastien Boeuf
cb59f826ad scripts: Use virtio-fs-virtio-iommu branch as custom kernel
Because we want both early support for virtio-fs and virtio-iommu, our
custom kernel is now based on the kernel branch virtio-fs-virtio-iommu.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-10-10 16:07:50 +02:00
Sebastien Boeuf
688ec0eb2d resources: Update kernel config
In order to support nested virtualization and nested device passthrough
from our CI tests, we need some extra kernel configuration options to be
enabled.

CONFIG_KVM and CONFIG_VIRTUALIZATION for nested virtualization.
CONFIG_VFIO for nested device passthrough.
CONFIG_VIRTIO_IOMMU and CONFIG_ACPI_IORT for virtio-iommu support.

With all these new options applied, we can leverage virtio-iommu to
attach some VFIO devices to it and pass them through a second layer of
virtualization.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-10-10 16:07:50 +02:00
Samuel Ortiz
8e018d6feb vfio: Move vfio-bindings to crates.io
And remove our local vfio-bindings code.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-10-10 10:32:40 +02:00
Samuel Ortiz
c446b9d510 Cargo: Move virtio-bindings to crates.io
v0.1.0 was just published.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-10-10 10:32:40 +02:00
dependabot-preview[bot]
6df7cd0e4b build(deps): bump openssl-sys from 0.9.50 to 0.9.51
Bumps [openssl-sys](https://github.com/sfackler/rust-openssl) from 0.9.50 to 0.9.51.
- [Release notes](https://github.com/sfackler/rust-openssl/releases)
- [Commits](https://github.com/sfackler/rust-openssl/compare/openssl-sys-v0.9.50...openssl-sys-v0.9.51)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-10-10 07:44:14 +00:00
dependabot-preview[bot]
3a04db5936 build(deps): bump arrayvec from 0.4.11 to 0.4.12
Bumps [arrayvec](https://github.com/bluss/arrayvec) from 0.4.11 to 0.4.12.
- [Release notes](https://github.com/bluss/arrayvec/releases)
- [Commits](https://github.com/bluss/arrayvec/compare/0.4.11...0.4.12)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-10-10 01:04:35 +00:00
dependabot-preview[bot]
ad7d02cd74 build(deps): bump nodrop from 0.1.13 to 0.1.14
Bumps [nodrop](https://github.com/bluss/arrayvec) from 0.1.13 to 0.1.14.
- [Release notes](https://github.com/bluss/arrayvec/releases)
- [Commits](https://github.com/bluss/arrayvec/compare/nodrop-0.1.13...nodrop-0.1.14)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-10-09 15:12:04 +00:00
Samuel Ortiz
1298b508bf vmm: Manage the exit and reset behaviours from the control loop
So that we don't need to forward an ExitBehaviour up to the VMM thread.
This simplifies the control loop and the VMM thread even further.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-10-08 18:03:27 -07:00
Samuel Ortiz
a95fa1c4e8 vmm: api: Add a VMM shutdown command
This shuts the current VM down, if any, and then exits the VMM process.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-10-08 18:03:27 -07:00
Samuel Ortiz
228adebc32 vmm: Unreference the VM when shutting down
This way, we are forced to re-create the VM object when moving from
shutdown to boot.

Fixes: #321

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-10-08 17:24:05 +02:00
Samuel Ortiz
14eb071b29 Cargo: Move to crates.io vmm-sys-util
Use the newly published 0.1.1 version.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-10-08 07:28:53 -07:00
Sebastien Boeuf
5652cc7a05 README: Remove wrong statement about the firmware
The firmware has been recently updated to find the EFI partition, no
matter if the disk is not the first one of the list.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-10-07 10:12:07 +02:00
Sebastien Boeuf
2c50c963f5 docs: Explain how to use the virtual IOMMU
This patch introduces a specific documentation for the virtual IOMMU
device. This is important to understand what the use cases are for this
new device and how to properly use it with virtio devices.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-10-07 10:12:07 +02:00
Sebastien Boeuf
46848fdc43 ci: Add integration test for virtio-iommu
Simply test the guest can find the PCI device corresponding to the
virtio-iommu device.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-10-07 10:12:07 +02:00
Sebastien Boeuf
b918220b49 vmm: Support virtio-pci devices attached to a virtual IOMMU
This commit is the glue between the virtio-pci devices attached to the
vIOMMU, and the IORT ACPI table exposing them to the guest as sitting
behind this vIOMMU.

An important thing is the trait implementation provided to the virtio
vrings for each device attached to the vIOMMU, as they need to perform
proper address translation before they can access the buffers.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-10-07 10:12:07 +02:00
Sebastien Boeuf
278ab05cbc vmm: Add iommu=on|off option for --vsock
Having the virtual IOMMU created with --iommu is one thing, but we also
need a way to decide if a virtio-vsock device should be attached to this
virtual IOMMU or not. That's why we introduce an extra option "iommu"
with the value "on" or "off". By default, the device is not attached,
which means "iommu=off".

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-10-07 10:12:07 +02:00
Sebastien Boeuf
32d07e40cc vmm: Add iommu=on|off option for --console
Having the virtual IOMMU created with --iommu is one thing, but we also
need a way to decide if a virtio-console device should be attached to
this virtual IOMMU or not. That's why we introduce an extra option
"iommu" with the value "on" or "off". By default, the device is not
attached, which means "iommu=off".

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-10-07 10:12:07 +02:00
Sebastien Boeuf
63869bde75 vmm: Add iommu=on|off option for --pmem
Having the virtual IOMMU created with --iommu is one thing, but we also
need a way to decide if a virtio-pmem device should be attached to this
virtual IOMMU or not. That's why we introduce an extra option "iommu"
with the value "on" or "off". By default, the device is not attached,
which means "iommu=off".

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-10-07 10:12:07 +02:00
Sebastien Boeuf
fb4769388b vmm: Add iommu=on|off option for --rng
Having the virtual IOMMU created with --iommu is one thing, but we also
need a way to decide if a virtio-rng device should be attached to this
virtual IOMMU or not. That's why we introduce an extra option "iommu"
with the value "on" or "off". By default, the device is not attached,
which means "iommu=off".

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-10-07 10:12:07 +02:00
Sebastien Boeuf
20c4ed829a vmm: Add iommu=on|off option for --net
Having the virtual IOMMU created with --iommu is one thing, but we also
need a way to decide if a virtio-net device should be attached to this
virtual IOMMU or not. That's why we introduce an extra option "iommu"
with the value "on" or "off". By default, the device is not attached,
which means "iommu=off".

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-10-07 10:12:07 +02:00
Sebastien Boeuf
4b8d7e718d vmm: Add iommu=on|off option for --disk
Having the virtual IOMMU created with --iommu is one thing, but we also
need a way to decide if a virtio-blk device should be attached to this
virtual IOMMU or not. That's why we introduce an extra option "iommu"
with the value "on" or "off". By default, the device is not attached,
which means "iommu=off".

One side effect of this new option is that we had to introduce a new
option for the disk path, simply called "path=".

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-10-07 10:12:07 +02:00
Sebastien Boeuf
6e0aa56f06 vmm: Add iommu field to the VmConfig
Adding a simple iommu boolean field to the VmConfig structure so that we
can later use it to create a virtio-iommu device for the current VM.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-10-07 10:12:07 +02:00
Sebastien Boeuf
03352f45f9 arch: Create ACPI IORT table
The virtual IOMMU exposed through virtio-iommu device has a dependency
on ACPI. It needs to expose the device ID of the virtio-iommu device,
and all the other devices attached to this virtual IOMMU. The IDs are
expressed from a PCI bus perspective, based on segment, bus, device and
function.

The guest relies on the topology description provided by the IORT table
to attach devices to the virtio-iommu device.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-10-07 10:12:07 +02:00
Sebastien Boeuf
f40adff2a1 vm-virtio: Add virtio-iommu support
This patch introduces the first implementation of the virtio-iommu
device. This device emulates an IOMMU for the guest, which allows
special use cases like nesting passed through devices, or even using
IOVAs from the guest.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-10-07 10:12:07 +02:00
Sebastien Boeuf
0acb1e329d vm-virtio: Translate addresses for devices attached to IOMMU
In case some virtio devices are attached to the virtual IOMMU, their
vring addresses need to be translated from IOVA into GPA. Otherwise it
makes no sense to try to access them, and they would cause out of range
errors.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-10-07 10:12:07 +02:00
Sebastien Boeuf
6566c739e1 vm-virtio: Add IOMMU support to virtio-vsock
Adding virtio feature VIRTIO_F_IOMMU_PLATFORM when explicitly asked by
the user. The need for this feature is to be able to attach the virtio
device to a virtual IOMMU.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-10-07 10:12:07 +02:00
Sebastien Boeuf
9ab00dcb75 vm-virtio: Add IOMMU support to virtio-rng
Adding virtio feature VIRTIO_F_IOMMU_PLATFORM when explicitly asked by
the user. The need for this feature is to be able to attach the virtio
device to a virtual IOMMU.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-10-07 10:12:07 +02:00
Sebastien Boeuf
ee1899c6f6 vm-virtio: Add IOMMU support to virtio-pmem
Adding virtio feature VIRTIO_F_IOMMU_PLATFORM when explicitly asked by
the user. The need for this feature is to be able to attach the virtio
device to a virtual IOMMU.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-10-07 10:12:07 +02:00
Sebastien Boeuf
392f1ec155 vm-virtio: Add IOMMU support to virtio-console
Adding virtio feature VIRTIO_F_IOMMU_PLATFORM when explicitly asked by
the user. The need for this feature is to be able to attach the virtio
device to a virtual IOMMU.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-10-07 10:12:07 +02:00
Sebastien Boeuf
9fad680db1 vm-virtio: Add IOMMU support to virtio-net
Adding virtio feature VIRTIO_F_IOMMU_PLATFORM when explicitly asked by
the user. The need for this feature is to be able to attach the virtio
device to a virtual IOMMU.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-10-07 10:12:07 +02:00
Sebastien Boeuf
9ebb1a55bc vm-virtio: Add IOMMU support to virtio-blk
Adding virtio feature VIRTIO_F_IOMMU_PLATFORM when explicitly asked by
the user. The need for this feature is to be able to attach the virtio
device to a virtual IOMMU.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-10-07 10:12:07 +02:00
Sebastien Boeuf
85e1865cb5 vm-virtio: Implement reset() for vhost-user-fs
The virtio specification defines a device can be reset, which was not
supported by this vhost-user-fs implementation. The reason it is needed
is to support unbinding this device from the guest driver, and rebind it
to vfio-pci driver.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-10-07 10:12:07 +02:00
Sebastien Boeuf
4b1328a29c vm-virtio: Implement reset() for vhost-user-net
The virtio specification defines a device can be reset, which was not
supported by this vhost-user-net implementation. The reason it is needed
is to support unbinding this device from the guest driver, and rebind it
to vfio-pci driver.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-10-07 10:12:07 +02:00
Sebastien Boeuf
8225d4cd6e vm-virtio: Implement reset() for virtio-console
The virtio specification defines a device can be reset, which was not
supported by this virtio-console implementation. The reason it is needed
is to support unbinding this device from the guest driver, and rebind it
to vfio-pci driver.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-10-07 10:12:07 +02:00
Sebastien Boeuf
dac7737919 vm-virtio: Implement reset() for virtio-vsock
The virtio specification defines a device can be reset, which was not
supported by this virtio-vsock implementation. The reason it is needed
is to support unbinding this device from the guest driver, and rebind
it to vfio-pci driver.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-10-07 10:12:07 +02:00
Sebastien Boeuf
3e750de43f vm-virtio: Implement reset() for virtio-pmem
The virtio specification defines a device can be reset, which was not
supported by this virtio-pmem implementation. The reason it is needed
is to support unbinding this device from the guest driver, and rebind
it to vfio-pci driver.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-10-07 10:12:07 +02:00
Sebastien Boeuf
eb91bc812b vm-virtio: Implement reset() for virtio-rng
The virtio specification defines a device can be reset, which was not
supported by this virtio-rng implementation. The reason it is needed
is to support unbinding this device from the guest driver, and rebind
it to vfio-pci driver.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-10-07 10:12:07 +02:00
Sebastien Boeuf
59b4aaba87 vm-virtio: Implement reset() for virtio-net
The virtio specification defines a device can be reset, which was not
supported by this virtio-net implementation. The reason it is needed is
to support unbinding this device from the guest driver, and rebind it to
vfio-pci driver.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-10-07 10:12:07 +02:00
Samuel Ortiz
8288cb2ac8 micro_http: Use Firecracker version
As of commit 2b94334a, Firecracker includes all the changes we need.
We can now switch to using it instead of carrying a copy.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-10-04 06:52:34 -07:00
Samuel Ortiz
2a466132a0 vmm: api: Set the HTTP response header Server field
To "Cloud Hypervisor API" and not "Firecracker API".

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-10-04 09:36:33 +02:00
Samuel Ortiz
fc5c210498 micro_http: Set the response headers Server value
And implement the Default trait for ResponseHeaders, falling back to
Firecracker API.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-10-04 09:36:33 +02:00
Samuel Ortiz
8dbb16df4d main: Add a simple HTTP API integration test
For now we test that we can create and boot a guest from the HTTP API.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-10-04 09:36:33 +02:00
Samuel Ortiz
7abbad0a62 vmm: Be more idiomatic when calling into the VMM API
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-10-04 09:36:33 +02:00
Samuel Ortiz
7328ecdb3b vmm: Implement the /api/v1/vm.delete endpoint
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-10-04 09:36:33 +02:00
Samuel Ortiz
f9daf2e247 vmm: Factorize the vm boot and shutdown code
So that the API handling state machine is cleaner and easier to read.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-10-04 09:36:33 +02:00
Samuel Ortiz
43b3642955 vmm: Clean Error handling up
We used to have errors definitions spread across vmm, vm, api,
and http.

We now have a cleaner separation: All API routines only return an
ApiResult. All VM operations, including the VMM wrappers, return a
VmResult. This makes it easier to carry errors up to the HTTP caller.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-10-04 09:36:33 +02:00
Samuel Ortiz
42758244a0 vmm: Implement the /api/v1/vm.info endpoint
This, for now, returns the VM config and its state.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-10-04 09:36:33 +02:00
Samuel Ortiz
27af983ec9 vmm: Track the VM state
We will expose it through the api/v1/vm.info endpoint.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-10-04 09:36:33 +02:00
Samuel Ortiz
b70344158b vmm: Handle the missing VM error
When trying to boot or shut a VM down, return an error if the VM was not
previously created.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-10-04 09:36:33 +02:00
Samuel Ortiz
7e0cb078ed vmm: Only build a new VM when booting it
In order to support further use cases where a VM configuration could be
modified through the HTTP API, we only store the passed VM config when
being asked to create a VM. The actual creation will happen when booting
a new config for the first time.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-10-04 09:36:33 +02:00
Samuel Ortiz
9a93f4f0a6 micro_http: Fix clippy warning
Use a more idiomatic "let Ok(foo) = result" construct for:

105 |           if try_numeric.is_ok() {
    |              ------------------- the check is happening here
106 |              self.content_length = try_numeric.unwrap();
    |                                   ^^^^^^^^^^^^^^^^^^^^

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-10-04 09:36:33 +02:00
Samuel Ortiz
c505cfae2b vmm: Implement the VM HTTP endpoint handlers
Implement the vm.create, vm.boot, vm.shutdown and vm.reboot HTTP endpoint
handlers.

Fixes: #244

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-10-04 09:36:33 +02:00
Samuel Ortiz
8a5e47f989 vmm: Implement the shutdown and reboot API
We factorize some of the code for both the API helpers and the VMM
thread.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-10-04 09:36:33 +02:00
Samuel Ortiz
46cde1a38e vmm: Rename the VM start and stop operations to boot and shutdown
To match the OpenAPI description. And also to map the real life
terminology.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-10-04 09:36:33 +02:00
Samuel Ortiz
ce0b475ef7 vmm: Move the VM creation and startup helpers to the api module
They're API wrappers, not VMM ones.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-10-04 09:36:33 +02:00
Samuel Ortiz
f674019ea1 vmm: {De}serialize VmConfig
We use the serde crate to serialize and deserialize the VmVConfig
structure. This structure will be passed from the HTTP API caller as a
JSON payload and we need to deserialize it into a VmConfig.

For a convenient use of the HTTP API, we also provide Default traits
implementations for some of the VmConfig fields (vCPUs, memory, etc...).

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-10-04 09:36:33 +02:00
Samuel Ortiz
f2de4d0315 vmm: config: Make the cmdline config serializable
The linux_loader crate Cmdline struct is not serializable.
Instead of forcing the upstream create to carry a serde dependency, we
simply use a String for the passed command line and build the actual
CmdLine when we need it (in vm::new()).
Also, the cmdline offset is not a configuration knob, so we remove it.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-10-04 09:36:33 +02:00
Samuel Ortiz
6a722e5c0b vmm: config: Make VhostUser configs serializable
They point to a vm_virtio structure (VhostUserConfig) and in order to
make the whole config serializable (through the serde crate for
example), we'd have to add a serde dependency to the vm_virtio crate.

Instead we use a local, serializable structure and convert it to
VhostUserConfig from the DeviceManager code.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-10-04 09:36:33 +02:00
Samuel Ortiz
aa31748781 vmm: Start the HTTP server thread
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-10-04 09:36:33 +02:00
Samuel Ortiz
b14fd37db9 vmm: Make --kernel optional
The kernel path was the only mandatory command line option.
With the addition of the --api-socket option, we can run without a
kernel path and get it later through the API.

Since we can end up with VM configurations that are no longer valid by
default, we need to provide a validation check for it. For now, if the
kernel path is not defined, the VM configuration is invalid.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-10-04 09:36:33 +02:00
Samuel Ortiz
f27aa21e3f main: Add API socket option
The API server will unconditionally run through a UNIX domain socket
which default path is /run/user/<uid>/cloud-hypervisor.<pid>.

The --api-socket command line option allows to override that default
value with some custom socket path.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-10-04 09:36:33 +02:00
Samuel Ortiz
fe5561df50 main: Group cli options logically
With the API server socket option, we will be able to support a model
where the user can start cloud-hypervisor with no options or an
alternative API server socket path. In this case, we don't want to try
to start a new guest VM, and for that we need to know if the user has
set any VM configuration at all. Grouping all VM configuration specific
options together is one way to be able to know about it.

If the user has not set any VM configuration, we only start the API
server. If it has set anything, we will verify that the overall
configuration is valid and will implicitly convert that configuration
into a request to the API server.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-10-04 09:36:33 +02:00
Samuel Ortiz
2371325f9c vmm: api: Add HTTP server
The Cloud Hyper HTTP server runs a synchronous, multi-threaded
loop that receives HTTP requests and tries to call the corresponding
endpoint handlers for the requests URIs.

An endpoint handler will parse the HTTP request and potentially
translate it into and IPC request. The handler holds an notifier and an
mspc Sender for respectively notifying and sending the IPC payload to
the VMM API server. The handler then waits for an API server response
and translate it back into an HTTP response.
The HTTP server is responsible for sending the reponse back to the
caller.

The HTTP server uses a static routes hash table that maps URIs to
endpoint handlers.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-10-04 09:36:33 +02:00
Samuel Ortiz
e50f4418a2 micro_http: Import Firecracker HTTP 1.x implementation
Based on Firecracker commit 58edf03b.

We're going to use the micro_http crate to serve the cloud-hypervisor
HTTP API.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-10-04 09:36:33 +02:00
Samuel Ortiz
8916dad2da vmm: api: Add cloud-hypervisor OpenAPI documentation
The cloud-hypervisor API uses HTTP as a transport and is accessible
through a local UNIX socket.

The API root path is /api/v1 and is a collection of RPC-style methods.
All methods are static, unlike typical REST APIs. Variable (e.g. device
IDs) are passed through the request body.

Fixes: #244

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-10-04 09:36:33 +02:00
dependabot-preview[bot]
1b66a2fa60 build(deps): bump serde_json from 1.0.40 to 1.0.41
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.40 to 1.0.41.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.40...v1.0.41)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-10-03 22:06:24 +00:00
Rob Bradford
8ea4145f98 devices, vmm: Add legacy CMOS device
Based off of crosvm revision b5237bbcf074eb30cf368a138c0835081e747d71
add a CMOS device. This environments that can't use KVM clock to get the
current time (e.g. Windows and EFI.)

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-10-03 14:57:49 +01:00
dependabot-preview[bot]
47367eb61b build(deps): bump openssl-sys from 0.9.49 to 0.9.50
Bumps [openssl-sys](https://github.com/sfackler/rust-openssl) from 0.9.49 to 0.9.50.
- [Release notes](https://github.com/sfackler/rust-openssl/releases)
- [Commits](https://github.com/sfackler/rust-openssl/compare/openssl-sys-v0.9.49...openssl-sys-v0.9.50)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-10-03 09:06:31 +00:00
Rob Bradford
df3e5c874f tests: Add support for integration testing Ubuntu "eoan"
Refactor the Ubuntu testing infrastructure to support testing different
versions.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-10-02 13:59:48 +01:00
Samuel Ortiz
8ec6cda0c5 ci: Do not look for vubridge to decide if qemu must be built
We no longer build vubridge, so we end up cloning qemu and building
virtiofs and the block backend all the time.

Fixes: #312

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-10-02 09:39:55 +02:00
Cathy Zhang
8c33eb3069 src: Add integration test for vhost-user-net backend
An integration test relying on the new vhost-user-net backend now
replaces the previous test using the QEMU test backend. This allows
us to avoid building the QEMU backend, and we now really exercise the
vhost-user-net implementation as it is used for the ssh communication
in this test.

Signed-off-by: Cathy Zhang <cathy.zhang@intel.com>
Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-09-30 13:06:00 -07:00
Cathy Zhang
f6d1a9d9b8 src: Add vhost-user-net backend
Create vhost-user-net backend with Tap interface, to offload network
transaction from cloud-hypervisor. The goal is to provide flexibility
about the backend being in use, but also more security as it will allow
users to isolate the backend with different security profiles since it
will run as a dedicated process on the host.

Signed-off-by: Cathy Zhang <cathy.zhang@intel.com>
Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-09-30 13:06:00 -07:00
Cathy Zhang
d724511a91 vm-virtio: Add set_protocol_features in vhost-user-net
While implement vhost-user-net backend with Tap interface, it keeps
failed to enable the tx vring, since there is a checking in
slave_req_handler.rs to require acked_protocol_features to be setup
as a pre-requirement, which is filled by set_protocol_features call.
Add this call in vhost-user-net device implementation to address the issue.

Signed-off-by: Cathy Zhang <cathy.zhang@intel.com>
2019-09-30 13:06:00 -07:00
Sebastien Boeuf
9ff42060e0 vhost_user_backend: Fix clippy issues
Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-09-30 13:06:00 -07:00
Cathy Zhang
db151819f1 Cargo.toml: Add workspace config changes
Remove workspace from vhost_user_backend/Cargo.toml to have
vhost-user-backend compiled in cloud-hypervisor. Add workspace in
Cargo.toml to have vhost-user-backend consumed by vhost-user-net.

Signed-off-by: Cathy Zhang <cathy.zhang@intel.com>
2019-09-30 13:06:00 -07:00
Rob Bradford
9356af80c6 arch: Mark the PCI MMCONFIG region as reserved in the E820 tables
The PCI Express Firmware specification says that the region may
be included in the E820 tables (but it must always be in the ACPI
tables.)

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-09-30 18:00:31 +01:00
Rob Bradford
6a4a931b9e arch: acpi: Reserve the PCI MMCONFIG region
The PCI Express Firmware spec says that the region to be used for PCI
MMCONFIG should be reserved as part of the motherboard's resources in
the ACPI tables.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-09-30 18:00:31 +01:00
Rob Bradford
038f198129 arch: acpi: Fix off-by-one error in size of PCI device region
When comparing offsets it is necessry to increment by one to give the
appropriate size.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-09-30 18:00:31 +01:00
Rob Bradford
833a3d456c pci, vmm: Expose the PCI bus for configuration via MMIO
Refactor the PCI datastructures to move the device ownership to a PciBus
struct. This PciBus struct can then be used by both a PciConfigIo and
PciConfigMmio in order to expose the configuration space via both IO
port and also via MMIO for PCI MMCONFIG.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-09-30 18:00:31 +01:00
Rob Bradford
c0ca3b6b8e arch: acpi, layout: Correctly calculate and expose PCI MMCONFIG area
The PCI MMCONFIG area must be below 4GiB and must not be part of the
device space. Shrink the device area and put the PCI MMCONFIG region
above it.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-09-30 18:00:31 +01:00
Rob Bradford
a9eb352aea arch: acpi: Patch the 32-bit PCI device area in the APCI table
Patch the table with the currently used constants. This will be relevant
when we want to adjust the size of the PCI device area to accomodate the
PCI MMCONFIG region.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-09-30 18:00:31 +01:00
Sebastien Boeuf
09392f0530 vhost_user_backend: Fix remaining issues
This commit fixes all the remaining issues that were found as part of
the integration with vhost-user-net.

It fixes the way to notify that a vring is used, by using the proper
EventFd.

It removes the process_queue() function from the trait, since the
complexity it was introducing was leading to deadlocks with mutexes.

It moves the register/unregister functions for registering custom events
from the backend, from the VringEpollHandler to the VringWorker. This
allows for a lot of simplification and solve a deadlock issue.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-09-30 09:26:11 -07:00
Cathy Zhang
527dd68ce1 vhost_user_backend: Update vmm_va_to_gpa with adding offset
The original logic does not has any problem without offset, since the
current offset is zero. However, if offset is not zero, while convert
vmm address to backend process address, it needs to consider the
offset.

Signed-off-by: Cathy Zhang <cathy.zhang@intel.com>
2019-09-30 09:26:11 -07:00
Cathy Zhang
4a1af7f63c vhost-user-backend: Correct error handling in run
The error handling here to trigger break epoll seems not correct,
epoll will be ended once one event is handled, no matter successfully
or failed. Fix it.

Signed-off-by: Cathy Zhang <cathy.zhang@intel.com>
2019-09-30 09:26:11 -07:00
Cathy Zhang
c4309515c9 vhost-user-backend: Remove one checking from set_features
The vhost-user protocol does not indicate set_features could not
be issued more than once, the checking is not needed at all, and
prevent communication between master and slave. Remove it to
fix the issue.

Signed-off-by: Cathy Zhang <cathy.zhang@intel.com>
2019-09-30 09:26:11 -07:00
Sebastien Boeuf
347611b0c7 vhost_user_backend: Pass a backend that can be modified
This patch modifies the library so that a consumer can update the
backend after it's been passed to the daemon.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-09-30 09:26:11 -07:00
Sebastien Boeuf
f14ab872ec vhost_user_backend: Give access to the EpollVringHandler
By letting the consumer of this crate getting access to the vring
handler, we will be able to let it perform several actions without
producing a deadlock.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-09-30 09:26:11 -07:00
Sebastien Boeuf
cfc8c39446 vhost_user_backend: Provide some default trait implementations
We cannot expect every backend to support GET_CONFIG and SET_CONFIG
commands. That's why this patch adds some default implementations for
the trait VhostUserBackend regarding both get_config() and set_config()
functions.

Signed-off-by: Cathy Zhang <cathy.zhang@intel.com>
Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-09-30 09:26:11 -07:00
Sebastien Boeuf
1aab372a06 vhost_user_backend: Make the backend a server
The code needs to initialize a listener to accept connection from the
VMM being the client in this case.

Signed-off-by: Cathy Zhang <cathy.zhang@intel.com>
Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-09-30 09:26:11 -07:00
Sebastien Boeuf
c1b26b1fab vhost_user_backend: Don't process disabled queues
Every time an event is triggered, it needs to be read, but only based on
the status of the vring (enabled or not) will decide if the queue needs
to be processed.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-09-30 09:26:11 -07:00
Sebastien Boeuf
d80ac43ef1 vhost_user_backend: Remove useless started field
The Queue structure already contains a field "ready" that can be used to
track the status of the vrings.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-09-30 09:26:11 -07:00
Sebastien Boeuf
5f076923d3 vhost_user_backend: Allow for proper error propagation
Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-09-30 09:26:11 -07:00
Sebastien Boeuf
36de390caf vhost_user_backend: Make some trait functions as mutable
Let's be realistic, the trait VhostUserBackend will need to have mutable
self for some functions like handle_event, process_queue and set_config,
which is the reason why this commit needs to introduce a RwLock on the
backend instance that was passed around as a simple Arc.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-09-30 09:26:11 -07:00
Sebastien Boeuf
d4f7f73bc8 vhost_user_backend: Move to a per-queue RwLock
Instead of locking every queues whenever something needs to be updated,
this patch modifies the code design to lock each Vring independently.
This allows for much finer granularity, and will allow multiple queues
to be handled at the same time.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-09-30 09:26:11 -07:00
Sebastien Boeuf
4ed81894aa vhost_user_backend: Replace Mutex with RwLock when possible
Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-09-30 09:26:11 -07:00
Sebastien Boeuf
2e2cad91ae vhost_user_backend: Add new crate
The purpose of this new crate is to provide a common library to all
vhost-user backend implementations. The more is handled by this library,
the less duplication will need to happen in each vhost-user daemon.

This crate relies a lot on vhost_rs, vm-memory and vm-virtio crates.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-09-30 09:26:11 -07:00
Rob Bradford
b5ee9212c1 vmm, devices: Use APIC address constant
In order to avoid introducing a dependency on arch in the devices crate
pass the constant in to the IOAPIC device creation.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-09-27 11:48:30 -07:00
Rob Bradford
162791b571 vmm, arch: Use IOAPIC constants from layout in DeviceManager
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-09-27 11:48:30 -07:00
Rob Bradford
8207b2e97d arch: Move addresses for GDT and IDT tables to layout module
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-09-27 11:48:30 -07:00
Rob Bradford
a0455167d0 vmm: Use layout constant for kernel command line
Remove the unnecessary field on CmdlineConfig and switch to using the
common offset.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-09-27 11:48:30 -07:00
Rob Bradford
1bc47507b7 arch: Move initial page table addresses to layout module
These are part of RAM and are used as the initial page table entries for
booting the OS and firmware (identity mapping.)

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-09-27 11:48:30 -07:00
Rob Bradford
5ba61f6d5e arch: Move address of MPTABLE to layout module
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-09-27 11:48:30 -07:00
Rob Bradford
6d6e290000 arch: Move APIC and IOAPIC addresses into layout
Move the addresses used for the APIC and IOAPIC into our new memory
layout module.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-09-27 11:48:30 -07:00
Rob Bradford
0e7a1fc923 arch, vmm: Start documenting major regions of RAM and reserved memory
Using the existing layout module start documenting the major regions of
RAM and those areas that are reserved. Some of the constants have also
been renamed to be more consistent and some functions that returned
constant variables have been replaced.

Future commits will move more constants into this file to make it the
canonical source of information about the memory layout.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-09-27 08:55:47 -07:00
Rob Bradford
f63cb85f93 net_util: Implement fmt::Display for MacAddr
Updated clippy does not like the declaration of a "to_string()" function
and instead requires fmt::Display to be implemented.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-09-27 08:05:56 -07:00
Rob Bradford
ff1cb11946 arch: Use if-let notation
To keep updated clippy happy.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-09-27 08:05:56 -07:00
Rob Bradford
df2516f229 vhost_rs: Add clippy override
clippy wants us to use "if let Some(fd) = fd {}" but to combine that
with && is an experimental feature.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-09-27 08:05:56 -07:00
Rob Bradford
2ae3919181 vm-virtio: Fix formatting
With the 1.38.0 toolchain rustfmt is even stricter about formatting now

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-09-27 08:05:56 -07:00
Samuel Ortiz
8188074300 main: Start the VMM thread
We now start the main VMM thread, which will be listening for VM and IPC
related events.
In order to start the configured VM, we no longer directly call the VM
API but we use the IPC instead, to first create and then start a VM.

Fixes: #303

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-09-26 16:21:14 +02:00
Samuel Ortiz
e235c6de4f vmm: Add VM creation and startup helpers
Based on the newly defined Cloud Hypervisor IPC, those helpers send
VmCreate and VmStart requests respectively. This will be used by the
main thread to create and start a VM based on the CLI parameters.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-09-26 16:21:14 +02:00
Samuel Ortiz
151f96e454 vmm: Add a VMM thread startup routine
This starts the main, single VMM thread, which:

1. Creates the VMM instance
2. Starts the VMM control loop
3. Manages the VMM control loop exits for handling resets and shutdowns.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-09-26 16:21:14 +02:00
Samuel Ortiz
2f1ff23066 vmm: (Re-)Introduce a VMM structure
Unlike the Vmm structure we removed with commit bdfd1a3f, this new one
is really meant to represent the VM monitoring/management object.
For that, we implement a control loop that will replace the one that's
currently embedded within the Vm structure itself.
This will allow us to decouple the VM lifecycle management from the VM
object itself, by having a constantly running VMM control loop.

Besides the VM specific events (exit, reset, stdin for now), the VMM
control loop also handles all the Cloud Hypervisor IPC requests.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-09-26 16:21:14 +02:00
Samuel Ortiz
4671a5831f vmm: Move the EpollContext implementation to lib
The VMM thread and control loop will be the sole consumer of the
EpollContext and EpollDispatch API, so let's move it to lib.rs.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-09-26 16:21:14 +02:00
Samuel Ortiz
03ab6839c1 vmm: Introduce Cloud Hypervisor IPC
Cloud Hypervisor IPC is a simple, mpsc based protocol for threads to
send command to the furture VMM thread. This patch adds the API
definition for that IPC, which will be used by both the main thread
to e.g. start a new VM based on the CLI arguments and the future HTTP
server to relay external requests received from a local Unix domain
socket.
We are moving it to its own "api" module because this is where the
external API (HTTP based) will also be implemented.

The VMM thread will be listening for IPC requests from an mpsc receiver,
process them and send a response back through another mpsc channel.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-09-26 16:21:14 +02:00
Samuel Ortiz
6710a39b5a vmm: Pass the exit and reset fds to the vm creation method
As we're going to move the control loop to the VMM thread, the exit and
reset EventFds are no longer going to be owned by the VM.
We pass a copy of them when creating the Vm instead.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-09-26 16:21:14 +02:00
Samuel Ortiz
feb1c33084 vmm: Add a VM config getter
We will need it from the VMM thread, when trying to reboot a VM.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-09-26 16:21:14 +02:00
Samuel Ortiz
47167a658e vmm: Add a VM console handling method
In order to handle the VM STDIN stream from a separate VMM thread
without having to export the DeviceManager, we simply add a console
handling method to the Vm structure.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-09-26 16:21:14 +02:00
Samuel Ortiz
ea7abc6c80 vmm: Add a VM stop method
In order to transfer the control loop to a separate VMM thread, we want
to shrink the VM control loop to a bare minimum.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-09-26 16:21:14 +02:00
Samuel Ortiz
e6ef9ece2c vmm: Move the tty setting to the VM start routine
We want to shrink the control loop to a bare minimal.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-09-26 16:21:14 +02:00
Samuel Ortiz
2e9d815701 vmm: Use a reference counted VmConfig when creating a new VM
Once passed to the VM creation routine, a VmConfig structure is
immutable. We can simply carry a Arc of it instead of a reference.
This also allows us to remove any lifetime bound from our VM.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-09-26 16:21:14 +02:00
Samuel Ortiz
2e0f1c2afe README: Update Slack invitation link
The current one is no longer valid.
The new one does not expire.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-09-25 13:25:00 +01:00
dependabot-preview[bot]
e869283b4e build(deps): bump cfg-if from 0.1.9 to 0.1.10
Bumps [cfg-if](https://github.com/alexcrichton/cfg-if) from 0.1.9 to 0.1.10.
- [Release notes](https://github.com/alexcrichton/cfg-if/releases)
- [Commits](https://github.com/alexcrichton/cfg-if/compare/0.1.9...0.1.10)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-09-24 20:30:43 +00:00
Samuel Ortiz
bdfd1a3f38 vmm: Remove the Vmm structure
The Vmm structure is just a placeholder for the KVM instance. We can
create it directly from the VM creation routine instead.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-09-24 10:12:04 +02:00
Samuel Ortiz
9c5135da7a vmm: Simplify the VM start flow
We can integrate the kernel loading into the VM start method.
The VM start flow is then: Vm::new() -> vm.start(), which feels more
natural.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-09-24 10:12:04 +02:00
Samuel Ortiz
b79c1f7722 vmm: Derive the clone trait for VmConfig
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-09-24 08:39:39 +01:00
Samuel Ortiz
acc60b0ad5 vmm: Make VsockConfig owned
Convert Path to PathBuf and remove the associated lifetime.
Now we can remove the VmConfig associated lifetime.

Fixes #298

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-09-24 08:39:39 +01:00
Samuel Ortiz
3dc7aff00e vmm: Make vhost-user configuration owned
Convert Path to PathBuf, &str to String and remove the associated lifetime.

Fixes #298

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-09-24 08:39:39 +01:00
Samuel Ortiz
5f8a62f3d0 vmm: Make DeviceConfig owned
Convert Path to PathBuf and remove the associated lifetime.

Fixes #298

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-09-24 08:39:39 +01:00
Samuel Ortiz
36137232f0 vmm: Make ConsoleConfig owned
Convert Path to PathBuf and remove the associated lifetime.

Fixes #298

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-09-24 08:39:39 +01:00
Samuel Ortiz
79a02f9171 vmm: Make PmemConfig owned
Convert Path to PathBuf and remove the associated lifetime.

Fixes #298

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-09-24 08:39:39 +01:00
Samuel Ortiz
00674cd850 vmm: Make FsConfig owned
Convert Path to PathBuf, &str to String and remove the associated lifetime.

Fixes #298

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-09-24 08:39:39 +01:00
Samuel Ortiz
5323da031c vmm: Make RngConfig owned
Convert Path to PathBuf and remove the associated lifetime.

Fixes #298

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-09-24 08:39:39 +01:00
Samuel Ortiz
0688bec298 vmm: Make NetConfig owned
Convert str to String and remove the associated lifetime.

Fixes #298

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-09-24 08:39:39 +01:00
Samuel Ortiz
675e46355c vmm: Make DiskConfig owned
Convert Path to PathBuf and remove the associated lifetime.

Fixes #298

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-09-24 08:39:39 +01:00
Samuel Ortiz
036890e5be vmm: Make KernelConfig owned
Convert Path to PathBuf and remove the associated lifetime.

Fixes #298

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-09-24 08:39:39 +01:00
Samuel Ortiz
9c5bfb8e13 vmm: Make MemoryConfig owned
Convert Path to PathBuf and remove the associated lifetime.

Fixes #298

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-09-24 08:39:39 +01:00
dependabot-preview[bot]
dbff0e94b8 build(deps): bump backtrace from 0.3.37 to 0.3.38
Bumps [backtrace](https://github.com/rust-lang/backtrace-rs) from 0.3.37 to 0.3.38.
- [Release notes](https://github.com/rust-lang/backtrace-rs/releases)
- [Commits](https://github.com/rust-lang/backtrace-rs/compare/0.3.37...0.3.38)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-09-23 20:08:09 +00:00
Sebastien Boeuf
0c8f9d2768 ci: Boot from vhost-user-blk with hypervisor-fw
Now that vhost-user has been fixed regarding size of the virtqueues,
booting a VM with the firmware from a vhost-user-blk backend actually
works. That's why this commit updates the previously introduced
integration test to make it use the firmware instead of direct kernel
boot.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-09-23 17:29:38 +01:00
Sebastien Boeuf
f06b2aaaa7 vm-virtio: vhost-user: Set the right vring size
The vhost-user implementation was always passing the maximum size
supported by the virtqueues to the backend, but this is obviously wrong
as it must pass the size being set by the driver running in the guest.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-09-23 17:29:38 +01:00
dependabot-preview[bot]
a2f3da3488 build(deps): bump arc-swap from 0.4.2 to 0.4.3
Bumps [arc-swap](https://github.com/vorner/arc-swap) from 0.4.2 to 0.4.3.
- [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.2...v0.4.3)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-09-21 08:11:43 +00:00
Sebastien Boeuf
2cd406ba50 vm-virtio: Fix virtio-pci BAR type
The 32 or 64 bits type for the memory BAR was not set correctly. This
patch ensure the right type is applied to the BAR.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-09-21 09:11:30 +01:00
Sebastien Boeuf
29b3848ffb ci: Add a test to validate the VM can be booted from vhost-user-blk
Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-09-20 15:56:51 +02:00
Sebastien Boeuf
d723b7dae8 vm-virtio: vhost-user-blk: Add support for reset
If we expect the vhost-user-blk device to be used for booting a VMM
along with the firmware, then need the device to support being reset.

In the vhost-user context, this means the backend needs to be informed
the vrings are disabled and stopped, and the owner needs to be reset
too.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-09-20 15:56:51 +02:00
Sebastien Boeuf
0a229ef4f5 ci: Extend vhost-user-blk test to validate the content
This commit extends the existing integration test related to
vhost-user-blk by validating the block image contains one file
"foo" containing "bar".

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-09-20 15:56:51 +02:00
Yang Zhong
6b06cec611 ci: add test case for vhost-user-blk
Currently we need use backend device from Qemu to test vhost-user-blk
device. Once the rust backend is ready, we will replace it.

Signed-off-by: Yang Zhong <yang.zhong@intel.com>
2019-09-20 15:56:51 +02:00
Yang Zhong
360980d93c vhost-user-blk: enable write_config for WCE
In vhost-user-blk, only WCE value can be set back to device in
guest kernel like
echo "write through" > /sys/block/vda/cache_type

So write_config() will only set WCE value from guest kernel to
vhost user side.

Signed-off-by: Yang Zhong <yang.zhong@intel.com>
2019-09-20 15:56:51 +02:00
Yang Zhong
39083d705b vhost-user-blk: make read_config work
Since config space in vhost-user-blk are mostly from backend
device, this change will get config space info from backend
by vhost-user protocol.

Signed-off-by: Yang Zhong <yang.zhong@intel.com>
2019-09-20 15:56:51 +02:00
Yang Zhong
a949ab21f7 main: add arguments entry for vhost-user-blk
To add vhost-user-blk support, it requires to add arguments
passed for it.

Signed-off-by: Yang Zhong <yang.zhong@intel.com>
2019-09-20 15:56:51 +02:00
Yang Zhong
4164853ec6 vmm: add vhost-user-blk support
Update vm configuration and device initial process to add
vhost-user-blk support.

Signed-off-by: Yang Zhong <yang.zhong@intel.com>
2019-09-20 15:56:51 +02:00
Yang Zhong
c7559bb7a4 config: make error definition common
Since vhost-user-blk use same error definition with vhost-user-net,
those errors need define to common usage.

Signed-off-by: Yang Zhong <yang.zhong@intel.com>
2019-09-20 15:56:51 +02:00
Yang Zhong
397d388710 vm-virtio: Add vhost-user-blk implementation
vhost-user-blk has better performance than virtio-blk, so we need
add vhost-user-blk support with SPDK in Rust-based VMMs.

Signed-off-by: Yang Zhong <yang.zhong@intel.com>
2019-09-20 15:56:51 +02:00
Yang Zhong
b232de9963 vhost_rs: Add INFLIGHT_SHMFD protocol feature
Add to the vhost_rs crate the INFLIGHT_SHMFD protocol feature that was
missing from the list. The feature has been recently introduced in
vhost-user specification, which is the reason why it was missing.

Signed-off-by: Yang Zhong <yang.zhong@intel.com>
2019-09-20 15:56:51 +02:00
Sebastien Boeuf
927148dd3c vhost_rs: Fix GET_CONFIG command
Following the vhost-user specification that can be found at
https://github.com/qemu/qemu/blob/master/docs/interop/vhost-user.rst#virtio-device-config-space
this patch fixes the GET_CONFIG command.

This command is very specific as it needs to provide a body and a
payload corresponding to the content of the configuration structure as
an array of bytes. As a reply, it receives the body and payload provided
by the slave.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-09-20 15:56:51 +02:00
Yang Zhong
c347f84d74 Revert "vhost_rs: add config messge support"
This reverts commit 3e99098bf3 because we
realized there was no need for those extra functions to be introduced as
the existing one from the vhost crate are sufficient for the vhost-user
configuration use case.
2019-09-20 15:56:51 +02:00
Sebastien Boeuf
0a0c7358a2 virtio-bindings: Rely on the upstream crate from rust-vmm
Now that virtio-bindings is a crate part of the rust-vmm project, we
want to rely on this one instead of the local one we had so far.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-09-19 07:13:54 -07:00
Rob Bradford
5f0337c21d tests: Add a "huge" memory test
Now that we have expanded our address space we should add automated
testing to try VMs that use a large amount of RAM.

As the hypervisor does an anonymous mmap() for the backing of memory and
during a typical test boot the guest will not touch it all it should be
possible to test large RAM VMs even if that exceeds the RAM of the host
machine.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-09-19 05:23:15 -07:00
Rob Bradford
b488d4859b arch: x86_64: Fix E820 table for RAM
The last byte was missing from the E820 RAM area. This was due to the
function using the last address relative to the first address in the
range to calculate the size. This incorrectly calculated the size by
one. This produced incorrect E820 tables like this:

[    0.000000] BIOS-e820: [mem 0x0000000000000000-0x000000000009ffff] usable
[    0.000000] BIOS-e820: [mem 0x0000000000100000-0x000000001ffffffe] usable

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-09-19 10:43:55 +01:00
Rob Bradford
5b3ca78dac vmm: Use the full host physical address range
Probe for the size of the host physical address range and use that to
establish the address range for the VM. This removes the limitation on
the size of the VM RAM and gives more space for the devices.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-09-19 10:43:55 +01:00
Rob Bradford
180e6d1e78 vm-virtio: Allocate BARs for virtio-block devices in 32-bit hole
Currently all devices and guest memory share the same 64GiB
allocation. With guest memory working upwards and devices working
downwards. This creates issues if you want to either have a VM with a
large amount of memory or want to have devices with a large allocation
(e.g. virtio-pmem.)

As it is possible for the hypervisor to place devices anywhere in its
address range it is required for simplistic users like the firmware to
set up an identity page table mapping across the full range. Currently
the hypervisor sets up an identify mapping of 1GiB which the firmware
extends to 64GiB to match the current address space size of the
hypervisor.

A simpler solution is to place the device needed for booting with the
firmware (virtio-block) inside the 32-bit memory hole. This allows the
firmware to easily access the block device and paves the way for
increasing the address space beyond the current 64GiB limit.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-09-19 10:43:55 +01:00
Rob Bradford
f0360c92d9 arch: acpi: Set the upper device range based on RAM levels
After the 32-bit gap the memory is shared between the devices and the
RAM. Ensure that the ACPI tables correctly indicate where the RAM ends
and the device area starts by patching the precompiled tables. We get
the following valid output now from the PCI bus probing (8GiB guest)

[    0.317757] pci_bus 0000:00: resource 4 [io  0x0000-0x0cf7 window]
[    0.319035] pci_bus 0000:00: resource 5 [io  0x0d00-0xffff window]
[    0.320215] pci_bus 0000:00: resource 6 [mem 0x000a0000-0x000bffff window]
[    0.321431] pci_bus 0000:00: resource 7 [mem 0xc0000000-0xfebfffff window]
[    0.322613] pci_bus 0000:00: resource 8 [mem 0x240000000-0xfffffffff window]

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-09-19 10:43:55 +01:00
Rob Bradford
f9b0875a60 arch: acpi: Correct range for the 32-bit device hole
There was an off-by-error in the result making the hole one byte too
big and ending at an address too high.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-09-19 10:43:55 +01:00
dependabot-preview[bot]
bf4f3db6c4 build(deps): bump vmm-sys-util from 07ef2e2 to 27e7ff1
Bumps [vmm-sys-util](https://github.com/rust-vmm/vmm-sys-util) from `07ef2e2` to `27e7ff1`.
- [Release notes](https://github.com/rust-vmm/vmm-sys-util/releases)
- [Commits](07ef2e280e...27e7ff14d6)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-09-18 11:48:09 +00:00
Rob Bradford
eb60106159 arch: acpi: Correct starting length of MCFG table
The starting length of the MCFG table was too long resulting in the
kernel trying to get extra MCFG entries from the table that weren't
there resulting in the following error message from the kernel:

PCI: no memory for MCFG entries

The MCFG table also has an 8 bytes of padding at the start before the
table begins.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-09-18 06:27:59 +02:00
dependabot-preview[bot]
67ef4f0d74 build(deps): bump linux-loader from b270081 to 6cf23a8
Bumps [linux-loader](https://github.com/rust-vmm/linux-loader) from `b270081` to `6cf23a8`.
- [Release notes](https://github.com/rust-vmm/linux-loader/releases)
- [Commits](b2700818ad...6cf23a8bca)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-09-17 07:50:59 +00:00
Rob Bradford
f622a76597 build: Build test all supported build configurations on Travis
Treat warnings as errors for these test builds.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-09-16 08:55:35 -07:00
Rob Bradford
3bc11a4a2e vmm: Make the "mmio" only build generate no errors
Rerrange "use" statements and make rename variables and fields to
indicate they might be unused.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-09-16 08:55:35 -07:00
Rob Bradford
4df5ebea12 vmm: Add devices to IO/MMIO bus closer to creation
This removes the register_devices() function with all that functionality
spread across the places where the devices are created.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-09-16 08:55:35 -07:00
Rob Bradford
0739c2c7fd vm-virtio: Fix compilation warning from "mmio" feature only build
Use the correct constant for the newly initialised device state.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-09-16 08:55:35 -07:00
Rob Bradford
7358144f09 vmm: Cleanup warning from "pci" feature only build
Mark exit_evt with an underscore it may be unused (it is ignored if the
"acpi" feature is not turned on.)

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-09-16 08:55:35 -07:00
Rob Bradford
3567206059 build, tests: Update to ssh2 0.4.0
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-09-16 13:21:48 +01:00
dependabot-preview[bot]
ea7f4d7f0b build(deps): bump serde from 1.0.100 to 1.0.101
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.100 to 1.0.101.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.100...v1.0.101)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-09-16 11:47:24 +00:00
dependabot-preview[bot]
c0fede0774 build(deps): bump libssh2-sys from 0.2.11 to 0.2.12
Bumps [libssh2-sys](https://github.com/alexcrichton/ssh2-rs) from 0.2.11 to 0.2.12.
- [Release notes](https://github.com/alexcrichton/ssh2-rs/releases)
- [Commits](https://github.com/alexcrichton/ssh2-rs/compare/libssh2-sys-0.2.11...libssh2-sys-0.2.12)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-09-16 11:47:12 +00:00
Rob Bradford
1097afbaff tests: Run MMIO supported integration tests
Run these tests after the normal tests and only if those have succeeded.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-09-13 12:30:13 +01:00
Rob Bradford
ccb83282e9 tests: Add integration testing for MMIO based virtio
Label tests that definitely can't function with virtio-mmio (because
they use the firmware) and within those that can be used mark individual
assertions that will no longer hold (around PCI.)

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-09-13 12:30:13 +01:00
Rob Bradford
3ad4b8486f tests: Remove unused "kernel_path" variable
tests_virtio_vsock uses a firmware based boot

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-09-13 12:30:13 +01:00
Rob Bradford
1099f0726b vmm: Add MMIO support
Add (non-default) support for using MMIO for virtio devices. This can be
tested by:

cargo build --no-default-features --features "mmio"

All necessary options will be included injected into the kernel
commandline.

Fixes: #243

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-09-13 12:30:13 +01:00
Rob Bradford
26974c7625 vm-virtio: Add MMIO transport
Derived from the crosvm code at 5656c124af2bb956dba19e409a269ca588c685e3
and adapted to work within cloud-hypervisor:

Main differences:

* Interrupt handling is done via a VirtioInterrupt turned into a
devices::Interrupt
* GuestMemory -> GuestMemoryMmap
* Differences in read/write for BusDevice
* Different crates for EventFd and GuestAddress

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-09-13 12:30:13 +01:00
Rob Bradford
c042483953 build: make PCI (virtio and vfio) disableable at build time
Although included by default it is now possible to build without PCI
support.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-09-13 12:30:13 +01:00
Rob Bradford
6d27ac9dfc vmm: Allow the DeviceManager to inject extra kernel commandline entries
This is useful for virtio-mmio to be able to provide the commandline
entries for the devices.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-09-13 12:30:13 +01:00
Rob Bradford
3df1680888 devices: Require Interrupt trait implementations to support Sync
This is necesary to be able easily translate an Interrupt to a
VirtioInterrupt which is already Sync.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-09-13 12:30:13 +01:00
Sebastien Boeuf
f5a44ea1ad docs: Explain how to create a custom Clear Linux cloud image
This is a quick guide on how to create a custom Clear Linux image based
on the official tooling provided by Clear Linux. If for any reason, the
image we are using is missing some interesting bundles that are packaged
by Clear Linux, this documentation will be the guide on how to proceed.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-09-11 19:13:29 +02:00
Sebastien Boeuf
e950aa6b9a ci: Reduce integration testing time
By relying on a decompressed image, this patch assumes that downloading
the image will always be faster than decompressing it from the worker
VM.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-09-11 06:41:01 +02:00
Rob Bradford
05b5115e67 vmm: Call DeviceManager's register_devices() on creation
Rather than calling it at the very start of the VM execution (i.e. when
the VCPUs are created) do it as part of the DeviceManager creation.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-09-10 20:04:00 +02:00
Rob Bradford
7edc46f492 vmm: Make virtio device creation independent of PCI
Create the virtio devices independently of adding them to the PCI bus.
Instead accrue the devices in a vector and add them to the bus en-masse.
This will allow the virtio device creation to be used independently of
PCI based transport.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-09-10 09:41:53 -07:00
Sebastien Boeuf
7c6ef7fd63 ci: Add integration test for virtio-vsock
This patch extends the current set of integration tests to correctly
validate that virtio-vsock is functional. It establishes a communication
between host and guest relying on the newly integrated vsock device.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-09-10 17:57:49 +02:00
Sebastien Boeuf
bf37b960ec ci: Rely on custom Clear Linux cloud image
Rely on the newly generated Clear Linux image for the integration
testing of cloud-hypervisor. The image has been generated using the
Clear Linux clr-installer tooling, which means it is in compliance with
the Clear Linux licensing.

This new image contains one more bundle that was not part of the default
cloudguest image. This bundle is basic-sysadmin, and contains both nc
and socat utilities.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-09-10 17:57:49 +02:00
Sebastien Boeuf
4d86359a09 ci: Install socat on the host VM
In order to interact between host and guest through socket connection,
socat is a useful utility needed for integration testing.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-09-10 17:57:49 +02:00
Sebastien Boeuf
5e5c2f2c48 ci: Allow tests to print some useful information with println
In order to make the tests more verbose and help identify more quickly
where a test might be failing, this patch adds the ability for the unit
tests to print useful information with println.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-09-10 16:33:12 +01:00
Sebastien Boeuf
a9b2207bcf ci: Allow threads to send ssh commands
The goal here is to decouple the Guest instance from the ssh connection
to send some commands to the guest. The reason being to allow ssh
commands to be issued from a different thread, which can be useful to
wait for the end of a command with a thread.join().

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-09-10 17:29:23 +02:00
Samuel Ortiz
40fc6c3f0f README: Update rust-hypervisor-firmware link
We should use the 0.2.0 firmware.

Fixes: #258

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-09-10 08:39:50 +01:00
Rob Bradford
389f9e3779 tests: Check that the test binary cleanly terminated
As part of the reboot test check that the binary cleanly terminated
after the subsequent shutdown.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-09-09 08:24:34 -07:00
Rob Bradford
8f37dec498 vmm: "close" the SIGWINCH signal handler
Rather than sending a signal to the signal handler used for handling
SIGWINCH calls instead use the crate provided termination method. This
also unregisters the signal handler which also means that there won't be
a leaked signal handler remaining.

This leaked signal handler is what was causing a failure to cleanup up
the thread on subsequent requests breaking two reboots in a row.

Fixes: #252

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-09-09 15:42:26 +02:00
dependabot-preview[bot]
c00b58de9b build(deps): bump pkg-config from 0.3.15 to 0.3.16
Bumps [pkg-config](https://github.com/rust-lang/pkg-config-rs) from 0.3.15 to 0.3.16.
- [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.15...0.3.16)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-09-09 12:48:05 +00:00
dependabot-preview[bot]
5f752dade7 build(deps): bump serde from 1.0.99 to 1.0.100
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.99 to 1.0.100.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.99...v1.0.100)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-09-09 08:17:00 +00:00
dependabot-preview[bot]
8e7d67fcfe build(deps): bump vmm-sys-util from 8703cfd to 07ef2e2
Bumps [vmm-sys-util](https://github.com/rust-vmm/vmm-sys-util) from `8703cfd` to `07ef2e2`.
- [Release notes](https://github.com/rust-vmm/vmm-sys-util/releases)
- [Commits](8703cfd427...07ef2e280e)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-09-09 08:16:31 +00:00
dependabot-preview[bot]
b515d48eca build(deps): bump cc from 1.0.42 to 1.0.45
Bumps [cc](https://github.com/alexcrichton/cc-rs) from 1.0.42 to 1.0.45.
- [Release notes](https://github.com/alexcrichton/cc-rs/releases)
- [Commits](https://github.com/alexcrichton/cc-rs/compare/1.0.42...1.0.45)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-09-09 08:15:49 +00:00
Rob Bradford
eb46aa2b22 vmm: If acpi feature is disabled make "reboot" shutdown
With ACPI disabled there is no way to support both reset and shutdown so
make the VMM exit if the VM is rebootet (via i8042 or triple-fault
reset.)

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-09-06 11:03:44 -07:00
Sebastien Boeuf
7975394901 vm-virtio: vsock: Port unit testing from Firecracker
This unit testing porting effort is based off of Firecracker commit
1e1cb6f8f8003e0bdce11d265f0feb23249a03f6

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-09-06 10:51:25 -07:00
Sebastien Boeuf
5a3472847d vm-virtio: vsock: Implement VsockEpollHandler
This is the last step connecting the dots between the virtio-vsock
device and the bulk of the logic hosted in the unix and csm modules.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-09-06 10:51:25 -07:00
Sebastien Boeuf
475e487ac3 vmm: Create vsock backend
This commit relies on the new vsock::unix module to create the backend
that will be used from the virtio-vsock device.

The concept of backend is interesting here as it would allow for a vhost
kernel backend to be plugged if that was needed someday.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-09-06 10:51:25 -07:00
Sebastien Boeuf
434a5d0edf vm-virtio: vsock: Port submodule unix from Firecracker
This code porting is based off of Firecracker commit
1e1cb6f8f8003e0bdce11d265f0feb23249a03f6

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-09-06 10:51:25 -07:00
Sebastien Boeuf
df61a8fea2 vm-virtio: vsock: Port submodule csm and packet from Firecracker
This code porting is based off of Firecracker commit
1e1cb6f8f8003e0bdce11d265f0feb23249a03f6

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-09-06 10:51:25 -07:00
Sebastien Boeuf
22f91ab3a2 vm-virtio: Move vsock to its own module
There is a lot of code related to this virtio-vsock hybrid
implementation, that's why it's better to keep it under its
own module.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-09-06 10:51:25 -07:00
Sebastien Boeuf
4ccc81fdf9 vmm: Create virtio-vsock device
Based on previous patch introducing the new flag "--vsock", this commit
creates a new virtio-vsock device based on the presence of this flag.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-09-06 10:51:25 -07:00
Sebastien Boeuf
11e7ece9f5 vmm: Add new flag "--vsock"
The new flag vsock is meant to be used in order to create a VM with a
virtio-vsock device attached to it. Two parameters are needed with this
device, "cid" representing the guest context ID, and "sock" representing
the UNIX socket path which can be accessed from the host.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-09-06 10:51:25 -07:00
Sebastien Boeuf
c48ca61417 vm-virtio: Add virtio-vsock skeleton
This is the first commit introducing the support for virtio-vsock.

This is based off of Firecracker commit
1e1cb6f8f8003e0bdce11d265f0feb23249a03f6

Fixes #102

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-09-06 10:51:25 -07:00
Sebastien Boeuf
69e27288a2 vmm: Allocate enough MSI-X vectors for multiqueue virtio devices
The default number of MSI-X vector allocated was 2, which is the minimum
defined by the virtio specification. The reason for this minimum is that
virtio needs at least one interrupt to signal that configuration changed
and at least one to specify something happened regarding the virtqueues.

But this current implementation is not optimal because our VMM supports
as many MSI-X vectors as allowed by the MSI-X specification (2048 max).
For that reason, the current patch relies on the number of virtqueues
needed by the virtio device to determine the right amount of MSI-X
vectors needed. It's important not to forget the dedicated vector for
any configuration change too.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-09-06 10:51:25 -07:00
Rob Bradford
d2db34edf2 vmm: Hide underlying console setup from VM
Refactor the underlying console details into the DeviceManager and
abstract away.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-09-06 09:26:37 -07:00
Rob Bradford
d089ee4e25 vmm: Move ownership of the exit/reset EventFd to Vm structure
It makes more sense there as it is used by more than just the
DeviceManager.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-09-06 09:26:37 -07:00
Rob Bradford
2f4de81175 vmm: Access ioapic/io_bus/mmio_bus from DeviceManager via accessor
This paves the way for introducing a trait for the DeviceManager.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-09-06 09:26:37 -07:00
Rob Bradford
9ac967e3d8 vmm: Split DeviceManager into it's own file
Refactor out DeviceManager into it's own file. This is part of a bigger
effort to reduce complexity in the vm.rs file but will also allow future
separation to allow making PCI support optional.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-09-06 09:26:37 -07:00
dependabot-preview[bot]
f740a3568d build(deps): bump getrandom from 0.1.11 to 0.1.12
Bumps [getrandom](https://github.com/rust-random/getrandom) from 0.1.11 to 0.1.12.
- [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.11...v0.1.12)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-09-06 14:07:55 +00:00
dependabot-preview[bot]
e21bc972b0 build(deps): bump vmm-sys-util from fd4dcd1 to 8703cfd
Bumps [vmm-sys-util](https://github.com/rust-vmm/vmm-sys-util) from `fd4dcd1` to `8703cfd`.
- [Release notes](https://github.com/rust-vmm/vmm-sys-util/releases)
- [Commits](fd4dcd172e...8703cfd427)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-09-06 06:37:14 +00:00
dependabot-preview[bot]
d446a8217a build(deps): bump blake2b_simd from 0.5.7 to 0.5.8
Bumps [blake2b_simd](https://github.com/oconnor663/blake2_simd) from 0.5.7 to 0.5.8.
- [Release notes](https://github.com/oconnor663/blake2_simd/releases)
- [Commits](https://github.com/oconnor663/blake2_simd/compare/0.5.7...0.5.8)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-09-05 18:41:53 +00:00
dependabot-preview[bot]
2432ad07ea build(deps): bump cc from 1.0.41 to 1.0.42
Bumps [cc](https://github.com/alexcrichton/cc-rs) from 1.0.41 to 1.0.42.
- [Release notes](https://github.com/alexcrichton/cc-rs/releases)
- [Commits](https://github.com/alexcrichton/cc-rs/compare/1.0.41...1.0.42)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-09-05 16:41:42 +00:00
Rob Bradford
9661e8da5d build: Really make the acpi feature disableable
The command "cargo build --no-default-features" does not recursively
disable the default features across the workspace. Instead add an acpi
feature at the top-level, making it default, and then make that feature
conditional on all the crate acpi features.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-09-05 08:58:47 -07:00
97 changed files with 14622 additions and 6406 deletions

View File

@@ -8,8 +8,14 @@ before_script:
- rustup component add rustfmt
script:
- cargo build --release
- cargo build --release --no-default-features
- cargo rustc --bin cloud-hypervisor -- -D warnings
- 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

411
Cargo.lock generated
View File

@@ -25,7 +25,7 @@ dependencies = [
[[package]]
name = "arc-swap"
version = "0.4.2"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
@@ -37,7 +37,7 @@ dependencies = [
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"kvm-bindings 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"kvm-ioctls 0.2.0 (git+https://github.com/rust-vmm/kvm-ioctls)",
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"linux-loader 0.1.0 (git+https://github.com/rust-vmm/linux-loader)",
"rand 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"vm-memory 0.1.0 (git+https://github.com/rust-vmm/vm-memory)",
@@ -54,10 +54,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "arrayvec"
version = "0.4.11"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)",
"nodrop 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -65,7 +65,7 @@ name = "atty"
version = "0.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -76,22 +76,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "backtrace"
version = "0.3.37"
version = "0.3.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"backtrace-sys 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
"backtrace-sys 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "backtrace-sys"
version = "0.1.31"
version = "0.1.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cc 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
"cc 1.0.46 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -107,11 +107,6 @@ name = "bitflags"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "bitflags"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "bitflags"
version = "1.1.0"
@@ -119,11 +114,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "blake2b_simd"
version = "0.5.7"
version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
"arrayvec 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
"arrayvec 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)",
"constant_time_eq 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -148,12 +143,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "cc"
version = "1.0.41"
version = "1.0.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "cfg-if"
version = "0.1.9"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
@@ -177,11 +172,22 @@ dependencies = [
"clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
"credibility 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
"dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"epoll 4.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"ssh2 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
"net_gen 0.1.0",
"net_util 0.1.0",
"ssh2 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
"vhost_rs 0.1.0",
"vhost_user_backend 0.1.0",
"virtio-bindings 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"vm-device 0.1.0",
"vm-memory 0.1.0 (git+https://github.com/rust-vmm/vm-memory)",
"vm-virtio 0.1.0",
"vmm 0.1.0",
"vmm-sys-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -202,8 +208,8 @@ name = "credibility"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"failure_derive 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -211,7 +217,7 @@ name = "crossbeam-utils"
version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -223,11 +229,11 @@ dependencies = [
"epoll 4.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"kvm-bindings 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"kvm-ioctls 0.2.0 (git+https://github.com/rust-vmm/kvm-ioctls)",
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"vm-memory 0.1.0 (git+https://github.com/rust-vmm/vm-memory)",
"vmm-sys-util 0.1.0 (git+https://github.com/rust-vmm/vmm-sys-util)",
"vmm-sys-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -235,7 +241,7 @@ name = "dirs"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"dirs-sys 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -244,8 +250,8 @@ name = "dirs-sys"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_users 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -256,27 +262,27 @@ version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "failure"
version = "0.1.5"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"backtrace 0.3.37 (registry+https://github.com/rust-lang/crates.io-index)",
"failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"backtrace 0.3.40 (registry+https://github.com/rust-lang/crates.io-index)",
"failure_derive 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "failure_derive"
version = "0.1.5"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)",
"synstructure 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
"synstructure 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -286,12 +292,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "getrandom"
version = "0.1.11"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
"wasi 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"wasi 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -304,7 +310,7 @@ name = "ipnetwork"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -332,7 +338,7 @@ version = "0.2.0"
source = "git+https://github.com/rust-vmm/kvm-ioctls#30adb021584845eed4124bc8ef3b2b96cd9e159f"
dependencies = [
"kvm-bindings 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -342,19 +348,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "libc"
version = "0.2.62"
version = "0.2.65"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "libssh2-sys"
version = "0.2.11"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cc 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
"cc 1.0.46 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"libz-sys 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)",
"openssl-sys 0.9.49 (registry+https://github.com/rust-lang/crates.io-index)",
"pkg-config 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
"openssl-sys 0.9.51 (registry+https://github.com/rust-lang/crates.io-index)",
"pkg-config 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
"vcpkg 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -363,16 +369,16 @@ name = "libz-sys"
version = "1.0.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cc 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
"pkg-config 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
"cc 1.0.46 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"pkg-config 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
"vcpkg 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "linux-loader"
version = "0.1.0"
source = "git+https://github.com/rust-vmm/linux-loader#b2700818add982aa361842a0b8da776b3d1b5cb3"
source = "git+https://github.com/rust-vmm/linux-loader#6cf23a8bca79b5517fa11f90f7f9d828a8290e9d"
dependencies = [
"vm-memory 0.1.0 (git+https://github.com/rust-vmm/vm-memory)",
]
@@ -390,7 +396,7 @@ name = "log"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -398,11 +404,16 @@ name = "memchr"
version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "micro_http"
version = "0.1.0"
source = "git+https://github.com/firecracker-microvm/firecracker#2b94334a6a2a5f7dd07ca94f37cc4d9bdbf57ce6"
[[package]]
name = "net_gen"
version = "0.1.0"
dependencies = [
"vmm-sys-util 0.1.0 (git+https://github.com/rust-vmm/vmm-sys-util)",
"vmm-sys-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -410,29 +421,37 @@ name = "net_util"
version = "0.1.0"
dependencies = [
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"net_gen 0.1.0",
"pnet 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)",
"vmm-sys-util 0.1.0 (git+https://github.com/rust-vmm/vmm-sys-util)",
"serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)",
"vmm-sys-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "nodrop"
version = "0.1.13"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "num_cpus"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "openssl-sys"
version = "0.9.49"
version = "0.9.51"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"cc 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
"pkg-config 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
"cc 1.0.46 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"pkg-config 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
"vcpkg 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -444,16 +463,16 @@ dependencies = [
"devices 0.1.0",
"kvm-bindings 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"kvm-ioctls 0.2.0 (git+https://github.com/rust-vmm/kvm-ioctls)",
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"vm-allocator 0.1.0",
"vm-memory 0.1.0 (git+https://github.com/rust-vmm/vm-memory)",
"vmm-sys-util 0.1.0 (git+https://github.com/rust-vmm/vmm-sys-util)",
"vmm-sys-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "pkg-config"
version = "0.3.15"
version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
@@ -480,7 +499,7 @@ version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"ipnetwork 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"pnet_base 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)",
"pnet_sys 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -521,7 +540,7 @@ name = "pnet_sys"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -531,7 +550,7 @@ name = "pnet_transport"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"pnet_base 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)",
"pnet_packet 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)",
"pnet_sys 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -544,15 +563,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "proc-macro2"
version = "0.4.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "proc-macro2"
version = "1.0.1"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -563,19 +574,11 @@ name = "qcow"
version = "0.1.0"
dependencies = [
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"remain 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
"tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"vmm-sys-util 0.1.0 (git+https://github.com/rust-vmm/vmm-sys-util)",
]
[[package]]
name = "quote"
version = "0.6.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)",
"vmm-sys-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -583,7 +586,7 @@ name = "quote"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -592,7 +595,7 @@ version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -603,8 +606,8 @@ name = "rand"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"getrandom 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
"getrandom 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_core 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -637,7 +640,7 @@ name = "rand_core"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"getrandom 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
"getrandom 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -655,7 +658,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
"fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -679,7 +682,7 @@ name = "redox_users"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)",
"rust-argon2 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -707,9 +710,9 @@ name = "remain"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -726,7 +729,7 @@ version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
"blake2b_simd 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)",
"blake2b_simd 0.5.8 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -742,22 +745,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "ryu"
version = "1.0.0"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "serde"
version = "1.0.99"
version = "1.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "serde_derive"
version = "1.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "serde_json"
version = "1.0.40"
version = "1.0.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
"ryu 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)",
"ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -765,7 +778,7 @@ name = "signal-hook"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"signal-hook-registry 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -774,18 +787,18 @@ name = "signal-hook-registry"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"arc-swap 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
"arc-swap 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "ssh2"
version = "0.3.3"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
"libssh2-sys 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"libssh2-sys 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -795,33 +808,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "syn"
version = "0.15.44"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "syn"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "synstructure"
version = "0.10.2"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -838,7 +841,7 @@ name = "syntex_errors"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)",
"syntex_pos 0.42.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -860,7 +863,7 @@ version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)",
"syntex_errors 0.42.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -883,8 +886,8 @@ name = "tempfile"
version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)",
"remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -916,6 +919,14 @@ dependencies = [
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "threadpool"
version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "unicode-width"
version = "0.1.6"
@@ -926,11 +937,6 @@ name = "unicode-xid"
version = "0.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "unicode-xid"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "unicode-xid"
version = "0.2.0"
@@ -959,20 +965,22 @@ dependencies = [
"devices 0.1.0",
"kvm-bindings 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"kvm-ioctls 0.2.0 (git+https://github.com/rust-vmm/kvm-ioctls)",
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"pci 0.1.0",
"vfio-bindings 0.0.1",
"vfio-bindings 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"vm-allocator 0.1.0",
"vm-device 0.1.0",
"vm-memory 0.1.0 (git+https://github.com/rust-vmm/vm-memory)",
"vmm-sys-util 0.1.0 (git+https://github.com/rust-vmm/vmm-sys-util)",
"vmm-sys-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "vfio-bindings"
version = "0.0.1"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"vmm-sys-util 0.1.0 (git+https://github.com/rust-vmm/vmm-sys-util)",
"vmm-sys-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -980,31 +988,53 @@ name = "vhost_rs"
version = "0.1.0"
dependencies = [
"bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"vm-memory 0.1.0 (git+https://github.com/rust-vmm/vm-memory)",
"vmm-sys-util 0.1.0 (git+https://github.com/rust-vmm/vmm-sys-util)",
"vmm-sys-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "vhost_user_backend"
version = "0.1.0"
dependencies = [
"epoll 4.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"vhost_rs 0.1.0",
"vm-memory 0.1.0 (git+https://github.com/rust-vmm/vm-memory)",
"vm-virtio 0.1.0",
"vmm-sys-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "virtio-bindings"
version = "0.1.0"
source = "git+https://github.com/rust-vmm/virtio-bindings#c5676476f1048d212f53ca80711d2b132dcebe23"
[[package]]
name = "virtio-bindings"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "vm-allocator"
version = "0.1.0"
dependencies = [
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"vm-memory 0.1.0 (git+https://github.com/rust-vmm/vm-memory)",
]
[[package]]
name = "vm-device"
version = "0.1.0"
[[package]]
name = "vm-memory"
version = "0.1.0"
source = "git+https://github.com/rust-vmm/vm-memory#8669369d17a470d72743fc616a26ee6b95f230f5"
dependencies = [
"cast 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -1015,17 +1045,18 @@ dependencies = [
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"devices 0.1.0",
"epoll 4.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"net_gen 0.1.0",
"net_util 0.1.0",
"pci 0.1.0",
"tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"vhost_rs 0.1.0",
"virtio-bindings 0.1.0",
"virtio-bindings 0.1.0 (git+https://github.com/rust-vmm/virtio-bindings)",
"vm-allocator 0.1.0",
"vm-device 0.1.0",
"vm-memory 0.1.0 (git+https://github.com/rust-vmm/vm-memory)",
"vmm-sys-util 0.1.0 (git+https://github.com/rust-vmm/vmm-sys-util)",
"vmm-sys-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -1038,31 +1069,37 @@ dependencies = [
"epoll 4.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"kvm-bindings 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"kvm-ioctls 0.2.0 (git+https://github.com/rust-vmm/kvm-ioctls)",
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"linux-loader 0.1.0 (git+https://github.com/rust-vmm/linux-loader)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"micro_http 0.1.0 (git+https://github.com/firecracker-microvm/firecracker)",
"net_util 0.1.0",
"pci 0.1.0",
"qcow 0.1.0",
"serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)",
"signal-hook 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"threadpool 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
"vfio 0.0.1",
"vm-allocator 0.1.0",
"vm-memory 0.1.0 (git+https://github.com/rust-vmm/vm-memory)",
"vm-virtio 0.1.0",
"vmm-sys-util 0.1.0 (git+https://github.com/rust-vmm/vmm-sys-util)",
"vmm-sys-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "vmm-sys-util"
version = "0.1.0"
source = "git+https://github.com/rust-vmm/vmm-sys-util#fd4dcd172e5e607bb480ff8c9004c90f6671043f"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "wasi"
version = "0.5.0"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
@@ -1106,23 +1143,22 @@ dependencies = [
[metadata]
"checksum aho-corasick 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "81ce3d38065e618af2d7b77e10c5ad9a069859b4be3c2250f674af3840d9c8a5"
"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
"checksum arc-swap 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "854ede29f7a0ce90519fb2439d030320c6201119b87dab0ee96044603e1130b9"
"checksum arc-swap 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "f1a1eca3195b729bbd64e292ef2f5fff6b1c28504fed762ce2b1013dde4d8e92"
"checksum arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0d382e583f07208808f6b1249e60848879ba3543f57c32277bf52d69c2f0f0ee"
"checksum arrayvec 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "b8d73f9beda665eaa98ab9e4f7442bd4e7de6652587de55b2525e52e29c1b0ba"
"checksum arrayvec 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9"
"checksum atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90"
"checksum autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "b671c8fb71b457dd4ae18c4ba1e59aa81793daacc361d82fcd410cef0d491875"
"checksum backtrace 0.3.37 (registry+https://github.com/rust-lang/crates.io-index)" = "5180c5a20655b14a819b652fd2378fa5f1697b6c9ddad3e695c2f9cedf6df4e2"
"checksum backtrace-sys 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)" = "82a830b4ef2d1124a711c71d263c5abdc710ef8e907bd508c88be475cebc422b"
"checksum backtrace 0.3.40 (registry+https://github.com/rust-lang/crates.io-index)" = "924c76597f0d9ca25d762c25a4d369d51267536465dc5064bdf0eb073ed477ea"
"checksum backtrace-sys 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)" = "5d6575f128516de27e3ce99689419835fce9643a9b215a14d2b5b685be018491"
"checksum base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e"
"checksum bitflags 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4f67931368edf3a9a51d29886d245f1c3db2f1ef0dcc9e35ff70341b78c10d23"
"checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d"
"checksum bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d155346769a6855b86399e9bc3814ab343cd3d62c7e985113d46a0ec3c281fd"
"checksum blake2b_simd 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)" = "bf775a81bb2d464e20ff170ac20316c7b08a43d11dbc72f0f82e8e8d3d6d0499"
"checksum blake2b_simd 0.5.8 (registry+https://github.com/rust-lang/crates.io-index)" = "5850aeee1552f495dd0250014cf64b82b7c8879a89d83b33bbdace2cc4f63182"
"checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5"
"checksum c2-chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7d64d04786e0f528460fc884753cf8dddcc466be308f6026f8e355c41a0e4101"
"checksum cast 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "926013f2860c46252efceabb19f4a6b308197505082c609025aa6706c011d427"
"checksum cc 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)" = "8dae9c4b8fedcae85592ba623c4fd08cfdab3e3b72d6df780c6ead964a69bfff"
"checksum cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33"
"checksum cc 1.0.46 (registry+https://github.com/rust-lang/crates.io-index)" = "0213d356d3c4ea2c18c40b037c3be23cd639825c18f25ee670ac7813beeef99c"
"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
"checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9"
"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
"checksum constant_time_eq 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "995a44c877f9212528ccc74b21a232f66ad69001e40ede5bcee2ac9ef2657120"
@@ -1131,10 +1167,10 @@ dependencies = [
"checksum dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3"
"checksum dirs-sys 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "afa0b23de8fd801745c471deffa6e12d248f962c9fd4b4c33787b055599bde7b"
"checksum epoll 4.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "990bcfe26bea89669ede68c3f970f61d02568dbc8660317c98d805ea4e710685"
"checksum failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "795bd83d3abeb9220f257e597aa0080a508b27533824adf336529648f6abf7e2"
"checksum failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ea1063915fd7ef4309e222a5a07cf9c319fb9c7836b1f89b85458672dbb127e1"
"checksum failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "f8273f13c977665c5db7eb2b99ae520952fe5ac831ae4cd09d80c4c7042b5ed9"
"checksum failure_derive 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0bc225b78e0391e4b8683440bf2e63c2deeeb2ce5189eab46e2b68c6d3725d08"
"checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
"checksum getrandom 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "fc344b02d3868feb131e8b5fe2b9b0a1cc42942679af493061fc13b853243872"
"checksum getrandom 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "473a1265acc8ff1e808cd0a1af8cee3c2ee5200916058a2ca113c29f2d903571"
"checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb"
"checksum ipnetwork 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b3d862c86f7867f19b693ec86765e0252d82e53d4240b9b629815675a0714ad1"
"checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f"
@@ -1142,16 +1178,18 @@ dependencies = [
"checksum kvm-bindings 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c223e8703d2eb76d990c5f58e29c85b0f6f50e24b823babde927948e7c71fc03"
"checksum kvm-ioctls 0.2.0 (git+https://github.com/rust-vmm/kvm-ioctls)" = "<none>"
"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
"checksum libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)" = "34fcd2c08d2f832f376f4173a231990fa5aef4e99fb569867318a227ef4c06ba"
"checksum libssh2-sys 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "126a1f4078368b163bfdee65fbab072af08a1b374a5551b21e87ade27b1fbf9d"
"checksum libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)" = "1a31a0627fdf1f6a39ec0dd577e101440b7db22672c0901fe00a9a6fbb5c24e8"
"checksum libssh2-sys 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)" = "8914d10b159fc288f2b6f253c94bd0c15a777fd5a297691141d89674b87e66fd"
"checksum libz-sys 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)" = "2eb5e43362e38e2bca2fd5f5134c4d4564a23a5c28e9b95411652021a8675ebe"
"checksum linux-loader 0.1.0 (git+https://github.com/rust-vmm/linux-loader)" = "<none>"
"checksum log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b"
"checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7"
"checksum memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e"
"checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945"
"checksum openssl-sys 0.9.49 (registry+https://github.com/rust-lang/crates.io-index)" = "f4fad9e54bd23bd4cbbe48fdc08a1b8091707ac869ef8508edea2fec77dcc884"
"checksum pkg-config 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c1d2cfa5a714db3b5f24f0915e74fcdf91d09d496ba61329705dda7774d2af"
"checksum micro_http 0.1.0 (git+https://github.com/firecracker-microvm/firecracker)" = "<none>"
"checksum nodrop 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb"
"checksum num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bcef43580c035376c0705c42792c294b66974abbfd2789b511784023f71f3273"
"checksum openssl-sys 0.9.51 (registry+https://github.com/rust-lang/crates.io-index)" = "ba24190c8f0805d3bd2ce028f439fe5af1d55882bbe6261bed1dbc93b50dd6b1"
"checksum pkg-config 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)" = "72d5370d90f49f70bd033c3d75e87fc529fbfff9d6f7cccef07d6170079d91ea"
"checksum pnet 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)" = "63d693c84430248366146e3181ff9d330243464fa9e6146c372b2f3eb2e2d8e7"
"checksum pnet_base 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4df28acf2fcc77436dd2b91a9a0c2bb617f9ca5f2acefee1a4135058b9f9801f"
"checksum pnet_datalink 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b34f8ca857599d05b6b082e9baff8d27c54cb9c26568cf3c0993a5755816966"
@@ -1161,9 +1199,7 @@ dependencies = [
"checksum pnet_sys 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)" = "682b2eca84cc440bce8336813f78eb6d3cb0fed89fe0e87ae22acfca8363f176"
"checksum pnet_transport 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5faa55dcf725487a699adcff88dfea8f17ea34fa2640528866d9acbb4e3a104f"
"checksum ppv-lite86 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e3cbf9f658cdb5000fcf6f362b8ea2ba154b9f146a61c7a20d647034c6b6561b"
"checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759"
"checksum proc-macro2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4c5c2380ae88876faae57698be9e9775e3544decad214599c3a6266cca6ac802"
"checksum quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1"
"checksum proc-macro2 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "90cf5f418035b98e655e9cdb225047638296b862b42411c4e45bb88d700f7fc0"
"checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe"
"checksum rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293"
"checksum rand 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d47eab0e83d9693d40f825f86948aa16eff6750ead4bdffc4ab95b8b3a7f052c"
@@ -1183,16 +1219,16 @@ dependencies = [
"checksum rust-argon2 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4ca4eaef519b494d1f2848fc602d18816fed808a981aedf4f1f00ceb7c9d32cf"
"checksum rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783"
"checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda"
"checksum ryu 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c92464b447c0ee8c4fb3824ecc8383b81717b9f1e74ba2e72540aef7b9f82997"
"checksum serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)" = "fec2851eb56d010dc9a21b89ca53ee75e6528bab60c11e89d38390904982da9f"
"checksum serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)" = "051c49229f282f7c6f3813f8286cc1e3323e8051823fce42c7ea80fe13521704"
"checksum ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8"
"checksum serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)" = "9796c9b7ba2ffe7a9ce53c2287dfc48080f4b2b362fcc245a259b3a7201119dd"
"checksum serde_derive 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)" = "4b133a43a1ecd55d4086bd5b4dc6c1751c68b1bfbeba7a5040442022c7e7c02e"
"checksum serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)" = "2f72eb2a68a7dc3f9a691bfda9305a1c017a6215e5a4545c258500d2099a37c2"
"checksum signal-hook 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4f61c4d59f3aaa9f61bba6450a9b80ba48362fd7d651689e7a10c453b1f6dc68"
"checksum signal-hook-registry 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1797d48f38f91643908bb14e35e79928f9f4b3cefb2420a564dde0991b4358dc"
"checksum ssh2 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "dee822d619a700f98c4de3b5931f272ecc7cf2e924ceb2df47b61df4ae033a0c"
"checksum ssh2 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "875fc74ffc41049e306a247f4191dd9c2ebfb7c5e0a6e4ddfdccacaba732e8aa"
"checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
"checksum syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)" = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5"
"checksum syn 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "158521e6f544e7e3dcfc370ac180794aa38cb34a1b1e07609376d4adcf429b93"
"checksum synstructure 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "02353edf96d6e4dc81aea2d8490a7e9db177bf8acb0e951c24940bf866cb313f"
"checksum syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "66850e97125af79138385e9b88339cbcd037e3f28ceab8c5ad98e64f0f1f80bf"
"checksum synstructure 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3f085a5855930c0441ca1288cf044ea4aecf4f43a91668abdb870b4ba546a203"
"checksum syntex 0.42.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0a30b08a6b383a22e5f6edc127d169670d48f905bb00ca79a00ea3e442ebe317"
"checksum syntex_errors 0.42.0 (registry+https://github.com/rust-lang/crates.io-index)" = "04c48f32867b6114449155b2a82114b86d4b09e1bddb21c47ff104ab9172b646"
"checksum syntex_pos 0.42.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3fd49988e52451813c61fecbe9abb5cfd4e1b7bb6cdbb980a6fbcbab859171a6"
@@ -1202,16 +1238,19 @@ dependencies = [
"checksum term 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "fa63644f74ce96fbeb9b794f66aff2a52d601cbd5e80f4b97123e3899f4570f1"
"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
"checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b"
"checksum threadpool 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e2f0c90a5f3459330ac8bc0d2f879c693bb7a2f59689c1083fc4ef83834da865"
"checksum unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7007dbd421b92cc6e28410fe7362e2e0a2503394908f417b68ec8d1c364c4e20"
"checksum unicode-xid 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "36dff09cafb4ec7c8cf0023eb0b686cb6ce65499116a12201c9e11840ca01beb"
"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
"checksum utf8-ranges 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b4ae116fef2b7fea257ed6440d3cfcff7f190865f170cdad00bb6465bf18ecba"
"checksum vcpkg 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "33dd455d0f96e90a75803cfeb7f948768c08d70a6de9a8d2362461935698bf95"
"checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a"
"checksum vfio-bindings 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "df0379abaedb35cac2636dd2ee75496ab921430fa5775b90f13bbda3e2abaf66"
"checksum virtio-bindings 0.1.0 (git+https://github.com/rust-vmm/virtio-bindings)" = "<none>"
"checksum virtio-bindings 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ff512178285488516ed85f15b5d0113a7cdb89e9e8a760b269ae4f02b84bd6b"
"checksum vm-memory 0.1.0 (git+https://github.com/rust-vmm/vm-memory)" = "<none>"
"checksum vmm-sys-util 0.1.0 (git+https://github.com/rust-vmm/vmm-sys-util)" = "<none>"
"checksum wasi 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fd5442abcac6525a045cc8c795aedb60da7a2e5e89c7bf18a0d5357849bb23c7"
"checksum vmm-sys-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "46996f56aeae31fbc0532ae57a944e00089302f03b18c10c76eebfd9249f4a6c"
"checksum wasi 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b89c3ce4ce14bdc6fb6beaf9ec7928ca331de5df7e5ea278375642a2f478570d"
"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
"checksum winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "f10e386af2b13e47c89e7236a7a14a086791a2b88ebad6df9bf42040195cf770"
"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"

View File

@@ -6,21 +6,42 @@ edition = "2018"
[dependencies]
clap = "2.33.0"
epoll = "4.1.0"
lazy_static = "1.4.0"
libc = "0.2.65"
log = { version = "0.4.8", features = ["std"] }
net_gen = { path = "net_gen" }
net_util = { path = "net_util" }
vhost_user_backend = { path = "vhost_user_backend"}
virtio-bindings = "0.1.0"
vmm = { path = "vmm" }
vm-device = { path = "vm-device" }
vm-memory = { git = "https://github.com/rust-vmm/vm-memory" }
vmm-sys-util = "0.1.1"
vm-virtio = { path = "vm-virtio" }
[dev-dependencies]
ssh2 = "0.3.3"
ssh2 = "0.4.0"
dirs = "2.0.2"
credibility = "0.1.3"
tempdir= "0.3.7"
lazy_static= "1.4.0"
[dependencies.vhost_rs]
path = "vhost_rs"
features = ["vhost-user-slave"]
[features]
default = []
default = ["acpi", "pci", "cmos"]
acpi = ["vmm/acpi"]
pci = ["vmm/pci_support"]
mmio = ["vmm/mmio_support"]
cmos = ["vmm/cmos"]
# Integration tests require a special environment to run in
integration_tests = []
[workspace]
members = [
"vhost_user_backend",
]

2
Jenkinsfile vendored
View File

@@ -5,7 +5,7 @@ stage ("Builds") {
}
stage ('Install system packages') {
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"
sh "sudo apt-get install -yq flex bison libelf-dev qemu-utils qemu-system libglib2.0-dev libpixman-1-dev libseccomp-dev socat"
}
stage ('Install Rust') {
sh "nohup curl https://sh.rustup.rs -sSf | sh -s -- -y"

View File

@@ -104,7 +104,7 @@ We need to get the latest `rust-hypervisor-firmware` release and also a working
$ pushd $CLOUDH
$ wget https://download.clearlinux.org/releases/29160/clear/clear-29160-kvm.img.xz
$ unxz clear-29160-kvm.img.xz
$ wget https://github.com/intel/rust-hypervisor-firmware/releases/download/0.1.0/hypervisor-fw
$ wget https://github.com/intel/rust-hypervisor-firmware/releases/download/0.2.0/hypervisor-fw
$ popd
```
@@ -113,7 +113,7 @@ $ pushd $CLOUDH
$ sudo setcap cap_net_admin+ep ./cloud-hypervisor/target/release/cloud-hypervisor
$ ./cloud-hypervisor/target/release/cloud-hypervisor \
--kernel ./hypervisor-fw \
--disk ./clear-29160-kvm.img \
--disk path=clear-29160-kvm.img \
--cpus 4 \
--memory size=1024M \
--net "tap=,mac=,ip=,mask=" \
@@ -121,7 +121,7 @@ $ ./cloud-hypervisor/target/release/cloud-hypervisor \
$ popd
```
Multiple arguments can be given to the `--disk` parameter, currently the firmware requires that the bootable image is on the first disk.
Multiple arguments can be given to the `--disk` parameter.
### Custom kernel and disk image
@@ -167,7 +167,7 @@ $ pushd $CLOUDH
$ sudo setcap cap_net_admin+ep ./cloud-hypervisor/target/release/cloud-hypervisor
$ ./cloud-hypervisor/target/release/cloud-hypervisor \
--kernel ./linux-cloud-hypervisor/arch/x86/boot/compressed/vmlinux.bin \
--disk ./clear-29160-kvm.img \
--disk path=clear-29160-kvm.img \
--cmdline "console=hvc0 reboot=k panic=1 nomodules i8042.noaux i8042.nomux i8042.nopnp i8042.dumbkbd root=/dev/vda3" \
--cpus 4 \
--memory size=1024M \
@@ -187,7 +187,7 @@ $ ./cloud-hypervisor/target/release/cloud-hypervisor \
--kernel ./linux-cloud-hypervisor/arch/x86/boot/compressed/vmlinux.bin \
--console off \
--serial tty \
--disk ./clear-29160-kvm.img \
--disk path=clear-29160-kvm.img \
--cmdline "console=ttyS0 reboot=k panic=1 nomodules i8042.noaux i8042.nomux i8042.nopnp i8042.dumbkbd root=/dev/vda3" \
--cpus 4 \
--memory size=1024M \
@@ -255,7 +255,7 @@ etc, are all equal and welcome means of contribution. See the [CONTRIBUTING](CON
## Join us
Get an [invite to our Slack channel](https://join.slack.com/t/cloud-hypervisor/shared_invite/enQtNjY3MTE3MDkwNDQ4LTc0YzlmYzQxZDkxNDVhYzZjZjA5MTkxMGY3NTI3YzMzYTFkM2IyY2E0YTIxMzkyYTEwYzdlMzBhMWYxYzVmNDI)
Get an [invite to our Slack channel](https://join.slack.com/t/cloud-hypervisor/shared_invite/enQtNjY3MTE3MDkwNDQ4LWQ1MTA1ZDVmODkwMWQ1MTRhYzk4ZGNlN2UwNTI3ZmFlODU0OTcwOWZjMTkwZDExYWE3YjFmNzgzY2FmNDAyMjI)
and [join us on Slack](https://cloud-hypervisor.slack.com/).
# 6. Security

View File

@@ -4,7 +4,7 @@ version = "0.1.0"
authors = ["The Chromium OS Authors"]
[features]
default = ["acpi"]
default = []
acpi = ["acpi_tables"]
[dependencies]

View File

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

View File

@@ -13,6 +13,7 @@ extern crate byteorder;
extern crate kvm_bindings;
extern crate libc;
#[cfg(feature = "acpi")]
extern crate acpi_tables;
extern crate arch_gen;
extern crate kvm_ioctls;
@@ -20,7 +21,6 @@ extern crate linux_loader;
extern crate vm_memory;
use std::result;
use vm_memory::GuestAddress;
#[derive(Debug, PartialEq)]
pub enum Error {
@@ -52,9 +52,6 @@ pub enum RegionType {
Reserved,
}
// 1MB. We don't put anything above here except the kernel itself.
pub const HIMEM_START: GuestAddress = GuestAddress(0x100000);
#[cfg(target_arch = "aarch64")]
pub mod aarch64;
@@ -69,6 +66,5 @@ pub mod x86_64;
#[cfg(target_arch = "x86_64")]
pub use x86_64::{
arch_memory_regions, configure_system, get_32bit_gap_start as get_reserved_mem_addr,
layout::CMDLINE_MAX_SIZE, layout::CMDLINE_START,
arch_memory_regions, configure_system, layout, layout::CMDLINE_MAX_SIZE, layout::CMDLINE_START,
};

View File

@@ -10,6 +10,10 @@ 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,
@@ -51,12 +55,67 @@ struct PCIRangeEntry {
_reserved: u32,
}
pub fn create_dsdt_table(serial_enabled: bool) -> SDT {
#[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 40 bytes
As the output contains a table header that is not required the first 36 bytes
should be disregarded.
*/
@@ -112,9 +171,9 @@ pub fn create_dsdt_table(serial_enabled: bool) -> SDT {
DWordMemory (ResourceProducer, PosDecode, MinFixed, MaxFixed, NonCacheable, ReadWrite,
0x00000000, // Granularity
0xC0000000, // Range Minimum
0xFEC00000, // Range Maximum
0xFEBFFFFF, // Range Maximum
0x00000000, // Translation Offset
0x3EC00001, // Length
0x3EC00000, // Length
,, , AddressRangeMemory, TypeStatic)
QWordMemory (ResourceProducer, PosDecode, MinFixed, MaxFixed, Cacheable, ReadWrite,
0x0000000000000000, // Granularity
@@ -126,7 +185,7 @@ pub fn create_dsdt_table(serial_enabled: bool) -> SDT {
})
}
*/
let pci_dsdt_data = [
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,
@@ -138,13 +197,57 @@ pub fn create_dsdt_table(serial_enabled: bool) -> SDT {
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, 0x00, 0x00, 0xC0, 0xFE, 0x00, 0x00,
0x00, 0x00, 0x01, 0x00, 0xC0, 0x3E, 0x8A, 0x2B, 0x00, 0x00, 0x0C, 0x03, 0x00, 0x00, 0x00,
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)
{
@@ -183,6 +286,7 @@ pub fn create_dsdt_table(serial_enabled: bool) -> SDT {
// 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);
}
@@ -194,13 +298,16 @@ 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 = super::EBDA_START;
let rsdp_offset = layout::RSDP_POINTER;
let mut tables: Vec<u64> = Vec::new();
// DSDT
let dsdt = create_dsdt_table(serial_enabled);
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)
@@ -238,7 +345,7 @@ pub fn create_acpi_tables(
// MADT
let mut madt = SDT::new(*b"APIC", 44, 5, *b"CLOUDH", *b"CHMADT ", 1);
madt.write(36, super::mptable::APIC_DEFAULT_PHYS_BASE);
madt.write(36, layout::APIC_START);
for cpu in 0..num_cpus {
let lapic = LocalAPIC {
@@ -255,7 +362,7 @@ pub fn create_acpi_tables(
r#type: 1,
length: 12,
ioapic_id: 0,
apic_address: super::mptable::IO_APIC_DEFAULT_PHYS_BASE,
apic_address: layout::IOAPIC_START.0 as u32,
gsi_base: 0,
..Default::default()
});
@@ -276,11 +383,14 @@ pub fn create_acpi_tables(
tables.push(madt_offset.0);
// MCFG
let mut mcfg = SDT::new(*b"MCFG", 60, 1, *b"CLOUDH", *b"CHMCFG ", 1);
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: super::MEM_32BIT_DEVICES_GAP_SIZE,
base_address: layout::PCI_MMCONFIG_START.0,
segment: 0,
start: 0,
end: 0xff,
@@ -293,6 +403,65 @@ pub fn create_acpi_tables(
.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 {
@@ -300,7 +469,7 @@ pub fn create_acpi_tables(
}
xsdt.update_checksum();
let xsdt_offset = mcfg_offset.checked_add(mcfg.len() as u64).unwrap();
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");

View File

@@ -5,21 +5,90 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE-BSD-3-Clause file.
use vm_memory::GuestAddress;
use vm_memory::{GuestAddress, GuestUsize};
/// Magic addresses externally used to lay out x86_64 VMs.
/*
Memory layout documentation and constants
~~~~~~ ~~~~~~ ~~~~~~~~~~~~~ ~~~ ~~~~~~~~~
Constants are in order and grouped by range. Take care to update all references
when making changes and keep them in order.
*/
// ** Low RAM (start: 0, length: 640KiB) **
pub const LOW_RAM_START: GuestAddress = GuestAddress(0x0);
// == Fixed addresses within the "Low RAM" range: ==
// Initial GDT/IDT needed to boot kernel
pub const BOOT_GDT_START: GuestAddress = GuestAddress(0x500);
pub const BOOT_IDT_START: GuestAddress = GuestAddress(0x520);
/// The 'zero page', a.k.a linux kernel bootparams.
pub const ZERO_PAGE_START: GuestAddress = GuestAddress(0x7000);
/// Initial stack for the boot CPU.
pub const BOOT_STACK_START: GuestAddress = GuestAddress(0x8000);
pub const BOOT_STACK_POINTER: GuestAddress = GuestAddress(0x8ff0);
// Initial pagetables.
pub const PML4_START: GuestAddress = GuestAddress(0x9000);
pub const PDPTE_START: GuestAddress = GuestAddress(0xa000);
pub const PDE_START: GuestAddress = GuestAddress(0xb000);
/// Kernel command line start address.
pub const CMDLINE_START: GuestAddress = GuestAddress(0x20000);
/// Kernel command line start address maximum size.
pub const CMDLINE_MAX_SIZE: usize = 0x10000;
/// Address for the TSS setup.
pub const KVM_TSS_ADDRESS: GuestAddress = GuestAddress(0xfffbd000);
// MPTABLE, describing VCPUS.
pub const MPTABLE_START: GuestAddress = GuestAddress(0x9fc00);
/// The 'zero page', a.k.a linux kernel bootparams.
pub const ZERO_PAGE_START: GuestAddress = GuestAddress(0x7000);
// == End of "Low RAM" range. ==
// ** EBDA reserved area (start: 640KiB, length: 384KiB) **
pub const EBDA_START: GuestAddress = GuestAddress(0xa0000);
// == Fixed constants within the "EBDA" range ==
// ACPI RSDP table
pub const RSDP_POINTER: GuestAddress = EBDA_START;
// == End of "EBDA" range ==
// ** High RAM (start: 1MiB, length: 3071MiB) **
pub const HIGH_RAM_START: GuestAddress = GuestAddress(0x100000);
// == No fixed addresses in the "High RAM" range ==
// ** 32-bit reserved area (start: 3GiB, length: 1GiB) **
pub const MEM_32BIT_RESERVED_START: GuestAddress = GuestAddress(0xc000_0000);
pub const MEM_32BIT_RESERVED_SIZE: GuestUsize = (1024 << 20);
// == Fixed constants within the "32-bit reserved" range ==
// Sub range: 32-bit PCI devices (start: 3GiB, length: 640Mib)
pub const MEM_32BIT_DEVICES_START: GuestAddress = MEM_32BIT_RESERVED_START;
pub const MEM_32BIT_DEVICES_SIZE: GuestUsize = (640 << 20);
// PCI MMCONFIG space (start: after the device space, length: 256MiB)
pub const PCI_MMCONFIG_START: GuestAddress =
GuestAddress(MEM_32BIT_DEVICES_START.0 + MEM_32BIT_DEVICES_SIZE);
pub const PCI_MMCONFIG_SIZE: GuestUsize = (256 << 20);
// IOAPIC
pub const IOAPIC_START: GuestAddress = GuestAddress(0xfec0_0000);
pub const IOAPIC_SIZE: GuestUsize = 0x20;
// APIC
pub const APIC_START: GuestAddress = GuestAddress(0xfee0_0000);
/// Address for the TSS setup.
pub const KVM_TSS_ADDRESS: GuestAddress = GuestAddress(0xfffb_d000);
// == End of "32-bit reserved" range. ==
// ** 64-bit RAM start (start: 4GiB, length: varies) **
pub const RAM_64BIT_START: GuestAddress = GuestAddress(0x1_0000_0000);

View File

@@ -5,7 +5,9 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE-BSD-3-Clause file.
#[cfg(feature = "acpi")]
mod acpi;
mod gdt;
pub mod interrupts;
pub mod layout;
@@ -20,6 +22,7 @@ use vm_memory::{
};
const E820_RAM: u32 = 1;
const E820_RESERVED: u32 = 2;
// This is a workaround to the Rust enforcement specifying that any implementation of a foreign
// trait (in this case `DataInit`) where:
@@ -46,73 +49,53 @@ impl From<Error> for super::Error {
}
}
// Where BIOS/VGA magic would live on a real PC.
const EBDA_START: GuestAddress = GuestAddress(0xa0000);
const FIRST_ADDR_PAST_32BITS: GuestAddress = GuestAddress(1 << 32);
// Our 32-bit memory gap starts at 3G.
pub const MEM_32BIT_GAP_START: GuestAddress = GuestAddress(0xc000_0000);
// Our 32-bit memory gap size is 1GB.
const MEM_32BIT_GAP_SIZE: GuestUsize = (1024 << 20);
// We reserve 768MB in our memory gap for 32-bit devices (e.g. 32-bit PCI BARs).
const MEM_32BIT_DEVICES_GAP_SIZE: GuestUsize = (768 << 20);
/// Returns a Vec of the valid memory addresses.
/// These should be used to configure the GuestMemory structure for the platform.
/// For x86_64 all addresses are valid from the start of the kernel except a
/// carve out at the end of 32bit address space.
pub fn arch_memory_regions(size: GuestUsize) -> Vec<(GuestAddress, usize, RegionType)> {
let reserved_memory_gap_start = MEM_32BIT_GAP_START
.checked_add(MEM_32BIT_DEVICES_GAP_SIZE)
let reserved_memory_gap_start = layout::MEM_32BIT_RESERVED_START
.checked_add(layout::MEM_32BIT_DEVICES_SIZE)
.expect("32-bit reserved region is too large");
let requested_memory_size = GuestAddress(size as u64);
let mut regions = Vec::new();
// case1: guest memory fits before the gap
if size as u64 <= MEM_32BIT_GAP_START.raw_value() {
if size as u64 <= layout::MEM_32BIT_RESERVED_START.raw_value() {
regions.push((GuestAddress(0), size as usize, RegionType::Ram));
// case2: guest memory extends beyond the gap
} else {
// push memory before the gap
regions.push((
GuestAddress(0),
MEM_32BIT_GAP_START.raw_value() as usize,
layout::MEM_32BIT_RESERVED_START.raw_value() as usize,
RegionType::Ram,
));
regions.push((
FIRST_ADDR_PAST_32BITS,
requested_memory_size.unchecked_offset_from(MEM_32BIT_GAP_START) as usize,
layout::RAM_64BIT_START,
requested_memory_size.unchecked_offset_from(layout::MEM_32BIT_RESERVED_START) as usize,
RegionType::Ram,
));
}
// Add the 32-bit device memory hole as a sub region.
regions.push((
MEM_32BIT_GAP_START,
MEM_32BIT_DEVICES_GAP_SIZE as usize,
layout::MEM_32BIT_RESERVED_START,
layout::MEM_32BIT_DEVICES_SIZE as usize,
RegionType::SubRegion,
));
// Add the 32-bit reserved memory hole as a sub region.
regions.push((
reserved_memory_gap_start,
(MEM_32BIT_GAP_SIZE - MEM_32BIT_DEVICES_GAP_SIZE) as usize,
(layout::MEM_32BIT_RESERVED_SIZE - layout::MEM_32BIT_DEVICES_SIZE) as usize,
RegionType::Reserved,
));
regions
}
/// X86 specific memory hole/memory mapped devices/reserved area.
pub fn get_32bit_gap_start() -> GuestAddress {
FIRST_ADDR_PAST_32BITS
.checked_sub(MEM_32BIT_GAP_SIZE)
.expect("32-bit hole is too large")
}
/// Configures the system and should be called once per vm before starting vcpu threads.
///
/// # Arguments
@@ -121,30 +104,29 @@ pub fn get_32bit_gap_start() -> GuestAddress {
/// * `cmdline_addr` - Address in `guest_mem` where the kernel command line was loaded.
/// * `cmdline_size` - Size of the kernel command line in bytes including the null terminator.
/// * `num_cpus` - Number of virtual CPUs the guest will have.
#[allow(clippy::too_many_arguments)]
pub fn configure_system(
guest_mem: &GuestMemoryMmap,
cmdline_addr: GuestAddress,
cmdline_size: usize,
num_cpus: u8,
setup_hdr: Option<setup_header>,
serial_enabled: bool,
_serial_enabled: bool,
_end_of_range: GuestAddress,
_virt_iommu: Option<(u32, &[u32])>,
) -> super::Result<()> {
const KERNEL_BOOT_FLAG_MAGIC: u16 = 0xaa55;
const KERNEL_HDR_MAGIC: u32 = 0x53726448;
const KERNEL_LOADER_OTHER: u8 = 0xff;
const KERNEL_MIN_ALIGNMENT_BYTES: u32 = 0x1000000; // Must be non-zero.
let first_addr_past_32bits = FIRST_ADDR_PAST_32BITS;
let end_32bit_gap_start = get_32bit_gap_start();
let himem_start = super::HIMEM_START;
// Note that this puts the mptable at the last 1k of Linux's 640k base RAM
mptable::setup_mptable(guest_mem, num_cpus).map_err(Error::MpTableSetup)?;
let mut params: BootParamsWrapper = BootParamsWrapper(boot_params::default());
if setup_hdr.is_some() {
params.0.hdr = setup_hdr.unwrap();
if let Some(hdr) = setup_hdr {
params.0.hdr = hdr;
params.0.hdr.cmd_line_ptr = cmdline_addr.raw_value() as u32;
params.0.hdr.cmdline_size = cmdline_size as u32;
} else {
@@ -156,36 +138,55 @@ pub fn configure_system(
params.0.hdr.kernel_alignment = KERNEL_MIN_ALIGNMENT_BYTES;
};
add_e820_entry(&mut params.0, 0, 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();
if mem_end < end_32bit_gap_start {
if mem_end < layout::MEM_32BIT_RESERVED_START {
add_e820_entry(
&mut params.0,
himem_start.raw_value(),
mem_end.unchecked_offset_from(himem_start),
layout::HIGH_RAM_START.raw_value(),
mem_end.unchecked_offset_from(layout::HIGH_RAM_START) + 1,
E820_RAM,
)?;
} else {
add_e820_entry(
&mut params.0,
himem_start.raw_value(),
end_32bit_gap_start.unchecked_offset_from(himem_start),
layout::HIGH_RAM_START.raw_value(),
layout::MEM_32BIT_RESERVED_START.unchecked_offset_from(layout::HIGH_RAM_START),
E820_RAM,
)?;
if mem_end > first_addr_past_32bits {
if mem_end > layout::RAM_64BIT_START {
add_e820_entry(
&mut params.0,
first_addr_past_32bits.raw_value(),
mem_end.unchecked_offset_from(first_addr_past_32bits),
layout::RAM_64BIT_START.raw_value(),
mem_end.unchecked_offset_from(layout::RAM_64BIT_START) + 1,
E820_RAM,
)?;
}
}
add_e820_entry(
&mut params.0,
layout::PCI_MMCONFIG_START.0,
layout::PCI_MMCONFIG_SIZE,
E820_RESERVED,
)?;
#[cfg(feature = "acpi")]
{
let rsdp_addr = acpi::create_acpi_tables(guest_mem, num_cpus, serial_enabled);
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;
}
@@ -241,21 +242,20 @@ mod tests {
assert_eq!(GuestAddress(1 << 32), regions[1].0);
}
#[test]
fn test_32bit_gap() {
assert_eq!(
get_32bit_gap_start(),
FIRST_ADDR_PAST_32BITS
.checked_sub(MEM_32BIT_GAP_SIZE as u64)
.expect("32-bit hole is too large")
);
}
#[test]
fn test_system_configuration() {
let no_vcpus = 4;
let gm = GuestMemoryMmap::new(&vec![(GuestAddress(0), 0x10000)]).unwrap();
let config_err = configure_system(&gm, GuestAddress(0), 0, 1, None, false);
let config_err = configure_system(
&gm,
GuestAddress(0),
0,
1,
None,
false,
GuestAddress((1 << 36) - 1),
None,
);
assert!(config_err.is_err());
assert_eq!(
config_err.unwrap_err(),
@@ -273,7 +273,17 @@ mod tests {
.map(|r| (r.0, r.1))
.collect();
let gm = GuestMemoryMmap::new(&ram_regions).unwrap();
configure_system(&gm, GuestAddress(0), 0, no_vcpus, None, false).unwrap();
configure_system(
&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.
let mem_size = 3328 << 20;
@@ -284,7 +294,17 @@ mod tests {
.map(|r| (r.0, r.1))
.collect();
let gm = GuestMemoryMmap::new(&ram_regions).unwrap();
configure_system(&gm, GuestAddress(0), 0, no_vcpus, None, false).unwrap();
configure_system(
&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.
let mem_size = 3330 << 20;
@@ -295,7 +315,17 @@ mod tests {
.map(|r| (r.0, r.1))
.collect();
let gm = GuestMemoryMmap::new(&ram_regions).unwrap();
configure_system(&gm, GuestAddress(0), 0, no_vcpus, None, false).unwrap();
configure_system(
&gm,
GuestAddress(0),
0,
no_vcpus,
None,
false,
GuestAddress((1 << 36) - 1),
None,
)
.unwrap();
}
#[test]

View File

@@ -13,7 +13,8 @@ use std::slice;
use libc::c_char;
use arch_gen::x86::mpspec;
use vm_memory::{Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryMmap};
use layout::{APIC_START, IOAPIC_START, MPTABLE_START};
use vm_memory::{Address, ByteValued, Bytes, GuestMemory, GuestMemoryMmap};
// This is a workaround to the Rust enforcement specifying that any implementation of a foreign
// trait (in this case `ByteValued`) where:
@@ -44,9 +45,6 @@ unsafe impl ByteValued for MpcTableWrapper {}
unsafe impl ByteValued for MpcLintsrcWrapper {}
unsafe impl ByteValued for MpfIntelWrapper {}
// MPTABLE, describing VCPUS.
const MPTABLE_START: GuestAddress = GuestAddress(0x9fc00);
#[derive(Debug, PartialEq)]
pub enum Error {
/// There was too little guest memory to store the entire MP table.
@@ -92,8 +90,6 @@ const MPC_SPEC: i8 = 4;
const MPC_OEM: [c_char; 8] = char_array!(c_char; 'F', 'C', ' ', ' ', ' ', ' ', ' ', ' ');
const MPC_PRODUCT_ID: [c_char; 12] = ['0' as c_char; 12];
const BUS_TYPE_ISA: [u8; 6] = char_array!(u8; 'I', 'S', 'A', ' ', ' ', ' ');
pub const IO_APIC_DEFAULT_PHYS_BASE: u32 = 0xfec00000; // source: linux/arch/x86/include/asm/apicdef.h
pub const APIC_DEFAULT_PHYS_BASE: u32 = 0xfee00000; // source: linux/arch/x86/include/asm/apicdef.h
const APIC_VERSION: u8 = 0x14;
const CPU_STEPPING: u32 = 0x600;
const CPU_FEATURE_APIC: u32 = 0x200;
@@ -208,7 +204,7 @@ pub fn setup_mptable(mem: &GuestMemoryMmap, num_cpus: u8) -> Result<()> {
mpc_ioapic.0.apicid = ioapicid;
mpc_ioapic.0.apicver = APIC_VERSION;
mpc_ioapic.0.flags = mpspec::MPC_APIC_USABLE as u8;
mpc_ioapic.0.apicaddr = IO_APIC_DEFAULT_PHYS_BASE;
mpc_ioapic.0.apicaddr = IOAPIC_START.0 as u32;
mem.write_obj(mpc_ioapic, base_mp)
.map_err(|_| Error::WriteMpcIoapic)?;
base_mp = base_mp.unchecked_add(size as u64);
@@ -271,7 +267,7 @@ pub fn setup_mptable(mem: &GuestMemoryMmap, num_cpus: u8) -> Result<()> {
mpc_table.0.spec = MPC_SPEC;
mpc_table.0.oem = MPC_OEM;
mpc_table.0.productid = MPC_PRODUCT_ID;
mpc_table.0.lapic = APIC_DEFAULT_PHYS_BASE;
mpc_table.0.lapic = APIC_START.0 as u32;
checksum = checksum.wrapping_add(compute_checksum(&mpc_table.0));
mpc_table.0.checksum = (!checksum).wrapping_add(1) as i8;
mem.write_obj(mpc_table, table_base)
@@ -284,7 +280,7 @@ pub fn setup_mptable(mem: &GuestMemoryMmap, num_cpus: u8) -> Result<()> {
#[cfg(test)]
mod tests {
use super::*;
use vm_memory::GuestUsize;
use vm_memory::{GuestAddress, GuestUsize};
fn table_entry_size(type_: u8) -> usize {
match type_ as u32 {

View File

@@ -11,12 +11,8 @@ use super::gdt::{gdt_entry, kvm_segment_from_gdt};
use arch_gen::x86::msr_index;
use kvm_bindings::{kvm_fpu, kvm_msr_entry, kvm_msrs, kvm_regs, kvm_sregs};
use kvm_ioctls::VcpuFd;
use vm_memory::{Address, Bytes, GuestAddress, GuestMemory, GuestMemoryMmap};
// Initial pagetables.
const PML4_START: GuestAddress = GuestAddress(0x9000);
const PDPTE_START: GuestAddress = GuestAddress(0xa000);
const PDE_START: GuestAddress = GuestAddress(0xb000);
use layout::{BOOT_GDT_START, BOOT_IDT_START, PDE_START, PDPTE_START, PML4_START};
use vm_memory::{Address, Bytes, GuestMemory, GuestMemoryMmap};
// MTRR constants
const MTRR_ENABLE: u64 = 0x800; // IA32_MTRR_DEF_TYPE MSR: E (MTRRs enabled) flag, bit 11
@@ -127,9 +123,6 @@ pub fn setup_sregs(mem: &GuestMemoryMmap, vcpu: &VcpuFd) -> Result<()> {
vcpu.set_sregs(&sregs).map_err(Error::SetStatusRegisters)
}
const BOOT_GDT_OFFSET: GuestAddress = GuestAddress(0x500);
const BOOT_IDT_OFFSET: GuestAddress = GuestAddress(0x520);
const BOOT_GDT_MAX: usize = 4;
const EFER_LMA: u64 = 0x400;
@@ -140,7 +133,7 @@ const X86_CR0_PG: u64 = 0x80000000;
const X86_CR4_PAE: u64 = 0x20;
fn write_gdt_table(table: &[u64], guest_mem: &GuestMemoryMmap) -> Result<()> {
let boot_gdt_addr = BOOT_GDT_OFFSET;
let boot_gdt_addr = BOOT_GDT_START;
for (index, entry) in table.iter().enumerate() {
let addr = guest_mem
.checked_offset(boot_gdt_addr, index * mem::size_of::<u64>())
@@ -153,7 +146,7 @@ fn write_gdt_table(table: &[u64], guest_mem: &GuestMemoryMmap) -> Result<()> {
}
fn write_idt_value(val: u64, guest_mem: &GuestMemoryMmap) -> Result<()> {
let boot_idt_addr = BOOT_IDT_OFFSET;
let boot_idt_addr = BOOT_IDT_START;
guest_mem
.write_obj(val, boot_idt_addr)
.map_err(|_| Error::WriteIDT)
@@ -173,11 +166,11 @@ fn configure_segments_and_sregs(mem: &GuestMemoryMmap, sregs: &mut kvm_sregs) ->
// Write segments
write_gdt_table(&gdt_table[..], mem)?;
sregs.gdt.base = BOOT_GDT_OFFSET.raw_value();
sregs.gdt.base = BOOT_GDT_START.raw_value();
sregs.gdt.limit = mem::size_of_val(&gdt_table) as u16 - 1;
write_idt_value(0, mem)?;
sregs.idt.base = BOOT_IDT_OFFSET.raw_value();
sregs.idt.base = BOOT_IDT_START.raw_value();
sregs.idt.limit = mem::size_of::<u64>() as u16 - 1;
sregs.cs = code_seg;
@@ -305,20 +298,20 @@ mod tests {
let gm = create_guest_mem();
configure_segments_and_sregs(&gm, &mut sregs).unwrap();
assert_eq!(0x0, read_u64(&gm, BOOT_GDT_OFFSET));
assert_eq!(0x0, read_u64(&gm, BOOT_GDT_START));
assert_eq!(
0xaf9b000000ffff,
read_u64(&gm, BOOT_GDT_OFFSET.unchecked_add(8))
read_u64(&gm, BOOT_GDT_START.unchecked_add(8))
);
assert_eq!(
0xcf93000000ffff,
read_u64(&gm, BOOT_GDT_OFFSET.unchecked_add(16))
read_u64(&gm, BOOT_GDT_START.unchecked_add(16))
);
assert_eq!(
0x8f8b000000ffff,
read_u64(&gm, BOOT_GDT_OFFSET.unchecked_add(24))
read_u64(&gm, BOOT_GDT_START.unchecked_add(24))
);
assert_eq!(0x0, read_u64(&gm, BOOT_IDT_OFFSET));
assert_eq!(0x0, read_u64(&gm, BOOT_IDT_START));
assert_eq!(0, sregs.cs.base);
assert_eq!(0xfffff, sregs.ds.limit);

View File

@@ -11,11 +11,12 @@ kvm-ioctls = { git = "https://github.com/rust-vmm/kvm-ioctls", branch = "master"
libc = "0.2.60"
log = "0.4.8"
vm-memory = { git = "https://github.com/rust-vmm/vm-memory" }
vmm-sys-util = { git = "https://github.com/rust-vmm/vmm-sys-util" }
vmm-sys-util = "0.1.1"
[dev-dependencies]
tempfile = "3.1.0"
[features]
default = ["acpi"]
acpi = []
default = []
acpi = []
cmos = []

View File

@@ -15,6 +15,7 @@ use kvm_bindings::kvm_msi;
use kvm_ioctls::VmFd;
use std::sync::Arc;
use std::{io, result};
use vm_memory::GuestAddress;
#[derive(Debug)]
pub enum Error {
@@ -156,6 +157,7 @@ pub struct Ioapic {
reg_sel: u32,
reg_entries: [RedirectionTableEntry; NUM_IOAPIC_PINS],
vm_fd: Arc<VmFd>,
apic_address: GuestAddress,
}
impl BusDevice for Ioapic {
@@ -194,12 +196,13 @@ impl BusDevice for Ioapic {
}
impl Ioapic {
pub fn new(vm_fd: Arc<VmFd>) -> Ioapic {
pub fn new(vm_fd: Arc<VmFd>, apic_address: GuestAddress) -> Ioapic {
Ioapic {
id: 0,
reg_sel: 0,
reg_entries: [0; NUM_IOAPIC_PINS],
vm_fd,
apic_address,
}
}
@@ -239,7 +242,7 @@ impl Ioapic {
let redirection_hint: u8 = 1;
// Generate MSI message address
let address_lo: u32 = 0xfee0_0000
let address_lo: u32 = self.apic_address.0 as u32
| u32::from(destination_id) << 12
| u32::from(redirection_hint) << 3
| u32::from(destination_mode) << 2;

115
devices/src/legacy/cmos.rs Normal file
View File

@@ -0,0 +1,115 @@
// Copyright 2017 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use libc::{gmtime_r, time, time_t, tm};
use std::cmp::min;
use std::mem;
use crate::BusDevice;
const INDEX_MASK: u8 = 0x7f;
const INDEX_OFFSET: u64 = 0x0;
const DATA_OFFSET: u64 = 0x1;
const DATA_LEN: usize = 128;
/// A CMOS/RTC device commonly seen on x86 I/O port 0x70/0x71.
pub struct Cmos {
index: u8,
data: [u8; DATA_LEN],
}
impl Cmos {
/// Constructs a CMOS/RTC device with initial data.
/// `mem_below_4g` is the size of memory in bytes below the 32-bit gap.
/// `mem_above_4g` is the size of memory in bytes above the 32-bit gap.
pub fn new(mem_below_4g: u64, mem_above_4g: u64) -> Cmos {
let mut data = [0u8; DATA_LEN];
// Extended memory from 16 MB to 4 GB in units of 64 KB
let ext_mem = min(
0xFFFF,
mem_below_4g.saturating_sub(16 * 1024 * 1024) / (64 * 1024),
);
data[0x34] = ext_mem as u8;
data[0x35] = (ext_mem >> 8) as u8;
// High memory (> 4GB) in units of 64 KB
let high_mem = min(0x00FF_FFFF, mem_above_4g / (64 * 1024));
data[0x5b] = high_mem as u8;
data[0x5c] = (high_mem >> 8) as u8;
data[0x5d] = (high_mem >> 16) as u8;
Cmos { index: 0, data }
}
}
impl BusDevice for Cmos {
fn write(&mut self, _base: u64, offset: u64, data: &[u8]) {
if data.len() != 1 {
return;
}
match offset {
INDEX_OFFSET => self.index = data[0] & INDEX_MASK,
DATA_OFFSET => self.data[self.index as usize] = data[0],
o => panic!("bad write offset on CMOS device: {}", o),
}
}
fn read(&mut self, _base: u64, offset: u64, data: &mut [u8]) {
fn to_bcd(v: u8) -> u8 {
assert!(v < 100);
((v / 10) << 4) | (v % 10)
}
if data.len() != 1 {
return;
}
data[0] = match offset {
INDEX_OFFSET => self.index,
DATA_OFFSET => {
let seconds;
let minutes;
let hours;
let week_day;
let day;
let month;
let year;
// The time and gmtime_r calls are safe as long as the structs they are given are
// large enough, and neither of them fail. It is safe to zero initialize the tm
// struct because it contains only plain data.
unsafe {
let mut tm: tm = mem::zeroed();
let mut now: time_t = 0;
time(&mut now as *mut _);
gmtime_r(&now, &mut tm as *mut _);
// The following lines of code are safe but depend on tm being in scope.
seconds = tm.tm_sec;
minutes = tm.tm_min;
hours = tm.tm_hour;
week_day = tm.tm_wday + 1;
day = tm.tm_mday;
month = tm.tm_mon + 1;
year = tm.tm_year;
};
match self.index {
0x00 => to_bcd(seconds as u8),
0x02 => to_bcd(minutes as u8),
0x04 => to_bcd(hours as u8),
0x06 => to_bcd(week_day as u8),
0x07 => to_bcd(day as u8),
0x08 => to_bcd(month as u8),
0x09 => to_bcd((year % 100) as u8),
0x32 => to_bcd(((year + 1900) / 100) as u8),
_ => {
// self.index is always guaranteed to be in range via INDEX_MASK.
self.data[(self.index & INDEX_MASK) as usize]
}
}
}
o => panic!("bad read offset on CMOS device: {}", o),
}
}
}

View File

@@ -5,8 +5,12 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE-BSD-3-Clause file.
#[cfg(feature = "cmos")]
mod cmos;
mod i8042;
mod serial;
#[cfg(feature = "cmos")]
pub use self::cmos::Cmos;
pub use self::i8042::I8042Device;
pub use self::serial::Serial;

View File

@@ -67,6 +67,6 @@ pub enum Error {
IoError(io::Error),
}
pub trait Interrupt: Send {
pub trait Interrupt: Send + Sync {
fn deliver(&self) -> result::Result<(), std::io::Error>;
}

96
docs/custom-image.md Normal file
View File

@@ -0,0 +1,96 @@
# How to create a custom Clear Linux image
In the context of adding more utility to the cloudguest image being used
for integration testing, this is a quick guide on how to achieve the creation
of your own Clear Linux image using the official Clear Linux tooling.
## Prepare the environment
From the host, the goal is run a Clear Linux VM that will allow us to build
the custom image we want.
```bash
# Get latest CL version:
IMG_VERSION=$(curl https://download.clearlinux.org/latest)
# Get latest clear-kvm image:
wget -P $HOME/workloads/ https://download.clearlinux.org/current/clear-${IMG_VERSION}-kvm.img.xz
# Extract the image
unxz $HOME/workloads/clear-${IMG_VERSION}-kvm.img.xz
# Make sure cloud-hypervisor binary has CAP_NET_ADMIN capability set
sudo setcap cap_net_admin+ep cloud-hypervisor
# Boot cloud-hypervisor VM with the downloaded image
./cloud-hypervisor -v --kernel $HOME/workloads/vmlinux --disk path=clear-${IMG_VERSION}-kvm.img --cmdline "console=ttyS0 console=hvc0 reboot=k panic=1 nomodules root=/dev/vda3 rw" --cpus 1 --memory size=4G --net tap=,mac=
# Setup connectivity
# First make sure to enable IP forwarding (disabled on Linux by default)
sudo bash -c "echo 1 > /proc/sys/net/ipv4/ip_forward"
# Retrieve the interface name and the gateway IP
IFACE=$(ip route | grep default | awk -F 'dev' '{print $2}' | awk -F ' ' '{print $1}')
GW=$(ip route | grep vmtap0 | awk -F ' ' '{print $1}')
# Create a new masquerade rule to tag the packets going out
sudo iptables -t nat -A POSTROUTING -s ${GW} -o ${IFACE} -j MASQUERADE
```
## Create the image
From the guest, we can now create the image.
```bash
# Setup connectivity
sudo ip addr add 192.168.249.2/24 dev enp0s3
sudo ip route add default via 192.168.249.1
# Install necessary bundles
sudo swupd bundle-add clr-installer
sudo swupd bundle-add os-installer
# Download and update cloudguest image configuration
wget https://download.clearlinux.org/current/config/image/cloudguest.yaml
sed -i '/size: \"864M\"/d' cloudguest.yaml
sed -i 's/\"800M\"/\"2G\"/g' cloudguest.yaml
sed -i 's/bootloader,/bootloader,\n iperf,/g' cloudguest.yaml
sed -i 's/systemd-networkd-autostart/sysadmin-basic,\n systemd-networkd-autostart/g' cloudguest.yaml
# Create the custom cloudguest image
clr-installer -c cloudguest.yaml
# Make the guest accessible through ssh
sudo mkdir -p /etc/ssh
sudo bash -c "echo 'PermitRootLogin yes' >> /etc/ssh/sshd_config"
```
### Retrieve the image
Once the new image has been created and the guest is accessible through
`ssh`, it is time to retrieve the image from the host.
```bash
# Retrieve new image (this is a raw image)
scp root@192.168.249.2:cloudguest.img .
mv cloudguest.img clear-cloudguest-raw.img
# Create the QCOW image from the RAW image
qemu-img convert -p -f raw -O qcow2 clear-cloudguest-raw.img clear-cloudguest.img
# Compress the QCOW image
xz -k -T $(nproc) clear-cloudguest.img
```
## Switch CI to use the new image
### Upload to Azure storage
The next step is to update the image stored as part of the Azure storage
account, replacing it with the newly created image. This will make this
new image available from the integration tests.
This is usually achieved through the web interface.
### Update integration tests
Last step is about updating the integration tests to work with this new image.
The key point is to identify the UUID of this new image so that it can be used
directly from the tests.
Proceed as follow to determine this UUID:
```bash
# Mount the image
sudo mount -o loop,offset=$((2048 * 512)) clear-cloudguest-raw.img /mnt/
# Identify UUID
sudo cat /mnt/loader/entries/Clear-linux-kvm-*.conf | grep "root=PARTUUID="
# Unmount the image
sudo umount /mnt
```

View File

@@ -43,7 +43,7 @@ to easily grep for the tracing logs (e.g.
```
./target/debug/cloud-hypervisor \
--kernel ~/rust-hypervisor-firmware/target/target/release/hypervisor-fw \
--disk ~/hypervisor/images/clear-30080-kvm.img \
--disk path=~/hypervisor/images/clear-30080-kvm.img \
--cpus 4 \
--memory size=1024M \
--rng \

View File

@@ -53,7 +53,7 @@ Assuming you have `clear-kvm.img` and `custom-vmlinux.bin` on your system, here
./cloud-hypervisor \
--cpus 4 \
--memory "size=512,file=/dev/shm" \
--disk clear-kvm.img \
--disk path=clear-kvm.img \
--kernel custom-vmlinux.bin \
--cmdline "console=ttyS0 reboot=k panic=1 nomodules root=/dev/vda3" \
--fs tag=virtiofs,sock=/tmp/virtiofs,num_queues=1,queue_size=512

209
docs/iommu.md Normal file
View File

@@ -0,0 +1,209 @@
# Virtual IOMMU
## Rationales
Having the possibility to expose a virtual IOMMU to the guest can be
interesting to support specific use cases. That being said, it is always
important to keep in mind a virtual IOMMU can impact the performance of the
attached devices, which is the reason why one should be careful when enabling
this feature.
### Protect nested virtual machines
The first reason why one might want to expose a virtual IOMMU to the guest is
to increase the security regarding the memory accesses performed by the virtual
devices (VIRTIO devices), on behalf of the guest drivers.
With a virtual IOMMU, the VMM stands between the guest driver and its device
counterpart, validating and translating every address before to try accessing
the guest memory. This is standard interposition that is performed here by the
VMM.
The increased security does not apply for a simple case where we have one VM
per VMM. Because the guest cannot be trusted, as we always consider it could
be malicious and gain unauthorized privileges inside the VM, preventing some
devices from accessing the entire guest memory is pointless.
But let's take the interesting case of nested virtualization, and let's assume
we have a VMM running a first layer VM. This L1 guest is fully trusted as the
user intends to run multiple VMs from this L1. We can end up with multiple L2
VMs running on a single L1 VM. In this particular case, and without exposing a
virtual IOMMU to the L1 guest, it would be possible for any L2 guest to use the
device implementation from the host VMM to access the entire guest L1 memory.
The virtual IOMMU prevents from this kind of trouble as it will validate the
addresses the device is authorized to access.
### Achieve VFIO nested
Another reason for having a virtual IOMMU is to allow passing physical devices
from the host through multiple layers of virtualization. Let's take as example
a system with a physical IOMMU running a VM with a virtual IOMMU. The
implementation of the virtual IOMMU is responsible for updating the physical
DMA Remapping table (DMAR) everytime the DMA mapping changes. This must happen
through the VFIO framework on the host as this is the only userspace interface
to interact with a physical IOMMU.
Relying on this update mechanism, it is possible to attach physical devices to
the virtual IOMMU, which allows these devices to be passed from L1 to another
layer of virtualization.
## Why virtio-iommu?
The Cloud Hypervisor project decided to implement the brand new virtio-iommu
device in order to provide a virtual IOMMU to its users. The reason being the
simplicity brought by the paravirtualization solution. By having one side
handled from the guest itself, it removes the complexity of trapping memory
page accesses and shadowing them. This is why the project will not try to
implement a full emulation of a physical IOMMU.
## Pre-requisites
### Kernel
Since virtio-iommu has landed partially into the version 5.3 of the Linux
kernel, a special branch is needed to get things working with Cloud Hypervisor.
By partially, we are talking about x86 specifically, as it is already fully
functional for ARM architectures.
## Usage
In order to expose a virtual IOMMU to the guest, it is required to create a
virtio-iommu device and expose it through the ACPI IORT table. This can be
simply achieved by attaching at least one device to the virtual IOMMU.
The way to expose to the guest a specific device as sitting behind this IOMMU
is to explicitly tag it from the command line with the option `iommu=on`.
Not all devices support this extra option, and the default value will always
be `off` since we want to avoid the performance impact for most users who don't
need this.
Refer to the command line `--help` to find out which device support to be
attached to the virtual IOMMU.
Below is a simple example exposing the `virtio-blk` device as attached to the
virtual IOMMU:
```bash
./cloud-hypervisor \
--cpus 1 \
--memory size=512M \
--disk path=clear-kvm.img,iommu=on \
--kernel custom-bzImage \
--cmdline "console=ttyS0 root=/dev/vda3" \
```
From a guest perspective, it is easy to verify if the device is protected by
the virtual IOMMU. Check the directories listed under
`/sys/kernel/iommu_groups`:
```bash
ls /sys/kernel/iommu_groups
0
```
In this case, only one IOMMU group should be created. Under this group, it is
possible to find out the b/d/f of the device(s) part of this group.
```bash
ls /sys/kernel/iommu_groups/0/devices/
0000:00:03.0
```
And you can validate the device is the one we expect running `lspci`:
```bash
lspci
00:00.0 Host bridge: Intel Corporation Device 0d57
00:01.0 Unassigned class [ffff]: Red Hat, Inc. Device 1057
00:02.0 Unassigned class [ffff]: Red Hat, Inc. Virtio console
00:03.0 Mass storage controller: Red Hat, Inc. Virtio block device
00:04.0 Unassigned class [ffff]: Red Hat, Inc. Virtio RNG
```
## Faster mappings
By default, the guest memory is mapped with 4k pages and no huge pages, which
causes the virtual IOMMU device to be asked for 4k mappings only. This
configuration slows down the setup of the physical IOMMU as an important number
of requests need to be issued in order to create large mappings.
One use case is even more impacted by the slowdown, the nested VFIO case. When
passing a device through a L2 guest, the VFIO driver running in L1 will update
the DMAR entries for the specific device. Because VFIO pins the entire guest
memory, this means the entire mapping of the L2 guest need to be stored into
multiple 4k mappings. Obviously, the bigger the L2 guest RAM is, the longer the
update of the mappings will last. There is an additional problem happening in
this case, if the L2 guest RAM is quite large, it will require a large number
of mappings, which might exceed the VFIO limit set on the host. The default
value is 65536, which can simply be reached with a 256MiB sized RAM.
The way to solve both problems, the slowdown and the limit being exceeded, is
to reduce the amount of requests to describe those same large mappings. This
can be achieved by using 2MiB pages, known as huge pages. By seeing the guest
RAM as larger pages, and because the virtual IOMMU device supports it, the
guest will require less mappings, which will prevent the limit from being
exceeded, but also will take less time to process them on the host. That's
how using huge pages as much as possible can speed up VM boot time.
### Basic usage
Let's look at an example of how to run a guest with huge pages.
First, make sure your system has enough pages to cover the entire guest RAM:
```bash
# This example creates 4096 hugepages
echo 4096 > /proc/sys/vm/nr_hugepages
```
Next step is simply to create the VM. Two things are important, first we want
the VM RAM to be mapped on huge pages by backing it with `/dev/hugepages`. And
second thing, we need to create some huge pages in the guest itself so they can
be consumed.
```bash
./cloud-hypervisor \
--cpus 1 \
--memory size=8G,file=/dev/hugepages \
--disk path=clear-kvm.img \
--kernel custom-bzImage \
--cmdline "console=ttyS0 root=/dev/vda3 hugepagesz=2M hugepages=2048" \
--net tap=,mac=,iommu=on
```
### Nested usage
Let's now look at the specific example of nested virtualization. In order to
reach optimized performances, the L2 guest also need to be mapped based on
huge pages. Here is how to achieve this, assuming the physical device you are
passing through is `0000:00:01.0`.
```bash
./cloud-hypervisor \
--cpus 1 \
--memory size=8G,file=/dev/hugepages \
--disk path=clear-kvm.img \
--kernel custom-bzImage \
--cmdline "console=ttyS0 root=/dev/vda3 kvm-intel.nested=1 vfio_iommu_type1.allow_unsafe_interrupts rw hugepagesz=2M hugepages=2048" \
--device path=/sys/bus/pci/devices/0000:00:01.0,iommu=on
```
Once the L1 VM is running, unbind the device from the default driver in the
guest, and bind it to VFIO (it should appear as `0000:00:04.0`).
```bash
echo 0000:00:04.0 > /sys/bus/pci/devices/0000\:00\:04.0/driver/unbind
echo 8086 1502 > /sys/bus/pci/drivers/vfio-pci/new_id
```
Last thing is to start the L2 guest with the huge pages memory backend.
```bash
./cloud-hypervisor \
--cpus 1 \
--memory size=4G,file=/dev/hugepages \
--disk path=clear-kvm.img \
--kernel custom-bzImage \
--cmdline "console=ttyS0 root=/dev/vda3" \
--device path=/sys/bus/pci/devices/0000:00:04.0
```

View File

@@ -10,7 +10,7 @@ Use one `--net` command-line argument from cloud-hypervisor to specify the emula
./cloud-hypervisor \
--cpus 4 \
--memory "size=512M" \
--disk my-root-disk.img \
--disk path=my-root-disk.img \
--kernel my-vmlinux.bin \
--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 \

View File

@@ -66,13 +66,13 @@ takes the device's sysfs path as an argument. In our example it is
```
./target/debug/cloud-hypervisor \
--kernel ~/vmlinux \
--disk ~/clear-29160-kvm.img \
--disk path=~/clear-29160-kvm.img \
--console off \
--serial tty \
--cmdline "console=ttyS0 reboot=k panic=1 nomodules i8042.noaux i8042.nomux i8042.nopnp i8042.dumbkbd root=/dev/vda3" \
--cpus 4 \
--memory size=512M \
--device /sys/bus/pci/devices/0000:01:00.0/
--device path=/sys/bus/pci/devices/0000:01:00.0/
```
The guest kernel will then detect the card reader on its PCI bus and provided

View File

@@ -4,4 +4,4 @@ version = "0.1.0"
authors = ["The Chromium OS Authors"]
[dependencies]
vmm-sys-util = { git = "https://github.com/rust-vmm/vmm-sys-util" }
vmm-sys-util = "0.1.1"

View File

@@ -7,9 +7,9 @@ authors = ["The Chromium OS Authors"]
libc = "0.2.60"
rand = "0.7.0"
serde = "1.0.98"
vmm-sys-util = "0.1.1"
net_gen = { path = "../net_gen" }
vmm-sys-util = { git = "https://github.com/rust-vmm/vmm-sys-util" }
[dev-dependencies]
lazy_static = "1.3.0"

View File

@@ -6,6 +6,7 @@
// found in the THIRD-PARTY file.
use rand::Rng;
use std::fmt;
use std::result::Result;
use serde::de::{Deserialize, Deserializer, Error};
@@ -66,14 +67,6 @@ impl MacAddr {
&self.bytes
}
pub fn to_string(self) -> String {
let b = &self.bytes;
format!(
"{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
b[0], b[1], b[2], b[3], b[4], b[5]
)
}
pub fn local_random() -> MacAddr {
// Generate a fully random MAC
let mut random_bytes = rand::thread_rng().gen::<[u8; MAC_ADDR_LEN]>();
@@ -87,6 +80,17 @@ impl MacAddr {
}
}
impl fmt::Display for MacAddr {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let b = &self.bytes;
write!(
f,
"{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
b[0], b[1], b[2], b[3], b[4], b[5]
)
}
}
impl Serialize for MacAddr {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where

View File

@@ -49,6 +49,15 @@ impl PartialEq for Tap {
}
}
impl std::clone::Clone for Tap {
fn clone(&self) -> Self {
Tap {
tap_file: self.tap_file.try_clone().unwrap(),
if_name: self.if_name,
}
}
}
// Returns a byte vector representing the contents of a null terminated C string which
// contains if_name.
fn build_terminated_if_name(if_name: &str) -> Result<Vec<u8>> {

View File

@@ -13,4 +13,4 @@ kvm-ioctls = { git = "https://github.com/rust-vmm/kvm-ioctls", branch = "master"
libc = "0.2.60"
log = "0.4.8"
vm-memory = { git = "https://github.com/rust-vmm/vm-memory" }
vmm-sys-util = { git = "https://github.com/rust-vmm/vmm-sys-util" }
vmm-sys-util = "0.1.1"

View File

@@ -71,23 +71,19 @@ impl PciDevice for PciRoot {
}
}
pub struct PciConfigIo {
pub struct PciBus {
/// Devices attached to this bus.
/// Device 0 is host bridge.
devices: Vec<Arc<Mutex<dyn PciDevice>>>,
/// Config space register.
config_address: u32,
}
impl PciConfigIo {
impl PciBus {
pub fn new(pci_root: PciRoot) -> Self {
let mut devices: Vec<Arc<Mutex<dyn PciDevice>>> = Vec::new();
devices.push(Arc::new(Mutex::new(pci_root)));
PciConfigIo {
devices,
config_address: 0,
}
PciBus { devices }
}
pub fn register_mapping(
@@ -119,6 +115,25 @@ impl PciConfigIo {
Ok(())
}
pub fn next_device_id(&self) -> u32 {
self.devices.len() as u32
}
}
pub struct PciConfigIo {
/// Config space register.
config_address: u32,
pci_bus: Arc<Mutex<PciBus>>,
}
impl PciConfigIo {
pub fn new(pci_bus: Arc<Mutex<PciBus>>) -> Self {
PciConfigIo {
pci_bus,
config_address: 0,
}
}
pub fn config_space_read(&self) -> u32 {
let enabled = (self.config_address & 0x8000_0000) != 0;
if !enabled {
@@ -138,9 +153,14 @@ impl PciConfigIo {
return 0xffff_ffff;
}
self.devices.get(device).map_or(0xffff_ffff, |d| {
d.lock().unwrap().read_config_register(register)
})
self.pci_bus
.lock()
.unwrap()
.devices
.get(device)
.map_or(0xffff_ffff, |d| {
d.lock().unwrap().read_config_register(register)
})
}
pub fn config_space_write(&mut self, offset: u64, data: &[u8]) {
@@ -161,7 +181,7 @@ impl PciConfigIo {
return;
}
if let Some(d) = self.devices.get(device) {
if let Some(d) = self.pci_bus.lock().unwrap().devices.get(device) {
d.lock()
.unwrap()
.write_config_register(register, offset, data);
@@ -223,17 +243,12 @@ impl BusDevice for PciConfigIo {
/// Emulates PCI memory-mapped configuration access mechanism.
pub struct PciConfigMmio {
/// Devices attached to this bus.
/// Device 0 is host bridge.
devices: Vec<Arc<Mutex<dyn PciDevice>>>,
pci_bus: Arc<Mutex<PciBus>>,
}
impl PciConfigMmio {
pub fn new(pci_root: PciRoot) -> Self {
let mut devices: Vec<Arc<Mutex<dyn PciDevice>>> = Vec::new();
devices.push(Arc::new(Mutex::new(pci_root)));
PciConfigMmio { devices }
pub fn new(pci_bus: Arc<Mutex<PciBus>>) -> Self {
PciConfigMmio { pci_bus }
}
fn config_space_read(&self, config_address: u32) -> u32 {
@@ -244,9 +259,14 @@ impl PciConfigMmio {
return 0xffff_ffff;
}
self.devices.get(device).map_or(0xffff_ffff, |d| {
d.lock().unwrap().read_config_register(register)
})
self.pci_bus
.lock()
.unwrap()
.devices
.get(device)
.map_or(0xffff_ffff, |d| {
d.lock().unwrap().read_config_register(register)
})
}
fn config_space_write(&mut self, config_address: u32, offset: u64, data: &[u8]) {
@@ -261,7 +281,7 @@ impl PciConfigMmio {
return;
}
if let Some(d) = self.devices.get(device) {
if let Some(d) = self.pci_bus.lock().unwrap().devices.get(device) {
d.lock()
.unwrap()
.write_config_register(register, offset, data);

View File

@@ -16,7 +16,7 @@ mod device;
mod msi;
mod msix;
pub use self::bus::{PciConfigIo, PciConfigMmio, PciRoot, PciRootError};
pub use self::bus::{PciBus, PciConfigIo, PciConfigMmio, PciRoot, PciRootError};
pub use self::configuration::{
PciBarConfiguration, PciBarPrefetchable, PciBarRegionType, PciCapability, PciCapabilityID,
PciClassCode, PciConfiguration, PciHeaderType, PciMassStorageSubclass,

View File

@@ -13,7 +13,7 @@ byteorder = "1.3.2"
libc = "0.2.60"
log = "0.4.8"
remain = "0.1.3"
vmm-sys-util = { git = "https://github.com/rust-vmm/vmm-sys-util" }
vmm-sys-util = "0.1.1"
[dev-dependencies]
tempfile = "3.1.0"

View File

@@ -1,19 +1,98 @@
- [v0.3.0](#v030)
+ [Block device offloading](#block-device-offloading)
+ [Network device backend](#network-device-backend)
+ [Virtual sockets](#virtual-sockets)
+ [HTTP based API](#http-based-api)
+ [Memory mapped virtio transport](#memory-mapped-virtio-transport)
+ [Paravirtualized IOMMU](#paravirtualized-iommu)
+ [Ubuntu 19.10](#ubuntu-1910)
+ [Guest large memory](#guest-large-memory)
- [v0.2.0](#v020)
- [Network device offloading](#network-device-offloading)
- [Minimal hardware-reduced ACPI](#minimal-hardware-reduced-acpi)
- [Debug I/O port](#debug-io-port)
- [Improved direct device assignment](#improved-direct-device-assignment)
- [Improved shared filesystem](#improved-shared-filesystem)
- [Ubuntu bionic based CI](#ubuntu-bionic-based-ci)
+ [Network device offloading](#network-device-offloading)
+ [Minimal hardware-reduced ACPI](#minimal-hardware-reduced-acpi)
+ [Debug I/O port](#debug-i-o-port)
+ [Improved direct device assignment](#improved-direct-device-assignment)
+ [Improved shared filesystem](#improved-shared-filesystem)
+ [Ubuntu bionic based CI](#ubuntu-bionic-based-ci)
- [v0.1.0](#v010)
- [Shared filesystem](#shared-filesystem)
- [Initial direct device assignment support](#initial-direct-device-assignment-support)
- [Userspace IOAPIC](#userspace-ioapic)
- [Virtual persistent memory](#virtual-persistent-memory)
- [Linux kernel bzImage](#linux-kernel-bzimage)
- [Console over virtio](#console-over-virtio)
- [Unit testing](#unit-testing)
- [Integration tests parallelization](#integration-tests-parallelization)
+ [Shared filesystem](#shared-filesystem)
+ [Initial direct device assignment support](#initial-direct-device-assignment-support)
+ [Userspace IOAPIC](#userspace-ioapic)
+ [Virtual persistent memory](#virtual-persistent-memory)
+ [Linux kernel bzImage](#linux-kernel-bzimage)
+ [Console over virtio](#console-over-virtio)
+ [Unit testing](#unit-testing)
+ [Integration tests parallelization](#integration-tests-parallelization)
# v0.3.0
This release has been tracked through the [0.3.0 project](https://github.com/intel/cloud-hypervisor/projects/3).
Highlights for `cloud-hypervisor` version 0.3.0 include:
### Block device offloading
We continue to work on offloading paravirtualized I/O to external processes,
and we added support for
[vhost-user-blk](https://access.redhat.com/solutions/3394851) backends.
This enables `cloud-hypervisor` users to plug a `vhost-user` based block device
like [SPDK](https://spdk.io)) into the VMM as their paravirtualized storage
backend.
### Network device backend
The previous release provided support for
[vhost-user-net](https://access.redhat.com/solutions/3394851) backends. Now we
also provide a TAP based vhost-user-net backend, implemented in Rust. Together
with the vhost-user-net device implementation, this will eventually become the
Cloud Hypervisor default paravirtualized networking architecture.
### Virtual sockets
In order to more efficiently and securely communicate between host and guest,
we added an 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.
### HTTP based API
In anticipation of the need to support asynchronous operations to Cloud
Hypervisor guests (e.g. resources hotplug and guest migration), we added a HTTP
based API to the VMM. The API will be more extensively documented during the
next release cycle.
### Memory mapped virtio transport
In order to support potential PCI-free use cases, we added support for the
[virtio MMIO](https://docs.oasis-open.org/virtio/virtio/v1.1/cs01/virtio-v1.1-cs01.html#x1-1440002)
transport layer. This will allow us to support simple, minimal guest
configurations that do not require a PCI bus emulation.
### Paravirtualized IOMMU
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)
device through virtio. This allows for a safer nested virtio and directly
assigned devices support.
To add the IOMMU support, we had to make some CLI changes for Cloud Hypervisor
users to be able to specify if devices had to be handled through this virtual
IOMMU or not. In particular, the `--disk` option now expects disk paths to be
prefixed with a `path=` string, and supports an optional `iommu=[on|off]`
setting.
### Ubuntu 19.10
With the latest [hypervisor firmware](https://github.com/intel/rust-hypervisor-firmware),
we can now support the latest
[Ubuntu 19.10 (Eoan Ermine)](http://releases.ubuntu.com/19.10/) cloud images.
### Large memory guests
After simplifying and changing our guest address space handling, we can now
support guests with large amount of memory (more than 64GB).
# v0.2.0

View File

@@ -4,10 +4,10 @@
#
#
# Compiler: gcc (Ubuntu 7.4.0-1ubuntu1~18.04.1) 7.4.0
# Compiler: gcc (Ubuntu 8.3.0-6ubuntu1) 8.3.0
#
CONFIG_CC_IS_GCC=y
CONFIG_GCC_VERSION=70400
CONFIG_GCC_VERSION=80300
CONFIG_CLANG_VERSION=0
CONFIG_CC_CAN_LINK=y
CONFIG_CC_HAS_ASM_GOTO=y
@@ -61,6 +61,7 @@ CONFIG_IRQ_DOMAIN=y
CONFIG_IRQ_DOMAIN_HIERARCHY=y
CONFIG_GENERIC_MSI_IRQ=y
CONFIG_GENERIC_MSI_IRQ_DOMAIN=y
CONFIG_IRQ_MSI_IOMMU=y
CONFIG_GENERIC_IRQ_MATRIX_ALLOCATOR=y
CONFIG_GENERIC_IRQ_RESERVATION_MODE=y
CONFIG_IRQ_FORCED_THREADING=y
@@ -485,6 +486,7 @@ CONFIG_HAVE_ACPI_APEI_NMI=y
# CONFIG_DPTF_POWER is not set
# CONFIG_PMIC_OPREGION is not set
# CONFIG_ACPI_CONFIGFS is not set
CONFIG_ACPI_IORT=y
CONFIG_X86_PM_TIMER=y
# CONFIG_SFI is not set
@@ -584,7 +586,25 @@ CONFIG_EFI_EARLYCON=y
# end of Firmware Drivers
CONFIG_HAVE_KVM=y
# CONFIG_VIRTUALIZATION is not set
CONFIG_HAVE_KVM_IRQCHIP=y
CONFIG_HAVE_KVM_IRQFD=y
CONFIG_HAVE_KVM_IRQ_ROUTING=y
CONFIG_HAVE_KVM_EVENTFD=y
CONFIG_KVM_MMIO=y
CONFIG_KVM_ASYNC_PF=y
CONFIG_HAVE_KVM_MSI=y
CONFIG_HAVE_KVM_CPU_RELAX_INTERCEPT=y
CONFIG_KVM_VFIO=y
CONFIG_KVM_GENERIC_DIRTYLOG_READ_PROTECT=y
CONFIG_HAVE_KVM_IRQ_BYPASS=y
CONFIG_HAVE_KVM_NO_POLL=y
CONFIG_VIRTUALIZATION=y
CONFIG_KVM=y
CONFIG_KVM_INTEL=y
# CONFIG_KVM_AMD is not set
# CONFIG_VHOST_NET is not set
# CONFIG_VHOST_VSOCK is not set
# CONFIG_VHOST_CROSS_ENDIAN_LEGACY is not set
#
# General architecture-dependent options
@@ -598,6 +618,7 @@ CONFIG_JUMP_LABEL=y
# CONFIG_STATIC_KEYS_SELFTEST is not set
CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS=y
CONFIG_ARCH_USE_BUILTIN_BSWAP=y
CONFIG_USER_RETURN_NOTIFIER=y
CONFIG_HAVE_IOREMAP_PROT=y
CONFIG_HAVE_KPROBES=y
CONFIG_HAVE_KRETPROBES=y
@@ -733,6 +754,7 @@ CONFIG_BLK_PM=y
# CONFIG_IOSCHED_BFQ is not set
# end of IO Schedulers
CONFIG_PREEMPT_NOTIFIERS=y
CONFIG_INLINE_SPIN_UNLOCK_IRQ=y
CONFIG_INLINE_READ_UNLOCK=y
CONFIG_INLINE_READ_UNLOCK_IRQ=y
@@ -789,6 +811,7 @@ CONFIG_CONTIG_ALLOC=y
CONFIG_PHYS_ADDR_T_64BIT=y
CONFIG_BOUNCE=y
CONFIG_VIRT_TO_BUS=y
CONFIG_MMU_NOTIFIER=y
CONFIG_KSM=y
CONFIG_DEFAULT_MMAP_MIN_ADDR=4096
CONFIG_TRANSPARENT_HUGEPAGE=y
@@ -1750,6 +1773,17 @@ CONFIG_UIO_DMEM_GENIRQ=y
# CONFIG_UIO_NETX is not set
# CONFIG_UIO_PRUSS is not set
# CONFIG_UIO_MF624 is not set
CONFIG_VFIO_IOMMU_TYPE1=y
CONFIG_VFIO_VIRQFD=y
CONFIG_VFIO=y
# CONFIG_VFIO_NOIOMMU is not set
CONFIG_VFIO_PCI=y
# CONFIG_VFIO_PCI_VGA is not set
CONFIG_VFIO_PCI_MMAP=y
CONFIG_VFIO_PCI_INTX=y
# CONFIG_VFIO_PCI_IGD is not set
# CONFIG_VFIO_MDEV is not set
CONFIG_IRQ_BYPASS_MANAGER=y
# CONFIG_VIRT_DRIVERS is not set
CONFIG_VIRTIO=y
CONFIG_VIRTIO_MENU=y
@@ -1794,6 +1828,8 @@ CONFIG_CLKBLD_I8253=y
CONFIG_MAILBOX=y
CONFIG_PCC=y
# CONFIG_ALTERA_MBOX is not set
CONFIG_IOMMU_IOVA=y
CONFIG_IOMMU_API=y
CONFIG_IOMMU_SUPPORT=y
#
@@ -1802,9 +1838,12 @@ CONFIG_IOMMU_SUPPORT=y
# end of Generic IOMMU Pagetable Support
# CONFIG_IOMMU_DEBUGFS is not set
# CONFIG_IOMMU_DEFAULT_PASSTHROUGH is not set
CONFIG_IOMMU_DMA=y
# CONFIG_AMD_IOMMU is not set
# CONFIG_INTEL_IOMMU is not set
# CONFIG_IRQ_REMAP is not set
CONFIG_VIRTIO_IOMMU=y
#
# Remoteproc drivers
@@ -2368,6 +2407,7 @@ CONFIG_ZLIB_DEFLATE=y
CONFIG_LZO_COMPRESS=y
CONFIG_LZO_DECOMPRESS=y
# CONFIG_XZ_DEC is not set
CONFIG_INTERVAL_TREE=y
CONFIG_XARRAY_MULTI=y
CONFIG_ASSOCIATIVE_ARRAY=y
CONFIG_HAS_IOMEM=y

View File

@@ -14,25 +14,16 @@ if [ ! -f "$FW" ]; then
popd
fi
OVMF_URL="https://cdn.download.clearlinux.org/image/OVMF.fd"
OVMF="$WORKLOADS_DIR/OVMF.fd"
if [ ! -f "$OVMF" ]; then
pushd $WORKLOADS_DIR
wget --quiet $OVMF_URL
popd
fi
CLEAR_OS_IMAGE_NAME="clear-29810-cloud.img"
CLEAR_OS_IMAGE_URL="https://cloudhypervisorstorage.blob.core.windows.net/images/$CLEAR_OS_IMAGE_NAME.xz"
CLEAR_OS_IMAGE_NAME="clear-cloudguest.img"
CLEAR_OS_IMAGE_URL="https://cloudhypervisorstorage.blob.core.windows.net/images/$CLEAR_OS_IMAGE_NAME"
CLEAR_OS_IMAGE="$WORKLOADS_DIR/$CLEAR_OS_IMAGE_NAME"
if [ ! -f "$CLEAR_OS_IMAGE" ]; then
pushd $WORKLOADS_DIR
wget --quiet $CLEAR_OS_IMAGE_URL
unxz $CLEAR_OS_IMAGE_NAME.xz
popd
fi
CLEAR_OS_RAW_IMAGE_NAME="clear-29810-cloud-raw.img"
CLEAR_OS_RAW_IMAGE_NAME="clear-cloudguest-raw.img"
CLEAR_OS_RAW_IMAGE="$WORKLOADS_DIR/$CLEAR_OS_RAW_IMAGE_NAME"
if [ ! -f "$CLEAR_OS_RAW_IMAGE" ]; then
pushd $WORKLOADS_DIR
@@ -58,6 +49,24 @@ if [ ! -f "$BIONIC_OS_RAW_IMAGE" ]; then
fi
EOAN_OS_IMAGE_NAME="eoan-server-cloudimg-amd64.img"
EOAN_OS_IMAGE_URL="https://cloudhypervisorstorage.blob.core.windows.net/images/$EOAN_OS_IMAGE_NAME"
EOAN_OS_IMAGE="$WORKLOADS_DIR/$EOAN_OS_IMAGE_NAME"
if [ ! -f "$EOAN_OS_IMAGE" ]; then
pushd $WORKLOADS_DIR
wget --quiet $EOAN_OS_IMAGE_URL
popd
fi
EOAN_OS_RAW_IMAGE_NAME="eoan-server-cloudimg-amd64-raw.img"
EOAN_OS_RAW_IMAGE="$WORKLOADS_DIR/$EOAN_OS_RAW_IMAGE_NAME"
if [ ! -f "$EOAN_OS_RAW_IMAGE" ]; then
pushd $WORKLOADS_DIR
qemu-img convert -p -f qcow2 -O raw $EOAN_OS_IMAGE_NAME $EOAN_OS_RAW_IMAGE_NAME
popd
fi
# Build custom kernel based on virtio-pmem and virtio-fs upstream patches
VMLINUX_IMAGE="$WORKLOADS_DIR/vmlinux"
BZIMAGE_IMAGE="$WORKLOADS_DIR/bzImage"
@@ -67,9 +76,9 @@ LINUX_CUSTOM_DIR="linux-custom"
if [ ! -f "$VMLINUX_IMAGE" ]; then
SRCDIR=$PWD
pushd $WORKLOADS_DIR
git clone --depth 1 "https://github.com/sboeuf/linux.git" -b "virtio-fs" $LINUX_CUSTOM_DIR
git clone --depth 1 "https://github.com/sboeuf/linux.git" -b "virtio-fs-virtio-iommu" $LINUX_CUSTOM_DIR
pushd $LINUX_CUSTOM_DIR
cp $SRCDIR/resources/linux-virtio-fs-config .config
cp $SRCDIR/resources/linux-virtio-fs-virtio-iommu-config .config
make bzImage -j `nproc`
cp vmlinux $VMLINUX_IMAGE
cp arch/x86/boot/bzImage $BZIMAGE_IMAGE
@@ -79,22 +88,36 @@ if [ ! -f "$VMLINUX_IMAGE" ]; then
fi
VIRTIOFSD="$WORKLOADS_DIR/virtiofsd"
VUBRIDGE="$WORKLOADS_DIR/vubridge"
VUBD="$WORKLOADS_DIR/vubd"
QEMU_DIR="qemu_build"
if [ ! -f "$VIRTIOFSD" ] || [ ! -f "$VUBRIDGE" ]; then
if [ ! -f "$VIRTIOFSD" ] || [ ! -f "$VUBD" ]; then
pushd $WORKLOADS_DIR
git clone --depth 1 "https://github.com/sboeuf/qemu.git" -b "virtio-fs" $QEMU_DIR
pushd $QEMU_DIR
./configure --prefix=$PWD --target-list=x86_64-softmmu
make virtiofsd tests/vhost-user-bridge -j `nproc`
make virtiofsd vhost-user-blk -j `nproc`
cp virtiofsd $VIRTIOFSD
cp tests/vhost-user-bridge $VUBRIDGE
cp vhost-user-blk $VUBD
popd
rm -rf $QEMU_DIR
sudo setcap cap_dac_override,cap_sys_admin+epi "virtiofsd"
popd
fi
BLK_IMAGE="$WORKLOADS_DIR/blk.img"
MNT_DIR="mount_image"
if [ ! -f "$BLK_IMAGE" ]; then
pushd $WORKLOADS_DIR
fallocate -l 16M $BLK_IMAGE
mkfs.ext4 -j $BLK_IMAGE
mkdir $MNT_DIR
sudo mount -t ext4 $BLK_IMAGE $MNT_DIR
sudo bash -c "echo bar > $MNT_DIR/foo"
sudo umount $BLK_IMAGE
rm -r $MNT_DIR
popd
fi
SHARED_DIR="$WORKLOADS_DIR/shared_dir"
if [ ! -d "$SHARED_DIR" ]; then
mkdir -p $SHARED_DIR
@@ -111,10 +134,10 @@ if [ ! -d "$VFIO_DIR" ]; then
fi
# VFIO test network setup.
# We reserve a different IP class for it: 172.16.0.0/24.
# We reserve a different IP class for it: 172.17.0.0/24.
sudo ip link add name vfio-br0 type bridge
sudo ip link set vfio-br0 up
sudo ip addr add 172.16.0.1/24 dev vfio-br0
sudo ip addr add 172.17.0.1/24 dev vfio-br0
sudo ip tuntap add vfio-tap0 mode tap
sudo ip link set vfio-tap0 master vfio-br0
@@ -126,19 +149,31 @@ sudo ip link set vfio-tap1 up
cargo build
sudo setcap cap_net_admin+ep target/debug/cloud-hypervisor
sudo setcap cap_net_admin+ep target/debug/vhost_user_net
# We always copy a fresh version of our binary for our L2 guest.
cp target/debug/cloud-hypervisor $VFIO_DIR
# We need qemu to have NET_ADMIN as well.
sudo setcap cap_net_admin+ep /usr/bin/qemu-system-x86_64
sudo adduser $USER kvm
newgrp kvm << EOF
export RUST_BACKTRACE=1
cargo test --features "integration_tests"
cargo test --features "integration_tests" -- --nocapture
EOF
RES=$?
if [ $RES -eq 0 ]; then
# virtio-mmio based testing
cargo build --no-default-features --features "mmio"
sudo setcap cap_net_admin+ep target/debug/cloud-hypervisor
newgrp kvm << EOF
export RUST_BACKTRACE=1
cargo test --features "integration_tests,mmio" -- --nocapture
EOF
RES=$?
fi
# Tear VFIO test network down
sudo ip link del vfio-br0
sudo ip link del vfio-tap0

597
src/bin/vhost_user_net.rs Normal file
View File

@@ -0,0 +1,597 @@
// 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 log;
extern crate net_util;
extern crate vhost_rs;
extern crate vhost_user_backend;
extern crate vm_virtio;
use clap::{App, Arg};
use epoll;
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() {
let cmd_arguments = App::new("vhost-user-net backend")
.version(crate_version!())
.author(crate_authors!())
.about("Launch a vhost-user-net backend.")
.arg(
Arg::with_name("backend")
.long("backend")
.help(
"Backend parameters \"ip=<ip_addr>,\
mask=<net_mask>,sock=<socket_path>\"",
)
.takes_value(true)
.min_values(1),
)
.get_matches();
let vhost_user_net_backend = cmd_arguments.value_of("backend").unwrap();
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

@@ -47,10 +47,8 @@ write_files:
content: |
#!/bin/bash
mount -t 9p -o trans=virtio cloud_hypervisor /mnt -oversion=9p2000.L,posixacl,cache=loose
modprobe vfio_iommu_type1 allow_unsafe_interrupts
modprobe vfio_pci
bash -c "echo 0000:00:03.0 > /sys/bus/pci/devices/0000\:00\:03.0/driver/unbind"
mount -t virtio_fs virtiofs /mnt -o rootmode=040000,user_id=0,group_id=0,dax
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"
/mnt/cloud-hypervisor --console off --serial tty --kernel /mnt/vmlinux --cmdline "console=ttyS0 reboot=k panic=1 nomodules i8042.noaux i8042.nomux i8042.nopnp i8042.dumbkbd root=/dev/vda2 VFIOTAG" --disk /mnt/clear-29810-cloud.img /mnt/cloudinit.img --cpus 1 --memory size=512M --rng --device /sys/bus/pci/devices/0000:00:03.0/
/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/

View File

@@ -1,12 +0,0 @@
[package]
name = "vfio-bindings"
version = "0.0.1"
authors = ["The Cloud Hypervisor Authors"]
edition = "2018"
[dependencies]
vmm-sys-util = { git = "https://github.com/rust-vmm/vmm-sys-util" }
[features]
default = ["v5_0_0"]
v5_0_0 = []

View File

@@ -1,16 +0,0 @@
// Copyright 2017 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE-BSD-3-Clause file.
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
// generated with bindgen linux/uapi/linux/vfio.h --constified-enum '*' --with-derive-default
#[cfg(feature = "v5_0_0")]
mod v5_0_0;
pub mod bindings {
#[cfg(feature = "v5_0_0")]
pub use super::v5_0_0::*;
}

View File

@@ -1,6 +0,0 @@
#![allow(clippy::all)]
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
pub mod vfio;

File diff suppressed because it is too large Load Diff

View File

@@ -11,9 +11,10 @@ kvm-ioctls = { git = "https://github.com/rust-vmm/kvm-ioctls", branch = "master"
libc = "0.2.60"
log = "0.4.8"
pci = { path = "../pci" }
vfio-bindings = { path = "../vfio-bindings" }
vfio-bindings = "0.1.0"
vm-allocator = { path = "../vm-allocator" }
vmm-sys-util = { git = "https://github.com/rust-vmm/vmm-sys-util" }
vm-device = { path = "../vm-device" }
vmm-sys-util = "0.1.1"
[dependencies.vm-memory]
git = "https://github.com/rust-vmm/vm-memory"

View File

@@ -14,6 +14,7 @@ extern crate log;
extern crate pci;
extern crate vfio_bindings;
extern crate vm_allocator;
extern crate vm_device;
extern crate vm_memory;
#[macro_use]
extern crate vmm_sys_util;
@@ -24,7 +25,7 @@ mod vfio_pci;
use std::mem::size_of;
pub use vfio_device::{VfioDevice, VfioError};
pub use vfio_device::{VfioContainer, VfioDevice, VfioDmaMapping, VfioError};
pub use vfio_pci::{VfioPciDevice, VfioPciError};
// Returns a `Vec<T>` with a size in bytes at least as large as `size_in_bytes`.

View File

@@ -14,11 +14,13 @@ use std::mem;
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
use std::os::unix::prelude::FileExt;
use std::path::{Path, PathBuf};
use std::result;
use std::sync::{Arc, RwLock};
use std::u32;
use vfio_bindings::bindings::vfio::*;
use vfio_ioctls::*;
use vm_memory::{Address, GuestMemory, GuestMemoryMmap, GuestMemoryRegion};
use vm_device::ExternalDmaMapping;
use vm_memory::{Address, GuestAddress, GuestMemory, GuestMemoryMmap, GuestMemoryRegion};
use vmm_sys_util::eventfd::EventFd;
use vmm_sys_util::ioctl::*;
@@ -103,7 +105,7 @@ struct vfio_region_info_with_cap {
cap_info: __IncompleteArrayField<u8>,
}
struct VfioContainer {
pub struct VfioContainer {
container: File,
}
@@ -151,7 +153,7 @@ impl VfioContainer {
Ok(())
}
fn vfio_dma_map(&self, iova: u64, size: u64, user_addr: u64) -> Result<()> {
pub fn vfio_dma_map(&self, iova: u64, size: u64, user_addr: u64) -> Result<()> {
let dma_map = vfio_iommu_type1_dma_map {
argsz: mem::size_of::<vfio_iommu_type1_dma_map>() as u32,
flags: VFIO_DMA_MAP_FLAG_READ | VFIO_DMA_MAP_FLAG_WRITE,
@@ -170,7 +172,7 @@ impl VfioContainer {
Ok(())
}
fn vfio_dma_unmap(&self, iova: u64, size: u64) -> Result<()> {
pub fn vfio_dma_unmap(&self, iova: u64, size: u64) -> Result<()> {
let mut dma_unmap = vfio_iommu_type1_dma_unmap {
argsz: mem::size_of::<vfio_iommu_type1_dma_unmap>() as u32,
flags: 0,
@@ -198,7 +200,7 @@ impl AsRawFd for VfioContainer {
struct VfioGroup {
group: File,
device: Arc<DeviceFd>,
container: VfioContainer,
container: Arc<VfioContainer>,
}
impl VfioGroup {
@@ -225,7 +227,7 @@ impl VfioGroup {
return Err(VfioError::GroupViable);
}
let container = VfioContainer::new()?;
let container = Arc::new(VfioContainer::new()?);
if container.get_api_version() as u32 != VFIO_API_VERSION {
return Err(VfioError::VfioApiVersion);
}
@@ -506,6 +508,68 @@ impl VfioDeviceInfo {
}
}
/// This structure implements the ExternalDmaMapping trait. It is meant to
/// be used when the caller tries to provide a way to update the mappings
/// associated with a specific VFIO container.
pub struct VfioDmaMapping {
container: Arc<VfioContainer>,
memory: Arc<RwLock<GuestMemoryMmap>>,
}
impl VfioDmaMapping {
pub fn new(container: Arc<VfioContainer>, memory: Arc<RwLock<GuestMemoryMmap>>) -> Self {
VfioDmaMapping { container, memory }
}
}
impl ExternalDmaMapping for VfioDmaMapping {
fn map(&self, iova: u64, gpa: u64, size: u64) -> result::Result<(), io::Error> {
let user_addr = if let Some(addr) = self
.memory
.read()
.unwrap()
.get_host_address(GuestAddress(gpa))
{
addr as u64
} else {
return Err(io::Error::new(
io::ErrorKind::Other,
format!(
"failed to convert guest address 0x{:x} into \
host user virtual address",
gpa
),
));
};
self.container
.vfio_dma_map(iova, size, user_addr)
.map_err(|e| {
io::Error::new(
io::ErrorKind::Other,
format!(
"failed to map memory for VFIO container, \
iova 0x{:x}, gpa 0x{:x}, size 0x{:x}: {:?}",
iova, gpa, size, e
),
)
})
}
fn unmap(&self, iova: u64, size: u64) -> result::Result<(), io::Error> {
self.container.vfio_dma_unmap(iova, size).map_err(|e| {
io::Error::new(
io::ErrorKind::Other,
format!(
"failed to unmap memory for VFIO container, \
iova 0x{:x}, size 0x{:x}: {:?}",
iova, size, e
),
)
})
}
}
/// Vfio device for exposing regions which could be read/write to kernel vfio device.
pub struct VfioDevice {
device: File,
@@ -514,6 +578,7 @@ pub struct VfioDevice {
regions: Vec<VfioRegion>,
irqs: HashMap<u32, VfioIrq>,
mem: Arc<RwLock<GuestMemoryMmap>>,
iommu_attached: bool,
}
impl VfioDevice {
@@ -524,6 +589,7 @@ impl VfioDevice {
sysfspath: &Path,
device_fd: Arc<DeviceFd>,
mem: Arc<RwLock<GuestMemoryMmap>>,
iommu_attached: bool,
) -> Result<Self> {
let uuid_path: PathBuf = [sysfspath, Path::new("iommu_group")].iter().collect();
let group_path = uuid_path.read_link().map_err(|_| VfioError::InvalidPath)?;
@@ -545,6 +611,7 @@ impl VfioDevice {
regions,
irqs,
mem,
iommu_attached,
})
}
@@ -762,6 +829,10 @@ impl VfioDevice {
}
}
pub fn get_container(&self) -> Arc<VfioContainer> {
self.group.container.clone()
}
fn vfio_dma_map(&self, iova: u64, size: u64, user_addr: u64) -> Result<()> {
self.group.container.vfio_dma_map(iova, size, user_addr)
}
@@ -773,22 +844,26 @@ impl VfioDevice {
/// Add all guest memory regions into vfio container's iommu table,
/// then vfio kernel driver could access guest memory from gfn
pub fn setup_dma_map(&self) -> Result<()> {
self.mem.read().unwrap().with_regions(|_index, region| {
self.vfio_dma_map(
region.start_addr().raw_value(),
region.len() as u64,
region.as_ptr() as u64,
)
})?;
if !self.iommu_attached {
self.mem.read().unwrap().with_regions(|_index, region| {
self.vfio_dma_map(
region.start_addr().raw_value(),
region.len() as u64,
region.as_ptr() as u64,
)
})?;
}
Ok(())
}
/// remove all guest memory regions from vfio containers iommu table
/// then vfio kernel driver couldn't access this guest memory
pub fn unset_dma_map(&self) -> Result<()> {
self.mem.read().unwrap().with_regions(|_index, region| {
self.vfio_dma_unmap(region.start_addr().raw_value(), region.len() as u64)
})?;
if !self.iommu_attached {
self.mem.read().unwrap().with_regions(|_index, region| {
self.vfio_dma_unmap(region.start_addr().raw_value(), region.len() as u64)
})?;
}
Ok(())
}

View File

@@ -15,7 +15,7 @@ vhost-user-slave = []
[dependencies]
bitflags = "1.1.0"
libc = "0.2.60"
vmm-sys-util = { git = "https://github.com/rust-vmm/vmm-sys-util" }
vmm-sys-util = "0.1.1"
[dependencies.vm-memory]
git = "https://github.com/rust-vmm/vm-memory"

View File

@@ -197,45 +197,6 @@ impl<R: Req> Endpoint<R> {
Ok(())
}
/// Send a message with header, body and config info. Optional file descriptors may be attached to
/// the message.
///
/// # Return:
/// * - number of bytes sent on success
/// * - SocketRetry: temporary error caused by signals or short of resources.
/// * - SocketBroken: the underline socket is broken.
/// * - SocketError: other socket related errors.
/// * - PartialMessage: received a partial message.
pub fn send_config_message(
&mut self,
hdr: &VhostUserMsgHeader<R>,
user_config: &VhostUserConfig,
buf: &mut [u8],
fds: Option<&[RawFd]>,
) -> Result<()> {
// Safe because there can't be other mutable referance to hdr and body.
let iovs = unsafe {
[
slice::from_raw_parts(
hdr as *const VhostUserMsgHeader<R> as *const u8,
mem::size_of::<VhostUserMsgHeader<R>>(),
),
slice::from_raw_parts(
user_config as *const VhostUserConfig as *const u8,
mem::size_of::<VhostUserConfig>(),
),
slice::from_raw_parts(buf.as_ptr() as *const u8, buf.len()),
]
};
let bytes = self.send_iovec(&iovs[..], fds)?;
let total =
mem::size_of::<VhostUserMsgHeader<R>>() + mem::size_of::<VhostUserConfig>() + buf.len();
if bytes != total {
return Err(Error::PartialMessage);
}
Ok(())
}
/// Send a message with header, body and payload. Optional file descriptors
/// may also be attached to the message.
///

View File

@@ -40,11 +40,12 @@ pub trait VhostUserMaster: VhostBackend {
offset: u32,
size: u32,
flags: VhostUserConfigFlags,
) -> Result<Vec<u8>>;
buf: &[u8],
) -> Result<(VhostUserConfig, VhostUserConfigPayload)>;
/// Change the virtio device configuration space. It also can be used for live migration on the
/// destination host to set readonly configuration space fields.
fn set_config(&mut self, offset: u32, buf: &[u8], flags: VhostUserConfigFlags) -> Result<()>;
fn set_config(&mut self, offset: u32, flags: VhostUserConfigFlags, buf: &[u8]) -> Result<()>;
/// Setup slave communication channel.
fn set_slave_request_fd(&mut self, fd: RawFd) -> Result<()>;
@@ -166,9 +167,12 @@ impl VhostBackend for Master {
node.wait_for_ack(&hdr).map_err(|e| e.into())
}
// Clippy doesn't seem to know that if let with && is still experimental
#[allow(clippy::unnecessary_unwrap)]
fn set_log_base(&mut self, base: u64, fd: Option<RawFd>) -> Result<()> {
let mut node = self.node.lock().unwrap();
let val = VhostUserU64::new(base);
if node.acked_protocol_features & VhostUserProtocolFeatures::LOG_SHMFD.bits() != 0
&& fd.is_some()
{
@@ -344,7 +348,8 @@ impl VhostUserMaster for Master {
offset: u32,
size: u32,
flags: VhostUserConfigFlags,
) -> Result<Vec<u8>> {
buf: &[u8],
) -> Result<(VhostUserConfig, VhostUserConfigPayload)> {
let body = VhostUserConfig::new(offset, size, flags);
if !body.is_valid() {
return error_code(VhostUserError::InvalidParam);
@@ -356,24 +361,24 @@ impl VhostUserMaster for Master {
return error_code(VhostUserError::InvalidOperation);
}
// TODO: vhost-user spec states that:
// vhost-user spec states that:
// "Master payload: virtio device config space"
// But what content should the payload contains for a get_config() request?
// So current implementation doesn't conform to the spec.
let hdr = node.send_request_with_config_body(MasterReq::GET_CONFIG, &body, None)?;
let (reply, buf, rfds) = node.recv_reply_with_payload::<VhostUserConfig>(&hdr)?;
// "Slave payload: virtio device config space"
let hdr = node.send_request_with_payload(MasterReq::GET_CONFIG, &body, buf, None)?;
let (body_reply, buf_reply, rfds) =
node.recv_reply_with_payload::<VhostUserConfig>(&hdr)?;
if rfds.is_some() {
Endpoint::<MasterReq>::close_rfds(rfds);
return error_code(VhostUserError::InvalidMessage);
} else if reply.size == 0 {
} else if body_reply.size == 0 {
return error_code(VhostUserError::SlaveInternalError);
} else if reply.size != body.size || reply.size as usize != buf.len() {
} else if body_reply.size != body.size || body_reply.size as usize != buf.len() {
return error_code(VhostUserError::InvalidMessage);
}
Ok(buf)
Ok((body_reply, buf_reply))
}
fn set_config(&mut self, offset: u32, buf: &[u8], flags: VhostUserConfigFlags) -> Result<()> {
fn set_config(&mut self, offset: u32, flags: VhostUserConfigFlags, buf: &[u8]) -> Result<()> {
if buf.len() > MAX_MSG_SIZE {
return error_code(VhostUserError::InvalidParam);
}
@@ -480,27 +485,6 @@ impl MasterInternal {
Ok(hdr)
}
fn send_request_with_config_body(
&mut self,
code: MasterReq,
user_config: &VhostUserConfig,
fds: Option<&[RawFd]>,
) -> VhostUserResult<VhostUserMsgHeader<MasterReq>> {
if mem::size_of::<VhostUserConfig>() + user_config.size as usize > MAX_MSG_SIZE {
return Err(VhostUserError::InvalidParam);
}
self.check_state()?;
let hdr = Self::new_request_header(
code,
mem::size_of::<VhostUserConfig>() as u32 + user_config.size,
);
let mut buf = vec![0; user_config.size as usize];
self.main_sock
.send_config_message(&hdr, user_config, &mut buf, fds)?;
Ok(hdr)
}
fn send_request_with_payload<T: Sized, P: Sized>(
&mut self,
code: MasterReq,
@@ -571,7 +555,7 @@ impl MasterInternal {
}
self.check_state()?;
let mut buf = vec![0; MAX_MSG_SIZE - mem::size_of::<T>()];
let mut buf: Vec<u8> = vec![0; hdr.get_size() as usize - mem::size_of::<T>()];
let (reply, body, bytes, rfds) = self.main_sock.recv_payload_into_buf::<T>(&mut buf)?;
if !reply.is_reply_for(hdr)
|| reply.get_size() as usize != mem::size_of::<T>() + bytes

View File

@@ -346,6 +346,8 @@ bitflags! {
const SLAVE_SEND_FD = 0x0000_0400;
/// Allow the slave to register a host notifier.
const HOST_NOTIFIER = 0x0000_0800;
/// Support inflight shmfd.
const INFLIGHT_SHMFD = 0x0000_1000;
}
}

View File

@@ -0,0 +1,22 @@
[package]
name = "vhost_user_backend"
version = "0.1.0"
authors = ["The Cloud Hypervisor Authors"]
edition = "2018"
[features]
default = []
pci_support = ["vm-virtio/pci_support"]
mmio_support = ["vm-virtio/mmio_support"]
[dependencies]
epoll = "4.1.0"
libc = "0.2.65"
vm-memory = { git = "https://github.com/rust-vmm/vm-memory" }
vm-virtio = { path = "../vm-virtio" }
vmm-sys-util = "0.1.1"
[dependencies.vhost_rs]
path = "../vhost_rs"
features = ["vhost-user-slave"]

View File

@@ -0,0 +1,759 @@
// Copyright 2019 Intel Corporation. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
//
// Copyright 2019 Alibaba Cloud Computing. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
use std::error;
use std::fs::File;
use std::io;
use std::num::Wrapping;
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
use std::result;
use std::sync::{Arc, Mutex, RwLock};
use std::thread;
use vhost_rs::vhost_user::message::{
VhostUserConfigFlags, VhostUserMemoryRegion, VhostUserProtocolFeatures,
VhostUserVirtioFeatures, VhostUserVringAddrFlags, VhostUserVringState,
VHOST_USER_CONFIG_OFFSET, VHOST_USER_CONFIG_SIZE,
};
use vhost_rs::vhost_user::{
Error as VhostUserError, Result as VhostUserResult, SlaveListener, VhostUserSlaveReqHandler,
};
use vm_memory::guest_memory::FileOffset;
use vm_memory::{GuestAddress, GuestMemoryMmap};
use vm_virtio::Queue;
use vmm_sys_util::eventfd::EventFd;
#[derive(Debug)]
/// Errors related to vhost-user daemon.
pub enum Error {
/// Failed to create a new vhost-user handler.
NewVhostUserHandler(VhostUserHandlerError),
/// Failed creating vhost-user slave listener.
CreateSlaveListener(VhostUserError),
/// Failed creating vhost-user slave handler.
CreateSlaveReqHandler(VhostUserError),
/// Failed starting daemon thread.
StartDaemon(io::Error),
/// Failed waiting for daemon thread.
WaitDaemon(std::boxed::Box<dyn std::any::Any + std::marker::Send>),
/// Failed handling a vhost-user request.
HandleRequest(VhostUserError),
/// Failed to process queue.
ProcessQueue(VringEpollHandlerError),
/// Failed to register listener.
RegisterListener(io::Error),
/// Failed to unregister listener.
UnregisterListener(io::Error),
}
/// Result of vhost-user daemon operations.
pub type Result<T> = result::Result<T, Error>;
/// This trait must be implemented by the caller in order to provide backend
/// specific implementation.
pub trait VhostUserBackend: Send + Sync + 'static {
/// Number of queues.
fn num_queues(&self) -> usize;
/// Depth of each queue.
fn max_queue_size(&self) -> usize;
/// Virtio features.
fn features(&self) -> u64;
/// Update guest memory regions.
fn update_memory(&mut self, mem: GuestMemoryMmap) -> result::Result<(), io::Error>;
/// This function gets called if the backend registered some additional
/// listeners onto specific file descriptors. The library can handle
/// virtqueues on its own, but does not know what to do with events
/// happening on custom listeners.
fn handle_event(
&mut self,
device_event: u16,
evset: epoll::Events,
vrings: &[Arc<RwLock<Vring>>],
) -> result::Result<bool, io::Error>;
/// Get virtio device configuration.
/// A default implementation is provided as we cannot expect all backends
/// to implement this function.
fn get_config(&self, _offset: u32, _size: u32) -> Vec<u8> {
Vec::new()
}
/// Set virtio device configuration.
/// A default implementation is provided as we cannot expect all backends
/// to implement this function.
fn set_config(&mut self, _offset: u32, _buf: &[u8]) -> result::Result<(), io::Error> {
Ok(())
}
}
/// This structure is the public API the backend is allowed to interact with
/// in order to run a fully functional vhost-user daemon.
pub struct VhostUserDaemon<S: VhostUserBackend> {
name: String,
sock_path: String,
handler: Arc<Mutex<VhostUserHandler<S>>>,
main_thread: Option<thread::JoinHandle<Result<()>>>,
}
impl<S: VhostUserBackend> VhostUserDaemon<S> {
/// Create the daemon instance, providing the backend implementation of
/// VhostUserBackend.
/// Under the hood, this will start a dedicated thread responsible for
/// listening onto registered event. Those events can be vring events or
/// custom events from the backend, but they get to be registered later
/// during the sequence.
pub fn new(name: String, sock_path: String, backend: Arc<RwLock<S>>) -> Result<Self> {
let handler = Arc::new(Mutex::new(
VhostUserHandler::new(backend).map_err(Error::NewVhostUserHandler)?,
));
Ok(VhostUserDaemon {
name,
sock_path,
handler,
main_thread: None,
})
}
/// Connect to the vhost-user socket and run a dedicated thread handling
/// all requests coming through this socket. This runs in an infinite loop
/// that should be terminating once the other end of the socket (the VMM)
/// disconnects.
pub fn start(&mut self) -> Result<()> {
let mut slave_listener =
SlaveListener::new(self.sock_path.as_str(), true, self.handler.clone())
.map_err(Error::CreateSlaveListener)?;
let mut slave_handler = slave_listener
.accept()
.map_err(Error::CreateSlaveReqHandler)?
.unwrap();
let handle = thread::Builder::new()
.name(self.name.clone())
.spawn(move || loop {
slave_handler
.handle_request()
.map_err(Error::HandleRequest)?;
})
.map_err(Error::StartDaemon)?;
self.main_thread = Some(handle);
Ok(())
}
/// Wait for the thread handling the vhost-user socket connection to
/// terminate.
pub fn wait(&mut self) -> Result<()> {
if let Some(handle) = self.main_thread.take() {
let _ = handle.join().map_err(Error::WaitDaemon)?;
}
Ok(())
}
/// Retrieve the vring worker. This is necessary to perform further
/// actions like registering and unregistering some extra event file
/// descriptors.
pub fn get_vring_worker(&self) -> Arc<VringWorker> {
self.handler.lock().unwrap().get_vring_worker()
}
}
struct AddrMapping {
vmm_addr: u64,
size: u64,
offset: u64,
}
struct Memory {
mappings: Vec<AddrMapping>,
}
pub struct Vring {
queue: Queue,
kick: Option<EventFd>,
call: Option<EventFd>,
err: Option<EventFd>,
enabled: bool,
}
impl Vring {
fn new(max_queue_size: u16) -> Self {
Vring {
queue: Queue::new(max_queue_size),
kick: None,
call: None,
err: None,
enabled: false,
}
}
pub fn mut_queue(&mut self) -> &mut Queue {
&mut self.queue
}
pub fn signal_used_queue(&self) -> result::Result<(), io::Error> {
if let Some(call) = self.call.as_ref() {
return call.write(1);
}
Ok(())
}
}
#[derive(Debug)]
/// Errors related to vring epoll handler.
pub enum VringEpollHandlerError {
/// Failed to process the queue from the backend.
ProcessQueueBackendProcessing(io::Error),
/// Failed to signal used queue.
SignalUsedQueue(io::Error),
/// Failed to read the event from kick EventFd.
HandleEventReadKick(io::Error),
/// Failed to handle the event from the backend.
HandleEventBackendHandling(io::Error),
}
/// Result of vring epoll handler operations.
type VringEpollHandlerResult<T> = std::result::Result<T, VringEpollHandlerError>;
struct VringEpollHandler<S: VhostUserBackend> {
backend: Arc<RwLock<S>>,
vrings: Vec<Arc<RwLock<Vring>>>,
}
impl<S: VhostUserBackend> VringEpollHandler<S> {
fn handle_event(
&self,
device_event: u16,
evset: epoll::Events,
) -> VringEpollHandlerResult<bool> {
let num_queues = self.vrings.len();
if (device_event as usize) < num_queues {
if let Some(kick) = &self.vrings[device_event as usize].read().unwrap().kick {
kick.read()
.map_err(VringEpollHandlerError::HandleEventReadKick)?;
}
// If the vring is not enabled, it should not be processed.
// The event is only read to be discarded.
if !self.vrings[device_event as usize].read().unwrap().enabled {
return Ok(false);
}
}
self.backend
.write()
.unwrap()
.handle_event(device_event, evset, &self.vrings)
.map_err(VringEpollHandlerError::HandleEventBackendHandling)
}
}
#[derive(Debug)]
/// Errors related to vring worker.
enum VringWorkerError {
/// Failed while waiting for events.
EpollWait(io::Error),
}
/// Result of vring worker operations.
type VringWorkerResult<T> = std::result::Result<T, VringWorkerError>;
pub struct VringWorker {
epoll_fd: RawFd,
}
impl VringWorker {
fn run<S: VhostUserBackend>(&self, handler: VringEpollHandler<S>) -> VringWorkerResult<()> {
const EPOLL_EVENTS_LEN: usize = 100;
let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN];
'epoll: loop {
let num_events = match epoll::wait(self.epoll_fd, -1, &mut events[..]) {
Ok(res) => res,
Err(e) => {
if e.kind() == io::ErrorKind::Interrupted {
// It's well defined from the epoll_wait() syscall
// documentation that the epoll loop can be interrupted
// before any of the requested events occurred or the
// timeout expired. In both those cases, epoll_wait()
// returns an error of type EINTR, but this should not
// be considered as a regular error. Instead it is more
// appropriate to retry, by calling into epoll_wait().
continue;
}
return Err(VringWorkerError::EpollWait(e));
}
};
for event in events.iter().take(num_events) {
let evset = match epoll::Events::from_bits(event.events) {
Some(evset) => evset,
None => {
let evbits = event.events;
println!("epoll: ignoring unknown event set: 0x{:x}", evbits);
continue;
}
};
let ev_type = event.data as u16;
if let Err(e) = handler.handle_event(ev_type, evset) {
println!(
"vring handler handle event {} with error {:?}\n",
ev_type, e
);
break 'epoll;
}
}
}
Ok(())
}
/// Register a custom event only meaningful to the caller. When this event
/// is later triggered, and because only the caller knows what to do about
/// it, the backend implementation of `handle_event` will be called.
/// This lets entire control to the caller about what needs to be done for
/// this special event, without forcing it to run its own dedicated epoll
/// loop for it.
pub fn register_listener(
&self,
fd: RawFd,
ev_type: epoll::Events,
data: u64,
) -> result::Result<(), io::Error> {
epoll::ctl(
self.epoll_fd,
epoll::ControlOptions::EPOLL_CTL_ADD,
fd,
epoll::Event::new(ev_type, data),
)
}
/// Unregister a custom event. If the custom event is triggered after this
/// function has been called, nothing will happen as it will be removed
/// from the list of file descriptors the epoll loop is listening to.
pub fn unregister_listener(
&self,
fd: RawFd,
ev_type: epoll::Events,
data: u64,
) -> result::Result<(), io::Error> {
epoll::ctl(
self.epoll_fd,
epoll::ControlOptions::EPOLL_CTL_DEL,
fd,
epoll::Event::new(ev_type, data),
)
}
}
#[derive(Debug)]
/// Errors related to vhost-user handler.
pub enum VhostUserHandlerError {
/// Failed to create epoll file descriptor.
EpollCreateFd(io::Error),
/// Failed to spawn vring worker.
SpawnVringWorker(io::Error),
/// Could not find the mapping from memory regions.
MissingMemoryMapping,
}
impl std::fmt::Display for VhostUserHandlerError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
VhostUserHandlerError::EpollCreateFd(e) => write!(f, "failed creating epoll fd: {}", e),
VhostUserHandlerError::SpawnVringWorker(e) => {
write!(f, "failed spawning the vring worker: {}", e)
}
VhostUserHandlerError::MissingMemoryMapping => write!(f, "Missing memory mapping"),
}
}
}
impl error::Error for VhostUserHandlerError {}
/// Result of vhost-user handler operations.
type VhostUserHandlerResult<T> = std::result::Result<T, VhostUserHandlerError>;
struct VhostUserHandler<S: VhostUserBackend> {
backend: Arc<RwLock<S>>,
worker: Arc<VringWorker>,
owned: bool,
features_acked: bool,
acked_features: u64,
acked_protocol_features: u64,
num_queues: usize,
max_queue_size: usize,
memory: Option<Memory>,
vrings: Vec<Arc<RwLock<Vring>>>,
}
impl<S: VhostUserBackend> VhostUserHandler<S> {
fn new(backend: Arc<RwLock<S>>) -> VhostUserHandlerResult<Self> {
let num_queues = backend.read().unwrap().num_queues();
let max_queue_size = backend.read().unwrap().max_queue_size();
let mut vrings: Vec<Arc<RwLock<Vring>>> = Vec::new();
for _ in 0..num_queues {
let vring = Arc::new(RwLock::new(Vring::new(max_queue_size as u16)));
vrings.push(vring);
}
// Create the epoll file descriptor
let epoll_fd = epoll::create(true).map_err(VhostUserHandlerError::EpollCreateFd)?;
let vring_handler = VringEpollHandler {
backend: backend.clone(),
vrings: vrings.clone(),
};
let vring_worker = Arc::new(VringWorker { epoll_fd });
let worker = vring_worker.clone();
thread::Builder::new()
.name("vring_worker".to_string())
.spawn(move || vring_worker.run(vring_handler))
.map_err(VhostUserHandlerError::SpawnVringWorker)?;
Ok(VhostUserHandler {
backend,
worker,
owned: false,
features_acked: false,
acked_features: 0,
acked_protocol_features: 0,
num_queues,
max_queue_size,
memory: None,
vrings,
})
}
fn get_vring_worker(&self) -> Arc<VringWorker> {
self.worker.clone()
}
fn vmm_va_to_gpa(&self, vmm_va: u64) -> VhostUserHandlerResult<u64> {
if let Some(memory) = &self.memory {
for mapping in memory.mappings.iter() {
if vmm_va >= mapping.vmm_addr && vmm_va < mapping.vmm_addr + mapping.size {
return Ok(vmm_va - mapping.vmm_addr + mapping.offset);
}
}
}
Err(VhostUserHandlerError::MissingMemoryMapping)
}
}
impl<S: VhostUserBackend> VhostUserSlaveReqHandler for VhostUserHandler<S> {
fn set_owner(&mut self) -> VhostUserResult<()> {
if self.owned {
return Err(VhostUserError::InvalidOperation);
}
self.owned = true;
Ok(())
}
fn reset_owner(&mut self) -> VhostUserResult<()> {
self.owned = false;
self.features_acked = false;
self.acked_features = 0;
self.acked_protocol_features = 0;
Ok(())
}
fn get_features(&mut self) -> VhostUserResult<u64> {
Ok(self.backend.read().unwrap().features())
}
fn set_features(&mut self, features: u64) -> VhostUserResult<()> {
if !self.owned {
return Err(VhostUserError::InvalidOperation);
} else if (features & !self.backend.read().unwrap().features()) != 0 {
return Err(VhostUserError::InvalidParam);
}
self.acked_features = features;
self.features_acked = true;
// If VHOST_USER_F_PROTOCOL_FEATURES has not been negotiated,
// the ring is initialized in an enabled state.
// If VHOST_USER_F_PROTOCOL_FEATURES has been negotiated,
// the ring is initialized in a disabled state. Client must not
// pass data to/from the backend until ring is enabled by
// VHOST_USER_SET_VRING_ENABLE with parameter 1, or after it has
// been disabled by VHOST_USER_SET_VRING_ENABLE with parameter 0.
let vring_enabled =
self.acked_features & VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits() == 0;
for vring in self.vrings.iter_mut() {
vring.write().unwrap().enabled = vring_enabled;
}
Ok(())
}
fn get_protocol_features(&mut self) -> VhostUserResult<VhostUserProtocolFeatures> {
Ok(VhostUserProtocolFeatures::all())
}
fn set_protocol_features(&mut self, features: u64) -> VhostUserResult<()> {
// Note: slave that reported VHOST_USER_F_PROTOCOL_FEATURES must
// support this message even before VHOST_USER_SET_FEATURES was
// called.
self.acked_protocol_features = features;
Ok(())
}
fn set_mem_table(
&mut self,
ctx: &[VhostUserMemoryRegion],
fds: &[RawFd],
) -> VhostUserResult<()> {
// We need to create tuple of ranges from the list of VhostUserMemoryRegion
// that we get from the caller.
let mut regions: Vec<(GuestAddress, usize, Option<FileOffset>)> = Vec::new();
let mut mappings: Vec<AddrMapping> = Vec::new();
for (idx, region) in ctx.iter().enumerate() {
let g_addr = GuestAddress(region.guest_phys_addr);
let len = (region.memory_size + region.mmap_offset) as usize;
let file = unsafe { File::from_raw_fd(fds[idx]) };
let f_off = FileOffset::new(file, 0);
regions.push((g_addr, len, Some(f_off)));
mappings.push(AddrMapping {
vmm_addr: region.user_addr,
size: region.memory_size,
offset: region.mmap_offset,
});
}
let mem = GuestMemoryMmap::with_files(regions).map_err(|e| {
VhostUserError::ReqHandlerError(io::Error::new(io::ErrorKind::Other, e))
})?;
self.backend
.write()
.unwrap()
.update_memory(mem)
.map_err(|e| {
VhostUserError::ReqHandlerError(io::Error::new(io::ErrorKind::Other, e))
})?;
self.memory = Some(Memory { mappings });
Ok(())
}
fn get_queue_num(&mut self) -> VhostUserResult<u64> {
Ok(self.num_queues as u64)
}
fn set_vring_num(&mut self, index: u32, num: u32) -> VhostUserResult<()> {
if index as usize >= self.num_queues || num == 0 || num as usize > self.max_queue_size {
return Err(VhostUserError::InvalidParam);
}
self.vrings[index as usize].write().unwrap().queue.size = num as u16;
Ok(())
}
fn set_vring_addr(
&mut self,
index: u32,
_flags: VhostUserVringAddrFlags,
descriptor: u64,
used: u64,
available: u64,
_log: u64,
) -> VhostUserResult<()> {
if index as usize >= self.num_queues {
return Err(VhostUserError::InvalidParam);
}
if self.memory.is_some() {
let desc_table = self.vmm_va_to_gpa(descriptor).map_err(|e| {
VhostUserError::ReqHandlerError(io::Error::new(io::ErrorKind::Other, e))
})?;
let avail_ring = self.vmm_va_to_gpa(available).map_err(|e| {
VhostUserError::ReqHandlerError(io::Error::new(io::ErrorKind::Other, e))
})?;
let used_ring = self.vmm_va_to_gpa(used).map_err(|e| {
VhostUserError::ReqHandlerError(io::Error::new(io::ErrorKind::Other, e))
})?;
self.vrings[index as usize]
.write()
.unwrap()
.queue
.desc_table = GuestAddress(desc_table);
self.vrings[index as usize]
.write()
.unwrap()
.queue
.avail_ring = GuestAddress(avail_ring);
self.vrings[index as usize].write().unwrap().queue.used_ring = GuestAddress(used_ring);
Ok(())
} else {
Err(VhostUserError::InvalidParam)
}
}
fn set_vring_base(&mut self, index: u32, base: u32) -> VhostUserResult<()> {
self.vrings[index as usize]
.write()
.unwrap()
.queue
.next_avail = Wrapping(base as u16);
self.vrings[index as usize].write().unwrap().queue.next_used = Wrapping(base as u16);
Ok(())
}
fn get_vring_base(&mut self, index: u32) -> VhostUserResult<VhostUserVringState> {
if index as usize >= self.num_queues {
return Err(VhostUserError::InvalidParam);
}
// Quote from vhost-user specification:
// Client must start ring upon receiving a kick (that is, detecting
// that file descriptor is readable) on the descriptor specified by
// VHOST_USER_SET_VRING_KICK, and stop ring upon receiving
// VHOST_USER_GET_VRING_BASE.
self.vrings[index as usize].write().unwrap().queue.ready = false;
if let Some(fd) = self.vrings[index as usize].read().unwrap().kick.as_ref() {
self.worker
.unregister_listener(fd.as_raw_fd(), epoll::Events::EPOLLIN, u64::from(index))
.map_err(VhostUserError::ReqHandlerError)?;
}
let next_avail = self.vrings[index as usize]
.read()
.unwrap()
.queue
.next_avail
.0 as u16;
Ok(VhostUserVringState::new(index, u32::from(next_avail)))
}
fn set_vring_kick(&mut self, index: u8, fd: Option<RawFd>) -> VhostUserResult<()> {
if index as usize >= self.num_queues {
return Err(VhostUserError::InvalidParam);
}
if let Some(kick) = self.vrings[index as usize].write().unwrap().kick.take() {
// Close file descriptor set by previous operations.
let _ = unsafe { libc::close(kick.as_raw_fd()) };
}
self.vrings[index as usize].write().unwrap().kick =
fd.map(|x| unsafe { EventFd::from_raw_fd(x) });
// Quote from vhost-user specification:
// Client must start ring upon receiving a kick (that is, detecting
// that file descriptor is readable) on the descriptor specified by
// VHOST_USER_SET_VRING_KICK, and stop ring upon receiving
// VHOST_USER_GET_VRING_BASE.
self.vrings[index as usize].write().unwrap().queue.ready = true;
if let Some(fd) = self.vrings[index as usize].read().unwrap().kick.as_ref() {
self.worker
.register_listener(fd.as_raw_fd(), epoll::Events::EPOLLIN, u64::from(index))
.map_err(VhostUserError::ReqHandlerError)?;
}
Ok(())
}
fn set_vring_call(&mut self, index: u8, fd: Option<RawFd>) -> VhostUserResult<()> {
if index as usize >= self.num_queues {
return Err(VhostUserError::InvalidParam);
}
if let Some(call) = self.vrings[index as usize].write().unwrap().call.take() {
// Close file descriptor set by previous operations.
let _ = unsafe { libc::close(call.as_raw_fd()) };
}
self.vrings[index as usize].write().unwrap().call =
fd.map(|x| unsafe { EventFd::from_raw_fd(x) });
Ok(())
}
fn set_vring_err(&mut self, index: u8, fd: Option<RawFd>) -> VhostUserResult<()> {
if index as usize >= self.num_queues {
return Err(VhostUserError::InvalidParam);
}
if let Some(err) = self.vrings[index as usize].write().unwrap().err.take() {
// Close file descriptor set by previous operations.
let _ = unsafe { libc::close(err.as_raw_fd()) };
}
self.vrings[index as usize].write().unwrap().err =
fd.map(|x| unsafe { EventFd::from_raw_fd(x) });
Ok(())
}
fn set_vring_enable(&mut self, index: u32, enable: bool) -> VhostUserResult<()> {
// This request should be handled only when VHOST_USER_F_PROTOCOL_FEATURES
// has been negotiated.
if self.acked_features & VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits() == 0 {
return Err(VhostUserError::InvalidOperation);
} else if index as usize >= self.num_queues {
return Err(VhostUserError::InvalidParam);
}
// Slave must not pass data to/from the backend until ring is
// enabled by VHOST_USER_SET_VRING_ENABLE with parameter 1,
// or after it has been disabled by VHOST_USER_SET_VRING_ENABLE
// with parameter 0.
self.vrings[index as usize].write().unwrap().enabled = enable;
Ok(())
}
fn get_config(
&mut self,
offset: u32,
size: u32,
_flags: VhostUserConfigFlags,
) -> 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))
}
fn set_config(
&mut self,
offset: u32,
buf: &[u8],
_flags: VhostUserConfigFlags,
) -> 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
.write()
.unwrap()
.set_config(offset, buf)
.map_err(VhostUserError::ReqHandlerError)
}
}

View File

@@ -1,6 +0,0 @@
[package]
name = "virtio-bindings"
version = "0.1.0"
authors = ["The Chromium OS Authors"]
[dependencies]

View File

@@ -1,31 +0,0 @@
From 67247b7abc56a11d8cae7eb354994d818fb72e93 Mon Sep 17 00:00:00 2001
From: Andreea Florescu <fandree@amazon.com>
Date: Tue, 15 Jan 2019 18:30:07 +0200
Subject: [PATCH] virtio_gen: remove derive Debug from packed struct
Bindgen automatically adds derive debug on virtio_net_ctrl_mac, a packed
structure. This generates a warning while building.
Manually remove the Debug derive.
Signed-off-by: Andreea Florescu <fandree@amazon.com>
---
virtio_gen/src/virtio_net.rs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/virtio_gen/src/virtio_net.rs b/virtio_gen/src/virtio_net.rs
index 0b68d09..a1c9dca 100644
--- a/virtio_gen/src/virtio_net.rs
+++ b/virtio_gen/src/virtio_net.rs
@@ -681,7 +681,7 @@ fn bindgen_test_layout_virtio_net_ctrl_hdr() {
}
pub type virtio_net_ctrl_ack = __u8;
#[repr(C, packed)]
-#[derive(Debug, Default)]
+#[derive(Default)]
pub struct virtio_net_ctrl_mac {
pub entries: __virtio32,
pub macs: __IncompleteArrayField<[__u8; 6usize]>,
--
2.7.4

View File

@@ -1,15 +0,0 @@
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
//
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE-BSD-3-Clause file.
#![allow(clippy::all)]
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
pub mod virtio_blk;
pub mod virtio_net;
pub mod virtio_ring;

View File

@@ -1,486 +0,0 @@
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
/* automatically generated by rust-bindgen */
pub const __BITS_PER_LONG: u32 = 64;
pub const __FD_SETSIZE: u32 = 1024;
pub const VIRTIO_ID_NET: u32 = 1;
pub const VIRTIO_ID_BLOCK: u32 = 2;
pub const VIRTIO_ID_CONSOLE: u32 = 3;
pub const VIRTIO_ID_RNG: u32 = 4;
pub const VIRTIO_ID_BALLOON: u32 = 5;
pub const VIRTIO_ID_RPMSG: u32 = 7;
pub const VIRTIO_ID_SCSI: u32 = 8;
pub const VIRTIO_ID_9P: u32 = 9;
pub const VIRTIO_ID_RPROC_SERIAL: u32 = 11;
pub const VIRTIO_ID_CAIF: u32 = 12;
pub const VIRTIO_ID_GPU: u32 = 16;
pub const VIRTIO_ID_INPUT: u32 = 18;
pub const VIRTIO_CONFIG_S_ACKNOWLEDGE: u32 = 1;
pub const VIRTIO_CONFIG_S_DRIVER: u32 = 2;
pub const VIRTIO_CONFIG_S_DRIVER_OK: u32 = 4;
pub const VIRTIO_CONFIG_S_FEATURES_OK: u32 = 8;
pub const VIRTIO_CONFIG_S_FAILED: u32 = 128;
pub const VIRTIO_TRANSPORT_F_START: u32 = 28;
pub const VIRTIO_TRANSPORT_F_END: u32 = 33;
pub const VIRTIO_F_NOTIFY_ON_EMPTY: u32 = 24;
pub const VIRTIO_F_ANY_LAYOUT: u32 = 27;
pub const VIRTIO_F_VERSION_1: u32 = 32;
pub const VIRTIO_BLK_F_SIZE_MAX: u32 = 1;
pub const VIRTIO_BLK_F_SEG_MAX: u32 = 2;
pub const VIRTIO_BLK_F_GEOMETRY: u32 = 4;
pub const VIRTIO_BLK_F_RO: u32 = 5;
pub const VIRTIO_BLK_F_BLK_SIZE: u32 = 6;
pub const VIRTIO_BLK_F_TOPOLOGY: u32 = 10;
pub const VIRTIO_BLK_F_MQ: u32 = 12;
pub const VIRTIO_BLK_F_BARRIER: u32 = 0;
pub const VIRTIO_BLK_F_SCSI: u32 = 7;
pub const VIRTIO_BLK_F_FLUSH: u32 = 9;
pub const VIRTIO_BLK_F_CONFIG_WCE: u32 = 11;
pub const VIRTIO_BLK_F_WCE: u32 = 9;
pub const VIRTIO_BLK_ID_BYTES: u32 = 20;
pub const VIRTIO_BLK_T_IN: u32 = 0;
pub const VIRTIO_BLK_T_OUT: u32 = 1;
pub const VIRTIO_BLK_T_SCSI_CMD: u32 = 2;
pub const VIRTIO_BLK_T_FLUSH: u32 = 4;
pub const VIRTIO_BLK_T_GET_ID: u32 = 8;
pub const VIRTIO_BLK_T_BARRIER: u32 = 2147483648;
pub const VIRTIO_BLK_S_OK: u32 = 0;
pub const VIRTIO_BLK_S_IOERR: u32 = 1;
pub const VIRTIO_BLK_S_UNSUPP: u32 = 2;
pub type __s8 = ::std::os::raw::c_schar;
pub type __u8 = ::std::os::raw::c_uchar;
pub type __s16 = ::std::os::raw::c_short;
pub type __u16 = ::std::os::raw::c_ushort;
pub type __s32 = ::std::os::raw::c_int;
pub type __u32 = ::std::os::raw::c_uint;
pub type __s64 = ::std::os::raw::c_longlong;
pub type __u64 = ::std::os::raw::c_ulonglong;
#[repr(C)]
#[derive(Debug, Default, Copy, Clone, PartialEq)]
pub struct __kernel_fd_set {
pub fds_bits: [::std::os::raw::c_ulong; 16usize],
}
#[test]
fn bindgen_test_layout___kernel_fd_set() {
assert_eq!(
::std::mem::size_of::<__kernel_fd_set>(),
128usize,
concat!("Size of: ", stringify!(__kernel_fd_set))
);
assert_eq!(
::std::mem::align_of::<__kernel_fd_set>(),
8usize,
concat!("Alignment of ", stringify!(__kernel_fd_set))
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<__kernel_fd_set>())).fds_bits as *const _ as usize },
0usize,
concat!(
"Offset of field: ",
stringify!(__kernel_fd_set),
"::",
stringify!(fds_bits)
)
);
}
pub type __kernel_sighandler_t =
::std::option::Option<unsafe extern "C" fn(arg1: ::std::os::raw::c_int)>;
pub type __kernel_key_t = ::std::os::raw::c_int;
pub type __kernel_mqd_t = ::std::os::raw::c_int;
pub type __kernel_old_uid_t = ::std::os::raw::c_ushort;
pub type __kernel_old_gid_t = ::std::os::raw::c_ushort;
pub type __kernel_old_dev_t = ::std::os::raw::c_ulong;
pub type __kernel_long_t = ::std::os::raw::c_long;
pub type __kernel_ulong_t = ::std::os::raw::c_ulong;
pub type __kernel_ino_t = __kernel_ulong_t;
pub type __kernel_mode_t = ::std::os::raw::c_uint;
pub type __kernel_pid_t = ::std::os::raw::c_int;
pub type __kernel_ipc_pid_t = ::std::os::raw::c_int;
pub type __kernel_uid_t = ::std::os::raw::c_uint;
pub type __kernel_gid_t = ::std::os::raw::c_uint;
pub type __kernel_suseconds_t = __kernel_long_t;
pub type __kernel_daddr_t = ::std::os::raw::c_int;
pub type __kernel_uid32_t = ::std::os::raw::c_uint;
pub type __kernel_gid32_t = ::std::os::raw::c_uint;
pub type __kernel_size_t = __kernel_ulong_t;
pub type __kernel_ssize_t = __kernel_long_t;
pub type __kernel_ptrdiff_t = __kernel_long_t;
#[repr(C)]
#[derive(Debug, Default, Copy, Clone, PartialEq)]
pub struct __kernel_fsid_t {
pub val: [::std::os::raw::c_int; 2usize],
}
#[test]
fn bindgen_test_layout___kernel_fsid_t() {
assert_eq!(
::std::mem::size_of::<__kernel_fsid_t>(),
8usize,
concat!("Size of: ", stringify!(__kernel_fsid_t))
);
assert_eq!(
::std::mem::align_of::<__kernel_fsid_t>(),
4usize,
concat!("Alignment of ", stringify!(__kernel_fsid_t))
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<__kernel_fsid_t>())).val as *const _ as usize },
0usize,
concat!(
"Offset of field: ",
stringify!(__kernel_fsid_t),
"::",
stringify!(val)
)
);
}
pub type __kernel_off_t = __kernel_long_t;
pub type __kernel_loff_t = ::std::os::raw::c_longlong;
pub type __kernel_time_t = __kernel_long_t;
pub type __kernel_clock_t = __kernel_long_t;
pub type __kernel_timer_t = ::std::os::raw::c_int;
pub type __kernel_clockid_t = ::std::os::raw::c_int;
pub type __kernel_caddr_t = *mut ::std::os::raw::c_char;
pub type __kernel_uid16_t = ::std::os::raw::c_ushort;
pub type __kernel_gid16_t = ::std::os::raw::c_ushort;
pub type __le16 = __u16;
pub type __be16 = __u16;
pub type __le32 = __u32;
pub type __be32 = __u32;
pub type __le64 = __u64;
pub type __be64 = __u64;
pub type __sum16 = __u16;
pub type __wsum = __u32;
pub type __virtio16 = __u16;
pub type __virtio32 = __u32;
pub type __virtio64 = __u64;
#[repr(C, packed)]
#[derive(Debug, Default, Copy, Clone, PartialEq)]
pub struct virtio_blk_config {
pub capacity: __u64,
pub size_max: __u32,
pub seg_max: __u32,
pub geometry: virtio_blk_config_virtio_blk_geometry,
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,
pub unused: __u8,
pub num_queues: __u16,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone, PartialEq)]
pub struct virtio_blk_config_virtio_blk_geometry {
pub cylinders: __u16,
pub heads: __u8,
pub sectors: __u8,
}
#[test]
fn bindgen_test_layout_virtio_blk_config_virtio_blk_geometry() {
assert_eq!(
::std::mem::size_of::<virtio_blk_config_virtio_blk_geometry>(),
4usize,
concat!(
"Size of: ",
stringify!(virtio_blk_config_virtio_blk_geometry)
)
);
assert_eq!(
::std::mem::align_of::<virtio_blk_config_virtio_blk_geometry>(),
2usize,
concat!(
"Alignment of ",
stringify!(virtio_blk_config_virtio_blk_geometry)
)
);
assert_eq!(
unsafe {
&(*(::std::ptr::null::<virtio_blk_config_virtio_blk_geometry>())).cylinders as *const _
as usize
},
0usize,
concat!(
"Offset of field: ",
stringify!(virtio_blk_config_virtio_blk_geometry),
"::",
stringify!(cylinders)
)
);
assert_eq!(
unsafe {
&(*(::std::ptr::null::<virtio_blk_config_virtio_blk_geometry>())).heads as *const _
as usize
},
2usize,
concat!(
"Offset of field: ",
stringify!(virtio_blk_config_virtio_blk_geometry),
"::",
stringify!(heads)
)
);
assert_eq!(
unsafe {
&(*(::std::ptr::null::<virtio_blk_config_virtio_blk_geometry>())).sectors as *const _
as usize
},
3usize,
concat!(
"Offset of field: ",
stringify!(virtio_blk_config_virtio_blk_geometry),
"::",
stringify!(sectors)
)
);
}
#[test]
fn bindgen_test_layout_virtio_blk_config() {
assert_eq!(
::std::mem::size_of::<virtio_blk_config>(),
36usize,
concat!("Size of: ", stringify!(virtio_blk_config))
);
assert_eq!(
::std::mem::align_of::<virtio_blk_config>(),
1usize,
concat!("Alignment of ", stringify!(virtio_blk_config))
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<virtio_blk_config>())).capacity as *const _ as usize },
0usize,
concat!(
"Offset of field: ",
stringify!(virtio_blk_config),
"::",
stringify!(capacity)
)
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<virtio_blk_config>())).size_max as *const _ as usize },
8usize,
concat!(
"Offset of field: ",
stringify!(virtio_blk_config),
"::",
stringify!(size_max)
)
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<virtio_blk_config>())).seg_max as *const _ as usize },
12usize,
concat!(
"Offset of field: ",
stringify!(virtio_blk_config),
"::",
stringify!(seg_max)
)
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<virtio_blk_config>())).geometry as *const _ as usize },
16usize,
concat!(
"Offset of field: ",
stringify!(virtio_blk_config),
"::",
stringify!(geometry)
)
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<virtio_blk_config>())).blk_size as *const _ as usize },
20usize,
concat!(
"Offset of field: ",
stringify!(virtio_blk_config),
"::",
stringify!(blk_size)
)
);
assert_eq!(
unsafe {
&(*(::std::ptr::null::<virtio_blk_config>())).physical_block_exp as *const _ as usize
},
24usize,
concat!(
"Offset of field: ",
stringify!(virtio_blk_config),
"::",
stringify!(physical_block_exp)
)
);
assert_eq!(
unsafe {
&(*(::std::ptr::null::<virtio_blk_config>())).alignment_offset as *const _ as usize
},
25usize,
concat!(
"Offset of field: ",
stringify!(virtio_blk_config),
"::",
stringify!(alignment_offset)
)
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<virtio_blk_config>())).min_io_size as *const _ as usize },
26usize,
concat!(
"Offset of field: ",
stringify!(virtio_blk_config),
"::",
stringify!(min_io_size)
)
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<virtio_blk_config>())).opt_io_size as *const _ as usize },
28usize,
concat!(
"Offset of field: ",
stringify!(virtio_blk_config),
"::",
stringify!(opt_io_size)
)
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<virtio_blk_config>())).wce as *const _ as usize },
32usize,
concat!(
"Offset of field: ",
stringify!(virtio_blk_config),
"::",
stringify!(wce)
)
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<virtio_blk_config>())).unused as *const _ as usize },
33usize,
concat!(
"Offset of field: ",
stringify!(virtio_blk_config),
"::",
stringify!(unused)
)
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<virtio_blk_config>())).num_queues as *const _ as usize },
34usize,
concat!(
"Offset of field: ",
stringify!(virtio_blk_config),
"::",
stringify!(num_queues)
)
);
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone, PartialEq)]
pub struct virtio_blk_outhdr {
pub type_: __virtio32,
pub ioprio: __virtio32,
pub sector: __virtio64,
}
#[test]
fn bindgen_test_layout_virtio_blk_outhdr() {
assert_eq!(
::std::mem::size_of::<virtio_blk_outhdr>(),
16usize,
concat!("Size of: ", stringify!(virtio_blk_outhdr))
);
assert_eq!(
::std::mem::align_of::<virtio_blk_outhdr>(),
8usize,
concat!("Alignment of ", stringify!(virtio_blk_outhdr))
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<virtio_blk_outhdr>())).type_ as *const _ as usize },
0usize,
concat!(
"Offset of field: ",
stringify!(virtio_blk_outhdr),
"::",
stringify!(type_)
)
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<virtio_blk_outhdr>())).ioprio as *const _ as usize },
4usize,
concat!(
"Offset of field: ",
stringify!(virtio_blk_outhdr),
"::",
stringify!(ioprio)
)
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<virtio_blk_outhdr>())).sector as *const _ as usize },
8usize,
concat!(
"Offset of field: ",
stringify!(virtio_blk_outhdr),
"::",
stringify!(sector)
)
);
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone, PartialEq)]
pub struct virtio_scsi_inhdr {
pub errors: __virtio32,
pub data_len: __virtio32,
pub sense_len: __virtio32,
pub residual: __virtio32,
}
#[test]
fn bindgen_test_layout_virtio_scsi_inhdr() {
assert_eq!(
::std::mem::size_of::<virtio_scsi_inhdr>(),
16usize,
concat!("Size of: ", stringify!(virtio_scsi_inhdr))
);
assert_eq!(
::std::mem::align_of::<virtio_scsi_inhdr>(),
4usize,
concat!("Alignment of ", stringify!(virtio_scsi_inhdr))
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<virtio_scsi_inhdr>())).errors as *const _ as usize },
0usize,
concat!(
"Offset of field: ",
stringify!(virtio_scsi_inhdr),
"::",
stringify!(errors)
)
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<virtio_scsi_inhdr>())).data_len as *const _ as usize },
4usize,
concat!(
"Offset of field: ",
stringify!(virtio_scsi_inhdr),
"::",
stringify!(data_len)
)
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<virtio_scsi_inhdr>())).sense_len as *const _ as usize },
8usize,
concat!(
"Offset of field: ",
stringify!(virtio_scsi_inhdr),
"::",
stringify!(sense_len)
)
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<virtio_scsi_inhdr>())).residual as *const _ as usize },
12usize,
concat!(
"Offset of field: ",
stringify!(virtio_scsi_inhdr),
"::",
stringify!(residual)
)
);
}

View File

@@ -1,734 +0,0 @@
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
/* automatically generated by rust-bindgen */
#[repr(C)]
#[derive(Default)]
pub struct __IncompleteArrayField<T>(::std::marker::PhantomData<T>, [T; 0]);
impl<T> __IncompleteArrayField<T> {
#[inline]
pub fn new() -> Self {
__IncompleteArrayField(::std::marker::PhantomData, [])
}
#[inline]
pub unsafe fn as_ptr(&self) -> *const T {
::std::mem::transmute(self)
}
#[inline]
pub unsafe fn as_mut_ptr(&mut self) -> *mut T {
::std::mem::transmute(self)
}
#[inline]
pub unsafe fn as_slice(&self, len: usize) -> &[T] {
::std::slice::from_raw_parts(self.as_ptr(), len)
}
#[inline]
pub unsafe fn as_mut_slice(&mut self, len: usize) -> &mut [T] {
::std::slice::from_raw_parts_mut(self.as_mut_ptr(), len)
}
}
impl<T> ::std::fmt::Debug for __IncompleteArrayField<T> {
fn fmt(&self, fmt: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
fmt.write_str("__IncompleteArrayField")
}
}
impl<T> ::std::clone::Clone for __IncompleteArrayField<T> {
#[inline]
fn clone(&self) -> Self {
Self::new()
}
}
pub const __BITS_PER_LONG: u32 = 64;
pub const __FD_SETSIZE: u32 = 1024;
pub const VIRTIO_ID_NET: u32 = 1;
pub const VIRTIO_ID_BLOCK: u32 = 2;
pub const VIRTIO_ID_CONSOLE: u32 = 3;
pub const VIRTIO_ID_RNG: u32 = 4;
pub const VIRTIO_ID_BALLOON: u32 = 5;
pub const VIRTIO_ID_RPMSG: u32 = 7;
pub const VIRTIO_ID_SCSI: u32 = 8;
pub const VIRTIO_ID_9P: u32 = 9;
pub const VIRTIO_ID_RPROC_SERIAL: u32 = 11;
pub const VIRTIO_ID_CAIF: u32 = 12;
pub const VIRTIO_ID_GPU: u32 = 16;
pub const VIRTIO_ID_INPUT: u32 = 18;
pub const VIRTIO_CONFIG_S_ACKNOWLEDGE: u32 = 1;
pub const VIRTIO_CONFIG_S_DRIVER: u32 = 2;
pub const VIRTIO_CONFIG_S_DRIVER_OK: u32 = 4;
pub const VIRTIO_CONFIG_S_FEATURES_OK: u32 = 8;
pub const VIRTIO_CONFIG_S_FAILED: u32 = 128;
pub const VIRTIO_TRANSPORT_F_START: u32 = 28;
pub const VIRTIO_TRANSPORT_F_END: u32 = 33;
pub const VIRTIO_F_NOTIFY_ON_EMPTY: u32 = 24;
pub const VIRTIO_F_ANY_LAYOUT: u32 = 27;
pub const VIRTIO_F_VERSION_1: u32 = 32;
pub const ETH_ALEN: u32 = 6;
pub const ETH_TLEN: u32 = 2;
pub const ETH_HLEN: u32 = 14;
pub const ETH_ZLEN: u32 = 60;
pub const ETH_DATA_LEN: u32 = 1500;
pub const ETH_FRAME_LEN: u32 = 1514;
pub const ETH_FCS_LEN: u32 = 4;
pub const ETH_P_LOOP: u32 = 96;
pub const ETH_P_PUP: u32 = 512;
pub const ETH_P_PUPAT: u32 = 513;
pub const ETH_P_TSN: u32 = 8944;
pub const ETH_P_IP: u32 = 2048;
pub const ETH_P_X25: u32 = 2053;
pub const ETH_P_ARP: u32 = 2054;
pub const ETH_P_BPQ: u32 = 2303;
pub const ETH_P_IEEEPUP: u32 = 2560;
pub const ETH_P_IEEEPUPAT: u32 = 2561;
pub const ETH_P_BATMAN: u32 = 17157;
pub const ETH_P_DEC: u32 = 24576;
pub const ETH_P_DNA_DL: u32 = 24577;
pub const ETH_P_DNA_RC: u32 = 24578;
pub const ETH_P_DNA_RT: u32 = 24579;
pub const ETH_P_LAT: u32 = 24580;
pub const ETH_P_DIAG: u32 = 24581;
pub const ETH_P_CUST: u32 = 24582;
pub const ETH_P_SCA: u32 = 24583;
pub const ETH_P_TEB: u32 = 25944;
pub const ETH_P_RARP: u32 = 32821;
pub const ETH_P_ATALK: u32 = 32923;
pub const ETH_P_AARP: u32 = 33011;
pub const ETH_P_8021Q: u32 = 33024;
pub const ETH_P_IPX: u32 = 33079;
pub const ETH_P_IPV6: u32 = 34525;
pub const ETH_P_PAUSE: u32 = 34824;
pub const ETH_P_SLOW: u32 = 34825;
pub const ETH_P_WCCP: u32 = 34878;
pub const ETH_P_MPLS_UC: u32 = 34887;
pub const ETH_P_MPLS_MC: u32 = 34888;
pub const ETH_P_ATMMPOA: u32 = 34892;
pub const ETH_P_PPP_DISC: u32 = 34915;
pub const ETH_P_PPP_SES: u32 = 34916;
pub const ETH_P_LINK_CTL: u32 = 34924;
pub const ETH_P_ATMFATE: u32 = 34948;
pub const ETH_P_PAE: u32 = 34958;
pub const ETH_P_AOE: u32 = 34978;
pub const ETH_P_8021AD: u32 = 34984;
pub const ETH_P_802_EX1: u32 = 34997;
pub const ETH_P_TIPC: u32 = 35018;
pub const ETH_P_8021AH: u32 = 35047;
pub const ETH_P_MVRP: u32 = 35061;
pub const ETH_P_1588: u32 = 35063;
pub const ETH_P_PRP: u32 = 35067;
pub const ETH_P_FCOE: u32 = 35078;
pub const ETH_P_TDLS: u32 = 35085;
pub const ETH_P_FIP: u32 = 35092;
pub const ETH_P_80221: u32 = 35095;
pub const ETH_P_LOOPBACK: u32 = 36864;
pub const ETH_P_QINQ1: u32 = 37120;
pub const ETH_P_QINQ2: u32 = 37376;
pub const ETH_P_QINQ3: u32 = 37632;
pub const ETH_P_EDSA: u32 = 56026;
pub const ETH_P_AF_IUCV: u32 = 64507;
pub const ETH_P_802_3_MIN: u32 = 1536;
pub const ETH_P_802_3: u32 = 1;
pub const ETH_P_AX25: u32 = 2;
pub const ETH_P_ALL: u32 = 3;
pub const ETH_P_802_2: u32 = 4;
pub const ETH_P_SNAP: u32 = 5;
pub const ETH_P_DDCMP: u32 = 6;
pub const ETH_P_WAN_PPP: u32 = 7;
pub const ETH_P_PPP_MP: u32 = 8;
pub const ETH_P_LOCALTALK: u32 = 9;
pub const ETH_P_CAN: u32 = 12;
pub const ETH_P_CANFD: u32 = 13;
pub const ETH_P_PPPTALK: u32 = 16;
pub const ETH_P_TR_802_2: u32 = 17;
pub const ETH_P_MOBITEX: u32 = 21;
pub const ETH_P_CONTROL: u32 = 22;
pub const ETH_P_IRDA: u32 = 23;
pub const ETH_P_ECONET: u32 = 24;
pub const ETH_P_HDLC: u32 = 25;
pub const ETH_P_ARCNET: u32 = 26;
pub const ETH_P_DSA: u32 = 27;
pub const ETH_P_TRAILER: u32 = 28;
pub const ETH_P_PHONET: u32 = 245;
pub const ETH_P_IEEE802154: u32 = 246;
pub const ETH_P_CAIF: u32 = 247;
pub const ETH_P_XDSA: u32 = 248;
pub const VIRTIO_NET_F_CSUM: u32 = 0;
pub const VIRTIO_NET_F_GUEST_CSUM: u32 = 1;
pub const VIRTIO_NET_F_CTRL_GUEST_OFFLOADS: u32 = 2;
pub const VIRTIO_NET_F_MTU: u32 = 3;
pub const VIRTIO_NET_F_MAC: u32 = 5;
pub const VIRTIO_NET_F_GUEST_TSO4: u32 = 7;
pub const VIRTIO_NET_F_GUEST_TSO6: u32 = 8;
pub const VIRTIO_NET_F_GUEST_ECN: u32 = 9;
pub const VIRTIO_NET_F_GUEST_UFO: u32 = 10;
pub const VIRTIO_NET_F_HOST_TSO4: u32 = 11;
pub const VIRTIO_NET_F_HOST_TSO6: u32 = 12;
pub const VIRTIO_NET_F_HOST_ECN: u32 = 13;
pub const VIRTIO_NET_F_HOST_UFO: u32 = 14;
pub const VIRTIO_NET_F_MRG_RXBUF: u32 = 15;
pub const VIRTIO_NET_F_STATUS: u32 = 16;
pub const VIRTIO_NET_F_CTRL_VQ: u32 = 17;
pub const VIRTIO_NET_F_CTRL_RX: u32 = 18;
pub const VIRTIO_NET_F_CTRL_VLAN: u32 = 19;
pub const VIRTIO_NET_F_CTRL_RX_EXTRA: u32 = 20;
pub const VIRTIO_NET_F_GUEST_ANNOUNCE: u32 = 21;
pub const VIRTIO_NET_F_MQ: u32 = 22;
pub const VIRTIO_NET_F_CTRL_MAC_ADDR: u32 = 23;
pub const VIRTIO_NET_F_GSO: u32 = 6;
pub const VIRTIO_NET_S_LINK_UP: u32 = 1;
pub const VIRTIO_NET_S_ANNOUNCE: u32 = 2;
pub const VIRTIO_NET_HDR_F_NEEDS_CSUM: u32 = 1;
pub const VIRTIO_NET_HDR_F_DATA_VALID: u32 = 2;
pub const VIRTIO_NET_HDR_GSO_NONE: u32 = 0;
pub const VIRTIO_NET_HDR_GSO_TCPV4: u32 = 1;
pub const VIRTIO_NET_HDR_GSO_UDP: u32 = 3;
pub const VIRTIO_NET_HDR_GSO_TCPV6: u32 = 4;
pub const VIRTIO_NET_HDR_GSO_ECN: u32 = 128;
pub const VIRTIO_NET_OK: u32 = 0;
pub const VIRTIO_NET_ERR: u32 = 1;
pub const VIRTIO_NET_CTRL_RX: u32 = 0;
pub const VIRTIO_NET_CTRL_RX_PROMISC: u32 = 0;
pub const VIRTIO_NET_CTRL_RX_ALLMULTI: u32 = 1;
pub const VIRTIO_NET_CTRL_RX_ALLUNI: u32 = 2;
pub const VIRTIO_NET_CTRL_RX_NOMULTI: u32 = 3;
pub const VIRTIO_NET_CTRL_RX_NOUNI: u32 = 4;
pub const VIRTIO_NET_CTRL_RX_NOBCAST: u32 = 5;
pub const VIRTIO_NET_CTRL_MAC: u32 = 1;
pub const VIRTIO_NET_CTRL_MAC_TABLE_SET: u32 = 0;
pub const VIRTIO_NET_CTRL_MAC_ADDR_SET: u32 = 1;
pub const VIRTIO_NET_CTRL_VLAN: u32 = 2;
pub const VIRTIO_NET_CTRL_VLAN_ADD: u32 = 0;
pub const VIRTIO_NET_CTRL_VLAN_DEL: u32 = 1;
pub const VIRTIO_NET_CTRL_ANNOUNCE: u32 = 3;
pub const VIRTIO_NET_CTRL_ANNOUNCE_ACK: u32 = 0;
pub const VIRTIO_NET_CTRL_MQ: u32 = 4;
pub const VIRTIO_NET_CTRL_MQ_VQ_PAIRS_SET: u32 = 0;
pub const VIRTIO_NET_CTRL_MQ_VQ_PAIRS_MIN: u32 = 1;
pub const VIRTIO_NET_CTRL_MQ_VQ_PAIRS_MAX: u32 = 32768;
pub const VIRTIO_NET_CTRL_GUEST_OFFLOADS: u32 = 5;
pub const VIRTIO_NET_CTRL_GUEST_OFFLOADS_SET: u32 = 0;
pub type __s8 = ::std::os::raw::c_schar;
pub type __u8 = ::std::os::raw::c_uchar;
pub type __s16 = ::std::os::raw::c_short;
pub type __u16 = ::std::os::raw::c_ushort;
pub type __s32 = ::std::os::raw::c_int;
pub type __u32 = ::std::os::raw::c_uint;
pub type __s64 = ::std::os::raw::c_longlong;
pub type __u64 = ::std::os::raw::c_ulonglong;
#[repr(C)]
#[derive(Debug, Default, Copy, Clone, PartialEq)]
pub struct __kernel_fd_set {
pub fds_bits: [::std::os::raw::c_ulong; 16usize],
}
#[test]
fn bindgen_test_layout___kernel_fd_set() {
assert_eq!(
::std::mem::size_of::<__kernel_fd_set>(),
128usize,
concat!("Size of: ", stringify!(__kernel_fd_set))
);
assert_eq!(
::std::mem::align_of::<__kernel_fd_set>(),
8usize,
concat!("Alignment of ", stringify!(__kernel_fd_set))
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<__kernel_fd_set>())).fds_bits as *const _ as usize },
0usize,
concat!(
"Offset of field: ",
stringify!(__kernel_fd_set),
"::",
stringify!(fds_bits)
)
);
}
pub type __kernel_sighandler_t =
::std::option::Option<unsafe extern "C" fn(arg1: ::std::os::raw::c_int)>;
pub type __kernel_key_t = ::std::os::raw::c_int;
pub type __kernel_mqd_t = ::std::os::raw::c_int;
pub type __kernel_old_uid_t = ::std::os::raw::c_ushort;
pub type __kernel_old_gid_t = ::std::os::raw::c_ushort;
pub type __kernel_old_dev_t = ::std::os::raw::c_ulong;
pub type __kernel_long_t = ::std::os::raw::c_long;
pub type __kernel_ulong_t = ::std::os::raw::c_ulong;
pub type __kernel_ino_t = __kernel_ulong_t;
pub type __kernel_mode_t = ::std::os::raw::c_uint;
pub type __kernel_pid_t = ::std::os::raw::c_int;
pub type __kernel_ipc_pid_t = ::std::os::raw::c_int;
pub type __kernel_uid_t = ::std::os::raw::c_uint;
pub type __kernel_gid_t = ::std::os::raw::c_uint;
pub type __kernel_suseconds_t = __kernel_long_t;
pub type __kernel_daddr_t = ::std::os::raw::c_int;
pub type __kernel_uid32_t = ::std::os::raw::c_uint;
pub type __kernel_gid32_t = ::std::os::raw::c_uint;
pub type __kernel_size_t = __kernel_ulong_t;
pub type __kernel_ssize_t = __kernel_long_t;
pub type __kernel_ptrdiff_t = __kernel_long_t;
#[repr(C)]
#[derive(Debug, Default, Copy, Clone, PartialEq)]
pub struct __kernel_fsid_t {
pub val: [::std::os::raw::c_int; 2usize],
}
#[test]
fn bindgen_test_layout___kernel_fsid_t() {
assert_eq!(
::std::mem::size_of::<__kernel_fsid_t>(),
8usize,
concat!("Size of: ", stringify!(__kernel_fsid_t))
);
assert_eq!(
::std::mem::align_of::<__kernel_fsid_t>(),
4usize,
concat!("Alignment of ", stringify!(__kernel_fsid_t))
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<__kernel_fsid_t>())).val as *const _ as usize },
0usize,
concat!(
"Offset of field: ",
stringify!(__kernel_fsid_t),
"::",
stringify!(val)
)
);
}
pub type __kernel_off_t = __kernel_long_t;
pub type __kernel_loff_t = ::std::os::raw::c_longlong;
pub type __kernel_time_t = __kernel_long_t;
pub type __kernel_clock_t = __kernel_long_t;
pub type __kernel_timer_t = ::std::os::raw::c_int;
pub type __kernel_clockid_t = ::std::os::raw::c_int;
pub type __kernel_caddr_t = *mut ::std::os::raw::c_char;
pub type __kernel_uid16_t = ::std::os::raw::c_ushort;
pub type __kernel_gid16_t = ::std::os::raw::c_ushort;
pub type __le16 = __u16;
pub type __be16 = __u16;
pub type __le32 = __u32;
pub type __be32 = __u32;
pub type __le64 = __u64;
pub type __be64 = __u64;
pub type __sum16 = __u16;
pub type __wsum = __u32;
pub type __virtio16 = __u16;
pub type __virtio32 = __u32;
pub type __virtio64 = __u64;
#[repr(C, packed)]
#[derive(Debug, Default, Copy, Clone, PartialEq)]
pub struct ethhdr {
pub h_dest: [::std::os::raw::c_uchar; 6usize],
pub h_source: [::std::os::raw::c_uchar; 6usize],
pub h_proto: __be16,
}
#[test]
fn bindgen_test_layout_ethhdr() {
assert_eq!(
::std::mem::size_of::<ethhdr>(),
14usize,
concat!("Size of: ", stringify!(ethhdr))
);
assert_eq!(
::std::mem::align_of::<ethhdr>(),
1usize,
concat!("Alignment of ", stringify!(ethhdr))
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<ethhdr>())).h_dest as *const _ as usize },
0usize,
concat!(
"Offset of field: ",
stringify!(ethhdr),
"::",
stringify!(h_dest)
)
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<ethhdr>())).h_source as *const _ as usize },
6usize,
concat!(
"Offset of field: ",
stringify!(ethhdr),
"::",
stringify!(h_source)
)
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<ethhdr>())).h_proto as *const _ as usize },
12usize,
concat!(
"Offset of field: ",
stringify!(ethhdr),
"::",
stringify!(h_proto)
)
);
}
#[repr(C, packed)]
#[derive(Debug, Default, Copy, Clone, PartialEq)]
pub struct virtio_net_config {
pub mac: [__u8; 6usize],
pub status: __u16,
pub max_virtqueue_pairs: __u16,
pub mtu: __u16,
}
#[test]
fn bindgen_test_layout_virtio_net_config() {
assert_eq!(
::std::mem::size_of::<virtio_net_config>(),
12usize,
concat!("Size of: ", stringify!(virtio_net_config))
);
assert_eq!(
::std::mem::align_of::<virtio_net_config>(),
1usize,
concat!("Alignment of ", stringify!(virtio_net_config))
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<virtio_net_config>())).mac as *const _ as usize },
0usize,
concat!(
"Offset of field: ",
stringify!(virtio_net_config),
"::",
stringify!(mac)
)
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<virtio_net_config>())).status as *const _ as usize },
6usize,
concat!(
"Offset of field: ",
stringify!(virtio_net_config),
"::",
stringify!(status)
)
);
assert_eq!(
unsafe {
&(*(::std::ptr::null::<virtio_net_config>())).max_virtqueue_pairs as *const _ as usize
},
8usize,
concat!(
"Offset of field: ",
stringify!(virtio_net_config),
"::",
stringify!(max_virtqueue_pairs)
)
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<virtio_net_config>())).mtu as *const _ as usize },
10usize,
concat!(
"Offset of field: ",
stringify!(virtio_net_config),
"::",
stringify!(mtu)
)
);
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone, PartialEq)]
pub struct virtio_net_hdr_v1 {
pub flags: __u8,
pub gso_type: __u8,
pub hdr_len: __virtio16,
pub gso_size: __virtio16,
pub csum_start: __virtio16,
pub csum_offset: __virtio16,
pub num_buffers: __virtio16,
}
#[test]
fn bindgen_test_layout_virtio_net_hdr_v1() {
assert_eq!(
::std::mem::size_of::<virtio_net_hdr_v1>(),
12usize,
concat!("Size of: ", stringify!(virtio_net_hdr_v1))
);
assert_eq!(
::std::mem::align_of::<virtio_net_hdr_v1>(),
2usize,
concat!("Alignment of ", stringify!(virtio_net_hdr_v1))
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<virtio_net_hdr_v1>())).flags as *const _ as usize },
0usize,
concat!(
"Offset of field: ",
stringify!(virtio_net_hdr_v1),
"::",
stringify!(flags)
)
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<virtio_net_hdr_v1>())).gso_type as *const _ as usize },
1usize,
concat!(
"Offset of field: ",
stringify!(virtio_net_hdr_v1),
"::",
stringify!(gso_type)
)
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<virtio_net_hdr_v1>())).hdr_len as *const _ as usize },
2usize,
concat!(
"Offset of field: ",
stringify!(virtio_net_hdr_v1),
"::",
stringify!(hdr_len)
)
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<virtio_net_hdr_v1>())).gso_size as *const _ as usize },
4usize,
concat!(
"Offset of field: ",
stringify!(virtio_net_hdr_v1),
"::",
stringify!(gso_size)
)
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<virtio_net_hdr_v1>())).csum_start as *const _ as usize },
6usize,
concat!(
"Offset of field: ",
stringify!(virtio_net_hdr_v1),
"::",
stringify!(csum_start)
)
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<virtio_net_hdr_v1>())).csum_offset as *const _ as usize },
8usize,
concat!(
"Offset of field: ",
stringify!(virtio_net_hdr_v1),
"::",
stringify!(csum_offset)
)
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<virtio_net_hdr_v1>())).num_buffers as *const _ as usize },
10usize,
concat!(
"Offset of field: ",
stringify!(virtio_net_hdr_v1),
"::",
stringify!(num_buffers)
)
);
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone, PartialEq)]
pub struct virtio_net_hdr {
pub flags: __u8,
pub gso_type: __u8,
pub hdr_len: __virtio16,
pub gso_size: __virtio16,
pub csum_start: __virtio16,
pub csum_offset: __virtio16,
}
#[test]
fn bindgen_test_layout_virtio_net_hdr() {
assert_eq!(
::std::mem::size_of::<virtio_net_hdr>(),
10usize,
concat!("Size of: ", stringify!(virtio_net_hdr))
);
assert_eq!(
::std::mem::align_of::<virtio_net_hdr>(),
2usize,
concat!("Alignment of ", stringify!(virtio_net_hdr))
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<virtio_net_hdr>())).flags as *const _ as usize },
0usize,
concat!(
"Offset of field: ",
stringify!(virtio_net_hdr),
"::",
stringify!(flags)
)
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<virtio_net_hdr>())).gso_type as *const _ as usize },
1usize,
concat!(
"Offset of field: ",
stringify!(virtio_net_hdr),
"::",
stringify!(gso_type)
)
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<virtio_net_hdr>())).hdr_len as *const _ as usize },
2usize,
concat!(
"Offset of field: ",
stringify!(virtio_net_hdr),
"::",
stringify!(hdr_len)
)
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<virtio_net_hdr>())).gso_size as *const _ as usize },
4usize,
concat!(
"Offset of field: ",
stringify!(virtio_net_hdr),
"::",
stringify!(gso_size)
)
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<virtio_net_hdr>())).csum_start as *const _ as usize },
6usize,
concat!(
"Offset of field: ",
stringify!(virtio_net_hdr),
"::",
stringify!(csum_start)
)
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<virtio_net_hdr>())).csum_offset as *const _ as usize },
8usize,
concat!(
"Offset of field: ",
stringify!(virtio_net_hdr),
"::",
stringify!(csum_offset)
)
);
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone, PartialEq)]
pub struct virtio_net_hdr_mrg_rxbuf {
pub hdr: virtio_net_hdr,
pub num_buffers: __virtio16,
}
#[test]
fn bindgen_test_layout_virtio_net_hdr_mrg_rxbuf() {
assert_eq!(
::std::mem::size_of::<virtio_net_hdr_mrg_rxbuf>(),
12usize,
concat!("Size of: ", stringify!(virtio_net_hdr_mrg_rxbuf))
);
assert_eq!(
::std::mem::align_of::<virtio_net_hdr_mrg_rxbuf>(),
2usize,
concat!("Alignment of ", stringify!(virtio_net_hdr_mrg_rxbuf))
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<virtio_net_hdr_mrg_rxbuf>())).hdr as *const _ as usize },
0usize,
concat!(
"Offset of field: ",
stringify!(virtio_net_hdr_mrg_rxbuf),
"::",
stringify!(hdr)
)
);
assert_eq!(
unsafe {
&(*(::std::ptr::null::<virtio_net_hdr_mrg_rxbuf>())).num_buffers as *const _ as usize
},
10usize,
concat!(
"Offset of field: ",
stringify!(virtio_net_hdr_mrg_rxbuf),
"::",
stringify!(num_buffers)
)
);
}
#[repr(C, packed)]
#[derive(Debug, Default, Copy, Clone, PartialEq)]
pub struct virtio_net_ctrl_hdr {
pub class: __u8,
pub cmd: __u8,
}
#[test]
fn bindgen_test_layout_virtio_net_ctrl_hdr() {
assert_eq!(
::std::mem::size_of::<virtio_net_ctrl_hdr>(),
2usize,
concat!("Size of: ", stringify!(virtio_net_ctrl_hdr))
);
assert_eq!(
::std::mem::align_of::<virtio_net_ctrl_hdr>(),
1usize,
concat!("Alignment of ", stringify!(virtio_net_ctrl_hdr))
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<virtio_net_ctrl_hdr>())).class as *const _ as usize },
0usize,
concat!(
"Offset of field: ",
stringify!(virtio_net_ctrl_hdr),
"::",
stringify!(class)
)
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<virtio_net_ctrl_hdr>())).cmd as *const _ as usize },
1usize,
concat!(
"Offset of field: ",
stringify!(virtio_net_ctrl_hdr),
"::",
stringify!(cmd)
)
);
}
pub type virtio_net_ctrl_ack = __u8;
#[repr(C, packed)]
#[derive(Default)]
pub struct virtio_net_ctrl_mac {
pub entries: __virtio32,
pub macs: __IncompleteArrayField<[__u8; 6usize]>,
}
#[test]
fn bindgen_test_layout_virtio_net_ctrl_mac() {
assert_eq!(
::std::mem::size_of::<virtio_net_ctrl_mac>(),
4usize,
concat!("Size of: ", stringify!(virtio_net_ctrl_mac))
);
assert_eq!(
::std::mem::align_of::<virtio_net_ctrl_mac>(),
1usize,
concat!("Alignment of ", stringify!(virtio_net_ctrl_mac))
);
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone, PartialEq)]
pub struct virtio_net_ctrl_mq {
pub virtqueue_pairs: __virtio16,
}
#[test]
fn bindgen_test_layout_virtio_net_ctrl_mq() {
assert_eq!(
::std::mem::size_of::<virtio_net_ctrl_mq>(),
2usize,
concat!("Size of: ", stringify!(virtio_net_ctrl_mq))
);
assert_eq!(
::std::mem::align_of::<virtio_net_ctrl_mq>(),
2usize,
concat!("Alignment of ", stringify!(virtio_net_ctrl_mq))
);
assert_eq!(
unsafe {
&(*(::std::ptr::null::<virtio_net_ctrl_mq>())).virtqueue_pairs as *const _ as usize
},
0usize,
concat!(
"Offset of field: ",
stringify!(virtio_net_ctrl_mq),
"::",
stringify!(virtqueue_pairs)
)
);
}

View File

@@ -1,453 +0,0 @@
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
/* automatically generated by rust-bindgen */
#[repr(C)]
#[derive(Default)]
pub struct __IncompleteArrayField<T>(::std::marker::PhantomData<T>, [T; 0]);
impl<T> __IncompleteArrayField<T> {
#[inline]
pub fn new() -> Self {
__IncompleteArrayField(::std::marker::PhantomData, [])
}
#[inline]
pub unsafe fn as_ptr(&self) -> *const T {
::std::mem::transmute(self)
}
#[inline]
pub unsafe fn as_mut_ptr(&mut self) -> *mut T {
::std::mem::transmute(self)
}
#[inline]
pub unsafe fn as_slice(&self, len: usize) -> &[T] {
::std::slice::from_raw_parts(self.as_ptr(), len)
}
#[inline]
pub unsafe fn as_mut_slice(&mut self, len: usize) -> &mut [T] {
::std::slice::from_raw_parts_mut(self.as_mut_ptr(), len)
}
}
impl<T> ::std::fmt::Debug for __IncompleteArrayField<T> {
fn fmt(&self, fmt: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
fmt.write_str("__IncompleteArrayField")
}
}
impl<T> ::std::clone::Clone for __IncompleteArrayField<T> {
#[inline]
fn clone(&self) -> Self {
Self::new()
}
}
pub const _STDINT_H: u32 = 1;
pub const _FEATURES_H: u32 = 1;
pub const _DEFAULT_SOURCE: u32 = 1;
pub const __USE_ISOC11: u32 = 1;
pub const __USE_ISOC99: u32 = 1;
pub const __USE_ISOC95: u32 = 1;
pub const __USE_POSIX_IMPLICITLY: u32 = 1;
pub const _POSIX_SOURCE: u32 = 1;
pub const _POSIX_C_SOURCE: u32 = 200809;
pub const __USE_POSIX: u32 = 1;
pub const __USE_POSIX2: u32 = 1;
pub const __USE_POSIX199309: u32 = 1;
pub const __USE_POSIX199506: u32 = 1;
pub const __USE_XOPEN2K: u32 = 1;
pub const __USE_XOPEN2K8: u32 = 1;
pub const _ATFILE_SOURCE: u32 = 1;
pub const __USE_MISC: u32 = 1;
pub const __USE_ATFILE: u32 = 1;
pub const __USE_FORTIFY_LEVEL: u32 = 0;
pub const _STDC_PREDEF_H: u32 = 1;
pub const __STDC_IEC_559__: u32 = 1;
pub const __STDC_IEC_559_COMPLEX__: u32 = 1;
pub const __STDC_ISO_10646__: u32 = 201505;
pub const __STDC_NO_THREADS__: u32 = 1;
pub const __GNU_LIBRARY__: u32 = 6;
pub const __GLIBC__: u32 = 2;
pub const __GLIBC_MINOR__: u32 = 23;
pub const _SYS_CDEFS_H: u32 = 1;
pub const __WORDSIZE: u32 = 64;
pub const __WORDSIZE_TIME64_COMPAT32: u32 = 1;
pub const __SYSCALL_WORDSIZE: u32 = 64;
pub const _BITS_WCHAR_H: u32 = 1;
pub const INT8_MIN: i32 = -128;
pub const INT16_MIN: i32 = -32768;
pub const INT32_MIN: i32 = -2147483648;
pub const INT8_MAX: u32 = 127;
pub const INT16_MAX: u32 = 32767;
pub const INT32_MAX: u32 = 2147483647;
pub const UINT8_MAX: u32 = 255;
pub const UINT16_MAX: u32 = 65535;
pub const UINT32_MAX: u32 = 4294967295;
pub const INT_LEAST8_MIN: i32 = -128;
pub const INT_LEAST16_MIN: i32 = -32768;
pub const INT_LEAST32_MIN: i32 = -2147483648;
pub const INT_LEAST8_MAX: u32 = 127;
pub const INT_LEAST16_MAX: u32 = 32767;
pub const INT_LEAST32_MAX: u32 = 2147483647;
pub const UINT_LEAST8_MAX: u32 = 255;
pub const UINT_LEAST16_MAX: u32 = 65535;
pub const UINT_LEAST32_MAX: u32 = 4294967295;
pub const INT_FAST8_MIN: i32 = -128;
pub const INT_FAST16_MIN: i64 = -9223372036854775808;
pub const INT_FAST32_MIN: i64 = -9223372036854775808;
pub const INT_FAST8_MAX: u32 = 127;
pub const INT_FAST16_MAX: u64 = 9223372036854775807;
pub const INT_FAST32_MAX: u64 = 9223372036854775807;
pub const UINT_FAST8_MAX: u32 = 255;
pub const UINT_FAST16_MAX: i32 = -1;
pub const UINT_FAST32_MAX: i32 = -1;
pub const INTPTR_MIN: i64 = -9223372036854775808;
pub const INTPTR_MAX: u64 = 9223372036854775807;
pub const UINTPTR_MAX: i32 = -1;
pub const PTRDIFF_MIN: i64 = -9223372036854775808;
pub const PTRDIFF_MAX: u64 = 9223372036854775807;
pub const SIG_ATOMIC_MIN: i32 = -2147483648;
pub const SIG_ATOMIC_MAX: u32 = 2147483647;
pub const SIZE_MAX: i32 = -1;
pub const WINT_MIN: u32 = 0;
pub const WINT_MAX: u32 = 4294967295;
pub const __BITS_PER_LONG: u32 = 64;
pub const __FD_SETSIZE: u32 = 1024;
pub const VRING_DESC_F_NEXT: u32 = 1;
pub const VRING_DESC_F_WRITE: u32 = 2;
pub const VRING_DESC_F_INDIRECT: u32 = 4;
pub const VRING_USED_F_NO_NOTIFY: u32 = 1;
pub const VRING_AVAIL_F_NO_INTERRUPT: u32 = 1;
pub const VIRTIO_RING_F_INDIRECT_DESC: u32 = 28;
pub const VIRTIO_RING_F_EVENT_IDX: u32 = 29;
pub const VRING_AVAIL_ALIGN_SIZE: u32 = 2;
pub const VRING_USED_ALIGN_SIZE: u32 = 4;
pub const VRING_DESC_ALIGN_SIZE: u32 = 16;
pub type int_least8_t = ::std::os::raw::c_schar;
pub type int_least16_t = ::std::os::raw::c_short;
pub type int_least32_t = ::std::os::raw::c_int;
pub type int_least64_t = ::std::os::raw::c_long;
pub type uint_least8_t = ::std::os::raw::c_uchar;
pub type uint_least16_t = ::std::os::raw::c_ushort;
pub type uint_least32_t = ::std::os::raw::c_uint;
pub type uint_least64_t = ::std::os::raw::c_ulong;
pub type int_fast8_t = ::std::os::raw::c_schar;
pub type int_fast16_t = ::std::os::raw::c_long;
pub type int_fast32_t = ::std::os::raw::c_long;
pub type int_fast64_t = ::std::os::raw::c_long;
pub type uint_fast8_t = ::std::os::raw::c_uchar;
pub type uint_fast16_t = ::std::os::raw::c_ulong;
pub type uint_fast32_t = ::std::os::raw::c_ulong;
pub type uint_fast64_t = ::std::os::raw::c_ulong;
pub type intmax_t = ::std::os::raw::c_long;
pub type uintmax_t = ::std::os::raw::c_ulong;
pub type __s8 = ::std::os::raw::c_schar;
pub type __u8 = ::std::os::raw::c_uchar;
pub type __s16 = ::std::os::raw::c_short;
pub type __u16 = ::std::os::raw::c_ushort;
pub type __s32 = ::std::os::raw::c_int;
pub type __u32 = ::std::os::raw::c_uint;
pub type __s64 = ::std::os::raw::c_longlong;
pub type __u64 = ::std::os::raw::c_ulonglong;
#[repr(C)]
#[derive(Debug, Default, Copy, Clone, PartialEq)]
pub struct __kernel_fd_set {
pub fds_bits: [::std::os::raw::c_ulong; 16usize],
}
#[test]
fn bindgen_test_layout___kernel_fd_set() {
assert_eq!(
::std::mem::size_of::<__kernel_fd_set>(),
128usize,
concat!("Size of: ", stringify!(__kernel_fd_set))
);
assert_eq!(
::std::mem::align_of::<__kernel_fd_set>(),
8usize,
concat!("Alignment of ", stringify!(__kernel_fd_set))
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<__kernel_fd_set>())).fds_bits as *const _ as usize },
0usize,
concat!(
"Offset of field: ",
stringify!(__kernel_fd_set),
"::",
stringify!(fds_bits)
)
);
}
pub type __kernel_sighandler_t =
::std::option::Option<unsafe extern "C" fn(arg1: ::std::os::raw::c_int)>;
pub type __kernel_key_t = ::std::os::raw::c_int;
pub type __kernel_mqd_t = ::std::os::raw::c_int;
pub type __kernel_old_uid_t = ::std::os::raw::c_ushort;
pub type __kernel_old_gid_t = ::std::os::raw::c_ushort;
pub type __kernel_old_dev_t = ::std::os::raw::c_ulong;
pub type __kernel_long_t = ::std::os::raw::c_long;
pub type __kernel_ulong_t = ::std::os::raw::c_ulong;
pub type __kernel_ino_t = __kernel_ulong_t;
pub type __kernel_mode_t = ::std::os::raw::c_uint;
pub type __kernel_pid_t = ::std::os::raw::c_int;
pub type __kernel_ipc_pid_t = ::std::os::raw::c_int;
pub type __kernel_uid_t = ::std::os::raw::c_uint;
pub type __kernel_gid_t = ::std::os::raw::c_uint;
pub type __kernel_suseconds_t = __kernel_long_t;
pub type __kernel_daddr_t = ::std::os::raw::c_int;
pub type __kernel_uid32_t = ::std::os::raw::c_uint;
pub type __kernel_gid32_t = ::std::os::raw::c_uint;
pub type __kernel_size_t = __kernel_ulong_t;
pub type __kernel_ssize_t = __kernel_long_t;
pub type __kernel_ptrdiff_t = __kernel_long_t;
#[repr(C)]
#[derive(Debug, Default, Copy, Clone, PartialEq)]
pub struct __kernel_fsid_t {
pub val: [::std::os::raw::c_int; 2usize],
}
#[test]
fn bindgen_test_layout___kernel_fsid_t() {
assert_eq!(
::std::mem::size_of::<__kernel_fsid_t>(),
8usize,
concat!("Size of: ", stringify!(__kernel_fsid_t))
);
assert_eq!(
::std::mem::align_of::<__kernel_fsid_t>(),
4usize,
concat!("Alignment of ", stringify!(__kernel_fsid_t))
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<__kernel_fsid_t>())).val as *const _ as usize },
0usize,
concat!(
"Offset of field: ",
stringify!(__kernel_fsid_t),
"::",
stringify!(val)
)
);
}
pub type __kernel_off_t = __kernel_long_t;
pub type __kernel_loff_t = ::std::os::raw::c_longlong;
pub type __kernel_time_t = __kernel_long_t;
pub type __kernel_clock_t = __kernel_long_t;
pub type __kernel_timer_t = ::std::os::raw::c_int;
pub type __kernel_clockid_t = ::std::os::raw::c_int;
pub type __kernel_caddr_t = *mut ::std::os::raw::c_char;
pub type __kernel_uid16_t = ::std::os::raw::c_ushort;
pub type __kernel_gid16_t = ::std::os::raw::c_ushort;
pub type __le16 = __u16;
pub type __be16 = __u16;
pub type __le32 = __u32;
pub type __be32 = __u32;
pub type __le64 = __u64;
pub type __be64 = __u64;
pub type __sum16 = __u16;
pub type __wsum = __u32;
pub type __virtio16 = __u16;
pub type __virtio32 = __u32;
pub type __virtio64 = __u64;
#[repr(C)]
#[derive(Debug, Default, Copy, Clone, PartialEq)]
pub struct vring_desc {
pub addr: __virtio64,
pub len: __virtio32,
pub flags: __virtio16,
pub next: __virtio16,
}
#[test]
fn bindgen_test_layout_vring_desc() {
assert_eq!(
::std::mem::size_of::<vring_desc>(),
16usize,
concat!("Size of: ", stringify!(vring_desc))
);
assert_eq!(
::std::mem::align_of::<vring_desc>(),
8usize,
concat!("Alignment of ", stringify!(vring_desc))
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<vring_desc>())).addr as *const _ as usize },
0usize,
concat!(
"Offset of field: ",
stringify!(vring_desc),
"::",
stringify!(addr)
)
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<vring_desc>())).len as *const _ as usize },
8usize,
concat!(
"Offset of field: ",
stringify!(vring_desc),
"::",
stringify!(len)
)
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<vring_desc>())).flags as *const _ as usize },
12usize,
concat!(
"Offset of field: ",
stringify!(vring_desc),
"::",
stringify!(flags)
)
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<vring_desc>())).next as *const _ as usize },
14usize,
concat!(
"Offset of field: ",
stringify!(vring_desc),
"::",
stringify!(next)
)
);
}
#[repr(C)]
#[derive(Debug, Default)]
pub struct vring_avail {
pub flags: __virtio16,
pub idx: __virtio16,
pub ring: __IncompleteArrayField<__virtio16>,
}
#[test]
fn bindgen_test_layout_vring_avail() {
assert_eq!(
::std::mem::size_of::<vring_avail>(),
4usize,
concat!("Size of: ", stringify!(vring_avail))
);
assert_eq!(
::std::mem::align_of::<vring_avail>(),
2usize,
concat!("Alignment of ", stringify!(vring_avail))
);
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone, PartialEq)]
pub struct vring_used_elem {
pub id: __virtio32,
pub len: __virtio32,
}
#[test]
fn bindgen_test_layout_vring_used_elem() {
assert_eq!(
::std::mem::size_of::<vring_used_elem>(),
8usize,
concat!("Size of: ", stringify!(vring_used_elem))
);
assert_eq!(
::std::mem::align_of::<vring_used_elem>(),
4usize,
concat!("Alignment of ", stringify!(vring_used_elem))
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<vring_used_elem>())).id as *const _ as usize },
0usize,
concat!(
"Offset of field: ",
stringify!(vring_used_elem),
"::",
stringify!(id)
)
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<vring_used_elem>())).len as *const _ as usize },
4usize,
concat!(
"Offset of field: ",
stringify!(vring_used_elem),
"::",
stringify!(len)
)
);
}
#[repr(C)]
#[derive(Debug, Default)]
pub struct vring_used {
pub flags: __virtio16,
pub idx: __virtio16,
pub ring: __IncompleteArrayField<vring_used_elem>,
pub __bindgen_align: [u32; 0usize],
}
#[test]
fn bindgen_test_layout_vring_used() {
assert_eq!(
::std::mem::size_of::<vring_used>(),
4usize,
concat!("Size of: ", stringify!(vring_used))
);
assert_eq!(
::std::mem::align_of::<vring_used>(),
4usize,
concat!("Alignment of ", stringify!(vring_used))
);
}
#[repr(C)]
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct vring {
pub num: ::std::os::raw::c_uint,
pub desc: *mut vring_desc,
pub avail: *mut vring_avail,
pub used: *mut vring_used,
}
#[test]
fn bindgen_test_layout_vring() {
assert_eq!(
::std::mem::size_of::<vring>(),
32usize,
concat!("Size of: ", stringify!(vring))
);
assert_eq!(
::std::mem::align_of::<vring>(),
8usize,
concat!("Alignment of ", stringify!(vring))
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<vring>())).num as *const _ as usize },
0usize,
concat!(
"Offset of field: ",
stringify!(vring),
"::",
stringify!(num)
)
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<vring>())).desc as *const _ as usize },
8usize,
concat!(
"Offset of field: ",
stringify!(vring),
"::",
stringify!(desc)
)
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<vring>())).avail as *const _ as usize },
16usize,
concat!(
"Offset of field: ",
stringify!(vring),
"::",
stringify!(avail)
)
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<vring>())).used as *const _ as usize },
24usize,
concat!(
"Offset of field: ",
stringify!(vring),
"::",
stringify!(used)
)
);
}
impl Default for vring {
fn default() -> Self {
unsafe { ::std::mem::zeroed() }
}
}

7
vm-device/Cargo.toml Normal file
View File

@@ -0,0 +1,7 @@
[package]
name = "vm-device"
version = "0.1.0"
authors = ["The Cloud Hypervisor Authors"]
edition = "2018"
[dependencies]

11
vm-device/src/lib.rs Normal file
View File

@@ -0,0 +1,11 @@
/// Trait meant for triggering the DMA mapping update related to an external
/// 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
/// guest.
pub trait ExternalDmaMapping: Send + Sync {
/// Map a memory range
fn map(&self, iova: u64, gpa: u64, size: u64) -> std::result::Result<(), std::io::Error>;
/// Unmap a memory range
fn unmap(&self, iova: u64, size: u64) -> std::result::Result<(), std::io::Error>;
}

View File

@@ -4,6 +4,11 @@ version = "0.1.0"
authors = ["Samuel Ortiz <sameo@linux.intel.com>"]
edition = "2018"
[features]
default = []
pci_support = ["pci"]
mmio_support = []
[dependencies]
byteorder = "1.3.2"
devices = { path = "../devices" }
@@ -12,11 +17,12 @@ libc = "0.2.60"
log = "0.4.8"
net_gen = { path = "../net_gen" }
net_util = { path = "../net_util" }
pci = { path = "../pci" }
pci = { path = "../pci", optional = true }
tempfile = "3.1.0"
virtio-bindings = { path = "../virtio-bindings" }
virtio-bindings = { git = "https://github.com/rust-vmm/virtio-bindings", version = "0.1", features = ["virtio-v5_0_0"]}
vm-allocator = { path = "../vm-allocator" }
vmm-sys-util = { git = "https://github.com/rust-vmm/vmm-sys-util" }
vm-device = { path = "../vm-device" }
vmm-sys-util = "0.1.1"
[dependencies.vhost_rs]
path = "../vhost_rs"

View File

@@ -26,7 +26,7 @@ use super::{
VirtioDeviceType, VirtioInterruptType,
};
use crate::VirtioInterrupt;
use virtio_bindings::virtio_blk::*;
use virtio_bindings::bindings::virtio_blk::*;
use vm_memory::{Bytes, GuestAddress, GuestMemory, GuestMemoryError, GuestMemoryMmap};
use vmm_sys_util::eventfd::EventFd;
@@ -503,6 +503,7 @@ impl<T: DiskFile> Block<T> {
mut disk_image: T,
disk_path: PathBuf,
is_disk_read_only: bool,
iommu: bool,
) -> io::Result<Block<T>> {
let disk_size = disk_image.seek(SeekFrom::End(0))? as u64;
if disk_size % SECTOR_SIZE != 0 {
@@ -515,6 +516,10 @@ impl<T: DiskFile> Block<T> {
let mut avail_features = (1u64 << VIRTIO_F_VERSION_1) | (1u64 << VIRTIO_BLK_F_FLUSH);
if iommu {
avail_features |= 1u64 << VIRTIO_F_IOMMU_PLATFORM;
}
if is_disk_read_only {
avail_features |= 1u64 << VIRTIO_BLK_F_RO;
};

View File

@@ -8,6 +8,7 @@ use std::cmp;
use std::collections::VecDeque;
use std::io;
use std::io::Write;
use std::ops::DerefMut;
use std::os::unix::io::AsRawFd;
use std::result;
use std::sync::{Arc, Mutex, RwLock};
@@ -16,7 +17,7 @@ use std::thread;
use super::Error as DeviceError;
use super::{
ActivateError, ActivateResult, DeviceEventT, Queue, VirtioDevice, VirtioDeviceType,
VirtioInterruptType, VIRTIO_F_VERSION_1,
VirtioInterruptType, VIRTIO_F_IOMMU_PLATFORM, VIRTIO_F_VERSION_1,
};
use crate::VirtioInterrupt;
use std::sync::atomic::{AtomicU64, Ordering};
@@ -57,7 +58,7 @@ struct ConsoleEpollHandler {
mem: Arc<RwLock<GuestMemoryMmap>>,
interrupt_cb: Arc<VirtioInterrupt>,
in_buffer: Arc<Mutex<VecDeque<u8>>>,
out: Box<dyn io::Write + Send>,
out: Arc<Mutex<Box<dyn io::Write + Send + Sync + 'static>>>,
input_queue_evt: EventFd,
output_queue_evt: EventFd,
input_evt: EventFd,
@@ -130,8 +131,13 @@ impl ConsoleEpollHandler {
let mem = self.mem.read().unwrap();
for avail_desc in trans_queue.iter(&mem) {
let len;
let _ = mem.write_to(avail_desc.addr, &mut self.out, avail_desc.len as usize);
let _ = self.out.flush();
let mut out = self.out.lock().unwrap();
let _ = mem.write_to(
avail_desc.addr,
&mut out.deref_mut(),
avail_desc.len as usize,
);
let _ = out.flush();
len = avail_desc.len;
used_desc_heads[used_count] = (avail_desc.index, len);
@@ -321,17 +327,24 @@ pub struct Console {
acked_features: u64,
config: Arc<Mutex<VirtioConsoleConfig>>,
input: Arc<ConsoleInput>,
out: Option<Box<dyn io::Write + Send>>,
out: Arc<Mutex<Box<dyn io::Write + Send + Sync + 'static>>>,
queue_evts: Option<Vec<EventFd>>,
interrupt_cb: Option<Arc<VirtioInterrupt>>,
}
impl Console {
/// Create a new virtio console device that gets random data from /dev/urandom.
pub fn new(
out: Option<Box<dyn io::Write + Send>>,
out: Box<dyn io::Write + Send + Sync + 'static>,
cols: u16,
rows: u16,
iommu: bool,
) -> io::Result<(Console, Arc<ConsoleInput>)> {
let avail_features = 1u64 << VIRTIO_F_VERSION_1 | 1u64 << VIRTIO_CONSOLE_F_SIZE;
let mut avail_features = 1u64 << VIRTIO_F_VERSION_1 | 1u64 << VIRTIO_CONSOLE_F_SIZE;
if iommu {
avail_features |= 1u64 << VIRTIO_F_IOMMU_PLATFORM;
}
let input_evt = EventFd::new(EFD_NONBLOCK).unwrap();
let config_evt = EventFd::new(EFD_NONBLOCK).unwrap();
@@ -351,7 +364,9 @@ impl Console {
acked_features: 0u64,
config: console_config,
input: console_input.clone(),
out,
out: Arc::new(Mutex::new(out)),
queue_evts: None,
interrupt_cb: None,
},
console_input,
))
@@ -456,6 +471,21 @@ impl VirtioDevice for Console {
};
self.kill_evt = Some(self_kill_evt);
// 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.clone());
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);
self.input
.acked_features
.store(self.acked_features, Ordering::Relaxed);
@@ -466,31 +496,41 @@ impl VirtioDevice for Console {
}
}
if let Some(out) = self.out.take() {
let mut handler = ConsoleEpollHandler {
queues,
mem,
interrupt_cb,
in_buffer: self.input.in_buffer.clone(),
out,
input_queue_evt: queue_evts.remove(0),
output_queue_evt: queue_evts.remove(0),
input_evt: self.input.input_evt.try_clone().unwrap(),
config_evt: self.input.config_evt.try_clone().unwrap(),
kill_evt,
};
let mut handler = ConsoleEpollHandler {
queues,
mem,
interrupt_cb,
in_buffer: self.input.in_buffer.clone(),
out: self.out.clone(),
input_queue_evt: queue_evts.remove(0),
output_queue_evt: queue_evts.remove(0),
input_evt: self.input.input_evt.try_clone().unwrap(),
config_evt: self.input.config_evt.try_clone().unwrap(),
kill_evt,
};
let worker_result = thread::Builder::new()
.name("virtio_console".to_string())
.spawn(move || handler.run());
let worker_result = thread::Builder::new()
.name("virtio_console".to_string())
.spawn(move || handler.run());
if let Err(e) = worker_result {
error!("failed to spawn virtio_console worker: {}", e);
return Err(ActivateError::BadActivate);;
}
return Ok(());
if let Err(e) = worker_result {
error!("failed to spawn virtio_console worker: {}", e);
return Err(ActivateError::BadActivate);
}
Err(ActivateError::BadActivate)
Ok(())
}
fn reset(&mut self) -> Option<(Arc<VirtioInterrupt>, Vec<EventFd>)> {
if let Some(kill_evt) = self.kill_evt.take() {
// Ignore the result because there is nothing we can do about it.
let _ = kill_evt.write(1);
}
// Return the interrupt and queue EventFDs
Some((
self.interrupt_cb.take().unwrap(),
self.queue_evts.take().unwrap(),
))
}
}

View File

@@ -22,6 +22,9 @@ pub type VirtioInterrupt = Box<
+ Sync,
>;
pub type VirtioIommuRemapping =
Box<dyn Fn(u64) -> std::result::Result<u64, std::io::Error> + Send + Sync>;
#[derive(Clone)]
pub struct VirtioSharedMemory {
pub offset: u64,
@@ -83,4 +86,18 @@ pub trait VirtioDevice: Send {
fn get_shm_regions(&self) -> Option<VirtioSharedMemoryList> {
None
}
fn iommu_translate(&self, addr: u64) -> u64 {
addr
}
}
/// Trait providing address translation the same way a physical DMA remapping
/// table would provide translation between an IOVA and a physical address.
/// The goal of this trait is to be used by virtio devices to perform the
/// address translation before they try to read from the guest physical address.
/// On the other side, the implementation itself should be provided by the code
/// emulating the IOMMU for the guest.
pub trait DmaRemapping: Send + Sync {
fn translate(&self, id: u32, addr: u64) -> std::result::Result<u64, std::io::Error>;
}

914
vm-virtio/src/iommu.rs Normal file
View File

@@ -0,0 +1,914 @@
// Copyright © 2019 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
use epoll;
use libc::EFD_NONBLOCK;
use std::cmp;
use std::collections::BTreeMap;
use std::fmt::{self, Display};
use std::io::{self, Write};
use std::mem::size_of;
use std::ops::Bound::Included;
use std::os::unix::io::AsRawFd;
use std::result;
use std::sync::{Arc, RwLock};
use std::thread;
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 vmm_sys_util::eventfd::EventFd;
/// Queues sizes
const QUEUE_SIZE: u16 = 256;
const NUM_QUEUES: usize = 2;
const QUEUE_SIZES: &[u16] = &[QUEUE_SIZE; NUM_QUEUES];
/// New descriptors are pending on the request queue.
/// "requestq" is meant to be used anytime an action is required to be
/// performed on behalf of the guest driver.
const REQUEST_Q_EVENT: DeviceEventT = 0;
/// New descriptors are pending on the event queue.
/// "eventq" lets the device report any fault or other asynchronous event to
/// the guest driver.
const EVENT_Q_EVENT: DeviceEventT = 1;
/// The device has been dropped.
const KILL_EVENT: DeviceEventT = 2;
/// Virtio IOMMU features
#[allow(unused)]
const VIRTIO_IOMMU_F_INPUT_RANGE: u32 = 0;
#[allow(unused)]
const VIRTIO_IOMMU_F_DOMAIN_BITS: u32 = 1;
#[allow(unused)]
const VIRTIO_IOMMU_F_MAP_UNMAP: u32 = 2;
#[allow(unused)]
const VIRTIO_IOMMU_F_BYPASS: u32 = 3;
#[allow(unused)]
const VIRTIO_IOMMU_F_PROBE: u32 = 4;
// Support 2MiB and 4KiB page sizes.
#[allow(unused)]
const VIRTIO_IOMMU_PAGE_SIZE_MASK: u64 = (2 << 20) | (4 << 10);
#[allow(unused)]
#[derive(Copy, Clone, Debug, Default)]
#[repr(packed)]
struct VirtioIommuRange {
start: u64,
end: u64,
}
unsafe impl ByteValued for VirtioIommuRange {}
#[derive(Copy, Clone, Debug, Default)]
#[repr(packed)]
struct VirtioIommuConfig {
page_size_mask: u64,
input_range: VirtioIommuRange,
domain_bits: u8,
padding: [u8; 3],
probe_size: u32,
}
unsafe impl ByteValued for VirtioIommuConfig {}
/// Virtio IOMMU request type
const VIRTIO_IOMMU_T_ATTACH: u8 = 1;
const VIRTIO_IOMMU_T_DETACH: u8 = 2;
const VIRTIO_IOMMU_T_MAP: u8 = 3;
const VIRTIO_IOMMU_T_UNMAP: u8 = 4;
const VIRTIO_IOMMU_T_PROBE: u8 = 5;
#[allow(unused)]
#[derive(Copy, Clone, Debug, Default)]
#[repr(packed)]
struct VirtioIommuReqHead {
type_: u8,
reserved: [u8; 3],
}
unsafe impl ByteValued for VirtioIommuReqHead {}
/// Virtio IOMMU request status
const VIRTIO_IOMMU_S_OK: u8 = 0;
#[allow(unused)]
const VIRTIO_IOMMU_S_IOERR: u8 = 1;
#[allow(unused)]
const VIRTIO_IOMMU_S_UNSUPP: u8 = 2;
#[allow(unused)]
const VIRTIO_IOMMU_S_DEVERR: u8 = 3;
#[allow(unused)]
const VIRTIO_IOMMU_S_INVAL: u8 = 4;
#[allow(unused)]
const VIRTIO_IOMMU_S_RANGE: u8 = 5;
#[allow(unused)]
const VIRTIO_IOMMU_S_NOENT: u8 = 6;
#[allow(unused)]
const VIRTIO_IOMMU_S_FAULT: u8 = 7;
#[allow(unused)]
#[derive(Copy, Clone, Debug, Default)]
#[repr(packed)]
struct VirtioIommuReqTail {
status: u8,
reserved: [u8; 3],
}
unsafe impl ByteValued for VirtioIommuReqTail {}
/// ATTACH request
#[allow(unused)]
#[derive(Copy, Clone, Debug, Default)]
#[repr(packed)]
struct VirtioIommuReqAttach {
domain: u32,
endpoint: u32,
reserved: [u8; 8],
}
unsafe impl ByteValued for VirtioIommuReqAttach {}
/// DETACH request
#[allow(unused)]
#[derive(Copy, Clone, Debug, Default)]
#[repr(packed)]
struct VirtioIommuReqDetach {
domain: u32,
endpoint: u32,
reserved: [u8; 8],
}
unsafe impl ByteValued for VirtioIommuReqDetach {}
/// Virtio IOMMU request MAP flags
#[allow(unused)]
const VIRTIO_IOMMU_MAP_F_READ: u32 = 1;
#[allow(unused)]
const VIRTIO_IOMMU_MAP_F_WRITE: u32 = 1 << 1;
#[allow(unused)]
const VIRTIO_IOMMU_MAP_F_EXEC: u32 = 1 << 2;
#[allow(unused)]
const VIRTIO_IOMMU_MAP_F_MMIO: u32 = 1 << 3;
/// MAP request
#[allow(unused)]
#[derive(Copy, Clone, Debug, Default)]
#[repr(packed)]
struct VirtioIommuReqMap {
domain: u32,
virt_start: u64,
virt_end: u64,
phys_start: u64,
flags: u32,
}
unsafe impl ByteValued for VirtioIommuReqMap {}
/// UNMAP request
#[allow(unused)]
#[derive(Copy, Clone, Debug, Default)]
#[repr(packed)]
struct VirtioIommuReqUnmap {
domain: u32,
virt_start: u64,
virt_end: u64,
reserved: [u8; 4],
}
unsafe impl ByteValued for VirtioIommuReqUnmap {}
/// Virtio IOMMU request PROBE types
#[allow(unused)]
const VIRTIO_IOMMU_PROBE_T_MASK: u32 = 0xfff;
#[allow(unused)]
const VIRTIO_IOMMU_PROBE_T_NONE: u32 = 0;
#[allow(unused)]
const VIRTIO_IOMMU_PROBE_T_RESV_MEM: u32 = 1;
/// PROBE request
#[allow(unused)]
#[derive(Copy, Clone, Debug, Default)]
#[repr(packed)]
struct VirtioIommuReqProbe {
endpoint: u32,
reserved: [u64; 8],
}
unsafe impl ByteValued for VirtioIommuReqProbe {}
#[allow(unused)]
#[derive(Copy, Clone, Debug, Default)]
#[repr(packed)]
struct VirtioIommuProbeProperty {
type_: u16,
length: u16,
}
unsafe impl ByteValued for VirtioIommuProbeProperty {}
/// Virtio IOMMU request PROBE property RESV_MEM subtypes
#[allow(unused)]
const VIRTIO_IOMMU_RESV_MEM_T_RESERVED: u32 = 0;
#[allow(unused)]
const VIRTIO_IOMMU_RESV_MEM_T_MSI: u32 = 1;
#[allow(unused)]
#[derive(Copy, Clone, Debug, Default)]
#[repr(packed)]
struct VirtioIommuProbeResvMem {
head: VirtioIommuProbeProperty,
subtype: u8,
reserved: [u8; 3],
start: u64,
end: u64,
}
unsafe impl ByteValued for VirtioIommuProbeResvMem {}
/// Virtio IOMMU fault flags
#[allow(unused)]
const VIRTIO_IOMMU_FAULT_F_READ: u32 = 1;
#[allow(unused)]
const VIRTIO_IOMMU_FAULT_F_WRITE: u32 = 1 << 1;
#[allow(unused)]
const VIRTIO_IOMMU_FAULT_F_EXEC: u32 = 1 << 2;
#[allow(unused)]
const VIRTIO_IOMMU_FAULT_F_ADDRESS: u32 = 1 << 8;
/// Virtio IOMMU fault reasons
#[allow(unused)]
const VIRTIO_IOMMU_FAULT_R_UNKNOWN: u32 = 0;
#[allow(unused)]
const VIRTIO_IOMMU_FAULT_R_DOMAIN: u32 = 1;
#[allow(unused)]
const VIRTIO_IOMMU_FAULT_R_MAPPING: u32 = 2;
/// Fault reporting through eventq
#[allow(unused)]
#[derive(Copy, Clone, Debug, Default)]
#[repr(packed)]
struct VirtioIommuFault {
reason: u8,
reserved: [u8; 3],
flags: u32,
endpoint: u32,
reserved1: u32,
address: u64,
}
unsafe impl ByteValued for VirtioIommuFault {}
#[derive(Debug)]
enum Error {
/// Guest gave us bad memory addresses.
GuestMemory(GuestMemoryError),
/// Guest gave us a write only descriptor that protocol says to read from.
UnexpectedWriteOnlyDescriptor,
/// Guest gave us a read only descriptor that protocol says to write to.
UnexpectedReadOnlyDescriptor,
/// Guest gave us too few descriptors in a descriptor chain.
DescriptorChainTooShort,
/// Guest gave us a buffer that was too short to use.
BufferLengthTooSmall,
/// Guest sent us invalid request.
InvalidRequest,
/// Guest sent us invalid ATTACH request.
InvalidAttachRequest,
/// Guest sent us invalid DETACH request.
InvalidDetachRequest,
/// Guest sent us invalid MAP request.
InvalidMapRequest,
/// Guest sent us invalid UNMAP request.
InvalidUnmapRequest,
/// Guest sent us invalid PROBE request.
InvalidProbeRequest,
/// Failed to performing external mapping.
ExternalMapping(io::Error),
/// Failed to performing external unmapping.
ExternalUnmapping(io::Error),
}
impl Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::Error::*;
match self {
BufferLengthTooSmall => write!(f, "buffer length too small"),
DescriptorChainTooShort => write!(f, "descriptor chain too short"),
GuestMemory(e) => write!(f, "bad guest memory address: {}", e),
InvalidRequest => write!(f, "invalid request"),
InvalidAttachRequest => write!(f, "invalid attach request"),
InvalidDetachRequest => write!(f, "invalid detach request"),
InvalidMapRequest => write!(f, "invalid map request"),
InvalidUnmapRequest => write!(f, "invalid unmap request"),
InvalidProbeRequest => write!(f, "invalid probe request"),
UnexpectedReadOnlyDescriptor => write!(f, "unexpected read-only descriptor"),
UnexpectedWriteOnlyDescriptor => write!(f, "unexpected write-only descriptor"),
ExternalMapping(e) => write!(f, "failed performing external mapping: {}", e),
ExternalUnmapping(e) => write!(f, "failed performing external unmapping: {}", e),
}
}
}
#[derive(Debug, PartialEq)]
enum RequestType {
Attach,
Detach,
Map,
Unmap,
Probe,
}
struct Request {
#[allow(unused)]
type_: RequestType,
status_addr: GuestAddress,
}
impl Request {
// Parse the available vring buffer. Based on the hashmap table of external
// mappings required from various devices such as VFIO or vhost-user ones,
// this function might update the hashmap table of external mappings per
// domain.
// Basically, the VMM knows about the device_id <=> mapping relationship
// before running the VM, but at runtime, a new domain <=> mapping hashmap
// is created based on the information provided from the guest driver for
// virtio-iommu (giving the link device_id <=> domain).
fn parse(
avail_desc: &DescriptorChain,
mem: &GuestMemoryMmap,
mapping: &Arc<IommuMapping>,
ext_mapping: &BTreeMap<u32, Arc<dyn ExternalDmaMapping>>,
ext_domain_mapping: &mut BTreeMap<u32, Arc<dyn ExternalDmaMapping>>,
) -> result::Result<Request, Error> {
// The head contains the request type which MUST be readable.
if avail_desc.is_write_only() {
return Err(Error::UnexpectedWriteOnlyDescriptor);
}
if (avail_desc.len as usize) < size_of::<VirtioIommuReqHead>() {
return Err(Error::InvalidRequest);
}
let req_head: VirtioIommuReqHead =
mem.read_obj(avail_desc.addr).map_err(Error::GuestMemory)?;
let req_offset = size_of::<VirtioIommuReqHead>();
let desc_size_left = (avail_desc.len as usize) - req_offset;
let req_addr = if let Some(addr) = avail_desc.addr.checked_add(req_offset as u64) {
addr
} else {
return Err(Error::InvalidRequest);
};
let request_type = match req_head.type_ {
VIRTIO_IOMMU_T_ATTACH => {
if desc_size_left != size_of::<VirtioIommuReqAttach>() {
return Err(Error::InvalidAttachRequest);
}
let req: VirtioIommuReqAttach = mem
.read_obj(req_addr as GuestAddress)
.map_err(Error::GuestMemory)?;
debug!("Attach request {:?}", req);
// Copy the value to use it as a proper reference.
let domain = req.domain;
let endpoint = req.endpoint;
// Add endpoint associated with specific domain
mapping.endpoints.write().unwrap().insert(endpoint, domain);
// If the endpoint is part of the list of devices with an
// external mapping, insert a new entry for the corresponding
// domain, with the same reference to the trait.
if let Some(map) = ext_mapping.get(&endpoint) {
ext_domain_mapping.insert(domain, map.clone());
}
// Add new domain with no mapping if the entry didn't exist yet
let mut mappings = mapping.mappings.write().unwrap();
if !mappings.contains_key(&domain) {
mappings.insert(domain, BTreeMap::new());
}
RequestType::Attach
}
VIRTIO_IOMMU_T_DETACH => {
if desc_size_left != size_of::<VirtioIommuReqDetach>() {
return Err(Error::InvalidDetachRequest);
}
let req: VirtioIommuReqDetach = mem
.read_obj(req_addr as GuestAddress)
.map_err(Error::GuestMemory)?;
debug!("Detach request {:?}", req);
// Copy the value to use it as a proper reference.
let domain = req.domain;
let endpoint = req.endpoint;
// If the endpoint is part of the list of devices with an
// external mapping, remove the entry for the corresponding
// domain.
if ext_mapping.contains_key(&endpoint) {
ext_domain_mapping.remove(&domain);
}
// Remove endpoint associated with specific domain
mapping.endpoints.write().unwrap().remove(&endpoint);
RequestType::Detach
}
VIRTIO_IOMMU_T_MAP => {
if desc_size_left != size_of::<VirtioIommuReqMap>() {
return Err(Error::InvalidMapRequest);
}
let req: VirtioIommuReqMap = mem
.read_obj(req_addr as GuestAddress)
.map_err(Error::GuestMemory)?;
debug!("Map request {:?}", req);
// Copy the value to use it as a proper reference.
let domain = req.domain;
// Trigger external mapping if necessary.
if let Some(ext_map) = ext_domain_mapping.get(&domain) {
let size = req.virt_end - req.virt_start + 1;
ext_map
.map(req.virt_start, req.phys_start, size)
.map_err(Error::ExternalMapping)?;
}
// Add new mapping associated with the domain
if let Some(entry) = mapping.mappings.write().unwrap().get_mut(&domain) {
entry.insert(
req.virt_start,
Mapping {
gpa: req.phys_start,
size: req.virt_end - req.virt_start + 1,
},
);
} else {
return Err(Error::InvalidMapRequest);
}
RequestType::Map
}
VIRTIO_IOMMU_T_UNMAP => {
if desc_size_left != size_of::<VirtioIommuReqUnmap>() {
return Err(Error::InvalidUnmapRequest);
}
let req: VirtioIommuReqUnmap = mem
.read_obj(req_addr as GuestAddress)
.map_err(Error::GuestMemory)?;
debug!("Unmap request {:?}", req);
// Copy the value to use it as a proper reference.
let domain = req.domain;
let virt_start = req.virt_start;
// Trigger external unmapping if necessary.
if let Some(ext_map) = ext_domain_mapping.get(&domain) {
let size = req.virt_end - virt_start + 1;
ext_map
.unmap(virt_start, size)
.map_err(Error::ExternalUnmapping)?;
}
// Add new mapping associated with the domain
if let Some(entry) = mapping.mappings.write().unwrap().get_mut(&domain) {
entry.remove(&virt_start);
}
RequestType::Unmap
}
VIRTIO_IOMMU_T_PROBE => {
if desc_size_left != size_of::<VirtioIommuReqProbe>() {
return Err(Error::InvalidProbeRequest);
}
let req: VirtioIommuReqProbe = mem
.read_obj(req_addr as GuestAddress)
.map_err(Error::GuestMemory)?;
debug!("Probe request {:?}", req);
RequestType::Probe
}
_ => return Err(Error::InvalidRequest),
};
let status_desc = avail_desc
.next_descriptor()
.ok_or(Error::DescriptorChainTooShort)?;
// The status MUST always be writable
if !status_desc.is_write_only() {
return Err(Error::UnexpectedReadOnlyDescriptor);
}
if (status_desc.len as usize) < size_of::<VirtioIommuReqTail>() {
return Err(Error::BufferLengthTooSmall);
}
Ok(Request {
type_: request_type,
status_addr: status_desc.addr,
})
}
}
struct IommuEpollHandler {
queues: Vec<Queue>,
mem: Arc<RwLock<GuestMemoryMmap>>,
interrupt_cb: Arc<VirtioInterrupt>,
queue_evts: Vec<EventFd>,
kill_evt: EventFd,
mapping: Arc<IommuMapping>,
ext_mapping: BTreeMap<u32, Arc<dyn ExternalDmaMapping>>,
ext_domain_mapping: BTreeMap<u32, Arc<dyn ExternalDmaMapping>>,
}
impl IommuEpollHandler {
fn request_queue(&mut self) -> bool {
let mut used_desc_heads = [(0, 0); QUEUE_SIZE as usize];
let mut used_count = 0;
let mem = self.mem.read().unwrap();
for avail_desc in self.queues[0].iter(&mem) {
let len = match Request::parse(
&avail_desc,
&mem,
&self.mapping,
&self.ext_mapping,
&mut self.ext_domain_mapping,
) {
Ok(ref req) => {
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) => {
error!("Failed to parse available descriptor chain: {:?}", e);
0
}
};
used_desc_heads[used_count] = (avail_desc.index, len);
used_count += 1;
}
for &(desc_index, len) in &used_desc_heads[..used_count] {
self.queues[0].add_used(&mem, desc_index, len);
}
used_count > 0
}
fn event_queue(&mut self) -> bool {
false
}
fn signal_used_queue(&self, queue: &Queue) -> result::Result<(), DeviceError> {
(self.interrupt_cb)(&VirtioInterruptType::Queue, Some(queue)).map_err(|e| {
error!("Failed to signal used queue: {:?}", e);
DeviceError::FailedSignalingUsedQueue(e)
})
}
fn run(&mut self) -> result::Result<(), DeviceError> {
// Create the epoll file descriptor
let epoll_fd = epoll::create(true).map_err(DeviceError::EpollCreateFd)?;
// Add events
epoll::ctl(
epoll_fd,
epoll::ControlOptions::EPOLL_CTL_ADD,
self.queue_evts[0].as_raw_fd(),
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(REQUEST_Q_EVENT)),
)
.map_err(DeviceError::EpollCtl)?;
epoll::ctl(
epoll_fd,
epoll::ControlOptions::EPOLL_CTL_ADD,
self.queue_evts[1].as_raw_fd(),
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(EVENT_Q_EVENT)),
)
.map_err(DeviceError::EpollCtl)?;
epoll::ctl(
epoll_fd,
epoll::ControlOptions::EPOLL_CTL_ADD,
self.kill_evt.as_raw_fd(),
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(KILL_EVENT)),
)
.map_err(DeviceError::EpollCtl)?;
const EPOLL_EVENTS_LEN: usize = 100;
let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN];
'epoll: loop {
let num_events = match epoll::wait(epoll_fd, -1, &mut events[..]) {
Ok(res) => res,
Err(e) => {
if e.kind() == io::ErrorKind::Interrupted {
// It's well defined from the epoll_wait() syscall
// documentation that the epoll loop can be interrupted
// before any of the requested events occurred or the
// timeout expired. In both those cases, epoll_wait()
// returns an error of type EINTR, but this should not
// be considered as a regular error. Instead it is more
// appropriate to retry, by calling into epoll_wait().
continue;
}
return Err(DeviceError::EpollWait(e));
}
};
for event in events.iter().take(num_events) {
let ev_type = event.data as u16;
match ev_type {
REQUEST_Q_EVENT => {
if let Err(e) = self.queue_evts[0].read() {
error!("Failed to get queue event: {:?}", e);
break 'epoll;
} else if self.request_queue() {
if let Err(e) = self.signal_used_queue(&self.queues[0]) {
error!("Failed to signal used queue: {:?}", e);
break 'epoll;
}
}
}
EVENT_Q_EVENT => {
if let Err(e) = self.queue_evts[1].read() {
error!("Failed to get queue event: {:?}", e);
break 'epoll;
} else if self.event_queue() {
if let Err(e) = self.signal_used_queue(&self.queues[1]) {
error!("Failed to signal used queue: {:?}", e);
break 'epoll;
}
}
}
KILL_EVENT => {
debug!("kill_evt received, stopping epoll loop");
break 'epoll;
}
_ => {
error!("Unknown event for virtio-iommu");
break 'epoll;
}
}
}
info!("Exit epoll loop");
}
Ok(())
}
}
#[derive(Clone, Copy)]
struct Mapping {
gpa: u64,
size: u64,
}
pub struct IommuMapping {
// Domain related to an endpoint.
endpoints: Arc<RwLock<BTreeMap<u32, u32>>>,
// List of mappings per domain.
mappings: Arc<RwLock<BTreeMap<u32, BTreeMap<u64, Mapping>>>>,
}
impl DmaRemapping for IommuMapping {
fn translate(&self, id: u32, addr: u64) -> std::result::Result<u64, std::io::Error> {
debug!("Translate addr 0x{:x}", addr);
if let Some(domain) = self.endpoints.read().unwrap().get(&id) {
if let Some(mapping) = self.mappings.read().unwrap().get(domain) {
let range_start = if VIRTIO_IOMMU_PAGE_SIZE_MASK > addr {
0
} else {
addr - VIRTIO_IOMMU_PAGE_SIZE_MASK
};
for (&key, &value) in mapping.range((Included(&range_start), Included(&addr))) {
if addr >= key && addr < key + value.size {
let new_addr = addr - key + value.gpa;
debug!("Into new_addr 0x{:x}", new_addr);
return Ok(new_addr);
}
}
}
}
debug!("Into same addr...");
Ok(addr)
}
}
pub struct Iommu {
kill_evt: Option<EventFd>,
avail_features: u64,
acked_features: u64,
config: VirtioIommuConfig,
mapping: Arc<IommuMapping>,
ext_mapping: BTreeMap<u32, Arc<dyn ExternalDmaMapping>>,
queue_evts: Option<Vec<EventFd>>,
interrupt_cb: Option<Arc<VirtioInterrupt>>,
}
impl Iommu {
pub fn new() -> io::Result<(Self, Arc<IommuMapping>)> {
let config = VirtioIommuConfig {
page_size_mask: VIRTIO_IOMMU_PAGE_SIZE_MASK,
..Default::default()
};
let mapping = Arc::new(IommuMapping {
endpoints: Arc::new(RwLock::new(BTreeMap::new())),
mappings: Arc::new(RwLock::new(BTreeMap::new())),
});
Ok((
Iommu {
kill_evt: None,
avail_features: 1u64 << VIRTIO_F_VERSION_1 | 1u64 << VIRTIO_IOMMU_F_MAP_UNMAP,
acked_features: 0u64,
config,
mapping: mapping.clone(),
ext_mapping: BTreeMap::new(),
queue_evts: None,
interrupt_cb: None,
},
mapping,
))
}
pub fn add_external_mapping(&mut self, device_id: u32, mapping: Arc<dyn ExternalDmaMapping>) {
self.ext_mapping.insert(device_id, mapping);
}
}
impl Drop for Iommu {
fn drop(&mut self) {
if let Some(kill_evt) = self.kill_evt.take() {
// Ignore the result because there is nothing we can do about it.
let _ = kill_evt.write(1);
}
}
}
impl VirtioDevice for Iommu {
fn device_type(&self) -> u32 {
VirtioDeviceType::TYPE_IOMMU as u32
}
fn queue_max_sizes(&self) -> &[u16] {
QUEUE_SIZES
}
fn features(&self, page: u32) -> u32 {
match page {
// 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) {
let mut v = match page {
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.
let unrequested_features = v & !self.avail_features;
if unrequested_features != 0 {
warn!("Received acknowledge request for unknown feature.");
// Don't count these features as acked.
v &= !unrequested_features;
}
self.acked_features |= v;
}
fn read_config(&self, offset: u64, mut data: &mut [u8]) {
let config_slice = self.config.as_slice();
let config_len = config_slice.len() as u64;
if offset >= config_len {
error!("Failed to read config space");
return;
}
if let Some(end) = offset.checked_add(data.len() as u64) {
// This write can't fail, offset and end are checked against config_len.
data.write_all(&config_slice[offset as usize..cmp::min(end, config_len) as usize])
.unwrap();
}
}
fn write_config(&mut self, _offset: u64, _data: &[u8]) {
warn!("virtio-iommu device configuration is read-only");
}
fn activate(
&mut self,
mem: Arc<RwLock<GuestMemoryMmap>>,
interrupt_cb: Arc<VirtioInterrupt>,
queues: Vec<Queue>,
queue_evts: Vec<EventFd>,
) -> ActivateResult {
if queues.len() != NUM_QUEUES || queue_evts.len() != NUM_QUEUES {
error!(
"Cannot perform activate. Expected {} queue(s), got {}",
NUM_QUEUES,
queues.len()
);
return Err(ActivateError::BadActivate);
}
let (self_kill_evt, kill_evt) =
match EventFd::new(EFD_NONBLOCK).and_then(|e| Ok((e.try_clone()?, e))) {
Ok(v) => v,
Err(e) => {
error!("failed creating kill EventFd pair: {}", e);
return Err(ActivateError::BadActivate);
}
};
self.kill_evt = Some(self_kill_evt);
// 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.clone());
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 handler = IommuEpollHandler {
queues,
mem,
interrupt_cb,
queue_evts,
kill_evt,
mapping: self.mapping.clone(),
ext_mapping: self.ext_mapping.clone(),
ext_domain_mapping: BTreeMap::new(),
};
let worker_result = thread::Builder::new()
.name("virtio-iommu".to_string())
.spawn(move || handler.run());
if let Err(e) = worker_result {
error!("failed to spawn virtio-iommu worker: {}", e);
return Err(ActivateError::BadActivate);
}
Ok(())
}
fn reset(&mut self) -> Option<(Arc<VirtioInterrupt>, Vec<EventFd>)> {
if let Some(kill_evt) = self.kill_evt.take() {
// Ignore the result because there is nothing we can do about it.
let _ = kill_evt.write(1);
}
// Return the interrupt and queue EventFDs
Some((
self.interrupt_cb.take().unwrap(),
self.queue_evts.take().unwrap(),
))
}
}

View File

@@ -12,6 +12,7 @@
extern crate epoll;
#[macro_use]
extern crate log;
#[cfg(feature = "pci_support")]
extern crate pci;
extern crate vhost_rs;
extern crate virtio_bindings;
@@ -23,10 +24,12 @@ use std::io;
mod block;
mod console;
mod device;
mod iommu;
pub mod net;
mod pmem;
mod queue;
mod rng;
pub mod vsock;
pub mod transport;
pub mod vhost_user;
@@ -34,10 +37,12 @@ pub mod vhost_user;
pub use self::block::*;
pub use self::console::*;
pub use self::device::*;
pub use self::iommu::*;
pub use self::net::*;
pub use self::pmem::*;
pub use self::queue::*;
pub use self::rng::*;
pub use self::vsock::*;
const DEVICE_INIT: u32 = 0x00;
const DEVICE_ACKNOWLEDGE: u32 = 0x01;
@@ -47,6 +52,8 @@ const DEVICE_FEATURES_OK: u32 = 0x08;
const DEVICE_FAILED: u32 = 0x80;
const VIRTIO_F_VERSION_1: u32 = 32;
const VIRTIO_F_IOMMU_PLATFORM: u32 = 33;
const VIRTIO_F_IN_ORDER: u32 = 35;
// Types taken from linux/virtio_ids.h
#[derive(Copy, Clone)]
@@ -63,6 +70,7 @@ enum VirtioDeviceType {
TYPE_GPU = 16,
TYPE_INPUT = 18,
TYPE_VSOCK = 19,
TYPE_IOMMU = 23,
TYPE_FS = 26,
TYPE_PMEM = 27,
TYPE_UNKNOWN = 0xFF,
@@ -79,6 +87,7 @@ impl From<u32> for VirtioDeviceType {
16 => VirtioDeviceType::TYPE_GPU,
18 => VirtioDeviceType::TYPE_INPUT,
19 => VirtioDeviceType::TYPE_VSOCK,
23 => VirtioDeviceType::TYPE_IOMMU,
26 => VirtioDeviceType::TYPE_FS,
27 => VirtioDeviceType::TYPE_PMEM,
_ => VirtioDeviceType::TYPE_UNKNOWN,
@@ -126,6 +135,10 @@ pub enum ActivateError {
VhostUserSetup(vhost_user::Error),
/// Failed to setup vhost-user daemon.
VhostUserNetSetup(vhost_user::Error),
/// Failed to setup vhost-user daemon.
VhostUserBlkSetup(vhost_user::Error),
/// Failed to reset vhost-user daemon.
VhostUserReset(vhost_user::Error),
}
pub type ActivateResult = std::result::Result<(), ActivateError>;

View File

@@ -28,7 +28,7 @@ use super::{
};
use crate::VirtioInterrupt;
use net_util::{MacAddr, Tap, TapError, MAC_ADDR_LEN};
use virtio_bindings::virtio_net::*;
use virtio_bindings::bindings::virtio_net::*;
use vm_memory::{Bytes, GuestAddress, GuestMemoryMmap};
use vmm_sys_util::eventfd::EventFd;
@@ -439,11 +439,13 @@ pub struct Net {
// The config space will only consist of the MAC address specified by the user,
// or nothing, if no such address if provided.
config_space: Vec<u8>,
queue_evts: Option<Vec<EventFd>>,
interrupt_cb: Option<Arc<VirtioInterrupt>>,
}
impl Net {
/// Create a new virtio network device with the given TAP interface.
pub fn new_with_tap(tap: Tap, guest_mac: Option<&MacAddr>) -> Result<Self> {
pub fn new_with_tap(tap: Tap, guest_mac: Option<&MacAddr>, iommu: bool) -> 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,
@@ -462,6 +464,10 @@ impl Net {
| 1 << VIRTIO_NET_F_HOST_UFO
| 1 << VIRTIO_F_VERSION_1;
if iommu {
avail_features |= 1u64 << VIRTIO_F_IOMMU_PLATFORM;
}
let mut config_space;
if let Some(mac) = guest_mac {
config_space = Vec::with_capacity(MAC_ADDR_LEN);
@@ -481,18 +487,25 @@ impl Net {
avail_features,
acked_features: 0u64,
config_space,
queue_evts: None,
interrupt_cb: None,
})
}
/// Create a new virtio network device with the given IP address and
/// netmask.
pub fn new(ip_addr: Ipv4Addr, netmask: Ipv4Addr, guest_mac: Option<&MacAddr>) -> Result<Self> {
pub fn new(
ip_addr: Ipv4Addr,
netmask: Ipv4Addr,
guest_mac: Option<&MacAddr>,
iommu: bool,
) -> 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, guest_mac)
Self::new_with_tap(tap, guest_mac, iommu)
}
}
@@ -597,7 +610,22 @@ impl VirtioDevice for Net {
};
self.kill_evt = Some(self_kill_evt);
if let Some(tap) = self.tap.take() {
if let Some(tap) = self.tap.clone() {
// 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.clone());
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 rx_queue = queues.remove(0);
let tx_queue = queues.remove(0);
let rx_queue_evt = queue_evts.remove(0);
@@ -626,4 +654,17 @@ impl VirtioDevice for Net {
}
Err(ActivateError::BadActivate)
}
fn reset(&mut self) -> Option<(Arc<VirtioInterrupt>, Vec<EventFd>)> {
if let Some(kill_evt) = self.kill_evt.take() {
// Ignore the result because there is nothing we can do about it.
let _ = kill_evt.write(1);
}
// Return the interrupt and queue EventFDs
Some((
self.interrupt_cb.take().unwrap(),
self.queue_evts.take().unwrap(),
))
}
}

View File

@@ -21,7 +21,7 @@ use std::thread;
use super::Error as DeviceError;
use super::{
ActivateError, ActivateResult, DescriptorChain, DeviceEventT, Queue, VirtioDevice,
VirtioDeviceType, VIRTIO_F_VERSION_1,
VirtioDeviceType, VIRTIO_F_IOMMU_PLATFORM, VIRTIO_F_VERSION_1,
};
use crate::{VirtioInterrupt, VirtioInterruptType};
use vm_memory::{
@@ -291,21 +291,31 @@ pub struct Pmem {
avail_features: u64,
acked_features: u64,
config: VirtioPmemConfig,
queue_evts: Option<Vec<EventFd>>,
interrupt_cb: Option<Arc<VirtioInterrupt>>,
}
impl Pmem {
pub fn new(disk: File, addr: GuestAddress, size: GuestUsize) -> io::Result<Pmem> {
pub fn new(disk: File, addr: GuestAddress, size: GuestUsize, iommu: bool) -> io::Result<Pmem> {
let config = VirtioPmemConfig {
start: addr.raw_value().to_le(),
size: size.to_le(),
};
let mut avail_features = 1u64 << VIRTIO_F_VERSION_1;
if iommu {
avail_features |= 1u64 << VIRTIO_F_IOMMU_PLATFORM;
}
Ok(Pmem {
kill_evt: None,
disk: Some(disk),
avail_features: 1u64 << VIRTIO_F_VERSION_1,
avail_features,
acked_features: 0u64,
config,
queue_evts: None,
interrupt_cb: None,
})
}
}
@@ -407,7 +417,26 @@ impl VirtioDevice for Pmem {
};
self.kill_evt = Some(self_kill_evt);
if let Some(disk) = self.disk.take() {
// 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.clone());
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);
if let Some(disk) = self.disk.as_ref() {
let disk = disk.try_clone().map_err(|e| {
error!("failed cloning pmem disk: {}", e);
ActivateError::BadActivate
})?;
let mut handler = PmemEpollHandler {
queue: queues.remove(0),
mem,
@@ -423,11 +452,24 @@ impl VirtioDevice for Pmem {
if let Err(e) = worker_result {
error!("failed to spawn virtio_pmem worker: {}", e);
return Err(ActivateError::BadActivate);;
return Err(ActivateError::BadActivate);
}
return Ok(());
}
Err(ActivateError::BadActivate)
}
fn reset(&mut self) -> Option<(Arc<VirtioInterrupt>, Vec<EventFd>)> {
if let Some(kill_evt) = self.kill_evt.take() {
// Ignore the result because there is nothing we can do about it.
let _ = kill_evt.write(1);
}
// Return the interrupt and queue EventFDs
Some((
self.interrupt_cb.take().unwrap(),
self.queue_evts.take().unwrap(),
))
}
}

View File

@@ -11,7 +11,9 @@
use std::cmp::min;
use std::num::Wrapping;
use std::sync::atomic::{fence, Ordering};
use std::sync::Arc;
use crate::device::VirtioIommuRemapping;
use vm_memory::{
Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryMmap, GuestUsize,
};
@@ -41,10 +43,13 @@ unsafe impl ByteValued for Descriptor {}
/// A virtio descriptor chain.
pub struct DescriptorChain<'a> {
mem: &'a GuestMemoryMmap,
desc_table: GuestAddress,
queue_size: u16,
ttl: u16, // used to prevent infinite chain cycles
iommu_mapping_cb: Option<Arc<VirtioIommuRemapping>>,
/// Reference to guest memory
pub mem: &'a GuestMemoryMmap,
/// Index into the descriptor table
pub index: u16,
@@ -69,6 +74,7 @@ impl<'a> DescriptorChain<'a> {
desc_table: GuestAddress,
queue_size: u16,
index: u16,
iommu_mapping_cb: Option<Arc<VirtioIommuRemapping>>,
) -> Option<DescriptorChain> {
if index >= queue_size {
return None;
@@ -89,16 +95,25 @@ impl<'a> DescriptorChain<'a> {
return None;
}
};
// Translate address if necessary
let desc_addr = if let Some(iommu_mapping_cb) = &iommu_mapping_cb {
(iommu_mapping_cb)(desc.addr).unwrap()
} else {
desc.addr
};
let chain = DescriptorChain {
mem,
desc_table,
queue_size,
ttl: queue_size,
index,
addr: GuestAddress(desc.addr),
addr: GuestAddress(desc_addr),
len: desc.len,
flags: desc.flags,
next: desc.next,
iommu_mapping_cb,
};
if chain.is_valid() {
@@ -135,12 +150,17 @@ impl<'a> DescriptorChain<'a> {
/// the head of the next _available_ descriptor chain.
pub fn next_descriptor(&self) -> Option<DescriptorChain<'a>> {
if self.has_next() {
DescriptorChain::checked_new(self.mem, self.desc_table, self.queue_size, self.next).map(
|mut c| {
c.ttl = self.ttl - 1;
c
},
DescriptorChain::checked_new(
self.mem,
self.desc_table,
self.queue_size,
self.next,
self.iommu_mapping_cb.clone(),
)
.map(|mut c| {
c.ttl = self.ttl - 1;
c
})
} else {
None
}
@@ -156,6 +176,7 @@ pub struct AvailIter<'a, 'b> {
last_index: Wrapping<u16>,
queue_size: u16,
next_avail: &'b mut Wrapping<u16>,
iommu_mapping_cb: Option<Arc<VirtioIommuRemapping>>,
}
impl<'a, 'b> AvailIter<'a, 'b> {
@@ -168,6 +189,7 @@ impl<'a, 'b> AvailIter<'a, 'b> {
last_index: Wrapping(0),
queue_size: 0,
next_avail: q_next_avail,
iommu_mapping_cb: None,
}
}
}
@@ -197,8 +219,13 @@ impl<'a, 'b> Iterator for AvailIter<'a, 'b> {
self.next_index += Wrapping(1);
let ret =
DescriptorChain::checked_new(self.mem, self.desc_table, self.queue_size, desc_index);
let ret = DescriptorChain::checked_new(
self.mem,
self.desc_table,
self.queue_size,
desc_index,
self.iommu_mapping_cb.clone(),
);
if ret.is_some() {
*self.next_avail += Wrapping(1);
}
@@ -230,8 +257,10 @@ pub struct Queue {
/// Guest physical address of the used ring
pub used_ring: GuestAddress,
next_avail: Wrapping<u16>,
next_used: Wrapping<u16>,
pub next_avail: Wrapping<u16>,
pub next_used: Wrapping<u16>,
pub iommu_mapping_cb: Option<Arc<VirtioIommuRemapping>>,
}
impl Queue {
@@ -247,6 +276,7 @@ impl Queue {
used_ring: GuestAddress(0),
next_avail: Wrapping(0),
next_used: Wrapping(0),
iommu_mapping_cb: None,
}
}
@@ -254,6 +284,26 @@ impl Queue {
self.max_size
}
pub fn enable(&mut self, set: bool) {
self.ready = set;
if set {
// Translate address of descriptor table and vrings.
if let Some(iommu_mapping_cb) = &self.iommu_mapping_cb {
self.desc_table =
GuestAddress((iommu_mapping_cb)(self.desc_table.raw_value()).unwrap());
self.avail_ring =
GuestAddress((iommu_mapping_cb)(self.avail_ring.raw_value()).unwrap());
self.used_ring =
GuestAddress((iommu_mapping_cb)(self.used_ring.raw_value()).unwrap());
}
} else {
self.desc_table = GuestAddress(0);
self.avail_ring = GuestAddress(0);
self.used_ring = GuestAddress(0);
}
}
/// Return the actual size of the queue, as the driver may not set up a
/// queue as big as the device allows.
pub fn actual_size(&self) -> u16 {
@@ -352,6 +402,7 @@ impl Queue {
last_index: Wrapping(last_index),
queue_size,
next_avail: &mut self.next_avail,
iommu_mapping_cb: self.iommu_mapping_cb.clone(),
}
}
@@ -645,14 +696,16 @@ pub(crate) mod tests {
assert!(vq.end().0 < 0x1000);
// index >= queue_size
assert!(DescriptorChain::checked_new(m, vq.start(), 16, 16).is_none());
assert!(DescriptorChain::checked_new(m, vq.start(), 16, 16, None).is_none());
// desc_table address is way off
assert!(DescriptorChain::checked_new(m, GuestAddress(0x00ff_ffff_ffff), 16, 0).is_none());
assert!(
DescriptorChain::checked_new(m, GuestAddress(0x00ff_ffff_ffff), 16, 0, None).is_none()
);
// the addr field of the descriptor is way off
vq.dtable[0].addr.set(0x0fff_ffff_ffff);
assert!(DescriptorChain::checked_new(m, vq.start(), 16, 0).is_none());
assert!(DescriptorChain::checked_new(m, vq.start(), 16, 0, None).is_none());
// let's create some invalid chains
@@ -661,7 +714,7 @@ pub(crate) mod tests {
vq.dtable[0].addr.set(0x1000);
// ...but the length is too large
vq.dtable[0].len.set(0xffff_ffff);
assert!(DescriptorChain::checked_new(m, vq.start(), 16, 0).is_none());
assert!(DescriptorChain::checked_new(m, vq.start(), 16, 0, None).is_none());
}
{
@@ -671,7 +724,7 @@ pub(crate) mod tests {
//..but the the index of the next descriptor is too large
vq.dtable[0].next.set(16);
assert!(DescriptorChain::checked_new(m, vq.start(), 16, 0).is_none());
assert!(DescriptorChain::checked_new(m, vq.start(), 16, 0, None).is_none());
}
// finally, let's test an ok chain
@@ -680,7 +733,7 @@ pub(crate) mod tests {
vq.dtable[0].next.set(1);
vq.dtable[1].set(0x2000, 0x1000, 0, 0);
let c = DescriptorChain::checked_new(m, vq.start(), 16, 0).unwrap();
let c = DescriptorChain::checked_new(m, vq.start(), 16, 0, None).unwrap();
assert_eq!(c.mem as *const GuestMemoryMmap, m as *const GuestMemoryMmap);
assert_eq!(c.desc_table, vq.start());

View File

@@ -15,7 +15,7 @@ use std::thread;
use super::Error as DeviceError;
use super::{
ActivateError, ActivateResult, DeviceEventT, Queue, VirtioDevice, VirtioDeviceType,
VIRTIO_F_VERSION_1,
VIRTIO_F_IOMMU_PLATFORM, VIRTIO_F_VERSION_1,
};
use crate::{VirtioInterrupt, VirtioInterruptType};
use vm_memory::{Bytes, GuestMemoryMmap};
@@ -158,19 +158,27 @@ pub struct Rng {
random_file: Option<File>,
avail_features: u64,
acked_features: u64,
queue_evts: Option<Vec<EventFd>>,
interrupt_cb: Option<Arc<VirtioInterrupt>>,
}
impl Rng {
/// Create a new virtio rng device that gets random data from /dev/urandom.
pub fn new(path: &str) -> io::Result<Rng> {
pub fn new(path: &str, iommu: bool) -> io::Result<Rng> {
let random_file = File::open(path)?;
let avail_features = 1u64 << VIRTIO_F_VERSION_1;
let mut avail_features = 1u64 << VIRTIO_F_VERSION_1;
if iommu {
avail_features |= 1u64 << VIRTIO_F_IOMMU_PLATFORM;
}
Ok(Rng {
kill_evt: None,
random_file: Some(random_file),
avail_features,
acked_features: 0u64,
queue_evts: None,
interrupt_cb: None,
})
}
}
@@ -261,7 +269,26 @@ impl VirtioDevice for Rng {
};
self.kill_evt = Some(self_kill_evt);
if let Some(random_file) = self.random_file.take() {
// 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.clone());
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);
if let Some(file) = self.random_file.as_ref() {
let random_file = file.try_clone().map_err(|e| {
error!("failed cloning rng source: {}", e);
ActivateError::BadActivate
})?;
let mut handler = RngEpollHandler {
queues,
mem,
@@ -277,11 +304,24 @@ impl VirtioDevice for Rng {
if let Err(e) = worker_result {
error!("failed to spawn virtio_rng worker: {}", e);
return Err(ActivateError::BadActivate);;
return Err(ActivateError::BadActivate);
}
return Ok(());
}
Err(ActivateError::BadActivate)
}
fn reset(&mut self) -> Option<(Arc<VirtioInterrupt>, Vec<EventFd>)> {
if let Some(kill_evt) = self.kill_evt.take() {
// Ignore the result because there is nothing we can do about it.
let _ = kill_evt.write(1);
}
// Return the interrupt and queue EventFDs
Some((
self.interrupt_cb.take().unwrap(),
self.queue_evts.take().unwrap(),
))
}
}

View File

@@ -0,0 +1,248 @@
// Copyright 2017 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, RwLock};
use byteorder::{ByteOrder, LittleEndian};
use libc::EFD_NONBLOCK;
use crate::{
Queue, VirtioDevice, VirtioInterrupt, VirtioInterruptType, DEVICE_ACKNOWLEDGE, DEVICE_DRIVER,
DEVICE_DRIVER_OK, DEVICE_FAILED, DEVICE_FEATURES_OK, DEVICE_INIT,
INTERRUPT_STATUS_CONFIG_CHANGED, INTERRUPT_STATUS_USED_RING,
};
use devices::{BusDevice, Interrupt};
use vm_memory::{GuestAddress, GuestMemoryMmap};
use vmm_sys_util::{errno::Result, eventfd::EventFd};
const VENDOR_ID: u32 = 0;
const MMIO_MAGIC_VALUE: u32 = 0x7472_6976;
const MMIO_VERSION: u32 = 2;
/// Implements the
/// [MMIO](http://docs.oasis-open.org/virtio/virtio/v1.0/cs04/virtio-v1.0-cs04.html#x1-1090002)
/// transport for virtio devices.
///
/// This requires 3 points of installation to work with a VM:
///
/// 1. Mmio reads and writes must be sent to this device at what is referred to here as MMIO base.
/// 1. `Mmio::queue_evts` must be installed at `virtio::NOTIFY_REG_OFFSET` offset from the MMIO
/// base. Each event in the array must be signaled if the index is written at that offset.
/// 1. `Mmio::interrupt_evt` must signal an interrupt that the guest driver is listening to when it
/// is written to.
///
/// Typically one page (4096 bytes) of MMIO address space is sufficient to handle this transport
/// and inner virtio device.
pub struct MmioDevice {
device: Box<dyn VirtioDevice>,
device_activated: bool,
features_select: u32,
acked_features_select: u32,
queue_select: u32,
interrupt_status: Arc<AtomicUsize>,
interrupt_cb: Option<Arc<VirtioInterrupt>>,
driver_status: u32,
config_generation: u32,
queues: Vec<Queue>,
queue_evts: Vec<EventFd>,
mem: Option<Arc<RwLock<GuestMemoryMmap>>>,
}
impl MmioDevice {
/// Constructs a new MMIO transport for the given virtio device.
pub fn new(
mem: Arc<RwLock<GuestMemoryMmap>>,
device: Box<dyn VirtioDevice>,
) -> Result<MmioDevice> {
let mut queue_evts = Vec::new();
for _ in device.queue_max_sizes().iter() {
queue_evts.push(EventFd::new(EFD_NONBLOCK)?)
}
let queues = device
.queue_max_sizes()
.iter()
.map(|&s| Queue::new(s))
.collect();
Ok(MmioDevice {
device,
device_activated: false,
features_select: 0,
acked_features_select: 0,
queue_select: 0,
interrupt_status: Arc::new(AtomicUsize::new(0)),
interrupt_cb: None,
driver_status: DEVICE_INIT,
config_generation: 0,
queues,
queue_evts,
mem: Some(mem),
})
}
/// 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
/// value being written equals the index of the event in this list.
pub fn queue_evts(&self) -> &[EventFd] {
self.queue_evts.as_slice()
}
fn is_driver_ready(&self) -> bool {
let ready_bits = DEVICE_ACKNOWLEDGE | DEVICE_DRIVER | DEVICE_DRIVER_OK | DEVICE_FEATURES_OK;
self.driver_status == ready_bits && self.driver_status & DEVICE_FAILED == 0
}
fn are_queues_valid(&self) -> bool {
if let Some(mem) = self.mem.as_ref() {
self.queues.iter().all(|q| q.is_valid(&mem.read().unwrap()))
} else {
false
}
}
fn with_queue<U, F>(&self, d: U, f: F) -> U
where
F: FnOnce(&Queue) -> U,
{
match self.queues.get(self.queue_select as usize) {
Some(queue) => f(queue),
None => d,
}
}
fn with_queue_mut<F: FnOnce(&mut Queue)>(&mut self, f: F) -> bool {
if let Some(queue) = self.queues.get_mut(self.queue_select as usize) {
f(queue);
true
} else {
false
}
}
pub fn assign_interrupt(&mut self, interrupt: Box<dyn Interrupt>) {
let interrupt_status = self.interrupt_status.clone();
let cb = Arc::new(Box::new(
move |int_type: &VirtioInterruptType, _queue: Option<&Queue>| {
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()
},
) as VirtioInterrupt);
self.interrupt_cb = Some(cb);
}
}
impl BusDevice for MmioDevice {
fn read(&mut self, _base: u64, offset: u64, data: &mut [u8]) {
match offset {
0x00..=0xff if data.len() == 4 => {
let v = match offset {
0x0 => MMIO_MAGIC_VALUE,
0x04 => MMIO_VERSION,
0x08 => self.device.device_type(),
0x0c => VENDOR_ID, // vendor id
0x10 => {
self.device.features(self.features_select)
| if self.features_select == 1 { 0x1 } else { 0x0 }
}
0x34 => self.with_queue(0, |q| u32::from(q.get_max_size())),
0x44 => self.with_queue(0, |q| q.ready as u32),
0x60 => self.interrupt_status.load(Ordering::SeqCst) as u32,
0x70 => self.driver_status,
0xfc => self.config_generation,
_ => {
warn!("unknown virtio mmio register read: 0x{:x}", offset);
return;
}
};
LittleEndian::write_u32(data, v);
}
0x100..=0xfff => self.device.read_config(offset - 0x100, data),
_ => {
warn!(
"invalid virtio mmio read: 0x{:x}:0x{:x}",
offset,
data.len()
);
}
};
}
fn write(&mut self, _base: u64, offset: u64, data: &[u8]) {
fn hi(v: &mut GuestAddress, x: u32) {
*v = (*v & 0xffff_ffff) | (u64::from(x) << 32)
}
fn lo(v: &mut GuestAddress, x: u32) {
*v = (*v & !0xffff_ffff) | u64::from(x)
}
let mut mut_q = false;
match offset {
0x00..=0xff if data.len() == 4 => {
let v = LittleEndian::read_u32(data);
match offset {
0x14 => self.features_select = v,
0x20 => self.device.ack_features(self.acked_features_select, v),
0x24 => self.acked_features_select = v,
0x30 => self.queue_select = v,
0x38 => mut_q = self.with_queue_mut(|q| q.size = v as u16),
0x44 => mut_q = self.with_queue_mut(|q| q.ready = v == 1),
0x64 => {
self.interrupt_status
.fetch_and(!(v as usize), Ordering::SeqCst);
}
0x70 => self.driver_status = v,
0x80 => mut_q = self.with_queue_mut(|q| lo(&mut q.desc_table, v)),
0x84 => mut_q = self.with_queue_mut(|q| hi(&mut q.desc_table, v)),
0x90 => mut_q = self.with_queue_mut(|q| lo(&mut q.avail_ring, v)),
0x94 => mut_q = self.with_queue_mut(|q| hi(&mut q.avail_ring, v)),
0xa0 => mut_q = self.with_queue_mut(|q| lo(&mut q.used_ring, v)),
0xa4 => mut_q = self.with_queue_mut(|q| hi(&mut q.used_ring, v)),
_ => {
warn!("unknown virtio mmio register write: 0x{:x}", offset);
return;
}
}
}
0x100..=0xfff => return self.device.write_config(offset - 0x100, data),
_ => {
warn!(
"invalid virtio mmio write: 0x{:x}:0x{:x}",
offset,
data.len()
);
return;
}
}
if self.device_activated && mut_q {
warn!("virtio queue was changed after device was activated");
}
if !self.device_activated && self.is_driver_ready() && self.are_queues_valid() {
if let Some(interrupt_cb) = self.interrupt_cb.take() {
if self.mem.is_some() {
let mem = self.mem.as_ref().unwrap().clone();
self.device
.activate(
mem,
interrupt_cb,
self.queues.clone(),
self.queue_evts.split_off(0),
)
.expect("Failed to activate device");
self.device_activated = true;
}
}
}
}
}

View File

@@ -2,8 +2,18 @@
//
// SPDX-License-Identifier: Apache-2.0
#[cfg(feature = "pci_support")]
mod pci_common_config;
#[cfg(feature = "pci_support")]
mod pci_device;
#[cfg(feature = "pci_support")]
pub use pci_common_config::VirtioPciCommonConfig;
#[cfg(feature = "pci_support")]
pub use pci_device::VirtioPciDevice;
#[cfg(feature = "mmio_support")]
mod mmio;
#[cfg(feature = "mmio_support")]
pub use mmio::MmioDevice;
#[cfg(feature = "mmio_support")]
pub const NOTIFY_REG_OFFSET: u32 = 0x50;

View File

@@ -149,7 +149,7 @@ impl VirtioPciCommonConfig {
0x16 => self.queue_select = value,
0x18 => self.with_queue_mut(queues, |q| q.size = value),
0x1a => self.with_queue_mut(queues, |q| q.vector = value),
0x1c => self.with_queue_mut(queues, |q| q.ready = value == 1),
0x1c => self.with_queue_mut(queues, |q| q.enable(value == 1)),
_ => {
warn!("invalid virtio register word write: 0x{:x}", offset);
}

View File

@@ -7,6 +7,7 @@
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
extern crate devices;
#[cfg(feature = "pci_support")]
extern crate pci;
extern crate vm_allocator;
extern crate vm_memory;
@@ -30,8 +31,8 @@ use vmm_sys_util::{errno::Result, eventfd::EventFd};
use super::VirtioPciCommonConfig;
use crate::{
Queue, VirtioDevice, VirtioDeviceType, VirtioInterrupt, VirtioInterruptType,
DEVICE_ACKNOWLEDGE, DEVICE_DRIVER, DEVICE_DRIVER_OK, DEVICE_FAILED, DEVICE_FEATURES_OK,
DEVICE_INIT, INTERRUPT_STATUS_CONFIG_CHANGED, INTERRUPT_STATUS_USED_RING,
VirtioIommuRemapping, DEVICE_ACKNOWLEDGE, DEVICE_DRIVER, DEVICE_DRIVER_OK, DEVICE_FAILED,
DEVICE_FEATURES_OK, DEVICE_INIT, INTERRUPT_STATUS_CONFIG_CHANGED, INTERRUPT_STATUS_USED_RING,
};
#[allow(clippy::enum_variant_names)]
@@ -238,6 +239,9 @@ pub struct VirtioPciDevice {
// Setting PCI BAR
settings_bar: u8,
// Whether to use 64-bit bar location or 32-bit
use_64bit_bar: bool,
}
impl VirtioPciDevice {
@@ -246,6 +250,7 @@ impl VirtioPciDevice {
memory: Arc<RwLock<GuestMemoryMmap>>,
device: Box<dyn VirtioDevice>,
msix_num: u16,
iommu_mapping_cb: Option<Arc<VirtioIommuRemapping>>,
) -> Result<Self> {
let mut queue_evts = Vec::new();
for _ in device.queue_max_sizes().iter() {
@@ -254,7 +259,11 @@ impl VirtioPciDevice {
let queues = device
.queue_max_sizes()
.iter()
.map(|&s| Queue::new(s))
.map(|&s| {
let mut queue = Queue::new(s);
queue.iommu_mapping_cb = iommu_mapping_cb.clone();
queue
})
.collect();
let pci_device_id = VIRTIO_PCI_DEVICE_ID_BASE + device.device_type() as u16;
@@ -267,15 +276,22 @@ impl VirtioPciDevice {
(None, None)
};
// All device types *except* virtio block devices should be allocated a 64-bit bar
// The block devices should be given a 32-bit BAR so that they are easily accessible
// to firmware without requiring excessive identity mapping.
let mut use_64bit_bar = true;
let (class, subclass) = match VirtioDeviceType::from(device.device_type()) {
VirtioDeviceType::TYPE_NET => (
PciClassCode::NetworkController,
&PciNetworkControllerSubclass::EthernetController as &dyn PciSubclass,
),
VirtioDeviceType::TYPE_BLOCK => (
PciClassCode::MassStorage,
&PciMassStorageSubclass::MassStorage as &dyn PciSubclass,
),
VirtioDeviceType::TYPE_BLOCK => {
use_64bit_bar = false;
(
PciClassCode::MassStorage,
&PciMassStorageSubclass::MassStorage as &dyn PciSubclass,
)
}
_ => (
PciClassCode::Other,
&PciVirtioSubclass::NonTransitionalBase as &dyn PciSubclass,
@@ -314,6 +330,7 @@ impl VirtioPciDevice {
queue_evts,
memory: Some(memory),
settings_bar: 0,
use_64bit_bar,
})
}
@@ -530,24 +547,32 @@ impl PciDevice for VirtioPciDevice {
// Allocate the virtio-pci capability BAR.
// See http://docs.oasis-open.org/virtio/virtio/v1.0/cs04/virtio-v1.0-cs04.html#x1-740004
let virtio_pci_bar_addr = allocator
.allocate_mmio_addresses(None, CAPABILITY_BAR_SIZE, None)
.ok_or(PciDeviceError::IoAllocationFailed(CAPABILITY_BAR_SIZE))?;
let (virtio_pci_bar_addr, region_type) = if self.use_64bit_bar {
let region_type = PciBarRegionType::Memory64BitRegion;
let addr = allocator
.allocate_mmio_addresses(None, CAPABILITY_BAR_SIZE, None)
.ok_or(PciDeviceError::IoAllocationFailed(CAPABILITY_BAR_SIZE))?;
ranges.push((addr, CAPABILITY_BAR_SIZE, region_type));
(addr, region_type)
} else {
let region_type = PciBarRegionType::Memory32BitRegion;
let addr = allocator
.allocate_mmio_hole_addresses(None, CAPABILITY_BAR_SIZE, None)
.ok_or(PciDeviceError::IoAllocationFailed(CAPABILITY_BAR_SIZE))?;
ranges.push((addr, CAPABILITY_BAR_SIZE, region_type));
(addr, region_type)
};
let config = PciBarConfiguration::default()
.set_register_index(0)
.set_address(virtio_pci_bar_addr.raw_value())
.set_size(CAPABILITY_BAR_SIZE);
.set_size(CAPABILITY_BAR_SIZE)
.set_region_type(region_type);
let virtio_pci_bar =
self.configuration.add_pci_bar(&config).map_err(|e| {
PciDeviceError::IoRegistrationFailed(virtio_pci_bar_addr.raw_value(), e)
})? as u8;
ranges.push((
virtio_pci_bar_addr,
CAPABILITY_BAR_SIZE,
PciBarRegionType::Memory64BitRegion,
));
// Once the BARs are allocated, the capabilities can be added to the PCI configuration.
self.add_pci_capabilities(virtio_pci_bar)?;

View File

@@ -0,0 +1,290 @@
// Copyright 2019 Intel Corporation. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
use libc;
use libc::EFD_NONBLOCK;
use std::cmp;
use std::io::Write;
use std::ptr::null;
use std::sync::{Arc, RwLock};
use std::thread;
use std::vec::Vec;
use crate::VirtioInterrupt;
use vm_memory::GuestMemoryMmap;
use vmm_sys_util::eventfd::EventFd;
use super::super::{ActivateError, ActivateResult, Queue, VirtioDevice, VirtioDeviceType};
use super::handler::*;
use super::vu_common_ctrl::*;
use super::{Error, Result};
use std::mem;
use vhost_rs::vhost_user::message::VhostUserConfigFlags;
use vhost_rs::vhost_user::message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures};
use vhost_rs::vhost_user::{Master, VhostUserMaster, VhostUserMasterReqHandler};
use vhost_rs::VhostBackend;
use virtio_bindings::bindings::virtio_blk::*;
macro_rules! offset_of {
($ty:ty, $field:ident) => {
unsafe { &(*(null() as *const $ty)).$field as *const _ as usize }
};
}
struct SlaveReqHandler {}
impl VhostUserMasterReqHandler for SlaveReqHandler {}
pub struct Blk {
vhost_user_blk: Master,
kill_evt: Option<EventFd>,
avail_features: u64,
acked_features: u64,
config_space: Vec<u8>,
queue_sizes: Vec<u16>,
queue_evts: Option<Vec<EventFd>>,
interrupt_cb: Option<Arc<VirtioInterrupt>>,
}
impl Blk {
/// Create a new vhost-user-blk device
pub fn new(wce: bool, vu_cfg: VhostUserConfig) -> Result<Blk> {
let mut vhost_user_blk = Master::connect(&vu_cfg.sock, vu_cfg.num_queues as u64)
.map_err(Error::VhostUserCreateMaster)?;
// Filling device and vring features VMM supports.
let mut avail_features = 1 << VIRTIO_BLK_F_SEG_MAX
| 1 << VIRTIO_BLK_F_RO
| 1 << VIRTIO_BLK_F_BLK_SIZE
| 1 << VIRTIO_BLK_F_FLUSH
| 1 << VIRTIO_BLK_F_TOPOLOGY
| 1 << VIRTIO_F_VERSION_1
| VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
if wce {
avail_features |= 1 << VIRTIO_BLK_F_CONFIG_WCE;
}
// Set vhost-user owner.
vhost_user_blk
.set_owner()
.map_err(Error::VhostUserSetOwner)?;
// Get features from backend, do negotiation to get a feature collection which
// both VMM and backend support.
let backend_features = vhost_user_blk
.get_features()
.map_err(Error::VhostUserGetFeatures)?;
avail_features &= backend_features;
// Set features back is required by the vhost crate mechanism, since the
// later vhost call will check if features is filled in master before execution.
vhost_user_blk
.set_features(avail_features)
.map_err(Error::VhostUserSetFeatures)?;
// Identify if protocol features are supported by the slave.
let mut acked_features = 0;
if avail_features & VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits() != 0 {
acked_features |= VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
let mut protocol_features = vhost_user_blk
.get_protocol_features()
.map_err(Error::VhostUserGetProtocolFeatures)?;
protocol_features |= VhostUserProtocolFeatures::MQ;
protocol_features &= !VhostUserProtocolFeatures::INFLIGHT_SHMFD;
vhost_user_blk
.set_protocol_features(protocol_features)
.map_err(Error::VhostUserSetProtocolFeatures)?;
}
let config_len = mem::size_of::<virtio_blk_config>();
let config_space: Vec<u8> = vec![0u8; config_len as usize];
let (_, mut config_space) = vhost_user_blk
.get_config(
0,
config_len as u32,
VhostUserConfigFlags::WRITABLE,
config_space.as_slice(),
)
.unwrap();
let queue_num_offset = offset_of!(virtio_blk_config, num_queues);
// Only set num_queues value(u16).
let num_queues_slice = (vu_cfg.num_queues as u16).to_le_bytes();
config_space[queue_num_offset..queue_num_offset + mem::size_of::<u16>()]
.copy_from_slice(&num_queues_slice);
Ok(Blk {
vhost_user_blk,
kill_evt: None,
avail_features,
acked_features,
config_space,
queue_sizes: vec![vu_cfg.queue_size; vu_cfg.num_queues],
queue_evts: None,
interrupt_cb: None,
})
}
}
impl Drop for Blk {
fn drop(&mut self) {
if let Some(kill_evt) = self.kill_evt.take() {
if let Err(e) = kill_evt.write(1) {
error!("failed to kill vhost-user-blk: {:?}", e);
}
}
}
}
impl VirtioDevice for Blk {
fn device_type(&self) -> u32 {
VirtioDeviceType::TYPE_BLOCK as u32
}
fn queue_max_sizes(&self) -> &[u16] {
&self.queue_sizes
}
fn features(&self, page: u32) -> u32 {
match page {
0 => self.avail_features as u32,
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) {
let mut v = match page {
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.
let unrequested_features = v & !self.avail_features;
if unrequested_features != 0 {
warn!("Received acknowledge request for unknown feature: {:x}", v);
// Don't count these features as acked.
v &= !unrequested_features;
}
self.acked_features |= v;
}
fn read_config(&self, offset: u64, mut data: &mut [u8]) {
let config_len = self.config_space.len() as u64;
if offset >= config_len {
error!("Failed to read config space");
return;
}
if let Some(end) = offset.checked_add(data.len() as u64) {
// 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])
.unwrap();
}
}
fn write_config(&mut self, offset: u64, data: &[u8]) {
let data_len = data.len() as u64;
let config_len = self.config_space.len() as u64;
if offset + data_len > config_len {
error!("Failed to write config space");
return;
}
// In fact, write_config() only handle wce value in vhost-user-blk.
// so, we can only set wce value here.
if self.config_space[offset as usize] == data[0] {
return;
}
self.vhost_user_blk
.set_config(offset as u32, VhostUserConfigFlags::WRITABLE, data)
.expect("Failed to set config");
self.config_space[offset as usize] = data[0];
}
fn activate(
&mut self,
mem: Arc<RwLock<GuestMemoryMmap>>,
interrupt_cb: Arc<VirtioInterrupt>,
queues: Vec<Queue>,
queue_evts: Vec<EventFd>,
) -> ActivateResult {
let (self_kill_evt, kill_evt) =
match EventFd::new(EFD_NONBLOCK).and_then(|e| Ok((e.try_clone()?, e))) {
Ok(v) => v,
Err(e) => {
error!("failed creating kill EventFd pair: {}", e);
return Err(ActivateError::BadActivate);
}
};
self.kill_evt = Some(self_kill_evt);
// 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.clone());
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 vu_interrupt_list = setup_vhost_user(
&mut self.vhost_user_blk,
&mem.read().unwrap(),
queues,
queue_evts,
self.acked_features,
)
.map_err(ActivateError::VhostUserBlkSetup)?;
let mut handler = VhostUserEpollHandler::<SlaveReqHandler>::new(VhostUserEpollConfig {
interrupt_cb,
kill_evt,
vu_interrupt_list,
slave_req_handler: None,
});
let handler_result = thread::Builder::new()
.name("vhost_user_blk".to_string())
.spawn(move || {
if let Err(e) = handler.run() {
error!("net worker thread exited with error {:?}!", e);
}
});
if let Err(e) = handler_result {
error!("vhost-user blk thread create failed with error {:?}", e);
}
Ok(())
}
fn reset(&mut self) -> Option<(Arc<VirtioInterrupt>, Vec<EventFd>)> {
if let Err(e) = reset_vhost_user(&mut self.vhost_user_blk, self.queue_sizes.len()) {
error!("Failed to reset vhost-user daemon: {:?}", e);
return None;
}
if let Some(kill_evt) = self.kill_evt.take() {
// Ignore the result because there is nothing we can do about it.
let _ = kill_evt.write(1);
}
// Return the interrupt and queue EventFDs
Some((
self.interrupt_cb.take().unwrap(),
self.queue_evts.take().unwrap(),
))
}
}

View File

@@ -1,7 +1,7 @@
// Copyright 2019 Intel Corporation. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
use super::vu_common_ctrl::setup_vhost_user;
use super::vu_common_ctrl::{reset_vhost_user, setup_vhost_user};
use super::{Error, Result};
use crate::vhost_user::handler::{VhostUserEpollConfig, VhostUserEpollHandler};
use crate::{
@@ -118,6 +118,8 @@ pub struct Fs {
kill_evt: Option<EventFd>,
cache: Option<(VirtioSharedMemoryList, u64)>,
slave_req_support: bool,
queue_evts: Option<Vec<EventFd>>,
interrupt_cb: Option<Arc<VirtioInterrupt>>,
}
impl Fs {
@@ -199,6 +201,8 @@ impl Fs {
kill_evt: None,
cache,
slave_req_support,
queue_evts: None,
interrupt_cb: None,
})
}
}
@@ -305,6 +309,21 @@ impl VirtioDevice for Fs {
};
self.kill_evt = Some(self_kill_evt);
// 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.clone());
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 vu_call_evt_queue_list = setup_vhost_user(
&mut self.vu,
&mem.read().unwrap(),
@@ -361,6 +380,24 @@ impl VirtioDevice for Fs {
Ok(())
}
fn reset(&mut self) -> Option<(Arc<VirtioInterrupt>, Vec<EventFd>)> {
if let Err(e) = reset_vhost_user(&mut self.vu, self.queue_sizes.len()) {
error!("Failed to reset vhost-user daemon: {:?}", e);
return None;
}
if let Some(kill_evt) = self.kill_evt.take() {
// Ignore the result because there is nothing we can do about it.
let _ = kill_evt.write(1);
}
// Return the interrupt and queue EventFDs
Some((
self.interrupt_cb.take().unwrap(),
self.queue_evts.take().unwrap(),
))
}
fn get_shm_regions(&self) -> Option<VirtioSharedMemoryList> {
if let Some(cache) = self.cache.clone() {
Some(cache.0)

View File

@@ -12,11 +12,13 @@ use std::io;
use vhost_rs::Error as VhostError;
use vm_memory::Error as MmapError;
pub mod blk;
pub mod fs;
mod handler;
pub mod net;
pub mod vu_common_ctrl;
pub use self::blk::Blk;
pub use self::fs::*;
pub use self::net::Net;
pub use self::vu_common_ctrl::VhostUserConfig;
@@ -59,6 +61,8 @@ pub enum Error {
VhostUserProtocolNotSupport,
/// Set owner failed.
VhostUserSetOwner(VhostError),
/// Reset owner failed.
VhostUserResetOwner(VhostError),
/// Set features failed.
VhostUserSetFeatures(VhostError),
/// Set protocol features failed.
@@ -91,5 +95,7 @@ pub enum Error {
VhostUserSetSlaveRequestFd(vhost_rs::Error),
/// Invalid used address.
UsedAddress,
/// Invalid features provided from vhost-user backend
InvalidFeatures,
}
type Result<T> = std::result::Result<T, Error>;

View File

@@ -19,33 +19,33 @@ use super::super::{ActivateError, ActivateResult, Queue, VirtioDevice, VirtioDev
use super::handler::*;
use super::vu_common_ctrl::*;
use super::{Error, Result};
use vhost_rs::vhost_user::message::VhostUserVirtioFeatures;
use vhost_rs::vhost_user::message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures};
use vhost_rs::vhost_user::{Master, VhostUserMaster, VhostUserMasterReqHandler};
use vhost_rs::VhostBackend;
use virtio_bindings::virtio_net;
use virtio_bindings::virtio_ring;
use virtio_bindings::bindings::virtio_net;
use virtio_bindings::bindings::virtio_ring;
struct SlaveReqHandler {}
impl VhostUserMasterReqHandler for SlaveReqHandler {}
pub struct Net {
vhost_user_net: Master,
kill_evt: EventFd,
kill_evt: Option<EventFd>,
avail_features: u64,
acked_features: u64,
backend_features: u64,
config_space: Vec<u8>,
queue_sizes: Vec<u16>,
queue_evts: Option<Vec<EventFd>>,
interrupt_cb: Option<Arc<VirtioInterrupt>>,
}
impl<'a> Net {
impl Net {
/// Create a new vhost-user-net device
pub fn new(mac_addr: MacAddr, vu_cfg: VhostUserConfig<'a>) -> Result<Net> {
let mut vhost_user_net = Master::connect(vu_cfg.sock, vu_cfg.num_queues as u64)
pub fn new(mac_addr: MacAddr, vu_cfg: VhostUserConfig) -> Result<Net> {
let mut vhost_user_net = Master::connect(&vu_cfg.sock, vu_cfg.num_queues as u64)
.map_err(Error::VhostUserCreateMaster)?;
let kill_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::CreateKillEventFd)?;
// Filling device and vring features VMM supports.
let mut avail_features = 1 << virtio_net::VIRTIO_NET_F_GUEST_CSUM
| 1 << virtio_net::VIRTIO_NET_F_CSUM
@@ -82,9 +82,13 @@ impl<'a> Net {
let mut acked_features = 0;
if avail_features & VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits() != 0 {
acked_features |= VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
vhost_user_net
let mut protocol_features = vhost_user_net
.get_protocol_features()
.map_err(Error::VhostUserGetProtocolFeatures)?;
protocol_features &= VhostUserProtocolFeatures::MQ;
vhost_user_net
.set_protocol_features(protocol_features)
.map_err(Error::VhostUserSetProtocolFeatures)?;
} else {
return Err(Error::VhostUserProtocolNotSupport);
}
@@ -104,20 +108,24 @@ impl<'a> Net {
Ok(Net {
vhost_user_net,
kill_evt,
kill_evt: None,
avail_features,
acked_features,
backend_features,
config_space,
queue_sizes: vec![vu_cfg.queue_size; vu_cfg.num_queues],
queue_evts: None,
interrupt_cb: None,
})
}
}
impl Drop for Net {
fn drop(&mut self) {
if let Err(_e) = self.kill_evt.write(1) {
error!("failed to kill vhost-user-net with error {}", _e);
if let Some(kill_evt) = self.kill_evt.take() {
if let Err(e) = kill_evt.write(1) {
error!("failed to kill vhost-user-net: {:?}", e);
}
}
}
}
@@ -193,10 +201,30 @@ impl VirtioDevice for Net {
queues: Vec<Queue>,
queue_evts: Vec<EventFd>,
) -> ActivateResult {
let handler_kill_evt = self
.kill_evt
.try_clone()
.map_err(|_| ActivateError::CloneKillEventFd)?;
let (self_kill_evt, kill_evt) =
match EventFd::new(EFD_NONBLOCK).and_then(|e| Ok((e.try_clone()?, e))) {
Ok(v) => v,
Err(e) => {
error!("failed creating kill EventFd pair: {}", e);
return Err(ActivateError::BadActivate);
}
};
self.kill_evt = Some(self_kill_evt);
// 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.clone());
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 vu_interrupt_list = setup_vhost_user(
&mut self.vhost_user_net,
@@ -209,7 +237,7 @@ impl VirtioDevice for Net {
let mut handler = VhostUserEpollHandler::<SlaveReqHandler>::new(VhostUserEpollConfig {
interrupt_cb,
kill_evt: handler_kill_evt,
kill_evt,
vu_interrupt_list,
slave_req_handler: None,
});
@@ -226,4 +254,22 @@ impl VirtioDevice for Net {
}
Ok(())
}
fn reset(&mut self) -> Option<(Arc<VirtioInterrupt>, Vec<EventFd>)> {
if let Err(e) = reset_vhost_user(&mut self.vhost_user_net, self.queue_sizes.len()) {
error!("Failed to reset vhost-user daemon: {:?}", e);
return None;
}
if let Some(kill_evt) = self.kill_evt.take() {
// Ignore the result because there is nothing we can do about it.
let _ = kill_evt.write(1);
}
// Return the interrupt and queue EventFDs
Some((
self.interrupt_cb.take().unwrap(),
self.queue_evts.take().unwrap(),
))
}
}

View File

@@ -14,9 +14,9 @@ use super::{Error, Result};
use vhost_rs::vhost_user::{Master, VhostUserMaster};
use vhost_rs::{VhostBackend, VhostUserMemoryRegionInfo, VringConfigData};
#[derive(Debug, Copy, Clone)]
pub struct VhostUserConfig<'a> {
pub sock: &'a str,
#[derive(Debug, Clone)]
pub struct VhostUserConfig {
pub sock: String,
pub num_queues: usize,
pub queue_size: u16,
}
@@ -54,7 +54,7 @@ pub fn setup_vhost_user_vring(
let mut vu_interrupt_list = Vec::new();
for (queue_index, queue) in queues.into_iter().enumerate() {
vu.set_vring_num(queue_index, queue.get_max_size())
vu.set_vring_num(queue_index, queue.actual_size())
.map_err(Error::VhostUserSetVringNum)?;
let config_data = VringConfigData {
@@ -106,3 +106,18 @@ pub fn setup_vhost_user(
setup_vhost_user_vring(vu, mem, queues, queue_evts)
}
pub fn reset_vhost_user(vu: &mut Master, num_queues: usize) -> Result<()> {
for queue_index in 0..num_queues {
// Disable the vrings.
vu.set_vring_enable(queue_index, false)
.map_err(Error::VhostUserSetVringEnable)?;
// Stop the vrings.
vu.get_vring_base(queue_index)
.map_err(Error::VhostUserSetFeatures)?;
}
// Reset the owner.
vu.reset_owner().map_err(Error::VhostUserResetOwner)
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,129 @@
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
//
/// This module implements our vsock connection state machine. The heavy lifting is done by
/// `connection::VsockConnection`, while this file only defines some constants and helper structs.
///
mod connection;
mod txbuf;
pub use connection::VsockConnection;
pub mod defs {
/// Vsock connection TX buffer capacity.
pub const CONN_TX_BUF_SIZE: usize = 64 * 1024;
/// After the guest thinks it has filled our TX buffer up to this limit (in bytes), we'll send
/// them a credit update packet, to let them know we can handle more.
pub const CONN_CREDIT_UPDATE_THRESHOLD: usize = CONN_TX_BUF_SIZE - 4 * 4 * 1024;
/// Connection request timeout, in millis.
pub const CONN_REQUEST_TIMEOUT_MS: u64 = 2000;
/// Connection graceful shutdown timeout, in millis.
pub const CONN_SHUTDOWN_TIMEOUT_MS: u64 = 2000;
}
#[derive(Debug)]
pub enum Error {
/// Attempted to push data to a full TX buffer.
TxBufFull,
/// An I/O error occurred, when attempting to flush the connection TX buffer.
TxBufFlush(std::io::Error),
/// An I/O error occurred, when attempting to write data to the host-side stream.
StreamWrite(std::io::Error),
}
type Result<T> = std::result::Result<T, Error>;
/// A vsock connection state.
///
#[derive(Debug, PartialEq)]
pub enum ConnState {
/// The connection has been initiated by the host end, but is yet to be confirmed by the guest.
LocalInit,
/// The connection has been initiated by the guest, but we are yet to confirm it, by sending
/// a response packet (VSOCK_OP_RESPONSE).
PeerInit,
/// The connection handshake has been performed successfully, and data can now be exchanged.
Established,
/// The host (AF_UNIX) socket was closed.
LocalClosed,
/// A VSOCK_OP_SHUTDOWN packet was received from the guest. The tuple represents the guest R/W
/// indication: (will_not_recv_anymore_data, will_not_send_anymore_data).
PeerClosed(bool, bool),
/// The connection is scheduled to be forcefully terminated as soon as possible.
Killed,
}
/// An RX indication, used by `VsockConnection` to schedule future `recv_pkt()` responses.
/// For instance, after being notified that there is available data to be read from the host stream
/// (via `notify()`), the connection will store a `PendingRx::Rw` to be later inspected by
/// `recv_pkt()`.
///
#[derive(Clone, Copy, PartialEq)]
enum PendingRx {
/// We need to yield a connection request packet (VSOCK_OP_REQUEST).
Request = 0,
/// We need to yield a connection response packet (VSOCK_OP_RESPONSE).
Response = 1,
/// We need to yield a forceful connection termination packet (VSOCK_OP_RST).
Rst = 2,
/// We need to yield a data packet (VSOCK_OP_RW), by reading from the AF_UNIX socket.
Rw = 3,
/// We need to yield a credit update packet (VSOCK_OP_CREDIT_UPDATE).
CreditUpdate = 4,
}
impl PendingRx {
/// Transform the enum value into a bitmask, that can be used for set operations.
///
fn into_mask(self) -> u16 {
1u16 << (self as u16)
}
}
/// A set of RX indications (`PendingRx` items).
///
struct PendingRxSet {
data: u16,
}
impl PendingRxSet {
/// Insert an item into the set.
///
fn insert(&mut self, it: PendingRx) {
self.data |= it.into_mask();
}
/// Remove an item from the set and return:
/// - true, if the item was in the set; or
/// - false, if the item wasn't in the set.
///
fn remove(&mut self, it: PendingRx) -> bool {
let ret = self.contains(it);
self.data &= !it.into_mask();
ret
}
/// Check if an item is present in this set.
///
fn contains(&self, it: PendingRx) -> bool {
self.data & it.into_mask() != 0
}
/// Check if the set is empty.
///
fn is_empty(&self) -> bool {
self.data == 0
}
}
/// Create a set containing only one item.
///
impl From<PendingRx> for PendingRxSet {
fn from(it: PendingRx) -> Self {
Self {
data: it.into_mask(),
}
}
}

View File

@@ -0,0 +1,279 @@
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
//
use std::io::Write;
use std::mem;
use std::num::Wrapping;
use super::defs;
use super::{Error, Result};
/// A simple ring-buffer implementation, used by vsock connections to buffer TX (guest -> host)
/// data. Memory for this buffer is allocated lazily, since buffering will only be needed when
/// the host can't read fast enough.
///
pub struct TxBuf {
/// The actual u8 buffer - only allocated after the first push.
data: Option<Box<[u8; Self::SIZE]>>,
/// Ring-buffer head offset - where new data is pushed to.
head: Wrapping<u32>,
/// Ring-buffer tail offset - where data is flushed from.
tail: Wrapping<u32>,
}
impl TxBuf {
/// Total buffer size, in bytes.
///
const SIZE: usize = defs::CONN_TX_BUF_SIZE;
/// Ring-buffer constructor.
///
pub fn new() -> Self {
Self {
data: None,
head: Wrapping(0),
tail: Wrapping(0),
}
}
/// Get the used length of this buffer - number of bytes that have been pushed in, but not
/// yet flushed out.
///
pub fn len(&self) -> usize {
(self.head - self.tail).0 as usize
}
/// Push a byte slice onto the ring-buffer.
///
/// Either the entire source slice will be pushed to the ring-buffer, or none of it, if
/// there isn't enough room, in which case `Err(Error::TxBufFull)` is returned.
///
pub fn push(&mut self, src: &[u8]) -> Result<()> {
// Error out if there's no room to push the entire slice.
if self.len() + src.len() > Self::SIZE {
return Err(Error::TxBufFull);
}
// We're using a closure here to return the boxed slice, instead of a value (i.e.
// `get_or_insert_with()` instead of `get_or_insert()`), because we only want the box
// created when `self.data` is None. If we were to use `get_or_insert(box)`, the box
// argument would always get evaluated (which implies a heap allocation), even though
// it would later be discarded (when `self.data.is_some()`). Apparently, clippy fails
// to see this, and insists on issuing some warning.
#[allow(clippy::redundant_closure)]
let data = self.data.get_or_insert_with(||
// Using uninitialized memory here is quite safe, since we never read from any
// area of the buffer before writing to it. First we push, then we flush only
// what had been prviously pushed.
Box::new(unsafe {mem::uninitialized::<[u8; Self::SIZE]>()}));
// Buffer head, as an offset into the data slice.
let head_ofs = self.head.0 as usize % Self::SIZE;
// Pushing a slice to this buffer can take either one or two slice copies: - one copy,
// if the slice fits between `head_ofs` and `Self::SIZE`; or - two copies, if the
// ring-buffer head wraps around.
// First copy length: we can only go from the head offset up to the total buffer size.
let len = std::cmp::min(Self::SIZE - head_ofs, src.len());
data[head_ofs..(head_ofs + len)].copy_from_slice(&src[..len]);
// If the slice didn't fit, the buffer head will wrap around, and pushing continues
// from the start of the buffer (`&self.data[0]`).
if len < src.len() {
data[..(src.len() - len)].copy_from_slice(&src[len..]);
}
// Either way, we've just pushed exactly `src.len()` bytes, so that's the amount by
// which the (wrapping) buffer head needs to move forward.
self.head += Wrapping(src.len() as u32);
Ok(())
}
/// Flush the contents of the ring-buffer to a writable stream.
///
/// Return the number of bytes that have been transferred out of the ring-buffer and into
/// the writable stream.
///
pub fn flush_to<W>(&mut self, sink: &mut W) -> Result<usize>
where
W: Write,
{
// Nothing to do, if this buffer holds no data.
if self.is_empty() {
return Ok(0);
}
// Buffer tail, as an offset into the buffer data slice.
let tail_ofs = self.tail.0 as usize % Self::SIZE;
// Flushing the buffer can take either one or two writes:
// - one write, if the tail doesn't need to wrap around to reach the head; or
// - two writes, if the tail would wrap around: tail to slice end, then slice end to
// head.
// First write length: the lesser of tail to slice end, or tail to head.
let len_to_write = std::cmp::min(Self::SIZE - tail_ofs, self.len());
// It's safe to unwrap here, since we've already checked if the buffer was empty.
let data = self.data.as_ref().unwrap();
// Issue the first write and absorb any `WouldBlock` error (we can just try again
// later).
let written = sink
.write(&data[tail_ofs..(tail_ofs + len_to_write)])
.map_err(Error::TxBufFlush)?;
// Move the buffer tail ahead by the amount (of bytes) we were able to flush out.
self.tail += Wrapping(written as u32);
// If we weren't able to flush out as much as we tried, there's no point in attempting
// our second write.
if written < len_to_write {
return Ok(written);
}
// Attempt our second write. This will return immediately if a second write isn't
// needed, since checking for an empty buffer is the first thing we do in this
// function.
Ok(written + self.flush_to(sink)?)
}
/// Check if the buffer holds any data that hasn't yet been flushed out.
///
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Error as IoError;
use std::io::Result as IoResult;
use std::io::{ErrorKind, Write};
struct TestSink {
data: Vec<u8>,
err: Option<IoError>,
capacity: usize,
}
impl TestSink {
const DEFAULT_CAPACITY: usize = 2 * TxBuf::SIZE;
fn new() -> Self {
Self {
data: Vec::with_capacity(Self::DEFAULT_CAPACITY),
err: None,
capacity: Self::DEFAULT_CAPACITY,
}
}
}
impl Write for TestSink {
fn write(&mut self, src: &[u8]) -> IoResult<usize> {
if self.err.is_some() {
return Err(self.err.take().unwrap());
}
let len_to_push = std::cmp::min(self.capacity - self.data.len(), src.len());
self.data.extend_from_slice(&src[..len_to_push]);
Ok(len_to_push)
}
fn flush(&mut self) -> IoResult<()> {
Ok(())
}
}
impl TestSink {
fn clear(&mut self) {
self.data = Vec::with_capacity(self.capacity);
self.err = None;
}
fn set_err(&mut self, err: IoError) {
self.err = Some(err);
}
fn set_capacity(&mut self, capacity: usize) {
self.capacity = capacity;
if self.data.len() > self.capacity {
self.data.resize(self.capacity, 0);
}
}
}
#[test]
fn test_push_nowrap() {
let mut txbuf = TxBuf::new();
let mut sink = TestSink::new();
assert!(txbuf.is_empty());
assert!(txbuf.data.is_none());
txbuf.push(&[1, 2, 3, 4]).unwrap();
txbuf.push(&[5, 6, 7, 8]).unwrap();
txbuf.flush_to(&mut sink).unwrap();
assert_eq!(sink.data, [1, 2, 3, 4, 5, 6, 7, 8]);
}
#[test]
fn test_push_wrap() {
let mut txbuf = TxBuf::new();
let mut sink = TestSink::new();
let mut tmp: Vec<u8> = Vec::new();
tmp.resize(TxBuf::SIZE - 2, 0);
txbuf.push(tmp.as_slice()).unwrap();
txbuf.flush_to(&mut sink).unwrap();
sink.clear();
txbuf.push(&[1, 2, 3, 4]).unwrap();
assert_eq!(txbuf.flush_to(&mut sink).unwrap(), 4);
assert_eq!(sink.data, [1, 2, 3, 4]);
}
#[test]
fn test_push_error() {
let mut txbuf = TxBuf::new();
let mut tmp = Vec::with_capacity(TxBuf::SIZE);
tmp.resize(TxBuf::SIZE - 1, 0);
txbuf.push(tmp.as_slice()).unwrap();
match txbuf.push(&[1, 2]) {
Err(Error::TxBufFull) => (),
other => panic!("Unexpected result: {:?}", other),
}
}
#[test]
fn test_incomplete_flush() {
let mut txbuf = TxBuf::new();
let mut sink = TestSink::new();
sink.set_capacity(2);
txbuf.push(&[1, 2, 3, 4]).unwrap();
assert_eq!(txbuf.flush_to(&mut sink).unwrap(), 2);
assert_eq!(txbuf.len(), 2);
assert_eq!(sink.data, [1, 2]);
sink.set_capacity(4);
assert_eq!(txbuf.flush_to(&mut sink).unwrap(), 2);
assert!(txbuf.is_empty());
assert_eq!(sink.data, [1, 2, 3, 4]);
}
#[test]
fn test_flush_error() {
const EACCESS: i32 = 13;
let mut txbuf = TxBuf::new();
let mut sink = TestSink::new();
txbuf.push(&[1, 2, 3, 4]).unwrap();
let io_err = IoError::from_raw_os_error(EACCESS);
sink.set_err(io_err);
match txbuf.flush_to(&mut sink) {
Err(Error::TxBufFlush(ref err)) if err.kind() == ErrorKind::PermissionDenied => (),
other => panic!("Unexpected result: {:?}", other),
}
}
}

View File

@@ -0,0 +1,885 @@
// Copyright 2019 Intel Corporation. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
//
// Portions Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
//
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the THIRD-PARTY file.
/// This is the `VirtioDevice` implementation for our vsock device. It handles the virtio-level
/// device logic: feature negociation, device configuration, and device activation.
/// The run-time device logic (i.e. event-driven data handling) is implemented by
/// `super::epoll_handler::EpollHandler`.
///
/// We aim to conform to the VirtIO v1.1 spec:
/// https://docs.oasis-open.org/virtio/virtio/v1.1/virtio-v1.1.html
///
/// The vsock device has two input parameters: a CID to identify the device, and a `VsockBackend`
/// to use for offloading vsock traffic.
///
/// Upon its activation, the vsock device creates its `EpollHandler`, passes it the event-interested
/// file descriptors, and registers these descriptors with the VMM `EpollContext`. Going forward,
/// the `EpollHandler` will get notified whenever an event occurs on the just-registered FDs:
/// - an RX queue FD;
/// - a TX queue FD;
/// - an event queue FD; and
/// - a backend FD.
///
use epoll;
use libc::EFD_NONBLOCK;
use std;
use std::io;
use std::os::unix::io::AsRawFd;
use std::result;
use std::sync::{Arc, RwLock};
use std::thread;
use super::{VsockBackend, VsockPacket};
use crate::Error as DeviceError;
use crate::VirtioInterrupt;
use crate::{
ActivateError, ActivateResult, DeviceEventT, Queue, VirtioDevice, VirtioDeviceType,
VirtioInterruptType, VIRTIO_F_IN_ORDER, VIRTIO_F_IOMMU_PLATFORM, VIRTIO_F_VERSION_1,
};
use byteorder::{ByteOrder, LittleEndian};
use vm_memory::GuestMemoryMmap;
use vmm_sys_util::eventfd::EventFd;
const QUEUE_SIZE: u16 = 256;
const NUM_QUEUES: usize = 3;
const QUEUE_SIZES: &[u16] = &[QUEUE_SIZE; NUM_QUEUES];
// New descriptors are pending on the rx queue.
pub const RX_QUEUE_EVENT: DeviceEventT = 0;
// New descriptors are pending on the tx queue.
pub const TX_QUEUE_EVENT: DeviceEventT = 1;
// New descriptors are pending on the event queue.
pub const EVT_QUEUE_EVENT: DeviceEventT = 2;
// Notification coming from the backend.
pub const BACKEND_EVENT: DeviceEventT = 3;
// The device has been dropped.
pub const KILL_EVENT: DeviceEventT = 4;
pub const EVENTS_LEN: usize = 5;
/// The `VsockEpollHandler` implements the runtime logic of our vsock device:
/// 1. Respond to TX queue events by wrapping virtio buffers into `VsockPacket`s, then sending those
/// packets to the `VsockBackend`;
/// 2. Forward backend FD event notifications to the `VsockBackend`;
/// 3. Fetch incoming packets from the `VsockBackend` and place them into the virtio RX queue;
/// 4. Whenever we have processed some virtio buffers (either TX or RX), let the driver know by
/// raising our assigned IRQ.
///
/// In a nutshell, the `VsockEpollHandler` logic looks like this:
/// - on TX queue event:
/// - fetch all packets from the TX queue and send them to the backend; then
/// - if the backend has queued up any incoming packets, fetch them into any available RX buffers.
/// - on RX queue event:
/// - fetch any incoming packets, queued up by the backend, into newly available RX buffers.
/// - on backend event:
/// - forward the event to the backend; then
/// - again, attempt to fetch any incoming packets queued by the backend into virtio RX buffers.
///
pub struct VsockEpollHandler<B: VsockBackend> {
pub mem: Arc<RwLock<GuestMemoryMmap>>,
pub queues: Vec<Queue>,
pub queue_evts: Vec<EventFd>,
pub kill_evt: EventFd,
pub interrupt_cb: Arc<VirtioInterrupt>,
pub backend: Arc<RwLock<B>>,
}
impl<B> VsockEpollHandler<B>
where
B: VsockBackend,
{
/// Signal the guest driver that we've used some virtio buffers that it had previously made
/// available.
///
fn signal_used_queue(&self, queue: &Queue) -> result::Result<(), DeviceError> {
debug!("vsock: raising IRQ");
(self.interrupt_cb)(&VirtioInterruptType::Queue, Some(queue)).map_err(|e| {
error!("Failed to signal used queue: {:?}", e);
DeviceError::FailedSignalingUsedQueue(e)
})
}
/// Walk the driver-provided RX queue buffers and attempt to fill them up with any data that we
/// have pending.
///
fn process_rx(&mut self) -> result::Result<(), DeviceError> {
debug!("vsock: epoll_handler::process_rx()");
let mut used_desc_heads = [(0, 0); QUEUE_SIZE as usize];
let mut used_count = 0;
let mem = self.mem.read().unwrap();
for avail_desc in self.queues[0].iter(&mem) {
let used_len = match VsockPacket::from_rx_virtq_head(&avail_desc) {
Ok(mut pkt) => {
if self.backend.write().unwrap().recv_pkt(&mut pkt).is_ok() {
pkt.hdr().len() as u32 + pkt.len()
} else {
// We are using a consuming iterator over the virtio buffers, so, if we can't
// fill in this buffer, we'll need to undo the last iterator step.
self.queues[0].go_to_previous_position();
break;
}
}
Err(e) => {
warn!("vsock: RX queue error: {:?}", e);
0
}
};
used_desc_heads[used_count] = (avail_desc.index, used_len);
used_count += 1;
}
for &(desc_index, len) in &used_desc_heads[..used_count] {
self.queues[0].add_used(&mem, desc_index, len);
}
if used_count > 0 {
self.signal_used_queue(&self.queues[0])
} else {
Ok(())
}
}
/// Walk the driver-provided TX queue buffers, package them up as vsock packets, and send them to
/// the backend for processing.
///
fn process_tx(&mut self) -> result::Result<(), DeviceError> {
debug!("vsock: epoll_handler::process_tx()");
let mut used_desc_heads = [(0, 0); QUEUE_SIZE as usize];
let mut used_count = 0;
let mem = self.mem.read().unwrap();
for avail_desc in self.queues[1].iter(&mem) {
let pkt = match VsockPacket::from_tx_virtq_head(&avail_desc) {
Ok(pkt) => pkt,
Err(e) => {
error!("vsock: error reading TX packet: {:?}", e);
used_desc_heads[used_count] = (avail_desc.index, 0);
used_count += 1;
continue;
}
};
if self.backend.write().unwrap().send_pkt(&pkt).is_err() {
self.queues[1].go_to_previous_position();
break;
}
used_desc_heads[used_count] = (avail_desc.index, 0);
used_count += 1;
}
for &(desc_index, len) in &used_desc_heads[..used_count] {
self.queues[1].add_used(&mem, desc_index, len);
}
if used_count > 0 {
self.signal_used_queue(&self.queues[1])
} else {
Ok(())
}
}
fn run(&mut self) -> result::Result<(), DeviceError> {
// Create the epoll file descriptor
let epoll_fd = epoll::create(true).map_err(DeviceError::EpollCreateFd)?;
// Add events
epoll::ctl(
epoll_fd,
epoll::ControlOptions::EPOLL_CTL_ADD,
self.queue_evts[0].as_raw_fd(),
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(RX_QUEUE_EVENT)),
)
.map_err(DeviceError::EpollCtl)?;
epoll::ctl(
epoll_fd,
epoll::ControlOptions::EPOLL_CTL_ADD,
self.queue_evts[1].as_raw_fd(),
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(TX_QUEUE_EVENT)),
)
.map_err(DeviceError::EpollCtl)?;
epoll::ctl(
epoll_fd,
epoll::ControlOptions::EPOLL_CTL_ADD,
self.queue_evts[2].as_raw_fd(),
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(EVT_QUEUE_EVENT)),
)
.map_err(DeviceError::EpollCtl)?;
epoll::ctl(
epoll_fd,
epoll::ControlOptions::EPOLL_CTL_ADD,
self.backend.read().unwrap().get_polled_fd(),
epoll::Event::new(
self.backend.read().unwrap().get_polled_evset(),
u64::from(BACKEND_EVENT),
),
)
.map_err(DeviceError::EpollCtl)?;
epoll::ctl(
epoll_fd,
epoll::ControlOptions::EPOLL_CTL_ADD,
self.kill_evt.as_raw_fd(),
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(KILL_EVENT)),
)
.map_err(DeviceError::EpollCtl)?;
let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EVENTS_LEN];
'epoll: loop {
let num_events = match epoll::wait(epoll_fd, -1, &mut events[..]) {
Ok(res) => res,
Err(e) => {
if e.kind() == io::ErrorKind::Interrupted {
// It's well defined from the epoll_wait() syscall
// documentation that the epoll loop can be interrupted
// before any of the requested events occurred or the
// timeout expired. In both those cases, epoll_wait()
// returns an error of type EINTR, but this should not
// be considered as a regular error. Instead it is more
// appropriate to retry, by calling into epoll_wait().
continue;
}
return Err(DeviceError::EpollWait(e));
}
};
for event in events.iter().take(num_events) {
let evset = match epoll::Events::from_bits(event.events) {
Some(evset) => evset,
None => {
let evbits = event.events;
warn!("epoll: ignoring unknown event set: 0x{:x}", evbits);
continue;
}
};
let ev_type = event.data as DeviceEventT;
if self.handle_event(ev_type, evset)? {
break 'epoll;
}
}
}
Ok(())
}
pub fn handle_event(
&mut self,
device_event: DeviceEventT,
evset: epoll::Events,
) -> Result<bool, DeviceError> {
match device_event {
RX_QUEUE_EVENT => {
debug!("vsock: RX queue event");
if let Err(e) = self.queue_evts[0].read() {
error!("Failed to get RX queue event: {:?}", e);
return Err(DeviceError::FailedReadingQueue {
event_type: "rx queue event",
underlying: e,
});
} else if self.backend.read().unwrap().has_pending_rx() {
self.process_rx()?;
}
}
TX_QUEUE_EVENT => {
debug!("vsock: TX queue event");
if let Err(e) = self.queue_evts[1].read() {
error!("Failed to get TX queue event: {:?}", e);
return Err(DeviceError::FailedReadingQueue {
event_type: "tx queue event",
underlying: e,
});
} else {
self.process_tx()?;
// The backend may have queued up responses to the packets we sent during TX queue
// processing. If that happened, we need to fetch those responses and place them
// into RX buffers.
if self.backend.read().unwrap().has_pending_rx() {
self.process_rx()?;
}
}
}
EVT_QUEUE_EVENT => {
debug!("vsock: EVT queue event");
if let Err(e) = self.queue_evts[2].read() {
error!("Failed to get EVT queue event: {:?}", e);
return Err(DeviceError::FailedReadingQueue {
event_type: "evt queue event",
underlying: e,
});
}
}
BACKEND_EVENT => {
debug!("vsock: backend event");
self.backend.write().unwrap().notify(evset);
// After the backend has been kicked, it might've freed up some resources, so we
// can attempt to send it more data to process.
// In particular, if `self.backend.send_pkt()` halted the TX queue processing (by
// reurning an error) at some point in the past, now is the time to try walking the
// TX queue again.
self.process_tx()?;
if self.backend.read().unwrap().has_pending_rx() {
self.process_rx()?;
}
}
KILL_EVENT => {
debug!("KILL_EVENT received, stopping epoll loop");
return Ok(true);
}
other => {
error!("Unknown event for virtio-vsock");
return Err(DeviceError::UnknownEvent {
device: "vsock",
event: other,
});
}
}
Ok(false)
}
}
/// Virtio device exposing virtual socket to the guest.
pub struct Vsock<B: VsockBackend> {
cid: u64,
backend: Arc<RwLock<B>>,
kill_evt: Option<EventFd>,
avail_features: u64,
acked_features: u64,
queue_evts: Option<Vec<EventFd>>,
interrupt_cb: Option<Arc<VirtioInterrupt>>,
}
impl<B> Vsock<B>
where
B: VsockBackend,
{
/// Create a new virtio-vsock device with the given VM CID and vsock
/// backend.
pub fn new(cid: u64, backend: B, iommu: bool) -> io::Result<Vsock<B>> {
let mut avail_features = 1u64 << VIRTIO_F_VERSION_1 | 1u64 << VIRTIO_F_IN_ORDER;
if iommu {
avail_features |= 1u64 << VIRTIO_F_IOMMU_PLATFORM;
}
Ok(Vsock {
cid,
backend: Arc::new(RwLock::new(backend)),
kill_evt: None,
avail_features,
acked_features: 0u64,
queue_evts: None,
interrupt_cb: None,
})
}
}
impl<B> Drop for Vsock<B>
where
B: VsockBackend,
{
fn drop(&mut self) {
if let Some(kill_evt) = self.kill_evt.take() {
// Ignore the result because there is nothing we can do about it.
let _ = kill_evt.write(1);
}
}
}
impl<B> VirtioDevice for Vsock<B>
where
B: VsockBackend + Sync + 'static,
{
fn device_type(&self) -> u32 {
VirtioDeviceType::TYPE_VSOCK as u32
}
fn queue_max_sizes(&self) -> &[u16] {
QUEUE_SIZES
}
fn features(&self, page: u32) -> u32 {
match page {
// 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) {
let mut v = match page {
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.
let unrequested_features = v & !self.avail_features;
if unrequested_features != 0 {
warn!("Received acknowledge request for unknown feature.");
// Don't count these features as acked.
v &= !unrequested_features;
}
self.acked_features |= v;
}
fn read_config(&self, offset: u64, data: &mut [u8]) {
match offset {
0 if data.len() == 8 => LittleEndian::write_u64(data, self.cid),
0 if data.len() == 4 => LittleEndian::write_u32(data, (self.cid & 0xffff_ffff) as u32),
4 if data.len() == 4 => {
LittleEndian::write_u32(data, ((self.cid >> 32) & 0xffff_ffff) as u32)
}
_ => warn!(
"vsock: virtio-vsock received invalid read request of {} bytes at offset {}",
data.len(),
offset
),
}
}
fn write_config(&mut self, offset: u64, data: &[u8]) {
warn!(
"vsock: guest driver attempted to write device config (offset={:x}, len={:x})",
offset,
data.len()
);
}
fn activate(
&mut self,
mem: Arc<RwLock<GuestMemoryMmap>>,
interrupt_cb: Arc<VirtioInterrupt>,
queues: Vec<Queue>,
queue_evts: Vec<EventFd>,
) -> ActivateResult {
if queues.len() != NUM_QUEUES || queue_evts.len() != NUM_QUEUES {
error!(
"Cannot perform activate. Expected {} queue(s), got {}",
NUM_QUEUES,
queues.len()
);
return Err(ActivateError::BadActivate);
}
let (self_kill_evt, kill_evt) =
match EventFd::new(EFD_NONBLOCK).and_then(|e| Ok((e.try_clone()?, e))) {
Ok(v) => v,
Err(e) => {
error!("failed creating kill EventFd pair: {}", e);
return Err(ActivateError::BadActivate);
}
};
self.kill_evt = Some(self_kill_evt);
// 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.clone());
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 handler = VsockEpollHandler {
mem,
queues,
queue_evts,
kill_evt,
interrupt_cb,
backend: self.backend.clone(),
};
let worker_result = thread::Builder::new()
.name("virtio_vsock".to_string())
.spawn(move || handler.run());
if let Err(e) = worker_result {
error!("failed to spawn virtio_vsock worker: {}", e);
return Err(ActivateError::BadActivate);
}
Ok(())
}
fn reset(&mut self) -> Option<(Arc<VirtioInterrupt>, Vec<EventFd>)> {
if let Some(kill_evt) = self.kill_evt.take() {
// Ignore the result because there is nothing we can do about it.
let _ = kill_evt.write(1);
}
// Return the interrupt and queue EventFDs
Some((
self.interrupt_cb.take().unwrap(),
self.queue_evts.take().unwrap(),
))
}
}
#[cfg(test)]
mod tests {
use super::super::tests::TestContext;
use super::super::*;
use super::*;
use crate::vsock::device::{BACKEND_EVENT, EVT_QUEUE_EVENT, RX_QUEUE_EVENT, TX_QUEUE_EVENT};
#[test]
fn test_virtio_device() {
let mut ctx = TestContext::new();
let avail_features = 1u64 << VIRTIO_F_VERSION_1 | 1u64 << VIRTIO_F_IN_ORDER;
let device_features = avail_features;
let driver_features: u64 = avail_features | 1 | (1 << 32);
let device_pages = [
(device_features & 0xffff_ffff) as u32,
(device_features >> 32) as u32,
];
let driver_pages = [
(driver_features & 0xffff_ffff) as u32,
(driver_features >> 32) as u32,
];
assert_eq!(
ctx.device.device_type(),
VirtioDeviceType::TYPE_VSOCK as u32
);
assert_eq!(ctx.device.queue_max_sizes(), QUEUE_SIZES);
assert_eq!(ctx.device.features(0), device_pages[0]);
assert_eq!(ctx.device.features(1), device_pages[1]);
assert_eq!(ctx.device.features(2), 0);
// Ack device features, page 0.
ctx.device.ack_features(0, driver_pages[0]);
// Ack device features, page 1.
ctx.device.ack_features(1, driver_pages[1]);
// Ack some bogus page (i.e. 2). This should have no side effect.
ctx.device.ack_features(2, 0);
// Attempt to un-ack the first feature page. This should have no side effect.
ctx.device.ack_features(0, !driver_pages[0]);
// Check that no side effect are present, and that the acked features are exactly the same
// as the device features.
assert_eq!(ctx.device.acked_features, device_features & driver_features);
// Test reading 32-bit chunks.
let mut data = [0u8; 8];
ctx.device.read_config(0, &mut data[..4]);
assert_eq!(
u64::from(LittleEndian::read_u32(&data)),
ctx.cid & 0xffff_ffff
);
ctx.device.read_config(4, &mut data[4..]);
assert_eq!(
u64::from(LittleEndian::read_u32(&data[4..])),
(ctx.cid >> 32) & 0xffff_ffff
);
// Test reading 64-bit.
let mut data = [0u8; 8];
ctx.device.read_config(0, &mut data);
assert_eq!(LittleEndian::read_u64(&data), ctx.cid);
// Check that out-of-bounds reading doesn't mutate the destination buffer.
let mut data = [0u8, 1, 2, 3, 4, 5, 6, 7];
ctx.device.read_config(2, &mut data);
assert_eq!(data, [0u8, 1, 2, 3, 4, 5, 6, 7]);
// Just covering lines here, since the vsock device has no writable config.
// A warning is, however, logged, if the guest driver attempts to write any config data.
ctx.device.write_config(0, &data[..4]);
// Test a bad activation.
let bad_activate = ctx.device.activate(
Arc::new(RwLock::new(ctx.mem.clone())),
Arc::new(
Box::new(move |_: &VirtioInterruptType, _: Option<&Queue>| Ok(()))
as VirtioInterrupt,
),
Vec::new(),
Vec::new(),
);
match bad_activate {
Err(ActivateError::BadActivate) => (),
other => panic!("{:?}", other),
}
// Test a correct activation.
ctx.device
.activate(
Arc::new(RwLock::new(ctx.mem.clone())),
Arc::new(
Box::new(move |_: &VirtioInterruptType, _: Option<&Queue>| Ok(()))
as VirtioInterrupt,
),
vec![Queue::new(256), Queue::new(256), Queue::new(256)],
vec![
EventFd::new(EFD_NONBLOCK).unwrap(),
EventFd::new(EFD_NONBLOCK).unwrap(),
EventFd::new(EFD_NONBLOCK).unwrap(),
],
)
.unwrap();
}
#[test]
fn test_irq() {
// Test case: successful IRQ signaling.
{
let test_ctx = TestContext::new();
let ctx = test_ctx.create_epoll_handler_context();
let queue = Queue::new(256);
assert!(ctx.handler.signal_used_queue(&queue).is_ok());
}
}
#[test]
fn test_txq_event() {
// Test case:
// - the driver has something to send (there's data in the TX queue); and
// - the backend has no pending RX data.
{
let test_ctx = TestContext::new();
let mut ctx = test_ctx.create_epoll_handler_context();
ctx.handler.backend.write().unwrap().set_pending_rx(false);
ctx.signal_txq_event();
// The available TX descriptor should have been used.
assert_eq!(ctx.guest_txvq.used.idx.get(), 1);
// The available RX descriptor should be untouched.
assert_eq!(ctx.guest_rxvq.used.idx.get(), 0);
}
// Test case:
// - the driver has something to send (there's data in the TX queue); and
// - the backend also has some pending RX data.
{
let test_ctx = TestContext::new();
let mut ctx = test_ctx.create_epoll_handler_context();
ctx.handler.backend.write().unwrap().set_pending_rx(true);
ctx.signal_txq_event();
// Both available RX and TX descriptors should have been used.
assert_eq!(ctx.guest_txvq.used.idx.get(), 1);
assert_eq!(ctx.guest_rxvq.used.idx.get(), 1);
}
// Test case:
// - the driver has something to send (there's data in the TX queue); and
// - the backend errors out and cannot process the TX queue.
{
let test_ctx = TestContext::new();
let mut ctx = test_ctx.create_epoll_handler_context();
ctx.handler.backend.write().unwrap().set_pending_rx(false);
ctx.handler
.backend
.write()
.unwrap()
.set_tx_err(Some(VsockError::NoData));
ctx.signal_txq_event();
// Both RX and TX queues should be untouched.
assert_eq!(ctx.guest_txvq.used.idx.get(), 0);
assert_eq!(ctx.guest_rxvq.used.idx.get(), 0);
}
// Test case:
// - the driver supplied a malformed TX buffer.
{
let test_ctx = TestContext::new();
let mut ctx = test_ctx.create_epoll_handler_context();
// Invalidate the packet header descriptor, by setting its length to 0.
ctx.guest_txvq.dtable[0].len.set(0);
ctx.signal_txq_event();
// The available descriptor should have been consumed, but no packet should have
// reached the backend.
assert_eq!(ctx.guest_txvq.used.idx.get(), 1);
assert_eq!(ctx.handler.backend.read().unwrap().tx_ok_cnt, 0);
}
// Test case: spurious TXQ_EVENT.
{
let test_ctx = TestContext::new();
let mut ctx = test_ctx.create_epoll_handler_context();
match ctx
.handler
.handle_event(TX_QUEUE_EVENT, epoll::Events::EPOLLIN)
{
Err(DeviceError::FailedReadingQueue { .. }) => (),
other => panic!("{:?}", other),
}
}
}
#[test]
fn test_rxq_event() {
// Test case:
// - there is pending RX data in the backend; and
// - the driver makes RX buffers available; and
// - the backend successfully places its RX data into the queue.
{
let test_ctx = TestContext::new();
let mut ctx = test_ctx.create_epoll_handler_context();
ctx.handler.backend.write().unwrap().set_pending_rx(true);
ctx.handler
.backend
.write()
.unwrap()
.set_rx_err(Some(VsockError::NoData));
ctx.signal_rxq_event();
// The available RX buffer should've been left untouched.
assert_eq!(ctx.guest_rxvq.used.idx.get(), 0);
}
// Test case:
// - there is pending RX data in the backend; and
// - the driver makes RX buffers available; and
// - the backend errors out, when attempting to receive data.
{
let test_ctx = TestContext::new();
let mut ctx = test_ctx.create_epoll_handler_context();
ctx.handler.backend.write().unwrap().set_pending_rx(true);
ctx.signal_rxq_event();
// The available RX buffer should have been used.
assert_eq!(ctx.guest_rxvq.used.idx.get(), 1);
}
// Test case: the driver provided a malformed RX descriptor chain.
{
let test_ctx = TestContext::new();
let mut ctx = test_ctx.create_epoll_handler_context();
// Invalidate the packet header descriptor, by setting its length to 0.
ctx.guest_rxvq.dtable[0].len.set(0);
// The chain should've been processed, without employing the backend.
assert!(ctx.handler.process_rx().is_ok());
assert_eq!(ctx.guest_rxvq.used.idx.get(), 1);
assert_eq!(ctx.handler.backend.read().unwrap().rx_ok_cnt, 0);
}
// Test case: spurious RXQ_EVENT.
{
let test_ctx = TestContext::new();
let mut ctx = test_ctx.create_epoll_handler_context();
ctx.handler.backend.write().unwrap().set_pending_rx(false);
match ctx
.handler
.handle_event(RX_QUEUE_EVENT, epoll::Events::EPOLLIN)
{
Err(DeviceError::FailedReadingQueue { .. }) => (),
other => panic!("{:?}", other),
}
}
}
#[test]
fn test_evq_event() {
// Test case: spurious EVQ_EVENT.
{
let test_ctx = TestContext::new();
let mut ctx = test_ctx.create_epoll_handler_context();
ctx.handler.backend.write().unwrap().set_pending_rx(false);
match ctx
.handler
.handle_event(EVT_QUEUE_EVENT, epoll::Events::EPOLLIN)
{
Err(DeviceError::FailedReadingQueue { .. }) => (),
other => panic!("{:?}", other),
}
}
}
#[test]
fn test_backend_event() {
// Test case:
// - a backend event is received; and
// - the backend has pending RX data.
{
let test_ctx = TestContext::new();
let mut ctx = test_ctx.create_epoll_handler_context();
ctx.handler.backend.write().unwrap().set_pending_rx(true);
ctx.handler
.handle_event(BACKEND_EVENT, epoll::Events::EPOLLIN)
.unwrap();
// The backend should've received this event.
assert_eq!(
ctx.handler.backend.read().unwrap().evset,
Some(epoll::Events::EPOLLIN)
);
// TX queue processing should've been triggered.
assert_eq!(ctx.guest_txvq.used.idx.get(), 1);
// RX queue processing should've been triggered.
assert_eq!(ctx.guest_rxvq.used.idx.get(), 1);
}
// Test case:
// - a backend event is received; and
// - the backend doesn't have any pending RX data.
{
let test_ctx = TestContext::new();
let mut ctx = test_ctx.create_epoll_handler_context();
ctx.handler.backend.write().unwrap().set_pending_rx(false);
ctx.handler
.handle_event(BACKEND_EVENT, epoll::Events::EPOLLIN)
.unwrap();
// The backend should've received this event.
assert_eq!(
ctx.handler.backend.read().unwrap().evset,
Some(epoll::Events::EPOLLIN)
);
// TX queue processing should've been triggered.
assert_eq!(ctx.guest_txvq.used.idx.get(), 1);
// The RX queue should've been left untouched.
assert_eq!(ctx.guest_rxvq.used.idx.get(), 0);
}
}
#[test]
fn test_unknown_event() {
let test_ctx = TestContext::new();
let mut ctx = test_ctx.create_epoll_handler_context();
match ctx.handler.handle_event(0xff, epoll::Events::EPOLLIN) {
Err(DeviceError::UnknownEvent { .. }) => (),
other => panic!("{:?}", other),
}
}
}

337
vm-virtio/src/vsock/mod.rs Normal file
View File

@@ -0,0 +1,337 @@
// Copyright 2019 Intel Corporation. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
//
// Portions Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
//
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the THIRD-PARTY file.
mod csm;
mod device;
mod packet;
mod unix;
pub use self::device::Vsock;
pub use self::unix::VsockUnixBackend;
pub use self::unix::VsockUnixError;
use std::os::unix::io::RawFd;
use packet::VsockPacket;
mod defs {
/// Max vsock packet data/buffer size.
pub const MAX_PKT_BUF_SIZE: usize = 64 * 1024;
pub mod uapi {
/// Vsock packet operation IDs.
/// Defined in `/include/uapi/linux/virtio_vsock.h`.
///
/// Connection request.
pub const VSOCK_OP_REQUEST: u16 = 1;
/// Connection response.
pub const VSOCK_OP_RESPONSE: u16 = 2;
/// Connection reset.
pub const VSOCK_OP_RST: u16 = 3;
/// Connection clean shutdown.
pub const VSOCK_OP_SHUTDOWN: u16 = 4;
/// Connection data (read/write).
pub const VSOCK_OP_RW: u16 = 5;
/// Flow control credit update.
pub const VSOCK_OP_CREDIT_UPDATE: u16 = 6;
/// Flow control credit update request.
pub const VSOCK_OP_CREDIT_REQUEST: u16 = 7;
/// Vsock packet flags.
/// Defined in `/include/uapi/linux/virtio_vsock.h`.
///
/// Valid with a VSOCK_OP_SHUTDOWN packet: the packet sender will receive no more data.
pub const VSOCK_FLAGS_SHUTDOWN_RCV: u32 = 1;
/// Valid with a VSOCK_OP_SHUTDOWN packet: the packet sender will send no more data.
pub const VSOCK_FLAGS_SHUTDOWN_SEND: u32 = 2;
/// Vsock packet type.
/// Defined in `/include/uapi/linux/virtio_vsock.h`.
///
/// Stream / connection-oriented packet (the only currently valid type).
pub const VSOCK_TYPE_STREAM: u16 = 1;
pub const VSOCK_HOST_CID: u64 = 2;
}
}
#[derive(Debug)]
pub enum VsockError {
/// The vsock data/buffer virtio descriptor length is smaller than expected.
BufDescTooSmall,
/// The vsock data/buffer virtio descriptor is expected, but missing.
BufDescMissing,
/// Chained GuestMemory error.
GuestMemory,
/// Bounds check failed on guest memory pointer.
GuestMemoryBounds,
/// The vsock header descriptor length is too small.
HdrDescTooSmall(u32),
/// The vsock header `len` field holds an invalid value.
InvalidPktLen(u32),
/// A data fetch was attempted when no data was available.
NoData,
/// A data buffer was expected for the provided packet, but it is missing.
PktBufMissing,
/// Encountered an unexpected write-only virtio descriptor.
UnreadableDescriptor,
/// Encountered an unexpected read-only virtio descriptor.
UnwritableDescriptor,
}
type Result<T> = std::result::Result<T, VsockError>;
#[derive(Debug)]
pub enum VsockEpollHandlerError {
/// The vsock data/buffer virtio descriptor length is smaller than expected.
BufDescTooSmall,
/// The vsock data/buffer virtio descriptor is expected, but missing.
BufDescMissing,
/// Chained GuestMemory error.
GuestMemory,
/// Bounds check failed on guest memory pointer.
GuestMemoryBounds,
/// The vsock header descriptor length is too small.
HdrDescTooSmall(u32),
/// The vsock header `len` field holds an invalid value.
InvalidPktLen(u32),
/// A data fetch was attempted when no data was available.
NoData,
/// A data buffer was expected for the provided packet, but it is missing.
PktBufMissing,
/// Encountered an unexpected write-only virtio descriptor.
UnreadableDescriptor,
/// Encountered an unexpected read-only virtio descriptor.
UnwritableDescriptor,
}
/// A passive, event-driven object, that needs to be notified whenever an epoll-able event occurs.
/// An event-polling control loop will use `get_polled_fd()` and `get_polled_evset()` to query
/// the listener for the file descriptor and the set of events it's interested in. When such an
/// event occurs, the control loop will route the event to the listener via `notify()`.
///
pub trait VsockEpollListener {
/// Get the file descriptor the listener needs polled.
fn get_polled_fd(&self) -> RawFd;
/// Get the set of events for which the listener wants to be notified.
fn get_polled_evset(&self) -> epoll::Events;
/// Notify the listener that one ore more events have occurred.
fn notify(&mut self, evset: epoll::Events);
}
/// Any channel that handles vsock packet traffic: sending and receiving packets. Since we're
/// implementing the device model here, our responsibility is to always process the sending of
/// packets (i.e. the TX queue). So, any locally generated data, addressed to the driver (e.g.
/// a connection response or RST), will have to be queued, until we get to processing the RX queue.
///
/// Note: `recv_pkt()` and `send_pkt()` are named analogous to `Read::read()` and `Write::write()`,
/// respectively. I.e.
/// - `recv_pkt(&mut pkt)` will read data from the channel, and place it into `pkt`; and
/// - `send_pkt(&pkt)` will fetch data from `pkt`, and place it into the channel.
pub trait VsockChannel {
/// Read/receive an incoming packet from the channel.
fn recv_pkt(&mut self, pkt: &mut VsockPacket) -> Result<()>;
/// Write/send a packet through the channel.
fn send_pkt(&mut self, pkt: &VsockPacket) -> Result<()>;
/// Checks whether there is pending incoming data inside the channel, meaning that a subsequent
/// call to `recv_pkt()` won't fail.
fn has_pending_rx(&self) -> bool;
}
/// The vsock backend, which is basically an epoll-event-driven vsock channel, that needs to be
/// sendable through a mpsc channel (the latter due to how `vmm::EpollContext` works).
/// Currently, the only implementation we have is `crate::virtio::unix::muxer::VsockMuxer`, which
/// translates guest-side vsock connections to host-side Unix domain socket connections.
pub trait VsockBackend: VsockChannel + VsockEpollListener + Send {}
#[cfg(test)]
mod tests {
use libc::EFD_NONBLOCK;
use super::device::{VsockEpollHandler, RX_QUEUE_EVENT, TX_QUEUE_EVENT};
use super::packet::VSOCK_PKT_HDR_SIZE;
use super::*;
use std::os::unix::io::AsRawFd;
use std::sync::{Arc, RwLock};
use vmm_sys_util::eventfd::EventFd;
use crate::device::{VirtioInterrupt, VirtioInterruptType};
use crate::queue::tests::VirtQueue as GuestQ;
use crate::queue::Queue;
use crate::{VIRTQ_DESC_F_NEXT, VIRTQ_DESC_F_WRITE};
use vm_memory::{GuestAddress, GuestMemoryMmap};
pub struct TestBackend {
pub evfd: EventFd,
pub rx_err: Option<VsockError>,
pub tx_err: Option<VsockError>,
pub pending_rx: bool,
pub rx_ok_cnt: usize,
pub tx_ok_cnt: usize,
pub evset: Option<epoll::Events>,
}
impl TestBackend {
pub fn new() -> Self {
Self {
evfd: EventFd::new(EFD_NONBLOCK).unwrap(),
rx_err: None,
tx_err: None,
pending_rx: false,
rx_ok_cnt: 0,
tx_ok_cnt: 0,
evset: None,
}
}
pub fn set_rx_err(&mut self, err: Option<VsockError>) {
self.rx_err = err;
}
pub fn set_tx_err(&mut self, err: Option<VsockError>) {
self.tx_err = err;
}
pub fn set_pending_rx(&mut self, prx: bool) {
self.pending_rx = prx;
}
}
impl VsockChannel for TestBackend {
fn recv_pkt(&mut self, _pkt: &mut VsockPacket) -> Result<()> {
match self.rx_err.take() {
None => {
self.rx_ok_cnt += 1;
Ok(())
}
Some(e) => Err(e),
}
}
fn send_pkt(&mut self, _pkt: &VsockPacket) -> Result<()> {
match self.tx_err.take() {
None => {
self.tx_ok_cnt += 1;
Ok(())
}
Some(e) => Err(e),
}
}
fn has_pending_rx(&self) -> bool {
self.pending_rx
}
}
impl VsockEpollListener for TestBackend {
fn get_polled_fd(&self) -> RawFd {
self.evfd.as_raw_fd()
}
fn get_polled_evset(&self) -> epoll::Events {
epoll::Events::EPOLLIN
}
fn notify(&mut self, evset: epoll::Events) {
self.evset = Some(evset);
}
}
impl VsockBackend for TestBackend {}
pub struct TestContext {
pub cid: u64,
pub mem: GuestMemoryMmap,
pub mem_size: usize,
pub device: Vsock<TestBackend>,
}
impl TestContext {
pub fn new() -> Self {
const CID: u64 = 52;
const MEM_SIZE: usize = 1024 * 1024 * 128;
Self {
cid: CID,
mem: GuestMemoryMmap::new(&[(GuestAddress(0), MEM_SIZE)]).unwrap(),
mem_size: MEM_SIZE,
device: Vsock::new(CID, TestBackend::new(), false).unwrap(),
}
}
pub fn create_epoll_handler_context(&self) -> EpollHandlerContext {
const QSIZE: u16 = 2;
let guest_rxvq = GuestQ::new(GuestAddress(0x0010_0000), &self.mem, QSIZE as u16);
let guest_txvq = GuestQ::new(GuestAddress(0x0020_0000), &self.mem, QSIZE as u16);
let guest_evvq = GuestQ::new(GuestAddress(0x0030_0000), &self.mem, QSIZE as u16);
let rxvq = guest_rxvq.create_queue();
let txvq = guest_txvq.create_queue();
let evvq = guest_evvq.create_queue();
// Set up one available descriptor in the RX queue.
guest_rxvq.dtable[0].set(
0x0040_0000,
VSOCK_PKT_HDR_SIZE as u32,
VIRTQ_DESC_F_WRITE | VIRTQ_DESC_F_NEXT,
1,
);
guest_rxvq.dtable[1].set(0x0040_1000, 4096, VIRTQ_DESC_F_WRITE, 0);
guest_rxvq.avail.ring[0].set(0);
guest_rxvq.avail.idx.set(1);
// Set up one available descriptor in the TX queue.
guest_txvq.dtable[0].set(0x0050_0000, VSOCK_PKT_HDR_SIZE as u32, VIRTQ_DESC_F_NEXT, 1);
guest_txvq.dtable[1].set(0x0050_1000, 4096, 0, 0);
guest_txvq.avail.ring[0].set(0);
guest_txvq.avail.idx.set(1);
let queues = vec![rxvq, txvq, evvq];
let queue_evts = vec![
EventFd::new(EFD_NONBLOCK).unwrap(),
EventFd::new(EFD_NONBLOCK).unwrap(),
EventFd::new(EFD_NONBLOCK).unwrap(),
];
let interrupt_cb = Arc::new(Box::new(
move |_: &VirtioInterruptType, _: Option<&Queue>| Ok(()),
) as VirtioInterrupt);
EpollHandlerContext {
guest_rxvq,
guest_txvq,
guest_evvq,
handler: VsockEpollHandler {
mem: Arc::new(RwLock::new(self.mem.clone())),
queues,
queue_evts,
kill_evt: EventFd::new(EFD_NONBLOCK).unwrap(),
interrupt_cb,
backend: Arc::new(RwLock::new(TestBackend::new())),
},
}
}
}
pub struct EpollHandlerContext<'a> {
pub handler: VsockEpollHandler<TestBackend>,
pub guest_rxvq: GuestQ<'a>,
pub guest_txvq: GuestQ<'a>,
pub guest_evvq: GuestQ<'a>,
}
impl<'a> EpollHandlerContext<'a> {
pub fn signal_txq_event(&mut self) {
self.handler.queue_evts[1].write(1).unwrap();
self.handler
.handle_event(TX_QUEUE_EVENT, epoll::Events::EPOLLIN)
.unwrap();
}
pub fn signal_rxq_event(&mut self) {
self.handler.queue_evts[0].write(1).unwrap();
self.handler
.handle_event(RX_QUEUE_EVENT, epoll::Events::EPOLLIN)
.unwrap();
}
}
}

View File

@@ -0,0 +1,657 @@
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
//
/// `VsockPacket` provides a thin wrapper over the buffers exchanged via virtio queues.
/// There are two components to a vsock packet, each using its own descriptor in a
/// virtio queue:
/// - the packet header; and
/// - the packet data/buffer.
/// There is a 1:1 relation between descriptor chains and packets: the first (chain head) holds
/// the header, and an optional second descriptor holds the data. The second descriptor is only
/// present for data packets (VSOCK_OP_RW).
///
/// `VsockPacket` wraps these two buffers and provides direct access to the data stored
/// in guest memory. This is done to avoid unnecessarily copying data from guest memory
/// to temporary buffers, before passing it on to the vsock backend.
///
use byteorder::{ByteOrder, LittleEndian};
use super::super::DescriptorChain;
use super::defs;
use super::{Result, VsockError};
// The vsock packet header is defined by the C struct:
//
// ```C
// struct virtio_vsock_hdr {
// le64 src_cid;
// le64 dst_cid;
// le32 src_port;
// le32 dst_port;
// le32 len;
// le16 type;
// le16 op;
// le32 flags;
// le32 buf_alloc;
// le32 fwd_cnt;
// };
// ```
//
// This structed will occupy the buffer pointed to by the head descriptor. We'll be accessing it
// as a byte slice. To that end, we define below the offsets for each field struct, as well as the
// packed struct size, as a bunch of `usize` consts.
// Note that these offsets are only used privately by the `VsockPacket` struct, the public interface
// consisting of getter and setter methods, for each struct field, that will also handle the correct
// endianess.
/// The vsock packet header struct size (when packed).
pub const VSOCK_PKT_HDR_SIZE: usize = 44;
// Source CID.
const HDROFF_SRC_CID: usize = 0;
// Destination CID.
const HDROFF_DST_CID: usize = 8;
// Source port.
const HDROFF_SRC_PORT: usize = 16;
// Destination port.
const HDROFF_DST_PORT: usize = 20;
// Data length (in bytes) - may be 0, if there is no data buffer.
const HDROFF_LEN: usize = 24;
// Socket type. Currently, only connection-oriented streams are defined by the vsock protocol.
const HDROFF_TYPE: usize = 28;
// Operation ID - one of the VSOCK_OP_* values; e.g.
// - VSOCK_OP_RW: a data packet;
// - VSOCK_OP_REQUEST: connection request;
// - VSOCK_OP_RST: forcefull connection termination;
// etc (see `super::defs::uapi` for the full list).
const HDROFF_OP: usize = 30;
// Additional options (flags) associated with the current operation (`op`).
// Currently, only used with shutdown requests (VSOCK_OP_SHUTDOWN).
const HDROFF_FLAGS: usize = 32;
// Size (in bytes) of the packet sender receive buffer (for the connection to which this packet
// belongs).
const HDROFF_BUF_ALLOC: usize = 36;
// Number of bytes the sender has received and consumed (for the connection to which this packet
// belongs). For instance, for our Unix backend, this counter would be the total number of bytes
// we have successfully written to a backing Unix socket.
const HDROFF_FWD_CNT: usize = 40;
/// The vsock packet, implemented as a wrapper over a virtq descriptor chain:
/// - the chain head, holding the packet header; and
/// - (an optional) data/buffer descriptor, only present for data packets (VSOCK_OP_RW).
///
pub struct VsockPacket {
hdr: *mut u8,
buf: Option<*mut u8>,
buf_size: usize,
}
impl VsockPacket {
/// Create the packet wrapper from a TX virtq chain head.
///
/// The chain head is expected to hold valid packet header data. A following packet buffer
/// descriptor can optionally end the chain. Bounds and pointer checks are performed when
/// creating the wrapper.
///
pub fn from_tx_virtq_head(head: &DescriptorChain) -> Result<Self> {
// All buffers in the TX queue must be readable.
//
if head.is_write_only() {
return Err(VsockError::UnreadableDescriptor);
}
// The packet header should fit inside the head descriptor.
if head.len < VSOCK_PKT_HDR_SIZE as u32 {
return Err(VsockError::HdrDescTooSmall(head.len));
}
let mut pkt = Self {
hdr: head
.mem
.get_host_address(head.addr)
.ok_or_else(|| VsockError::GuestMemory)? as *mut u8,
buf: None,
buf_size: 0,
};
// No point looking for a data/buffer descriptor, if the packet is zero-lengthed.
if pkt.len() == 0 {
return Ok(pkt);
}
// Reject weirdly-sized packets.
//
if pkt.len() > defs::MAX_PKT_BUF_SIZE as u32 {
return Err(VsockError::InvalidPktLen(pkt.len()));
}
// If the packet header showed a non-zero length, there should be a data descriptor here.
let buf_desc = head.next_descriptor().ok_or(VsockError::BufDescMissing)?;
// TX data should be read-only.
if buf_desc.is_write_only() {
return Err(VsockError::UnreadableDescriptor);
}
// The data buffer should be large enough to fit the size of the data, as described by
// the header descriptor.
if buf_desc.len < pkt.len() {
return Err(VsockError::BufDescTooSmall);
}
pkt.buf_size = buf_desc.len as usize;
pkt.buf = Some(
buf_desc
.mem
.get_host_address(buf_desc.addr)
.ok_or_else(|| VsockError::GuestMemory)? as *mut u8,
);
Ok(pkt)
}
/// Create the packet wrapper from an RX virtq chain head.
///
/// There must be two descriptors in the chain, both writable: a header descriptor and a data
/// descriptor. Bounds and pointer checks are performed when creating the wrapper.
///
pub fn from_rx_virtq_head(head: &DescriptorChain) -> Result<Self> {
// All RX buffers must be writable.
//
if !head.is_write_only() {
return Err(VsockError::UnwritableDescriptor);
}
// The packet header should fit inside the head descriptor.
if head.len < VSOCK_PKT_HDR_SIZE as u32 {
return Err(VsockError::HdrDescTooSmall(head.len));
}
// All RX descriptor chains should have a header and a data descriptor.
if !head.has_next() {
return Err(VsockError::BufDescMissing);
}
let buf_desc = head.next_descriptor().ok_or(VsockError::BufDescMissing)?;
Ok(Self {
hdr: head
.mem
.get_host_address(head.addr)
.ok_or_else(|| VsockError::GuestMemory)? as *mut u8,
buf: Some(
buf_desc
.mem
.get_host_address(buf_desc.addr)
.ok_or_else(|| VsockError::GuestMemory)? as *mut u8,
),
buf_size: buf_desc.len as usize,
})
}
/// Provides in-place, byte-slice, access to the vsock packet header.
///
pub fn hdr(&self) -> &[u8] {
// This is safe since bound checks have already been performed when creating the packet
// from the virtq descriptor.
unsafe { std::slice::from_raw_parts(self.hdr as *const u8, VSOCK_PKT_HDR_SIZE) }
}
/// Provides in-place, byte-slice, mutable access to the vsock packet header.
///
pub fn hdr_mut(&mut self) -> &mut [u8] {
// This is safe since bound checks have already been performed when creating the packet
// from the virtq descriptor.
unsafe { std::slice::from_raw_parts_mut(self.hdr, VSOCK_PKT_HDR_SIZE) }
}
/// Provides in-place, byte-slice access to the vsock packet data buffer.
///
/// Note: control packets (e.g. connection request or reset) have no data buffer associated.
/// For those packets, this method will return `None`.
/// Also note: calling `len()` on the returned slice will yield the buffer size, which may be
/// (and often is) larger than the length of the packet data. The packet data length
/// is stored in the packet header, and accessible via `VsockPacket::len()`.
pub fn buf(&self) -> Option<&[u8]> {
self.buf.map(|ptr| {
// This is safe since bound checks have already been performed when creating the packet
// from the virtq descriptor.
unsafe { std::slice::from_raw_parts(ptr as *const u8, self.buf_size) }
})
}
/// Provides in-place, byte-slice, mutable access to the vsock packet data buffer.
///
/// Note: control packets (e.g. connection request or reset) have no data buffer associated.
/// For those packets, this method will return `None`.
/// Also note: calling `len()` on the returned slice will yield the buffer size, which may be
/// (and often is) larger than the length of the packet data. The packet data length
/// is stored in the packet header, and accessible via `VsockPacket::len()`.
pub fn buf_mut(&mut self) -> Option<&mut [u8]> {
self.buf.map(|ptr| {
// This is safe since bound checks have already been performed when creating the packet
// from the virtq descriptor.
unsafe { std::slice::from_raw_parts_mut(ptr, self.buf_size) }
})
}
pub fn src_cid(&self) -> u64 {
LittleEndian::read_u64(&self.hdr()[HDROFF_SRC_CID..])
}
pub fn set_src_cid(&mut self, cid: u64) -> &mut Self {
LittleEndian::write_u64(&mut self.hdr_mut()[HDROFF_SRC_CID..], cid);
self
}
pub fn dst_cid(&self) -> u64 {
LittleEndian::read_u64(&self.hdr()[HDROFF_DST_CID..])
}
pub fn set_dst_cid(&mut self, cid: u64) -> &mut Self {
LittleEndian::write_u64(&mut self.hdr_mut()[HDROFF_DST_CID..], cid);
self
}
pub fn src_port(&self) -> u32 {
LittleEndian::read_u32(&self.hdr()[HDROFF_SRC_PORT..])
}
pub fn set_src_port(&mut self, port: u32) -> &mut Self {
LittleEndian::write_u32(&mut self.hdr_mut()[HDROFF_SRC_PORT..], port);
self
}
pub fn dst_port(&self) -> u32 {
LittleEndian::read_u32(&self.hdr()[HDROFF_DST_PORT..])
}
pub fn set_dst_port(&mut self, port: u32) -> &mut Self {
LittleEndian::write_u32(&mut self.hdr_mut()[HDROFF_DST_PORT..], port);
self
}
pub fn len(&self) -> u32 {
LittleEndian::read_u32(&self.hdr()[HDROFF_LEN..])
}
pub fn set_len(&mut self, len: u32) -> &mut Self {
LittleEndian::write_u32(&mut self.hdr_mut()[HDROFF_LEN..], len);
self
}
pub fn type_(&self) -> u16 {
LittleEndian::read_u16(&self.hdr()[HDROFF_TYPE..])
}
pub fn set_type(&mut self, type_: u16) -> &mut Self {
LittleEndian::write_u16(&mut self.hdr_mut()[HDROFF_TYPE..], type_);
self
}
pub fn op(&self) -> u16 {
LittleEndian::read_u16(&self.hdr()[HDROFF_OP..])
}
pub fn set_op(&mut self, op: u16) -> &mut Self {
LittleEndian::write_u16(&mut self.hdr_mut()[HDROFF_OP..], op);
self
}
pub fn flags(&self) -> u32 {
LittleEndian::read_u32(&self.hdr()[HDROFF_FLAGS..])
}
pub fn set_flags(&mut self, flags: u32) -> &mut Self {
LittleEndian::write_u32(&mut self.hdr_mut()[HDROFF_FLAGS..], flags);
self
}
pub fn set_flag(&mut self, flag: u32) -> &mut Self {
self.set_flags(self.flags() | flag);
self
}
pub fn buf_alloc(&self) -> u32 {
LittleEndian::read_u32(&self.hdr()[HDROFF_BUF_ALLOC..])
}
pub fn set_buf_alloc(&mut self, buf_alloc: u32) -> &mut Self {
LittleEndian::write_u32(&mut self.hdr_mut()[HDROFF_BUF_ALLOC..], buf_alloc);
self
}
pub fn fwd_cnt(&self) -> u32 {
LittleEndian::read_u32(&self.hdr()[HDROFF_FWD_CNT..])
}
pub fn set_fwd_cnt(&mut self, fwd_cnt: u32) -> &mut Self {
LittleEndian::write_u32(&mut self.hdr_mut()[HDROFF_FWD_CNT..], fwd_cnt);
self
}
}
#[cfg(test)]
mod tests {
use vm_memory::{GuestAddress, GuestMemoryMmap};
use super::super::tests::TestContext;
use super::*;
use crate::queue::tests::VirtqDesc as GuestQDesc;
use crate::vsock::defs::MAX_PKT_BUF_SIZE;
use crate::VIRTQ_DESC_F_WRITE;
macro_rules! create_context {
($test_ctx:ident, $handler_ctx:ident) => {
let $test_ctx = TestContext::new();
let mut $handler_ctx = $test_ctx.create_epoll_handler_context();
// For TX packets, hdr.len should be set to a valid value.
set_pkt_len(1024, &$handler_ctx.guest_txvq.dtable[0], &$test_ctx.mem);
};
}
macro_rules! expect_asm_error {
(tx, $test_ctx:expr, $handler_ctx:expr, $err:pat) => {
expect_asm_error!($test_ctx, $handler_ctx, $err, from_tx_virtq_head, 1);
};
(rx, $test_ctx:expr, $handler_ctx:expr, $err:pat) => {
expect_asm_error!($test_ctx, $handler_ctx, $err, from_rx_virtq_head, 0);
};
($test_ctx:expr, $handler_ctx:expr, $err:pat, $ctor:ident, $vq:expr) => {
match VsockPacket::$ctor(
&$handler_ctx.handler.queues[$vq]
.iter(&$test_ctx.mem)
.next()
.unwrap(),
) {
Err($err) => (),
Ok(_) => panic!("Packet assembly should've failed!"),
Err(other) => panic!("Packet assembly failed with: {:?}", other),
}
};
}
fn set_pkt_len(len: u32, guest_desc: &GuestQDesc, mem: &GuestMemoryMmap) {
let hdr_gpa = guest_desc.addr.get();
let hdr_ptr = mem.get_host_address(GuestAddress(hdr_gpa)).unwrap() as *mut u8;
let len_ptr = unsafe { hdr_ptr.add(HDROFF_LEN) };
LittleEndian::write_u32(unsafe { std::slice::from_raw_parts_mut(len_ptr, 4) }, len);
}
#[test]
#[allow(clippy::cognitive_complexity)]
fn test_tx_packet_assembly() {
// Test case: successful TX packet assembly.
{
create_context!(test_ctx, handler_ctx);
let pkt = VsockPacket::from_tx_virtq_head(
&handler_ctx.handler.queues[1]
.iter(&test_ctx.mem)
.next()
.unwrap(),
)
.unwrap();
assert_eq!(pkt.hdr().len(), VSOCK_PKT_HDR_SIZE);
assert_eq!(
pkt.buf().unwrap().len(),
handler_ctx.guest_txvq.dtable[1].len.get() as usize
);
}
// Test case: error on write-only hdr descriptor.
{
create_context!(test_ctx, handler_ctx);
handler_ctx.guest_txvq.dtable[0]
.flags
.set(VIRTQ_DESC_F_WRITE);
expect_asm_error!(tx, test_ctx, handler_ctx, VsockError::UnreadableDescriptor);
}
// Test case: header descriptor has insufficient space to hold the packet header.
{
create_context!(test_ctx, handler_ctx);
handler_ctx.guest_txvq.dtable[0]
.len
.set(VSOCK_PKT_HDR_SIZE as u32 - 1);
expect_asm_error!(tx, test_ctx, handler_ctx, VsockError::HdrDescTooSmall(_));
}
// Test case: zero-length TX packet.
{
create_context!(test_ctx, handler_ctx);
set_pkt_len(0, &handler_ctx.guest_txvq.dtable[0], &test_ctx.mem);
let mut pkt = VsockPacket::from_tx_virtq_head(
&handler_ctx.handler.queues[1]
.iter(&test_ctx.mem)
.next()
.unwrap(),
)
.unwrap();
assert!(pkt.buf().is_none());
assert!(pkt.buf_mut().is_none());
}
// Test case: TX packet has more data than we can handle.
{
create_context!(test_ctx, handler_ctx);
set_pkt_len(
MAX_PKT_BUF_SIZE as u32 + 1,
&handler_ctx.guest_txvq.dtable[0],
&test_ctx.mem,
);
expect_asm_error!(tx, test_ctx, handler_ctx, VsockError::InvalidPktLen(_));
}
// Test case:
// - packet header advertises some data length; and
// - the data descriptor is missing.
{
create_context!(test_ctx, handler_ctx);
set_pkt_len(1024, &handler_ctx.guest_txvq.dtable[0], &test_ctx.mem);
handler_ctx.guest_txvq.dtable[0].flags.set(0);
expect_asm_error!(tx, test_ctx, handler_ctx, VsockError::BufDescMissing);
}
// Test case: error on write-only buf descriptor.
{
create_context!(test_ctx, handler_ctx);
handler_ctx.guest_txvq.dtable[1]
.flags
.set(VIRTQ_DESC_F_WRITE);
expect_asm_error!(tx, test_ctx, handler_ctx, VsockError::UnreadableDescriptor);
}
// Test case: the buffer descriptor cannot fit all the data advertised by the the
// packet header `len` field.
{
create_context!(test_ctx, handler_ctx);
set_pkt_len(8 * 1024, &handler_ctx.guest_txvq.dtable[0], &test_ctx.mem);
handler_ctx.guest_txvq.dtable[1].len.set(4 * 1024);
expect_asm_error!(tx, test_ctx, handler_ctx, VsockError::BufDescTooSmall);
}
}
#[test]
fn test_rx_packet_assembly() {
// Test case: successful RX packet assembly.
{
create_context!(test_ctx, handler_ctx);
let pkt = VsockPacket::from_rx_virtq_head(
&handler_ctx.handler.queues[0]
.iter(&test_ctx.mem)
.next()
.unwrap(),
)
.unwrap();
assert_eq!(pkt.hdr().len(), VSOCK_PKT_HDR_SIZE);
assert_eq!(
pkt.buf().unwrap().len(),
handler_ctx.guest_rxvq.dtable[1].len.get() as usize
);
}
// Test case: read-only RX packet header.
{
create_context!(test_ctx, handler_ctx);
handler_ctx.guest_rxvq.dtable[0].flags.set(0);
expect_asm_error!(rx, test_ctx, handler_ctx, VsockError::UnwritableDescriptor);
}
// Test case: RX descriptor head cannot fit the entire packet header.
{
create_context!(test_ctx, handler_ctx);
handler_ctx.guest_rxvq.dtable[0]
.len
.set(VSOCK_PKT_HDR_SIZE as u32 - 1);
expect_asm_error!(rx, test_ctx, handler_ctx, VsockError::HdrDescTooSmall(_));
}
// Test case: RX descriptor chain is missing the packet buffer descriptor.
{
create_context!(test_ctx, handler_ctx);
handler_ctx.guest_rxvq.dtable[0]
.flags
.set(VIRTQ_DESC_F_WRITE);
expect_asm_error!(rx, test_ctx, handler_ctx, VsockError::BufDescMissing);
}
}
#[test]
#[allow(clippy::cognitive_complexity)]
fn test_packet_hdr_accessors() {
const SRC_CID: u64 = 1;
const DST_CID: u64 = 2;
const SRC_PORT: u32 = 3;
const DST_PORT: u32 = 4;
const LEN: u32 = 5;
const TYPE: u16 = 6;
const OP: u16 = 7;
const FLAGS: u32 = 8;
const BUF_ALLOC: u32 = 9;
const FWD_CNT: u32 = 10;
create_context!(test_ctx, handler_ctx);
let mut pkt = VsockPacket::from_rx_virtq_head(
&handler_ctx.handler.queues[0]
.iter(&test_ctx.mem)
.next()
.unwrap(),
)
.unwrap();
// Test field accessors.
pkt.set_src_cid(SRC_CID)
.set_dst_cid(DST_CID)
.set_src_port(SRC_PORT)
.set_dst_port(DST_PORT)
.set_len(LEN)
.set_type(TYPE)
.set_op(OP)
.set_flags(FLAGS)
.set_buf_alloc(BUF_ALLOC)
.set_fwd_cnt(FWD_CNT);
assert_eq!(pkt.src_cid(), SRC_CID);
assert_eq!(pkt.dst_cid(), DST_CID);
assert_eq!(pkt.src_port(), SRC_PORT);
assert_eq!(pkt.dst_port(), DST_PORT);
assert_eq!(pkt.len(), LEN);
assert_eq!(pkt.type_(), TYPE);
assert_eq!(pkt.op(), OP);
assert_eq!(pkt.flags(), FLAGS);
assert_eq!(pkt.buf_alloc(), BUF_ALLOC);
assert_eq!(pkt.fwd_cnt(), FWD_CNT);
// Test individual flag setting.
let flags = pkt.flags() | 0b1000;
pkt.set_flag(0b1000);
assert_eq!(pkt.flags(), flags);
// Test packet header as-slice access.
//
assert_eq!(pkt.hdr().len(), VSOCK_PKT_HDR_SIZE);
assert_eq!(
SRC_CID,
LittleEndian::read_u64(&pkt.hdr()[HDROFF_SRC_CID..])
);
assert_eq!(
DST_CID,
LittleEndian::read_u64(&pkt.hdr()[HDROFF_DST_CID..])
);
assert_eq!(
SRC_PORT,
LittleEndian::read_u32(&pkt.hdr()[HDROFF_SRC_PORT..])
);
assert_eq!(
DST_PORT,
LittleEndian::read_u32(&pkt.hdr()[HDROFF_DST_PORT..])
);
assert_eq!(LEN, LittleEndian::read_u32(&pkt.hdr()[HDROFF_LEN..]));
assert_eq!(TYPE, LittleEndian::read_u16(&pkt.hdr()[HDROFF_TYPE..]));
assert_eq!(OP, LittleEndian::read_u16(&pkt.hdr()[HDROFF_OP..]));
assert_eq!(FLAGS, LittleEndian::read_u32(&pkt.hdr()[HDROFF_FLAGS..]));
assert_eq!(
BUF_ALLOC,
LittleEndian::read_u32(&pkt.hdr()[HDROFF_BUF_ALLOC..])
);
assert_eq!(
FWD_CNT,
LittleEndian::read_u32(&pkt.hdr()[HDROFF_FWD_CNT..])
);
assert_eq!(pkt.hdr_mut().len(), VSOCK_PKT_HDR_SIZE);
for b in pkt.hdr_mut() {
*b = 0;
}
assert_eq!(pkt.src_cid(), 0);
assert_eq!(pkt.dst_cid(), 0);
assert_eq!(pkt.src_port(), 0);
assert_eq!(pkt.dst_port(), 0);
assert_eq!(pkt.len(), 0);
assert_eq!(pkt.type_(), 0);
assert_eq!(pkt.op(), 0);
assert_eq!(pkt.flags(), 0);
assert_eq!(pkt.buf_alloc(), 0);
assert_eq!(pkt.fwd_cnt(), 0);
}
#[test]
fn test_packet_buf() {
create_context!(test_ctx, handler_ctx);
let mut pkt = VsockPacket::from_rx_virtq_head(
&handler_ctx.handler.queues[0]
.iter(&test_ctx.mem)
.next()
.unwrap(),
)
.unwrap();
assert_eq!(
pkt.buf().unwrap().len(),
handler_ctx.guest_rxvq.dtable[1].len.get() as usize
);
assert_eq!(
pkt.buf_mut().unwrap().len(),
handler_ctx.guest_rxvq.dtable[1].len.get() as usize
);
for i in 0..pkt.buf().unwrap().len() {
pkt.buf_mut().unwrap()[i] = (i % 0x100) as u8;
assert_eq!(pkt.buf().unwrap()[i], (i % 0x100) as u8);
}
}
}

View File

@@ -0,0 +1,50 @@
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
//
/// This module implements the Unix Domain Sockets backend for vsock - a mediator between
/// guest-side AF_VSOCK sockets and host-side AF_UNIX sockets. The heavy lifting is performed by
/// `muxer::VsockMuxer`, a connection multiplexer that uses `super::csm::VsockConnection` for
/// handling vsock connection states.
/// Check out `muxer.rs` for a more detailed explanation of the inner workings of this backend.
///
mod muxer;
mod muxer_killq;
mod muxer_rxq;
pub use muxer::VsockMuxer as VsockUnixBackend;
pub use Error as VsockUnixError;
mod defs {
/// Maximum number of established connections that we can handle.
pub const MAX_CONNECTIONS: usize = 1023;
/// Size of the muxer RX packet queue.
pub const MUXER_RXQ_SIZE: usize = 256;
/// Size of the muxer connection kill queue.
pub const MUXER_KILLQ_SIZE: usize = 128;
}
#[derive(Debug)]
pub enum Error {
/// Error registering a new epoll-listening FD.
EpollAdd(std::io::Error),
/// Error creating an epoll FD.
EpollFdCreate(std::io::Error),
/// The host made an invalid vsock port connection request.
InvalidPortRequest,
/// Error accepting a new connection from the host-side Unix socket.
UnixAccept(std::io::Error),
/// Error binding to the host-side Unix socket.
UnixBind(std::io::Error),
/// Error connecting to a host-side Unix socket.
UnixConnect(std::io::Error),
/// Error reading from host-side Unix socket.
UnixRead(std::io::Error),
/// Muxer connection limit reached.
TooManyConnections,
}
type Result<T> = std::result::Result<T, Error>;
type MuxerConnection = super::csm::VsockConnection<std::os::unix::net::UnixStream>;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,140 @@
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
//
/// `MuxerKillQ` implements a helper object that `VsockMuxer` can use for scheduling forced
/// connection termination. I.e. after one peer issues a clean shutdown request
/// (VSOCK_OP_SHUTDOWN), the concerned connection is queued for termination (VSOCK_OP_RST) in
/// the near future (herein implemented via an expiring timer).
///
/// Whenever the muxer needs to schedule a connection for termination, it pushes it (or rather
/// an identifier - the connection key) to this queue. A subsequent pop() operation will
/// succeed if and only if the first connection in the queue is ready to be terminated (i.e.
/// its kill timer expired).
///
/// Without using this queue, the muxer would have to walk its entire connection pool
/// (hashmap), whenever it needs to check for expired kill timers. With this queue, both
/// scheduling and termination are performed in constant time. However, since we don't want to
/// waste space on a kill queue that's as big as the connection hashmap itself, it is possible
/// that this queue may become full at times. We call this kill queue "synchronized" if we are
/// certain that all connections that are awaiting termination are present in the queue. This
/// means a simple constant-time pop() operation is enough to check whether any connections
/// need to be terminated. When the kill queue becomes full, though, pushing fails, so
/// connections that should be terminated are left out. The queue is not synchronized anymore.
/// When that happens, the muxer will first drain the queue, and then replace it with a new
/// queue, created by walking the connection pool, looking for connections that will be
/// expiring in the future.
///
use std::collections::{HashMap, VecDeque};
use std::time::Instant;
use super::defs;
use super::muxer::ConnMapKey;
use super::MuxerConnection;
/// A kill queue item, holding the connection key and the scheduled time for termination.
///
#[derive(Clone, Copy)]
struct MuxerKillQItem {
key: ConnMapKey,
kill_time: Instant,
}
/// The connection kill queue: a FIFO structure, storing the connections that are scheduled for
/// termination.
///
pub struct MuxerKillQ {
/// The kill queue contents.
q: VecDeque<MuxerKillQItem>,
/// The kill queue sync status:
/// - when true, all connections that are awaiting termination are guaranteed to be in this
/// queue;
/// - when false, some connections may have been left out.
///
synced: bool,
}
impl MuxerKillQ {
const SIZE: usize = defs::MUXER_KILLQ_SIZE;
/// Trivial kill queue constructor.
///
pub fn new() -> Self {
Self {
q: VecDeque::with_capacity(Self::SIZE),
synced: true,
}
}
/// Create a kill queue by walking the connection pool, looking for connections that are
/// set to expire at some point in the future.
/// Note: if more than `Self::SIZE` connections are found, the queue will be created in an
/// out-of-sync state, and will be discarded after it is emptied.
///
pub fn from_conn_map(conn_map: &HashMap<ConnMapKey, MuxerConnection>) -> Self {
let mut q_buf: Vec<MuxerKillQItem> = Vec::with_capacity(Self::SIZE);
let mut synced = true;
for (key, conn) in conn_map.iter() {
if !conn.will_expire() {
continue;
}
if q_buf.len() >= Self::SIZE {
synced = false;
break;
}
q_buf.push(MuxerKillQItem {
key: *key,
kill_time: conn.expiry().unwrap(),
});
}
q_buf.sort_unstable_by_key(|it| it.kill_time);
Self {
q: q_buf.into(),
synced,
}
}
/// Push a connection key to the queue, scheduling it for termination at
/// `CONN_SHUTDOWN_TIMEOUT_MS` from now (the push time).
///
pub fn push(&mut self, key: ConnMapKey, kill_time: Instant) {
if !self.is_synced() || self.is_full() {
self.synced = false;
return;
}
self.q.push_back(MuxerKillQItem { key, kill_time });
}
/// Attempt to pop an expired connection from the kill queue.
///
/// This will succeed and return a connection key, only if the connection at the front of
/// the queue has expired. Otherwise, `None` is returned.
///
pub fn pop(&mut self) -> Option<ConnMapKey> {
if let Some(item) = self.q.front() {
if Instant::now() > item.kill_time {
return Some(self.q.pop_front().unwrap().key);
}
}
None
}
/// Check if the kill queue is synchronized with the connection pool.
///
pub fn is_synced(&self) -> bool {
self.synced
}
/// Check if the kill queue is empty, obviously.
///
pub fn is_empty(&self) -> bool {
self.q.len() == 0
}
/// Check if the kill queue is full.
///
pub fn is_full(&self) -> bool {
self.q.len() == Self::SIZE
}
}

View File

@@ -0,0 +1,139 @@
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
//
/// `MuxerRxQ` implements a helper object that `VsockMuxer` can use for queuing RX (host -> guest)
/// packets (or rather instructions on how to build said packets).
///
/// Under ideal operation, every connection, that has pending RX data, will be present in the muxer
/// RX queue. However, since the RX queue is smaller than the connection pool, it may, under some
/// conditions, become full, meaning that it can no longer account for all the connections that can
/// yield RX data. When that happens, we say that it is no longer "synchronized" (i.e. with the
/// connection pool). A desynchronized RX queue still holds valid data, and the muxer will
/// continue to pop packets from it. However, when a desynchronized queue is drained, additional
/// data may still be available, so the muxer will have to perform a more costly walk of the entire
/// connection pool to find it. This walk is performed here, as part of building an RX queue from
/// the connection pool. When an out-of-sync is drained, the muxer will discard it, and attempt to
/// rebuild a synced one.
///
use std::collections::{HashMap, VecDeque};
use super::super::VsockChannel;
use super::defs;
use super::muxer::{ConnMapKey, MuxerRx};
use super::MuxerConnection;
/// The muxer RX queue.
///
pub struct MuxerRxQ {
/// The RX queue data.
q: VecDeque<MuxerRx>,
/// The RX queue sync status.
synced: bool,
}
impl MuxerRxQ {
const SIZE: usize = defs::MUXER_RXQ_SIZE;
/// Trivial RX queue constructor.
///
pub fn new() -> Self {
Self {
q: VecDeque::with_capacity(Self::SIZE),
synced: true,
}
}
/// Attempt to build an RX queue, that is synchronized to the connection pool.
/// Note: the resulting queue may still be desynchronized, if there are too many connections
/// that have pending RX data. In that case, the muxer will first drain this queue, and
/// then try again to build a synchronized one.
///
pub fn from_conn_map(conn_map: &HashMap<ConnMapKey, MuxerConnection>) -> Self {
let mut q = VecDeque::new();
let mut synced = true;
for (key, conn) in conn_map.iter() {
if !conn.has_pending_rx() {
continue;
}
if q.len() >= Self::SIZE {
synced = false;
break;
}
q.push_back(MuxerRx::ConnRx(*key));
}
Self { q, synced }
}
/// Push a new RX item to the queue.
///
/// A push will fail when:
/// - trying to push a connection key onto an out-of-sync, or full queue; or
/// - trying to push an RST onto a queue already full of RSTs.
/// RSTs take precedence over connections, because connections can always be queried for
/// pending RX data later. Aside from this queue, there is no other storage for RSTs, so
/// failing to push one means that we have to drop the packet.
///
/// Returns:
/// - `true` if the new item has been successfully queued; or
/// - `false` if there was no room left in the queue.
///
pub fn push(&mut self, rx: MuxerRx) -> bool {
// Pushing to a non-full, synchronized queue will always succeed.
if self.is_synced() && !self.is_full() {
self.q.push_back(rx);
return true;
}
match rx {
MuxerRx::RstPkt { .. } => {
// If we just failed to push an RST packet, we'll look through the queue, trying to
// find a connection key that we could evict. This way, the queue does lose sync,
// but we don't drop any packets.
for qi in self.q.iter_mut().rev() {
if let MuxerRx::ConnRx(_) = qi {
*qi = rx;
self.synced = false;
return true;
}
}
}
MuxerRx::ConnRx(_) => {
self.synced = false;
}
};
false
}
/// Pop an RX item from the front of the queue.
///
pub fn pop(&mut self) -> Option<MuxerRx> {
self.q.pop_front()
}
/// Check if the RX queue is synchronized with the connection pool.
///
pub fn is_synced(&self) -> bool {
self.synced
}
/// Get the total number of items in the queue.
///
pub fn len(&self) -> usize {
self.q.len()
}
/// Check if the queue is empty.
///
pub fn is_empty(&self) -> bool {
self.len() == 0
}
/// Check if the queue is full.
///
pub fn is_full(&self) -> bool {
self.len() == Self::SIZE
}
}

View File

@@ -5,8 +5,11 @@ authors = ["The Cloud Hypervisor Authors"]
edition = "2018"
[features]
default = ["acpi"]
acpi = ["acpi_tables"]
default = []
acpi = ["acpi_tables","devices/acpi", "arch/acpi"]
pci_support = ["pci", "vfio", "vm-virtio/pci_support"]
mmio_support = ["vm-virtio/mmio_support"]
cmos = ["devices/cmos"]
[dependencies]
acpi_tables = { path = "../acpi_tables", optional = true }
@@ -15,16 +18,22 @@ devices = { path = "../devices" }
epoll = "4.1.0"
kvm-bindings = "0.1.1"
kvm-ioctls = { git = "https://github.com/rust-vmm/kvm-ioctls", branch = "master" }
lazy_static = "1.4.0"
libc = "0.2.62"
log = "0.4.8"
micro_http = { git = "https://github.com/firecracker-microvm/firecracker", branch = "master" }
net_util = { path = "../net_util" }
pci = {path = "../pci"}
pci = {path = "../pci", optional = true}
qcow = { path = "../qcow" }
vfio = { path = "../vfio" }
vm-virtio = { path = "../vm-virtio" }
serde = {version = ">=1.0.27", features = ["rc"] }
serde_derive = ">=1.0.27"
serde_json = ">=1.0.9"
vfio = { path = "../vfio", optional = true }
vm-allocator = { path = "../vm-allocator" }
vmm-sys-util = { git = "https://github.com/rust-vmm/vmm-sys-util" }
vm-virtio = { path = "../vm-virtio" }
vmm-sys-util = "0.1.1"
signal-hook = "0.1.10"
threadpool = "1.0"
[dependencies.linux-loader]
git = "https://github.com/rust-vmm/linux-loader"

138
vmm/src/api/http.rs Normal file
View File

@@ -0,0 +1,138 @@
// Copyright © 2019 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
extern crate threadpool;
use crate::api::http_endpoint::{VmActionHandler, VmCreate, VmInfo, VmmShutdown};
use crate::api::{ApiRequest, VmAction};
use crate::{Error, Result};
use micro_http::{HttpConnection, Request, Response, StatusCode, Version};
use std::collections::HashMap;
use std::io::{Read, Write};
use std::os::unix::net::UnixListener;
use std::sync::mpsc::Sender;
use std::thread;
use threadpool::ThreadPool;
use vmm_sys_util::eventfd::EventFd;
const HTTP_ROOT: &str = "/api/v1";
const NUM_THREADS: usize = 4;
/// An HTTP endpoint handler interface
pub trait EndpointHandler: Sync + Send {
/// Handles an HTTP request.
/// After parsing the request, the handler could decide to send an
/// associated API request down to the VMM API server to e.g. create
/// or start a VM. The request will block waiting for an answer from the
/// API server and translate that into an HTTP response.
fn handle_request(
&self,
req: &Request,
api_notifier: EventFd,
api_sender: Sender<ApiRequest>,
) -> Response;
}
/// An HTTP routes structure.
pub struct HttpRoutes {
/// routes is a hash table mapping endpoint URIs to their endpoint handlers.
pub routes: HashMap<String, Box<dyn EndpointHandler + Sync + Send>>,
}
macro_rules! endpoint {
($path:expr) => {
format!("{}{}", HTTP_ROOT, $path)
};
}
lazy_static! {
/// HTTP_ROUTES contain all the cloud-hypervisor HTTP routes.
pub static ref HTTP_ROUTES: HttpRoutes = {
let mut r = HttpRoutes {
routes: HashMap::new(),
};
r.routes.insert(endpoint!("/vm.create"), Box::new(VmCreate {}));
r.routes.insert(endpoint!("/vm.boot"), Box::new(VmActionHandler::new(VmAction::Boot)));
r.routes.insert(endpoint!("/vm.delete"), Box::new(VmActionHandler::new(VmAction::Delete)));
r.routes.insert(endpoint!("/vm.info"), Box::new(VmInfo {}));
r.routes.insert(endpoint!("/vm.pause"), Box::new(VmActionHandler::new(VmAction::Pause)));
r.routes.insert(endpoint!("/vm.resume"), Box::new(VmActionHandler::new(VmAction::Resume)));
r.routes.insert(endpoint!("/vm.shutdown"), Box::new(VmActionHandler::new(VmAction::Shutdown)));
r.routes.insert(endpoint!("/vm.reboot"), Box::new(VmActionHandler::new(VmAction::Reboot)));
r.routes.insert(endpoint!("/vmm.shutdown"), Box::new(VmmShutdown {}));
r
};
}
fn http_serve<T: Read + Write>(
http_connection: &mut HttpConnection<T>,
api_notifier: EventFd,
api_sender: Sender<ApiRequest>,
) {
if http_connection.try_read().is_err() {
http_connection.enqueue_response(Response::new(
Version::Http11,
StatusCode::InternalServerError,
));
return;
}
while let Some(request) = http_connection.pop_parsed_request() {
let sender = api_sender.clone();
let path = request.uri().get_abs_path().to_string();
let mut response = match HTTP_ROUTES.routes.get(&path) {
Some(route) => match api_notifier.try_clone() {
Ok(notifier) => route.handle_request(&request, notifier, sender),
Err(_) => Response::new(Version::Http11, StatusCode::InternalServerError),
},
None => Response::new(Version::Http11, StatusCode::NotFound),
};
response.set_server("Cloud Hypervisor API");
http_connection.enqueue_response(response);
}
}
pub fn start_http_thread(
path: &str,
api_notifier: EventFd,
api_sender: Sender<ApiRequest>,
) -> Result<thread::JoinHandle<Result<()>>> {
let listener = UnixListener::bind(path).map_err(Error::Bind)?;
let pool = ThreadPool::new(NUM_THREADS);
thread::Builder::new()
.name("http-server".to_string())
.spawn(move || {
for stream in listener.incoming() {
match stream {
Ok(s) => {
let sender = api_sender.clone();
let notifier = api_notifier.try_clone().map_err(Error::EventFdClone)?;
pool.execute(move || {
let mut http_connection = HttpConnection::new(s);
http_serve(&mut http_connection, notifier, sender);
// It's ok to panic from a threadpool managed thread,
// it won't make parent threads crash.
http_connection.try_write().unwrap();
});
}
Err(_) => continue,
}
}
pool.join();
Ok(())
})
.map_err(Error::HttpThreadSpawn)
}

View File

@@ -0,0 +1,192 @@
// Copyright © 2019 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
use crate::api::http::EndpointHandler;
use crate::api::{
vm_boot, vm_create, vm_delete, vm_info, vm_pause, vm_reboot, vm_resume, vm_shutdown,
vmm_shutdown, ApiError, ApiRequest, ApiResult, VmAction, VmConfig,
};
use micro_http::{Body, Method, Request, Response, StatusCode, Version};
use serde_json::Error as SerdeError;
use std::sync::mpsc::Sender;
use std::sync::Arc;
use vmm_sys_util::eventfd::EventFd;
/// Errors associated with VMM management
#[derive(Debug)]
pub enum HttpError {
/// API request receive error
SerdeJsonDeserialize(SerdeError),
/// Could not create a VM
VmCreate(ApiError),
/// Could not boot a VM
VmBoot(ApiError),
/// Could not get the VM information
VmInfo(ApiError),
/// Could not pause the VM
VmPause(ApiError),
/// Could not pause the VM
VmResume(ApiError),
/// Could not shut a VM down
VmShutdown(ApiError),
/// Could not reboot a VM
VmReboot(ApiError),
/// Could not act on a VM
VmAction(ApiError),
/// Could not shut the VMM down
VmmShutdown(ApiError),
}
fn error_response(error: HttpError, status: StatusCode) -> Response {
let mut response = Response::new(Version::Http11, status);
response.set_body(Body::new(format!("{:?}", error)));
response
}
// /api/v1/vm.create handler
pub struct VmCreate {}
impl EndpointHandler for VmCreate {
fn handle_request(
&self,
req: &Request,
api_notifier: EventFd,
api_sender: Sender<ApiRequest>,
) -> Response {
match req.method() {
Method::Put => {
match &req.body {
Some(body) => {
// Deserialize into a VmConfig
let vm_config: VmConfig = match serde_json::from_slice(body.raw())
.map_err(HttpError::SerdeJsonDeserialize)
{
Ok(config) => config,
Err(e) => return error_response(e, StatusCode::BadRequest),
};
// Call vm_create()
match vm_create(api_notifier, api_sender, Arc::new(vm_config))
.map_err(HttpError::VmCreate)
{
Ok(_) => Response::new(Version::Http11, StatusCode::OK),
Err(e) => error_response(e, StatusCode::InternalServerError),
}
}
None => Response::new(Version::Http11, StatusCode::BadRequest),
}
}
_ => Response::new(Version::Http11, StatusCode::BadRequest),
}
}
}
// Common handler for boot, shutdown and reboot
pub struct VmActionHandler {
action_fn: VmActionFn,
}
type VmActionFn = Box<dyn Fn(EventFd, Sender<ApiRequest>) -> ApiResult<()> + Send + Sync>;
impl VmActionHandler {
pub fn new(action: VmAction) -> Self {
let action_fn = Box::new(match action {
VmAction::Boot => vm_boot,
VmAction::Delete => vm_delete,
VmAction::Shutdown => vm_shutdown,
VmAction::Reboot => vm_reboot,
VmAction::Pause => vm_pause,
VmAction::Resume => vm_resume,
});
VmActionHandler { action_fn }
}
}
impl EndpointHandler for VmActionHandler {
fn handle_request(
&self,
req: &Request,
api_notifier: EventFd,
api_sender: Sender<ApiRequest>,
) -> Response {
match req.method() {
Method::Put => {
match (self.action_fn)(api_notifier, api_sender).map_err(|e| match e {
ApiError::VmBoot(_) => HttpError::VmBoot(e),
ApiError::VmShutdown(_) => HttpError::VmShutdown(e),
ApiError::VmReboot(_) => HttpError::VmReboot(e),
ApiError::VmPause(_) => HttpError::VmPause(e),
ApiError::VmResume(_) => HttpError::VmResume(e),
_ => HttpError::VmAction(e),
}) {
Ok(_) => Response::new(Version::Http11, StatusCode::OK),
Err(e) => error_response(e, StatusCode::InternalServerError),
}
}
_ => Response::new(Version::Http11, StatusCode::BadRequest),
}
}
}
// /api/v1/vm.info handler
pub struct VmInfo {}
impl EndpointHandler for VmInfo {
fn handle_request(
&self,
req: &Request,
api_notifier: EventFd,
api_sender: Sender<ApiRequest>,
) -> Response {
match req.method() {
Method::Get => match vm_info(api_notifier, api_sender).map_err(HttpError::VmInfo) {
Ok(info) => {
let mut response = Response::new(Version::Http11, StatusCode::OK);
let info_serialized = serde_json::to_string(&info).unwrap();
response.set_body(Body::new(info_serialized));
response
}
Err(e) => error_response(e, StatusCode::InternalServerError),
},
_ => Response::new(Version::Http11, StatusCode::BadRequest),
}
}
}
// /api/v1/vmm.shutdown handler
pub struct VmmShutdown {}
impl EndpointHandler for VmmShutdown {
fn handle_request(
&self,
req: &Request,
api_notifier: EventFd,
api_sender: Sender<ApiRequest>,
) -> Response {
match req.method() {
Method::Put => {
match vmm_shutdown(api_notifier, api_sender).map_err(HttpError::VmmShutdown) {
Ok(_) => Response::new(Version::Http11, StatusCode::OK),
Err(e) => error_response(e, StatusCode::InternalServerError),
}
}
_ => Response::new(Version::Http11, StatusCode::BadRequest),
}
}
}

278
vmm/src/api/mod.rs Normal file
View File

@@ -0,0 +1,278 @@
// Copyright © 2019 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
//! The internal VMM API for Cloud Hypervisor.
//!
//! This API is a synchronous, [mpsc](https://doc.rust-lang.org/std/sync/mpsc/)
//! based IPC for sending commands to the VMM thread, from other
//! Cloud Hypervisor threads. The IPC follows a command-response protocol, i.e.
//! each command will receive a response back.
//!
//! The main Cloud Hypervisor thread creates an API event file descriptor
//! to notify the VMM thread about pending API commands, together with an
//! API mpsc channel. The former is the IPC control plane, the latter is the
//! IPC data plane.
//! In order to use the IPC, a Cloud Hypervisor thread needs to have a clone
//! of both the API event file descriptor and the channel Sender. Then it must
//! go through the following steps:
//!
//! 1. The thread creates an mpsc channel for receiving the command response.
//! 2. The thread sends an ApiRequest to the Sender endpoint. The ApiRequest
//! contains the response channel Sender, for the VMM API server to be able
//! to send the response back.
//! 3. The thread writes to the API event file descriptor to notify the VMM
//! API server about a pending command.
//! 4. The thread reads the response back from the VMM API server, from the
//! response channel Receiver.
//! 5. The thread handles the response and forwards potential errors.
extern crate micro_http;
extern crate vmm_sys_util;
pub use self::http::start_http_thread;
pub mod http;
pub mod http_endpoint;
use crate::config::VmConfig;
use crate::vm::{Error as VmError, VmState};
use std::io;
use std::sync::mpsc::{channel, RecvError, SendError, Sender};
use std::sync::Arc;
use vmm_sys_util::eventfd::EventFd;
/// API errors are sent back from the VMM API server through the ApiResponse.
#[derive(Debug)]
pub enum ApiError {
/// Cannot write to EventFd.
EventFdWrite(io::Error),
/// API request send error
RequestSend(SendError<ApiRequest>),
/// Wrong reponse payload type
ResponsePayloadType,
/// API response receive error
ResponseRecv(RecvError),
/// The VM could not boot.
VmBoot(VmError),
/// The VM is already created.
VmAlreadyCreated,
/// The VM could not be created.
VmCreate(VmError),
/// The VM could not be deleted.
VmDelete(VmError),
/// The VM info is not available.
VmInfo(VmError),
/// The VM config is missing.
VmMissingConfig,
/// The VM could not be paused.
VmPause(VmError),
/// The VM could not resume.
VmResume(VmError),
/// The VM is not booted.
VmNotBooted,
/// The VM is not created.
VmNotCreated,
/// The VM could not shutdown.
VmShutdown(VmError),
/// The VM could not reboot.
VmReboot(VmError),
/// The VMM could not shutdown.
VmmShutdown(VmError),
}
pub type ApiResult<T> = std::result::Result<T, ApiError>;
#[derive(Clone, Deserialize, Serialize)]
pub struct VmInfo {
pub config: Arc<VmConfig>,
pub state: VmState,
}
pub enum ApiResponsePayload {
/// No data is sent on the channel.
Empty,
/// Virtual machine information
VmInfo(VmInfo),
}
/// This is the response sent by the VMM API server through the mpsc channel.
pub type ApiResponse = std::result::Result<ApiResponsePayload, ApiError>;
#[allow(clippy::large_enum_variant)]
pub enum ApiRequest {
/// Create the virtual machine. This request payload is a VM configuration
/// (VmConfig).
/// If the VMM API server could not create the VM, it will send a VmCreate
/// error back.
VmCreate(Arc<VmConfig>, Sender<ApiResponse>),
/// Boot the previously created virtual machine.
/// If the VM was not previously created, the VMM API server will send a
/// VmBoot error back.
VmBoot(Sender<ApiResponse>),
/// Delete the previously created virtual machine.
/// If the VM was not previously created, the VMM API server will send a
/// VmDelete error back.
/// If the VM is booted, we shut it down first.
VmDelete(Sender<ApiResponse>),
/// Request the VM information.
VmInfo(Sender<ApiResponse>),
/// Pause a VM.
VmPause(Sender<ApiResponse>),
/// Resume a VM.
VmResume(Sender<ApiResponse>),
/// Shut the previously booted virtual machine down.
/// If the VM was not previously booted or created, the VMM API server
/// will send a VmShutdown error back.
VmShutdown(Sender<ApiResponse>),
/// Reboot the previously booted virtual machine.
/// If the VM was not previously booted or created, the VMM API server
/// will send a VmReboot error back.
VmReboot(Sender<ApiResponse>),
/// Shut the VMM down.
/// This will shutdown and delete the current VM, if any, and then exit the
/// VMM process.
VmmShutdown(Sender<ApiResponse>),
}
pub fn vm_create(
api_evt: EventFd,
api_sender: Sender<ApiRequest>,
config: Arc<VmConfig>,
) -> ApiResult<()> {
let (response_sender, response_receiver) = channel();
// 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)??;
Ok(())
}
/// Represents a VM related action.
/// This is mostly used to factorize code between VM routines
/// that only differ by the IPC command they send.
pub enum VmAction {
/// Boot a VM
Boot,
/// Delete a VM
Delete,
/// Shut a VM down
Shutdown,
/// Reboot a VM
Reboot,
/// Pause a VM
Pause,
/// Resume a VM
Resume,
}
fn vm_action(api_evt: EventFd, api_sender: Sender<ApiRequest>, action: VmAction) -> ApiResult<()> {
let (response_sender, response_receiver) = channel();
let request = match action {
VmAction::Boot => ApiRequest::VmBoot(response_sender),
VmAction::Delete => ApiRequest::VmDelete(response_sender),
VmAction::Shutdown => ApiRequest::VmShutdown(response_sender),
VmAction::Reboot => ApiRequest::VmReboot(response_sender),
VmAction::Pause => ApiRequest::VmPause(response_sender),
VmAction::Resume => ApiRequest::VmResume(response_sender),
};
// Send the VM request.
api_sender.send(request).map_err(ApiError::RequestSend)?;
api_evt.write(1).map_err(ApiError::EventFdWrite)?;
response_receiver.recv().map_err(ApiError::ResponseRecv)??;
Ok(())
}
pub fn vm_boot(api_evt: EventFd, api_sender: Sender<ApiRequest>) -> ApiResult<()> {
vm_action(api_evt, api_sender, VmAction::Boot)
}
pub fn vm_delete(api_evt: EventFd, api_sender: Sender<ApiRequest>) -> ApiResult<()> {
vm_action(api_evt, api_sender, VmAction::Delete)
}
pub fn vm_shutdown(api_evt: EventFd, api_sender: Sender<ApiRequest>) -> ApiResult<()> {
vm_action(api_evt, api_sender, VmAction::Shutdown)
}
pub fn vm_reboot(api_evt: EventFd, api_sender: Sender<ApiRequest>) -> ApiResult<()> {
vm_action(api_evt, api_sender, VmAction::Reboot)
}
pub fn vm_pause(api_evt: EventFd, api_sender: Sender<ApiRequest>) -> ApiResult<()> {
vm_action(api_evt, api_sender, VmAction::Pause)
}
pub fn vm_resume(api_evt: EventFd, api_sender: Sender<ApiRequest>) -> ApiResult<()> {
vm_action(api_evt, api_sender, VmAction::Resume)
}
pub fn vm_info(api_evt: EventFd, api_sender: Sender<ApiRequest>) -> ApiResult<VmInfo> {
let (response_sender, response_receiver) = channel();
// Send the VM request.
api_sender
.send(ApiRequest::VmInfo(response_sender))
.map_err(ApiError::RequestSend)?;
api_evt.write(1).map_err(ApiError::EventFdWrite)?;
let vm_info = response_receiver.recv().map_err(ApiError::ResponseRecv)??;
match vm_info {
ApiResponsePayload::VmInfo(info) => Ok(info),
_ => Err(ApiError::ResponsePayloadType),
}
}
pub fn vmm_shutdown(api_evt: EventFd, api_sender: Sender<ApiRequest>) -> ApiResult<()> {
let (response_sender, response_receiver) = channel();
// Send the VMM shutdown request.
api_sender
.send(ApiRequest::VmmShutdown(response_sender))
.map_err(ApiError::RequestSend)?;
api_evt.write(1).map_err(ApiError::EventFdWrite)?;
response_receiver.recv().map_err(ApiError::ResponseRecv)??;
Ok(())
}

View File

@@ -0,0 +1,413 @@
openapi: 3.0.1
info:
title: Cloud Hypervisor API
description: Local HTTP based API for managing and inspecting a cloud-hypervisor virtual machine.
license:
name: Apache 2.0
url: http://www.apache.org/licenses/LICENSE-2.0.html
version: 0.3.0
servers:
- url: http://localhost/api/v1
paths:
/vmm.info:
get:
summary: Returns general information about the cloud-hypervisor Virtual Machine Monitor (VMM).
responses:
200:
description: The VMM information
content:
application/json:
schema:
$ref: '#/components/schemas/VmmInfo'
/vmm.shutdown:
put:
summary: Shuts the cloud-hypervisor VMM.
operationId: shutdownVMM
responses:
201:
description: The VMM successfully shutdown.
content: {}
/vm.info:
get:
summary: Returns general information about the cloud-hypervisor Virtual Machine (VM) instance.
responses:
200:
description: The VM information
content:
application/json:
schema:
$ref: '#/components/schemas/VmInfo'
/vm.create:
put:
summary: Create the cloud-hypervisor Virtual Machine (VM) instance. The instance is not booted, only created.
operationId: createVM
requestBody:
description: The VM configuration
content:
application/json:
schema:
$ref: '#/components/schemas/VmConfig'
required: true
responses:
201:
description: The VM instance was successfully created.
content: {}
/vm.delete:
put:
summary: Delete the cloud-hypervisor Virtual Machine (VM) instance.
operationId: deleteVM
responses:
201:
description: The VM instance was successfully deleted.
content: {}
/vm.boot:
put:
summary: Boot the previously created VM instance.
operationId: bootVM
responses:
201:
description: The VM instance successfully booted.
content: {}
404:
description: The VM instance could not boot because it is not created yet
content: {}
/vm.pause:
put:
summary: Pause a previously booted VM instance.
operationId: pauseVM
responses:
201:
description: The VM instance successfully paused.
content: {}
404:
description: The VM instance could not pause because it is not created yet
content: {}
405:
description: The VM instance could not pause because it is not booted.
content: {}
/vm.resume:
put:
summary: Resume a previously paused VM instance.
operationId: resumeVM
responses:
201:
description: The VM instance successfully paused.
content: {}
404:
description: The VM instance could not resume because it is not booted yet
content: {}
405:
description: The VM instance could not resume because it is not paused.
content: {}
/vm.shutdown:
put:
summary: Shut the VM instance down.
operationId: shutdownVM
responses:
201:
description: The VM instance successfully shut down.
content: {}
404:
description: The VM instance could not shut down because is not created.
content: {}
405:
description: The VM instance could not shut down because it is not started.
content: {}
/vm.reboot:
put:
summary: Reboot the VM instance.
operationId: rebootVM
responses:
201:
description: The VM instance successfully rebooted.
content: {}
404:
description: The VM instance could not reboot because it is not created.
content: {}
405:
description: The VM instance could not reboot because it is not booted.
content: {}
components:
schemas:
VmmInfo:
required:
- version
type: object
properties:
version:
type: string
description: Virtual Machine Monitor information
VmInfo:
required:
- config
- state
type: object
properties:
version:
type: string
state:
type: string
enum: [Created, Booted, Shutdown]
description: Virtual Machine information
VmConfig:
required:
- kernel
- cmdline
type: object
properties:
cpus:
$ref: '#/components/schemas/CpuConfig'
memory:
$ref: '#/components/schemas/MemoryConfig'
kernel:
$ref: '#/components/schemas/KernelConfig'
cmdline:
$ref: '#/components/schemas/CmdLineConfig'
disks:
type: array
items:
$ref: '#/components/schemas/DiskConfig'
net:
type: array
items:
$ref: '#/components/schemas/NetConfig'
rng:
$ref: '#/components/schemas/RngConfig'
fs:
type: array
items:
$ref: '#/components/schemas/FsConfig'
pmem:
type: array
items:
$ref: '#/components/schemas/PmemConfig'
serial:
$ref: '#/components/schemas/ConsoleConfig'
console:
$ref: '#/components/schemas/ConsoleConfig'
devices:
type: array
items:
$ref: '#/components/schemas/DeviceConfig'
vhost_user_net:
type: array
items:
$ref: '#/components/schemas/VhostUserNetConfig'
vhost_user_blk:
type: array
items:
$ref: '#/components/schemas/VhostUserBlkConfig'
vsock:
type: array
items:
$ref: '#/components/schemas/VsockConfig'
iommu:
type: boolean
default: false
description: Virtual machine configuration
CpuConfig:
required:
- cpu_count
type: object
properties:
cpu_count:
minimum: 1
default: 1
type: integer
MemoryConfig:
required:
- size
type: object
properties:
size:
type: integer
default: 512 MB
file:
type: string
KernelConfig:
required:
- path
type: object
properties:
path:
type: string
CmdLineConfig:
required:
- args
type: object
properties:
args:
type: string
DiskConfig:
required:
- path
type: object
properties:
path:
type: string
iommu:
type: boolean
default: false
NetConfig:
required:
- ip
- mask
- mac
type: object
properties:
tap:
type: string
ip:
type: string
mask:
type: string
mac:
type: string
iommu:
type: boolean
default: false
RngConfig:
required:
- src
type: object
properties:
src:
type: string
default: "/dev/urandom"
iommu:
type: boolean
default: false
FsConfig:
required:
- tag
- sock
- num_queues
- queue_size
type: object
properties:
tag:
type: string
sock:
type: string
num_queues:
type: integer
queue_size:
type: integer
cache_size:
type: integer
PmemConfig:
required:
- file
- size
type: object
properties:
file:
type: string
size:
type: integer
iommu:
type: boolean
default: false
ConsoleConfig:
required:
- mode
type: object
properties:
file:
type: string
mode:
type: string
enum: [Off, Tty, File, None]
iommu:
type: boolean
default: false
DeviceConfig:
required:
- path
type: object
properties:
path:
type: string
iommu:
type: boolean
default: false
VhostUserConfig:
required:
- sock
- num_queues
- queue_size
type: object
properties:
sock:
type: string
num_queues:
type: integer
queue:size:
type: integer
VhostUserNetConfig:
required:
- mac
- vu_cfg
type: object
properties:
mac:
type: string
vu_cfg:
$ref: '#/components/schemas/VhostUserConfig'
VhostUserBlkConfig:
required:
- wce
- vu_cfg
type: object
properties:
wce:
type: boolean
vu_cfg:
$ref: '#/components/schemas/VhostUserConfig'
VsockConfig:
required:
- cid
- sock
type: object
properties:
cid:
type: integer
minimum: 3
description: Guest Vsock CID
sock:
type: string
description: Path to UNIX domain socket, used to proxy vsock connections.
iommu:
type: boolean
default: false

View File

@@ -5,20 +5,16 @@
extern crate vm_virtio;
use linux_loader::cmdline::Cmdline;
use net_util::MacAddr;
use std::convert::From;
use std::net::AddrParseError;
use std::net::Ipv4Addr;
use std::path::Path;
use std::path::PathBuf;
use std::result;
use vm_memory::GuestAddress;
use vm_virtio::vhost_user::VhostUserConfig;
pub const DEFAULT_VCPUS: &str = "1";
pub const DEFAULT_MEMORY: &str = "size=512M";
pub const DEFAULT_VCPUS: u8 = 1;
pub const DEFAULT_MEMORY_MB: u64 = 512;
pub const DEFAULT_RNG_SOURCE: &str = "/dev/urandom";
const CMDLINE_OFFSET: GuestAddress = GuestAddress(0x20000);
/// Errors associated with VM configuration parameters.
#[derive(Debug)]
@@ -63,21 +59,31 @@ pub enum Error<'a> {
ParseTTYParam,
/// Failed parsing vhost-user-net mac parameter.
ParseVuNetMacParam(&'a str),
/// Failed parsing vhost-user-net sock parameter.
ParseVuNetSockParam,
/// Failed parsing vhost-user-net queue number parameter.
ParseVuNetNumQueuesParam(std::num::ParseIntError),
/// Failed parsing vhost-user-net queue size parameter.
ParseVuNetQueueSizeParam(std::num::ParseIntError),
/// Failed parsing vhost-user sock parameter.
ParseVuSockParam,
/// Failed parsing vhost-user queue number parameter.
ParseVuNumQueuesParam(std::num::ParseIntError),
/// Failed parsing vhost-user queue size parameter.
ParseVuQueueSizeParam(std::num::ParseIntError),
/// Failed parsing vhost-user-net server parameter.
ParseVuNetServerParam(std::num::ParseIntError),
/// Failed parsing vhost-user-blk wce parameter.
ParseVuBlkWceParam(std::str::ParseBoolError),
/// Failed parsing vsock context ID parameter.
ParseVsockCidParam(std::num::ParseIntError),
/// Failed parsing vsock socket path parameter.
ParseVsockSockParam,
/// Missing kernel configuration
ValidateMissingKernelConfig,
/// Failed parsing iommu parameter for the device.
ParseDeviceIommu,
}
pub type Result<'a, T> = result::Result<T, Error<'a>>;
pub struct VmParams<'a> {
pub cpus: &'a str,
pub memory: &'a str,
pub kernel: &'a str,
pub kernel: Option<&'a str>,
pub cmdline: Option<&'a str>,
pub disks: Option<Vec<&'a str>>,
pub net: Option<Vec<&'a str>>,
@@ -88,6 +94,8 @@ pub struct VmParams<'a> {
pub console: &'a str,
pub devices: Option<Vec<&'a str>>,
pub vhost_user_net: Option<Vec<&'a str>>,
pub vhost_user_blk: Option<Vec<&'a str>>,
pub vsock: Option<Vec<&'a str>>,
}
fn parse_size(size: &str) -> Result<u64> {
@@ -108,29 +116,49 @@ fn parse_size(size: &str) -> Result<u64> {
Ok(res << shift)
}
pub struct CpusConfig(pub u8);
fn parse_iommu(iommu: &str) -> Result<bool> {
if !iommu.is_empty() {
let res = match iommu {
"on" => true,
"off" => false,
_ => return Err(Error::ParseDeviceIommu),
};
Ok(res)
} else {
Ok(false)
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct CpusConfig {
pub cpu_count: u8,
}
impl CpusConfig {
pub fn parse(cpus: &str) -> Result<Self> {
Ok(CpusConfig(
cpus.parse::<u8>().map_err(Error::ParseCpusParams)?,
))
Ok(CpusConfig {
cpu_count: cpus.parse().map_err(Error::ParseCpusParams)?,
})
}
}
impl From<&CpusConfig> for u8 {
fn from(val: &CpusConfig) -> Self {
val.0
impl Default for CpusConfig {
fn default() -> Self {
CpusConfig {
cpu_count: DEFAULT_VCPUS,
}
}
}
pub struct MemoryConfig<'a> {
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct MemoryConfig {
pub size: u64,
pub file: Option<&'a Path>,
pub file: Option<PathBuf>,
}
impl<'a> MemoryConfig<'a> {
pub fn parse(memory: &'a str) -> Result<Self> {
impl MemoryConfig {
pub fn parse(memory: &str) -> Result<Self> {
// Split the parameters based on the comma delimiter
let params_list: Vec<&str> = memory.split(',').collect();
@@ -152,7 +180,7 @@ impl<'a> MemoryConfig<'a> {
return Err(Error::ParseMemoryFileParam);
}
Some(Path::new(file_str))
Some(PathBuf::from(file_str))
} else {
None
};
@@ -164,60 +192,77 @@ impl<'a> MemoryConfig<'a> {
}
}
pub struct KernelConfig<'a> {
pub path: &'a Path,
}
impl<'a> KernelConfig<'a> {
pub fn parse(kernel: &'a str) -> Result<Self> {
Ok(KernelConfig {
path: Path::new(kernel),
})
impl Default for MemoryConfig {
fn default() -> Self {
MemoryConfig {
size: DEFAULT_MEMORY_MB << 20,
file: None,
}
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct KernelConfig {
pub path: PathBuf,
}
#[derive(Clone, Deserialize, Serialize)]
pub struct CmdlineConfig {
pub args: Cmdline,
pub offset: GuestAddress,
pub args: String,
}
impl CmdlineConfig {
pub fn parse(cmdline: Option<&str>) -> Result<Self> {
let cmdline_str = cmdline
let args = cmdline
.map(std::string::ToString::to_string)
.unwrap_or_else(String::new);
let mut args = Cmdline::new(arch::CMDLINE_MAX_SIZE);
args.insert_str(cmdline_str).unwrap();
Ok(CmdlineConfig {
args,
offset: CMDLINE_OFFSET,
})
Ok(CmdlineConfig { args })
}
}
#[derive(Debug)]
pub struct DiskConfig<'a> {
pub path: &'a Path,
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct DiskConfig {
pub path: PathBuf,
#[serde(default)]
pub iommu: bool,
}
impl<'a> DiskConfig<'a> {
pub fn parse(disk: &'a str) -> Result<Self> {
impl DiskConfig {
pub fn parse(disk: &str) -> Result<Self> {
// Split the parameters based on the comma delimiter
let params_list: Vec<&str> = disk.split(',').collect();
let mut path_str: &str = "";
let mut iommu_str: &str = "";
for param in params_list.iter() {
if param.starts_with("path=") {
path_str = &param[5..];
} else if param.starts_with("iommu=") {
iommu_str = &param[6..];
}
}
Ok(DiskConfig {
path: Path::new(disk),
path: PathBuf::from(path_str),
iommu: parse_iommu(iommu_str)?,
})
}
}
pub struct NetConfig<'a> {
pub tap: Option<&'a str>,
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct NetConfig {
pub tap: Option<String>,
pub ip: Ipv4Addr,
pub mask: Ipv4Addr,
pub mac: MacAddr,
#[serde(default)]
pub iommu: bool,
}
impl<'a> NetConfig<'a> {
pub fn parse(net: &'a str) -> Result<Self> {
impl NetConfig {
pub fn parse(net: &str) -> Result<Self> {
// Split the parameters based on the comma delimiter
let params_list: Vec<&str> = net.split(',').collect();
@@ -225,6 +270,7 @@ impl<'a> NetConfig<'a> {
let mut ip_str: &str = "";
let mut mask_str: &str = "";
let mut mac_str: &str = "";
let mut iommu_str: &str = "";
for param in params_list.iter() {
if param.starts_with("tap=") {
@@ -235,16 +281,19 @@ impl<'a> NetConfig<'a> {
mask_str = &param[5..];
} else if param.starts_with("mac=") {
mac_str = &param[4..];
} else if param.starts_with("iommu=") {
iommu_str = &param[6..];
}
}
let mut tap: Option<&str> = None;
let mut tap: Option<String> = None;
let mut ip: Ipv4Addr = Ipv4Addr::new(192, 168, 249, 1);
let mut mask: Ipv4Addr = Ipv4Addr::new(255, 255, 255, 0);;
let mut mac: MacAddr = MacAddr::local_random();
let iommu = parse_iommu(iommu_str)?;
if !tap_str.is_empty() {
tap = Some(tap_str);
tap = Some(tap_str.to_string());
}
if !ip_str.is_empty() {
ip = ip_str.parse().map_err(Error::ParseNetIpParam)?;
@@ -256,33 +305,66 @@ impl<'a> NetConfig<'a> {
mac = MacAddr::parse_str(mac_str).map_err(Error::ParseNetMacParam)?;
}
Ok(NetConfig { tap, ip, mask, mac })
}
}
pub struct RngConfig<'a> {
pub src: &'a Path,
}
impl<'a> RngConfig<'a> {
pub fn parse(rng: &'a str) -> Result<Self> {
Ok(RngConfig {
src: Path::new(rng),
Ok(NetConfig {
tap,
ip,
mask,
mac,
iommu,
})
}
}
#[derive(Debug)]
pub struct FsConfig<'a> {
pub tag: &'a str,
pub sock: &'a Path,
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct RngConfig {
pub src: PathBuf,
#[serde(default)]
pub iommu: bool,
}
impl RngConfig {
pub fn parse(rng: &str) -> Result<Self> {
// Split the parameters based on the comma delimiter
let params_list: Vec<&str> = rng.split(',').collect();
let mut src_str: &str = "";
let mut iommu_str: &str = "";
for param in params_list.iter() {
if param.starts_with("src=") {
src_str = &param[4..];
} else if param.starts_with("iommu=") {
iommu_str = &param[6..];
}
}
Ok(RngConfig {
src: PathBuf::from(src_str),
iommu: parse_iommu(iommu_str)?,
})
}
}
impl Default for RngConfig {
fn default() -> Self {
RngConfig {
src: PathBuf::from(DEFAULT_RNG_SOURCE),
iommu: false,
}
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct FsConfig {
pub tag: String,
pub sock: PathBuf,
pub num_queues: usize,
pub queue_size: u16,
pub cache_size: Option<u64>,
}
impl<'a> FsConfig<'a> {
pub fn parse(fs: &'a str) -> Result<Self> {
impl FsConfig {
pub fn parse(fs: &str) -> Result<Self> {
// Split the parameters based on the comma delimiter
let params_list: Vec<&str> = fs.split(',').collect();
@@ -351,8 +433,8 @@ impl<'a> FsConfig<'a> {
}
Ok(FsConfig {
tag,
sock: Path::new(sock),
tag: tag.to_string(),
sock: PathBuf::from(sock),
num_queues,
queue_size,
cache_size,
@@ -360,24 +442,30 @@ impl<'a> FsConfig<'a> {
}
}
pub struct PmemConfig<'a> {
pub file: &'a Path,
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct PmemConfig {
pub file: PathBuf,
pub size: u64,
#[serde(default)]
pub iommu: bool,
}
impl<'a> PmemConfig<'a> {
pub fn parse(pmem: &'a str) -> Result<Self> {
impl PmemConfig {
pub fn parse(pmem: &str) -> Result<Self> {
// Split the parameters based on the comma delimiter
let params_list: Vec<&str> = pmem.split(',').collect();
let mut file_str: &str = "";
let mut size_str: &str = "";
let mut iommu_str: &str = "";
for param in params_list.iter() {
if param.starts_with("file=") {
file_str = &param[5..];
} else if param.starts_with("size=") {
size_str = &param[5..];
} else if param.starts_with("iommu=") {
iommu_str = &param[6..];
}
}
@@ -386,13 +474,14 @@ impl<'a> PmemConfig<'a> {
}
Ok(PmemConfig {
file: Path::new(file_str),
file: PathBuf::from(file_str),
size: parse_size(size_str)?,
iommu: parse_iommu(iommu_str)?,
})
}
}
#[derive(PartialEq)]
#[derive(Clone, PartialEq, Deserialize, Serialize)]
pub enum ConsoleOutputMode {
Off,
Tty,
@@ -409,59 +498,120 @@ impl ConsoleOutputMode {
}
}
pub struct ConsoleConfig<'a> {
pub file: Option<&'a Path>,
#[derive(Clone, Deserialize, Serialize)]
pub struct ConsoleConfig {
pub file: Option<PathBuf>,
pub mode: ConsoleOutputMode,
#[serde(default)]
pub iommu: bool,
}
impl<'a> ConsoleConfig<'a> {
pub fn parse(param: &'a str) -> Result<Self> {
if param == "off" {
Ok(Self {
mode: ConsoleOutputMode::Off,
file: None,
})
} else if param == "tty" {
Ok(Self {
mode: ConsoleOutputMode::Tty,
file: None,
})
} else if param.starts_with("file=") {
Ok(Self {
mode: ConsoleOutputMode::File,
file: Some(Path::new(&param[5..])),
})
} else if param.starts_with("null") {
Ok(Self {
mode: ConsoleOutputMode::Null,
file: None,
})
} else {
Err(Error::ParseConsoleParam)
impl ConsoleConfig {
pub fn parse(console: &str) -> Result<Self> {
// Split the parameters based on the comma delimiter
let params_list: Vec<&str> = console.split(',').collect();
let mut valid = false;
let mut file: Option<PathBuf> = None;
let mut mode: ConsoleOutputMode = ConsoleOutputMode::Off;
let mut iommu_str: &str = "";
for param in params_list.iter() {
if param.starts_with("iommu=") {
iommu_str = &param[6..];
} else {
if *param == "off" {
mode = ConsoleOutputMode::Off;
file = None;
} else if *param == "tty" {
mode = ConsoleOutputMode::Tty;
file = None;
} else if param.starts_with("file=") {
mode = ConsoleOutputMode::File;
file = Some(PathBuf::from(&param[5..]));
} else if param.starts_with("null") {
mode = ConsoleOutputMode::Null;
file = None;
} else {
return Err(Error::ParseConsoleParam);
}
valid = true;
}
}
if !valid {
return Err(Error::ParseConsoleParam);
}
Ok(Self {
mode,
file,
iommu: parse_iommu(iommu_str)?,
})
}
pub fn default_serial() -> Self {
ConsoleConfig {
file: None,
mode: ConsoleOutputMode::Null,
iommu: false,
}
}
pub fn default_console() -> Self {
ConsoleConfig {
file: None,
mode: ConsoleOutputMode::Tty,
iommu: false,
}
}
}
#[derive(Debug)]
pub struct DeviceConfig<'a> {
pub path: &'a Path,
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct DeviceConfig {
pub path: PathBuf,
#[serde(default)]
pub iommu: bool,
}
impl<'a> DeviceConfig<'a> {
pub fn parse(device: &'a str) -> Result<Self> {
impl DeviceConfig {
pub fn parse(device: &str) -> Result<Self> {
// Split the parameters based on the comma delimiter
let params_list: Vec<&str> = device.split(',').collect();
let mut path_str: &str = "";
let mut iommu_str: &str = "";
for param in params_list.iter() {
if param.starts_with("path=") {
path_str = &param[5..];
} else if param.starts_with("iommu=") {
iommu_str = &param[6..];
}
}
Ok(DeviceConfig {
path: Path::new(device),
path: PathBuf::from(path_str),
iommu: parse_iommu(iommu_str)?,
})
}
}
pub struct VhostUserNetConfig<'a> {
pub mac: MacAddr,
pub vu_cfg: VhostUserConfig<'a>,
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct VuConfig {
pub sock: String,
pub num_queues: usize,
pub queue_size: u16,
}
impl<'a> VhostUserNetConfig<'a> {
pub fn parse(vhost_user_net: &'a str) -> Result<Self> {
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct VhostUserNetConfig {
pub mac: MacAddr,
pub vu_cfg: VuConfig,
}
impl VhostUserNetConfig {
pub fn parse(vhost_user_net: &str) -> Result<Self> {
// Split the parameters based on the comma delimiter
let params_list: Vec<&str> = vhost_user_net.split(',').collect();
@@ -490,21 +640,21 @@ impl<'a> VhostUserNetConfig<'a> {
mac = MacAddr::parse_str(mac_str).map_err(Error::ParseVuNetMacParam)?;
}
if sock.is_empty() {
return Err(Error::ParseVuNetSockParam);
return Err(Error::ParseVuSockParam);
}
if !num_queues_str.is_empty() {
num_queues = num_queues_str
.parse()
.map_err(Error::ParseVuNetNumQueuesParam)?;
.map_err(Error::ParseVuNumQueuesParam)?;
}
if !queue_size_str.is_empty() {
queue_size = queue_size_str
.parse()
.map_err(Error::ParseVuNetQueueSizeParam)?;
.map_err(Error::ParseVuQueueSizeParam)?;
}
let vu_cfg = VhostUserConfig {
sock,
let vu_cfg = VuConfig {
sock: sock.to_string(),
num_queues,
queue_size,
};
@@ -513,29 +663,144 @@ impl<'a> VhostUserNetConfig<'a> {
}
}
pub struct VmConfig<'a> {
pub cpus: CpusConfig,
pub memory: MemoryConfig<'a>,
pub kernel: KernelConfig<'a>,
pub cmdline: CmdlineConfig,
pub disks: Option<Vec<DiskConfig<'a>>>,
pub net: Option<Vec<NetConfig<'a>>>,
pub rng: RngConfig<'a>,
pub fs: Option<Vec<FsConfig<'a>>>,
pub pmem: Option<Vec<PmemConfig<'a>>>,
pub serial: ConsoleConfig<'a>,
pub console: ConsoleConfig<'a>,
pub devices: Option<Vec<DeviceConfig<'a>>>,
pub vhost_user_net: Option<Vec<VhostUserNetConfig<'a>>>,
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct VsockConfig {
pub cid: u64,
pub sock: PathBuf,
#[serde(default)]
pub iommu: bool,
}
impl<'a> VmConfig<'a> {
pub fn parse(vm_params: VmParams<'a>) -> Result<Self> {
impl VsockConfig {
pub fn parse(vsock: &str) -> Result<Self> {
// Split the parameters based on the comma delimiter
let params_list: Vec<&str> = vsock.split(',').collect();
let mut cid_str: &str = "";
let mut sock_str: &str = "";
let mut iommu_str: &str = "";
for param in params_list.iter() {
if param.starts_with("cid=") {
cid_str = &param[4..];
} else if param.starts_with("sock=") {
sock_str = &param[5..];
} else if param.starts_with("iommu=") {
iommu_str = &param[6..];
}
}
if sock_str.is_empty() {
return Err(Error::ParseVsockSockParam);
}
Ok(VsockConfig {
cid: cid_str.parse::<u64>().map_err(Error::ParseVsockCidParam)?,
sock: PathBuf::from(sock_str),
iommu: parse_iommu(iommu_str)?,
})
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct VhostUserBlkConfig {
pub wce: bool,
pub vu_cfg: VuConfig,
}
impl VhostUserBlkConfig {
pub fn parse(vhost_user_blk: &str) -> Result<Self> {
// Split the parameters based on the comma delimiter
let params_list: Vec<&str> = vhost_user_blk.split(',').collect();
let mut sock: &str = "";
let mut num_queues_str: &str = "";
let mut queue_size_str: &str = "";
let mut wce_str: &str = "";
for param in params_list.iter() {
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..];
} else if param.starts_with("wce=") {
wce_str = &param[4..];
}
}
let mut num_queues: usize = 1;
let mut queue_size: u16 = 128;
let mut wce: bool = true;
if !num_queues_str.is_empty() {
num_queues = num_queues_str
.parse()
.map_err(Error::ParseVuNumQueuesParam)?;
}
if !queue_size_str.is_empty() {
queue_size = queue_size_str
.parse()
.map_err(Error::ParseVuQueueSizeParam)?;
}
if !wce_str.is_empty() {
wce = wce_str.parse().map_err(Error::ParseVuBlkWceParam)?;
}
let vu_cfg = VuConfig {
sock: sock.to_string(),
num_queues,
queue_size,
};
Ok(VhostUserBlkConfig { wce, vu_cfg })
}
}
#[derive(Clone, Deserialize, Serialize)]
pub struct VmConfig {
#[serde(default)]
pub cpus: CpusConfig,
#[serde(default)]
pub memory: MemoryConfig,
pub kernel: Option<KernelConfig>,
pub cmdline: CmdlineConfig,
pub disks: Option<Vec<DiskConfig>>,
pub net: Option<Vec<NetConfig>>,
#[serde(default)]
pub rng: RngConfig,
pub fs: Option<Vec<FsConfig>>,
pub pmem: Option<Vec<PmemConfig>>,
#[serde(default = "ConsoleConfig::default_serial")]
pub serial: ConsoleConfig,
#[serde(default = "ConsoleConfig::default_console")]
pub console: ConsoleConfig,
pub devices: Option<Vec<DeviceConfig>>,
pub vhost_user_net: Option<Vec<VhostUserNetConfig>>,
pub vhost_user_blk: Option<Vec<VhostUserBlkConfig>>,
pub vsock: Option<Vec<VsockConfig>>,
#[serde(default)]
pub iommu: bool,
}
impl VmConfig {
pub fn valid(&self) -> bool {
self.kernel.is_some()
}
pub fn parse(vm_params: VmParams) -> Result<Self> {
let mut iommu = false;
let mut disks: Option<Vec<DiskConfig>> = None;
if let Some(disk_list) = &vm_params.disks {
let mut disk_config_list = Vec::new();
for item in disk_list.iter() {
disk_config_list.push(DiskConfig::parse(item)?);
let disk_config = DiskConfig::parse(item)?;
if disk_config.iommu {
iommu = true;
}
disk_config_list.push(disk_config);
}
disks = Some(disk_config_list);
}
@@ -544,11 +809,20 @@ impl<'a> VmConfig<'a> {
if let Some(net_list) = &vm_params.net {
let mut net_config_list = Vec::new();
for item in net_list.iter() {
net_config_list.push(NetConfig::parse(item)?);
let net_config = NetConfig::parse(item)?;
if net_config.iommu {
iommu = true;
}
net_config_list.push(net_config);
}
net = Some(net_config_list);
}
let rng = RngConfig::parse(vm_params.rng)?;
if rng.iommu {
iommu = true;
}
let mut fs: Option<Vec<FsConfig>> = None;
if let Some(fs_list) = &vm_params.fs {
let mut fs_config_list = Vec::new();
@@ -562,12 +836,19 @@ impl<'a> VmConfig<'a> {
if let Some(pmem_list) = &vm_params.pmem {
let mut pmem_config_list = Vec::new();
for item in pmem_list.iter() {
pmem_config_list.push(PmemConfig::parse(item)?);
let pmem_config = PmemConfig::parse(item)?;
if pmem_config.iommu {
iommu = true;
}
pmem_config_list.push(pmem_config);
}
pmem = Some(pmem_config_list);
}
let console = ConsoleConfig::parse(vm_params.console)?;
if console.iommu {
iommu = true;
}
let serial = ConsoleConfig::parse(vm_params.serial)?;
if console.mode == ConsoleOutputMode::Tty && serial.mode == ConsoleOutputMode::Tty {
return Err(Error::ParseTTYParam);
@@ -577,7 +858,11 @@ impl<'a> VmConfig<'a> {
if let Some(device_list) = &vm_params.devices {
let mut device_config_list = Vec::new();
for item in device_list.iter() {
device_config_list.push(DeviceConfig::parse(item)?);
let device_config = DeviceConfig::parse(item)?;
if device_config.iommu {
iommu = true;
}
device_config_list.push(device_config);
}
devices = Some(device_config_list);
}
@@ -591,20 +876,52 @@ impl<'a> VmConfig<'a> {
vhost_user_net = Some(vhost_user_net_config_list);
}
let mut vsock: Option<Vec<VsockConfig>> = None;
if let Some(vsock_list) = &vm_params.vsock {
let mut vsock_config_list = Vec::new();
for item in vsock_list.iter() {
let vsock_config = VsockConfig::parse(item)?;
if vsock_config.iommu {
iommu = true;
}
vsock_config_list.push(vsock_config);
}
vsock = Some(vsock_config_list);
}
let mut vhost_user_blk: Option<Vec<VhostUserBlkConfig>> = None;
if let Some(vhost_user_blk_list) = &vm_params.vhost_user_blk {
let mut vhost_user_blk_config_list = Vec::new();
for item in vhost_user_blk_list.iter() {
vhost_user_blk_config_list.push(VhostUserBlkConfig::parse(item)?);
}
vhost_user_blk = Some(vhost_user_blk_config_list);
}
let mut kernel: Option<KernelConfig> = None;
if let Some(k) = vm_params.kernel {
kernel = Some(KernelConfig {
path: PathBuf::from(k),
});
}
Ok(VmConfig {
cpus: CpusConfig::parse(vm_params.cpus)?,
memory: MemoryConfig::parse(vm_params.memory)?,
kernel: KernelConfig::parse(vm_params.kernel)?,
kernel,
cmdline: CmdlineConfig::parse(vm_params.cmdline)?,
disks,
net,
rng: RngConfig::parse(vm_params.rng)?,
rng,
fs,
pmem,
serial,
console,
devices,
vhost_user_net,
vhost_user_blk,
vsock,
iommu,
})
}
}

1290
vmm/src/device_manager.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -3,67 +3,466 @@
// SPDX-License-Identifier: Apache-2.0
//
extern crate kvm_ioctls;
#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate log;
extern crate serde;
#[macro_use]
extern crate serde_derive;
extern crate serde_json;
extern crate vmm_sys_util;
use kvm_ioctls::*;
use std::fmt::{self, Display};
use std::result;
use crate::api::{ApiError, ApiRequest, ApiResponse, ApiResponsePayload, VmInfo};
use crate::config::VmConfig;
use crate::vm::{Error as VmError, Vm, VmState};
use libc::EFD_NONBLOCK;
use std::io;
use std::os::unix::io::{AsRawFd, RawFd};
use std::sync::mpsc::{Receiver, RecvError, SendError, Sender};
use std::sync::Arc;
use std::{result, thread};
use vmm_sys_util::eventfd::EventFd;
pub mod api;
pub mod config;
pub mod device_manager;
pub mod vm;
use self::config::VmConfig;
use self::vm::{ExitBehaviour, Vm};
/// Errors associated with VM management
/// Errors associated with VMM management
#[derive(Debug)]
#[allow(clippy::large_enum_variant)]
pub enum Error {
/// Cannot create a new VM.
VmNew(vm::Error),
/// API request receive error
ApiRequestRecv(RecvError),
/// Cannot start a VM.
VmStart(vm::Error),
/// API response send error
ApiResponseSend(SendError<ApiResponse>),
/// Cannot load a kernel.
LoadKernel(vm::Error),
/// Cannot bind to the UNIX domain socket path
Bind(io::Error),
/// Cannot clone EventFd.
EventFdClone(io::Error),
/// Cannot create EventFd.
EventFdCreate(io::Error),
/// Cannot read from EventFd.
EventFdRead(io::Error),
/// Cannot create epoll context.
Epoll(io::Error),
/// Cannot create HTTP thread
HttpThreadSpawn(io::Error),
/// Cannot handle the VM STDIN stream
Stdin(VmError),
/// Cannot reboot the VM
VmReboot(VmError),
/// Cannot shut a VM down
VmShutdown(VmError),
/// Cannot create VMM thread
VmmThreadSpawn(io::Error),
/// Cannot shut the VMM down
VmmShutdown(VmError),
}
pub type Result<T> = result::Result<T, Error>;
impl Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::Error::*;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum EpollDispatch {
Exit,
Reset,
Stdin,
Api,
}
match self {
VmNew(e) => write!(f, "Can not create a new virtual machine: {:?}", e),
VmStart(e) => write!(f, "Can not start a new virtual machine: {:?}", e),
LoadKernel(e) => write!(f, "Can not load a guest kernel: {:?}", e),
}
pub struct EpollContext {
raw_fd: RawFd,
dispatch_table: Vec<Option<EpollDispatch>>,
}
impl EpollContext {
pub fn new() -> result::Result<EpollContext, io::Error> {
let raw_fd = epoll::create(true)?;
// Initial capacity needs to be large enough to hold:
// * 1 exit event
// * 1 reset event
// * 1 stdin event
// * 1 API event
let mut dispatch_table = Vec::with_capacity(5);
dispatch_table.push(None);
Ok(EpollContext {
raw_fd,
dispatch_table,
})
}
pub fn add_stdin(&mut self) -> result::Result<(), io::Error> {
let dispatch_index = self.dispatch_table.len() as u64;
epoll::ctl(
self.raw_fd,
epoll::ControlOptions::EPOLL_CTL_ADD,
libc::STDIN_FILENO,
epoll::Event::new(epoll::Events::EPOLLIN, dispatch_index),
)?;
self.dispatch_table.push(Some(EpollDispatch::Stdin));
Ok(())
}
fn add_event<T>(&mut self, fd: &T, token: EpollDispatch) -> result::Result<(), io::Error>
where
T: AsRawFd,
{
let dispatch_index = self.dispatch_table.len() as u64;
epoll::ctl(
self.raw_fd,
epoll::ControlOptions::EPOLL_CTL_ADD,
fd.as_raw_fd(),
epoll::Event::new(epoll::Events::EPOLLIN, dispatch_index),
)?;
self.dispatch_table.push(Some(token));
Ok(())
}
}
struct Vmm {
kvm: Kvm,
impl AsRawFd for EpollContext {
fn as_raw_fd(&self) -> RawFd {
self.raw_fd
}
}
pub fn start_vmm_thread(
http_path: &str,
api_event: EventFd,
api_sender: Sender<ApiRequest>,
api_receiver: Receiver<ApiRequest>,
) -> Result<thread::JoinHandle<Result<()>>> {
let http_api_event = api_event.try_clone().map_err(Error::EventFdClone)?;
let thread = thread::Builder::new()
.name("vmm".to_string())
.spawn(move || {
let mut vmm = Vmm::new(api_event)?;
vmm.control_loop(Arc::new(api_receiver))
})
.map_err(Error::VmmThreadSpawn)?;
// The VMM thread is started, we can start serving HTTP requests
api::start_http_thread(http_path, http_api_event, api_sender)?;
Ok(thread)
}
pub struct Vmm {
epoll: EpollContext,
exit_evt: EventFd,
reset_evt: EventFd,
api_evt: EventFd,
vm: Option<Vm>,
vm_config: Option<Arc<VmConfig>>,
}
impl Vmm {
fn new() -> Result<Self> {
let kvm = Kvm::new().expect("new KVM instance creation failed");
Ok(Vmm { kvm })
fn new(api_evt: EventFd) -> Result<Self> {
let mut epoll = EpollContext::new().map_err(Error::Epoll)?;
let exit_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::EventFdCreate)?;
let reset_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::EventFdCreate)?;
if unsafe { libc::isatty(libc::STDIN_FILENO as i32) } != 0 {
epoll.add_stdin().map_err(Error::Epoll)?;
}
epoll
.add_event(&exit_evt, EpollDispatch::Exit)
.map_err(Error::Epoll)?;
epoll
.add_event(&reset_evt, EpollDispatch::Reset)
.map_err(Error::Epoll)?;
epoll
.add_event(&api_evt, EpollDispatch::Api)
.map_err(Error::Epoll)?;
Ok(Vmm {
epoll,
exit_evt,
reset_evt,
api_evt,
vm: None,
vm_config: None,
})
}
}
pub fn boot_kernel(config: VmConfig) -> Result<()> {
loop {
let vmm = Vmm::new()?;
let mut vm = Vm::new(&vmm.kvm, &config).map_err(Error::VmNew)?;
fn vm_boot(&mut self) -> result::Result<(), VmError> {
// Create a new VM is we don't have one yet.
if self.vm.is_none() {
let exit_evt = self.exit_evt.try_clone().map_err(VmError::EventFdClone)?;
let reset_evt = self.reset_evt.try_clone().map_err(VmError::EventFdClone)?;
let entry = vm.load_kernel().map_err(Error::LoadKernel)?;
if vm.start(entry).map_err(Error::VmStart)? == ExitBehaviour::Shutdown {
break;
if let Some(ref vm_config) = self.vm_config {
let vm = Vm::new(Arc::clone(vm_config), exit_evt, reset_evt)?;
self.vm = Some(vm);
}
}
// Now we can boot the VM.
if let Some(ref mut vm) = self.vm {
vm.boot()
} else {
Err(VmError::VmNotCreated)
}
}
Ok(())
fn vm_pause(&mut self) -> result::Result<(), VmError> {
if let Some(ref mut vm) = self.vm {
vm.pause()
} else {
Err(VmError::VmNotRunning)
}
}
fn vm_resume(&mut self) -> result::Result<(), VmError> {
if let Some(ref mut vm) = self.vm {
vm.resume()
} else {
Err(VmError::VmNotRunning)
}
}
fn vm_shutdown(&mut self) -> result::Result<(), VmError> {
if let Some(ref mut vm) = self.vm.take() {
vm.shutdown()
} else {
Err(VmError::VmNotRunning)
}
}
fn vm_reboot(&mut self) -> result::Result<(), VmError> {
// Without ACPI, a reset is equivalent to a shutdown
#[cfg(not(feature = "acpi"))]
{
if let Some(ref mut vm) = self.vm {
vm.shutdown()?;
return Ok(());
}
}
// First we stop the current VM and create a new one.
if let Some(ref mut vm) = self.vm {
let config = vm.get_config();
vm.shutdown()?;
let exit_evt = self.exit_evt.try_clone().map_err(VmError::EventFdClone)?;
let reset_evt = self.reset_evt.try_clone().map_err(VmError::EventFdClone)?;
self.vm = Some(Vm::new(config, exit_evt, reset_evt)?);
}
// Then we start the new VM.
if let Some(ref mut vm) = self.vm {
vm.boot()?;
} else {
return Err(VmError::VmNotCreated);
}
Ok(())
}
fn vm_info(&self) -> result::Result<VmInfo, VmError> {
match &self.vm_config {
Some(config) => {
let state = match &self.vm {
Some(vm) => vm.get_state()?,
None => VmState::Created,
};
Ok(VmInfo {
config: Arc::clone(config),
state,
})
}
None => Err(VmError::VmNotCreated),
}
}
fn vm_delete(&mut self) -> result::Result<(), VmError> {
if self.vm_config.is_none() {
return Ok(());
}
// First we try to shut the current VM down.
self.vm_shutdown()?;
self.vm_config = None;
Ok(())
}
fn vmm_shutdown(&mut self) -> result::Result<(), VmError> {
self.vm_delete()
}
fn control_loop(&mut self, api_receiver: Arc<Receiver<ApiRequest>>) -> Result<()> {
const EPOLL_EVENTS_LEN: usize = 100;
let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN];
let epoll_fd = self.epoll.as_raw_fd();
'outer: loop {
let num_events = match epoll::wait(epoll_fd, -1, &mut events[..]) {
Ok(res) => res,
Err(e) => {
if e.kind() == io::ErrorKind::Interrupted {
// It's well defined from the epoll_wait() syscall
// documentation that the epoll loop can be interrupted
// before any of the requested events occurred or the
// timeout expired. In both those cases, epoll_wait()
// returns an error of type EINTR, but this should not
// be considered as a regular error. Instead it is more
// appropriate to retry, by calling into epoll_wait().
continue;
}
return Err(Error::Epoll(e));
}
};
for event in events.iter().take(num_events) {
let dispatch_idx = event.data as usize;
if let Some(dispatch_type) = self.epoll.dispatch_table[dispatch_idx] {
match dispatch_type {
EpollDispatch::Exit => {
// Consume the event.
self.exit_evt.read().map_err(Error::EventFdRead)?;
self.vmm_shutdown().map_err(Error::VmmShutdown)?;
break 'outer;
}
EpollDispatch::Reset => {
// Consume the event.
self.reset_evt.read().map_err(Error::EventFdRead)?;
self.vm_reboot().map_err(Error::VmReboot)?;
}
EpollDispatch::Stdin => {
if let Some(ref vm) = self.vm {
vm.handle_stdin().map_err(Error::Stdin)?;
}
}
EpollDispatch::Api => {
// Consume the event.
self.api_evt.read().map_err(Error::EventFdRead)?;
// Read from the API receiver channel
let api_request = api_receiver.recv().map_err(Error::ApiRequestRecv)?;
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)?;
}
ApiRequest::VmDelete(sender) => {
let response = self
.vm_delete()
.map_err(ApiError::VmDelete)
.map(|_| ApiResponsePayload::Empty);
sender.send(response).map_err(Error::ApiResponseSend)?;
}
ApiRequest::VmBoot(sender) => {
// If we don't have a config, we can not boot a VM.
if self.vm_config.is_none() {
sender
.send(Err(ApiError::VmMissingConfig))
.map_err(Error::ApiResponseSend)?;
continue;
}
let response = self
.vm_boot()
.map_err(ApiError::VmBoot)
.map(|_| ApiResponsePayload::Empty);
sender.send(response).map_err(Error::ApiResponseSend)?;
}
ApiRequest::VmShutdown(sender) => {
let response = self
.vm_shutdown()
.map_err(ApiError::VmShutdown)
.map(|_| ApiResponsePayload::Empty);
sender.send(response).map_err(Error::ApiResponseSend)?;
}
ApiRequest::VmReboot(sender) => {
let response = self
.vm_reboot()
.map_err(ApiError::VmReboot)
.map(|_| ApiResponsePayload::Empty);
sender.send(response).map_err(Error::ApiResponseSend)?;
}
ApiRequest::VmInfo(sender) => {
let response = self
.vm_info()
.map_err(ApiError::VmInfo)
.map(ApiResponsePayload::VmInfo);
sender.send(response).map_err(Error::ApiResponseSend)?;
}
ApiRequest::VmPause(sender) => {
let response = self
.vm_pause()
.map_err(ApiError::VmPause)
.map(|_| ApiResponsePayload::Empty);
sender.send(response).map_err(Error::ApiResponseSend)?;
}
ApiRequest::VmResume(sender) => {
let response = self
.vm_resume()
.map_err(ApiError::VmResume)
.map(|_| ApiResponsePayload::Empty);
sender.send(response).map_err(Error::ApiResponseSend)?;
}
ApiRequest::VmmShutdown(sender) => {
let response = self
.vmm_shutdown()
.map_err(ApiError::VmmShutdown)
.map(|_| ApiResponsePayload::Empty);
sender.send(response).map_err(Error::ApiResponseSend)?;
break 'outer;
}
}
}
}
}
}
}
Ok(())
}
}

File diff suppressed because it is too large Load Diff