Compare commits

..

165 Commits

Author SHA1 Message Date
Samuel Ortiz
7688e6e231 release-notes: Add table of contents
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-09-05 16:47:12 +02:00
Samuel Ortiz
d784ac2982 release-notes: Add v0.2.0 notes
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-09-05 16:47:12 +02:00
Cathy Zhang
8c2a9a75ec vm-virtio: Update backend feature set for vhost-user-net
Regarding vhost-user-net, there are features in avail_features
and acked_features, like VIRTIO_NET_F_MAC which is required by
driver and device to transfer mac address through config space,
but not needed by backend, like ovs+dpdk, so it's necessary to
adjust backend_features based on acked_features before calling
set_features() API.

This fix is to record backend_features in vhost-user-net to avoid
requesting it twice.

Signed-off-by: Cathy Zhang <cathy.zhang@intel.com>
2019-09-05 07:11:58 -07:00
Cathy Zhang
b8622b5c69 vm-virtio: Address event count error and refactor data setting
New event is added in VhostUserEpollHandler for vhost-user fs,
but the total event count is not update accordingly. Fix the
issue and refactor the event data setting for new event
expansion in the future.

Signed-off-by: Cathy Zhang <cathy.zhang@intel.com>
2019-09-05 07:11:58 -07:00
Rob Bradford
fe9398fe87 scripts: Fix integration tests script
Running the integration test script showed the following error:

+ '[' '!' -f /home/rob/workloads/virtiofsd
scripts/run_integration_tests.sh: line 84: [: missing `]'
+ -f /home/rob/workloads/vubridge ']'
scripts/run_integration_tests.sh: line 84: -f: command not found

This was preventing the test from reusing build QEMU artifacts.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-09-05 13:13:50 +02:00
Rob Bradford
1f06c5907f Revert "vmm, tests: Disable reboot support"
This reverts commit 8308e1bf25.
2019-09-05 10:38:14 +01:00
Rob Bradford
5dd675710b vmm: Call munmap() on regions that have been mmap()ed
For virtio-fs and virtio-pmem regions of memory are manually mapped into
the address space of the VMM. In order to cleanly reboot we need to
unmap those regions.

Fixes: #223

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-09-05 10:38:14 +01:00
dependabot-preview[bot]
037807f949 build(deps): bump backtrace from 0.3.36 to 0.3.37
Bumps [backtrace](https://github.com/rust-lang/backtrace-rs) from 0.3.36 to 0.3.37.
- [Release notes](https://github.com/rust-lang/backtrace-rs/releases)
- [Commits](https://github.com/rust-lang/backtrace-rs/compare/0.3.36...0.3.37)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-09-04 20:41:39 +00:00
dependabot-preview[bot]
47ca277690 build(deps): bump backtrace from 0.3.35 to 0.3.36
Bumps [backtrace](https://github.com/rust-lang/backtrace-rs) from 0.3.35 to 0.3.36.
- [Release notes](https://github.com/rust-lang/backtrace-rs/releases)
- [Commits](https://github.com/rust-lang/backtrace-rs/compare/0.3.35...0.3.36)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-09-04 16:53:46 +00:00
Rob Bradford
f59cad15a3 vmm: Cleanup signal_handler thread used for console SIGWINCH handling
Do this by using the same mechanism as the vCPU threads by sending a
signal to the thread. As this is the same mechanism reuse the same code
and rename the "vcpus" member to "threads" to indicate this represents
both the vCPU threads and also the signal handler thread.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-09-04 09:21:01 -07:00
dependabot-preview[bot]
7ce0db90f0 build(deps): bump regex-syntax from 0.6.11 to 0.6.12
Bumps [regex-syntax](https://github.com/rust-lang/regex) from 0.6.11 to 0.6.12.
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/regex-syntax-0.6.11...regex-syntax-0.6.12)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-09-03 17:24:30 +00:00
Rob Bradford
9e764fc091 vmm, arch, devices: Put ACPI support behind a default feature
Put the ACPI support behind a feature and ensure that the code compiles
without that feature by adding an extra build to Travis.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-09-03 19:18:49 +02:00
Rob Bradford
bb2e7bb942 vmm: Shutdown vCPU threads
As part of the cleanup of the VM shutdown all the vCPU threads. This is
achieved by toggling a shared atomic boolean variable which is checked
in the vCPU loop. To trigger the vCPU code to look at this boolean it is
necessary to send a signal to the vCPU which will interrupt the running
KVM_RUN ioctl.

Fixes: #229

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-09-03 19:18:49 +02:00
Rob Bradford
40f9da524f tests: Add a basic direct boot test with acpi=off
Check that everything continues to function without ACPI.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-09-03 19:18:49 +02:00
Rob Bradford
8308e1bf25 vmm, tests: Disable reboot support
Being able to reboot requires us to identify all the resources we are
leaking and cleaning those up before we can enable reboot. For now if
the user requests a reboot then shutdown instead.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-09-03 19:18:49 +02:00
Rob Bradford
ad128bf72d vmm: Give vCPU and signal handler thread useful names
Sadly only the first few characters of the thread name is preserved so
use a shorter name for the vCPU thread for now. Also give the signal
handling thread a name.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-09-03 19:18:49 +02:00
Rob Bradford
7205700c5f tests: Add integration testing for VM reboot
Both with direct kernel boot, Ubuntu and Clear firmware boots.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-09-03 19:18:49 +02:00
Rob Bradford
3af5619256 tests: Use shutdown rather than reboot to shutdown the VMs
Now that we have ACPI shutdown support "reboot" will actually reboot the
VM rather than trigger the VMM to exit.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-09-03 19:18:49 +02:00
Rob Bradford
614eb68f16 vm: Make triple-fault and i8042 reset reboot the VM
Now we have ACPI shutdown we should reboot on these reset triggers.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-09-03 19:18:49 +02:00
Rob Bradford
5a187ee2c2 x86_64/devices: acpi: Add support for ACPI shutdown & reboot
Add an I/O port "device" to handle requests from the kernel to shutdown
or trigger a reboot, borrowing an I/O used for ACPI on the Q35 platform.
The details of this I/O port are included in the FADT
(SLEEP_STATUS_REG/SLEEP_CONTROL_REG/RESET_REG) with the details of the
value to write in the FADT for reset (RESET_VALUE) and in the DSDT for
shutdown (S5 -> 0x05)

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-09-03 19:18:49 +02:00
Rob Bradford
ae66a44d26 vmm: Support both reset and shutdown
Add a 2nd EventFd to the VM to control resetting (rebooting) the VM this
supplements the EventFd used for managing shutdown of the VM.

The default behaviour on i8042 or triple-fault based reset is currently
unchanged i.e. it will trigger a shutdown.

In order to support restarting the VM it was necessary to make start()
function take a reference to the config.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-09-03 19:18:49 +02:00
Rob Bradford
ebe8edd423 devices: i8042: Use error! macro
Now that we have the logging infrastructure in place there is no need to
use println!

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-09-03 19:18:49 +02:00
Sebastien Boeuf
011496bda0 arch: acpi: Fix legacy interrupt for serial device
The DSDT must declare the interrupt used by the serial device. This
helps the guest kernel matching the right interrupt to the 8250 serial
device. This is mandatory in case the IRQ routing is handled by ACPI, as
we must let ACPI know what do do with pin based interrupts.

One thing to notice, if we were using acpi=noirq from the kernel command
line, this would mean ACPI is not in charge of the IRQ routing, and the
device COM1 declaration would not be needed.

One additional requirement is to provide the appropriate interrupt
source override for the legacy ISA interrupts (0-15), which will give
the right information to the guest kernel about how to allocate the
associated IRQs.

Because we want to keep the MADT as simple as possible, and given that
our only device requiring pin based interrupt is the serial device, we
choose to only define the pin 4.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-09-03 19:18:49 +02:00
Rob Bradford
2610f4353d arch: acpi: Only add ACPI COM1 device if serial is turned on
Only add the ACPI PNP device for the COM1 serial port if it is not
turned off with "--serial off"

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-09-03 19:18:49 +02:00
Rob Bradford
15387cd96a arch: x86_64: acpi: Add DSDT table entries for PCI and COM1
Currently this has a hardcoded range from 32GiB to 64GiB for the 64-bit PCI
range. It should range from the top of ram to 64GiB.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-09-03 19:18:49 +02:00
Rob Bradford
638bf0378c arch: x86_64: acpi: Generate MCFG table
The MCFG table contains some PCI configuration details in particular
details of where the enhanced configuration space is.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-09-03 19:18:49 +02:00
Rob Bradford
451502b50b vm: If a VCPU thread errors out then exit the hypervisor
Currently when the VCPU thread exits on an error the VMM continues to
run with no way of shutting down the main thread.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-09-03 19:18:49 +02:00
Rob Bradford
98f81c36ec arch: x86_64: acpi: Generate MADT aka APIC table
This provides important APIC configuration details for the CPU. Even
though it duplicates some of the information already included in the
mptable it is necessary when booting with ACPI as the mptable is not
used.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-09-03 19:18:49 +02:00
Rob Bradford
ee83c2d44e arch: x86_64: Generate basic ACPI tables
Generate very basic ACPI tables for HW reduced ACPI.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-09-03 19:18:49 +02:00
Rob Bradford
eea6f1dc9e acpi_tables: Add initial ACPI tables support
Add a revision 2 RSDP table only supporting an XSDT along with support
for creating generic SDT based tables.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-09-03 19:18:49 +02:00
Yang Zhong
3e99098bf3 vhost_rs: add config messge support
The previous definitions does not cover config space read/write
and only cover general message as below:

A vhost-user message consists of 3 header fields and a payload.

+---------+-------+------+---------+
| request | flags | size | payload |
+---------+-------+------+---------+

but for config space, the payload include:

Virtio device config space
^^^^^^^^^^^^^^^^^^^^^^^^^^

+--------+------+-------+---------+
| offset | size | flags | payload |
+--------+------+-------+---------+

:offset: a 32-bit offset of virtio device's configuration space

:size: a 32-bit configuration space access size in bytes

🎏 a 32-bit value:
  - 0: Vhost master messages used for writeable fields
  - 1: Vhost master messages used for live migration

:payload: Size bytes array holding the contents of the virtio
          device's configuration space

This patch add specific functions for config message, which can
get/set config space from/to backend.

Signed-off-by: Yang Zhong <yang.zhong@intel.com>
2019-09-03 08:39:37 -07:00
Yang Zhong
e05de4514d vhost_rs: The vhost user version we support
The vhost user version should be same with backend.
define VHOST_USER_VERSION    (0x1)

Signed-off-by: Yang Zhong <yang.zhong@intel.com>
2019-09-03 08:39:37 -07:00
Yang Zhong
6fb7c3bbc2 vhost_rs: remove config space offset setting
There is one definition in message.rs file as below:
pub const VHOST_USER_CONFIG_OFFSET: u32 = 0x100

This definition is only for virtio mmio config space
and we will add this offset in virtio-mmio side and
not vhost user protocl side.

Signed-off-by: Yang Zhong <yang.zhong@intel.com>
2019-09-03 08:39:37 -07:00
Yang Zhong
a44a903587 vhost_rs: Change get_config()/set_config()
Use acked_protocol_features to replace acked_virtio_features in
get_config()/set_config() for protocol features like CONFIG.

This patch also fix wrong GET_CONFIG setting for set_config().

Signed-off-by: Yang Zhong <yang.zhong@intel.com>
2019-09-03 08:39:37 -07:00
Yang Zhong
b4187a1b9d vhost_rs: Change the VhostUserConfigFlags
The latest vhost user spec only define two members in
VhostSetConfigType, master and live migration. These
changes can make rust-vmm compatible with vhost user backend.

Signed-off-by: Yang Zhong <yang.zhong@intel.com>
2019-09-03 08:39:37 -07:00
Samuel Ortiz
8718043dfc cloud-hypervisor: Bump vmm-sys-util crate version
Bump from 829d605 to fd4dcd1.

PR #225 failed because we were still using the vmm-sys-util logging
macros and the crate's syslog module got removed.

This one relies on the previous commit switching to using the
log crate macros instead.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-09-02 15:07:42 +02:00
Samuel Ortiz
add0471120 vfio: Use the log crate macros
Instead of using the syslog vmm-sys-util ones.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-09-02 15:07:42 +02:00
Sebastien Boeuf
772191b409 vm-virtio: vhost-user: Rely on acked features to setup backend
At this point in the code, the acked features have been provided by the
guest and they can be set back to the backend. There's no need to
retrieve one more time the backend features for this purpose.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-08-31 17:33:17 +01:00
Sebastien Boeuf
97699a521f vm-virtio: vhost-user: Vring should be enabled after initialization
As mentioned in the vhost-user specification, each ring is initialized
in a stopped state. This means each ring should be enabled only after
it has been correctly initialized.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-08-31 17:33:17 +01:00
Sebastien Boeuf
a4ebcf486d vm-virtio: vhost-user-net: Map proper error when getting features
Simple patch replacing unwrap() with appropriate map_err().

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-08-31 17:33:17 +01:00
Sebastien Boeuf
cdfe576eb1 vm-virtio: vhost-user-net: Set the right set of features
The available features are masked with the backend features, therefore
the available features should be the one used when calling into
set_features() API.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-08-31 17:33:17 +01:00
Sebastien Boeuf
bc42420583 vm-virtio: Expand vhost-user handler to be reused from virtio-fs
In order to factorize the code between vhost-user-net and virtio-fs one
step further, this patch extends the vhost-user handler implementation
to support slave requests.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-08-31 17:33:17 +01:00
Sebastien Boeuf
b7d3ad9063 vm-virtio: fs: Factorize vhost-user setup
This patch factorizes the existing virtio-fs code by relying onto the
common code part of the vhost_user module in the vm-virtio crate.

In details, it factorizes the vhost-user setup, and reuses the error
types defined by the module instead of defining its own types.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-08-31 17:33:17 +01:00
Sebastien Boeuf
56cad00f2e vm-virtio: Move fs.rs to vhost_user module
vhost-user-net introduced a new module vhost_user inside the vm-virtio
crate. Because virtio-fs is actually vhost-user-fs, it belongs to this
new module and needs to be moved there.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-08-31 17:33:17 +01:00
Cathy Zhang
cc7a96e9d3 main: Add integration test
Use qemu/tests/vhost-user-bridge as the backend for integration
test for vhost-user-net.

Signed-off-by: Cathy Zhang <cathy.zhang@intel.com>
2019-08-30 15:00:26 +01:00
Cathy Zhang
f21d54f6b0 main: Add arguments entry for vhost-user-net
Signed-off-by: Cathy Zhang <cathy.zhang@intel.com>
2019-08-30 15:00:26 +01:00
Cathy Zhang
584a2cccee vmm: Add vhost-user-net support
Update vm configuration and device initial process to add
vhost-user-net support.

Signed-off-by: Cathy Zhang <cathy.zhang@intel.com>
2019-08-30 15:00:26 +01:00
Cathy Zhang
633f51af9c vm-virtio: Add vhost-user-net implementation
vhost-user framwork could provide good performance in data intensive
scenario due to the memory sharing mechanism. Implement vhost-user-net
device to get the benefit for Rust-based VMMs network.

Signed-off-by: Cathy Zhang <cathy.zhang@intel.com>
2019-08-30 15:00:26 +01:00
Cathy Zhang
51306555e7 vmm: Add hugetlbfs handling support
The currently directory handling process to open tempfile by
OpenOptions with custom_flags(O_TMPFILE) is workable for tmp
filesystem, but not workable for hugetlbfs, add new directory
handling process which works fine for both tmpfs and hugetlbfs.

Signed-off-by: Cathy Zhang <cathy.zhang@intel.com>
2019-08-30 15:00:26 +01:00
dependabot-preview[bot]
ce60ff16c4 build(deps): bump vmm-sys-util from a0b3893 to 829d605
Bumps [vmm-sys-util](https://github.com/rust-vmm/vmm-sys-util) from `a0b3893` to `829d605`.
- [Release notes](https://github.com/rust-vmm/vmm-sys-util/releases)
- [Commits](a0b3893a40...829d605a07)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-08-30 11:35:54 +00:00
dependabot-preview[bot]
3dd329052c build(deps): bump vmm-sys-util from 2177381 to a0b3893
Bumps [vmm-sys-util](https://github.com/rust-vmm/vmm-sys-util) from `2177381` to `a0b3893`.
- [Release notes](https://github.com/rust-vmm/vmm-sys-util/releases)
- [Commits](2177381ed6...a0b3893a40)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-08-29 07:59:39 +00:00
Sebastien Boeuf
b2f85cbdc4 vhost_rs: Wait for full request to be satisfied
The recvmsg syscall can split a request in multiple packets unless we
use the flag MSG_WAITALL to make sure the request will wait for the
whole data to be transferred before returning.

This flag is needed to prevent the vhost crate from returning the error
PartialMessage, which occured sporadically when using virtio-fs, and
which was detected as part of our continuous integration testing.

Fixes #182

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-08-29 11:01:32 +08:00
dependabot-preview[bot]
18a8bb0072 build(deps): bump vmm-sys-util from 7222869 to 2177381
Bumps [vmm-sys-util](https://github.com/rust-vmm/vmm-sys-util) from `7222869` to `2177381`.
- [Release notes](https://github.com/rust-vmm/vmm-sys-util/releases)
- [Commits](7222869ed3...2177381ed6)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-08-28 11:35:54 +00:00
dependabot-preview[bot]
151637b647 build(deps): bump cc from 1.0.40 to 1.0.41
Bumps [cc](https://github.com/alexcrichton/cc-rs) from 1.0.40 to 1.0.41.
- [Release notes](https://github.com/alexcrichton/cc-rs/releases)
- [Commits](https://github.com/alexcrichton/cc-rs/compare/1.0.40...1.0.41)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-08-28 01:13:15 +00:00
dependabot-preview[bot]
c316c161a6 build(deps): bump vm-memory from 1635f25 to 8669369
Bumps [vm-memory](https://github.com/rust-vmm/vm-memory) from `1635f25` to `8669369`.
- [Release notes](https://github.com/rust-vmm/vm-memory/releases)
- [Commits](1635f25afc...8669369d17)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-08-27 12:48:10 +00:00
dependabot-preview[bot]
808fcaa43b build(deps): bump lazy_static from 1.3.0 to 1.4.0
Bumps [lazy_static](https://github.com/rust-lang-nursery/lazy-static.rs) from 1.3.0 to 1.4.0.
- [Release notes](https://github.com/rust-lang-nursery/lazy-static.rs/releases)
- [Commits](https://github.com/rust-lang-nursery/lazy-static.rs/compare/1.3.0...1.4.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-08-26 13:30:59 +00:00
dependabot-preview[bot]
bc87c9f19b build(deps): bump kvm-ioctls from 37669f6 to 30adb02
Bumps [kvm-ioctls](https://github.com/rust-vmm/kvm-ioctls) from `37669f6` to `30adb02`.
- [Release notes](https://github.com/rust-vmm/kvm-ioctls/releases)
- [Commits](37669f60a0...30adb02158)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-08-26 13:30:36 +00:00
dependabot-preview[bot]
66a7a94a12 build(deps): bump getrandom from 0.1.10 to 0.1.11
Bumps [getrandom](https://github.com/rust-random/getrandom) from 0.1.10 to 0.1.11.
- [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.10...v0.1.11)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-08-25 18:03:45 +00:00
Sebastien Boeuf
dfb18ef14a net: Make TAP registration functions immutable
By making the registration functions immutable, this patch prevents from
self borrowing issues with the RwLock on self.mem.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-08-22 08:24:15 +01:00
Sebastien Boeuf
0b8856d148 vmm: Add RwLock to the GuestMemoryMmap
Following the refactoring of the code allowing multiple threads to
access the same instance of the guest memory, this patch goes one step
further by adding RwLock to it. This anticipates the future need for
being able to modify the content of the guest memory at runtime.

The reasons for adding regions to an existing guest memory could be:
- Add virtio-pmem and virtio-fs regions after the guest memory was
  created.
- Support future hotplug of devices, memory, or anything that would
  require more memory at runtime.

Because most of the time, the lock will be taken as read only, using
RwLock instead of Mutex is the right approach.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-08-22 08:24:15 +01:00
Sebastien Boeuf
ec0b5567c8 vmm: Share the guest memory instead of cloning it
The VMM guest memory was cloned (copied) everywhere the code needed to
have ownership of it. In order to clean the code, and in anticipation
for future support of modifying this guest memory instance at runtime,
it is important that every part of the code share the same instance.

Because VirtioDevice implementations need to have access to it from
different threads, that's why Arc must be used in this case.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-08-22 08:24:15 +01:00
Rob Bradford
f4d41d600b virtio: net: Remove TAP fd from epoll when no available descriptors
When there are no available descriptors in the queue (observed when the
network interface hasn't been brought up by the kernel) stop waiting for
notifications that the TAP fd should be read from.

This avoids a situation where the TAP device has data avaiable and wakes
up the virtio-net thread only for the virtio-net thread not read that
data as it has nowhere to put it.

When there are descriptors available in the queue then we resume waiting
for the epoll event on the TAP fd.

This bug demonstrated itself as 100% CPU usage for cloud-hypervisor
binary prior to the guest network interface being brought up. The
solution was inspired by the Firecracker virtio-net code.

Fixes: #208

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-08-21 08:41:28 -07:00
dependabot-preview[bot]
582fc7f989 build(deps): bump constant_time_eq from 0.1.3 to 0.1.4
Bumps [constant_time_eq](https://github.com/cesarb/constant_time_eq) from 0.1.3 to 0.1.4.
- [Release notes](https://github.com/cesarb/constant_time_eq/releases)
- [Commits](https://github.com/cesarb/constant_time_eq/compare/0.1.3...0.1.4)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-08-21 08:33:30 +00:00
Sebastien Boeuf
44d8ab06ac vm-virtio: Remove unused dependency from unit tests
AtomicSize was imported but not used.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-08-21 08:51:25 +01:00
Sebastien Boeuf
5f52dd2d1e net_util: Fix clippy error
Make sure to explicitly declare trait objects with the keywork "dyn".

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-08-21 08:51:25 +01:00
dependabot-preview[bot]
aface5bca2 build(deps): bump unicode-width from 0.1.5 to 0.1.6
Bumps [unicode-width](https://github.com/unicode-rs/unicode-width) from 0.1.5 to 0.1.6.
- [Release notes](https://github.com/unicode-rs/unicode-width/releases)
- [Commits](https://github.com/unicode-rs/unicode-width/commits/0.1.6)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-08-21 07:40:27 +00:00
Sebastien Boeuf
dc31db478a ci: Fix virtio-fs tests
The virtiofsd daemon takes a bit of time creating and listening on the
socket. By adding 10s timeout, we make sure the vhost-user socket has
been properly created before the VMM tries to connect to it.

Also, the daemon needs cap_dac_override capabilities to access debugfs
filesystem.

Last thing, both virtio-fs and virtio-pmem tests were slightly different
from the others since they were not explicitly killing cloud-hypervisor
and virtiofsd processes once the test was done.

Fixes #182

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-08-21 08:16:16 +01:00
dependabot-preview[bot]
b528e99b18 build(deps): bump backtrace from 0.3.34 to 0.3.35
Bumps [backtrace](https://github.com/rust-lang/backtrace-rs) from 0.3.34 to 0.3.35.
- [Release notes](https://github.com/rust-lang/backtrace-rs/releases)
- [Commits](https://github.com/rust-lang/backtrace-rs/compare/0.3.34...0.3.35)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-08-20 11:14:36 +00:00
dependabot-preview[bot]
e84179994e build(deps): bump blake2b_simd from 0.5.6 to 0.5.7
Bumps [blake2b_simd](https://github.com/oconnor663/blake2_simd) from 0.5.6 to 0.5.7.
- [Release notes](https://github.com/oconnor663/blake2_simd/releases)
- [Commits](https://github.com/oconnor663/blake2_simd/compare/0.5.6...0.5.7)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-08-20 10:55:39 +00:00
dependabot-preview[bot]
1d9ad9e7b0 build(deps): bump autocfg from 0.1.5 to 0.1.6
Bumps [autocfg](https://github.com/cuviper/autocfg) from 0.1.5 to 0.1.6.
- [Release notes](https://github.com/cuviper/autocfg/releases)
- [Commits](https://github.com/cuviper/autocfg/compare/0.1.5...0.1.6)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-08-20 10:55:23 +00:00
Rob Bradford
26a210a83a arch: x86_64: Fix EBDA adddress
This was set to the MP table address rather than the start of the EBDA.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-08-19 16:04:34 +01:00
dependabot-preview[bot]
55f01b2180 build(deps): bump remain from 0.1.3 to 0.1.4
Bumps [remain](https://github.com/dtolnay/remain) from 0.1.3 to 0.1.4.
- [Release notes](https://github.com/dtolnay/remain/releases)
- [Commits](https://github.com/dtolnay/remain/compare/0.1.3...0.1.4)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-08-19 10:18:09 +00:00
dependabot-preview[bot]
49a129f3d3 build(deps): bump serde from 1.0.98 to 1.0.99
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.98 to 1.0.99.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.98...v1.0.99)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-08-19 10:17:57 +00:00
dependabot-preview[bot]
1c23a09cc6 build(deps): bump getrandom from 0.1.9 to 0.1.10
Bumps [getrandom](https://github.com/rust-random/getrandom) from 0.1.9 to 0.1.10.
- [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.9...v0.1.10)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-08-19 10:17:45 +00:00
Rob Bradford
f0082fecb9 tests: Make panics generate a backtrace
This will help pinpoint issues when debugging test failures.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-08-16 11:13:36 -07:00
Rob Bradford
8b78e1221e tests: Use newgrp to run unit tests
Rather than set filesystem permissions on the /dev/kvm device instead
use the kvm group added by installing qemu for running the unit tests.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-08-16 09:54:07 -07:00
Rob Bradford
f5a6e3c1ca build: Drop vendor directory from rustfmt command
We've not had a vendor directory for some time now.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-08-16 09:54:07 -07:00
Rob Bradford
d6e3b703ab tests: Rename virtiofsd build path
Adjust to reflect that it's QEMU being built here in preparation for
subsequent PRs that also want to build QEMU.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-08-16 09:54:07 -07:00
Rob Bradford
71154d8362 tests: Use "-f" on directory rm commands
When running the script from an interactive environment there are always
some files inside the git directory that rm prompts to delete so instead
pass "-f" to avoid that.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-08-16 09:54:07 -07:00
dependabot-preview[bot]
7245cf7c5e build(deps): bump rust-argon2 from 0.5.0 to 0.5.1
Bumps [rust-argon2](https://github.com/sru-systems/rust-argon2) from 0.5.0 to 0.5.1.
- [Release notes](https://github.com/sru-systems/rust-argon2/releases)
- [Changelog](https://github.com/sru-systems/rust-argon2/blob/master/CHANGELOG.md)
- [Commits](https://github.com/sru-systems/rust-argon2/compare/0.5.0...0.5.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-08-16 07:49:03 +00:00
dependabot-preview[bot]
7adb9d55ec build(deps): bump libc from 0.2.61 to 0.2.62
Bumps [libc](https://github.com/rust-lang/libc) from 0.2.61 to 0.2.62.
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.61...0.2.62)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-08-16 07:22:34 +00:00
dependabot-preview[bot]
760791abbe build(deps): bump openssl-sys from 0.9.48 to 0.9.49
Bumps [openssl-sys](https://github.com/sfackler/rust-openssl) from 0.9.48 to 0.9.49.
- [Release notes](https://github.com/sfackler/rust-openssl/releases)
- [Commits](https://github.com/sfackler/rust-openssl/compare/openssl-sys-v0.9.48...openssl-sys-v0.9.49)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-08-16 06:54:47 +00:00
dependabot-preview[bot]
7b718f3029 build(deps): bump vmm-sys-util from 5f8c251 to 7222869
Bumps [vmm-sys-util](https://github.com/rust-vmm/vmm-sys-util) from `5f8c251` to `7222869`.
- [Release notes](https://github.com/rust-vmm/vmm-sys-util/releases)
- [Commits](5f8c251355...7222869ed3)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-08-16 06:54:23 +00:00
Rob Bradford
08ed88c8d1 tests: Remove potential sources of nested panics
panic()ing after a panic() has already been recovered by the credibility
test system (i.e. after an aver! has failed) results in an abort which
triggers SIGILL.

Adjust the SSH based commands to generate a Result<...,Error> which we
then either propagate through the test block. Or if the function is
directly being evaluated in an aver! macro call .unwrap_with_default()
(or .unwrap_or() in the case where the default would be wrong.)

See #182

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-08-15 11:37:42 -07:00
Sebastien Boeuf
ab6a8f19f0 tests: Fix virtio-fs with dax=off integration test
When virtio-fs is being tested through the integration tests, there is
one specific test where DAX and cache region are disabled. In this case
the virtiofsd daemon should be used with the correct option cache=none
instead of cache=always.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-08-15 19:24:46 +01:00
Rob Bradford
567eda45ec tests: Retrieve the bionic image from the Azure storage bucket
Avoid network delays by grabbing the bionic image from the local storage
bucket.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-08-15 18:57:53 +01:00
Johan Kuijpers
0affdd0df7 docs: Add networking HOWTO
Signed-off-by: Johan Kuijpers <johan.kuijpers@ericsson.com>
2019-08-15 17:52:09 +01:00
Sebastien Boeuf
658c076eb2 linters: Fix clippy issues
Latest clippy version complains about our existing code for the
following reasons:

- trait objects without an explicit `dyn` are deprecated
- `...` range patterns are deprecated
- lint `clippy::const_static_lifetime` has been renamed to
  `clippy::redundant_static_lifetimes`
- unnecessary `unsafe` block
- unneeded return statement

All these issues have been fixed through this patch, and rustfmt has
been run to cleanup potential formatting errors due to those changes.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-08-15 09:10:04 -07:00
Samuel Ortiz
c8364172a3 docs: Add debug I/O port HOWTO
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-08-15 16:06:54 +02:00
Samuel Ortiz
c52e276a5c vmm: Log debug ioport timestamps
We timestamp the VM creation time, and log the elapsed time between that
instant and the debug ioport events.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-08-15 16:06:54 +02:00
Samuel Ortiz
48a9300667 vmm: Log 0x80 IO port writes
The 0x80 IO port is typically used for BIOS debugging and testing on
bare metal x86 platforms.
We use that port and its dedicated 16 debug codes to time and track the
guest boot process.

Fixes #63

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-08-15 16:06:54 +02:00
dependabot-preview[bot]
10abfd4448 build(deps): bump vmm-sys-util from 54e256b to 5f8c251
Bumps [vmm-sys-util](https://github.com/rust-vmm/vmm-sys-util) from `54e256b` to `5f8c251`.
- [Release notes](https://github.com/rust-vmm/vmm-sys-util/releases)
- [Commits](54e256b2cb...5f8c251355)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-08-15 09:34:46 +00:00
dependabot-preview[bot]
6678cbfb79 build(deps): bump getrandom from 0.1.8 to 0.1.9
Bumps [getrandom](https://github.com/rust-random/getrandom) from 0.1.8 to 0.1.9.
- [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.8...v0.1.9)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-08-15 08:44:23 +00:00
Rob Bradford
513d2fdcf6 arch: x86_64: Update linux-loader crate
The linux-loader crate has been updated with a regnerated bootparams.rs
which has changed the API slightly. Update to the latest linux-loader
and adapt the code to reflect the changes:

* e820_map is renamed to e820_table (and all similar variables updated)
* e820entry is renamed to boot_e820_entry
* The E820 type constants are not no longer included

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-08-15 10:42:44 +02:00
Samuel Ortiz
76e3a30c31 pci: Simplify PciDevice trait
We do not use the on_device_sandboxed() and
register_device_capabilities() methods.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2019-08-14 18:13:44 +02:00
dependabot-preview[bot]
0d53a5890d build(deps): bump rustc-demangle from 0.1.15 to 0.1.16
Bumps [rustc-demangle](https://github.com/alexcrichton/rustc-demangle) from 0.1.15 to 0.1.16.
- [Release notes](https://github.com/alexcrichton/rustc-demangle/releases)
- [Commits](https://github.com/alexcrichton/rustc-demangle/compare/0.1.15...0.1.16)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-08-14 08:12:28 +00:00
Sebastien Boeuf
b3c809a78c tests: Fix virtio-pmem
By introducing new kernel configuration related to DAX support, the
tests are not working as they were before. The format of the image
passed through virtio-pmem needs to be in proper raw format, otherwise
the virtio-pmem driver cannot complete its probing.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-08-13 13:57:53 +02:00
Sebastien Boeuf
af9a72eab6 tests: Add virtio-fs tests with dax=on and dax=off
The existing integration tests are extended to support both use cases
where dax=on and dax=off.

In order to support DAX, the kernel configuration needs to be updated to
include CONFIG_FS_DAX and CONFIG_ZONE_DEVICE.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-08-13 13:57:53 +02:00
Sebastien Boeuf
2e0508cdc6 vm-virtio: fs: Add DAX shared region support
This patch enables the vhost-user protocol features to let the slave
initiates some request towards the master (VMM). It also takes care of
receiving the requests from the slave and take appropriate actions based
on the request type.

The way the flow works now are as follow:
 - The VMM creates a region of memory that is made available to the
   guest by exposing it through the virtio-fs PCI BAR 2.
 - The virtio-fs device is created by the VMM, exposing some protocol
   features bits to virtiofsd, letting it know that it can send some
   request to the VMM through a dedicated socket.
 - On behalf of the guest driver asking for reading or writing a file,
   virtiofsd sends a request to the VMM, asking for a file descriptor to
   be mapped into the shared memory region at a specific offset.
 - The guest can directly read/write the file at the offset of the
   memory region.

This implementation is more performant than the one using exclusively
the virtqueues. With the virtqueues, the content of the file needs to be
copied to the queues every time the guest is asking to access it.
With the shared memory region, the virtqueues become the control plane
where the libfuse commands are sent to virtiofsd. The data plane is
literally the whole memory region which does not need any extra copy of
the file content. The only penalty is the first time a file is accessed,
it needs to be mapped into the VMM virtual address space.

Another interesting case where this solution will not perform as well as
expected is when a file is larger than the region itself. This means the
file needs to be mapped in several times, but more than that this means
it needs to be remapped every time it's being accessed.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-08-13 13:57:53 +02:00
Sebastien Boeuf
3c29c47783 vmm: Create shared memory region for virtio-fs
When the cache_size parameter from virtio-fs device is not empty, the
VMM creates a dedicated memory region where the shared files will be
memory mapped by the virtio-fs device.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-08-13 13:57:53 +02:00
Sebastien Boeuf
74225ab5b3 config: Add option dax and cache_size to virtio-fs
In order to support the more performant version of virtio-fs, that is
the one relying on a shared memory region between host and guest, we
introduce two new parameters to the --fs device.

The "dax" parameter allows the user to choose if he wants to use the
shared memory region with virtio-fs. By default, this parameter is "on".

The "cache_size" parameter allows the user to specify the amount of
memory that should be shared between host and guest. By default, the
value of this parameter is 8Gib as advised by virtio-fs maintainers.

Note that dax=off and cache_size are incompatible.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-08-13 13:57:53 +02:00
Sebastien Boeuf
f30ba069b7 vm-virtio: Allocate shared memory regions on dedicated BAR
In the context of shared memory regions, they could not be present for
most of the virtio devices. For this reason, we prefer dedicate a BAR
for the shared memory regions.

Another reason is that memory regions, if there are several, can be
allocated all at once as a contiguous region, which then can be used as
its own BAR. It would be more complicated to try to allocate the BAR 0
holding the regular information about the virtio-pci device along with
the shared memory regions.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-08-13 13:57:53 +02:00
Sebastien Boeuf
e0fda0611c vm-virtio: Remove virtio-pci dependency from VirtioDevice
This patch cleans up the VirtioDevice trait. Since some function are PCI
specific and since they are not even used, it makes sense to remove them
from the trait definition.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-08-13 13:57:53 +02:00
Sebastien Boeuf
e2b38cc050 vm-virtio: Extend VirtioDevice trait to retrieve shared memory regions
Based on the newly added SharedMemoryConfig capability to the virtio
specification, and based on the fact that it is not tied to the type of
transport (pci or mmio), we can create as part of the VirtioDevice trait
a new method that will provide the shared memory regions associated with
the device.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-08-13 13:57:53 +02:00
Sebastien Boeuf
d97079d793 vm-virtio: Update VirtioPciCap and introduce VirtioPciCap64
Based on the latest version of the virtio specification, the structure
virtio_pci_cap has been updated and a new structure virtio_pci_cap64 has
been introduced.

virtio_pci_cap now includes a field "id" that does not modify the
existing structure size since there was a 3 bytes reserved field
already there. The id is used in the context of shared memory regions
which need to be identified since there could be more than one of this
kind of capability.

virtio_pci_cap64 is a new structure that includes virtio_pci_cap and
extends it to allow 64 bits offsets and 64 bits region length. This is
used in the context of shared memory regions capability, as we might need
to describe regions of 4G or more, that could be placed at a 4G offset
or more in the associated BAR.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-08-13 13:57:53 +02:00
Sebastien Boeuf
d180deb679 vm-virtio: pci: Fix PCI capability length
The length of the PCI capability as it is being calculated by the guest
was not accurate since it was not including the implicit 2 bytes offset.

The reason for this offset is that the structure itself does not contain
the capability ID (1 byte) and the next capability pointer (1 byte), but
the structure exposed through PCI config space does include those bytes.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-08-13 13:57:53 +02:00
Sebastien Boeuf
c6feb03dc0 vhost_rs: Allow MasterReqHandler to reply when needed
No matter if the communication is coming from the master or the slave,
it should always reply with an ack if the message header specifies that
it expects a reply.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-08-13 13:57:53 +02:00
Sebastien Boeuf
ef2e8b6bc2 tests: Update virtio-fs mount command
Because the way to mount virtio-fs filesystem changed with newest
kernel, we need to update the mount command in our integration tests.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-08-13 13:57:53 +02:00
Sebastien Boeuf
3645bf8d0f tests: Build virtiofsd from specific branch
In order to be able to use the latest version from virtiofsd binary, the
integration tests will now build it directly from a branch located on
sboeuf's QEMU fork. The same way the kernel is hosted on sboeuf's linux
kernel fork, this allows to update the version of the virtiofs daemon
based on latest patches from virtio-fs maintainers.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-08-13 13:57:53 +02:00
Sebastien Boeuf
021e8d9e13 tests: Move to new kernel 5.3-rc3
The last kernel we were using included some manual porting of the
virtio-pmem and virtio-fs patches. By moving to 5.3-rc3, we are now
closer to upstream since the virtio-pmem patches are part of the linux
kernel now. Additionally, this includes the latest patches from
virtio-fs maintainers, which works with the latest version of virtiofsd.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-08-13 13:57:53 +02:00
dependabot-preview[bot]
4b3fd906f7 build(deps): bump cc from 1.0.38 to 1.0.40
Bumps [cc](https://github.com/alexcrichton/cc-rs) from 1.0.38 to 1.0.40.
- [Release notes](https://github.com/alexcrichton/cc-rs/releases)
- [Commits](https://github.com/alexcrichton/cc-rs/compare/1.0.38...1.0.40)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-08-13 07:43:10 +00:00
dependabot-preview[bot]
bc5b72ff73 build(deps): bump libc from 0.2.60 to 0.2.61
Bumps [libc](https://github.com/rust-lang/libc) from 0.2.60 to 0.2.61.
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.60...0.2.61)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-08-13 07:21:11 +00:00
Rob Bradford
6c06420a11 vm-virtio: net: Fix out-of-range slice panic when under load
The numbr of bytes read was being incorrectly increased by the potential
length of the end of the sliced data rather than the number of bytes
that was in the range. This caused a panic when the the network was
under load by using iperf.

It's important to note that in the Firecracker code base the function
that read_slice() returns the number of bytes read which is used to
increment this counter. The VM memory version however only returns the
empty unit "()".

Fixes: #166

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-08-12 15:35:11 +01:00
dependabot-preview[bot]
b608671031 build(deps): bump syn from 0.15.43 to 0.15.44
Bumps [syn](https://github.com/dtolnay/syn) from 0.15.43 to 0.15.44.
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/0.15.43...0.15.44)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-08-12 11:07:32 +00:00
dependabot-preview[bot]
97c964891c build(deps): bump arc-swap from 0.4.1 to 0.4.2
Bumps [arc-swap](https://github.com/vorner/arc-swap) from 0.4.1 to 0.4.2.
- [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.1...v0.4.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-08-12 11:07:05 +00:00
fazlamehrab
df5058ec0a vm-virtio: Implement console size config feature
One of the features of the virtio console device is its size can be
configured and updated. Our first iteration of the console device
implementation is lack of this feature. As a result, it had a
default fixed size which could not be changed. This commit implements
the console config feature and lets us change the console size from
the vmm side.

During the activation of the device, vmm reads the current terminal
size, sets the console configuration accordinly, and lets the driver
know about this configuration by sending an interrupt. Later, if
someone changes the terminal size, the vmm detects the corresponding
event, updates the configuration, and sends interrupt as before. As a
result, the console device driver, in the guest, updates the console
size.

Signed-off-by: A K M Fazla Mehrab <fazla.mehrab.akm@intel.com>
2019-08-09 13:55:43 -07:00
Rob Bradford
d9a355f85a vmm: Add new "null" serial/console output mode
Poor performance was observed when booting kernels with "console=ttyS0"
and the serial port disabled.

This change introduces a "null" console output mode and makes it the
default for the serial console. In this case the serial port
is advertised as per other output modes but there is no input and any
output is dropped.

Fixes: #163

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-08-09 09:04:48 -07:00
Rob Bradford
f910476dd7 vmm: Only send stdin input to serial/console if it can handle it
Do not send the contents of stdin to the serial or console device if
they're not in tty mode.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-08-09 09:04:48 -07:00
Sebastien Boeuf
aa44726658 vm-virtio: Don't trigger an MSI-X interrupt if not enabled
Relying on the newly added MSI-X helper, the interrupt callback checks
the interrupts are enabled on the device before to try triggering the
interrupt.

Fixes #156

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-08-08 17:38:47 +01:00
Sebastien Boeuf
c0e2bbb23f pci: Add MSI-X helper to check if interrupts are enabled
In order to check if device's interrupts are enabled, this patch adds
a helper function to the MsixConfig structure so that at any point in
time we can check if an interrupt should be delivered or not.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-08-08 17:38:47 +01:00
dependabot-preview[bot]
4c9759e4fc build(deps): bump syn from 0.15.42 to 0.15.43
Bumps [syn](https://github.com/dtolnay/syn) from 0.15.42 to 0.15.43.
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/0.15.42...0.15.43)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-08-08 07:33:38 +00:00
dependabot-preview[bot]
b7ed71b012 build(deps): bump vmm-sys-util from 9014b7a to 54e256b
Bumps [vmm-sys-util](https://github.com/rust-vmm/vmm-sys-util) from `9014b7a` to `54e256b`.
- [Release notes](https://github.com/rust-vmm/vmm-sys-util/releases)
- [Commits](9014b7ab94...54e256b2cb)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-08-08 07:13:38 +00:00
dependabot-preview[bot]
8fcaf91d3b build(deps): bump redox_users from 0.3.0 to 0.3.1
Bumps redox_users from 0.3.0 to 0.3.1.

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-08-08 07:11:11 +00:00
Sebastien Boeuf
87195c9ccc pci: Fix vector control read/write from/to MSI-X table
The vector control offset is at the 4th byte of each MSI-X table entry.
For that reason, it is located at 0xc, and not 0x10 as implemented.

This commit fixes the current MSI-X code, allowing proper reading and
writing of each vector control register in the MSI-X table.

Fixes #156

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-08-08 08:10:18 +01:00
Rob Bradford
fca911e5f3 main: Add logging support controlled by command line
This makes the log macros (error!, warn!, info!, etc) in the code work.
It currently defaults to showing only error! messages, but by passing an
increasing number of "-v"s on the command line the verbosity can be
increased.

By default log output goes onto stderr but it can also be sent to a
file.

Fixes: #121

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-08-07 15:48:17 +01:00
Rob Bradford
91ce39e2a6 tests: Ensure that the test pipeline fails
With the addition of commands after running the integration tests the
exit code of the tests were lost.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-08-07 06:49:42 -07:00
dependabot-preview[bot]
6cc3e88742 build(deps): bump getrandom from 0.1.7 to 0.1.8
Bumps [getrandom](https://github.com/rust-random/getrandom) from 0.1.7 to 0.1.8.
- [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.7...v0.1.8)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-08-06 07:22:10 +00:00
dependabot-preview[bot]
d1cd3c89c6 build(deps): bump vmm-sys-util from c0bbae5 to 9014b7a
Bumps [vmm-sys-util](https://github.com/rust-vmm/vmm-sys-util) from `c0bbae5` to `9014b7a`.
- [Release notes](https://github.com/rust-vmm/vmm-sys-util/releases)
- [Commits](c0bbae555d...9014b7ab94)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-08-06 07:21:55 +00:00
dependabot-preview[bot]
91c7f271b3 build(deps): bump utf8-ranges from 1.0.3 to 1.0.4
Bumps [utf8-ranges](https://github.com/BurntSushi/utf8-ranges) from 1.0.3 to 1.0.4.
- [Release notes](https://github.com/BurntSushi/utf8-ranges/releases)
- [Commits](https://github.com/BurntSushi/utf8-ranges/compare/1.0.3...1.0.4)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-08-05 10:45:55 +00:00
dependabot-preview[bot]
87f148cbff build(deps): bump regex-syntax from 0.6.10 to 0.6.11
Bumps [regex-syntax](https://github.com/rust-lang/regex) from 0.6.10 to 0.6.11.
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/regex-syntax-0.6.10...regex-syntax-0.6.11)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-08-05 10:32:31 +00:00
dependabot-preview[bot]
778c60f21d build(deps): bump vmm-sys-util from 71b5b25 to c0bbae5
Bumps [vmm-sys-util](https://github.com/rust-vmm/vmm-sys-util) from `71b5b25` to `c0bbae5`.
- [Release notes](https://github.com/rust-vmm/vmm-sys-util/releases)
- [Commits](71b5b25dd5...c0bbae555d)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-08-05 10:31:17 +00:00
Sebastien Boeuf
846505d360 pci: Fix add_capability unit test
The structure provided to the add_capability() function should only
contain what comes after the capability ID and the next capability
pointer, which are located on the first WORD.

Because the structure TestCap included _vndr and _next fields, they
were directly set after the first WORD, while the assertion was
expecting to find the values of len and foo fields.

Fixes #105

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-08-03 08:43:44 +01:00
Rob Bradford
9caad7394d build, misc: Bump vmm-sys-util dependency
The structure of the vmm-sys-util crate has changed with lots of code
moving to submodules.

This change adjusts the use of the imported structs to reference the
submodules.

Fixes: #145

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-08-02 07:42:20 -07:00
Rob Bradford
ac950d9a97 build: Bulk update dependencies
Update all dependencies with "cargo upgrade" with the exception of
vmm-sys-utils which needs some extra porting work.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-08-02 15:22:37 +02:00
Dylan Reid
a1f408a247 qcow: bounds check the refcount table offset and size
If the header puts the refcount table outside the file size or if it
specifies a table much larger than needed, fail to open the file.

These might not be hard qcow errors, but they are situations that crosvm
will never encounter.

BUG=986061
TEST=fuzzer with new test cases completes in less than 5 seconds.

Signed-off-by: Dylan Reid <dgreid@chromium.org>
Change-Id: If048c96f6255ca81740e20f3f4eb7669467dbb7b
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/1716365
Reviewed-by: Daniel Verkamp <dverkamp@chromium.org>
Tested-by: kokoro <noreply+kokoro@google.com>
(cherry picked from crosvm commit 969a0b49ff0a9afbca18230181542bbe7e06b8f7)
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-08-02 12:58:04 +02:00
Dylan Reid
dfd44a6080 qcow: Add a zero_cluster method to raw file
Zeroing a cluster will be done from more than one place in qcow.rs soon,
add a helper to reduce duplication.

Change-Id: Idb40539f8e4ed2338fc84c0d53b37c913f2d90fe
Signed-off-by: Dylan Reid <dgreid@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/1697122
Reviewed-by: Daniel Verkamp <dverkamp@chromium.org>
Tested-by: kokoro <noreply+kokoro@google.com>
(cherry picked from crosvm commit 13c219139504d0a191948fd205c835a2505cadba)
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-08-02 12:58:04 +02:00
Dylan Reid
7d6bf75138 qcow: limit the size of a qcow file
There are many corner cases when handling sizes that approach u64::max.
Limit the files to 16TB.

BUG=979458
TEST=Added unittest to check large disks fail

Signed-off-by: Dylan Reid <dgreid@chromium.org>
Change-Id: I93a87c17267ae69102f8d46ced9dbea8c686d093
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/1679892
Reviewed-by: Daniel Verkamp <dverkamp@chromium.org>
Tested-by: kokoro <noreply+kokoro@google.com>
(cherry picked from crosvm commit 93b0c02227f3acf2c6ff127976875cf3ce98c003)
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-08-02 12:58:04 +02:00
Dylan Reid
20f8d8d700 qcow: Avoid overflow when taking ceiling of division
The extra % operation will be slower, but none of these divisions are in
hot paths. They are only used during setup. Many of these operations
take untrusted input from the disk file, so need to be hardened.

BUG=979458
TEST=unit tests still pass

Signed-off-by: Dylan Reid <dgreid@chromium.org>
Change-Id: I0e93c73b345faf643da53ea41bde3349d756bdc7
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/1679891
Reviewed-by: Daniel Verkamp <dverkamp@chromium.org>
Tested-by: kokoro <noreply+kokoro@google.com>
(cherry picked from crosvm commit eecbccc4d9d70b2fd63681a2b3ced6a6aafe81bb)
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-08-02 12:58:04 +02:00
Dylan Reid
4ba1d2274e qcow: disallow crazy l1 table sizes
Before this change, a corrupt or malicious qcow file could cause crosvm
to allocate absurd amounts of memory. The fuzzer found this case,
limit the L1 table size so it can't cause issues.

BUG=chromium:974123
TEST=run fuzzer locally, add unit test

Change-Id: Ieb6db6c87f71df726b3cc9a98404581fe32fb1ce
Signed-off-by: Dylan Reid <dgreid@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/1660890
Reviewed-by: Daniel Verkamp <dverkamp@chromium.org>
Tested-by: kokoro <noreply+kokoro@google.com>
(cherry picked from crosvm commit 70d7bad28414e4b0d8bdf2d5eb85618a3b1e83c6)
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-08-02 12:58:04 +02:00
Dylan Reid
bd612b6e53 qcow: Fix invalid_cluster_bits test
Start with a valid header so the invalid cluster bits field is tested in
isolation. Before this change the test would pass even if the cluster
bits check was removed from the code because the header was invalid for
other reasons.

BUG=none
TEST=this is a test

Change-Id: I5c09417ae3f974522652a50cb0fdc5dc0e10dd44
Signed-off-by: Dylan Reid <dgreid@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/1660889
Reviewed-by: Daniel Verkamp <dverkamp@chromium.org>
Tested-by: kokoro <noreply+kokoro@google.com>
(cherry picked from crosvm commit c9f254b1921335231b32550b5ae6b8416e1ca7aa)
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-08-02 12:58:04 +02:00
Dylan Reid
b713737f81 qcow: Limit file setups that consume excessive RAM
qcow currently makes assumptions about being able to fit the L1 and
refcount tables in memory. This isn't necessarily true for very large
files. The new limits to the size of in-memory buffers still allow 1TB
files with default cluster sizes. Because there aren't any 1 TB
chromebooks available yet, limit disks to that size.

This works around issues found by clusterfuzz related to large files
built with small clusters.

BUG=972165,972175,972160,972172
TEST=fuzzer locally + new unit tests

Change-Id: I15d5d8e3e61213780ff6aea5759b155c63d49ea3
Signed-off-by: Dylan Reid <dgreid@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/1651460
Reviewed-by: Zach Reizner <zachr@chromium.org>
Reviewed-by: Daniel Verkamp <dverkamp@chromium.org>
Tested-by: kokoro <noreply+kokoro@google.com>
(cherry picked from crosvm commit a094f91d2cc96e9eeb0681deb81c37e9a85e7a55)
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-08-02 12:58:04 +02:00
Dylan Reid
35a3b47554 qcow: Calculate the max refcounts as a u64
u32's get multiplied together and can overflow. A usize was being
returned, make everything a u64 to make sure it fits.

Change-Id: I87071d294f4e62247c9ae72244db059a7b528b62
Signed-off-by: Dylan Reid <dgreid@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/1651459
Reviewed-by: Daniel Verkamp <dverkamp@chromium.org>
Reviewed-by: Zach Reizner <zachr@chromium.org>
Tested-by: kokoro <noreply+kokoro@google.com>
(cherry picked from crosvm commit 21c941786ea0cb72114f3e9c7c940471664862b5)
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-08-02 12:58:04 +02:00
Dylan Reid
f927d1a2d7 qcow: better limits on cluster size
Add a lower limit because cases such as eight byte clusters aren't
practical and aren't worth handling, tracking a cluster costs 16 bytes.

Also put an upper limit on the cluster size, choose 21 bits to match
qemu.

Change-Id: Ifcab081d0e630b5d26b0eafa552bd7c695821686
Signed-off-by: Dylan Reid <dgreid@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/1651458
Reviewed-by: Zach Reizner <zachr@chromium.org>
Reviewed-by: Daniel Verkamp <dverkamp@chromium.org>
Tested-by: kokoro <noreply+kokoro@google.com>
(cherry picked from crosvm commit cae80e321acdccb1591124f6bf657758f1e75d1d)
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-08-02 12:58:04 +02:00
dependabot-preview[bot]
0c9547618a build(deps): bump vm-memory from 4c329f4 to 1635f25
Bumps [vm-memory](https://github.com/rust-vmm/vm-memory) from `4c329f4` to `1635f25`.
- [Release notes](https://github.com/rust-vmm/vm-memory/releases)
- [Commits](4c329f4e76...1635f25afc)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-08-02 09:59:46 +00:00
dependabot-preview[bot]
6abd50f4b1 build(deps): bump clap from 2.27.1 to 2.33.0
Bumps [clap](https://github.com/clap-rs/clap) from 2.27.1 to 2.33.0.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-08-02 08:35:38 +00:00
dependabot-preview[bot]
c7f8498571 build(deps): bump log from 0.4.6 to 0.4.8
Bumps [log](https://github.com/rust-lang/log) from 0.4.6 to 0.4.8.
- [Release notes](https://github.com/rust-lang/log/releases)
- [Changelog](https://github.com/rust-lang-nursery/log/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/log/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-08-02 08:22:44 +00:00
dependabot-preview[bot]
8a7cfe8ec4 build(deps): bump dirs from 2.0.1 to 2.0.2
Bumps [dirs](https://github.com/soc/dirs-rs) from 2.0.1 to 2.0.2.
- [Release notes](https://github.com/soc/dirs-rs/releases)
- [Commits](https://github.com/soc/dirs-rs/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-08-02 08:21:19 +00:00
Sebastien Boeuf
49ef201cd1 vfio: pci: Provide the right MSI-X table offset
When reading from or writing to the MSI-X table, the function provided
by the PCI crate expects the offset to start from the beginning of the
table. That's why it is VFIO specific code to be responsible for
providing the right offset, which means it needs to be the offset
substracted by the beginning of the MSI-X table offset.

This bug was not discovered until we tested VFIO with some device where
the MSI-X table was placed on a BAR at an offset different from 0x0.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-08-02 09:45:20 +02:00
Sebastien Boeuf
a548a01423 pci: Fix MSI-X table and PBA offsets
The offsets returned by the table_offset() and pba_offset() function
were wrong as they were shifting the value by 3 bits. The MSI-X spec
defines the MSI-X table and PBA offsets as being defined on 3-31 bits,
but this does not mean it has to be shifted. Instead, the address is
still on 32 bits and assumes the LSB bits 0-2 are set to 0.

VFIO was working fine with devices were the MSI-X offset were 0x0, but
the bug was found on a device where the offset was non-null.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-08-02 09:45:20 +02:00
Sebastien Boeuf
baec27698e vm-virtio: Don't break from epoll loop on EINTR
The existing code taking care of the epoll loop was too restrictive as
it was propagating the error returned from the epoll_wait() syscall, no
matter what was the error. This causes the epoll loop to be broken,
leading to a non-functional virtio device.

This patch enforces the parsing of the returned error and prevent from
the error propagation in case it is EINTR, which stands for Interrupted.
In case the epoll loop is interrupted, it is appropriate to retry.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-08-02 08:37:34 +01:00
Sebastien Boeuf
1a484a82f9 vmm: Don't break from epoll loop on EINTR
The existing code taking care of the epoll loop was too restrictive as
it was propagating the error returned from the epoll_wait() syscall, no
matter what was the error. This causes the epoll loop to be broken,
leading to the VM termination.

This patch enforces the parsing of the returned error and prevent from
the error propagation in case it is EINTR, which stands for Interrupted.
In case the epoll loop is interrupted, it is appropriate to retry.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-08-02 08:37:34 +01:00
Sebastien Boeuf
532f6a96f3 vmm: Factorize VM related information into a structure
In order to fix the clippy error complaining about the number of
arguments passed to a function exceeding the maximum of 7 arguments,
this patch factorizes those parameters into a more global one called
VmInfo.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-08-02 08:35:16 +01:00
Sebastien Boeuf
c0756c429d vmm: Increase memory slot from virtio-pmem
Since virtio-pmem uses a KVM user memory region, it needs to increment
the slot index in use to prevent from any conflict with further VFIO
allocations (used for mapping mappable memory BARs).

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-08-02 08:35:16 +01:00
Sebastien Boeuf
8c4c162109 arch: x86_64: Set MTRR default memory type as WB
In the context of VFIO, we use Vt-d, which means we rely on an IOMMU.
Depending on the IOMMU capability, and in particular if it is not able
to perform SC (Snooping Control), the memory will not be tagged as WB
by KVM, but instead the vCPU will rely on its MTRR/PAT MSRs to find the
appropriate way of interact with specific memory regions.

Because when Vt-d is not involved KVM sets the memory as WB (write-back)
the VMM should set the memory default as WB. That's why this patch sets
the MSR MTRRdefType with the default memory type being WB.

One thing that it is worth noting is that we might have to specifically
create some UC (uncacheable) regions if we see some issues with the
ranges corresponding to the MMIO ranges that should trap.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-08-01 20:14:46 +02:00
Rob Bradford
d52684450f tests: Add Ubuntu Bionic version of test_simple_launch
Introduce a new DiskConfig implementation for Ubuntu Bionic with
different cloud init preparation details and use this when testing with
test_simple_launch.

Adjust the memory expectation for downwards as the EFI boot results in a
slightly different memory map. Also enable serial port as Ubuntu does
not support a virtio-console based boot.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-08-01 14:37:04 +02:00
Rob Bradford
facc3b303a tests: Add Bionic to integration test script
Download Bionic image and convert to raw (the QCOW version of the file
doesn't currently work) for running the integration tests.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-08-01 14:37:04 +02:00
Rob Bradford
09aced9ed1 tests: Use logical name for disk paths
Rather than embed the disks in a vector and have integer indicies into
the vector for the different disks instead abstract this through an enum
type used on the DiskConfig trait.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-08-01 14:37:04 +02:00
Rob Bradford
56c4b7000a tests: Refactor integration tests to support different distributions
Extract the network configuration into its own struct and also extract
the prepare_files() and prepare_cloudinit() functions into a struct for
the Clear Linux distribution.

This struct is behind a trait that is used by the Guest implementation
to prepare the files.

This will allow a different implementation to be used for the Ubuntu
disk files.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-08-01 14:37:04 +02:00
Sebastien Boeuf
d18c8d4c8c vfio: pci: Add support for expansion ROM BAR
Relying on the newly added code in the pci crate, the vfio crate can now
properly expose an expansion ROM BAR if the device has one.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-07-31 09:28:29 +02:00
Sebastien Boeuf
d217089b54 pci: Add support for expansion ROM BAR
The expansion ROM BAR can be considered like a 32-bit memory BAR with a
slight difference regarding the amount of reserved bits at the beginning
of its 32-bit value. Bit 0 indicates if the BAR is enabled or disabled,
while bits 1-10 are reserved. The remaining upper 21 bits hold the BAR
address.

This commit extends the pci crate in order to support expansion ROM BAR.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-07-31 09:28:29 +02:00
Sebastien Boeuf
347f8a036b vfio: pci: Mask multi function device bit
In order to support VFIO for devices supporting multiple functions,
we need to mask the multi-function bit (bit 7 from Header Type byte).
Otherwise, the guest kernel ends up tryng to enumerate those devices.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-07-31 09:28:29 +02:00
Sebastien Boeuf
b6ae2ccda4 pci: Disable multiple functions
Every PCI device is exposed as a separate device, on a specific PCI
slot, and we explicitely don't support to expose a device as a multi
function device. For this reason, this patch makes sure the enumeration
of the PCI bus will not find some multi function device.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-07-31 09:28:29 +02:00
Rob Bradford
f86b9dd95e scripts: Add Ubuntu cloud-init data
Add cloud-init data for Ubuntu and introduce a convenience script that
can be used to generate cloud-init disk images for manual testing.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-07-30 14:26:26 +02:00
Rob Bradford
be199e5560 tests: Move Clear Linux cloud-init files to subdirectory
And rename the default user from "admin" to "cloud" as the admin user
clashes with a standard user and group name on Ubuntu.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2019-07-30 14:26:26 +02:00
Sebastien Boeuf
98d7955e34 vm-virtio: Add support for notifying about virtio config update
As per the VIRTIO specification, every virtio device configuration can
be updated while the guest is running. The guest needs to be notified
when this happens, and it can be done in two different ways, depending
on the type of interrupt being used for those devices.

In case the device uses INTx, the allocated IRQ pin is shared between
queues and configuration updates. The way for the guest to differentiate
between an interrupt meant for a virtqueue or meant for a configuration
update is tied to the value of the ISR status field. This field is a
simple 32 bits bitmask where only bit 0 and 1 can be changed, the rest
is reserved.

In case the device uses MSI/MSI-X, the driver should allocate a
dedicated vector for configuration updates. This case is much simpler as
it only requires the device to send the appropriate MSI vector.

The cloud-hypervisor codebase was not supporting the update of a virtio
device configuration. This patch extends the existing VirtioInterrupt
closure to accept a type that can be Config or Queue, so that based on
this type, the closure implementation can make the right choice about
which interrupt pin or vector to trigger.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-07-29 15:34:37 +01:00
80 changed files with 5319 additions and 1724 deletions

View File

@@ -9,9 +9,10 @@ before_script:
script:
- cargo build --release
- cargo build --release --no-default-features
- cargo test
- cargo clippy --all-targets --all-features -- -D warnings
- find . -name "*.rs" | grep -v "vendor/" | xargs rustfmt --check
- find . -name "*.rs" | xargs rustfmt --check
deploy:
provider: releases

596
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -5,15 +5,17 @@ authors = ["The Cloud Hypervisor Authors"]
edition = "2018"
[dependencies]
clap = "=2.27.1"
clap = "2.33.0"
lazy_static = "1.4.0"
log = { version = "0.4.8", features = ["std"] }
vmm = { path = "vmm" }
[dev-dependencies]
ssh2 = "=0.3.3"
dirs = "2.0.0"
ssh2 = "0.3.3"
dirs = "2.0.2"
credibility = "0.1.3"
tempdir="0.3.7"
lazy_static=">=1.1.0"
tempdir= "0.3.7"
lazy_static= "1.4.0"
[features]
default = []

5
Jenkinsfile vendored
View File

@@ -5,17 +5,16 @@ 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"
sh "sudo apt-get install -yq flex bison libelf-dev qemu-utils qemu-system libglib2.0-dev libpixman-1-dev libseccomp-dev"
}
stage ('Install Rust') {
sh "nohup curl https://sh.rustup.rs -sSf | sh -s -- -y"
}
stage ('Run unit tests') {
sh "sudo chmod a+rw /dev/kvm"
sh "scripts/run_unit_tests.sh"
}
stage ('Run integration tests') {
sh "sudo mount -t tmpfs tmpfs /tmp"
sh "sudo mount -t tmpfs tmpfs /tmp"
sh "scripts/run_integration_tests.sh"
}
}

9
acpi_tables/Cargo.toml Normal file
View File

@@ -0,0 +1,9 @@
[package]
name = "acpi_tables"
version = "0.1.0"
authors = ["The Cloud Hypervisor Authors"]
edition = "2018"
[dependencies]
vm-memory = { git = "https://github.com/rust-vmm/vm-memory" }

11
acpi_tables/src/lib.rs Normal file
View File

@@ -0,0 +1,11 @@
// Copyright © 2019 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
pub mod rsdp;
pub mod sdt;
fn generate_checksum(data: &[u8]) -> u8 {
(255 - data.iter().fold(0u8, |acc, x| acc.wrapping_add(*x))).wrapping_add(1)
}

67
acpi_tables/src/rsdp.rs Normal file
View File

@@ -0,0 +1,67 @@
// Copyright © 2019 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
use vm_memory::ByteValued;
#[repr(packed)]
#[derive(Clone, Copy, Default)]
pub struct RSDP {
pub signature: [u8; 8],
pub checksum: u8,
pub oem_id: [u8; 6],
pub revision: u8,
_rsdt_addr: u32,
pub length: u32,
pub xsdt_addr: u64,
pub extended_checksum: u8,
_reserved: [u8; 3],
}
unsafe impl ByteValued for RSDP {}
impl RSDP {
pub fn new(oem_id: [u8; 6], xsdt_addr: u64) -> Self {
let mut rsdp = RSDP {
signature: *b"RSD PTR ",
checksum: 0,
oem_id,
revision: 2,
_rsdt_addr: 0,
length: std::mem::size_of::<RSDP>() as u32,
xsdt_addr,
extended_checksum: 0,
_reserved: [0; 3],
};
rsdp.checksum = super::generate_checksum(&rsdp.as_slice()[0..19]);
rsdp.extended_checksum = super::generate_checksum(&rsdp.as_slice());;
rsdp
}
pub fn len() -> usize {
std::mem::size_of::<RSDP>()
}
}
#[cfg(test)]
mod tests {
use super::RSDP;
use vm_memory::bytes::ByteValued;
#[test]
fn test_rsdp() {
let rsdp = RSDP::new(*b"CHYPER", 0xdead_beef);
let sum = rsdp
.as_slice()
.iter()
.fold(0u8, |acc, x| acc.wrapping_add(*x));
assert_eq!(sum, 0);
let sum: u8 = rsdp
.as_slice()
.iter()
.fold(0u8, |acc, x| acc.wrapping_add(*x));
assert_eq!(sum, 0);
}
}

129
acpi_tables/src/sdt.rs Normal file
View File

@@ -0,0 +1,129 @@
// Copyright © 2019 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
#[repr(packed)]
pub struct GenericAddress {
pub address_space_id: u8,
pub register_bit_width: u8,
pub register_bit_offset: u8,
pub access_size: u8,
pub address: u64,
}
impl GenericAddress {
pub fn io_port_address(address: u16) -> Self {
GenericAddress {
address_space_id: 1,
register_bit_width: 8,
register_bit_offset: 0,
access_size: 1,
address: u64::from(address),
}
}
}
pub struct SDT {
data: Vec<u8>,
}
#[allow(clippy::len_without_is_empty)]
impl SDT {
pub fn new(
signature: [u8; 4],
length: u32,
revision: u8,
oem_id: [u8; 6],
oem_table: [u8; 8],
oem_revision: u32,
) -> Self {
assert!(length >= 36);
let mut data = Vec::with_capacity(length as usize);
data.extend_from_slice(&signature);
data.extend_from_slice(&length.to_le_bytes());
data.push(revision);
data.push(0); // checksum
data.extend_from_slice(&oem_id);
data.extend_from_slice(&oem_table);
data.extend_from_slice(&oem_revision.to_le_bytes());
data.extend_from_slice(b"CLDH");
data.extend_from_slice(&0u32.to_le_bytes());
assert_eq!(data.len(), 36);
data.resize(length as usize, 0);
let mut sdt = SDT { data };
sdt.update_checksum();
sdt
}
pub fn update_checksum(&mut self) {
self.data[9] = 0;
let checksum = super::generate_checksum(self.data.as_slice());
self.data[9] = checksum
}
pub fn as_slice(&self) -> &[u8] {
&self.data.as_slice()
}
pub fn append<T>(&mut self, value: T) {
let orig_length = self.data.len();
let new_length = orig_length + std::mem::size_of::<T>();
self.data.resize(new_length, 0);
self.write_u32(4, new_length as u32);
self.write(orig_length, value);
}
/// Write a value at the given offset
pub fn write<T>(&mut self, offset: usize, value: T) {
assert!((offset + (std::mem::size_of::<T>() - 1)) < self.data.len());
unsafe {
*(((self.data.as_mut_ptr() as usize) + offset) as *mut T) = value;
}
self.update_checksum();
}
pub fn write_u8(&mut self, offset: usize, val: u8) {
self.write(offset, val);
}
pub fn write_u16(&mut self, offset: usize, val: u16) {
self.write(offset, val);
}
pub fn write_u32(&mut self, offset: usize, val: u32) {
self.write(offset, val);
}
pub fn write_u64(&mut self, offset: usize, val: u64) {
self.write(offset, val);
}
pub fn len(&self) -> usize {
self.data.len()
}
}
#[cfg(test)]
mod tests {
use super::SDT;
#[test]
fn test_sdt() {
let mut sdt = SDT::new(*b"TEST", 40, 1, *b"CLOUDH", *b"TESTTEST", 1);
let sum: u8 = sdt
.as_slice()
.iter()
.fold(0u8, |acc, x| acc.wrapping_add(*x));
assert_eq!(sum, 0);
sdt.write_u32(36, 0x12345678);
let sum: u8 = sdt
.as_slice()
.iter()
.fold(0u8, |acc, x| acc.wrapping_add(*x));
assert_eq!(sum, 0);
}
}

View File

@@ -3,12 +3,17 @@ name = "arch"
version = "0.1.0"
authors = ["The Chromium OS Authors"]
[dependencies]
byteorder = "=1.2.1"
kvm-bindings = "0.1"
kvm-ioctls = { git = "https://github.com/rust-vmm/kvm-ioctls", branch = "master" }
libc = ">=0.2.39"
[features]
default = ["acpi"]
acpi = ["acpi_tables"]
[dependencies]
byteorder = "1.3.2"
kvm-bindings = "0.1.1"
kvm-ioctls = { git = "https://github.com/rust-vmm/kvm-ioctls", branch = "master" }
libc = "0.2.60"
acpi_tables = { path = "../acpi_tables", optional = true }
arch_gen = { path = "../arch_gen" }
[dependencies.vm-memory]
@@ -20,4 +25,4 @@ git = "https://github.com/rust-vmm/linux-loader"
features = ["elf", "bzimage"]
[dev-dependencies]
rand = ">=0.5.5"
rand = "0.7.0"

View File

@@ -16,6 +16,7 @@ pub fn configure_system(
_cmdline_addr: GuestAddress,
_cmdline_size: usize,
_num_cpus: u8,
_serial_enabled: bool,
) -> super::Result<()> {
Ok(())
}

View File

@@ -3,7 +3,7 @@
#![allow(
clippy::unreadable_literal,
clippy::const_static_lifetime,
clippy::redundant_static_lifetimes,
clippy::cast_lossless,
clippy::transmute_ptr_to_ptr,
clippy::cast_ptr_alignment
@@ -13,6 +13,7 @@ extern crate byteorder;
extern crate kvm_bindings;
extern crate libc;
extern crate acpi_tables;
extern crate arch_gen;
extern crate kvm_ioctls;
extern crate linux_loader;

315
arch/src/x86_64/acpi.rs Normal file
View File

@@ -0,0 +1,315 @@
// Copyright © 2019 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
use acpi_tables::{
rsdp::RSDP,
sdt::{GenericAddress, SDT},
};
use vm_memory::{GuestAddress, GuestMemoryMmap};
use vm_memory::{Address, ByteValued, Bytes};
#[repr(packed)]
struct LocalAPIC {
pub r#type: u8,
pub length: u8,
pub processor_id: u8,
pub apic_id: u8,
pub flags: u32,
}
#[repr(packed)]
#[derive(Default)]
struct IOAPIC {
pub r#type: u8,
pub length: u8,
pub ioapic_id: u8,
_reserved: u8,
pub apic_address: u32,
pub gsi_base: u32,
}
#[repr(packed)]
#[derive(Default)]
struct InterruptSourceOverride {
pub r#type: u8,
pub length: u8,
pub bus: u8,
pub source: u8,
pub gsi: u32,
pub flags: u16,
}
#[repr(packed)]
#[derive(Default)]
struct PCIRangeEntry {
pub base_address: u64,
pub segment: u16,
pub start: u8,
pub end: u8,
_reserved: u32,
}
pub fn create_dsdt_table(serial_enabled: bool) -> 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
should be disregarded.
*/
/*
Device (_SB.PCI0)
{
Name (_HID, EisaId ("PNP0A08") /* PCI Express Bus */) // _HID: Hardware ID
Name (_CID, EisaId ("PNP0A03") /* PCI Bus */) // _CID: Compatible ID
Name (_ADR, Zero) // _ADR: Address
Name (_SEG, Zero) // _SEG: PCI Segment
Name (_UID, Zero) // _UID: Unique ID
Name (SUPP, Zero)
}
Scope (_SB.PCI0)
{
Name (_CRS, ResourceTemplate () // _CRS: Current Resource Settings
{
WordBusNumber (ResourceProducer, MinFixed, MaxFixed, PosDecode,
0x0000, // Granularity
0x0000, // Range Minimum
0x00FF, // Range Maximum
0x0000, // Translation Offset
0x0100, // Length
,, )
IO (Decode16,
0x0CF8, // Range Minimum
0x0CF8, // Range Maximum
0x01, // Alignment
0x08, // Length
)
WordIO (ResourceProducer, MinFixed, MaxFixed, PosDecode, EntireRange,
0x0000, // Granularity
0x0000, // Range Minimum
0x0CF7, // Range Maximum
0x0000, // Translation Offset
0x0CF8, // Length
,, , TypeStatic, DenseTranslation)
WordIO (ResourceProducer, MinFixed, MaxFixed, PosDecode, EntireRange,
0x0000, // Granularity
0x0D00, // Range Minimum
0xFFFF, // Range Maximum
0x0000, // Translation Offset
0xF300, // Length
,, , TypeStatic, DenseTranslation)
DWordMemory (ResourceProducer, PosDecode, MinFixed, MaxFixed, Cacheable, ReadWrite,
0x00000000, // Granularity
0x000A0000, // Range Minimum
0x000BFFFF, // Range Maximum
0x00000000, // Translation Offset
0x00020000, // Length
,, , AddressRangeMemory, TypeStatic)
DWordMemory (ResourceProducer, PosDecode, MinFixed, MaxFixed, NonCacheable, ReadWrite,
0x00000000, // Granularity
0xC0000000, // Range Minimum
0xFEC00000, // Range Maximum
0x00000000, // Translation Offset
0x3EC00001, // Length
,, , AddressRangeMemory, TypeStatic)
QWordMemory (ResourceProducer, PosDecode, MinFixed, MaxFixed, Cacheable, ReadWrite,
0x0000000000000000, // Granularity
0x0000000800000000, // Range Minimum
0x0000000FFFFFFFFF, // Range Maximum
0x0000000000000000, // Translation Offset
0x0000000800000000, // Length
,, , AddressRangeMemory, TypeStatic)
})
}
*/
let pci_dsdt_data = [
0x5Bu8, 0x82, 0x36, 0x2E, 0x5F, 0x53, 0x42, 0x5F, 0x50, 0x43, 0x49, 0x30, 0x08, 0x5F, 0x48,
0x49, 0x44, 0x0C, 0x41, 0xD0, 0x0A, 0x08, 0x08, 0x5F, 0x43, 0x49, 0x44, 0x0C, 0x41, 0xD0,
0x0A, 0x03, 0x08, 0x5F, 0x41, 0x44, 0x52, 0x00, 0x08, 0x5F, 0x53, 0x45, 0x47, 0x00, 0x08,
0x5F, 0x55, 0x49, 0x44, 0x00, 0x08, 0x53, 0x55, 0x50, 0x50, 0x00, 0x10, 0x41, 0x0B, 0x2E,
0x5F, 0x53, 0x42, 0x5F, 0x50, 0x43, 0x49, 0x30, 0x08, 0x5F, 0x43, 0x52, 0x53, 0x11, 0x40,
0x0A, 0x0A, 0x9C, 0x88, 0x0D, 0x00, 0x02, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00,
0x00, 0x00, 0x00, 0x01, 0x47, 0x01, 0xF8, 0x0C, 0xF8, 0x0C, 0x01, 0x08, 0x88, 0x0D, 0x00,
0x01, 0x0C, 0x03, 0x00, 0x00, 0x00, 0x00, 0xF7, 0x0C, 0x00, 0x00, 0xF8, 0x0C, 0x88, 0x0D,
0x00, 0x01, 0x0C, 0x03, 0x00, 0x00, 0x00, 0x0D, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xF3, 0x87,
0x17, 0x00, 0x00, 0x0C, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0xFF, 0xFF,
0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x87, 0x17, 0x00, 0x00, 0x0C,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0xC0, 0xFE, 0x00, 0x00,
0x00, 0x00, 0x01, 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,
];
/*
Device (_SB.COM1)
{
Name (_HID, EisaId ("PNP0501") /* 16550A-compatible COM Serial Port */) // _HID: Hardware ID
Name (_UID, Zero) // _UID: Unique ID
Name (_CRS, ResourceTemplate () // _CRS: Current Resource Settings
{
Interrupt (ResourceConsumer, Edge, ActiveHigh, Exclusive, ,, )
{
0x00000004,
}
IO (Decode16,
0x03F8, // Range Minimum
0x03F8, // Range Maximum
0x00, // Alignment
0x08, // Length
)
})
}
*/
let com1_dsdt_data = [
0x5Bu8, 0x82, 0x36, 0x2E, 0x5F, 0x53, 0x42, 0x5F, 0x43, 0x4F, 0x4D, 0x31, 0x08, 0x5F, 0x48,
0x49, 0x44, 0x0C, 0x41, 0xD0, 0x05, 0x01, 0x08, 0x5F, 0x55, 0x49, 0x44, 0x00, 0x08, 0x5F,
0x43, 0x52, 0x53, 0x11, 0x16, 0x0A, 0x13, 0x89, 0x06, 0x00, 0x03, 0x01, 0x04, 0x00, 0x00,
0x00, 0x47, 0x01, 0xF8, 0x03, 0xF8, 0x03, 0x00, 0x08, 0x79, 0x00,
];
/*
Name (\_S5, Package (0x01) // _S5_: S5 System State
{
0x05
})
*/
let s5_sleep_data = [0x08u8, 0x5F, 0x53, 0x35, 0x5F, 0x12, 0x04, 0x01, 0x0A, 0x05];
// DSDT
let mut dsdt = SDT::new(*b"DSDT", 36, 6, *b"CLOUDH", *b"CHDSDT ", 1);
dsdt.append(pci_dsdt_data);
if serial_enabled {
dsdt.append(com1_dsdt_data);
}
dsdt.append(s5_sleep_data);
dsdt
}
pub fn create_acpi_tables(
guest_mem: &GuestMemoryMmap,
num_cpus: u8,
serial_enabled: bool,
) -> GuestAddress {
// RSDP is at the EBDA
let rsdp_offset = super::EBDA_START;
let mut tables: Vec<u64> = Vec::new();
// DSDT
let dsdt = create_dsdt_table(serial_enabled);
let dsdt_offset = rsdp_offset.checked_add(RSDP::len() as u64).unwrap();
guest_mem
.write_slice(dsdt.as_slice(), dsdt_offset)
.expect("Error writing DSDT table");
// FACP aka FADT
// Revision 6 of the ACPI FADT table is 276 bytes long
let mut facp = SDT::new(*b"FACP", 276, 6, *b"CLOUDH", *b"CHFACP ", 1);
// HW_REDUCED_ACPI and RESET_REG_SUP
let fadt_flags: u32 = 1 << 20 | 1 << 10;
facp.write(112, fadt_flags);
// RESET_REG
facp.write(116, GenericAddress::io_port_address(0x3c0));
// RESET_VALUE
facp.write(128, 1u8);
facp.write(131, 3u8); // FADT minor version
facp.write(140, dsdt_offset.0); // X_DSDT
// SLEEP_CONTROL_REG
facp.write(244, GenericAddress::io_port_address(0x3c0));
// SLEEP_STATUS_REG
facp.write(256, GenericAddress::io_port_address(0x3c0));
facp.write(268, b"CLOUDHYP"); // Hypervisor Vendor Identity
facp.update_checksum();
let facp_offset = dsdt_offset.checked_add(dsdt.len() as u64).unwrap();
guest_mem
.write_slice(facp.as_slice(), facp_offset)
.expect("Error writing FACP table");
tables.push(facp_offset.0);
// MADT
let mut madt = SDT::new(*b"APIC", 44, 5, *b"CLOUDH", *b"CHMADT ", 1);
madt.write(36, super::mptable::APIC_DEFAULT_PHYS_BASE);
for cpu in 0..num_cpus {
let lapic = LocalAPIC {
r#type: 0,
length: 8,
processor_id: cpu,
apic_id: cpu,
flags: 1,
};
madt.append(lapic);
}
madt.append(IOAPIC {
r#type: 1,
length: 12,
ioapic_id: 0,
apic_address: super::mptable::IO_APIC_DEFAULT_PHYS_BASE,
gsi_base: 0,
..Default::default()
});
madt.append(InterruptSourceOverride {
r#type: 2,
length: 10,
bus: 0,
source: 4,
gsi: 4,
flags: 0,
});
let madt_offset = facp_offset.checked_add(facp.len() as u64).unwrap();
guest_mem
.write_slice(madt.as_slice(), madt_offset)
.expect("Error writing MADT table");
tables.push(madt_offset.0);
// MCFG
let mut mcfg = SDT::new(*b"MCFG", 60, 1, *b"CLOUDH", *b"CHMCFG ", 1);
// 32-bit PCI enhanced configuration mechanism
mcfg.append(PCIRangeEntry {
base_address: super::MEM_32BIT_DEVICES_GAP_SIZE,
segment: 0,
start: 0,
end: 0xff,
..Default::default()
});
let mcfg_offset = madt_offset.checked_add(madt.len() as u64).unwrap();
guest_mem
.write_slice(mcfg.as_slice(), mcfg_offset)
.expect("Error writing MCFG table");
tables.push(mcfg_offset.0);
// XSDT
let mut xsdt = SDT::new(*b"XSDT", 36, 1, *b"CLOUDH", *b"CHXSDT ", 1);
for table in tables {
xsdt.append(table);
}
xsdt.update_checksum();
let xsdt_offset = mcfg_offset.checked_add(mcfg.len() as u64).unwrap();
guest_mem
.write_slice(xsdt.as_slice(), xsdt_offset)
.expect("Error writing XSDT table");
// RSDP
let rsdp = RSDP::new(*b"CLOUDH", xsdt_offset.0);
guest_mem
.write_slice(rsdp.as_slice(), rsdp_offset)
.expect("Error writing RSDP");
rsdp_offset
}

View File

@@ -5,6 +5,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE-BSD-3-Clause file.
mod acpi;
mod gdt;
pub mod interrupts;
pub mod layout;
@@ -12,12 +13,14 @@ mod mptable;
pub mod regs;
use crate::RegionType;
use linux_loader::loader::bootparam::{boot_params, setup_header, E820_RAM};
use linux_loader::loader::bootparam::{boot_params, setup_header};
use std::mem;
use vm_memory::{
Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryMmap, GuestUsize,
};
const E820_RAM: u32 = 1;
// This is a workaround to the Rust enforcement specifying that any implementation of a foreign
// trait (in this case `DataInit`) where:
// * the type that is implementing the trait is foreign or
@@ -44,11 +47,11 @@ impl From<Error> for super::Error {
}
// Where BIOS/VGA magic would live on a real PC.
const EBDA_START: GuestAddress = GuestAddress(0x9fc00);
const EBDA_START: GuestAddress = GuestAddress(0xa0000);
const FIRST_ADDR_PAST_32BITS: GuestAddress = GuestAddress(1 << 32);
// Our 32-bit memory gap starts at 3G.
const MEM_32BIT_GAP_START: GuestAddress = GuestAddress(0xc000_0000);
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);
@@ -124,6 +127,7 @@ pub fn configure_system(
cmdline_size: usize,
num_cpus: u8,
setup_hdr: Option<setup_header>,
serial_enabled: bool,
) -> super::Result<()> {
const KERNEL_BOOT_FLAG_MAGIC: u16 = 0xaa55;
const KERNEL_HDR_MAGIC: u32 = 0x53726448;
@@ -179,6 +183,12 @@ pub fn configure_system(
}
}
#[cfg(feature = "acpi")]
{
let rsdp_addr = acpi::create_acpi_tables(guest_mem, num_cpus, serial_enabled);
params.0.acpi_rsdp_addr = rsdp_addr.0;
}
let zero_page_addr = layout::ZERO_PAGE_START;
guest_mem
.checked_offset(zero_page_addr, mem::size_of::<boot_params>())
@@ -198,13 +208,13 @@ fn add_e820_entry(
size: u64,
mem_type: u32,
) -> Result<(), Error> {
if params.e820_entries >= params.e820_map.len() as u8 {
if params.e820_entries >= params.e820_table.len() as u8 {
return Err(Error::E820Configuration);
}
params.e820_map[params.e820_entries as usize].addr = addr;
params.e820_map[params.e820_entries as usize].size = size;
params.e820_map[params.e820_entries as usize].type_ = mem_type;
params.e820_table[params.e820_entries as usize].addr = addr;
params.e820_table[params.e820_entries as usize].size = size;
params.e820_table[params.e820_entries as usize].type_ = mem_type;
params.e820_entries += 1;
Ok(())
@@ -213,7 +223,7 @@ fn add_e820_entry(
#[cfg(test)]
mod tests {
use super::*;
use linux_loader::loader::bootparam::e820entry;
use linux_loader::loader::bootparam::boot_e820_entry;
#[test]
fn regions_lt_4gb() {
@@ -245,7 +255,7 @@ mod tests {
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);
let config_err = configure_system(&gm, GuestAddress(0), 0, 1, None, false);
assert!(config_err.is_err());
assert_eq!(
config_err.unwrap_err(),
@@ -263,7 +273,7 @@ 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).unwrap();
configure_system(&gm, GuestAddress(0), 0, no_vcpus, None, false).unwrap();
// Now assigning some memory that is equal to the start of the 32bit memory hole.
let mem_size = 3328 << 20;
@@ -274,7 +284,7 @@ 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).unwrap();
configure_system(&gm, GuestAddress(0), 0, no_vcpus, None, false).unwrap();
// Now assigning some memory that falls after the 32bit memory hole.
let mem_size = 3330 << 20;
@@ -285,19 +295,19 @@ 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).unwrap();
configure_system(&gm, GuestAddress(0), 0, no_vcpus, None, false).unwrap();
}
#[test]
fn test_add_e820_entry() {
let e820_map = [(e820entry {
let e820_table = [(boot_e820_entry {
addr: 0x1,
size: 4,
type_: 1,
}); 128];
let expected_params = boot_params {
e820_map,
e820_table,
e820_entries: 1,
..Default::default()
};
@@ -305,25 +315,25 @@ mod tests {
let mut params: boot_params = Default::default();
add_e820_entry(
&mut params,
e820_map[0].addr,
e820_map[0].size,
e820_map[0].type_,
e820_table[0].addr,
e820_table[0].size,
e820_table[0].type_,
)
.unwrap();
assert_eq!(
format!("{:?}", params.e820_map[0]),
format!("{:?}", expected_params.e820_map[0])
format!("{:?}", params.e820_table[0]),
format!("{:?}", expected_params.e820_table[0])
);
assert_eq!(params.e820_entries, expected_params.e820_entries);
// Exercise the scenario where the field storing the length of the e820 entry table is
// is bigger than the allocated memory.
params.e820_entries = params.e820_map.len() as u8 + 1;
params.e820_entries = params.e820_table.len() as u8 + 1;
assert!(add_e820_entry(
&mut params,
e820_map[0].addr,
e820_map[0].size,
e820_map[0].type_
e820_table[0].addr,
e820_table[0].size,
e820_table[0].type_
)
.is_err());
}

View File

@@ -92,8 +92,8 @@ const MPC_SPEC: i8 = 4;
const MPC_OEM: [c_char; 8] = char_array!(c_char; 'F', 'C', ' ', ' ', ' ', ' ', ' ', ' ');
const MPC_PRODUCT_ID: [c_char; 12] = ['0' as c_char; 12];
const BUS_TYPE_ISA: [u8; 6] = char_array!(u8; 'I', 'S', 'A', ' ', ' ', ' ');
const IO_APIC_DEFAULT_PHYS_BASE: u32 = 0xfec00000; // source: linux/arch/x86/include/asm/apicdef.h
const APIC_DEFAULT_PHYS_BASE: u32 = 0xfee00000; // source: linux/arch/x86/include/asm/apicdef.h
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;

View File

@@ -18,6 +18,10 @@ const PML4_START: GuestAddress = GuestAddress(0x9000);
const PDPTE_START: GuestAddress = GuestAddress(0xa000);
const PDE_START: GuestAddress = GuestAddress(0xb000);
// MTRR constants
const MTRR_ENABLE: u64 = 0x800; // IA32_MTRR_DEF_TYPE MSR: E (MTRRs enabled) flag, bit 11
const MTRR_MEM_TYPE_WB: u64 = 0x6;
#[derive(Debug)]
pub enum Error {
/// Failed to get SREGs for this CPU.
@@ -269,6 +273,11 @@ fn create_msr_entries() -> Vec<kvm_msr_entry> {
data: msr_index::MSR_IA32_MISC_ENABLE_FAST_STRING as u64,
..Default::default()
});
entries.push(kvm_msr_entry {
index: msr_index::MSR_MTRRdefType,
data: MTRR_ENABLE | MTRR_MEM_TYPE_WB,
..Default::default()
});
entries
}

View File

@@ -10,7 +10,7 @@
#[allow(non_snake_case)]
#[allow(
clippy::unreadable_literal,
clippy::const_static_lifetime,
clippy::redundant_static_lifetimes,
clippy::trivially_copy_pass_by_ref,
clippy::useless_transmute,
clippy::should_implement_trait,
@@ -19,8 +19,8 @@
pub mod bootparam;
#[allow(non_camel_case_types)]
#[allow(non_upper_case_globals)]
#[allow(clippy::unreadable_literal, clippy::const_static_lifetime)]
#[allow(clippy::unreadable_literal, clippy::redundant_static_lifetimes)]
pub mod mpspec;
#[allow(non_upper_case_globals)]
#[allow(clippy::unreadable_literal, clippy::const_static_lifetime)]
#[allow(clippy::unreadable_literal, clippy::redundant_static_lifetimes)]
pub mod msr_index;

View File

@@ -4,14 +4,18 @@ version = "0.1.0"
authors = ["The Chromium OS Authors"]
[dependencies]
byteorder = ">=1.2.1"
epoll = "=4.0.1"
kvm-bindings = "0.1"
byteorder = "1.3.2"
epoll = "4.1.0"
kvm-bindings = "0.1.1"
kvm-ioctls = { git = "https://github.com/rust-vmm/kvm-ioctls", branch = "master" }
libc = ">=0.2.39"
log = "*"
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" }
[dev-dependencies]
tempfile = ">=3.0.2"
tempfile = "3.1.0"
[features]
default = ["acpi"]
acpi = []

52
devices/src/acpi.rs Normal file
View File

@@ -0,0 +1,52 @@
// Copyright © 2019 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
use vmm_sys_util::eventfd::EventFd;
use BusDevice;
/// A device for handling ACPI shutdown and reboot
pub struct AcpiShutdownDevice {
exit_evt: EventFd,
reset_evt: EventFd,
}
impl AcpiShutdownDevice {
/// Constructs a device that will signal the given event when the guest requests it.
pub fn new(exit_evt: EventFd, reset_evt: EventFd) -> AcpiShutdownDevice {
AcpiShutdownDevice {
exit_evt,
reset_evt,
}
}
}
// Same I/O port used for shutdown and reboot
impl BusDevice for AcpiShutdownDevice {
// Spec has all fields as zero
fn read(&mut self, _base: u64, _offset: u64, data: &mut [u8]) {
for i in data.iter_mut() {
*i = 0;
}
}
fn write(&mut self, _base: u64, _offset: u64, data: &[u8]) {
if data[0] == 1 {
debug!("ACPI Reboot signalled");
if let Err(e) = self.reset_evt.write(1) {
error!("Error triggering ACPI reset event: {}", e);
}
}
// The ACPI DSDT table specifies the S5 sleep state (shutdown) as value 5
const S5_SLEEP_VALUE: u8 = 5;
const SLEEP_STATUS_EN_BIT: u8 = 5;
const SLEEP_VALUE_BIT: u8 = 2;
if data[0] == (S5_SLEEP_VALUE << SLEEP_VALUE_BIT) | (1 << SLEEP_STATUS_EN_BIT) {
debug!("ACPI Shutdown signalled");
if let Err(e) = self.exit_evt.write(1) {
error!("Error triggering ACPI shutdown event: {}", e);
}
}
}
}

View File

@@ -77,7 +77,7 @@ impl PartialOrd for BusRange {
/// only restriction is that no two devices can overlap in this address space.
#[derive(Clone, Default)]
pub struct Bus {
devices: BTreeMap<BusRange, Arc<Mutex<BusDevice>>>,
devices: BTreeMap<BusRange, Arc<Mutex<dyn BusDevice>>>,
}
impl Bus {
@@ -88,7 +88,7 @@ impl Bus {
}
}
fn first_before(&self, addr: u64) -> Option<(BusRange, &Mutex<BusDevice>)> {
fn first_before(&self, addr: u64) -> Option<(BusRange, &Mutex<dyn BusDevice>)> {
let (range, dev) = self
.devices
.range(..=BusRange { base: addr, len: 1 })
@@ -97,7 +97,7 @@ impl Bus {
Some((*range, dev))
}
pub fn resolve(&self, addr: u64) -> Option<(u64, u64, &Mutex<BusDevice>)> {
pub fn resolve(&self, addr: u64) -> Option<(u64, u64, &Mutex<dyn BusDevice>)> {
if let Some((range, dev)) = self.first_before(addr) {
let offset = addr - range.base;
if offset < range.len {
@@ -108,7 +108,7 @@ impl Bus {
}
/// Puts the given device at the given address space.
pub fn insert(&mut self, device: Arc<Mutex<BusDevice>>, base: u64, len: u64) -> Result<()> {
pub fn insert(&mut self, device: Arc<Mutex<dyn BusDevice>>, base: u64, len: u64) -> Result<()> {
if len == 0 {
return Err(Error::Overlap);
}

View File

@@ -188,7 +188,6 @@ impl BusDevice for Ioapic {
IOWIN_OFF => self.ioapic_write(value),
_ => {
error!("IOAPIC: failed writing at offset {}", offset);
return;
}
}
}

View File

@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE-BSD-3-Clause file.
use vmm_sys_util::EventFd;
use vmm_sys_util::eventfd::EventFd;
use BusDevice;
@@ -34,8 +34,9 @@ impl BusDevice for I8042Device {
fn write(&mut self, _base: u64, offset: u64, data: &[u8]) {
if data.len() == 1 && data[0] == 0xfe && offset == 3 {
debug!("i8042 reset signalled");
if let Err(e) = self.reset_evt.write(1) {
println!("Error triggering i8042 reset event: {}", e);
error!("Error triggering i8042 reset event: {}", e);
}
}
}

View File

@@ -8,7 +8,7 @@
use crate::{BusDevice, Interrupt};
use std::collections::VecDeque;
use std::{io, result};
use vmm_sys_util::Result;
use vmm_sys_util::errno::Result;
const LOOP_SIZE: usize = 0x40;
@@ -55,7 +55,7 @@ const DEFAULT_BAUD_DIVISOR: u16 = 12; // 9600 bps
pub struct Serial {
interrupt_enable: u8,
interrupt_identification: u8,
interrupt: Box<Interrupt>,
interrupt: Box<dyn Interrupt>,
line_control: u8,
line_status: u8,
modem_control: u8,
@@ -63,11 +63,11 @@ pub struct Serial {
scratch: u8,
baud_divisor: u16,
in_buffer: VecDeque<u8>,
out: Option<Box<io::Write + Send>>,
out: Option<Box<dyn io::Write + Send>>,
}
impl Serial {
fn new(interrupt: Box<Interrupt>, out: Option<Box<io::Write + Send>>) -> Serial {
pub fn new(interrupt: Box<dyn Interrupt>, out: Option<Box<dyn io::Write + Send>>) -> Serial {
Serial {
interrupt_enable: 0,
interrupt_identification: DEFAULT_INTERRUPT_IDENTIFICATION,
@@ -84,12 +84,12 @@ impl Serial {
}
/// Constructs a Serial port ready for output.
pub fn new_out(interrupt: Box<Interrupt>, out: Box<io::Write + Send>) -> Serial {
pub fn new_out(interrupt: Box<dyn Interrupt>, out: Box<dyn io::Write + Send>) -> Serial {
Self::new(interrupt, Some(out))
}
/// Constructs a Serial port with no connected output.
pub fn new_sink(interrupt: Box<Interrupt>) -> Serial {
pub fn new_sink(interrupt: Box<dyn Interrupt>) -> Serial {
Self::new(interrupt, None)
}
@@ -233,7 +233,7 @@ mod tests {
use super::*;
use std::io;
use std::sync::{Arc, Mutex};
use vmm_sys_util::EventFd;
use vmm_sys_util::eventfd::EventFd;
struct TestInterrupt {
event_fd: EventFd,

View File

@@ -19,10 +19,14 @@ extern crate vmm_sys_util;
use std::fs::File;
use std::{io, result};
#[cfg(feature = "acpi")]
mod acpi;
mod bus;
pub mod ioapic;
pub mod legacy;
#[cfg(feature = "acpi")]
pub use self::acpi::AcpiShutdownDevice;
pub use self::bus::{Bus, BusDevice, Error as BusError};
pub type DeviceEventT = u16;

61
docs/debug-port.md Normal file
View File

@@ -0,0 +1,61 @@
# `cloud-hypervisor` debug IO port
`cloud-hypervisor` uses the [`0x80`](https://www.intel.com/content/www/us/en/support/articles/000005500/boards-and-kits.html)
I/O port to trace user defined guest events.
Whenever the guest write one byte between `0x0` and `0xF` on this particular
I/O port, `cloud-hypervisor` will log and timestamp that event at the `debug`
log level.
It is up to the guest stack to decide when and what to write to the 0x80 port
in order to signal the host about specific events and have `cloud-hypervisor`
log it.
`cloud-hypervisor` defines several debug port code ranges that should be used
for debugging specific components of the guest software stack. When logging a
write of one of those codes to the debug port, `cloud-hypervisor` adds a
pre-defined string to the logs.
| Code Range | Component | Log string |
| ---------------- | ----------- | ------------ |
| `0x00` to `0x1f` | Firmware | `Firmware` |
| `0x20` to `0x3f` | Bootloader | `Bootloader` |
| `0x40` to `0x5f` | Kernel | `Kernel` |
| `0x60` to `0x7f` | Userspace | `Userspace` |
| `0x80` to `0xff` | Custom | `Custom` |
One typical use case is guest boot time measurement and tracing. By writing
different values to the debug I/O port at different boot process steps, the
guest will have `cloud-hypervisor` generate timestamped logs of all those steps.
That provides a basic but convenient way of measuring not only the overall guest
boot time but all intermediate steps as well.
## Logging
Assuming parts of the guest software stack have been instrumented to use the
`cloud-hypervisor` debug I/O port, we may want to gather the related logs.
To do so we need to start `cloud-hypervisor` with the right debug level
(`-vvv`). It is also recommended to have it log into a dedicated file in order
to easily grep for the tracing logs (e.g.
`--log-file /tmp/cloud-hypervisor.log`):
```
./target/debug/cloud-hypervisor \
--kernel ~/rust-hypervisor-firmware/target/target/release/hypervisor-fw \
--disk ~/hypervisor/images/clear-30080-kvm.img \
--cpus 4 \
--memory size=1024M \
--rng \
--log-file /tmp/ch-fw.log \
-vvv
```
After booting the guest, we then have to grep for the debug I/O port traces in
the log file:
```Shell
$ grep "Debug I/O port" /tmp/ch-fw.log
cloud-hypervisor: 19.762449ms: DEBUG:vmm/src/vm.rs:510 -- [Debug I/O port: Firmware code 0x0] 0.019004 seconds
cloud-hypervisor: 403.499628ms: DEBUG:vmm/src/vm.rs:510 -- [Debug I/O port: Firmware code 0x1] 0.402744 seconds
```

175
docs/networking.md Normal file
View File

@@ -0,0 +1,175 @@
# How to use networking
cloud-hypervisor can emulate one or more virtual network interfaces, represented at the hypervisor host by [tap devices](https://www.kernel.org/doc/Documentation/networking/tuntap.txt"). This guide briefly describes, in a manual and distribution neutral way, how to setup and use networking with cloud-hypevisor.
## Start cloud-hypervisor with net devices
Use one `--net` command-line argument from cloud-hypervisor to specify the emulation of one or more virtual NIC's. The example below instructs cloud-hypervisor to emulate for instance 2 virtual NIC's:
```bash
./cloud-hypervisor \
--cpus 4 \
--memory "size=512M" \
--disk 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 \
tap=ich1,mac=a4:a1:c2:00:00:02,ip=10.0.1.2,mask=255.255.255.0
```
The `--net` argument takes 1 or more space-separated strings of key value pairs containing the following 4 keys or fields:
| Name | Purpose | Optional |
| -------- |----------------------------| ----------|
| tap | tap device name | Yes |
| mac | vNIC mac address | Yes |
| ip | tap IP IP address | yes |
| mask | tap IP netmask | Yes |
## Configure the tap devices
After starting cloud-hypervisor as shown above, 2 tap devices with state down will become available at the host:
```bash
root@host:~# ip link show ich0
78: ich0: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether 72:54:12:ff:ce:6f brd ff:ff:ff:ff:ff:ff
root@host:~# ip link show ich1
79: ich1: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether 06:7a:fc:1b:9a:67 brd ff:ff:ff:ff:ff:ff
```
Set the tap devices to up state:
```bash
root@host:~# ip link set up ich0
root@host:~# ip link set up ich1
root@host:~# ip link show ich0
78: ich0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UNKNOWN mode DEFAULT group default qlen 1000
link/ether 72:54:12:ff:ce:6f brd ff:ff:ff:ff:ff:ff
root@host:~# ip link show ich1
79: ich1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UNKNOWN mode DEFAULT group default qlen 1000
link/ether 06:7a:fc:1b:9a:67 brd ff:ff:ff:ff:ff:ff
```
## Connect tap devices
Different networking models can be used to provide external connectivity. In this example we will
use 2 linux bridges emulating 2 different networks. The integration bridge (ich-int) in this example will also be used
for external connectivity.
Create the bridges and connect the cloud-hypervisor tap devices to the bridges:
```bash
root@host:~# brctl addbr ich-int
root@host:~# brctl addbr ich-dpl
root@host:~# ip link set up ich-int
root@host:~# ip link set up ich-dpl
root@host:~# brctl addif ich-int ich0
root@host:~# brctl addif ich-dpl ich1
root@host:~# brctl show
bridge name bridge id STP enabled interfaces
ich-dpl 8000.067afc1b9a67 no ich1
ich-int 8000.725412ffce6f no ich0
```
This completes the layer 2 wiring: The cloud-hypervisor is now connected to the hypervisor host via the 2 linux bridges.
## IP (Layer 3) provisioning
### Hypervisor host
On the hypervisor host add the network gateway IP address of each network to the 2 linux bridges:
```bash
root@host:~# ip addr add 192.168.4.1/24 dev ich-int
root@host:~# ip addr add 10.0.1.1/24 dev ich-dpl
```
The routing table of the hypervisor host should now also have corresponding routing entries:
```bash
root@host:~# route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 192.168.178.1 0.0.0.0 UG 600 0 0 wlan1
10.0.1.0 0.0.0.0 255.255.255.0 U 0 0 0 ich-dpl
192.168.4.0 0.0.0.0 255.255.255.0 U 0 0 0 ich-int
192.168.178.0 0.0.0.0 255.255.255.0 U 600 0 0 wlan1
```
### Virtual Machine
Within the virtual machine set the vNIC's to up state and provision the corresponding IP addresses on the 2 vNIC's. The steps outlined below use the ip command as an example. Alternative distribution specific procedures can also apply.
```bash
root@guest:~# ip link set up enp0s2
root@guest:~# ip link set up enp0s3
root@guest:~# ip addr add 192.168.4.2/24 dev enp0s2
root@guest:~# ip addr add 10.0.1.2/24 dev enp0s3
```
IP connectivity between the virtual machine and the hypervisor-host can be verified by sending
ICMP requests to the hypervisor-host for the gateway IP address from within the virtual machine:
```bash
root@guest:~# ping 192.168.4.1
PING 192.168.4.1 (192.168.4.1) 56(84) bytes of data.
64 bytes from 192.168.4.1: icmp_seq=1 ttl=64 time=0.456 ms
64 bytes from 192.168.4.1: icmp_seq=2 ttl=64 time=0.226 ms
root@guest:~# ping 10.0.1.1
PING 10.0.1.1 (10.0.1.1) 56(84) bytes of data.
64 bytes from 10.0.1.1: icmp_seq=1 ttl=64 time=0.449 ms
64 bytes from 10.0.1.1: icmp_seq=2 ttl=64 time=0.393 ms
```
The connection can now be used for instance to log into the virtual machine with
ssh under the precondition that the machine has an ssh daemon provisioned:
```bash
root@host:~# ssh root@192.168.4.2
The authenticity of host '192.168.4.2 (192.168.4.2)' can't be established.
ECDSA key fingerprint is SHA256:qNAUmTtDMW9pNuZARkpLQhfw+Yc1tqUDBrQp7aZGSjw.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '192.168.4.2' (ECDSA) to the list of known hosts.
root@192.168.4.2's password:
Linux cloud-hypervisor 5.2.0 #2 SMP Thu Jul 11 08:08:16 CEST 2019 x86_64
Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Fri Jul 12 13:27:56 2019 from 192.168.4.1
root@guest:~#
```
## Internet connectivity
To enable internet connectivity a default gw and a nameserver has to be set within
the virtual machine:
```bash
root@guest:~# ip route add default via 192.168.4.1
root@guest:~# cat /etc/resolv.conf
options timeout:2
domain vallis.nl
search vallis.nl
nameserver 192.168.178.1
```
make sure that the default gateway of the hypervisor host (in this example host 192.168.178.1 which is an adsl router) has an entry in the routing table for the 192.168.4.0/24 network otherwise IP connectivity will not work.
```bash
root@guest:~# nslookup ftp.nl.debian.org
Server: 192.168.178.1
Address: 192.168.178.1#53
Non-authoritative answer:
cdn-fastly.deb.debian.org canonical name = prod.debian.map.fastly.net.
Name: prod.debian.map.fastly.net
Address: 151.101.36.204
root@guest:~# apt-get update
Ign:1 http://cdn-fastly.deb.debian.org/debian stretch InRelease
Get:2 http://cdn-fastly.deb.debian.org/debian stretch Release [118 kB]
Get:3 http://cdn-fastly.deb.debian.org/debian stretch Release.gpg [2434 B]
Fetched 120 kB in 1s (110 kB/s)
```

View File

@@ -4,14 +4,14 @@ version = "0.1.0"
authors = ["The Chromium OS Authors"]
[dependencies]
libc = ">=0.2.39"
rand = ">=0.6.5"
serde = ">=1.0.27"
libc = "0.2.60"
rand = "0.7.0"
serde = "1.0.98"
net_gen = { path = "../net_gen" }
vmm-sys-util = { git = "https://github.com/rust-vmm/vmm-sys-util" }
[dev-dependencies]
lazy_static = ">=1.1.0"
pnet = "=0.22.0"
serde_json = ">=1.0.9"
lazy_static = "1.3.0"
pnet = "0.22.0"
serde_json = "1.0.40"

View File

@@ -396,7 +396,9 @@ mod tests {
// For a given interface name, this returns a tuple that contains the MAC address of the
// interface, an object that can be used to send Ethernet frames, and a receiver of
// Ethernet frames arriving at the specified interface.
fn pnet_get_mac_tx_rx(ifname: String) -> (MacAddr, Box<DataLinkSender>, Box<DataLinkReceiver>) {
fn pnet_get_mac_tx_rx(
ifname: String,
) -> (MacAddr, Box<dyn DataLinkSender>, Box<dyn DataLinkReceiver>) {
let interface_name_matches = |iface: &NetworkInterface| iface.name == ifname;
// Find the network interface with the provided name.

View File

@@ -6,11 +6,11 @@ edition = "2018"
[dependencies]
vm-allocator = { path = "../vm-allocator" }
byteorder = "*"
byteorder = "1.3.2"
devices = { path = "../devices" }
kvm-bindings = "0.1"
kvm-bindings = "0.1.1"
kvm-ioctls = { git = "https://github.com/rust-vmm/kvm-ioctls", branch = "master" }
libc = ">=0.2.39"
log = "*"
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" }

View File

@@ -125,7 +125,7 @@ impl PciConfigIo {
return 0xffff_ffff;
}
let (bus, device, _function, register) =
let (bus, device, function, register) =
parse_config_address(self.config_address & !0x8000_0000);
// Only support one bus.
@@ -133,6 +133,11 @@ impl PciConfigIo {
return 0xffff_ffff;
}
// Don't support multi-function devices.
if function > 0 {
return 0xffff_ffff;
}
self.devices.get(device).map_or(0xffff_ffff, |d| {
d.lock().unwrap().read_config_register(register)
})
@@ -187,8 +192,8 @@ impl BusDevice for PciConfigIo {
fn read(&mut self, _base: u64, offset: u64, data: &mut [u8]) {
// `offset` is relative to 0xcf8
let value = match offset {
0...3 => self.config_address,
4...7 => self.config_space_read(),
0..=3 => self.config_address,
4..=7 => self.config_space_read(),
_ => 0xffff_ffff,
};
@@ -209,8 +214,8 @@ impl BusDevice for PciConfigIo {
fn write(&mut self, _base: u64, offset: u64, data: &[u8]) {
// `offset` is relative to 0xcf8
match offset {
o @ 0...3 => self.set_config_address(o, data),
o @ 4...7 => self.config_space_write(o - 4, data),
o @ 0..=3 => self.set_config_address(o, data),
o @ 4..=7 => self.config_space_write(o - 4, data),
_ => (),
};
}

View File

@@ -14,8 +14,10 @@ const NUM_CONFIGURATION_REGISTERS: usize = 64;
const STATUS_REG: usize = 1;
const STATUS_REG_CAPABILITIES_USED_MASK: u32 = 0x0010_0000;
const BAR0_REG: usize = 4;
const ROM_BAR_REG: usize = 12;
const BAR_IO_ADDR_MASK: u32 = 0xffff_fffc;
const BAR_MEM_ADDR_MASK: u32 = 0xffff_fff0;
const ROM_BAR_ADDR_MASK: u32 = 0xffff_f800;
const NUM_BAR_REGS: usize = 6;
const CAPABILITY_LIST_HEAD_OFFSET: usize = 0x34;
const FIRST_CAPABILITY_OFFSET: usize = 0x40;
@@ -249,6 +251,8 @@ pub struct PciConfiguration {
writable_bits: [u32; NUM_CONFIGURATION_REGISTERS], // writable bits for each register.
bar_size: [u32; NUM_BAR_REGS],
bar_used: [bool; NUM_BAR_REGS],
rom_bar_size: u32,
rom_bar_used: bool,
// Contains the byte offset and size of the last capability.
last_capability: Option<(usize, usize)>,
msix_cap_reg_idx: Option<usize>,
@@ -289,6 +293,10 @@ pub enum Error {
CapabilityEmpty,
CapabilityLengthInvalid(usize),
CapabilitySpaceFull(usize),
RomBarAddressInvalid(u64, u64),
RomBarInUse(usize),
RomBarInvalid(usize),
RomBarSizeInvalid(u64),
}
pub type Result<T> = std::result::Result<T, Error>;
@@ -312,6 +320,10 @@ impl Display for Error {
CapabilityEmpty => write!(f, "empty capabilities are invalid"),
CapabilityLengthInvalid(l) => write!(f, "Invalid capability length {}", l),
CapabilitySpaceFull(s) => write!(f, "capability of size {} doesn't fit", s),
RomBarAddressInvalid(a, s) => write!(f, "address {} size {} too big", a, s),
RomBarInUse(b) => write!(f, "rom bar {} already used", b),
RomBarInvalid(b) => write!(f, "rom bar {} invalid, max {}", b, NUM_BAR_REGS - 1),
RomBarSizeInvalid(s) => write!(f, "rom bar address {} not a power of two", s),
}
}
}
@@ -362,6 +374,8 @@ impl PciConfiguration {
writable_bits,
bar_size,
bar_used: [false; NUM_BAR_REGS],
rom_bar_size: 0,
rom_bar_used: false,
last_capability: None,
msix_cap_reg_idx: None,
msix_config,
@@ -376,12 +390,15 @@ impl PciConfiguration {
/// Writes a 32bit register to `reg_idx` in the register map.
pub fn write_reg(&mut self, reg_idx: usize, value: u32) {
let mut mask = self.writable_bits[reg_idx];
if reg_idx >= BAR0_REG && reg_idx < BAR0_REG + NUM_BAR_REGS {
if reg_idx >= BAR0_REG
&& reg_idx < BAR0_REG + NUM_BAR_REGS
&& (value & BAR_MEM_ADDR_MASK) == BAR_MEM_ADDR_MASK
{
// Handle very specific case where the BAR is being written with
// all 1's to retrieve the BAR size on next BAR reading.
if value == 0xffff_ffff {
mask = self.bar_size[reg_idx - 4];
}
mask = self.bar_size[reg_idx - 4];
} else if reg_idx == ROM_BAR_REG && (value & ROM_BAR_ADDR_MASK) == ROM_BAR_ADDR_MASK {
mask = self.rom_bar_size;
}
if let Some(r) = self.registers.get_mut(reg_idx) {
@@ -500,6 +517,36 @@ impl PciConfiguration {
Ok(config.reg_idx)
}
/// Adds rom expansion BAR.
pub fn add_pci_rom_bar(&mut self, config: &PciBarConfiguration, active: u32) -> Result<usize> {
if self.rom_bar_used {
return Err(Error::RomBarInUse(config.reg_idx));
}
if config.size.count_ones() != 1 {
return Err(Error::RomBarSizeInvalid(config.size));
}
if config.reg_idx != ROM_BAR_REG {
return Err(Error::RomBarInvalid(config.reg_idx));
}
let end_addr = config
.addr
.checked_add(config.size - 1)
.ok_or_else(|| Error::RomBarAddressInvalid(config.addr, config.size))?;
if end_addr > u64::from(u32::max_value()) {
return Err(Error::RomBarAddressInvalid(config.addr, config.size));
}
self.registers[config.reg_idx] = (config.addr as u32) | active;
self.writable_bits[config.reg_idx] = ROM_BAR_ADDR_MASK;
self.rom_bar_size = config.size as u32;
self.rom_bar_used = true;
Ok(config.reg_idx)
}
/// Returns the address of the given 32 bits BAR region.
pub fn get_bar32_addr(&self, bar_num: usize) -> u32 {
let bar_idx = BAR0_REG + bar_num;
@@ -663,8 +710,6 @@ mod tests {
#[derive(Clone, Copy, Default)]
#[allow(dead_code)]
struct TestCap {
_vndr: u8,
_next: u8,
len: u8,
foo: u8,
}
@@ -683,7 +728,6 @@ mod tests {
}
#[test]
#[ignore]
fn add_capability() {
let mut cfg = PciConfiguration::new(
0x1234,
@@ -698,18 +742,11 @@ mod tests {
);
// Add two capabilities with different contents.
let cap1 = TestCap {
_vndr: 0,
_next: 0,
len: 4,
foo: 0xAA,
};
let cap1 = TestCap { len: 4, foo: 0xAA };
let cap1_offset = cfg.add_capability(&cap1).unwrap();
assert_eq!(cap1_offset % 4, 0);
let cap2 = TestCap {
_vndr: 0,
_next: 0,
len: 0x04,
foo: 0x55,
};

View File

@@ -11,14 +11,14 @@ use std::fmt::{self, Display};
use std::sync::Arc;
use vm_allocator::SystemAllocator;
use vm_memory::{GuestAddress, GuestUsize};
use vmm_sys_util::EventFd;
use vmm_sys_util::eventfd::EventFd;
pub struct InterruptParameters<'a> {
pub msix: Option<&'a MsixTableEntry>,
}
pub type InterruptDelivery =
Box<Fn(InterruptParameters) -> std::result::Result<(), std::io::Error> + Send + Sync>;
Box<dyn Fn(InterruptParameters) -> std::result::Result<(), std::io::Error> + Send + Sync>;
#[derive(Debug)]
pub enum Error {
@@ -70,11 +70,6 @@ pub trait PciDevice: BusDevice {
Ok(Vec::new())
}
/// Register any capabilties specified by the device.
fn register_device_capabilities(&mut self) -> Result<()> {
Ok(())
}
/// Gets a list of ioeventfds that should be registered with the running VM. The list is
/// returned as a Vec of (eventfd, addr, datamatch) tuples.
fn ioeventfds(&self) -> Vec<(&EventFd, u64, u64)> {
@@ -95,6 +90,4 @@ pub trait PciDevice: BusDevice {
/// * `addr` - The guest address inside the BAR.
/// * `data` - The data to write.
fn write_bar(&mut self, _base: u64, _offset: u64, _data: &[u8]) {}
/// Invoked when the device is sandboxed.
fn on_device_sandboxed(&mut self) {}
}

View File

@@ -53,6 +53,7 @@ pub struct MsixConfig {
pub pba_entries: Vec<u64>,
interrupt_cb: Option<Arc<InterruptDelivery>>,
masked: bool,
enabled: bool,
}
impl MsixConfig {
@@ -70,6 +71,7 @@ impl MsixConfig {
pba_entries,
interrupt_cb: None,
masked: false,
enabled: false,
}
}
@@ -81,10 +83,15 @@ impl MsixConfig {
self.masked
}
pub fn enabled(&self) -> bool {
self.enabled
}
pub fn set_msg_ctl(&mut self, reg: u16) {
let old_masked = self.masked;
self.masked = ((reg >> FUNCTION_MASK_BIT) & 1u16) == 1u16;
self.enabled = ((reg >> MSIX_ENABLE_BIT) & 1u16) == 1u16;
// If the Function Mask bit was set, and has just been cleared, it's
// important to go through the entire PBA to check if there was any
@@ -111,7 +118,7 @@ impl MsixConfig {
0x0 => self.table_entries[index].msg_addr_lo,
0x4 => self.table_entries[index].msg_addr_hi,
0x8 => self.table_entries[index].msg_data,
0x10 => self.table_entries[index].vector_ctl,
0xc => self.table_entries[index].vector_ctl,
_ => {
error!("invalid offset");
0
@@ -162,7 +169,7 @@ impl MsixConfig {
0x0 => self.table_entries[index].msg_addr_lo = value,
0x4 => self.table_entries[index].msg_addr_hi = value,
0x8 => self.table_entries[index].msg_data = value,
0x10 => {
0xc => {
old_entry = Some(self.table_entries[index].clone());
self.table_entries[index].vector_ctl = value;
}
@@ -359,11 +366,11 @@ impl MsixCap {
}
pub fn table_offset(&self) -> u32 {
self.table >> 3
self.table & 0xffff_fff8
}
pub fn pba_offset(&self) -> u32 {
self.pba >> 3
self.pba & 0xffff_fff8
}
pub fn table_bir(&self) -> u32 {

View File

@@ -9,11 +9,11 @@ license = "BSD-3-Clause"
path = "src/qcow.rs"
[dependencies]
byteorder = "*"
libc = "*"
log = "*"
remain = "*"
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" }
[dev-dependencies]
tempfile = "*"
tempfile = "3.1.0"

View File

@@ -12,9 +12,12 @@ mod vec_cache;
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use libc::{EINVAL, ENOSPC, ENOTSUP};
use remain::sorted;
use vmm_sys_util::{FileSetLen, FileSync, PunchHole, SeekHole, WriteZeroes};
use vmm_sys_util::{
file_traits::FileSetLen, file_traits::FileSync, seek_hole::SeekHole, write_zeroes::PunchHole,
write_zeroes::WriteZeroes,
};
use std::cmp::min;
use std::cmp::{max, min};
use std::fmt::{self, Display};
use std::fs::File;
use std::io::{self, Read, Seek, SeekFrom, Write};
@@ -31,18 +34,21 @@ pub enum Error {
BackingFilesNotSupported,
CompressedBlocksNotSupported,
EvictingCache(io::Error),
FileTooBig(u64),
GettingFileSize(io::Error),
GettingRefcount(refcount::Error),
InvalidClusterIndex,
InvalidClusterSize,
InvalidIndex,
InvalidL1TableOffset,
InvalidL1TableSize(u32),
InvalidMagic,
InvalidOffset(u64),
InvalidRefcountTableOffset,
InvalidRefcountTableSize,
InvalidRefcountTableSize(u64),
NoFreeClusters,
NoRefcountClusters,
NotEnoughSpaceForRefcounts,
OpeningFile(io::Error),
ReadingData(io::Error),
ReadingHeader(io::Error),
@@ -50,10 +56,14 @@ pub enum Error {
ReadingRefCountBlock(refcount::Error),
ReadingRefCounts(io::Error),
RebuildingRefCounts(io::Error),
RefcountTableOffEnd,
RefcountTableTooLarge,
SeekingFile(io::Error),
SettingFileSize(io::Error),
SettingRefcountRefcount(io::Error),
SizeTooSmallForNumberOfClusters,
TooManyL1Entries(u64),
TooManyRefcounts(u64),
UnsupportedRefcountOrder,
UnsupportedVersion(u32),
WritingData(io::Error),
@@ -72,18 +82,25 @@ impl Display for Error {
BackingFilesNotSupported => write!(f, "backing files not supported"),
CompressedBlocksNotSupported => write!(f, "compressed blocks not supported"),
EvictingCache(e) => write!(f, "failed to evict cache: {}", e),
FileTooBig(size) => write!(
f,
"file larger than max of {}: {}",
MAX_QCOW_FILE_SIZE, size
),
GettingFileSize(e) => write!(f, "failed to get file size: {}", e),
GettingRefcount(e) => write!(f, "failed to get refcount: {}", e),
InvalidClusterIndex => write!(f, "invalid cluster index"),
InvalidClusterSize => write!(f, "invalid cluster size"),
InvalidIndex => write!(f, "invalid index"),
InvalidL1TableOffset => write!(f, "invalid L1 table offset"),
InvalidL1TableSize(size) => write!(f, "invalid L1 table size {}", size),
InvalidMagic => write!(f, "invalid magic"),
InvalidOffset(_) => write!(f, "invalid offset"),
InvalidRefcountTableOffset => write!(f, "invalid refcount table offset"),
InvalidRefcountTableSize => write!(f, "invalid refcount table size"),
InvalidRefcountTableSize(size) => write!(f, "invalid refcount table size: {}", size),
NoFreeClusters => write!(f, "no free clusters"),
NoRefcountClusters => write!(f, "no refcount clusters"),
NotEnoughSpaceForRefcounts => write!(f, "not enough space for refcounts"),
OpeningFile(e) => write!(f, "failed to open file: {}", e),
ReadingData(e) => write!(f, "failed to read data: {}", e),
ReadingHeader(e) => write!(f, "failed to read header: {}", e),
@@ -91,10 +108,14 @@ impl Display for Error {
ReadingRefCountBlock(e) => write!(f, "failed to read ref count block: {}", e),
ReadingRefCounts(e) => write!(f, "failed to read ref counts: {}", e),
RebuildingRefCounts(e) => write!(f, "failed to rebuild ref counts: {}", e),
RefcountTableOffEnd => write!(f, "refcount table offset past file end"),
RefcountTableTooLarge => write!(f, "too many clusters specified for refcount table"),
SeekingFile(e) => write!(f, "failed to seek file: {}", e),
SettingFileSize(e) => write!(f, "failed to set file size: {}", e),
SettingRefcountRefcount(e) => write!(f, "failed to set refcount refcount: {}", e),
SizeTooSmallForNumberOfClusters => write!(f, "size too small for number of clusters"),
TooManyL1Entries(count) => write!(f, "l1 entry table too large: {}", count),
TooManyRefcounts(count) => write!(f, "ref count table too large: {}", count),
UnsupportedRefcountOrder => write!(f, "unsupported refcount order"),
UnsupportedVersion(v) => write!(f, "unsupported version: {}", v),
WritingData(e) => write!(f, "failed to write data: {}", e),
@@ -108,11 +129,21 @@ pub enum ImageType {
Qcow2,
}
// Maximum data size supported.
const MAX_QCOW_FILE_SIZE: u64 = 0x01 << 44; // 16 TB.
// QCOW magic constant that starts the header.
const QCOW_MAGIC: u32 = 0x5146_49fb;
// Default to a cluster size of 2^DEFAULT_CLUSTER_BITS
const DEFAULT_CLUSTER_BITS: u32 = 16;
const MAX_CLUSTER_BITS: u32 = 30;
// Limit clusters to reasonable sizes. Choose the same limits as qemu. Making the clusters smaller
// increases the amount of overhead for book keeping.
const MIN_CLUSTER_BITS: u32 = 9;
const MAX_CLUSTER_BITS: u32 = 21;
// The L1 and RefCount table are kept in RAM, only handle files that require less than 35M entries.
// This easily covers 1 TB files. When support for bigger files is needed the assumptions made to
// keep these tables in RAM needs to be thrown out.
const MAX_RAM_POINTER_TABLE_SIZE: u64 = 35_000_000;
// Only support 2 byte refcounts, 2^refcount_order bits.
const DEFAULT_REFCOUNT_ORDER: u32 = 4;
@@ -319,11 +350,15 @@ impl QcowHeader {
}
}
fn max_refcount_clusters(refcount_order: u32, cluster_size: u32, num_clusters: u32) -> usize {
let refcount_bytes = (0x01u32 << refcount_order) / 8;
let for_data = div_round_up_u32(num_clusters * refcount_bytes, cluster_size);
let for_refcounts = div_round_up_u32(for_data * refcount_bytes, cluster_size);
for_data as usize + for_refcounts as usize
fn max_refcount_clusters(refcount_order: u32, cluster_size: u32, num_clusters: u32) -> u64 {
// Use u64 as the product of the u32 inputs can overflow.
let refcount_bytes = (0x01 << u64::from(refcount_order)) / 8;
let for_data = div_round_up_u64(
u64::from(num_clusters) * refcount_bytes,
u64::from(cluster_size),
);
let for_refcounts = div_round_up_u64(for_data * refcount_bytes, u64::from(cluster_size));
for_data + for_refcounts
}
/// Represents a qcow2 file. This is a sparse file format maintained by the qemu project.
@@ -368,14 +403,20 @@ impl QcowFile {
return Err(Error::UnsupportedVersion(header.version));
}
// Make sure that the L1 table fits in RAM.
if u64::from(header.l1_size) > MAX_RAM_POINTER_TABLE_SIZE {
return Err(Error::InvalidL1TableSize(header.l1_size));
}
let cluster_bits: u32 = header.cluster_bits;
if cluster_bits > MAX_CLUSTER_BITS {
if cluster_bits < MIN_CLUSTER_BITS || cluster_bits > MAX_CLUSTER_BITS {
return Err(Error::InvalidClusterSize);
}
let cluster_size = 0x01u64 << cluster_bits;
if cluster_size < size_of::<u64>() as u64 {
// Can't fit an offset in a cluster, nothing is going to work.
return Err(Error::InvalidClusterSize);
// Limit the total size of the disk.
if header.size > MAX_QCOW_FILE_SIZE {
return Err(Error::FileTooBig(header.size));
}
// No current support for backing files.
@@ -398,8 +439,13 @@ impl QcowFile {
}
offset_is_cluster_boundary(header.backing_file_offset, header.cluster_bits)?;
offset_is_cluster_boundary(header.l1_table_offset, header.cluster_bits)?;
offset_is_cluster_boundary(header.refcount_table_offset, header.cluster_bits)?;
offset_is_cluster_boundary(header.snapshots_offset, header.cluster_bits)?;
// refcount table must be a cluster boundary, and within the file's virtual or actual size.
offset_is_cluster_boundary(header.refcount_table_offset, header.cluster_bits)?;
let file_size = file.metadata().map_err(Error::GettingFileSize)?.len();
if header.refcount_table_offset > max(file_size, header.size) {
return Err(Error::RefcountTableOffEnd);
}
// The first cluster should always have a non-zero refcount, so if it is 0,
// this is an old file with broken refcounts, which requires a rebuild.
@@ -432,6 +478,9 @@ impl QcowFile {
let num_l2_clusters = div_round_up_u64(num_clusters, l2_size);
let l1_clusters = div_round_up_u64(num_l2_clusters, cluster_size);
let header_clusters = div_round_up_u64(size_of::<QcowHeader>() as u64, cluster_size);
if num_l2_clusters > MAX_RAM_POINTER_TABLE_SIZE {
return Err(Error::TooManyL1Entries(num_l2_clusters));
}
let l1_table = VecCache::from_vec(
raw_file
.read_pointer_table(
@@ -447,7 +496,14 @@ impl QcowFile {
header.refcount_order,
cluster_size as u32,
(num_clusters + l1_clusters + num_l2_clusters + header_clusters) as u32,
) as u64;
);
// Check that the given header doesn't have a suspiciously sized refcount table.
if u64::from(header.refcount_table_clusters) > 2 * refcount_clusters {
return Err(Error::RefcountTableTooLarge);
}
if l1_clusters + refcount_clusters > MAX_RAM_POINTER_TABLE_SIZE {
return Err(Error::TooManyRefcounts(refcount_clusters));
}
let refcount_block_entries = cluster_size / refcount_bytes;
let refcounts = RefCount::new(
&mut raw_file,
@@ -716,7 +772,7 @@ impl QcowFile {
while refcounts[first_free_cluster as usize] != 0 {
first_free_cluster += 1;
if first_free_cluster >= refcounts.len() as u64 {
return Err(Error::InvalidRefcountTableSize);
return Err(Error::NotEnoughSpaceForRefcounts);
}
}
@@ -816,13 +872,13 @@ impl QcowFile {
max_valid_cluster_index += refblock_clusters + reftable_clusters;
max_valid_cluster_index += refblocks_for_refs + reftable_clusters_for_refs;
if max_valid_cluster_index > usize::max_value() as u64 {
return Err(Error::InvalidRefcountTableSize);
if max_valid_cluster_index > MAX_RAM_POINTER_TABLE_SIZE {
return Err(Error::InvalidRefcountTableSize(max_valid_cluster_index));
}
let max_valid_cluster_offset = max_valid_cluster_index * cluster_size;
if max_valid_cluster_offset < file_size - cluster_size {
return Err(Error::InvalidRefcountTableSize);
return Err(Error::InvalidRefcountTableSize(max_valid_cluster_offset));
}
let mut refcounts = vec![0; max_valid_cluster_index as usize];
@@ -1025,20 +1081,16 @@ impl QcowFile {
fn get_new_cluster(&mut self) -> std::io::Result<u64> {
// First use a pre allocated cluster if one is available.
if let Some(free_cluster) = self.avail_clusters.pop() {
let cluster_size = self.raw_file.cluster_size() as usize;
self.raw_file
.file_mut()
.seek(SeekFrom::Start(free_cluster))?;
self.raw_file.file_mut().write_zeroes(cluster_size)?;
self.raw_file.zero_cluster(free_cluster)?;
return Ok(free_cluster);
}
let max_valid_cluster_offset = self.refcounts.max_valid_cluster_offset();
if let Some(new_cluster) = self.raw_file.add_cluster_end(max_valid_cluster_offset)? {
return Ok(new_cluster);
Ok(new_cluster)
} else {
error!("No free clusters in get_new_cluster()");
return Err(std::io::Error::from_raw_os_error(ENOSPC));
Err(std::io::Error::from_raw_os_error(ENOSPC))
}
}
@@ -1514,12 +1566,12 @@ fn offset_is_cluster_boundary(offset: u64, cluster_bits: u32) -> Result<()> {
// Ceiling of the division of `dividend`/`divisor`.
fn div_round_up_u64(dividend: u64, divisor: u64) -> u64 {
(dividend + divisor - 1) / divisor
dividend / divisor + if dividend % divisor != 0 { 1 } else { 0 }
}
// Ceiling of the division of `dividend`/`divisor`.
fn div_round_up_u32(dividend: u32, divisor: u32) -> u32 {
(dividend + divisor - 1) / divisor
dividend / divisor + if dividend % divisor != 0 { 1 } else { 0 }
}
fn convert_copy<R, W>(reader: &mut R, writer: &mut W, offset: u64, size: u64) -> Result<()>
@@ -1693,13 +1745,37 @@ mod tests {
]
}
// Test case found by clusterfuzz to allocate excessive memory.
fn test_huge_header() -> Vec<u8> {
vec![
0x51, 0x46, 0x49, 0xfb, // magic
0x00, 0x00, 0x00, 0x03, // version
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // backing file offset
0x00, 0x00, 0x00, 0x00, // backing file size
0x00, 0x00, 0x00, 0x09, // cluster_bits
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, // size
0x00, 0x00, 0x00, 0x00, // crypt method
0x00, 0x00, 0x01, 0x00, // L1 size
0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, // L1 table offset
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, // refcount table offset
0x00, 0x00, 0x00, 0x03, // refcount table clusters
0x00, 0x00, 0x00, 0x00, // nb snapshots
0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, // snapshots offset
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // incompatible_features
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // compatible_features
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // autoclear_features
0x00, 0x00, 0x00, 0x04, // refcount_order
0x00, 0x00, 0x00, 0x68, // header_length
]
}
fn with_basic_file<F>(header: &[u8], mut testfn: F)
where
F: FnMut(File),
{
let mut disk_file: File = tempfile().unwrap();
disk_file.write_all(&header).unwrap();
disk_file.set_len(0x5_0000).unwrap();
disk_file.set_len(0x1_0000_0000).unwrap();
disk_file.seek(SeekFrom::Start(0)).unwrap();
testfn(disk_file); // File closed when the function exits.
@@ -1770,6 +1846,90 @@ mod tests {
});
}
#[test]
fn invalid_cluster_bits() {
let mut header = valid_header_v3();
header[23] = 3;
with_basic_file(&header, |disk_file: File| {
QcowFile::from(disk_file).expect_err("Failed to create file.");
});
}
#[test]
fn test_header_huge_file() {
let header = test_huge_header();
with_basic_file(&header, |disk_file: File| {
QcowFile::from(disk_file).expect_err("Failed to create file.");
});
}
#[test]
fn test_header_crazy_file_size_rejected() {
let mut header = valid_header_v3();
&mut header[24..32].copy_from_slice(&[0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1e]);
with_basic_file(&header, |disk_file: File| {
QcowFile::from(disk_file).expect_err("Failed to create file.");
});
}
#[test]
fn test_huge_l1_table() {
let mut header = valid_header_v3();
header[36] = 0x12;
with_basic_file(&header, |disk_file: File| {
QcowFile::from(disk_file).expect_err("Failed to create file.");
});
}
#[test]
fn test_header_1_tb_file_min_cluster() {
let mut header = test_huge_header();
header[24] = 0;
header[26] = 1;
header[31] = 0;
// 1 TB with the min cluster size makes the arrays too big, it should fail.
with_basic_file(&header, |disk_file: File| {
QcowFile::from(disk_file).expect_err("Failed to create file.");
});
}
#[test]
fn test_header_1_tb_file() {
let mut header = test_huge_header();
// reset to 1 TB size.
header[24] = 0;
header[26] = 1;
header[31] = 0;
// set cluster_bits
header[23] = 16;
with_basic_file(&header, |disk_file: File| {
let mut qcow = QcowFile::from(disk_file).expect("Failed to create file.");
qcow.seek(SeekFrom::Start(0x100_0000_0000 - 8))
.expect("Failed to seek.");
let value = 0x0000_0040_3f00_ffffu64;
qcow.write_all(&value.to_le_bytes())
.expect("failed to write data");
});
}
#[test]
fn test_header_huge_num_refcounts() {
let mut header = valid_header_v3();
&mut header[56..60].copy_from_slice(&[0x02, 0x00, 0xe8, 0xff]);
with_basic_file(&header, |disk_file: File| {
QcowFile::from(disk_file).expect_err("Created disk with crazy refcount clusters");
});
}
#[test]
fn test_header_huge_refcount_offset() {
let mut header = valid_header_v3();
&mut header[48..56].copy_from_slice(&[0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x02, 0x00]);
with_basic_file(&header, |disk_file: File| {
QcowFile::from(disk_file).expect_err("Created disk with crazy refcount offset");
});
}
#[test]
fn write_read_start() {
with_basic_file(&valid_header_v3(), |disk_file: File| {

View File

@@ -7,6 +7,7 @@ use std::io::{self, BufWriter, Seek, SeekFrom};
use std::mem::size_of;
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use vmm_sys_util::write_zeroes::WriteZeroes;
/// A qcow file. Allows reading/writing clusters and appending clusters.
#[derive(Debug)]
@@ -133,6 +134,14 @@ impl QcowRawFile {
pub fn cluster_offset(&self, address: u64) -> u64 {
address & self.cluster_mask
}
/// Zeros out a cluster in the file.
pub fn zero_cluster(&mut self, address: u64) -> io::Result<()> {
let cluster_size = self.cluster_size as usize;
self.file.seek(SeekFrom::Start(address))?;
self.file.write_zeroes(cluster_size)?;
Ok(())
}
}
impl Clone for QcowRawFile {

View File

@@ -1,3 +1,74 @@
- [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)
- [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)
# v0.2.0
This release has been tracked through the [0.2.0 project](https://github.com/intel/cloud-hypervisor/projects/2).
Highlights for `cloud-hypervisor` version 0.2.0 include:
### Network device offloading
As part of our general effort to offload paravirtualized I/O to external
processes, we added support for
[vhost-user-net](https://access.redhat.com/solutions/3394851) backends. This
enables `cloud-hypervisor` users to plug a `vhost-user` based networking device
(e.g. [DPDK](https://dpdk.org)) into the VMM as their virtio network backend.
### Minimal hardware-reduced ACPI
In order to properly implement and guest reset and shutdown, we implemented
a minimal version of the hardware-reduced ACPI specification. Together with
a tiny I/O port based ACPI device, this allows `cloud-hypervisor` guests to
cleanly reboot and shutdown.
The ACPI implementation is a `cloud-hypervisor` build time option that is
enabled by default.
### Debug I/O port
Based on the Firecracker idea of using a dedicated I/O port to measure guest
boot times, we added support for logging guest events through the
[0x80](https://www.intel.com/content/www/us/en/support/articles/000005500/boards-and-kits.html)
PC debug port. This allows, among other things, for granular guest boot time
measurements. See our [debug port documentation](https://github.com/intel/cloud-hypervisor/blob/master/docs/debug-port.md)
for more details.
### Improved direct device assignment
We fixed a major performance issue with our initial VFIO implementation: When
enabling VT-d through the KVM and VFIO APIs, our guest memory writes and reads
were (in many cases) not cached. After correctly tagging the guest memory from
`cloud-hypervisor` we're now able to reach the expected performance from
directly assigned devices.
### Improved shared filesystem
We added shared memory region with [DAX](https://www.kernel.org/doc/Documentation/filesystems/dax.txt)
support to our [virtio-fs](https://virtio-fs.gitlab.io/) shared file system.
This provides better shared filesystem IO performance with a smaller guest
memory footprint.
### Ubuntu bionic based CI
Thanks to our [simple KVM firmware](https://github.com/intel/rust-hypervisor-firmware)
improvements, we are now able to boot Ubuntu bionic images. We added those to
our CI pipeline.
# v0.1.0
This release has been tracked through the [0.1.0 project](https://github.com/intel/cloud-hypervisor/projects/1).

View File

@@ -1,14 +1,15 @@
#
# Automatically generated file; DO NOT EDIT.
# Linux/x86 5.2.0-rc5 Kernel Configuration
# Linux/x86 5.3.0-rc3 Kernel Configuration
#
#
# Compiler: gcc (Ubuntu 7.4.0-1ubuntu1~18.04) 7.4.0
# Compiler: gcc (Ubuntu 7.4.0-1ubuntu1~18.04.1) 7.4.0
#
CONFIG_CC_IS_GCC=y
CONFIG_GCC_VERSION=70400
CONFIG_CLANG_VERSION=0
CONFIG_CC_CAN_LINK=y
CONFIG_CC_HAS_ASM_GOTO=y
CONFIG_CC_HAS_WARN_MAYBE_UNINITIALIZED=y
CONFIG_IRQ_WORK=y
@@ -20,6 +21,7 @@ CONFIG_THREAD_INFO_IN_TASK=y
#
CONFIG_INIT_ENV_ARG_LIMIT=32
# CONFIG_COMPILE_TEST is not set
# CONFIG_HEADER_TEST is not set
CONFIG_LOCALVERSION=""
# CONFIG_LOCALVERSION_AUTO is not set
CONFIG_BUILD_SALT=""
@@ -128,6 +130,12 @@ CONFIG_LOG_BUF_SHIFT=21
CONFIG_LOG_CPU_MAX_BUF_SHIFT=12
CONFIG_PRINTK_SAFE_LOG_BUF_SHIFT=13
CONFIG_HAVE_UNSTABLE_SCHED_CLOCK=y
#
# Scheduler features
#
# end of Scheduler features
CONFIG_ARCH_SUPPORTS_NUMA_BALANCING=y
CONFIG_ARCH_WANT_BATCHED_UNMAP_TLB_FLUSH=y
CONFIG_ARCH_SUPPORTS_INT128=y
@@ -140,7 +148,6 @@ CONFIG_MEMCG_SWAP=y
CONFIG_MEMCG_SWAP_ENABLED=y
CONFIG_MEMCG_KMEM=y
CONFIG_BLK_CGROUP=y
# CONFIG_DEBUG_BLK_CGROUP is not set
CONFIG_CGROUP_WRITEBACK=y
CONFIG_CGROUP_SCHED=y
CONFIG_FAIR_GROUP_SCHED=y
@@ -255,7 +262,6 @@ CONFIG_NEED_PER_CPU_EMBED_FIRST_CHUNK=y
CONFIG_NEED_PER_CPU_PAGE_FIRST_CHUNK=y
CONFIG_ARCH_HIBERNATION_POSSIBLE=y
CONFIG_ARCH_SUSPEND_POSSIBLE=y
CONFIG_ARCH_WANT_HUGE_PMD_SHARE=y
CONFIG_ARCH_WANT_GENERAL_HUGETLB=y
CONFIG_ZONE_DMA32=y
CONFIG_AUDIT_ARCH=y
@@ -294,6 +300,7 @@ CONFIG_KVM_DEBUG_FS=y
CONFIG_PARAVIRT_TIME_ACCOUNTING=y
CONFIG_PARAVIRT_CLOCK=y
# CONFIG_JAILHOUSE_GUEST is not set
# CONFIG_ACRN_GUEST is not set
# CONFIG_MK8 is not set
# CONFIG_MPSC is not set
# CONFIG_MCORE2 is not set
@@ -310,6 +317,7 @@ CONFIG_CPU_SUP_INTEL=y
CONFIG_CPU_SUP_AMD=y
CONFIG_CPU_SUP_HYGON=y
CONFIG_CPU_SUP_CENTAUR=y
CONFIG_CPU_SUP_ZHAOXIN=y
CONFIG_HPET_TIMER=y
CONFIG_DMI=y
# CONFIG_GART_IOMMU is not set
@@ -399,6 +407,7 @@ CONFIG_HOTPLUG_CPU=y
# CONFIG_BOOTPARAM_HOTPLUG_CPU0 is not set
# CONFIG_DEBUG_HOTPLUG_CPU0 is not set
CONFIG_LEGACY_VSYSCALL_EMULATE=y
# CONFIG_LEGACY_VSYSCALL_XONLY is not set
# CONFIG_LEGACY_VSYSCALL_NONE is not set
# CONFIG_CMDLINE_BOOL is not set
CONFIG_MODIFY_LDT_SYSCALL=y
@@ -541,8 +550,6 @@ CONFIG_AMD_NB=y
# CONFIG_X86_X32 is not set
# end of Binary Emulations
CONFIG_HAVE_GENERIC_GUP=y
#
# Firmware Drivers
#
@@ -551,7 +558,6 @@ CONFIG_FIRMWARE_MEMMAP=y
CONFIG_DMIID=y
CONFIG_DMI_SYSFS=y
CONFIG_DMI_SCAN_MACHINE_NON_EFI_FALLBACK=y
# CONFIG_ISCSI_IBFT_FIND is not set
# CONFIG_FW_CFG_SYSFS is not set
# CONFIG_GOOGLE_FIRMWARE is not set
@@ -640,6 +646,7 @@ CONFIG_HAVE_MOVE_PMD=y
CONFIG_HAVE_ARCH_TRANSPARENT_HUGEPAGE=y
CONFIG_HAVE_ARCH_TRANSPARENT_HUGEPAGE_PUD=y
CONFIG_HAVE_ARCH_HUGE_VMAP=y
CONFIG_ARCH_WANT_HUGE_PMD_SHARE=y
CONFIG_HAVE_ARCH_SOFT_DIRTY=y
CONFIG_HAVE_MOD_ARCH_SPECIFIC=y
CONFIG_MODULES_USE_ELF_RELA=y
@@ -766,6 +773,7 @@ CONFIG_SPARSEMEM_EXTREME=y
CONFIG_SPARSEMEM_VMEMMAP_ENABLE=y
CONFIG_SPARSEMEM_VMEMMAP=y
CONFIG_HAVE_MEMBLOCK_NODE_MAP=y
CONFIG_HAVE_FAST_GUP=y
CONFIG_MEMORY_ISOLATION=y
CONFIG_HAVE_BOOTMEM_INFO_NODE=y
CONFIG_MEMORY_HOTPLUG=y
@@ -802,10 +810,11 @@ CONFIG_ZSMALLOC_STAT=y
CONFIG_GENERIC_EARLY_IOREMAP=y
# CONFIG_DEFERRED_STRUCT_PAGE_INIT is not set
# CONFIG_IDLE_PAGE_TRACKING is not set
CONFIG_ARCH_HAS_ZONE_DEVICE=y
# CONFIG_ZONE_DEVICE is not set
CONFIG_ARCH_HAS_HMM_MIRROR=y
CONFIG_ARCH_HAS_HMM_DEVICE=y
CONFIG_ARCH_HAS_PTE_DEVMAP=y
CONFIG_ZONE_DEVICE=y
CONFIG_DEV_PAGEMAP_OPS=y
# CONFIG_HMM_MIRROR is not set
# CONFIG_DEVICE_PRIVATE is not set
CONFIG_PERCPU_STATS=y
# CONFIG_GUP_BENCHMARK is not set
CONFIG_ARCH_HAS_PTE_SPECIAL=y
@@ -965,6 +974,7 @@ CONFIG_PCI_LOCKLESS_CONFIG=y
# CONFIG_PCI_IOV is not set
# CONFIG_PCI_PRI is not set
# CONFIG_PCI_PASID is not set
# CONFIG_PCI_P2PDMA is not set
CONFIG_PCI_LABEL=y
# CONFIG_HOTPLUG_PCI is not set
@@ -1016,9 +1026,11 @@ CONFIG_PREVENT_FIRMWARE_BUILD=y
# Firmware loader
#
CONFIG_FW_LOADER=y
CONFIG_FW_LOADER_PAGED_BUF=y
CONFIG_EXTRA_FIRMWARE=""
CONFIG_FW_LOADER_USER_HELPER=y
# CONFIG_FW_LOADER_USER_HELPER_FALLBACK is not set
# CONFIG_FW_LOADER_COMPRESS is not set
# end of Firmware loader
CONFIG_ALLOW_DEV_COREDUMP=y
@@ -1093,6 +1105,7 @@ CONFIG_VIRTIO_BLK=y
# CONFIG_HP_ILO is not set
# CONFIG_SRAM is not set
# CONFIG_PCI_ENDPOINT_TEST is not set
# CONFIG_XILINX_SDFEC is not set
# CONFIG_PVPANIC is not set
# CONFIG_C2PORT is not set
@@ -1710,6 +1723,8 @@ CONFIG_DMA_ACPI=y
CONFIG_DW_DMAC_CORE=y
# CONFIG_DW_DMAC is not set
# CONFIG_DW_DMAC_PCI is not set
# CONFIG_DW_EDMA is not set
# CONFIG_DW_EDMA_PCIE is not set
CONFIG_HSU_DMA=y
#
@@ -1867,12 +1882,10 @@ CONFIG_IOMMU_SUPPORT=y
#
# IRQ chip support
#
CONFIG_ARM_GIC_MAX_NR=1
# end of IRQ chip support
# CONFIG_IPACK_BUS is not set
# CONFIG_RESET_CONTROLLER is not set
# CONFIG_FMC is not set
#
# PHY Subsystem
@@ -1906,6 +1919,9 @@ CONFIG_ND_BLK=y
CONFIG_ND_CLAIM=y
CONFIG_ND_BTT=y
CONFIG_BTT=y
CONFIG_ND_PFN=y
CONFIG_NVDIMM_PFN=y
CONFIG_NVDIMM_DAX=y
CONFIG_DAX_DRIVER=y
CONFIG_DAX=y
# CONFIG_DEV_DAX is not set
@@ -1950,7 +1966,8 @@ CONFIG_FS_MBCACHE=y
# CONFIG_BTRFS_FS is not set
# CONFIG_NILFS2_FS is not set
# CONFIG_F2FS_FS is not set
# CONFIG_FS_DAX is not set
CONFIG_FS_DAX=y
CONFIG_FS_DAX_PMD=y
CONFIG_FS_POSIX_ACL=y
CONFIG_EXPORTFS=y
# CONFIG_EXPORTFS_BLOCK_OPS is not set
@@ -2014,6 +2031,7 @@ CONFIG_PROC_KCORE=y
CONFIG_PROC_SYSCTL=y
CONFIG_PROC_PAGE_MONITOR=y
CONFIG_PROC_CHILDREN=y
CONFIG_PROC_PID_ARCH_STATUS=y
CONFIG_KERNFS=y
CONFIG_SYSFS=y
CONFIG_TMPFS=y
@@ -2088,6 +2106,7 @@ CONFIG_NLS_UTF8=y
# Security options
#
CONFIG_KEYS=y
# CONFIG_KEYS_REQUEST_CACHE is not set
CONFIG_PERSISTENT_KEYRINGS=y
# CONFIG_BIG_KEYS is not set
# CONFIG_ENCRYPTED_KEYS is not set
@@ -2111,6 +2130,8 @@ CONFIG_LSM="yama,loadpin,safesetid,integrity"
# Memory initialization
#
CONFIG_INIT_STACK_NONE=y
# CONFIG_INIT_ON_ALLOC_DEFAULT_ON is not set
# CONFIG_INIT_ON_FREE_DEFAULT_ON is not set
# end of Memory initialization
# end of Kernel hardening options
# end of Security options
@@ -2144,7 +2165,6 @@ CONFIG_CRYPTO_GF128MUL=y
CONFIG_CRYPTO_NULL=y
CONFIG_CRYPTO_NULL2=y
# CONFIG_CRYPTO_PCRYPT is not set
CONFIG_CRYPTO_WORKQUEUE=y
CONFIG_CRYPTO_CRYPTD=y
# CONFIG_CRYPTO_AUTHENC is not set
CONFIG_CRYPTO_SIMD=y
@@ -2210,6 +2230,7 @@ CONFIG_CRYPTO_CRC32C=y
CONFIG_CRYPTO_CRC32C_INTEL=y
# CONFIG_CRYPTO_CRC32 is not set
# CONFIG_CRYPTO_CRC32_PCLMUL is not set
# CONFIG_CRYPTO_XXHASH is not set
CONFIG_CRYPTO_CRCT10DIF=y
# CONFIG_CRYPTO_CRCT10DIF_PCLMUL is not set
CONFIG_CRYPTO_GHASH=y
@@ -2243,6 +2264,7 @@ CONFIG_CRYPTO_AES=y
CONFIG_CRYPTO_AES_X86_64=y
CONFIG_CRYPTO_AES_NI_INTEL=y
# CONFIG_CRYPTO_ANUBIS is not set
CONFIG_CRYPTO_LIB_ARC4=y
CONFIG_CRYPTO_ARC4=y
# CONFIG_CRYPTO_BLOWFISH is not set
# CONFIG_CRYPTO_BLOWFISH_X86_64 is not set
@@ -2354,15 +2376,18 @@ CONFIG_HAS_DMA=y
CONFIG_NEED_SG_DMA_LENGTH=y
CONFIG_NEED_DMA_MAP_STATE=y
CONFIG_ARCH_DMA_ADDR_T_64BIT=y
CONFIG_ARCH_HAS_FORCE_DMA_UNENCRYPTED=y
CONFIG_SWIOTLB=y
# CONFIG_DMA_API_DEBUG is not set
CONFIG_SGL_ALLOC=y
CONFIG_CPU_RMAP=y
CONFIG_DQL=y
CONFIG_NLATTR=y
# CONFIG_DDR is not set
CONFIG_IRQ_POLL=y
# CONFIG_DIMLIB is not set
CONFIG_UCS2_STRING=y
CONFIG_HAVE_GENERIC_VDSO=y
CONFIG_GENERIC_GETTIMEOFDAY=y
CONFIG_FONT_SUPPORT=y
# CONFIG_FONTS is not set
CONFIG_FONT_8x8=y
@@ -2400,7 +2425,7 @@ CONFIG_STRIP_ASM_SYMS=y
# CONFIG_READABLE_ASM is not set
CONFIG_UNUSED_SYMBOLS=y
CONFIG_DEBUG_FS=y
# CONFIG_HEADERS_CHECK is not set
# CONFIG_HEADERS_INSTALL is not set
CONFIG_OPTIMIZE_INLINING=y
CONFIG_DEBUG_SECTION_MISMATCH=y
CONFIG_SECTION_MISMATCH_WARN_ONLY=y
@@ -2526,6 +2551,7 @@ CONFIG_RUNTIME_TESTING_MENU=y
# CONFIG_TEST_SORT is not set
# CONFIG_BACKTRACE_SELF_TEST is not set
# CONFIG_RBTREE_TEST is not set
# CONFIG_REED_SOLOMON_TEST is not set
# CONFIG_INTERVAL_TREE_TEST is not set
# CONFIG_ATOMIC64_SELFTEST is not set
# CONFIG_TEST_HEXDUMP is not set
@@ -2547,6 +2573,7 @@ CONFIG_RUNTIME_TESTING_MENU=y
# CONFIG_TEST_UDELAY is not set
# CONFIG_TEST_MEMCAT_P is not set
# CONFIG_TEST_STACKINIT is not set
# CONFIG_TEST_MEMINIT is not set
# CONFIG_MEMTEST is not set
CONFIG_BUG_ON_DATA_CORRUPTION=y
# CONFIG_SAMPLES is not set
@@ -2569,15 +2596,10 @@ CONFIG_EARLY_PRINTK=y
CONFIG_DOUBLEFAULT=y
# CONFIG_DEBUG_TLBFLUSH is not set
CONFIG_HAVE_MMIOTRACE_SUPPORT=y
CONFIG_IO_DELAY_TYPE_0X80=0
CONFIG_IO_DELAY_TYPE_0XED=1
CONFIG_IO_DELAY_TYPE_UDELAY=2
CONFIG_IO_DELAY_TYPE_NONE=3
CONFIG_IO_DELAY_0X80=y
# CONFIG_IO_DELAY_0XED is not set
# CONFIG_IO_DELAY_UDELAY is not set
# CONFIG_IO_DELAY_NONE is not set
CONFIG_DEFAULT_IO_DELAY_TYPE=0
# CONFIG_DEBUG_BOOT_PARAMS is not set
# CONFIG_CPA_DEBUG is not set
# CONFIG_DEBUG_ENTRY is not set

13
scripts/create-cloud-init.sh Executable file
View File

@@ -0,0 +1,13 @@
#!/bin/bash
set -x
rm /tmp/clear-cloudinit.img
mkdosfs -n config-2 -C /tmp/clear-cloudinit.img 8192
mcopy -oi /tmp/clear-cloudinit.img -s test_data/cloud-init/clear/openstack ::
rm /tmp/ubuntu-cloudinit.img
mkdosfs -n cidata -C /tmp/ubuntu-cloudinit.img 8192
mcopy -oi /tmp/ubuntu-cloudinit.img -s test_data/cloud-init/ubuntu/user-data ::
mcopy -oi /tmp/ubuntu-cloudinit.img -s test_data/cloud-init/ubuntu/meta-data ::
mcopy -oi /tmp/ubuntu-cloudinit.img -s test_data/cloud-init/ubuntu/network-config ::

View File

@@ -22,21 +22,38 @@ if [ ! -f "$OVMF" ]; then
popd
fi
OS_IMAGE_NAME="clear-29810-cloud.img"
OS_IMAGE_URL="https://cloudhypervisorstorage.blob.core.windows.net/images/$OS_IMAGE_NAME.xz"
OS_IMAGE="$WORKLOADS_DIR/$OS_IMAGE_NAME"
if [ ! -f "$OS_IMAGE" ]; then
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="$WORKLOADS_DIR/$CLEAR_OS_IMAGE_NAME"
if [ ! -f "$CLEAR_OS_IMAGE" ]; then
pushd $WORKLOADS_DIR
wget --quiet $OS_IMAGE_URL
unxz $OS_IMAGE_NAME.xz
wget --quiet $CLEAR_OS_IMAGE_URL
unxz $CLEAR_OS_IMAGE_NAME.xz
popd
fi
OS_RAW_IMAGE_NAME="clear-29810-cloud-raw.img"
OS_RAW_IMAGE="$WORKLOADS_DIR/$OS_RAW_IMAGE_NAME"
if [ ! -f "$OS_RAW_IMAGE" ]; then
CLEAR_OS_RAW_IMAGE_NAME="clear-29810-cloud-raw.img"
CLEAR_OS_RAW_IMAGE="$WORKLOADS_DIR/$CLEAR_OS_RAW_IMAGE_NAME"
if [ ! -f "$CLEAR_OS_RAW_IMAGE" ]; then
pushd $WORKLOADS_DIR
qemu-img convert -p -f qcow2 -O raw $OS_IMAGE_NAME $OS_RAW_IMAGE_NAME
qemu-img convert -p -f qcow2 -O raw $CLEAR_OS_IMAGE_NAME $CLEAR_OS_RAW_IMAGE_NAME
popd
fi
BIONIC_OS_IMAGE_NAME="bionic-server-cloudimg-amd64.img"
BIONIC_OS_IMAGE_URL="https://cloudhypervisorstorage.blob.core.windows.net/images/$BIONIC_OS_IMAGE_NAME"
BIONIC_OS_IMAGE="$WORKLOADS_DIR/$BIONIC_OS_IMAGE_NAME"
if [ ! -f "$BIONIC_OS_IMAGE" ]; then
pushd $WORKLOADS_DIR
wget --quiet $BIONIC_OS_IMAGE_URL
popd
fi
BIONIC_OS_RAW_IMAGE_NAME="bionic-server-cloudimg-amd64-raw.img"
BIONIC_OS_RAW_IMAGE="$WORKLOADS_DIR/$BIONIC_OS_RAW_IMAGE_NAME"
if [ ! -f "$BIONIC_OS_RAW_IMAGE" ]; then
pushd $WORKLOADS_DIR
qemu-img convert -p -f qcow2 -O raw $BIONIC_OS_IMAGE_NAME $BIONIC_OS_RAW_IMAGE_NAME
popd
fi
@@ -50,24 +67,31 @@ 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-pmem_and_virtio-fs" $LINUX_CUSTOM_DIR
git clone --depth 1 "https://github.com/sboeuf/linux.git" -b "virtio-fs" $LINUX_CUSTOM_DIR
pushd $LINUX_CUSTOM_DIR
cp $SRCDIR/resources/linux-virtio-pmem-and-virtio-fs-config .config
cp $SRCDIR/resources/linux-virtio-fs-config .config
make bzImage -j `nproc`
cp vmlinux $VMLINUX_IMAGE
cp arch/x86/boot/bzImage $BZIMAGE_IMAGE
popd
rm -r $LINUX_CUSTOM_DIR
rm -rf $LINUX_CUSTOM_DIR
popd
fi
VIRTIOFSD_URL="$(curl --silent https://api.github.com/repos/intel/nemu/releases/latest | grep "browser_download_url" | grep "virtiofsd-x86_64" | grep -o 'https://.*[^ "]')"
VIRTIOFSD="$WORKLOADS_DIR/virtiofsd"
if [ ! -f "$VIRTIOFSD" ]; then
VUBRIDGE="$WORKLOADS_DIR/vubridge"
QEMU_DIR="qemu_build"
if [ ! -f "$VIRTIOFSD" ] || [ ! -f "$VUBRIDGE" ]; then
pushd $WORKLOADS_DIR
wget --quiet $VIRTIOFSD_URL -O "virtiofsd"
chmod +x "virtiofsd"
sudo setcap cap_sys_admin+epi "virtiofsd"
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`
cp virtiofsd $VIRTIOFSD
cp tests/vhost-user-bridge $VUBRIDGE
popd
rm -rf $QEMU_DIR
sudo setcap cap_dac_override,cap_sys_admin+epi "virtiofsd"
popd
fi
@@ -81,7 +105,7 @@ fi
VFIO_DIR="$WORKLOADS_DIR/vfio"
if [ ! -d "$VFIO_DIR" ]; then
mkdir -p $VFIO_DIR
cp $OS_IMAGE $VFIO_DIR
cp $CLEAR_OS_IMAGE $VFIO_DIR
cp $FW $VFIO_DIR
cp $VMLINUX_IMAGE $VFIO_DIR
fi
@@ -110,10 +134,14 @@ 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"
EOF
RES=$?
# Tear VFIO test network down
sudo ip link del vfio-br0
sudo ip link del vfio-tap0
sudo ip link del vfio-tap1
exit $RES

View File

@@ -13,8 +13,12 @@ pushd target/debug
ls | grep net_util | grep -v "\.d" | xargs -n 1 sudo setcap cap_net_admin,cap_net_raw+ep
popd
for f in $(find . -name Cargo.toml -printf '%h\n' | sort -u); do
pushd $f > /dev/null;
sudo adduser $USER kvm
newgrp kvm << EOF || exit 1
for f in \$(find . -name Cargo.toml -printf '%h\n' | sort -u); do
pushd \$f > /dev/null;
export RUST_BACKTRACE=1
cargo test || exit 1;
popd > /dev/null;
done
EOF

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
#cloud-config
users:
- name: admin
- name: cloud
passwd: $6$7125787751a8d18a$sHwGySomUA1PawiNFWVCKYQN.Ec.Wzz0JtPPL1MvzFrkwmop2dq7.4CYf03A5oemPQ4pOFCCrtCelvFBEle/K.
sudo:
- ALL=(ALL) NOPASSWD:ALL

View File

@@ -0,0 +1,2 @@
instance-id: cloud
local-hostname: cloud

View File

@@ -0,0 +1,12 @@
network:
version: 1
config:
- type: physical
name: eth0
mac_address: 12:34:56:78:90:ab
subnets:
- type: static
address: 192.168.2.2/24
gateway: 192.168.2.1
dns_nameservers:
- 192.168.2.1

View File

@@ -0,0 +1,10 @@
#cloud-config
users:
- name: cloud
passwd: $6$7125787751a8d18a$sHwGySomUA1PawiNFWVCKYQN.Ec.Wzz0JtPPL1MvzFrkwmop2dq7.4CYf03A5oemPQ4pOFCCrtCelvFBEle/K.
sudo: ALL=(ALL) NOPASSWD:ALL
lock_passwd: False
inactive: False
shell: /bin/bash
ssh_pwauth: True

View File

@@ -4,12 +4,12 @@ version = "0.0.1"
authors = ["The Cloud Hypervisor Authors"]
[dependencies]
byteorder = ">=1.2.1"
byteorder = "1.3.2"
devices = { path = "../devices" }
kvm-bindings = "0.1"
kvm-bindings = "0.1.1"
kvm-ioctls = { git = "https://github.com/rust-vmm/kvm-ioctls", branch = "master" }
libc = ">=0.2.39"
log = "*"
libc = "0.2.60"
log = "0.4.8"
pci = { path = "../pci" }
vfio-bindings = { path = "../vfio-bindings" }
vm-allocator = { path = "../vm-allocator" }

View File

@@ -9,6 +9,7 @@ extern crate byteorder;
extern crate devices;
extern crate kvm_bindings;
extern crate kvm_ioctls;
#[macro_use]
extern crate log;
extern crate pci;
extern crate vfio_bindings;

View File

@@ -14,13 +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::sync::Arc;
use std::sync::{Arc, RwLock};
use std::u32;
use vfio_bindings::bindings::vfio::*;
use vfio_ioctls::*;
use vm_memory::{Address, GuestMemory, GuestMemoryMmap, GuestMemoryRegion};
use vmm_sys_util::eventfd::EventFd;
use vmm_sys_util::ioctl::*;
use vmm_sys_util::EventFd;
#[derive(Debug)]
pub enum VfioError {
@@ -513,14 +513,18 @@ pub struct VfioDevice {
group: VfioGroup,
regions: Vec<VfioRegion>,
irqs: HashMap<u32, VfioIrq>,
mem: GuestMemoryMmap,
mem: Arc<RwLock<GuestMemoryMmap>>,
}
impl VfioDevice {
/// Create a new vfio device, then guest read/write on this device could be
/// transfered into kernel vfio.
/// sysfspath specify the vfio device path in sys file system.
pub fn new(sysfspath: &Path, device_fd: Arc<DeviceFd>, mem: GuestMemoryMmap) -> Result<Self> {
pub fn new(
sysfspath: &Path,
device_fd: Arc<DeviceFd>,
mem: Arc<RwLock<GuestMemoryMmap>>,
) -> Result<Self> {
let uuid_path: PathBuf = [sysfspath, Path::new("iommu_group")].iter().collect();
let group_path = uuid_path.read_link().map_err(|_| VfioError::InvalidPath)?;
let group_osstr = group_path.file_name().ok_or(VfioError::InvalidPath)?;
@@ -769,7 +773,7 @@ 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.with_regions(|_index, region| {
self.mem.read().unwrap().with_regions(|_index, region| {
self.vfio_dma_map(
region.start_addr().raw_value(),
region.len() as u64,
@@ -782,7 +786,7 @@ impl VfioDevice {
/// 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.with_regions(|_index, region| {
self.mem.read().unwrap().with_regions(|_index, region| {
self.vfio_dma_unmap(region.start_addr().raw_value(), region.len() as u64)
})?;
Ok(())

View File

@@ -27,7 +27,7 @@ use std::{fmt, io};
use vfio_bindings::bindings::vfio::*;
use vm_allocator::SystemAllocator;
use vm_memory::{Address, GuestAddress, GuestUsize};
use vmm_sys_util::EventFd;
use vmm_sys_util::eventfd::EventFd;
#[derive(Debug)]
pub enum VfioPciError {
@@ -207,12 +207,14 @@ impl Interrupt {
fn msix_write_table(&mut self, offset: u64, data: &[u8]) {
if let Some(ref mut msix) = &mut self.msix {
let offset = offset - u64::from(msix.cap.table_offset());
msix.bar.write_table(offset, data)
}
}
fn msix_read_table(&self, offset: u64, data: &mut [u8]) {
if let Some(msix) = &self.msix {
let offset = offset - u64::from(msix.cap.table_offset());
msix.bar.read_table(offset, data)
}
}
@@ -386,11 +388,9 @@ impl VfioPciDevice {
..Default::default()
};
unsafe {
entry.u.msi.address_lo = route.msi_vector.msg_addr_lo;
entry.u.msi.address_hi = route.msi_vector.msg_addr_hi;
entry.u.msi.data = route.msi_vector.msg_data;
};
entry.u.msi.address_lo = route.msi_vector.msg_addr_lo;
entry.u.msi.address_hi = route.msi_vector.msg_addr_hi;
entry.u.msi.data = route.msi_vector.msg_data;
entry_vec.push(entry);
}
@@ -694,8 +694,6 @@ impl BusDevice for VfioPciDevice {
// First BAR offset in the PCI config space.
const PCI_CONFIG_BAR_OFFSET: u32 = 0x10;
// First BAR register index
const PCI_CONFIG_BAR0_INDEX: usize = 4;
// Capability register offset in the PCI config space.
const PCI_CONFIG_CAPABILITY_OFFSET: u32 = 0x34;
// IO BAR when first BAR bit is 1.
@@ -708,6 +706,10 @@ const PCI_CONFIG_MEMORY_BAR_64BIT: u32 = 0x4;
const PCI_CONFIG_REGISTER_SIZE: usize = 4;
// Number of BARs for a PCI device
const BAR_NUMS: usize = 6;
// PCI Header Type register index
const PCI_HEADER_TYPE_REG_INDEX: usize = 3;
// First BAR register index
const PCI_CONFIG_BAR0_INDEX: usize = 4;
// PCI ROM expansion BAR register index
const PCI_ROM_EXP_BAR_INDEX: usize = 12;
// PCI interrupt pin and line register index
@@ -726,14 +728,18 @@ impl PciDevice for VfioPciDevice {
// We're not saving the BAR address to restore it, because we
// are going to allocate a guest address for each BAR and write
// that new address back.
while bar_id < VFIO_PCI_ROM_REGION_INDEX {
while bar_id < VFIO_PCI_CONFIG_REGION_INDEX {
let mut lsb_size: u32 = 0xffff_ffff;
let mut msb_size = 0;
let mut region_size: u64;
let bar_addr: GuestAddress;
// Read the BAR size (Starts by all 1s to the BAR)
let bar_offset = PCI_CONFIG_BAR_OFFSET + bar_id * 4;
let bar_offset = if bar_id == VFIO_PCI_ROM_REGION_INDEX {
(PCI_ROM_EXP_BAR_INDEX * 4) as u32
} else {
PCI_CONFIG_BAR_OFFSET + bar_id * 4
};
self.vfio_pci_configuration
.write_config_dword(lsb_size, bar_offset);
@@ -748,15 +754,23 @@ impl PciDevice for VfioPciDevice {
}
// Is this an IO BAR?
let io_bar = match lsb_flag & PCI_CONFIG_IO_BAR {
PCI_CONFIG_IO_BAR => true,
_ => false,
let io_bar = if bar_id != VFIO_PCI_ROM_REGION_INDEX {
match lsb_flag & PCI_CONFIG_IO_BAR {
PCI_CONFIG_IO_BAR => true,
_ => false,
}
} else {
false
};
// Is this a 64-bit BAR?
let is_64bit_bar = match lsb_flag & PCI_CONFIG_MEMORY_BAR_64BIT {
PCI_CONFIG_MEMORY_BAR_64BIT => true,
_ => false,
let is_64bit_bar = if bar_id != VFIO_PCI_ROM_REGION_INDEX {
match lsb_flag & PCI_CONFIG_MEMORY_BAR_64BIT {
PCI_CONFIG_MEMORY_BAR_64BIT => true,
_ => false,
}
} else {
false
};
// By default, the region type is 32 bits memory BAR.
@@ -808,14 +822,15 @@ impl PciDevice for VfioPciDevice {
// In case the BAR is mappable directly, this means it might be
// set as KVM user memory region, which expects to deal with 4K
// pages. Therefore, the aligment has to be set accordingly.
let bar_alignment =
if self.device.get_region_flags(bar_id) & VFIO_REGION_INFO_FLAG_MMAP != 0 {
// 4K alignment
0x1000
} else {
// Default 16 bytes alignment
0x10
};
let bar_alignment = if (bar_id == VFIO_PCI_ROM_REGION_INDEX)
|| (self.device.get_region_flags(bar_id) & VFIO_REGION_INFO_FLAG_MMAP != 0)
{
// 4K alignment
0x1000
} else {
// Default 16 bytes alignment
0x10
};
if is_64bit_bar {
bar_addr = allocator
.allocate_mmio_addresses(None, region_size, Some(bar_alignment))
@@ -827,16 +842,28 @@ impl PciDevice for VfioPciDevice {
}
}
let reg_idx = if bar_id == VFIO_PCI_ROM_REGION_INDEX {
PCI_ROM_EXP_BAR_INDEX
} else {
bar_id as usize
};
// We can now build our BAR configuration block.
let config = PciBarConfiguration::default()
.set_register_index(bar_id as usize)
.set_register_index(reg_idx)
.set_address(bar_addr.raw_value())
.set_size(region_size)
.set_region_type(region_type);
self.configuration
.add_pci_bar(&config)
.map_err(|e| PciDeviceError::IoRegistrationFailed(bar_addr.raw_value(), e))?;
if bar_id == VFIO_PCI_ROM_REGION_INDEX {
self.configuration
.add_pci_rom_bar(&config, lsb_flag & 0x1)
.map_err(|e| PciDeviceError::IoRegistrationFailed(bar_addr.raw_value(), e))?;
} else {
self.configuration
.add_pci_bar(&config)
.map_err(|e| PciDeviceError::IoRegistrationFailed(bar_addr.raw_value(), e))?;
}
ranges.push((bar_addr, region_size, region_type));
self.mmio_regions.push(MmioRegion {
@@ -862,7 +889,9 @@ impl PciDevice for VfioPciDevice {
// When the guest wants to write to a BAR, we trap it into
// our local configuration space. We're not reprogramming
// VFIO device.
if reg_idx >= PCI_CONFIG_BAR0_INDEX && reg_idx < PCI_CONFIG_BAR0_INDEX + BAR_NUMS {
if (reg_idx >= PCI_CONFIG_BAR0_INDEX && reg_idx < PCI_CONFIG_BAR0_INDEX + BAR_NUMS)
|| reg_idx == PCI_ROM_EXP_BAR_INDEX
{
// We keep our local cache updated with the BARs.
// We'll read it back from there when the guest is asking
// for BARs (see read_config_register()).
@@ -900,23 +929,25 @@ impl PciDevice for VfioPciDevice {
// from our local configuration space. We want the guest to
// use that and not the VFIO device BARs as it does not map
// with the guest address space.
if reg_idx >= PCI_CONFIG_BAR0_INDEX && reg_idx < PCI_CONFIG_BAR0_INDEX + BAR_NUMS {
if (reg_idx >= PCI_CONFIG_BAR0_INDEX && reg_idx < PCI_CONFIG_BAR0_INDEX + BAR_NUMS)
|| reg_idx == PCI_ROM_EXP_BAR_INDEX
{
return self.configuration.read_reg(reg_idx);
}
// Since the ROM expansion BAR is not yet handled by the code, it is
// more proper to expose it to the guest as being disabled.
if reg_idx == PCI_ROM_EXP_BAR_INDEX {
return 0;
}
// Since we don't support INTx (only MSI and MSI-X), we should not
// expose an invalid Interrupt Pin to the guest. By using a specific
// mask in case the register being read correspond to the interrupt
// register, this code makes sure to always expose an Interrupt Pin
// value of 0, which stands for no interrupt pin support.
//
// Since we don't support passing multi-functions devices, we should
// mask the multi-function bit, bit 7 of the Header Type byte on the
// register 3.
let mask = if reg_idx == PCI_INTX_REG_INDEX {
0xffff_00ff
} else if reg_idx == PCI_HEADER_TYPE_REG_INDEX {
0xff7f_ffff
} else {
0xffff_ffff
};

View File

@@ -13,8 +13,8 @@ vhost-user-master = []
vhost-user-slave = []
[dependencies]
bitflags = ">=1.0.1"
libc = ">=0.2.39"
bitflags = "1.1.0"
libc = "0.2.60"
vmm-sys-util = { git = "https://github.com/rust-vmm/vmm-sys-util" }
[dependencies.vm-memory]
@@ -22,4 +22,4 @@ git = "https://github.com/rust-vmm/vm-memory"
optional = true
[dev-dependencies]
tempfile = "3.0.5"
tempfile = "3.1.0"

View File

@@ -11,7 +11,7 @@
use super::Result;
use std::os::unix::io::RawFd;
use vmm_sys_util::EventFd;
use vmm_sys_util::eventfd::EventFd;
/// Maximum number of memory regions supported.
pub const VHOST_MAX_MEMORY_REGIONS: usize = 255;

View File

@@ -15,8 +15,8 @@ use std::os::unix::io::{AsRawFd, RawFd};
use std::ptr::null;
use vm_memory::{Address, GuestAddress, GuestMemory, GuestUsize};
use vmm_sys_util::eventfd::EventFd;
use vmm_sys_util::ioctl::{ioctl, ioctl_with_mut_ref, ioctl_with_ptr, ioctl_with_ref};
use vmm_sys_util::EventFd;
use super::{
Error, Result, VhostBackend, VhostUserMemoryRegionInfo, VringConfigData,

View File

@@ -197,6 +197,45 @@ 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

@@ -8,7 +8,7 @@ use std::os::unix::io::{AsRawFd, RawFd};
use std::os::unix::net::UnixStream;
use std::sync::{Arc, Mutex};
use vmm_sys_util::EventFd;
use vmm_sys_util::eventfd::EventFd;
use super::connection::Endpoint;
use super::message::*;
@@ -352,7 +352,7 @@ impl VhostUserMaster for Master {
let mut node = self.node.lock().unwrap();
// depends on VhostUserProtocolFeatures::CONFIG
if node.acked_virtio_features & VhostUserProtocolFeatures::CONFIG.bits() == 0 {
if node.acked_protocol_features & VhostUserProtocolFeatures::CONFIG.bits() == 0 {
return error_code(VhostUserError::InvalidOperation);
}
@@ -360,7 +360,7 @@ impl VhostUserMaster for Master {
// "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_body(MasterReq::GET_CONFIG, &body, None)?;
let hdr = node.send_request_with_config_body(MasterReq::GET_CONFIG, &body, None)?;
let (reply, buf, rfds) = node.recv_reply_with_payload::<VhostUserConfig>(&hdr)?;
if rfds.is_some() {
Endpoint::<MasterReq>::close_rfds(rfds);
@@ -384,11 +384,11 @@ impl VhostUserMaster for Master {
let mut node = self.node.lock().unwrap();
// depends on VhostUserProtocolFeatures::CONFIG
if node.acked_virtio_features & VhostUserProtocolFeatures::CONFIG.bits() == 0 {
if node.acked_protocol_features & VhostUserProtocolFeatures::CONFIG.bits() == 0 {
return error_code(VhostUserError::InvalidOperation);
}
let hdr = node.send_request_with_payload(MasterReq::GET_CONFIG, &body, buf, None)?;
let hdr = node.send_request_with_payload(MasterReq::SET_CONFIG, &body, buf, None)?;
node.wait_for_ack(&hdr).map_err(|e| e.into())
}
@@ -480,6 +480,27 @@ 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,
@@ -555,7 +576,7 @@ impl MasterInternal {
if !reply.is_reply_for(hdr)
|| reply.get_size() as usize != mem::size_of::<T>() + bytes
|| rfds.is_some()
|| body.is_valid()
|| !body.is_valid()
{
Endpoint::<MasterReq>::close_rfds(rfds);
return Err(VhostUserError::InvalidMessage);
@@ -603,7 +624,7 @@ impl MasterInternal {
#[inline]
fn new_request_header(request: MasterReq, size: u32) -> VhostUserMsgHeader<MasterReq> {
// TODO: handle NEED_REPLY flag
VhostUserMsgHeader::new(request, 0, size)
VhostUserMsgHeader::new(request, 0x1, size)
}
}

View File

@@ -109,14 +109,14 @@ impl<S: VhostUserMasterReqHandler> MasterReqHandler<S> {
}
};
match hdr.get_code() {
let res = match hdr.get_code() {
SlaveReq::CONFIG_CHANGE_MSG => {
self.check_msg_size(&hdr, size, 0)?;
self.backend
.lock()
.unwrap()
.handle_config_change()
.map_err(Error::ReqHandlerError)?;
.map_err(Error::ReqHandlerError)
}
SlaveReq::FS_MAP => {
let msg = self.extract_msg_body::<VhostUserFSSlaveMsg>(&hdr, size, &buf)?;
@@ -124,7 +124,7 @@ impl<S: VhostUserMasterReqHandler> MasterReqHandler<S> {
.lock()
.unwrap()
.fs_slave_map(msg, rfds.unwrap()[0])
.map_err(Error::ReqHandlerError)?;
.map_err(Error::ReqHandlerError)
}
SlaveReq::FS_UNMAP => {
let msg = self.extract_msg_body::<VhostUserFSSlaveMsg>(&hdr, size, &buf)?;
@@ -132,7 +132,7 @@ impl<S: VhostUserMasterReqHandler> MasterReqHandler<S> {
.lock()
.unwrap()
.fs_slave_unmap(msg)
.map_err(Error::ReqHandlerError)?;
.map_err(Error::ReqHandlerError)
}
SlaveReq::FS_SYNC => {
let msg = self.extract_msg_body::<VhostUserFSSlaveMsg>(&hdr, size, &buf)?;
@@ -140,14 +140,14 @@ impl<S: VhostUserMasterReqHandler> MasterReqHandler<S> {
.lock()
.unwrap()
.fs_slave_sync(msg)
.map_err(Error::ReqHandlerError)?;
.map_err(Error::ReqHandlerError)
}
_ => {
return Err(Error::InvalidMessage);
}
}
_ => Err(Error::InvalidMessage),
};
Ok(())
self.send_ack_message(&hdr, &res)?;
res
}
fn check_state(&self) -> Result<()> {
@@ -217,6 +217,38 @@ impl<S: VhostUserMasterReqHandler> MasterReqHandler<S> {
}
Ok(msg)
}
fn new_reply_header<T: Sized>(
&self,
req: &VhostUserMsgHeader<SlaveReq>,
) -> Result<VhostUserMsgHeader<SlaveReq>> {
if mem::size_of::<T>() > MAX_MSG_SIZE {
return Err(Error::InvalidParam);
}
self.check_state()?;
Ok(VhostUserMsgHeader::new(
req.get_code(),
VhostUserHeaderFlag::REPLY.bits(),
mem::size_of::<T>() as u32,
))
}
fn send_ack_message(
&mut self,
req: &VhostUserMsgHeader<SlaveReq>,
res: &Result<()>,
) -> Result<()> {
if req.is_need_reply() {
let hdr = self.new_reply_header::<VhostUserU64>(req)?;
let val = match res {
Ok(_) => 0,
Err(_) => 1,
};
let msg = VhostUserU64::new(val);
self.sub_sock.send_message(&hdr, &msg, None)?;
}
Ok(())
}
}
impl<S: VhostUserMasterReqHandler> AsRawFd for MasterReqHandler<S> {

View File

@@ -540,12 +540,10 @@ impl VhostUserMsgValidator for VhostUserVringAddr {
bitflags! {
/// Flags for the device configuration message.
pub struct VhostUserConfigFlags: u32 {
/// TODO: seems the vhost-user spec has refined the definition, EMPTY is removed.
const EMPTY = 0x0;
/// Vhost master messages used for writable fields
const WRITABLE = 0x1;
/// Mark that message is part of an ongoing live-migration operation.
const LIVE_MIGRATION = 0x2;
/// Vhost master messages used for writeable fields.
const WRITABLE = 0x0;
/// Vhost master messages used for live migration.
const LIVE_MIGRATION = 0x1;
}
}
@@ -577,10 +575,9 @@ impl VhostUserMsgValidator for VhostUserConfig {
fn is_valid(&self) -> bool {
if (self.flags & !VhostUserConfigFlags::all().bits()) != 0 {
return false;
} else if self.offset < VHOST_USER_CONFIG_OFFSET
|| self.offset >= VHOST_USER_CONFIG_SIZE
} else if self.offset >= VHOST_USER_CONFIG_SIZE
|| self.size == 0
|| self.size > (VHOST_USER_CONFIG_SIZE - VHOST_USER_CONFIG_OFFSET)
|| self.size > VHOST_USER_CONFIG_SIZE
|| self.size + self.offset > VHOST_USER_CONFIG_SIZE
{
return false;
@@ -787,7 +784,7 @@ mod tests {
let mut msg = VhostUserConfig::new(
VHOST_USER_CONFIG_OFFSET,
VHOST_USER_CONFIG_SIZE - VHOST_USER_CONFIG_OFFSET,
VhostUserConfigFlags::EMPTY,
VhostUserConfigFlags::WRITABLE,
);
assert!(msg.is_valid());
@@ -804,7 +801,7 @@ mod tests {
msg.size = 2;
assert!(!msg.is_valid());
msg.size = 1;
msg.flags |= VhostUserConfigFlags::WRITABLE.bits();
msg.flags |= VhostUserConfigFlags::LIVE_MIGRATION.bits();
assert!(msg.is_valid());
msg.flags |= 0x4;
assert!(!msg.is_valid());

View File

@@ -116,10 +116,10 @@ impl Error {
}
}
impl std::convert::From<vmm_sys_util::Error> for Error {
impl std::convert::From<vmm_sys_util::errno::Error> for Error {
/// Convert raw socket errors into meaningful vhost-user errors.
///
/// The vmm_sys_util::Error is a simple wrapper over the raw errno, which doesn't means much
/// The vmm_sys_util::errno::Error is a simple wrapper over the raw errno, which doesn't means much
/// to the vhost-user connection manager. So convert it into meaningful errors to simplify
/// the connection manager logic.
///
@@ -128,7 +128,7 @@ impl std::convert::From<vmm_sys_util::Error> for Error {
/// * - Error::SocketBroken: the underline socket is broken.
/// * - Error::SocketError: other socket related errors.
#[allow(unreachable_patterns)] // EWOULDBLOCK equals to EGAIN on linux
fn from(err: vmm_sys_util::Error) -> Self {
fn from(err: vmm_sys_util::errno::Error) -> Self {
match err.errno() {
// The socket is marked nonblocking and the requested operation would block.
libc::EAGAIN => Error::SocketRetry(IOError::from_raw_os_error(libc::EAGAIN)),

View File

@@ -16,7 +16,7 @@ use std::ptr::{copy_nonoverlapping, null_mut, write_unaligned};
use libc::{
c_long, c_void, cmsghdr, iovec, msghdr, recvmsg, sendmsg, MSG_NOSIGNAL, SCM_RIGHTS, SOL_SOCKET,
};
use vmm_sys_util::{Error, Result};
use vmm_sys_util::errno::{Error, Result};
// Each of the following macros performs the same function as their C counterparts. They are each
// macros because they are used to size statically allocated arrays.
@@ -176,7 +176,7 @@ fn raw_recvmsg(fd: RawFd, iovecs: &mut [iovec], in_fds: &mut [RawFd]) -> Result<
// Safe because the msghdr was properly constructed from valid (or null) pointers of the
// indicated length and we check the return value.
let total_read = unsafe { recvmsg(fd, &mut msg, 0) };
let total_read = unsafe { recvmsg(fd, &mut msg, libc::MSG_WAITALL) };
if total_read == -1 {
return Err(Error::last());
@@ -335,7 +335,7 @@ mod tests {
use libc::cmsghdr;
use vmm_sys_util::EventFd;
use vmm_sys_util::eventfd::EventFd;
#[test]
fn buffer_len() {

View File

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

View File

@@ -5,15 +5,15 @@ authors = ["Samuel Ortiz <sameo@linux.intel.com>"]
edition = "2018"
[dependencies]
byteorder = "=1.2.1"
byteorder = "1.3.2"
devices = { path = "../devices" }
epoll = "=4.0.1"
libc = ">=0.2.39"
log = "*"
epoll = "4.1.0"
libc = "0.2.60"
log = "0.4.8"
net_gen = { path = "../net_gen" }
net_util = { path = "../net_util" }
pci = { path = "../pci" }
tempfile = ">=3.0.2"
tempfile = "3.1.0"
virtio-bindings = { path = "../virtio-bindings" }
vm-allocator = { path = "../vm-allocator" }
vmm-sys-util = { git = "https://github.com/rust-vmm/vmm-sys-util" }

View File

@@ -17,19 +17,18 @@ use std::os::linux::fs::MetadataExt;
use std::os::unix::io::AsRawFd;
use std::path::PathBuf;
use std::result;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::sync::{Arc, RwLock};
use std::thread;
use super::Error as DeviceError;
use super::{
ActivateError, ActivateResult, DescriptorChain, DeviceEventT, Queue, VirtioDevice,
VirtioDeviceType, INTERRUPT_STATUS_USED_RING,
VirtioDeviceType, VirtioInterruptType,
};
use crate::VirtioInterrupt;
use virtio_bindings::virtio_blk::*;
use vm_memory::{Bytes, GuestAddress, GuestMemory, GuestMemoryError, GuestMemoryMmap};
use vmm_sys_util::EventFd;
use vmm_sys_util::eventfd::EventFd;
const CONFIG_SPACE_SIZE: usize = 8;
const SECTOR_SHIFT: u8 = 9;
@@ -322,10 +321,9 @@ impl Request {
struct BlockEpollHandler<T: DiskFile> {
queues: Vec<Queue>,
mem: GuestMemoryMmap,
mem: Arc<RwLock<GuestMemoryMmap>>,
disk_image: T,
disk_nsectors: u64,
interrupt_status: Arc<AtomicUsize>,
interrupt_cb: Arc<VirtioInterrupt>,
disk_image_id: Vec<u8>,
}
@@ -336,14 +334,15 @@ impl<T: DiskFile> BlockEpollHandler<T> {
let mut used_desc_heads = [(0, 0); QUEUE_SIZE as usize];
let mut used_count = 0;
for avail_desc in queue.iter(&self.mem) {
let mem = self.mem.read().unwrap();
for avail_desc in queue.iter(&mem) {
let len;
match Request::parse(&avail_desc, &self.mem) {
match Request::parse(&avail_desc, &mem) {
Ok(request) => {
let status = match request.execute(
&mut self.disk_image,
self.disk_nsectors,
&self.mem,
&mem,
&self.disk_image_id,
) {
Ok(l) => {
@@ -358,7 +357,7 @@ impl<T: DiskFile> BlockEpollHandler<T> {
};
// We use unwrap because the request parsing process already checked that the
// status_addr was valid.
self.mem.write_obj(status, request.status_addr).unwrap();
mem.write_obj(status, request.status_addr).unwrap();
}
Err(e) => {
error!("Failed to parse available descriptor chain: {:?}", e);
@@ -370,18 +369,18 @@ impl<T: DiskFile> BlockEpollHandler<T> {
}
for &(desc_index, len) in &used_desc_heads[..used_count] {
queue.add_used(&self.mem, desc_index, len);
queue.add_used(&mem, desc_index, len);
}
used_count > 0
}
fn signal_used_queue(&self, queue_index: usize) -> result::Result<(), DeviceError> {
self.interrupt_status
.fetch_or(INTERRUPT_STATUS_USED_RING as usize, Ordering::SeqCst);
(self.interrupt_cb)(&self.queues[queue_index]).map_err(|e| {
error!("Failed to signal used queue: {:?}", e);
DeviceError::FailedSignalingUsedQueue(e)
})
(self.interrupt_cb)(&VirtioInterruptType::Queue, Some(&self.queues[queue_index])).map_err(
|e| {
error!("Failed to signal used queue: {:?}", e);
DeviceError::FailedSignalingUsedQueue(e)
},
)
}
#[allow(dead_code)]
@@ -424,8 +423,22 @@ impl<T: DiskFile> BlockEpollHandler<T> {
let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN];
'epoll: loop {
let num_events =
epoll::wait(epoll_fd, -1, &mut events[..]).map_err(DeviceError::EpollWait)?;
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;
@@ -598,9 +611,8 @@ impl<T: 'static + DiskFile + Send> VirtioDevice for Block<T> {
fn activate(
&mut self,
mem: GuestMemoryMmap,
mem: Arc<RwLock<GuestMemoryMmap>>,
interrupt_cb: Arc<VirtioInterrupt>,
status: Arc<AtomicUsize>,
queues: Vec<Queue>,
mut queue_evts: Vec<EventFd>,
) -> ActivateResult {
@@ -644,7 +656,6 @@ impl<T: 'static + DiskFile + Send> VirtioDevice for Block<T> {
mem,
disk_image,
disk_nsectors: self.disk_nsectors,
interrupt_status: status,
interrupt_cb,
disk_image_id,
};

View File

@@ -10,18 +10,18 @@ use std::io;
use std::io::Write;
use std::os::unix::io::AsRawFd;
use std::result;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, Mutex};
use std::sync::{Arc, Mutex, RwLock};
use std::thread;
use super::Error as DeviceError;
use super::{
ActivateError, ActivateResult, DeviceEventT, Queue, VirtioDevice, VirtioDeviceType,
INTERRUPT_STATUS_USED_RING, VIRTIO_F_VERSION_1,
VirtioInterruptType, VIRTIO_F_VERSION_1,
};
use crate::VirtioInterrupt;
use vm_memory::{Bytes, GuestMemoryMmap};
use vmm_sys_util::EventFd;
use std::sync::atomic::{AtomicU64, Ordering};
use vm_memory::{ByteValued, Bytes, GuestMemoryMmap};
use vmm_sys_util::eventfd::EventFd;
const QUEUE_SIZE: u16 = 256;
const NUM_QUEUES: usize = 2;
@@ -34,17 +34,34 @@ const OUTPUT_QUEUE_EVENT: DeviceEventT = 1;
const INPUT_EVENT: DeviceEventT = 2;
// The device has been dropped.
const KILL_EVENT: DeviceEventT = 3;
// Console configuration change event is triggered.
const CONFIG_EVENT: DeviceEventT = 4;
//Console size feature bit
const VIRTIO_CONSOLE_F_SIZE: u64 = 0;
#[derive(Copy, Clone, Debug, Default)]
#[repr(C)]
pub struct VirtioConsoleConfig {
cols: u16,
rows: u16,
max_nr_ports: u32,
emerg_wr: u32,
}
// Safe because it only has data and has no implicit padding.
unsafe impl ByteValued for VirtioConsoleConfig {}
struct ConsoleEpollHandler {
queues: Vec<Queue>,
mem: GuestMemoryMmap,
interrupt_status: Arc<AtomicUsize>,
mem: Arc<RwLock<GuestMemoryMmap>>,
interrupt_cb: Arc<VirtioInterrupt>,
in_buffer: Arc<Mutex<VecDeque<u8>>>,
out: Box<io::Write + Send>,
out: Box<dyn io::Write + Send>,
input_queue_evt: EventFd,
output_queue_evt: EventFd,
input_evt: EventFd,
config_evt: EventFd,
kill_evt: EventFd,
}
@@ -63,14 +80,15 @@ impl ConsoleEpollHandler {
let mut used_count = 0;
let mut write_count = 0;
for avail_desc in recv_queue.iter(&self.mem) {
let mem = self.mem.read().unwrap();
for avail_desc in recv_queue.iter(&mem) {
let len;
let limit = cmp::min(write_count + avail_desc.len as u32, count as u32);
let source_slice = in_buffer
.drain(write_count as usize..limit as usize)
.collect::<Vec<u8>>();
let write_result = self.mem.write_slice(&source_slice[..], avail_desc.addr);
let write_result = mem.write_slice(&source_slice[..], avail_desc.addr);
match write_result {
Ok(_) => {
@@ -92,7 +110,7 @@ impl ConsoleEpollHandler {
}
for &(desc_index, len) in &used_desc_heads[..used_count] {
recv_queue.add_used(&self.mem, desc_index, len);
recv_queue.add_used(&mem, desc_index, len);
}
used_count > 0
}
@@ -109,11 +127,10 @@ impl ConsoleEpollHandler {
let mut used_desc_heads = [(0, 0); QUEUE_SIZE as usize];
let mut used_count = 0;
for avail_desc in trans_queue.iter(&self.mem) {
let mem = self.mem.read().unwrap();
for avail_desc in trans_queue.iter(&mem) {
let len;
let _ = self
.mem
.write_to(avail_desc.addr, &mut self.out, avail_desc.len as usize);
let _ = mem.write_to(avail_desc.addr, &mut self.out, avail_desc.len as usize);
let _ = self.out.flush();
len = avail_desc.len;
@@ -122,15 +139,13 @@ impl ConsoleEpollHandler {
}
for &(desc_index, len) in &used_desc_heads[..used_count] {
trans_queue.add_used(&self.mem, desc_index, len);
trans_queue.add_used(&mem, desc_index, len);
}
used_count > 0
}
fn signal_used_queue(&self) -> result::Result<(), DeviceError> {
self.interrupt_status
.fetch_or(INTERRUPT_STATUS_USED_RING as usize, Ordering::SeqCst);
(self.interrupt_cb)(&self.queues[0]).map_err(|e| {
(self.interrupt_cb)(&VirtioInterruptType::Queue, Some(&self.queues[0])).map_err(|e| {
error!("Failed to signal used queue: {:?}", e);
DeviceError::FailedSignalingUsedQueue(e)
})
@@ -162,6 +177,14 @@ impl ConsoleEpollHandler {
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(INPUT_EVENT)),
)
.map_err(DeviceError::EpollCtl)?;
epoll::ctl(
epoll_fd,
epoll::ControlOptions::EPOLL_CTL_ADD,
self.config_evt.as_raw_fd(),
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(CONFIG_EVENT)),
)
.map_err(DeviceError::EpollCtl)?;
epoll::ctl(
epoll_fd,
epoll::ControlOptions::EPOLL_CTL_ADD,
@@ -174,8 +197,22 @@ impl ConsoleEpollHandler {
let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN];
'epoll: loop {
let num_events =
epoll::wait(epoll_fd, -1, &mut events[..]).map_err(DeviceError::EpollWait)?;
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;
@@ -206,6 +243,17 @@ impl ConsoleEpollHandler {
}
}
}
CONFIG_EVENT => {
if let Err(e) = self.config_evt.read() {
error!("Failed to get config event: {:?}", e);
break 'epoll;
} else if let Err(e) =
(self.interrupt_cb)(&VirtioInterruptType::Config, None)
{
error!("Failed to signal console driver: {:?}", e);
}
}
KILL_EVENT => {
debug!("KILL_EVENT received, stopping epoll loop");
break 'epoll;
@@ -221,19 +269,13 @@ impl ConsoleEpollHandler {
}
}
/// Virtio device for exposing console to the guest OS through virtio.
pub struct Console {
kill_evt: Option<EventFd>,
avail_features: u64,
acked_features: u64,
input: Arc<ConsoleInput>,
out: Option<Box<io::Write + Send>>,
}
/// Input device.
pub struct ConsoleInput {
input_evt: EventFd,
config_evt: EventFd,
in_buffer: Arc<Mutex<VecDeque<u8>>>,
config: Arc<Mutex<VirtioConsoleConfig>>,
acked_features: AtomicU64,
}
impl ConsoleInput {
@@ -242,18 +284,64 @@ impl ConsoleInput {
in_buffer.extend(input);
let _ = self.input_evt.write(1);
}
pub fn update_console_size(&self, cols: u16, rows: u16) {
if self
.acked_features
.fetch_and(1u64 << VIRTIO_CONSOLE_F_SIZE, Ordering::SeqCst)
!= 0
{
self.config.lock().unwrap().update_console_size(cols, rows);
//Send the interrupt to the driver
let _ = self.config_evt.write(1);
}
}
}
impl VirtioConsoleConfig {
pub fn new(cols: u16, rows: u16) -> Self {
VirtioConsoleConfig {
cols,
rows,
max_nr_ports: 1u32,
emerg_wr: 0u32,
}
}
pub fn update_console_size(&mut self, cols: u16, rows: u16) {
self.cols = cols;
self.rows = rows;
}
}
/// Virtio device for exposing console to the guest OS through virtio.
pub struct Console {
kill_evt: Option<EventFd>,
avail_features: u64,
acked_features: u64,
config: Arc<Mutex<VirtioConsoleConfig>>,
input: Arc<ConsoleInput>,
out: Option<Box<dyn io::Write + Send>>,
}
impl Console {
/// Create a new virtio console device that gets random data from /dev/urandom.
pub fn new(out: Option<Box<io::Write + Send>>) -> io::Result<(Console, Arc<ConsoleInput>)> {
let avail_features = 1u64 << VIRTIO_F_VERSION_1;
pub fn new(
out: Option<Box<dyn io::Write + Send>>,
cols: u16,
rows: u16,
) -> io::Result<(Console, Arc<ConsoleInput>)> {
let avail_features = 1u64 << VIRTIO_F_VERSION_1 | 1u64 << VIRTIO_CONSOLE_F_SIZE;
let input_evt = EventFd::new(EFD_NONBLOCK).unwrap();
let config_evt = EventFd::new(EFD_NONBLOCK).unwrap();
let console_config = Arc::new(Mutex::new(VirtioConsoleConfig::new(cols, rows)));
let console_input = Arc::new(ConsoleInput {
input_evt,
config_evt,
in_buffer: Arc::new(Mutex::new(VecDeque::new())),
config: console_config.clone(),
acked_features: AtomicU64::new(0),
});
Ok((
@@ -261,6 +349,7 @@ impl Console {
kill_evt: None,
avail_features,
acked_features: 0u64,
config: console_config,
input: console_input.clone(),
out,
},
@@ -321,19 +410,30 @@ impl VirtioDevice for Console {
self.acked_features |= v;
}
fn read_config(&self, _offset: u64, _data: &mut [u8]) {
warn!("Device specific configuration is not defined yet");
fn read_config(&self, offset: u64, mut data: &mut [u8]) {
let config = self.config.lock().unwrap();
let config_slice = 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!("Device specific configuration is not defined yet");
warn!("No device specific configration requires write");
}
fn activate(
&mut self,
mem: GuestMemoryMmap,
mem: Arc<RwLock<GuestMemoryMmap>>,
interrupt_cb: Arc<VirtioInterrupt>,
status: Arc<AtomicUsize>,
queues: Vec<Queue>,
mut queue_evts: Vec<EventFd>,
) -> ActivateResult {
@@ -356,17 +456,27 @@ impl VirtioDevice for Console {
};
self.kill_evt = Some(self_kill_evt);
self.input
.acked_features
.store(self.acked_features, Ordering::Relaxed);
if (self.acked_features & (1u64 << VIRTIO_CONSOLE_F_SIZE)) != 0 {
if let Err(e) = (interrupt_cb)(&VirtioInterruptType::Config, None) {
error!("Failed to signal console driver: {:?}", e);
}
}
if let Some(out) = self.out.take() {
let mut handler = ConsoleEpollHandler {
queues,
mem,
interrupt_status: status,
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,
};

View File

@@ -7,13 +7,33 @@
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
use super::*;
use pci::{PciBarConfiguration, PciCapability};
use std::sync::atomic::AtomicUsize;
use std::sync::Arc;
use vm_memory::GuestMemoryMmap;
use vmm_sys_util::EventFd;
use std::sync::{Arc, RwLock};
use vm_memory::{GuestAddress, GuestMemoryMmap, GuestUsize};
use vmm_sys_util::eventfd::EventFd;
pub type VirtioInterrupt = Box<Fn(&Queue) -> std::result::Result<(), std::io::Error> + Send + Sync>;
pub enum VirtioInterruptType {
Config,
Queue,
}
pub type VirtioInterrupt = Box<
dyn Fn(&VirtioInterruptType, Option<&Queue>) -> std::result::Result<(), std::io::Error>
+ Send
+ Sync,
>;
#[derive(Clone)]
pub struct VirtioSharedMemory {
pub offset: u64,
pub len: u64,
}
#[derive(Clone)]
pub struct VirtioSharedMemoryList {
pub addr: GuestAddress,
pub len: GuestUsize,
pub region_list: Vec<VirtioSharedMemory>,
}
/// Trait for virtio devices to be driven by a virtio transport.
///
@@ -47,9 +67,8 @@ pub trait VirtioDevice: Send {
/// Activates this device for real usage.
fn activate(
&mut self,
mem: GuestMemoryMmap,
mem: Arc<RwLock<GuestMemoryMmap>>,
interrupt_evt: Arc<VirtioInterrupt>,
status: Arc<AtomicUsize>,
queues: Vec<Queue>,
queue_evts: Vec<EventFd>,
) -> ActivateResult;
@@ -60,13 +79,8 @@ pub trait VirtioDevice: Send {
None
}
/// Returns any additional BAR configuration required by the device.
fn get_device_bars(&self) -> Vec<PciBarConfiguration> {
Vec::new()
}
/// Returns any additional capabilities required by the device.
fn get_device_caps(&self) -> Vec<Box<dyn PciCapability>> {
Vec::new()
/// Returns the list of shared memory regions required by the device.
fn get_shm_regions(&self) -> Option<VirtioSharedMemoryList> {
None
}
}

View File

@@ -1,438 +0,0 @@
// Copyright 2019 Intel Corporation. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
use super::Error as DeviceError;
use super::{
ActivateError, ActivateResult, Queue, VirtioDevice, VirtioDeviceType,
INTERRUPT_STATUS_USED_RING,
};
use crate::{VirtioInterrupt, VIRTIO_F_VERSION_1_BITMASK};
use epoll;
use libc::EFD_NONBLOCK;
use std::cmp;
use std::io;
use std::io::Write;
use std::os::unix::io::AsRawFd;
use std::result;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::thread;
use vhost_rs::vhost_user::message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures};
use vhost_rs::vhost_user::{Master, VhostUserMaster};
use vhost_rs::{VhostBackend, VhostUserMemoryRegionInfo, VringConfigData};
use vm_memory::{Address, Error as MmapError, GuestMemory, GuestMemoryMmap, GuestMemoryRegion};
use vmm_sys_util::EventFd;
const CONFIG_SPACE_TAG_SIZE: usize = 36;
const CONFIG_SPACE_NUM_QUEUES_SIZE: usize = 4;
const CONFIG_SPACE_SIZE: usize = CONFIG_SPACE_TAG_SIZE + CONFIG_SPACE_NUM_QUEUES_SIZE;
const NUM_QUEUE_OFFSET: usize = 1;
#[derive(Debug)]
pub enum Error {
/// common
/// Invalid descriptor table address.
DescriptorTableAddress,
/// Invalid used address.
UsedAddress,
/// Invalid available address.
AvailAddress,
/// vhost
/// Creating kill eventfd failed.
CreateKillEventFd(io::Error),
/// Cloning kill eventfd failed.
CloneKillEventFd(io::Error),
/// Error while polling for events.
PollError(io::Error),
/// Failed to create irq eventfd.
IrqEventCreate(io::Error),
/// Failed to read vhost eventfd.
VhostIrqRead(io::Error),
/// vhost-user
/// Connection to socket failed.
VhostUserConnect(vhost_rs::Error),
/// Get features failed.
VhostUserGetFeatures(vhost_rs::Error),
/// Get protocol features failed.
VhostUserGetProtocolFeatures(vhost_rs::Error),
/// Set owner failed.
VhostUserSetOwner(vhost_rs::Error),
/// Set features failed.
VhostUserSetFeatures(vhost_rs::Error),
/// Set protocol features failed.
VhostUserSetProtocolFeatures(vhost_rs::Error),
/// Set mem table failed.
VhostUserSetMemTable(vhost_rs::Error),
/// Set vring num failed.
VhostUserSetVringNum(vhost_rs::Error),
/// Set vring addr failed.
VhostUserSetVringAddr(vhost_rs::Error),
/// Set vring base failed.
VhostUserSetVringBase(vhost_rs::Error),
/// Set vring call failed.
VhostUserSetVringCall(vhost_rs::Error),
/// Set vring kick failed.
VhostUserSetVringKick(vhost_rs::Error),
/// Invalid features provided from vhost-user backend.
InvalidFeatures,
/// Missing file descriptor.
FdMissing,
/// Failure going through memory regions.
MemoryRegions(MmapError),
}
type Result<T> = result::Result<T, Error>;
struct FsEpollHandler {
vu_call_evt_queue_list: Vec<(EventFd, Queue)>,
interrupt_status: Arc<AtomicUsize>,
interrupt_cb: Arc<VirtioInterrupt>,
kill_evt: EventFd,
}
impl FsEpollHandler {
fn run(&mut self) -> result::Result<(), DeviceError> {
// Create the epoll file descriptor
let epoll_fd = epoll::create(true).map_err(DeviceError::EpollCreateFd)?;
for (evt_index, vu_call_evt_queue) in self.vu_call_evt_queue_list.iter().enumerate() {
// Add events
epoll::ctl(
epoll_fd,
epoll::ControlOptions::EPOLL_CTL_ADD,
vu_call_evt_queue.0.as_raw_fd(),
epoll::Event::new(epoll::Events::EPOLLIN, evt_index as u64),
)
.map_err(DeviceError::EpollCtl)?;
}
let kill_evt_index = self.vu_call_evt_queue_list.len();
epoll::ctl(
epoll_fd,
epoll::ControlOptions::EPOLL_CTL_ADD,
self.kill_evt.as_raw_fd(),
epoll::Event::new(epoll::Events::EPOLLIN, kill_evt_index as u64),
)
.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 =
epoll::wait(epoll_fd, -1, &mut events[..]).map_err(DeviceError::EpollWait)?;
for event in events.iter().take(num_events) {
let ev_type = event.data as usize;
match ev_type {
x if (x < kill_evt_index) => {
if let Err(e) = self.vu_call_evt_queue_list[x].0.read() {
error!("Failed to get queue event: {:?}", e);
break 'epoll;
} else {
self.interrupt_status
.fetch_or(INTERRUPT_STATUS_USED_RING as usize, Ordering::SeqCst);
if let Err(e) = (self.interrupt_cb)(&self.vu_call_evt_queue_list[x].1) {
error!(
"Failed to signal used queue: {:?}",
DeviceError::FailedSignalingUsedQueue(e)
);
break 'epoll;
}
}
}
x if (x == kill_evt_index) => {
debug!("KILL_EVENT received, stopping epoll loop");
break 'epoll;
}
_ => {
error!("Unknown event for virtio-fs");
}
}
}
}
Ok(())
}
}
pub struct Fs {
vu: Master,
queue_sizes: Vec<u16>,
avail_features: u64,
acked_features: u64,
config_space: Vec<u8>,
kill_evt: Option<EventFd>,
}
impl Fs {
/// Create a new virtio-fs device.
pub fn new(path: &str, tag: &str, req_num_queues: usize, queue_size: u16) -> Result<Fs> {
// Calculate the actual number of queues needed.
let num_queues = NUM_QUEUE_OFFSET + req_num_queues;
// Connect to the vhost-user socket.
let mut master =
Master::connect(path, num_queues as u64).map_err(Error::VhostUserConnect)?;
// Retrieve available features only when connecting the first time.
let mut avail_features = master.get_features().map_err(Error::VhostUserGetFeatures)?;
// Let only ack features we expect, that is VIRTIO_F_VERSION_1.
if (avail_features & VIRTIO_F_VERSION_1_BITMASK) != VIRTIO_F_VERSION_1_BITMASK {
return Err(Error::InvalidFeatures);
}
avail_features =
VIRTIO_F_VERSION_1_BITMASK | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
master
.set_features(avail_features)
.map_err(Error::VhostUserSetFeatures)?;
// Identify if protocol features are supported by the slave.
if (avail_features & VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits())
== VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits()
{
let mut protocol_features = master
.get_protocol_features()
.map_err(Error::VhostUserGetProtocolFeatures)?;
protocol_features &= VhostUserProtocolFeatures::MQ;
master
.set_protocol_features(protocol_features)
.map_err(Error::VhostUserSetProtocolFeatures)?;
}
// Create virtio device config space.
// First by adding the tag.
let mut config_space = tag.to_string().into_bytes();
config_space.resize(CONFIG_SPACE_SIZE, 0);
// And then by copying the number of queues.
let num_queues_slice = (req_num_queues as u32).to_le_bytes();
config_space[CONFIG_SPACE_TAG_SIZE..CONFIG_SPACE_SIZE].copy_from_slice(&num_queues_slice);
Ok(Fs {
vu: master,
queue_sizes: vec![queue_size; num_queues],
avail_features,
acked_features: 0u64,
config_space,
kill_evt: None,
})
}
fn setup_vu(
&mut self,
mem: &GuestMemoryMmap,
queues: Vec<Queue>,
queue_evts: Vec<EventFd>,
) -> Result<Vec<(EventFd, Queue)>> {
// Set vhost-user owner.
self.vu.set_owner().map_err(Error::VhostUserSetOwner)?;
// Set backend features.
self.vu
.set_features(self.acked_features)
.map_err(Error::VhostUserSetFeatures)?;
let mut regions: Vec<VhostUserMemoryRegionInfo> = Vec::new();
mem.with_regions_mut(|_, region| {
let (mmap_handle, mmap_offset) = match region.file_offset() {
Some(fo) => (fo.file().as_raw_fd(), fo.start()),
None => return Err(MmapError::NoMemoryRegion),
};
let vu_mem_reg = VhostUserMemoryRegionInfo {
guest_phys_addr: region.start_addr().raw_value(),
memory_size: region.len() as u64,
userspace_addr: region.as_ptr() as u64,
mmap_offset,
mmap_handle,
};
regions.push(vu_mem_reg);
Ok(())
})
.map_err(Error::MemoryRegions)?;
self.vu
.set_mem_table(regions.as_slice())
.map_err(Error::VhostUserSetMemTable)?;
let mut result = Vec::new();
for (queue_index, queue) in queues.into_iter().enumerate() {
self.vu
.set_vring_num(queue_index, queue.get_max_size())
.map_err(Error::VhostUserSetVringNum)?;
let vring_config = VringConfigData {
queue_max_size: queue.get_max_size(),
queue_size: queue.size,
flags: 0u32,
desc_table_addr: mem
.get_host_address(queue.desc_table)
.ok_or_else(|| Error::DescriptorTableAddress)?
as u64,
used_ring_addr: mem
.get_host_address(queue.used_ring)
.ok_or_else(|| Error::UsedAddress)? as u64,
avail_ring_addr: mem
.get_host_address(queue.avail_ring)
.ok_or_else(|| Error::AvailAddress)? as u64,
log_addr: None,
};
self.vu
.set_vring_addr(queue_index, &vring_config)
.map_err(Error::VhostUserSetVringAddr)?;
self.vu
.set_vring_base(queue_index, 0u16)
.map_err(Error::VhostUserSetVringBase)?;
let vu_call_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::IrqEventCreate)?;
self.vu
.set_vring_call(queue_index, &vu_call_evt)
.map_err(Error::VhostUserSetVringCall)?;
result.push((vu_call_evt, queue));
self.vu
.set_vring_kick(queue_index, &queue_evts[queue_index])
.map_err(Error::VhostUserSetVringKick)?;
}
Ok(result)
}
}
impl Drop for Fs {
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 Fs {
fn device_type(&self) -> u32 {
VirtioDeviceType::TYPE_FS as u32
}
fn queue_max_sizes(&self) -> &[u16] {
&self.queue_sizes.as_slice()
}
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!("fs: 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!("fs: 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!("fs: virtio-fs got unknown feature ack: {: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;
}
let (_, right) = self.config_space.split_at_mut(offset as usize);
right.copy_from_slice(&data[..]);
}
fn activate(
&mut self,
mem: GuestMemoryMmap,
interrupt_cb: Arc<VirtioInterrupt>,
status: Arc<AtomicUsize>,
queues: Vec<Queue>,
queue_evts: Vec<EventFd>,
) -> ActivateResult {
if queues.len() != self.queue_sizes.len() || queue_evts.len() != self.queue_sizes.len() {
error!(
"Cannot perform activate. Expected {} queue(s), got {}",
self.queue_sizes.len(),
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);
let vu_call_evt_queue_list = self
.setup_vu(&mem, queues, queue_evts)
.map_err(ActivateError::VhostUserSetup)?;
let mut handler = FsEpollHandler {
vu_call_evt_queue_list,
interrupt_status: status,
interrupt_cb,
kill_evt,
};
let worker_result = thread::Builder::new()
.name("virtio_fs".to_string())
.spawn(move || handler.run());
if let Err(e) = worker_result {
error!("failed to spawn virtio_blk worker: {}", e);
return Err(ActivateError::BadActivate);
}
Ok(())
}
}

View File

@@ -13,6 +13,7 @@ extern crate epoll;
#[macro_use]
extern crate log;
extern crate pci;
extern crate vhost_rs;
extern crate virtio_bindings;
extern crate vm_memory;
@@ -22,18 +23,17 @@ use std::io;
mod block;
mod console;
mod device;
pub mod fs;
pub mod net;
mod pmem;
mod queue;
mod rng;
pub mod transport;
pub mod vhost_user;
pub use self::block::*;
pub use self::console::*;
pub use self::device::*;
pub use self::fs::*;
pub use self::net::*;
pub use self::pmem::*;
pub use self::queue::*;
@@ -47,7 +47,6 @@ const DEVICE_FEATURES_OK: u32 = 0x08;
const DEVICE_FAILED: u32 = 0x80;
const VIRTIO_F_VERSION_1: u32 = 32;
const VIRTIO_F_VERSION_1_BITMASK: u64 = 1 << VIRTIO_F_VERSION_1;
// Types taken from linux/virtio_ids.h
#[derive(Copy, Clone)]
@@ -117,9 +116,16 @@ const INTERRUPT_STATUS_CONFIG_CHANGED: u32 = 0x2;
pub enum ActivateError {
EpollCtl(std::io::Error),
BadActivate,
/// Queue number is not correct
BadQueueNum,
/// Failed to clone Kill event
CloneKillEventFd,
/// Failed to create Vhost-user interrupt eventfd
VhostIrqCreate,
/// Failed to setup vhost-user daemon.
VhostUserSetup(fs::Error),
VhostUserSetup(vhost_user::Error),
/// Failed to setup vhost-user daemon.
VhostUserNetSetup(vhost_user::Error),
}
pub type ActivateResult = std::result::Result<(), ActivateError>;
@@ -143,4 +149,5 @@ pub enum Error {
EpollCreateFd(io::Error),
EpollCtl(io::Error),
EpollWait(io::Error),
FailedSignalingDriver(io::Error),
}

View File

@@ -13,10 +13,9 @@ use std::io::Read;
use std::io::{self, Write};
use std::mem;
use std::net::Ipv4Addr;
use std::os::unix::io::AsRawFd;
use std::os::unix::io::{AsRawFd, RawFd};
use std::result;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::sync::{Arc, RwLock};
use std::thread;
use std::vec::Vec;
@@ -25,13 +24,13 @@ use net_gen;
use super::Error as DeviceError;
use super::{
ActivateError, ActivateResult, DeviceEventT, Queue, VirtioDevice, VirtioDeviceType,
INTERRUPT_STATUS_USED_RING,
VirtioInterruptType,
};
use crate::VirtioInterrupt;
use net_util::{MacAddr, Tap, TapError, MAC_ADDR_LEN};
use virtio_bindings::virtio_net::*;
use vm_memory::{Bytes, GuestAddress, GuestMemoryMmap};
use vmm_sys_util::EventFd;
use vmm_sys_util::eventfd::EventFd;
/// The maximum buffer size when segmentation offload is enabled. This
/// includes the 12-byte virtio net header.
@@ -116,20 +115,19 @@ fn vnet_hdr_len() -> usize {
}
struct NetEpollHandler {
mem: GuestMemoryMmap,
mem: Arc<RwLock<GuestMemoryMmap>>,
tap: Tap,
rx: RxVirtio,
tx: TxVirtio,
interrupt_status: Arc<AtomicUsize>,
interrupt_cb: Arc<VirtioInterrupt>,
kill_evt: EventFd,
epoll_fd: RawFd,
rx_tap_listening: bool,
}
impl NetEpollHandler {
fn signal_used_queue(&self, queue: &Queue) -> result::Result<(), DeviceError> {
self.interrupt_status
.fetch_or(INTERRUPT_STATUS_USED_RING as usize, Ordering::SeqCst);
(self.interrupt_cb)(queue).map_err(|e| {
(self.interrupt_cb)(&VirtioInterruptType::Queue, Some(queue)).map_err(|e| {
error!("Failed to signal used queue: {:?}", e);
DeviceError::FailedSignalingUsedQueue(e)
})
@@ -139,9 +137,15 @@ impl NetEpollHandler {
// 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) -> bool {
let mut next_desc = self.rx.queue.iter(&self.mem).next();
let mem = self.mem.read().unwrap();
let mut next_desc = self.rx.queue.iter(&mem).next();
if next_desc.is_none() {
// Queue has no available descriptors
if self.rx_tap_listening {
self.unregister_tap_rx_listener().unwrap();
self.rx_tap_listening = false;
}
return false;
}
@@ -158,7 +162,7 @@ impl NetEpollHandler {
}
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 = self.mem.write_slice(source_slice, desc.addr);
let write_result = mem.write_slice(source_slice, desc.addr);
match write_result {
Ok(_) => {
@@ -182,9 +186,7 @@ impl NetEpollHandler {
}
}
self.rx
.queue
.add_used(&self.mem, head_index, write_count as u32);
self.rx.queue.add_used(&mem, head_index, write_count as u32);
// Mark that we have at least one pending packet and we need to interrupt the guest.
self.rx.deferred_irqs = true;
@@ -244,7 +246,8 @@ impl NetEpollHandler {
}
fn process_tx(&mut self) -> result::Result<(), DeviceError> {
while let Some(avail_desc) = self.tx.queue.iter(&self.mem).next() {
let mem = self.mem.read().unwrap();
while let Some(avail_desc) = self.tx.queue.iter(&mem).next() {
let head_index = avail_desc.index;
let mut read_count = 0;
let mut next_desc = Some(avail_desc);
@@ -266,13 +269,14 @@ impl NetEpollHandler {
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 = self.mem.read_slice(
let read_result = mem.read_slice(
&mut self.tx.frame_buf[read_count..limit as usize],
desc_addr,
);
match read_result {
Ok(_) => {
read_count += limit;
// Increment by number of bytes actually read
read_count += limit - read_count;
}
Err(e) => {
error!("Failed to read slice: {:?}", e);
@@ -289,7 +293,7 @@ impl NetEpollHandler {
}
};
self.tx.queue.add_used(&self.mem, head_index, 0);
self.tx.queue.add_used(&mem, head_index, 0);
}
Ok(())
@@ -299,34 +303,49 @@ impl NetEpollHandler {
self.tap.read(&mut self.rx.frame_buf)
}
fn register_tap_rx_listener(&self) -> std::result::Result<(), std::io::Error> {
epoll::ctl(
self.epoll_fd,
epoll::ControlOptions::EPOLL_CTL_ADD,
self.tap.as_raw_fd(),
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(RX_TAP_EVENT)),
)?;
Ok(())
}
fn unregister_tap_rx_listener(&self) -> std::result::Result<(), std::io::Error> {
epoll::ctl(
self.epoll_fd,
epoll::ControlOptions::EPOLL_CTL_DEL,
self.tap.as_raw_fd(),
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(RX_TAP_EVENT)),
)?;
Ok(())
}
fn run(&mut self) -> result::Result<(), DeviceError> {
// Create the epoll file descriptor
let epoll_fd = epoll::create(true).map_err(DeviceError::EpollCreateFd)?;
self.epoll_fd = epoll::create(true).map_err(DeviceError::EpollCreateFd)?;
// Add events
epoll::ctl(
epoll_fd,
self.epoll_fd,
epoll::ControlOptions::EPOLL_CTL_ADD,
self.rx.queue_evt.as_raw_fd(),
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(RX_QUEUE_EVENT)),
)
.map_err(DeviceError::EpollCtl)?;
epoll::ctl(
epoll_fd,
self.epoll_fd,
epoll::ControlOptions::EPOLL_CTL_ADD,
self.tx.queue_evt.as_raw_fd(),
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(TX_QUEUE_EVENT)),
)
.map_err(DeviceError::EpollCtl)?;
self.register_tap_rx_listener()
.map_err(DeviceError::EpollCtl)?;
self.rx_tap_listening = true;
epoll::ctl(
epoll_fd,
epoll::ControlOptions::EPOLL_CTL_ADD,
self.tap.as_raw_fd(),
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(RX_TAP_EVENT)),
)
.map_err(DeviceError::EpollCtl)?;
epoll::ctl(
epoll_fd,
self.epoll_fd,
epoll::ControlOptions::EPOLL_CTL_ADD,
self.kill_evt.as_raw_fd(),
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(KILL_EVENT)),
@@ -337,8 +356,22 @@ impl NetEpollHandler {
let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN];
'epoll: loop {
let num_events =
epoll::wait(epoll_fd, -1, &mut events[..]).map_err(DeviceError::EpollWait)?;
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(DeviceError::EpollWait(e));
}
};
for event in events.iter().take(num_events) {
let ev_type = event.data as u16;
@@ -352,6 +385,10 @@ impl NetEpollHandler {
}
self.resume_rx().unwrap();
if !self.rx_tap_listening {
self.register_tap_rx_listener().unwrap();
self.rx_tap_listening = true;
}
}
TX_QUEUE_EVENT => {
debug!("TX_QUEUE_EVENT received");
@@ -536,9 +573,8 @@ impl VirtioDevice for Net {
fn activate(
&mut self,
mem: GuestMemoryMmap,
mem: Arc<RwLock<GuestMemoryMmap>>,
interrupt_cb: Arc<VirtioInterrupt>,
status: Arc<AtomicUsize>,
mut queues: Vec<Queue>,
mut queue_evts: Vec<EventFd>,
) -> ActivateResult {
@@ -571,9 +607,10 @@ impl VirtioDevice for Net {
tap,
rx: RxVirtio::new(rx_queue, rx_queue_evt),
tx: TxVirtio::new(tx_queue, tx_queue_evt),
interrupt_status: status,
interrupt_cb,
kill_evt,
epoll_fd: 0,
rx_tap_listening: false,
};
let worker_result = thread::Builder::new()

View File

@@ -15,20 +15,19 @@ use std::io::{self, Write};
use std::mem::size_of;
use std::os::unix::io::AsRawFd;
use std::result;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::sync::{Arc, RwLock};
use std::thread;
use super::Error as DeviceError;
use super::{
ActivateError, ActivateResult, DescriptorChain, DeviceEventT, Queue, VirtioDevice,
VirtioDeviceType, INTERRUPT_STATUS_USED_RING, VIRTIO_F_VERSION_1,
VirtioDeviceType, VIRTIO_F_VERSION_1,
};
use crate::VirtioInterrupt;
use crate::{VirtioInterrupt, VirtioInterruptType};
use vm_memory::{
Address, ByteValued, Bytes, GuestAddress, GuestMemoryError, GuestMemoryMmap, GuestUsize,
};
use vmm_sys_util::EventFd;
use vmm_sys_util::eventfd::EventFd;
const QUEUE_SIZE: u16 = 256;
const NUM_QUEUES: usize = 1;
@@ -155,9 +154,8 @@ impl Request {
struct PmemEpollHandler {
queue: Queue,
mem: GuestMemoryMmap,
mem: Arc<RwLock<GuestMemoryMmap>>,
disk: File,
interrupt_status: Arc<AtomicUsize>,
interrupt_cb: Arc<VirtioInterrupt>,
queue_evt: EventFd,
kill_evt: EventFd,
@@ -167,8 +165,9 @@ impl PmemEpollHandler {
fn process_queue(&mut self) -> bool {
let mut used_desc_heads = [(0, 0); QUEUE_SIZE as usize];
let mut used_count = 0;
for avail_desc in self.queue.iter(&self.mem) {
let len = match Request::parse(&avail_desc, &self.mem) {
let mem = self.mem.read().unwrap();
for avail_desc in self.queue.iter(&mem) {
let len = match Request::parse(&avail_desc, &mem) {
Ok(ref req) if (req.type_ == RequestType::Flush) => {
let status_code = match self.disk.sync_all() {
Ok(()) => VIRTIO_PMEM_RESP_TYPE_OK,
@@ -179,7 +178,7 @@ impl PmemEpollHandler {
};
let resp = VirtioPmemResp { ret: status_code };
match self.mem.write_obj(resp, req.status_addr) {
match mem.write_obj(resp, req.status_addr) {
Ok(_) => size_of::<VirtioPmemResp>() as u32,
Err(e) => {
error!("bad guest memory address: {}", e);
@@ -203,15 +202,13 @@ impl PmemEpollHandler {
}
for &(desc_index, len) in &used_desc_heads[..used_count] {
self.queue.add_used(&self.mem, desc_index, len);
self.queue.add_used(&mem, desc_index, len);
}
used_count > 0
}
fn signal_used_queue(&self) -> result::Result<(), DeviceError> {
self.interrupt_status
.fetch_or(INTERRUPT_STATUS_USED_RING as usize, Ordering::SeqCst);
(self.interrupt_cb)(&self.queue).map_err(|e| {
(self.interrupt_cb)(&VirtioInterruptType::Queue, Some(&self.queue)).map_err(|e| {
error!("Failed to signal used queue: {:?}", e);
DeviceError::FailedSignalingUsedQueue(e)
})
@@ -241,8 +238,22 @@ impl PmemEpollHandler {
let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN];
'epoll: loop {
let num_events =
epoll::wait(epoll_fd, -1, &mut events[..]).map_err(DeviceError::EpollWait)?;
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;
@@ -372,9 +383,8 @@ impl VirtioDevice for Pmem {
fn activate(
&mut self,
mem: GuestMemoryMmap,
mem: Arc<RwLock<GuestMemoryMmap>>,
interrupt_cb: Arc<VirtioInterrupt>,
status: Arc<AtomicUsize>,
mut queues: Vec<Queue>,
mut queue_evts: Vec<EventFd>,
) -> ActivateResult {
@@ -402,7 +412,6 @@ impl VirtioDevice for Pmem {
queue: queues.remove(0),
mem,
disk,
interrupt_status: status,
interrupt_cb,
queue_evt: queue_evts.remove(0),
kill_evt,

View File

@@ -9,18 +9,17 @@ use std::fs::File;
use std::io;
use std::os::unix::io::AsRawFd;
use std::result;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::sync::{Arc, RwLock};
use std::thread;
use super::Error as DeviceError;
use super::{
ActivateError, ActivateResult, DeviceEventT, Queue, VirtioDevice, VirtioDeviceType,
INTERRUPT_STATUS_USED_RING, VIRTIO_F_VERSION_1,
VIRTIO_F_VERSION_1,
};
use crate::VirtioInterrupt;
use crate::{VirtioInterrupt, VirtioInterruptType};
use vm_memory::{Bytes, GuestMemoryMmap};
use vmm_sys_util::EventFd;
use vmm_sys_util::eventfd::EventFd;
const QUEUE_SIZE: u16 = 256;
const NUM_QUEUES: usize = 1;
@@ -33,9 +32,8 @@ const KILL_EVENT: DeviceEventT = 1;
struct RngEpollHandler {
queues: Vec<Queue>,
mem: GuestMemoryMmap,
mem: Arc<RwLock<GuestMemoryMmap>>,
random_file: File,
interrupt_status: Arc<AtomicUsize>,
interrupt_cb: Arc<VirtioInterrupt>,
queue_evt: EventFd,
kill_evt: EventFd,
@@ -47,14 +45,14 @@ impl RngEpollHandler {
let mut used_desc_heads = [(0, 0); QUEUE_SIZE as usize];
let mut used_count = 0;
for avail_desc in queue.iter(&self.mem) {
let mem = self.mem.read().unwrap();
for avail_desc in queue.iter(&mem) {
let mut len = 0;
// Drivers can only read from the random device.
if avail_desc.is_write_only() {
// Fill the read with data from the random device on the host.
if self
.mem
if mem
.read_from(
avail_desc.addr,
&mut self.random_file,
@@ -71,15 +69,13 @@ impl RngEpollHandler {
}
for &(desc_index, len) in &used_desc_heads[..used_count] {
queue.add_used(&self.mem, desc_index, len);
queue.add_used(&mem, desc_index, len);
}
used_count > 0
}
fn signal_used_queue(&self) -> result::Result<(), DeviceError> {
self.interrupt_status
.fetch_or(INTERRUPT_STATUS_USED_RING as usize, Ordering::SeqCst);
(self.interrupt_cb)(&self.queues[0]).map_err(|e| {
(self.interrupt_cb)(&VirtioInterruptType::Queue, Some(&self.queues[0])).map_err(|e| {
error!("Failed to signal used queue: {:?}", e);
DeviceError::FailedSignalingUsedQueue(e)
})
@@ -109,8 +105,22 @@ impl RngEpollHandler {
let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN];
'epoll: loop {
let num_events =
epoll::wait(epoll_fd, -1, &mut events[..]).map_err(DeviceError::EpollWait)?;
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;
@@ -227,9 +237,8 @@ impl VirtioDevice for Rng {
fn activate(
&mut self,
mem: GuestMemoryMmap,
mem: Arc<RwLock<GuestMemoryMmap>>,
interrupt_cb: Arc<VirtioInterrupt>,
status: Arc<AtomicUsize>,
queues: Vec<Queue>,
mut queue_evts: Vec<EventFd>,
) -> ActivateResult {
@@ -257,7 +266,6 @@ impl VirtioDevice for Rng {
queues,
mem,
random_file,
interrupt_status: status,
interrupt_cb,
queue_evt: queue_evts.remove(0),
kill_evt,

View File

@@ -8,6 +8,8 @@
extern crate byteorder;
use byteorder::{ByteOrder, LittleEndian};
use std::sync::atomic::{AtomicU16, Ordering};
use std::sync::Arc;
use vm_memory::GuestAddress;
use crate::{Queue, VirtioDevice};
@@ -40,7 +42,7 @@ pub struct VirtioPciCommonConfig {
pub device_feature_select: u32,
pub driver_feature_select: u32,
pub queue_select: u16,
pub msix_config: u16,
pub msix_config: Arc<AtomicU16>,
}
impl VirtioPciCommonConfig {
@@ -120,7 +122,7 @@ impl VirtioPciCommonConfig {
fn read_common_config_word(&self, offset: u64, queues: &[Queue]) -> u16 {
debug!("read_common_config_word: offset 0x{:x}", offset);
match offset {
0x10 => self.msix_config,
0x10 => self.msix_config.load(Ordering::SeqCst),
0x12 => queues.len() as u16, // num_queues
0x16 => self.queue_select,
0x18 => self.with_queue(queues, |q| q.size).unwrap_or(0),
@@ -143,7 +145,7 @@ impl VirtioPciCommonConfig {
fn write_common_config_word(&mut self, offset: u64, value: u16, queues: &mut Vec<Queue>) {
debug!("write_common_config_word: offset 0x{:x}", offset);
match offset {
0x10 => self.msix_config = value,
0x10 => self.msix_config.store(value, Ordering::SeqCst),
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),
@@ -252,10 +254,9 @@ mod tests {
use super::*;
use crate::{ActivateResult, VirtioInterrupt};
use std::sync::atomic::AtomicUsize;
use std::sync::Arc;
use std::sync::{Arc, RwLock};
use vm_memory::GuestMemoryMmap;
use vmm_sys_util::EventFd;
use vmm_sys_util::eventfd::EventFd;
struct DummyDevice(u32);
const QUEUE_SIZE: u16 = 256;
@@ -270,9 +271,8 @@ mod tests {
}
fn activate(
&mut self,
_mem: GuestMemoryMmap,
_mem: Arc<RwLock<GuestMemoryMmap>>,
_interrupt_evt: Arc<VirtioInterrupt>,
_status: Arc<AtomicUsize>,
_queues: Vec<Queue>,
_queue_evts: Vec<EventFd>,
) -> ActivateResult {
@@ -298,7 +298,7 @@ mod tests {
device_feature_select: 0x0,
driver_feature_select: 0x0,
queue_select: 0xff,
msix_config: 0,
msix_config: Arc::new(AtomicU16::new(0)),
};
let dev = &mut DummyDevice(0) as &mut dyn VirtioDevice;

View File

@@ -13,9 +13,8 @@ extern crate vm_memory;
extern crate vmm_sys_util;
use libc::EFD_NONBLOCK;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::sync::Mutex;
use std::sync::atomic::{AtomicU16, AtomicUsize, Ordering};
use std::sync::{Arc, Mutex, RwLock};
use devices::BusDevice;
use pci::{
@@ -26,12 +25,13 @@ use pci::{
};
use vm_allocator::SystemAllocator;
use vm_memory::{Address, ByteValued, GuestAddress, GuestMemoryMmap, GuestUsize, Le32};
use vmm_sys_util::{EventFd, Result};
use vmm_sys_util::{errno::Result, eventfd::EventFd};
use super::VirtioPciCommonConfig;
use crate::{
Queue, VirtioDevice, VirtioDeviceType, VirtioInterrupt, DEVICE_ACKNOWLEDGE, DEVICE_DRIVER,
DEVICE_DRIVER_OK, DEVICE_FAILED, DEVICE_FEATURES_OK, DEVICE_INIT,
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,
};
#[allow(clippy::enum_variant_names)]
@@ -41,6 +41,7 @@ enum PciCapabilityType {
IsrConfig = 3,
DeviceConfig = 4,
PciConfig = 5,
SharedMemoryConfig = 8,
}
#[allow(dead_code)]
@@ -50,7 +51,8 @@ struct VirtioPciCap {
cap_len: u8, // Generic PCI field: capability length
cfg_type: u8, // Identifies the structure.
pci_bar: u8, // Where to find it.
padding: [u8; 3], // Pad to full dword.
id: u8, // Multiple capabilities of the same type
padding: [u8; 2], // Pad to full dword.
offset: Le32, // Offset within bar.
length: Le32, // Length of the structure, in bytes.
}
@@ -67,15 +69,16 @@ impl PciCapability for VirtioPciCap {
}
}
const VIRTIO_PCI_CAPABILITY_BYTES: u8 = 16;
const VIRTIO_PCI_CAP_LEN_OFFSET: u8 = 2;
impl VirtioPciCap {
pub fn new(cfg_type: PciCapabilityType, pci_bar: u8, offset: u32, length: u32) -> Self {
VirtioPciCap {
cap_len: VIRTIO_PCI_CAPABILITY_BYTES,
cap_len: (std::mem::size_of::<VirtioPciCap>() as u8) + VIRTIO_PCI_CAP_LEN_OFFSET,
cfg_type: cfg_type as u8,
pci_bar,
padding: [0; 3],
id: 0,
padding: [0; 2],
offset: Le32::from(offset),
length: Le32::from(length),
}
@@ -112,10 +115,12 @@ impl VirtioPciNotifyCap {
) -> Self {
VirtioPciNotifyCap {
cap: VirtioPciCap {
cap_len: std::mem::size_of::<VirtioPciNotifyCap>() as u8,
cap_len: (std::mem::size_of::<VirtioPciNotifyCap>() as u8)
+ VIRTIO_PCI_CAP_LEN_OFFSET,
cfg_type: cfg_type as u8,
pci_bar,
padding: [0; 3],
id: 0,
padding: [0; 2],
offset: Le32::from(offset),
length: Le32::from(length),
},
@@ -124,6 +129,45 @@ impl VirtioPciNotifyCap {
}
}
#[allow(dead_code)]
#[repr(packed)]
#[derive(Clone, Copy, Default)]
struct VirtioPciCap64 {
cap: VirtioPciCap,
offset_hi: Le32,
length_hi: Le32,
}
// It is safe to implement ByteValued. All members are simple numbers and any value is valid.
unsafe impl ByteValued for VirtioPciCap64 {}
impl PciCapability for VirtioPciCap64 {
fn bytes(&self) -> &[u8] {
self.as_slice()
}
fn id(&self) -> PciCapabilityID {
PciCapabilityID::VendorSpecific
}
}
impl VirtioPciCap64 {
pub fn new(cfg_type: PciCapabilityType, pci_bar: u8, id: u8, offset: u64, length: u64) -> Self {
VirtioPciCap64 {
cap: VirtioPciCap {
cap_len: (std::mem::size_of::<VirtioPciCap64>() as u8) + VIRTIO_PCI_CAP_LEN_OFFSET,
cfg_type: cfg_type as u8,
pci_bar,
id,
padding: [0; 2],
offset: Le32::from(offset as u32),
length: Le32::from(length as u32),
},
offset_hi: Le32::from((offset >> 32) as u32),
length_hi: Le32::from((length >> 32) as u32),
}
}
}
#[allow(dead_code)]
#[derive(Copy, Clone)]
pub enum PciVirtioSubclass {
@@ -178,7 +222,7 @@ pub struct VirtioPciDevice {
msix_num: u16,
// Virtio device reference and status
device: Box<VirtioDevice>,
device: Box<dyn VirtioDevice>,
device_activated: bool,
// PCI interrupts.
@@ -190,7 +234,7 @@ pub struct VirtioPciDevice {
queue_evts: Vec<EventFd>,
// Guest memory
memory: Option<GuestMemoryMmap>,
memory: Option<Arc<RwLock<GuestMemoryMmap>>>,
// Setting PCI BAR
settings_bar: u8,
@@ -198,7 +242,11 @@ pub struct VirtioPciDevice {
impl VirtioPciDevice {
/// Constructs a new PCI transport for the given virtio device.
pub fn new(memory: GuestMemoryMmap, device: Box<VirtioDevice>, msix_num: u16) -> Result<Self> {
pub fn new(
memory: Arc<RwLock<GuestMemoryMmap>>,
device: Box<dyn VirtioDevice>,
msix_num: u16,
) -> Result<Self> {
let mut queue_evts = Vec::new();
for _ in device.queue_max_sizes().iter() {
queue_evts.push(EventFd::new(EFD_NONBLOCK)?)
@@ -222,15 +270,15 @@ impl VirtioPciDevice {
let (class, subclass) = match VirtioDeviceType::from(device.device_type()) {
VirtioDeviceType::TYPE_NET => (
PciClassCode::NetworkController,
&PciNetworkControllerSubclass::EthernetController as &PciSubclass,
&PciNetworkControllerSubclass::EthernetController as &dyn PciSubclass,
),
VirtioDeviceType::TYPE_BLOCK => (
PciClassCode::MassStorage,
&PciMassStorageSubclass::MassStorage as &PciSubclass,
&PciMassStorageSubclass::MassStorage as &dyn PciSubclass,
),
_ => (
PciClassCode::Other,
&PciVirtioSubclass::NonTransitionalBase as &PciSubclass,
&PciVirtioSubclass::NonTransitionalBase as &dyn PciSubclass,
),
};
@@ -254,7 +302,7 @@ impl VirtioPciDevice {
device_feature_select: 0,
driver_feature_select: 0,
queue_select: 0,
msix_config: 0,
msix_config: Arc::new(AtomicU16::new(0)),
},
msix_config,
msix_num,
@@ -290,7 +338,7 @@ impl VirtioPciDevice {
fn are_queues_valid(&self) -> bool {
if let Some(mem) = self.memory.as_ref() {
self.queues.iter().all(|q| q.is_valid(mem))
self.queues.iter().all(|q| q.is_valid(&mem.read().unwrap()))
} else {
false
}
@@ -376,10 +424,20 @@ impl PciDevice for VirtioPciDevice {
) {
self.configuration.set_irq(irq_num as u8, irq_pin);
let cb = Arc::new(Box::new(move |_queue: &Queue| {
let param = InterruptParameters { msix: None };
(irq_cb)(param)
}) as VirtioInterrupt);
let interrupt_status = self.interrupt_status.clone();
let cb = Arc::new(Box::new(
move |int_type: &VirtioInterruptType, _queue: Option<&Queue>| {
let param = InterruptParameters { msix: None };
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);
(irq_cb)(param)
},
) as VirtioInterrupt);
self.interrupt_cb = Some(cb);
}
@@ -393,22 +451,44 @@ impl PciDevice for VirtioPciDevice {
let msix_config_clone = msix_config.clone();
let cb = Arc::new(Box::new(move |queue: &Queue| {
let config = &mut msix_config_clone.lock().unwrap();
let entry = &config.table_entries[queue.vector as usize];
let common_config_msi_vector = self.common_config.msix_config.clone();
let cb = Arc::new(Box::new(
move |int_type: &VirtioInterruptType, queue: Option<&Queue>| {
let vector = match int_type {
VirtioInterruptType::Config => {
common_config_msi_vector.load(Ordering::SeqCst)
}
VirtioInterruptType::Queue => {
if let Some(q) = queue {
q.vector
} else {
0
}
}
};
// In case the vector control register associated with the entry
// has its first bit set, this means the vector is masked and the
// device should not inject the interrupt.
// Instead, the Pending Bit Array table is updated to reflect there
// is a pending interrupt for this specific vector.
if config.masked() || entry.masked() {
config.set_pba_bit(queue.vector, false);
return Ok(());
}
let config = &mut msix_config_clone.lock().unwrap();
let entry = &config.table_entries[vector as usize];
(msi_cb)(InterruptParameters { msix: Some(entry) })
}) as VirtioInterrupt);
// If MSI-X interrupts are not enabled for this device, then simply
// ignore the interrupt.
if !config.enabled() {
return Ok(());
}
// In case the vector control register associated with the entry
// has its first bit set, this means the vector is masked and the
// device should not inject the interrupt.
// Instead, the Pending Bit Array table is updated to reflect there
// is a pending interrupt for this specific vector.
if config.masked() || entry.masked() {
config.set_pba_bit(vector, false);
return Ok(());
}
(msi_cb)(InterruptParameters { msix: Some(entry) })
},
) as VirtioInterrupt);
self.interrupt_cb = Some(cb);
}
@@ -471,20 +551,29 @@ impl PciDevice for VirtioPciDevice {
// Once the BARs are allocated, the capabilities can be added to the PCI configuration.
self.add_pci_capabilities(virtio_pci_bar)?;
// Allocate the device specific BARs.
for config in self.device.get_device_bars() {
let device_bar_addr = allocator
.allocate_mmio_addresses(None, config.get_size(), None)
.ok_or_else(|| PciDeviceError::IoAllocationFailed(config.get_size()))?;
config.set_address(device_bar_addr.raw_value());
let _device_bar = self.configuration.add_pci_bar(&config).map_err(|e| {
PciDeviceError::IoRegistrationFailed(device_bar_addr.raw_value(), e)
})?;
ranges.push((
device_bar_addr,
config.get_size(),
PciBarRegionType::Memory64BitRegion,
));
// Allocate a dedicated BAR if there are some shared memory regions.
if let Some(shm_list) = self.device.get_shm_regions() {
let config = PciBarConfiguration::default()
.set_register_index(2)
.set_address(shm_list.addr.raw_value())
.set_size(shm_list.len);
let virtio_pci_shm_bar =
self.configuration.add_pci_bar(&config).map_err(|e| {
PciDeviceError::IoRegistrationFailed(shm_list.addr.raw_value(), e)
})? as u8;
for (idx, shm) in shm_list.region_list.iter().enumerate() {
let shm_cap = VirtioPciCap64::new(
PciCapabilityType::SharedMemoryConfig,
virtio_pci_shm_bar,
idx as u8,
shm.offset,
shm.len,
);
self.configuration
.add_capability(&shm_cap)
.map_err(PciDeviceError::CapabilitiesSetup)?;
}
}
Ok(ranges)
@@ -585,7 +674,6 @@ impl PciDevice for VirtioPciDevice {
.activate(
mem,
interrupt_cb,
self.interrupt_status.clone(),
self.queues.clone(),
self.queue_evts.split_off(0),
)

View File

@@ -0,0 +1,371 @@
// Copyright 2019 Intel Corporation. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
use super::vu_common_ctrl::setup_vhost_user;
use super::{Error, Result};
use crate::vhost_user::handler::{VhostUserEpollConfig, VhostUserEpollHandler};
use crate::{
ActivateError, ActivateResult, Queue, VirtioDevice, VirtioDeviceType, VirtioInterrupt,
VirtioSharedMemoryList, VIRTIO_F_VERSION_1,
};
use libc::{self, EFD_NONBLOCK};
use std::cmp;
use std::io;
use std::io::Write;
use std::os::unix::io::RawFd;
use std::sync::{Arc, Mutex, RwLock};
use std::thread;
use vhost_rs::vhost_user::message::{
VhostUserFSSlaveMsg, VhostUserProtocolFeatures, VhostUserVirtioFeatures,
};
use vhost_rs::vhost_user::{
HandlerResult, Master, MasterReqHandler, VhostUserMaster, VhostUserMasterReqHandler,
};
use vhost_rs::VhostBackend;
use vm_memory::GuestMemoryMmap;
use vmm_sys_util::eventfd::EventFd;
const CONFIG_SPACE_TAG_SIZE: usize = 36;
const CONFIG_SPACE_NUM_QUEUES_SIZE: usize = 4;
const CONFIG_SPACE_SIZE: usize = CONFIG_SPACE_TAG_SIZE + CONFIG_SPACE_NUM_QUEUES_SIZE;
const NUM_QUEUE_OFFSET: usize = 1;
struct SlaveReqHandler {
cache_size: u64,
mmap_cache_addr: u64,
}
impl VhostUserMasterReqHandler for SlaveReqHandler {
fn handle_config_change(&mut self) -> HandlerResult<()> {
debug!("handle_config_change");
Ok(())
}
fn fs_slave_map(&mut self, fs: &VhostUserFSSlaveMsg, fd: RawFd) -> HandlerResult<()> {
debug!("fs_slave_map");
let addr = self.mmap_cache_addr + fs.cache_offset[0];
let ret = unsafe {
libc::mmap(
addr as *mut libc::c_void,
fs.len[0] as usize,
fs.flags[0].bits() as i32,
libc::MAP_SHARED | libc::MAP_FIXED,
fd,
fs.fd_offset[0] as libc::off_t,
)
};
if ret == libc::MAP_FAILED {
return Err(io::Error::last_os_error());
}
let ret = unsafe { libc::close(fd) };
if ret == -1 {
return Err(io::Error::last_os_error());
}
Ok(())
}
fn fs_slave_unmap(&mut self, fs: &VhostUserFSSlaveMsg) -> HandlerResult<()> {
debug!("fs_slave_unmap");
let mut len = fs.len[0];
// Need to handle a special case where the slave ask for the unmapping
// of the entire mapping.
if len == 0xffff_ffff_ffff_ffff {
len = self.cache_size;
}
let addr = self.mmap_cache_addr + fs.cache_offset[0];
let ret = unsafe {
libc::mmap(
addr as *mut libc::c_void,
len as usize,
libc::PROT_NONE,
libc::MAP_ANONYMOUS | libc::MAP_PRIVATE | libc::MAP_FIXED,
-1,
0 as libc::off_t,
)
};
if ret == libc::MAP_FAILED {
return Err(io::Error::last_os_error());
}
Ok(())
}
fn fs_slave_sync(&mut self, fs: &VhostUserFSSlaveMsg) -> HandlerResult<()> {
debug!("fs_slave_sync");
let addr = self.mmap_cache_addr + fs.cache_offset[0];
let ret =
unsafe { libc::msync(addr as *mut libc::c_void, fs.len[0] as usize, libc::MS_SYNC) };
if ret == -1 {
return Err(io::Error::last_os_error());
}
Ok(())
}
}
pub struct Fs {
vu: Master,
queue_sizes: Vec<u16>,
avail_features: u64,
acked_features: u64,
config_space: Vec<u8>,
kill_evt: Option<EventFd>,
cache: Option<(VirtioSharedMemoryList, u64)>,
slave_req_support: bool,
}
impl Fs {
/// Create a new virtio-fs device.
pub fn new(
path: &str,
tag: &str,
req_num_queues: usize,
queue_size: u16,
cache: Option<(VirtioSharedMemoryList, u64)>,
) -> Result<Fs> {
let mut slave_req_support = false;
// Calculate the actual number of queues needed.
let num_queues = NUM_QUEUE_OFFSET + req_num_queues;
// Connect to the vhost-user socket.
let mut master =
Master::connect(path, num_queues as u64).map_err(Error::VhostUserCreateMaster)?;
// Filling device and vring features VMM supports.
let mut avail_features =
1 << VIRTIO_F_VERSION_1 | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
// Set vhost-user owner.
master.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 = master.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.
master
.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 = master
.get_protocol_features()
.map_err(Error::VhostUserGetProtocolFeatures)?;
if cache.is_some() {
protocol_features &= VhostUserProtocolFeatures::MQ
| VhostUserProtocolFeatures::REPLY_ACK
| VhostUserProtocolFeatures::SLAVE_REQ
| VhostUserProtocolFeatures::SLAVE_SEND_FD;
} else {
protocol_features &=
VhostUserProtocolFeatures::MQ | VhostUserProtocolFeatures::REPLY_ACK;
}
master
.set_protocol_features(protocol_features)
.map_err(Error::VhostUserSetProtocolFeatures)?;
slave_req_support = true;
}
// Create virtio device config space.
// First by adding the tag.
let mut config_space = tag.to_string().into_bytes();
config_space.resize(CONFIG_SPACE_SIZE, 0);
// And then by copying the number of queues.
let num_queues_slice = (req_num_queues as u32).to_le_bytes();
config_space[CONFIG_SPACE_TAG_SIZE..CONFIG_SPACE_SIZE].copy_from_slice(&num_queues_slice);
Ok(Fs {
vu: master,
queue_sizes: vec![queue_size; num_queues],
avail_features,
acked_features,
config_space,
kill_evt: None,
cache,
slave_req_support,
})
}
}
impl Drop for Fs {
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 Fs {
fn device_type(&self) -> u32 {
VirtioDeviceType::TYPE_FS as u32
}
fn queue_max_sizes(&self) -> &[u16] {
&self.queue_sizes.as_slice()
}
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!("fs: 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!("fs: 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!("fs: virtio-fs got unknown feature ack: {: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;
}
let (_, right) = self.config_space.split_at_mut(offset as usize);
right.copy_from_slice(&data[..]);
}
fn activate(
&mut self,
mem: Arc<RwLock<GuestMemoryMmap>>,
interrupt_cb: Arc<VirtioInterrupt>,
queues: Vec<Queue>,
queue_evts: Vec<EventFd>,
) -> ActivateResult {
if queues.len() != self.queue_sizes.len() || queue_evts.len() != self.queue_sizes.len() {
error!(
"Cannot perform activate. Expected {} queue(s), got {}",
self.queue_sizes.len(),
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);
let vu_call_evt_queue_list = setup_vhost_user(
&mut self.vu,
&mem.read().unwrap(),
queues,
queue_evts,
self.acked_features,
)
.map_err(ActivateError::VhostUserSetup)?;
// Initialize slave communication.
let slave_req_handler = if self.slave_req_support {
if let Some(cache) = self.cache.clone() {
let vu_master_req_handler = Arc::new(Mutex::new(SlaveReqHandler {
cache_size: cache.0.len,
mmap_cache_addr: cache.1,
}));
let req_handler = MasterReqHandler::new(vu_master_req_handler).map_err(|e| {
ActivateError::VhostUserSetup(Error::MasterReqHandlerCreation(e))
})?;
self.vu
.set_slave_request_fd(req_handler.get_tx_raw_fd())
.map_err(|e| {
ActivateError::VhostUserSetup(Error::VhostUserSetSlaveRequestFd(e))
})?;
Some(req_handler)
} else {
None
}
} else {
None
};
let mut handler = VhostUserEpollHandler::new(VhostUserEpollConfig {
vu_interrupt_list: vu_call_evt_queue_list,
interrupt_cb,
kill_evt,
slave_req_handler,
});
let worker_result = thread::Builder::new()
.name("virtio_fs".to_string())
.spawn(move || {
if let Err(e) = handler.run() {
error!("net worker thread exited with error {:?}!", e);
}
});
if let Err(e) = worker_result {
error!("failed to spawn virtio-fs worker: {}", e);
return Err(ActivateError::BadActivate);
}
Ok(())
}
fn get_shm_regions(&self) -> Option<VirtioSharedMemoryList> {
if let Some(cache) = self.cache.clone() {
Some(cache.0)
} else {
None
}
}
}

View File

@@ -0,0 +1,156 @@
// Copyright (c) 2019 Intel Corporation. All rights reserved.
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
//
// Copyright 2017 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE-BSD-3-Clause file.
//
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
use super::super::{Queue, VirtioInterruptType};
use super::{Error, Result};
use epoll;
use vmm_sys_util::eventfd::EventFd;
use crate::VirtioInterrupt;
use std::io;
use std::os::unix::io::AsRawFd;
use std::sync::Arc;
use vhost_rs::vhost_user::{MasterReqHandler, VhostUserMasterReqHandler};
/// Collection of common parameters required by vhost-user devices while
/// call Epoll handler.
///
/// # Arguments
/// * `interrupt_cb` interrupt for virtqueue change.
/// * `kill_evt` - EventFd used to kill the vhost-user device.
/// * `vu_interrupt_list` - virtqueue and EventFd to signal when buffer used.
pub struct VhostUserEpollConfig<S: VhostUserMasterReqHandler> {
pub interrupt_cb: Arc<VirtioInterrupt>,
pub kill_evt: EventFd,
pub vu_interrupt_list: Vec<(EventFd, Queue)>,
pub slave_req_handler: Option<MasterReqHandler<S>>,
}
pub struct VhostUserEpollHandler<S: VhostUserMasterReqHandler> {
vu_epoll_cfg: VhostUserEpollConfig<S>,
}
impl<S: VhostUserMasterReqHandler> VhostUserEpollHandler<S> {
/// Construct a new event handler for vhost-user based devices.
///
/// # Arguments
/// * `vu_epoll_cfg` - collection of common parameters for vhost-user devices
///
/// # Return
/// * `VhostUserEpollHandler` - epoll handler for vhost-user based devices
pub fn new(vu_epoll_cfg: VhostUserEpollConfig<S>) -> VhostUserEpollHandler<S> {
VhostUserEpollHandler { vu_epoll_cfg }
}
fn signal_used_queue(&self, queue: &Queue) -> Result<()> {
(self.vu_epoll_cfg.interrupt_cb)(&VirtioInterruptType::Queue, Some(queue))
.map_err(Error::FailedSignalingUsedQueue)
}
pub fn run(&mut self) -> Result<()> {
// Create the epoll file descriptor
let epoll_fd = epoll::create(true).map_err(Error::EpollCreateFd)?;
for (index, vhost_user_interrupt) in self.vu_epoll_cfg.vu_interrupt_list.iter().enumerate()
{
// Add events
epoll::ctl(
epoll_fd,
epoll::ControlOptions::EPOLL_CTL_ADD,
vhost_user_interrupt.0.as_raw_fd(),
epoll::Event::new(epoll::Events::EPOLLIN, index as u64),
)
.map_err(Error::EpollCtl)?;
}
let kill_evt_index = self.vu_epoll_cfg.vu_interrupt_list.len();
epoll::ctl(
epoll_fd,
epoll::ControlOptions::EPOLL_CTL_ADD,
self.vu_epoll_cfg.kill_evt.as_raw_fd(),
epoll::Event::new(epoll::Events::EPOLLIN, kill_evt_index as u64),
)
.map_err(Error::EpollCtl)?;
let mut index = kill_evt_index;
let slave_evt_index = if let Some(self_req_handler) = &self.vu_epoll_cfg.slave_req_handler {
index = kill_evt_index + 1;
epoll::ctl(
epoll_fd,
epoll::ControlOptions::EPOLL_CTL_ADD,
self_req_handler.as_raw_fd(),
epoll::Event::new(epoll::Events::EPOLLIN, index as u64),
)
.map_err(Error::EpollCtl)?;
Some(index)
} else {
None
};
let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); index + 1];
'poll: 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::EpollWait(e));
}
};
for event in events.iter().take(num_events) {
let ev_type = event.data as usize;
match ev_type {
x if x < kill_evt_index => {
self.vu_epoll_cfg.vu_interrupt_list[x]
.0
.read()
.map_err(Error::FailedReadingQueue)?;
if let Err(e) =
self.signal_used_queue(&self.vu_epoll_cfg.vu_interrupt_list[x].1)
{
error!("Failed to signal used queue: {:?}", e);
break 'poll;
}
}
x if kill_evt_index == x => {
debug!("KILL_EVENT received, stopping epoll loop");
break 'poll;
}
x if (slave_evt_index.is_some() && slave_evt_index.unwrap() == x) => {
if let Some(slave_req_handler) =
self.vu_epoll_cfg.slave_req_handler.as_mut()
{
slave_req_handler
.handle_request()
.map_err(Error::VhostUserSlaveRequest)?;
}
}
_ => {
error!("Unknown event for vhost-user");
}
}
}
}
Ok(())
}
}

View File

@@ -0,0 +1,95 @@
// Copyright 2019 Intel Corporation. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
extern crate epoll;
extern crate net_util;
extern crate vhost_rs;
extern crate virtio_bindings;
extern crate vm_memory;
use std;
use std::io;
use vhost_rs::Error as VhostError;
use vm_memory::Error as MmapError;
pub mod fs;
mod handler;
pub mod net;
pub mod vu_common_ctrl;
pub use self::fs::*;
pub use self::net::Net;
pub use self::vu_common_ctrl::VhostUserConfig;
#[derive(Debug)]
pub enum Error {
/// Invalid available address.
AvailAddress,
/// Queue number is not correct
BadQueueNum,
/// Creating kill eventfd failed.
CreateKillEventFd(io::Error),
/// Cloning kill eventfd failed.
CloneKillEventFd(io::Error),
/// Invalid descriptor table address.
DescriptorTableAddress,
/// Create Epoll eventfd failed
EpollCreateFd(io::Error),
/// Epoll ctl error
EpollCtl(io::Error),
/// Epoll wait error
EpollWait(io::Error),
/// Read queue failed.
FailedReadingQueue(io::Error),
/// Signal used queue failed.
FailedSignalingUsedQueue(io::Error),
/// Failed to read vhost eventfd.
MemoryRegions(MmapError),
/// Failed to create master.
VhostUserCreateMaster(VhostError),
/// Failed to open vhost device.
VhostUserOpen(VhostError),
/// Connection to socket failed.
VhostUserConnect(vhost_rs::Error),
/// Get features failed.
VhostUserGetFeatures(VhostError),
/// Get protocol features failed.
VhostUserGetProtocolFeatures(VhostError),
/// Vhost-user Backend not support vhost-user protocol.
VhostUserProtocolNotSupport,
/// Set owner failed.
VhostUserSetOwner(VhostError),
/// Set features failed.
VhostUserSetFeatures(VhostError),
/// Set protocol features failed.
VhostUserSetProtocolFeatures(VhostError),
/// Set mem table failed.
VhostUserSetMemTable(VhostError),
/// Set vring num failed.
VhostUserSetVringNum(VhostError),
/// Set vring addr failed.
VhostUserSetVringAddr(VhostError),
/// Set vring base failed.
VhostUserSetVringBase(VhostError),
/// Set vring call failed.
VhostUserSetVringCall(VhostError),
/// Set vring kick failed.
VhostUserSetVringKick(VhostError),
/// Set vring enable failed.
VhostUserSetVringEnable(VhostError),
/// Failed to create vhost eventfd.
VhostIrqCreate(io::Error),
/// Failed to read vhost eventfd.
VhostIrqRead(io::Error),
/// Failed to read vhost eventfd.
VhostUserMemoryRegion(MmapError),
/// Failed to handle vhost-user slave request.
VhostUserSlaveRequest(vhost_rs::vhost_user::Error),
/// Failed to create the master request handler from slave.
MasterReqHandlerCreation(vhost_rs::vhost_user::Error),
/// Set slave request fd failed.
VhostUserSetSlaveRequestFd(vhost_rs::Error),
/// Invalid used address.
UsedAddress,
}
type Result<T> = std::result::Result<T, Error>;

View File

@@ -0,0 +1,229 @@
// 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::sync::{Arc, RwLock};
use std::thread;
use std::vec::Vec;
use crate::VirtioInterrupt;
use net_util::{MacAddr, MAC_ADDR_LEN};
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 vhost_rs::vhost_user::message::VhostUserVirtioFeatures;
use vhost_rs::vhost_user::{Master, VhostUserMaster, VhostUserMasterReqHandler};
use vhost_rs::VhostBackend;
use virtio_bindings::virtio_net;
use virtio_bindings::virtio_ring;
struct SlaveReqHandler {}
impl VhostUserMasterReqHandler for SlaveReqHandler {}
pub struct Net {
vhost_user_net: Master,
kill_evt: EventFd,
avail_features: u64,
acked_features: u64,
backend_features: u64,
config_space: Vec<u8>,
queue_sizes: Vec<u16>,
}
impl<'a> 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)
.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
| 1 << virtio_net::VIRTIO_NET_F_GUEST_TSO4
| 1 << virtio_net::VIRTIO_NET_F_GUEST_TSO6
| 1 << virtio_net::VIRTIO_NET_F_GUEST_ECN
| 1 << virtio_net::VIRTIO_NET_F_GUEST_UFO
| 1 << virtio_net::VIRTIO_NET_F_HOST_TSO4
| 1 << virtio_net::VIRTIO_NET_F_HOST_TSO6
| 1 << virtio_net::VIRTIO_NET_F_HOST_ECN
| 1 << virtio_net::VIRTIO_NET_F_HOST_UFO
| 1 << virtio_net::VIRTIO_NET_F_MRG_RXBUF
| 1 << virtio_net::VIRTIO_F_NOTIFY_ON_EMPTY
| 1 << virtio_net::VIRTIO_F_VERSION_1
| 1 << virtio_ring::VIRTIO_RING_F_EVENT_IDX
| VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
vhost_user_net
.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_net
.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_net
.set_features(avail_features)
.map_err(Error::VhostUserSetFeatures)?;
let mut acked_features = 0;
if avail_features & VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits() != 0 {
acked_features |= VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
vhost_user_net
.get_protocol_features()
.map_err(Error::VhostUserGetProtocolFeatures)?;
} else {
return Err(Error::VhostUserProtocolNotSupport);
}
let mut config_space = Vec::with_capacity(MAC_ADDR_LEN);
unsafe { config_space.set_len(MAC_ADDR_LEN) }
config_space[..].copy_from_slice(mac_addr.get_bytes());
avail_features |= 1 << virtio_net::VIRTIO_NET_F_MAC;
// Send set_vring_base here, since it could tell backends, like OVS + DPDK,
// how many virt queues to be handled, which backend required to know at early stage.
for i in 0..vu_cfg.num_queues {
vhost_user_net
.set_vring_base(i, 0)
.map_err(Error::VhostUserSetVringBase)?;
}
Ok(Net {
vhost_user_net,
kill_evt,
avail_features,
acked_features,
backend_features,
config_space,
queue_sizes: vec![vu_cfg.queue_size; vu_cfg.num_queues],
})
}
}
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);
}
}
}
impl VirtioDevice for Net {
fn device_type(&self) -> u32 {
VirtioDeviceType::TYPE_NET 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;
}
let (_, right) = self.config_space.split_at_mut(offset as usize);
right.copy_from_slice(&data[..]);
}
fn activate(
&mut self,
mem: Arc<RwLock<GuestMemoryMmap>>,
interrupt_cb: Arc<VirtioInterrupt>,
queues: Vec<Queue>,
queue_evts: Vec<EventFd>,
) -> ActivateResult {
let handler_kill_evt = self
.kill_evt
.try_clone()
.map_err(|_| ActivateError::CloneKillEventFd)?;
let vu_interrupt_list = setup_vhost_user(
&mut self.vhost_user_net,
&mem.read().unwrap(),
queues,
queue_evts,
self.acked_features & self.backend_features,
)
.map_err(ActivateError::VhostUserNetSetup)?;
let mut handler = VhostUserEpollHandler::<SlaveReqHandler>::new(VhostUserEpollConfig {
interrupt_cb,
kill_evt: handler_kill_evt,
vu_interrupt_list,
slave_req_handler: None,
});
let handler_result = thread::Builder::new()
.name("vhost_user_net".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 net thread create failed with error {:?}", e);
}
Ok(())
}
}

View File

@@ -0,0 +1,108 @@
// Copyright 2019 Intel Corporation. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
use libc;
use libc::EFD_NONBLOCK;
use std::os::unix::io::AsRawFd;
use std::vec::Vec;
use vm_memory::{Address, Error as MmapError, GuestMemory, GuestMemoryMmap, GuestMemoryRegion};
use vmm_sys_util::eventfd::EventFd;
use super::super::Queue;
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,
pub num_queues: usize,
pub queue_size: u16,
}
pub fn setup_vhost_user_vring(
vu: &mut Master,
mem: &GuestMemoryMmap,
queues: Vec<Queue>,
queue_evts: Vec<EventFd>,
) -> Result<Vec<(EventFd, Queue)>> {
let mut regions: Vec<VhostUserMemoryRegionInfo> = Vec::new();
mem.with_regions_mut(|_, region| {
let (mmap_handle, mmap_offset) = match region.file_offset() {
Some(_file_offset) => (_file_offset.file().as_raw_fd(), _file_offset.start()),
None => return Err(MmapError::NoMemoryRegion),
};
let vhost_user_net_reg = VhostUserMemoryRegionInfo {
guest_phys_addr: region.start_addr().raw_value(),
memory_size: region.len() as u64,
userspace_addr: region.as_ptr() as u64,
mmap_offset,
mmap_handle,
};
regions.push(vhost_user_net_reg);
Ok(())
})
.map_err(Error::VhostUserMemoryRegion)?;
vu.set_mem_table(regions.as_slice())
.map_err(Error::VhostUserSetMemTable)?;
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())
.map_err(Error::VhostUserSetVringNum)?;
let config_data = VringConfigData {
queue_max_size: queue.get_max_size(),
queue_size: queue.actual_size(),
flags: 0u32,
desc_table_addr: mem
.get_host_address(queue.desc_table)
.ok_or_else(|| Error::DescriptorTableAddress)? as u64,
used_ring_addr: mem
.get_host_address(queue.used_ring)
.ok_or_else(|| Error::UsedAddress)? as u64,
avail_ring_addr: mem
.get_host_address(queue.avail_ring)
.ok_or_else(|| Error::AvailAddress)? as u64,
log_addr: None,
};
vu.set_vring_addr(queue_index, &config_data)
.map_err(Error::VhostUserSetVringAddr)?;
vu.set_vring_base(queue_index, 0u16)
.map_err(Error::VhostUserSetVringBase)?;
let vhost_user_interrupt = EventFd::new(EFD_NONBLOCK).map_err(Error::VhostIrqCreate)?;
vu.set_vring_call(queue_index, &vhost_user_interrupt)
.map_err(Error::VhostUserSetVringCall)?;
vu_interrupt_list.push((vhost_user_interrupt, queue));
vu.set_vring_kick(queue_index, &queue_evts[queue_index])
.map_err(Error::VhostUserSetVringKick)?;
vu.set_vring_enable(queue_index, true)
.map_err(Error::VhostUserSetVringEnable)?;
}
Ok(vu_interrupt_list)
}
pub fn setup_vhost_user(
vu: &mut Master,
mem: &GuestMemoryMmap,
queues: Vec<Queue>,
queue_evts: Vec<EventFd>,
acked_features: u64,
) -> Result<Vec<(EventFd, Queue)>> {
// Set features based on the acked features from the guest driver.
vu.set_features(acked_features)
.map_err(Error::VhostUserSetFeatures)?;
setup_vhost_user_vring(vu, mem, queues, queue_evts)
}

14
vmm/Cargo.toml Executable file → Normal file
View File

@@ -4,14 +4,19 @@ version = "0.1.0"
authors = ["The Cloud Hypervisor Authors"]
edition = "2018"
[features]
default = ["acpi"]
acpi = ["acpi_tables"]
[dependencies]
acpi_tables = { path = "../acpi_tables", optional = true }
arch = { path = "../arch" }
devices = { path = "../devices" }
epoll = "=4.0.1"
kvm-bindings = "0.1"
epoll = "4.1.0"
kvm-bindings = "0.1.1"
kvm-ioctls = { git = "https://github.com/rust-vmm/kvm-ioctls", branch = "master" }
libc = ">=0.2.39"
log = "*"
libc = "0.2.62"
log = "0.4.8"
net_util = { path = "../net_util" }
pci = {path = "../pci"}
qcow = { path = "../qcow" }
@@ -19,6 +24,7 @@ vfio = { path = "../vfio" }
vm-virtio = { path = "../vm-virtio" }
vm-allocator = { path = "../vm-allocator" }
vmm-sys-util = { git = "https://github.com/rust-vmm/vmm-sys-util" }
signal-hook = "0.1.10"
[dependencies.linux-loader]
git = "https://github.com/rust-vmm/linux-loader"

View File

@@ -3,6 +3,8 @@
// SPDX-License-Identifier: Apache-2.0
//
extern crate vm_virtio;
use linux_loader::cmdline::Cmdline;
use net_util::MacAddr;
use std::convert::From;
@@ -11,6 +13,7 @@ use std::net::Ipv4Addr;
use std::path::Path;
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";
@@ -46,6 +49,10 @@ pub enum Error<'a> {
ParseFsNumQueuesParam(std::num::ParseIntError),
/// Failed parsing fs queue size parameter.
ParseFsQueueSizeParam(std::num::ParseIntError),
/// Failed parsing fs dax parameter.
ParseFsDax,
/// Cannot have dax=off along with cache_size parameter.
InvalidCacheSizeWithDaxOff,
/// Failed parsing persitent memory file parameter.
ParsePmemFileParam,
/// Failed parsing size parameter.
@@ -54,6 +61,16 @@ pub enum Error<'a> {
ParseConsoleParam,
/// Both console and serial are tty.
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-net server parameter.
ParseVuNetServerParam(std::num::ParseIntError),
}
pub type Result<'a, T> = result::Result<T, Error<'a>>;
@@ -70,6 +87,7 @@ pub struct VmParams<'a> {
pub serial: &'a str,
pub console: &'a str,
pub devices: Option<Vec<&'a str>>,
pub vhost_user_net: Option<Vec<&'a str>>,
}
fn parse_size(size: &str) -> Result<u64> {
@@ -260,6 +278,7 @@ pub struct FsConfig<'a> {
pub sock: &'a Path,
pub num_queues: usize,
pub queue_size: u16,
pub cache_size: Option<u64>,
}
impl<'a> FsConfig<'a> {
@@ -271,6 +290,8 @@ impl<'a> FsConfig<'a> {
let mut sock: &str = "";
let mut num_queues_str: &str = "";
let mut queue_size_str: &str = "";
let mut dax_str: &str = "";
let mut cache_size_str: &str = "";
for param in params_list.iter() {
if param.starts_with("tag=") {
@@ -281,11 +302,18 @@ impl<'a> FsConfig<'a> {
num_queues_str = &param[11..];
} else if param.starts_with("queue_size=") {
queue_size_str = &param[11..];
} else if param.starts_with("dax=") {
dax_str = &param[4..];
} else if param.starts_with("cache_size=") {
cache_size_str = &param[11..];
}
}
let mut num_queues: usize = 1;
let mut queue_size: u16 = 1024;
let mut dax: bool = true;
// Default cache size set to 8Gib.
let mut cache_size: Option<u64> = Some(0x0002_0000_0000);
if tag.is_empty() {
return Err(Error::ParseFsTagParam);
@@ -303,12 +331,31 @@ impl<'a> FsConfig<'a> {
.parse()
.map_err(Error::ParseFsQueueSizeParam)?;
}
if !dax_str.is_empty() {
match dax_str {
"on" => dax = true,
"off" => dax = false,
_ => return Err(Error::ParseFsDax),
}
}
// Take appropriate decision about cache_size based on DAX being
// enabled or disabled.
if !dax {
if !cache_size_str.is_empty() {
return Err(Error::InvalidCacheSizeWithDaxOff);
}
cache_size = None;
} else if !cache_size_str.is_empty() {
cache_size = Some(parse_size(cache_size_str)?);
}
Ok(FsConfig {
tag,
sock: Path::new(sock),
num_queues,
queue_size,
cache_size,
})
}
}
@@ -350,6 +397,16 @@ pub enum ConsoleOutputMode {
Off,
Tty,
File,
Null,
}
impl ConsoleOutputMode {
pub fn input_enabled(&self) -> bool {
match self {
ConsoleOutputMode::Tty => true,
_ => false,
}
}
}
pub struct ConsoleConfig<'a> {
@@ -374,6 +431,11 @@ impl<'a> ConsoleConfig<'a> {
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)
}
@@ -393,6 +455,64 @@ impl<'a> DeviceConfig<'a> {
}
}
pub struct VhostUserNetConfig<'a> {
pub mac: MacAddr,
pub vu_cfg: VhostUserConfig<'a>,
}
impl<'a> VhostUserNetConfig<'a> {
pub fn parse(vhost_user_net: &'a str) -> Result<Self> {
// Split the parameters based on the comma delimiter
let params_list: Vec<&str> = vhost_user_net.split(',').collect();
let mut mac_str: &str = "";
let mut sock: &str = "";
let mut num_queues_str: &str = "";
let mut queue_size_str: &str = "";
for param in params_list.iter() {
if param.starts_with("mac=") {
mac_str = &param[4..];
} else if param.starts_with("sock=") {
sock = &param[5..];
} else if param.starts_with("num_queues=") {
num_queues_str = &param[11..];
} else if param.starts_with("queue_size=") {
queue_size_str = &param[11..];
}
}
let mut mac: MacAddr = MacAddr::local_random();
let mut num_queues: usize = 2;
let mut queue_size: u16 = 256;
if !mac_str.is_empty() {
mac = MacAddr::parse_str(mac_str).map_err(Error::ParseVuNetMacParam)?;
}
if sock.is_empty() {
return Err(Error::ParseVuNetSockParam);
}
if !num_queues_str.is_empty() {
num_queues = num_queues_str
.parse()
.map_err(Error::ParseVuNetNumQueuesParam)?;
}
if !queue_size_str.is_empty() {
queue_size = queue_size_str
.parse()
.map_err(Error::ParseVuNetQueueSizeParam)?;
}
let vu_cfg = VhostUserConfig {
sock,
num_queues,
queue_size,
};
Ok(VhostUserNetConfig { mac, vu_cfg })
}
}
pub struct VmConfig<'a> {
pub cpus: CpusConfig,
pub memory: MemoryConfig<'a>,
@@ -406,6 +526,7 @@ pub struct VmConfig<'a> {
pub serial: ConsoleConfig<'a>,
pub console: ConsoleConfig<'a>,
pub devices: Option<Vec<DeviceConfig<'a>>>,
pub vhost_user_net: Option<Vec<VhostUserNetConfig<'a>>>,
}
impl<'a> VmConfig<'a> {
@@ -461,6 +582,15 @@ impl<'a> VmConfig<'a> {
devices = Some(device_config_list);
}
let mut vhost_user_net: Option<Vec<VhostUserNetConfig>> = None;
if let Some(vhost_user_net_list) = &vm_params.vhost_user_net {
let mut vhost_user_net_config_list = Vec::new();
for item in vhost_user_net_list.iter() {
vhost_user_net_config_list.push(VhostUserNetConfig::parse(item)?);
}
vhost_user_net = Some(vhost_user_net_config_list);
}
Ok(VmConfig {
cpus: CpusConfig::parse(vm_params.cpus)?,
memory: MemoryConfig::parse(vm_params.memory)?,
@@ -474,6 +604,7 @@ impl<'a> VmConfig<'a> {
serial,
console,
devices,
vhost_user_net,
})
}
}

View File

@@ -15,7 +15,7 @@ pub mod config;
pub mod vm;
use self::config::VmConfig;
use self::vm::Vm;
use self::vm::{ExitBehaviour, Vm};
/// Errors associated with VM management
#[derive(Debug)]
@@ -55,11 +55,15 @@ impl Vmm {
}
pub fn boot_kernel(config: VmConfig) -> Result<()> {
let vmm = Vmm::new()?;
let mut vm = Vm::new(&vmm.kvm, config).map_err(Error::VmNew)?;
loop {
let vmm = Vmm::new()?;
let mut vm = Vm::new(&vmm.kvm, &config).map_err(Error::VmNew)?;
let entry = vm.load_kernel().map_err(Error::LoadKernel)?;
vm.start(entry).map_err(Error::VmStart)?;
let entry = vm.load_kernel().map_err(Error::LoadKernel)?;
if vm.start(entry).map_err(Error::VmStart)? == ExitBehaviour::Shutdown {
break;
}
}
Ok(())
}

File diff suppressed because it is too large Load Diff